Reference

MCP endpoint

The URL an MCP client calls, how it authenticates, the methods it can use, request and response examples, and every status code and error it can get back.

Each project has one MCP endpoint. Any client that speaks MCP over Streamable HTTP (Claude Code, Claude Desktop, Cursor, VS Code, the MCP Inspector, or your own code) calls it to list and run the tools, resources and prompts your agents registered. This page is the contract for that endpoint. To set up a specific client, see Connect a client.

The endpoint

POST https://api.spacefix.io/mcp/<project-id>

<project-id> is the project's id, shown on the project page next to MCP endpoint. The id is not a secret, and the endpoint grants nothing without a key.

The endpoint is stateless. There are no sessions, so any request can be sent on its own, and a client does not need to call initialize first. initialize returns an Mcp-Session-Id, but it only carries your client's name and version so the Logs page can show which client made each call; the gateway never requires or checks it. Only POST works. GET and DELETE are refused with 405.

Authentication

Send a client key (or a full key) in the Authorization header:

http
POST /mcp/8b43e8b5-1c7c-411f-a2d8-bbac5199432d HTTP/1.1
Authorization: Bearer sf_live_...
Content-Type: application/json
Accept: application/json, text/event-stream
SituationResponse
No Authorization header401 {"error":"Missing API key."} with WWW-Authenticate: Bearer realm="mcp"
Key unknown, revoked or expired401 {"error":"Invalid API key."} with WWW-Authenticate: Bearer realm="mcp", error="invalid_token", error_description="Invalid API key."
An agent key403 {"error":"This is an agent key. MCP clients connect with a client key."}
A key that belongs to another project403 {"error":"Forbidden - agent identity mismatch."}
Too many rejected keys from one address429 {"error":"Too many failed authentication attempts."} with Retry-After

A key can carry a tool allow-list and can be read-only. Tools outside the allow-list, and for a read-only key every tool that does not read (no readOnlyHint: true and not marked Read by you), are absent from tools/list and answer as if they did not exist. Resources and prompts are always listed. A project's Client access set to Read only applies the same limit to every key and token. Revoking a key takes effect on the next request.

If OAuth is turned on for your deployment, the endpoint accepts OAuth access tokens instead of keys: by default mcp:write (or mcp:invoke) reads and writes, and mcp:read alone is read-only. See Security.

Headers

HeaderRequiredMeaning
Authorization: Bearer <key>YesYour client or full key
Content-Type: application/jsonYes, on POSTOtherwise 415
Accept: application/json, text/event-streamYesBoth types must be listed, otherwise 406
traceparent, tracestateNoW3C trace context. If valid, your agent's span joins your trace. See Trace headers
MCP-Protocol-VersionNoA protocol revision. Unsupported ones are refused with 400

Protocol revisions accepted: 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05 and 2024-10-07. With no initialize the revision defaults to 2025-03-26.

The endpoint sets no CORS headers and answers no preflight, so a web page in a browser cannot call it. Server-side and desktop MCP clients are unaffected.

Methods

MethodBehaviour
initializeReturns the negotiated protocolVersion, capabilities (one entry for each of tools, resources and prompts that your agents registered, each with listChanged) and serverInfo (name is the project id, version is 1.0.0)
notifications/initialized and any other notification202 Accepted with an empty body
ping{"result":{}}
tools/listEvery tool visible to this key. There is no pagination: the whole list comes back. Tools outside the key's allow-list, and tools that do not read when the caller is read-only, are left out. With nothing to show the list is empty
tools/callRuns a tool on one of your agents
resources/listEvery registered resource: uri, name, description, mimeType. An empty list if none are registered
resources/readReads a resource from an agent
resources/templates/list{"resourceTemplates":[]}. Resource templates are not supported
prompts/listEvery registered prompt: name, title, description, arguments. An empty list if none are registered
prompts/getRenders a prompt on an agent
Anything else (logging/setLevel, completion/complete, resources/subscribe ...)JSON-RPC error -32601 Method not found

A batch (a JSON array of requests) is accepted. The answers arrive as one event: message per request in the same response.

listChanged is advertised but never sent: there is no server-initiated stream, so a client that wants a fresh list calls tools/list again.

Without a connected agent there is nothing to list, so every method, including initialize and ping, fails with 503 and -32001 (see Errors).

Examples

Set these once:

bash
U=https://api.spacefix.io/mcp/<project-id>
m() { curl -s -X POST "$U" \
  -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' "$@"; }

Initialize

bash
m -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
event: message
data: {"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":true},"resources":{"listChanged":true},"prompts":{"listChanged":true}},"serverInfo":{"name":"8b43e8b5-1c7c-411f-a2d8-bbac5199432d","version":"1.0.0"}},"jsonrpc":"2.0","id":1}

List tools

bash
m -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
event: message
data: {"result":{"tools":[{"name":"get_order","description":"Look up a single order by its id.","inputSchema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]}}]},"jsonrpc":"2.0","id":2}

tools/list returns the schema your agent registered, unchanged.

Call a tool

bash
m -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_order","arguments":{"id":"1"}}}'
event: message
data: {"result":{"content":[{"type":"text","text":"{\"id\":\"1\",\"item\":\"Widget\",\"total\":4200}"}]},"jsonrpc":"2.0","id":3}

Read a resource and render a prompt

bash
m -d '{"jsonrpc":"2.0","id":4,"method":"resources/read","params":{"uri":"orders://all"}}'
m -d '{"jsonrpc":"2.0","id":5,"method":"prompts/get","params":{"name":"summarise_order","arguments":{"id":"1"}}}'
event: message
data: {"result":{"contents":[{"uri":"orders://all","mimeType":"application/json","text":"[{\"id\":\"1\",\"item\":\"Widget\",\"total\":4200}]"}]},"jsonrpc":"2.0","id":4}

event: message
data: {"result":{"description":"Summarise an order.","messages":[{"role":"user","content":{"type":"text","text":"Summarise this order politely: Widget"}}]},"jsonrpc":"2.0","id":5}

A batch

bash
m -d '[{"jsonrpc":"2.0","id":20,"method":"ping"},{"jsonrpc":"2.0","id":21,"method":"resources/templates/list"}]'
event: message
data: {"result":{},"jsonrpc":"2.0","id":20}

event: message
data: {"result":{"resourceTemplates":[]},"jsonrpc":"2.0","id":21}

Responses

CaseResponse
A request that has an answer200, Content-Type: text/event-stream. The body is one event: message / data: <json-rpc response> frame per request, then the stream ends. While a slow call is running the connection sends a : keepalive comment every 15 seconds
A notification202 Accepted, empty body
A refusal by the gateway's own checksContent-Type: application/json, with the status listed under Errors

The answer to a tool call depends on what your handler returned:

Your handler returnedThe client receives
A string{"content":[{"type":"text","text":"<the string>"}]}
An object with a content array (already in MCP shape)Passed through untouched, including structuredContent and isError
Anything else, including nothing{"content":[{"type":"text","text":"<the value as JSON>"}]} (null for nothing)
An error{"content":[{"type":"text","text":"<your error message>"}],"isError":true}

A tool that fails is not a protocol error: it is a successful response with isError: true, so the model can read the message and recover. Resource and prompt failures are JSON-RPC errors, because MCP has no isError form for them.

A resource answer is {"contents":[{"uri","mimeType","text"}]}. A string result is the text, and anything else is JSON-encoded. The mimeType is the one the resource registered, or application/json if it has none. Only text is supported. A prompt answer passes through if it already has a messages array. A string becomes one user message, and anything else is JSON-encoded into one.

Arguments are validated before your agent runs

The gateway checks a tool call's arguments against the schema your agent registered, and a call that fails never reaches your handler and is not counted:

event: message
data: {"result":{"content":[{"type":"text","text":"Input validation error: Invalid arguments for tool get_order: data/id must be string"}],"isError":true},"jsonrpc":"2.0","id":6}

The keywords pattern, patternProperties and format are listed to clients as hints but are never enforced, so enforce those in your handler. A tools/call whose arguments is not an object is a JSON-RPC error: -32602 Invalid tools/call request: ....

Trace headers

If you send a valid traceparent (00-<32 hex>-<16 hex>-<2 hex>, with neither id all zeros), it is passed to your agent unchanged, with tracestate (first 512 characters). Your agent's span then becomes a child of yours, in the same trace. If you send none, or something malformed, the gateway starts a trace so every call can still be followed. The response does not carry a traceparent.

Every call that reaches your agent has a Request id and a Trace id, shown on the Logs page. See OpenTelemetry.

Rate limits

Requests are limited per minute per project, not per key. On the Free plan the limit is 600 a minute. Every request counts, including failed ones and initialize and tools/list. When you exceed it:

http
HTTP/1.1 429 Too Many Requests
Retry-After: 1

{"error":"Rate limit exceeded: 600 requests per minute."}

Retry-After is in whole seconds. This body is {"error": ...}, not JSON-RPC. See Plans and limits.

Errors

These are the responses the endpoint itself can produce, in the order the checks run. Messages are exact. <id> is the project id. The full list, including errors from agents and the dashboard, is in Errors.

StatusJSON-RPC codeMessageCause
401noneMissing API key. / Invalid API key.See Authentication
403noneThis is an agent key. MCP clients connect with a client key.Wrong key kind
429noneToo many failed authentication attempts.Too many rejected keys from one address
400noneThe JSON parser's message, for example Unexpected token 'n', "not json" is not valid JSONThe body is not valid JSON
413nonerequest entity too largeThe body is over 4 MB
403-32003This project is suspended: <reason>The project or account is suspended
403-32004This address is not on the project's client allow-list.The caller's address is not in Allowed client addresses
429noneRate limit exceeded: <n> requests per minute.Over the project's rate limit
403noneForbidden - agent identity mismatch.The key (or token) is for a different project than the URL
429-32002Monthly allowance of <n> calls on the <plan> plan is used up. It resets on the 1st (UTC), or upgrade the plan.Free plan, allowance spent. Only tools/call, resources/read and prompts/get are refused
503-32001No agent connected for server "<id>".No agent is connected
503-32001Every agent for server "<id>" is disabled.Agents are connected but all disabled
406-32000Not Acceptable: Client must accept both application/json and text/event-streamAccept is missing one of the two types
415-32000Unsupported Media Type: Content-Type must be application/jsonWrong Content-Type
405-32000Method not allowed.Not a POST
400-32000Bad Request: Unsupported protocol version: <v> (supported versions: 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05, 2024-10-07)MCP-Protocol-Version names an unknown revision
400-32600Bad Request: the request body is not a valid JSON-RPC messageValid JSON that is not a JSON-RPC request
200-32601Method not foundA method the endpoint does not implement. Listing tools, resources or prompts when there are none answers with an empty list, not this error
200-32602Tool <name> not foundThe tool is not registered, is disabled, is outside the key's allow-list, or does not read and the caller is read-only
200-32602Resource not found: <uri>, with data: {"uri":"<uri>"}No registered resource has that uri
200-32602Invalid arguments for prompt <name>: <details>A required prompt argument is missing
200-32602Invalid tools/call request: <details>params or arguments has the wrong type
200-32603Your agent's error text, or Request for resource "<uri>" timed out after <ms>ms. / Request for prompt "<name>" timed out after <ms>ms.A resource read or prompt render failed or timed out

JSON-RPC errors raised by the gateway's own checks (-32001 to -32004) look like this:

json
{"jsonrpc":"2.0","id":8,"error":{"code":-32001,"message":"No agent connected for server \"8b43e8b5-1c7c-411f-a2d8-bbac5199432d\"."}}

A tool that fails after it was accepted is a 200 with isError: true, and one of these messages:

MessageCause
Request for tool "<name>" timed out after <ms>ms.Your agent did not answer within the project's call timeout (30 seconds by default). The call is counted
Agent disconnected before returning a result.The agent's connection dropped while the call was running
Key revoked. / Key rotated. / Project deleted. / Account deleted.The key or project was removed while the call was running
Project suspended. / Account suspended.The project or account was suspended while the call was running
Service is shutting down.The gateway was restarting
No connected agent serves tool "<name>".Agents are connected but none registered that tool at the moment the call was sent
Every connected agent for this project is disabled.Every agent was disabled between the checks and dispatch
Tool failed.The agent reported a failure without a message
Your handler's own error messageYour tool threw or returned an error

Things worth knowing

  • Calls are not retried. A call goes to one agent, once. If the connection drops after your handler started, the handler may still finish and its result is dropped, so make state-changing tools safe to repeat.
  • Calls are shared across replicas in turn, per tool, across the project's enabled agents that registered it.
  • Lists come from one agent. tools/list, resources/list and prompts/list are built from the longest-connected enabled agent, so give every replica the same registration.
  • A cache hit still counts for rate limiting and, on Free, for the allowance check, but is not logged and not counted as a call.
  • Argument names, not values, are logged. See Observability.

Next steps

  • Connect a client: Claude Code, Claude Desktop, Cursor, VS Code and the MCP Inspector.
  • Errors: every error a customer can see.
  • Agent protocol: the other side, for connecting an agent without an SDK.