Transport
Discovery URLs
A bareGET /api/mcp returns a small descriptor with all the URLs an MCP-aware client needs:
A 401 from
/api/mcp carries a WWW-Authenticate: Bearer realm="halo-mcp", resource_metadata="...", as_uri="..." header pointing at the same documents, so a client doesn’t need to know the URLs ahead of time.
OAuth 2.1 endpoints
Supported grants:
authorization_code, refresh_token. Supported PKCE methods: S256 only. Supported response types: code. Supported token endpoint auth methods: none (public clients), client_secret_basic, client_secret_post.
Token formats
All three are 256-bit random values, base64url-encoded after the prefix. Halo persists only the SHA-256 hash; on every request the bearer is hashed and looked up. There is no JWT, no signature verification, no
jwks_uri.
JSON-RPC methods
initialize
Capabilities handshake. Required as the first call after a fresh OAuth.
Request:
ping
Liveness check. Returns an empty object on success.
tools/list
Returns the tool descriptors the token’s scopes (and the org’s connections + agent toggles) unlock.
Request:
<integration>.<action> for native integrations, mcp.<slug>.<tool> for connected external MCP servers, and prefixed bare names for the built-in tool families: search_docs / read_doc (docs proxy), setup_* (setup helpers), data_* (workspace-data reads plus the data_update_user / data_update_company writes), inbox_* (ticket replies, notes, triage, outreach), and automation_* (campaign create / edit / preview / activate). The annotations.destructiveHint: true flag is set on tools that mutate external state.
tools/call
Invoke a tool. Wrap arguments under params.arguments; pass session metadata under params._meta.
Request:
text content block. Structured tool output is JSON-stringified (with 2-space indent) inside that block.
Error codes
Standard JSON-RPC plus a couple of Halo-specific codes:
Tool-call failures preserve the original error code where possible:
unauthorized → -32002, validation → -32602, not_found → -32601, anything else → -32603.
Rate limits
Caps are per-token, on top of the IP-based dashboard limit. Hitting one returns HTTP 429 with
Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers. Per-action and per-org caps still apply on top.
DCR is rate-limited at 10 registrations per IP per hour.
Session metadata (_meta)
The MCP _meta envelope on tools/call lets clients pass per-call context that isn’t part of the tool’s input schema:
The binding is re-validated server-side on every call. A stale id, a forged id, or an id from a foreign tenant is rejected.
Helpful headers
Halo sets these on relevant responses:A bare-bones client
A minimalcurl walkthrough once you have an access token: