Portal
Documentation: all sections

MCP Gateway

The MCP server at POST /v1/mcp exposes Sable as tools. The MCP Gateway is the other direction: you register someone else's MCP server, and Sable hands you a proxy URL that governs, meters, and receipts every tool call your agent makes through it.

It is a tool firewall with a ledger. An agent pointed at the proxy URL can only call the tools you allowed, spends against a budget you set, and leaves a signed record of every call it made.

What it does, and what it does not

Every tools/call through the proxy is:

  1. Allowlist-checked before the upstream is dialed. A refused tool never reaches the third-party server — the refusal is a 403 with policy_denied, decided at the gateway.
  2. Metered. One mcp_call usage event per call, debited at the server's per-call price. It counts against the key's spend cap, its circuit breaker, and any mandate it was minted under, because it goes through the same billing path as everything else.
  3. Receipted. A signed, metadata-only receipt of kind mcp_call carrying the tool name, a sha256 fingerprint of the arguments, a fingerprint of the result, the latency, and the policy stamp — verifiable through the public POST /v1/receipts/verify.

What it is not: Sable governs and receipts the call. It does not sandbox the upstream server, cannot see what that server does with the arguments once they arrive, and cannot verify that its result is correct. The proof it gives you is "this tool was called with these arguments, under these rules, and cost this much" — not "the tool behaved."

Register a server

Registration is session-authed (it stores a credential). The auth_header you give is the header the upstream wants, and is AES-GCM sealed at rest — Sable opens it only at dial time, never logs it, and destroys it when you deregister.

curl https://api.buildsable.com/v1/mcp-servers \
-H "authorization: Bearer $SABLE_SESSION" \
-H 'content-type: application/json' \
-d '{
  "name": "github-tools",
  "url": "https://example.com/mcp",
  "auth_header": "Bearer ghp_your_upstream_token",
  "allowed_tools": ["search_issues", "read_file"],
  "price_micro_usd_per_call": 200
}'

The response carries a proxy_url:

{
  "id": "mcp_9f2c…",
  "name": "github-tools",
  "allowed_tools": ["search_issues", "read_file"],
  "price_micro_usd_per_call": 200,
  "enabled": true,
  "proxy_url": "https://api.buildsable.com/v1/mcp/servers/mcp_9f2c…"
}

Omit allowed_tools and every tool the upstream lists is permitted — the allowlist is the point of the feature, so set one. price_micro_usd_per_call defaults to the deployment's SABLE_MCP_PROXY_MICRO_USD_PER_CALL (100 µ$ = $0.0001) and can only be raised above it, so an expensive upstream tool shows up honestly in budgets.

Check it works before pointing an agent at it — POST /v1/mcp-servers/:id/test runs initialize + tools/list against the upstream and returns the tool names it advertises, plus which of them your allowlist permits.

Connect an agent

The proxy path is API-key authed, not session-authed. That is deliberate: it is what puts every call under a specific sk-sable_ key's budget, policy, circuit breaker, and the account kill switch.

claude mcp add --transport http sable-github \
https://api.buildsable.com/v1/mcp/servers/mcp_9f2c… \
--header "authorization: Bearer $SABLE_API_KEY"

tools/list through the proxy is filtered to the effective allowlist, so an agent never even sees a tool it may not call.

Two allowlists, intersected

A call is permitted only if the tool is in both:

Server listKey policy allowed_toolsEffective
not setnot setevery tool the upstream lists
[a, b]not set[a, b]
not set[b, c][b, c]
[a, b][b, c][b]
[a][c]nothing — every call refused

A policy with no allowed_tools rule (only a token ceiling, say) contributes nothing: a policy about token limits must not silently become an empty tool list.

The receipt

{
  "v": 1,
  "kind": "mcp_call",
  "request_id": "mcp_5c1b…",
  "server_id": "mcp_9f2c…",
  "tool": "search_issues",
  "unit": "calls",
  "quantity": 1,
  "status": "ok",
  "is_error": false,
  "latency_ms": 412,
  "cost_micro_usd": 200,
  "actions": {
    "count": 1,
    "root": "3f9a…",
    "calls": [{ "tool": "search_issues", "args_fp": "8c21…", "args_len": 48 }]
  },
  "result_fp": "a70e…",
  "result_len": 1264,
  "policy": { "id": "pol_…", "sha256": "…" },
  "node_id": "gateway",
  "created_at": "2026-09-04T10:31:02Z"
}

It comes back three ways: the x-sable-receipt, x-sable-receipt-sig and x-sable-receipt-signer response headers, and inside the JSON-RPC result at result._meta.sable_receipt.

actions is the same action attestation shape the chat path uses — the tool name plus a sha256 prefix of name|arguments. Anyone holding the same arguments recomputes the fingerprint; nobody holding only the receipt can recover them. result_fp does the same for what came back. Tag a call into an agent run by putting sable_run_id in the request's params._meta; Sable strips it before forwarding.

Metering and status

status on the usage event and the receipt is one of:

StatusMeaningBilled
okThe upstream returned a result.yes
tool_errorThe upstream returned a result with isError: true — the tool ran and failed.yes
upstream_errorThe upstream returned a JSON-RPC error object.yes
errorSable could not reach the upstream, it timed out, or its answer was unusable.no

The first three are billed because the upstream did the work; a transport failure is recorded at zero cost so the attempt is visible without charging for it.

Methods

MethodBehavior
tools/callAllowlist-checked, metered, receipted.
tools/listForwarded unmetered; the result is filtered to the effective allowlist.
initialize, ping, notifications/*, prompts/list, prompts/get, resources/list, resources/read, resources/templates/listForwarded unmetered.
everything else (sampling/*, roots/*, elicitation/*, logging/*, completion/*)Refused with JSON-RPC -32601.

The proxy governs tool calls; forwarding the rest blind would be a different, ungoverned product.

Limits and honest caveats

Endpoints

MethodPathAuth
POST/v1/mcp-serverssession
GET/v1/mcp-serverssession
PATCH/v1/mcp-servers/:idsession
DELETE/v1/mcp-servers/:idsession
POST/v1/mcp-servers/:id/testsession
POST/v1/mcp/servers/:idsk-sable_ key

Manage them in the portal at Portal → MCP Gateway.