Zerker MCP-Native Transport
Gateway reads the MCP envelope.
MCP traffic already passes through the proxy as JSON-RPC. Declaring protocol=mcp keeps that path and adds three things: which method was called, which tool, and whether the call is safe to retry.
Registering
Two fields, recorded on every call.
An MCP server is a catalog agent like any other. upstream_url is the Streamable HTTP endpoint. credential_ref works the same way for servers that authenticate with a bearer token or API key.
Register an MCP server
$ curl localhost:8080/v1/agents \ -H "Authorization: Bearer $TOKEN" \ -d '{ "name": "docs-search", "upstream_url": "https://mcp.example.com", "protocol": "mcp", "mcp_transport": "streamable_http" }'
Find them again
$ curl localhost:8080/v1/agents?protocol=mcp ← filters the catalog down to your MCP servers, which matters once you are running a mix of REST and MCP
No new surface
No separate MCP endpoint.
An MCP agent uses the same /v1/proxy/{id} and /v1/proxy/{id}/stream calls as everything else. The body is one MCP JSON-RPC request. Use the streaming endpoint when the server answers with an event stream.
$ curl localhost:8080/v1/proxy/{id}/stream \ -H "Authorization: Bearer $TOKEN" \ -H 'Mcp-Session-Id: 9d3f2b1a-…' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call", "params":{"name":"search_docs","arguments":{"query":"SSRF"}}}'
The caller owns the session. You run initialize, manage Mcp-Session-Id, and sequence the calls. Gateway holds no session state and passes the session header through in both directions, so it can be restarted at any time.
The mechanic
tools/call is never retried.
The proxy retries transient upstream failures. For MCP, the retry decision depends on the method. A tool call can have side effects, and repeating one could execute it twice, so Gateway never retries it.
| Method | Why | Retried |
|---|---|---|
initialize |
Known-idempotent — reading the same thing twice costs nothing but a round trip | Yes |
tools/list |
Yes | |
resources/list |
Yes | |
prompts/list |
Yes | |
tools/call |
Side effects are the whole point of a tool. A silent double-execution is worse than a visible failure | Never |
A generic proxy sees an opaque body and has to pick one retry policy for everything. Reading the envelope is what makes a per-method rule possible.
What it can see
Total calls, or invocations of one named tool.
Gateway parses the JSON-RPC envelope for the method and, for a tool call, the tool name, and stores both on the invocation record as mcp_method and mcp_tool. That is what turns a call count into usage you can bill.
$ curl localhost:8080/v1/invocations/inv_019842… { "id": "inv_019842…", "agent_id": "agt_019842…", "mcp_method": "tools/call", "mcp_tool": "search_docs", "status": "succeeded", "ttft_ms": 84 }
Both fields show up on GET /v1/invocations and on the per-agent
invocation fetch, so they filter and aggregate like anything else. See
observability.
Scope, on purpose
One transport.
Streamable HTTP is the only transport Gateway accepts.
| Transport | What happens | Why |
|---|---|---|
streamable_http |
Accepted | The transport the MCP spec moved to; an ordinary HTTPS endpoint we can guard like any other |
stdio |
Rejected at write time with 400 |
A local-process transport with no URL and no TLS. If it ships, it belongs in a client SDK |
| HTTP+SSE | Reserved, not accepted | Deprecated by the MCP spec in favour of Streamable HTTP |
No new SSRF exposure. An MCP upstream URL is validated at write time and again at dial time, like every other upstream.
Where the line falls
MCP support is open source.
Envelope parsing, method and tool capture, and method-gated retries are all in the open-source binary.
| Capability | Tier |
|---|---|
| MCP-native transport over Streamable HTTP | OSS |
| Method and tool capture, and method-gated retry safety | OSS |
| Usage and revenue dashboards built on that capture | Commercial |
Next
Read the calls back.
Method and tool are two fields on a record the proxy already writes. Observability is how you read them.