MCP

Your server speaks MCP at /mcp: http://127.0.0.1:3699/mcp for a local zsql serve. Point any MCP client at it and the agent gets the same reads the API has, as tools: list the branches, search the catalogue, plan SQL from a spec. Nothing to deploy or switch on. It is the same server, the same keys, the same limits, and it still never runs a query. With accounts off no key is needed; with accounts on, every tool call carries one.

Connect

claude mcp add --transport http 0sql http://127.0.0.1:3699/mcp \
  --header "Authorization: Bearer zqk_…"

Drop the --header for a server with accounts off.

Set ENABLE_TOOL_SEARCH=false in the environment Claude Code runs in. It then loads the 0sql tool schemas up front instead of behind a search, which in our evaluation cut a question from 4.8 model round trips to 3.4 and raised the pass rate. It applies to every MCP server in the session.

claude.ai reaches your server from outside, so it needs the public URL of a server with accounts on, behind TLS; see beyond the loopback. Then: Customize, Connectors, Add custom connector. Name it, give it the URL https://0sql.example.com/mcp, Continue. When asked how to sign in choose No sign in: the key is the credential. Under Request headers add Authorization with the value Bearer zqk_…, then Add. Enable the connector in a new conversation and its tools appear.

Streamable HTTP, stateless, JSON only. Each POST carries one JSON-RPC message and the header Authorization: Bearer zqk_… (the bare key is accepted too). There are no sessions and no event stream: GET and DELETE answer 405. Protocol revisions 2025-11-25, 2025-06-18 and 2025-03-26 are answered; a client asking for another gets the newest. initialize, ping and tools/list answer without a key; on a server with accounts on, every tool call without one is a 401. The Claude API’s MCP connector takes the key as its authorization_token.

Which key

On a server with accounts on:

KeyReachesPlans as
query key, zqk_the projects and branches it is grantedthe context bound to its grant
personal key, zsk_every project you can reachyou, by email, with no admin bypass

Over MCP the caller never says who it is. The model cannot claim a group, a role or an admin flag: a context among a tool’s arguments is refused, never dropped. On a branch with security policies a query key therefore needs a context bound to its grant, or every call is ContextRequired.

Bind one when you grant the key (you must own the project):

curl -s http://127.0.0.1:3699/account/query-keys/<key id>/grants \
  -H "Authorization: Bearer zsk_…" \
  -H "Content-Type: application/json" \
  -d '{"project": "customer-service", "branch": "main",
       "context": {"email": "agent@example.com",
                   "groups": [{"name": "CC-TMNT", "tags": ["call_center_id:TMNT"]}]}}'

The grant is always what its last call said: granting again without a context clears it, rotation keeps it, revoking drops it. A bound grant wins over the API as well: a request on it that sends any context is refused with ContextBound. Grants with no bound context behave as before, so applications that act for many users are untouched. Shape of a context: security context.

Tools

Every tool but list_projects takes a project uid and a branch; the branch defaults to the project’s production branch. sql, explain and explore take a query spec, never the shorthand, and the spec’s JSON schema travels in the tool definition. No tool deploys, changes or runs anything.

ToolReturns
list_projectsthe branches this key may read, with the size of each model
fieldswith no q, the whole catalogue of a branch as one text; with q, the fields containing or near the phrase, best first
tablesthe tables of a branch: physical name, datasource, cost, fields
exploregiven a spec, the dimensions and measures that can still be added
sqlthe SQL for a spec, with the datasource and adapter it is for
explainthe same, with timings, phases and the node graph

A result carries a text block with the JSON and structuredContent with the same value. A refusal from the planner comes back as the tool’s own result with isError, carrying the API’s class and message, so the model reads why (an unknown field, with the nearest ones) rather than a transport failure.

initialize returns the server’s instructions and a catalogue of every branch the key may read, under 48 KB. A client that keeps them in its prompt can answer a question with one sql call. Claude Code keeps about 2,000 characters of a server’s instructions, so there the model fetches the catalogue with fields first.

Limits

One HTTP request is one tool call. With accounts on it counts against the key’s plan allowance, and fields and explore also take an explore token; over the limit, 429 or 503 as on the API, so a client’s usual back-off applies. 30 seconds per call, one megabyte per body. Specs are planned in memory and never stored; an initialize writes one journal line naming the key and the client it declared.

Try it

Serve the demo’s customer-service model (or any project of yours), connect it, and ask: which call centers have the longest average handle time? The agent calls sql with a spec, and you get the statement to run. The chat dashboard tutorial builds the same loop into an application with the API.