> ## Documentation Index
> Fetch the complete documentation index at: https://docs.haloagents.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Connecting Clients

> Install Halo as an MCP server in Claude Desktop, Cursor, VS Code, or any MCP-aware client. OAuth setup is fully automatic on first connect.

## The endpoint

Point any MCP-aware client at:

```
https://app.haloagents.ai/api/mcp
```

That URL is all you need. Authentication, discovery, and consent are handled by the standard OAuth 2.1 flow that every modern MCP client implements out of the box, so the first time a client hits the endpoint Halo walks it through registration and consent automatically.

<Note>
  The MCP server is enabled by default for every org. If you get a 403 from the URL it means MCP has been explicitly disabled on your workspace. Ask your Halo admin to set the `mcp_server` feature flag back to `true` for your team.
</Note>

## What happens on first connect

The first time a client points at `/api/mcp`, this is the flow it walks (you only see steps 3 and 4 as a user):

<Steps>
  <Step title="Discovery">
    The client POSTs to `/api/mcp`, gets a 401 with `WWW-Authenticate: Bearer resource_metadata="..."`, and follows the link to discover Halo's authorization server.
  </Step>

  <Step title="Dynamic registration">
    The client registers itself via [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) at `/api/mcp/oauth/register` and receives a fresh `client_id`. No manual app setup is required on Halo's side.
  </Step>

  <Step title="Sign in to Halo">
    The client opens `/api/mcp/oauth/authorize` in your browser. If you're not already signed in, you bounce through `/signin` and back.
  </Step>

  <Step title="Approve the request">
    Halo shows the consent page (`/mcp/authorize`) listing the client's name, the org you're authorising, and the scopes it requested. You can deselect any scope before approving.
  </Step>

  <Step title="Token exchange">
    The client receives an authorization code, swaps it at `/api/mcp/oauth/token` (PKCE-verified), and stores the `access_token` + `refresh_token` pair. Subsequent JSON-RPC calls carry `Authorization: Bearer halo_at_...`.
  </Step>
</Steps>

Access tokens last 1 hour. Refresh tokens are long-lived but rotate on every use, so a stolen refresh token only works until the legitimate client refreshes next (the rotation revokes the entire derived chain).

## Installation by client

### Claude Desktop

Open `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or the equivalent on your platform and add Halo under `mcpServers`:

```json theme={null}
{
  "mcpServers": {
    "halo-agents": {
      "url": "https://app.haloagents.ai/api/mcp"
    }
  }
}
```

Restart Claude Desktop. The first time you reference a Halo tool in a chat, Claude will pop a browser window for the OAuth flow.

### Cursor

Open **Cursor Settings > MCP > Add new MCP server**. Configure:

| Field    | Value                               |
| -------- | ----------------------------------- |
| **Name** | `halo-agents`                       |
| **Type** | `HTTP`                              |
| **URL**  | `https://app.haloagents.ai/api/mcp` |

Save. Cursor will prompt for OAuth on first use.

### VS Code (Continue / GitHub Copilot Chat)

Add Halo under the MCP servers section of your AI extension's settings (the exact key varies by extension):

```json theme={null}
{
  "mcp.servers": {
    "halo-agents": {
      "url": "https://app.haloagents.ai/api/mcp"
    }
  }
}
```

Reload the window. The OAuth flow runs in your default browser.

### Custom clients

Any client that implements the [MCP HTTP transport](https://modelcontextprotocol.io/specification/server/transports) and OAuth 2.1 with PKCE can connect to Halo. Point at `/api/mcp` and discovery + dynamic client registration handle the rest. See [Protocol Reference](/mcp/protocol) for the full endpoint list.

## What the client sees

After the OAuth dance, the client calls `tools/list` to enumerate available tools. The set you see depends on three things:

1. **Your org's connected integrations** (HubSpot, Slack, Stripe, etc. in **Integrations**)
2. **The scopes you approved** at consent time
3. **Whether the token is bound to a specific agent** (defaults to your org's Ask AI agent)

A token only sees what its scopes unlock: without `docs:read` there is no `search_docs` / `read_doc`; without `inbox:write` there are no ticket-reply or outreach tools; without `automations:write` there are no campaign tools; without `sales:read` / `cs:read` / `workflows:read` there are no Sales, CS, or Workflow tools. The Sales, CS, and Workflow groups also require the org to have the matching suite (`sales_suite` / `cs_suite`) enabled — the tools stay hidden otherwise, scope or no scope. The protection is enforced at both `tools/list` (the tool is hidden) and `tools/call` (the call is rejected with an audited refusal), so dropping a scope is safe.

On the consent page, permissions are grouped with all **Read** ones first and all **Write** ones (`inbox:write`, `automations:write`, `data:write`, `sales:write`, `cs:write`, `workflows:write`, `actions:destructive`) below, and each row carries a **Read** / **Write** badge. Every permission is checked by default; untick anything you don't want to grant. Even when a write scope is granted, each write re-checks that you (the person who authorized the token) hold the matching dashboard permission, so the client can never do more than you can. See [Scopes & Permissions](/mcp/scopes-and-permissions) for the full breakdown.

## Upgrading an existing connection to write access

Scopes are frozen into the token at consent time, and token refresh can only narrow them, never widen them. So a connection made before write access shipped will keep working read-only but will not gain the write tools on its own. To add them:

1. Sign out of (or disconnect) the Halo MCP in your client, then reconnect it. This re-runs the OAuth flow.
2. On the Halo consent page, leave the write permissions checked (`inbox:write`, `automations:write`, `data:write`, and/or `actions:destructive`), or untick any you don't want.
3. Approve. The new token carries the write scopes and the write tools appear in `tools/list`.

The consent page always offers the full current scope menu, even for clients that registered before a scope existed, so a plain re-auth is enough — no need to delete and re-add the server entry.

## Pinning a customer to a session

When an operator uses an MCP client to research or act on behalf of a specific customer, they can pin the customer for the conversation. Halo reads the binding off the JSON-RPC `_meta` envelope:

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "tools/call",
  "params": {
    "name": "hubspot.create_deal",
    "arguments": { "name": "ACME renewal" },
    "_meta": {
      "bound_end_user_id": "u_abc123"
    }
  }
}
```

You can also pin by email with `_meta.bound_email`. Halo re-resolves the binding on every call against your org's customer table, so a stale or forged id can never bind to a foreign tenant.

How the pin reaches the wire depends on the client. Most clients let the user select a customer in the UI and forward the selection automatically; custom clients should set `_meta.bound_end_user_id` themselves.

## Revoking access

Today there's no dashboard view of connected MCP clients. Revoke by token instead:

```bash theme={null}
curl -X POST https://app.haloagents.ai/api/mcp/oauth/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "token=halo_at_..." \
  --data-urlencode "client_id=mcp_client_..."
```

The endpoint follows [RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009): pass either an access or a refresh token under `token=`, idempotent, always returns 200. Confidential clients also pass `client_secret` (or use HTTP Basic auth); public clients only need `client_id`.

Revoking a **refresh token** revokes the entire derived chain (every access and refresh token issued from the same authorization code), which is the right move if you suspect a compromise. Revoking a single **access token** just kills that token; the next refresh issues a new one.

Most MCP clients have a "Sign out" or "Disconnect" action that calls this endpoint for you. If you're an admin and don't have the token in hand, contact support and we'll revoke the chain server-side.

## Rate limits

Per-token caps sit on top of the IP-based dashboard limit:

| Method                       | Cap            |
| ---------------------------- | -------------- |
| `tools/list`                 | 60 per minute  |
| `tools/call`                 | 120 per minute |
| Other (`initialize`, `ping`) | 60 per minute  |

Hitting a cap returns 429 with `Retry-After`. Per-action and per-org caps still apply on top (a tool whose underlying integration has a 60/min limit will still hit that limit even if the MCP cap allows more).
