The endpoint
Point any MCP-aware client at: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.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):
1
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.2
Dynamic registration
The client registers itself via RFC 7591 at
/api/mcp/oauth/register and receives a fresh client_id. No manual app setup is required on Halo’s side.3
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.4
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.5
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_....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:
Cursor
Open Cursor Settings > MCP > Add new MCP server. Configure:
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):Custom clients
Any client that implements the MCP HTTP transport 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 for the full endpoint list.
What the client sees
After the OAuth dance, the client callstools/list to enumerate available tools. The set you see depends on three things:
- Your org’s connected integrations (HubSpot, Slack, Stripe, etc. in Integrations)
- The scopes you approved at consent time
- Whether the token is bound to a specific agent (defaults to your org’s Ask AI agent)
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 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:- Sign out of (or disconnect) the Halo MCP in your client, then reconnect it. This re-runs the OAuth flow.
- On the Halo consent page, leave the write permissions checked (
inbox:write,automations:write,data:write, and/oractions:destructive), or untick any you don’t want. - Approve. The new token carries the write scopes and the write tools appear in
tools/list.
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:
_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: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:
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).