Transport
| Aspect | Value |
|---|---|
| Protocol | Model Context Protocol 2025-03-26 |
| Transport | HTTP + JSON-RPC 2.0 (single endpoint, stateless per request) |
| Endpoint | POST https://app.haloagents.ai/api/mcp |
| Auth | OAuth 2.1 with PKCE, Authorization: Bearer halo_at_* |
| Server name | halo-agents |
| Server version | 1.0.0 |
Discovery URLs
A bareGET /api/mcp returns a small descriptor with all the URLs an MCP-aware client needs:
| URL | RFC | What it returns |
|---|---|---|
/.well-known/oauth-protected-resource | RFC 9728 | The resource id and the authorization-server issuer (https://app.haloagents.ai) to talk to. |
/.well-known/oauth-authorization-server | RFC 8414 | The full OAuth metadata: authorization_endpoint, token_endpoint, registration_endpoint, revocation_endpoint, supported scopes, supported PKCE methods. |
/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
| Endpoint | Method | RFC | Notes |
|---|---|---|---|
/api/mcp/oauth/register | POST | RFC 7591 | Dynamic Client Registration. Public; rate-limited at 10 registrations per IP per hour. |
/api/mcp/oauth/authorize | GET | RFC 6749 §4.1 | Authorization-code + PKCE entry point. Bounces unauthenticated users through /signin. |
/api/mcp/oauth/consent | POST | (Halo) | Internal. The consent UI’s form posts here to issue the authorization code. |
/api/mcp/oauth/token | POST | RFC 6749 §3.2 | Token + refresh grants. grant_type must be authorization_code or refresh_token. |
/api/mcp/oauth/revoke | POST | RFC 7009 | Idempotent. Always returns 200. |
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
| Token | Prefix | Lifetime |
|---|---|---|
| Access token | halo_at_ | 1 hour |
| Refresh token | halo_rt_ | Long-lived, rotates on every use |
| Authorization code | halo_ac_ | 5 minutes, single-use |
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 bare names (search_docs, read_doc) for the docs proxy tools. 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:| Code | Meaning |
|---|---|
-32700 | Parse error (invalid JSON body). |
-32600 | Invalid Request (bad JSON-RPC envelope). |
-32601 | Method not found, or unknown tool name on tools/call. |
-32602 | Invalid params (validation failure on tool args). |
-32603 | Internal error. |
-32001 | Unauthorized (no token, bad token, expired token). Returned with HTTP 401 + WWW-Authenticate. |
-32002 | Forbidden (scope denied, destructive blocked, integration disabled, agent disabled, rate limited, feature flag off). |
unauthorized → -32002, validation → -32602, not_found → -32601, anything else → -32603.
Rate limits
| Method | Per-token cap |
|---|---|
tools/list | 60 per minute |
tools/call | 120 per minute |
| Other | 60 per minute |
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:
| Key | Value | Effect |
|---|---|---|
bound_end_user_id | Halo customer id | The tool runs on behalf of that customer (resolved per call against your org). |
bound_email | Email string | Same as above but resolved by email lookup. Use when the client only knows the email. |
Helpful headers
Halo sets these on relevant responses:| Header | When |
|---|---|
WWW-Authenticate: Bearer realm="halo-mcp", resource_metadata="...", as_uri="..." | On 401 responses from /api/mcp. |
Cache-Control: public, max-age=300 | On the .well-known/* discovery documents. |
Cache-Control: no-store | On /oauth/token responses (per RFC 6749 §5.1). |
Retry-After, X-RateLimit-* | On 429 responses. |
A bare-bones client
A minimalcurl walkthrough once you have an access token: