# Run an AI assistant

Connect Claude, ChatGPT, or any other assistant to BrokerPlus through our MCP server, and ask it about
your deals, contacts, and tasks in plain language. The assistant can also do routine work: add a
note, create a task, move a deal. A tool that reaches a client acts at once, so your assistant asks
you before it calls one.

MCP is the Model Context Protocol, the open standard that assistants use to call outside tools. The
server is in beta, like the rest of the API.

The server covers the whole API: every endpoint in the [API reference](/reference) is a tool. Each
tool is one documented endpoint, called with your credentials. An assistant can't do anything that
the same endpoint can't do for the same broker, and the server stores no data, no keys, and no logic
of its own.

```
https://mcp.mos.getbrokerplus.ca/mcp
```

## Connect Claude on the web, ChatGPT, or another connector

Apps that add a server as a connector sign in to BrokerPlus. You don't need an API key.

1. In the app, add a custom connector with the URL `https://mcp.mos.getbrokerplus.ca/mcp`. In
   Claude, that is **Settings > Connectors**.
2. Select **Connect**. The app opens a BrokerPlus page that names it.
3. Pick your team, and **Read only** or **Read and write**. Then select **Connect**.
4. BrokerPlus sends you back to the app, and the tools are ready.

Only an admin of your brokerage can connect an app. The connection shows up with your keys in
**Settings > Developer**, marked **Connected App**. Revoke it there to disconnect the app.

## Sign in instead of a key

This section is for people who build their own MCP client. The server uses OAuth 2.1.

A request with no credentials gets `401`. Its `WWW-Authenticate` header points at
`https://mcp.mos.getbrokerplus.ca/.well-known/oauth-protected-resource/mcp`, which names the BrokerPlus
authorization server. The client discovers the rest from there.

- PKCE with `S256` only.
- Client ID Metadata Documents, and dynamic client registration for clients that still use it.
- Client authentication at the token endpoint: `none` for a public client, or `private_key_jwt` with
  an `RS256` or `ES256` signed assertion. A client that names `private_key_jwt` in its metadata
  document must publish its keys at `jwks_uri`. It sends a `client_assertion` on every token request,
  unless its `token_endpoint_auth_methods_supported` also lists `none`. An assertion that is sent is
  always checked. Dynamic registration makes public clients only.
- Scopes: `read` and `write`.
- An access token lasts one hour. The refresh token rotates on every use, so a connection in use
  never expires. One left idle for 60 days signs in again.

A token carries the same authority as an API key created for the same team, with the same read-only
choice, and it counts toward the brokerage's ten live keys. See
[Authentication](/authentication).

## Connect with an API key

Claude Code, Claude Desktop, and most local clients send a key instead. Create one for the assistant
in **Settings > Developer**, name it after the assistant, and set it to **Read only** if the assistant
should never change anything. See [Authentication](/authentication) for key limits such as
`no_sending`.

**Claude Code.** Run this in a terminal, then type `/mcp` in Claude Code to check that `brokerplus`
is connected:

```bash
claude mcp add --transport http brokerplus https://mcp.mos.getbrokerplus.ca/mcp \
  --header "Authorization: Bearer $BROKERPLUS_API_KEY"
```

**Claude Desktop.** Claude Desktop reaches the server through the `mcp-remote` bridge, which needs
Node.js. Go to **Settings > Developer**, select **Edit Config**, and add the server to
`claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "brokerplus": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://mcp.mos.getbrokerplus.ca/mcp",
        "--header",
        "Authorization:${BROKERPLUS_AUTH}"
      ],
      "env": { "BROKERPLUS_AUTH": "Bearer bp_live_..." }
    }
  }
}
```

Replace `bp_live_...` with your key and restart Claude Desktop. There is no space after the colon in
the `--header` value, because Claude Desktop on Windows splits arguments at spaces. The space lives in
the `env` value instead. This file holds your key in plain text, so keep it on your own computer.

**Any other client.** Speak Streamable HTTP to the URL above, and send the key as a bearer token on
every request, the same as for the API.

The server checks the key by calling `GET /v1/me`. If the API refuses the key, the server answers
`401`. The server hides every tool that the key's limits refuse: a read-only key sees only the tools
that read, and a key with the `no_sending` limit sees no tool that reaches a client.

## Things to ask

- "Which of my deals close in the next 14 days, and which conditions are still open on each one?"
- "List the mortgages in my book that mature in the next 120 days, with the savings to move each one."
- "Summarize the last five emails from Jordan Client, and draft a reply for me to review."
- "Make a task for each deal in the Submitted stage that hasn't moved in seven days."
- "Which leads came in this week, and which of them have no follow-up date?"

## What the tools are

There is one tool for each operation in the API, named after the operation with a `brokerplus_`
prefix. `GET /contacts/{id}/statistics` is `brokerplus_get_contact_statistics`, and `POST /tasks` is
`brokerplus_create_task`. The [API reference](/reference) names every operation.

| Area                 | Example tools                                                                                              |
| -------------------- | ---------------------------------------------------------------------------------------------------------- |
| Who and what         | `brokerplus_get_me`, `brokerplus_list_users`, `brokerplus_list_pipelines`                                  |
| Contacts and leads   | `brokerplus_list_contacts`, `brokerplus_create_contact_note`, `brokerplus_create_lead`, `brokerplus_convert_lead` |
| Deals                | `brokerplus_list_deals`, `brokerplus_update_deal`, `brokerplus_list_deal_conditions`, `brokerplus_raise_deal_flag` |
| The mortgage         | `brokerplus_get_deal_application`, `brokerplus_list_deal_applicants`, `brokerplus_get_deal_financials`     |
| Renewals             | `brokerplus_list_opportunities`, `brokerplus_create_opportunity`                                           |
| Calendar, mail, calls | `brokerplus_list_appointments`, `brokerplus_book_meeting`, `brokerplus_list_communications`, `brokerplus_log_call` |
| Tasks                | `brokerplus_list_tasks`, `brokerplus_create_task`, `brokerplus_complete_task`, `brokerplus_bulk_tasks`     |
| Messages to clients  | `brokerplus_create_contact_draft`, `brokerplus_send_contact_message`, `brokerplus_request_deal_documents`  |
| Follow-up            | `brokerplus_create_automation`, `brokerplus_turn_on_automation`, `brokerplus_create_template`, `brokerplus_schedule_campaign` |
| What the broker sees | `brokerplus_list_suggestions`, `brokerplus_list_notifications`, `brokerplus_list_deal_activity`           |

A tool's inputs are one flat object: the path id, the query parameters, and the request body sit side
by side. The tool for `POST /deals/{id}/notes` takes `id` and `body` together. Names and types match
the endpoint, because both come from the same contract.

Every tool that makes a `POST` takes an optional `idempotency_key`, sent as the `Idempotency-Key`
header. See [Idempotency](/idempotency).

The tool list is short, so that assistants can load all of it. Each description stops after its
first sentences, and each field's description after two. A tool's `outputSchema` names only `data`,
plus `pagination` for a list. To read the full contract of one tool, with every field of the
response, call `brokerplus_describe_operation` with its operation id.

### Results

A result has two parts:

- `structuredContent` is the `data` of the response, plus `pagination` for a list.
- The text starts with one line that names what came back, by name and id, followed by the JSON for
  assistants that read only text.

For example, `brokerplus_list_lead_stages` returns this text:

```
List lead stages: 2 of 2 (page 1 of 1).
- New (cmslst1...)
- Contacted (cmslst2...)

{"data":[{"id":"cmslst1...","name":"New","position":0}, ...],"pagination":{...}}
```

An error comes back as text marked as an error, with the API's status, code, message, and `details`.
The assistant can read `DUPLICATE_CONTACT` and the matching contacts, or `VALIDATION_ERROR` and the
failing fields, and fix its call. See [Errors](/errors).

### Hints for your assistant

Each tool carries MCP annotations. Claude, ChatGPT, and other clients use them to decide which calls
to confirm with you, so the annotations, not a list of blocked tools, keep an assistant from acting
without you.

| Annotation        | `true` for                                                                                                  |
| ----------------- | ----------------------------------------------------------------------------------------------------------- |
| `readOnlyHint`    | Every tool that reads. It changes nothing.                                                                  |
| `destructiveHint` | Every update, because it replaces the values you send, and every delete. Also every tool that reaches people outside the brokerage, and the writes that can't be undone or that replace a whole set: merging or archiving a contact, approving an intake, revoking a consent, resetting rates, reordering, and batch writes. |
| `idempotentHint`  | Reads, updates, and deletes, and the writes that say a second call changes nothing. A create is idempotent only with an `idempotency_key`. |
| `openWorldHint`   | Every tool that [reaches a client](/reaching-clients), such as `brokerplus_send_contact_message`, `brokerplus_schedule_campaign`, and `brokerplus_cancel_booking`. Also turning an automation or a lead form's reply on, changes to what clients see in their hub, creating an intake, and webhook endpoints. Each one reaches people outside the brokerage as soon as it runs, and its description says so. |

## Find and call any operation

Three more tools find and call any operation by its id. Use them when an assistant caps how many
tools it loads.

| Tool                            | What it does                                                                                     |
| ------------------------------- | ------------------------------------------------------------------------------------------------ |
| `brokerplus_search_operations`  | Finds operations by the words in `query`, and returns each `operation_id`, method, path, and summary. |
| `brokerplus_describe_operation` | Returns the inputs and the output schema of one `operation_id`.                                  |
| `brokerplus_call_operation`     | Makes the operation's request, with its inputs in `arguments`.                                   |

`brokerplus_call_operation` makes the same `/v1` request as the operation's own tool, with the same
credentials, permissions, and key limits. The three tools find and call only what the key's limits
allow.

## Keep an assistant in bounds

**A tool that reaches a client acts at once, as the broker.** An assistant can send a message,
request documents, book or cancel a meeting, or turn on an automation, and each of those goes out
when it runs. Its hints tell your assistant to ask you first.

**A delete is a tool too.** Its hints mark it destructive, and its description says what goes with
it and whether you can undo it.

To keep an assistant away from a kind of action, give its key a limit. The limits work the same way
for the server as for the API. See [Authentication](/authentication).

| Limit          | What the assistant can't do                                                         |
| -------------- | ----------------------------------------------------------------------------------- |
| `read_only`    | Anything that writes. It sees only the tools that read.                              |
| `no_sending`   | Anything that [reaches a client](/reaching-clients). It doesn't see those tools.      |
| `no_documents` | Read or add documents and files.                                                      |
| `leads_only`   | Anything but leads.                                                                   |

A tool that returns a signed link to a file returns the link, as the API does. The link works for a
short time, and anybody who has it can open the file, so a key that must not hand out files needs
the `no_documents` limit.

## Before you connect an assistant

- **The assistant acts as the broker.** It sees what the broker sees and can make every write the
  broker can make, including deletes, and it can reach the broker's clients unless its key has the
  `no_sending` limit. Read each change before you confirm it. The BrokerPlus audit log records every
  write under the broker's name.
- **Give each assistant its own key**, named after it, so you can revoke it without touching other
  integrations.
- **Mind the rate limit.** It is per key, and a question that reads the whole book can reach it. Ask
  about a stage, a date range, or a broker instead. A list tool returns 50 records a page, and the
  assistant asks for the next page when it needs more.
- **The server keeps nothing.** It logs no request bodies and stores no responses.
