Farcaster MCP-Native Transport
The same pipe — but it can read the envelope.
The proxy is protocol-agnostic, so MCP traffic already passes through it as raw
JSON-RPC. Declaring protocol=mcp keeps every guarantee of that path and
adds what a blind pipe structurally can't have: it knows which method was called,
which tool, and which of those are safe to retry.
Registering
Two fields. Nothing else moves.
An MCP server is a catalog agent like any other. upstream_url is
reused unchanged, because MCP's Streamable HTTP transport is an ordinary HTTPS
endpoint — not a new kind of resource. credential_ref is reused
unchanged too, 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
There is no "MCP endpoint."
An MCP agent uses the exact same /v1/proxy/{id} and
/v1/proxy/{id}/stream calls as everything else. The body is a single
MCP JSON-RPC request; the streaming endpoint is where a call that upgrades to a
server-sent event stream gets proxied.
$ 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, you manage
Mcp-Session-Id, you sequence the calls. Farcaster holds no server-side
session state, doesn't track capability negotiation, and doesn't cache
initialize results — the session header passes through both directions
with no special-casing at all. A gateway that remembered your session would be a
gateway you couldn't restart.
The mechanic
tools/call is never retried. Not once.
The proxy retries transient upstream failures. For MCP that retry is gated on the parsed method, because a retry is only safe if the call was. A tool call can have side effects, and repeating one inside an established session could execute it twice — so the gateway will not, under any circumstances, do it for you.
| 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 |
This is only possible because the envelope is parsed. A generic proxy sees an opaque body and has to choose one retry policy for everything — either repeating calls that should never repeat, or refusing to retry the reads where it's free.
What it can see
4,210 calls, or 3,000 invocations of search_docs.
MCP's JSON-RPC envelope is a small, stable, known shape. Farcaster parses it to
pull out 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 the
whole difference between usage you can bill against and a number that just goes up.
$ 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, and a reason for it.
Streamable HTTP is the only transport Farcaster accepts. That's a narrower surface than some gateways advertise, and the narrowness is the argument.
| 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. Supporting it would mean a hosted gateway executing local processes — if it ever ships, it belongs in a client SDK |
| HTTP+SSE | Reserved, not accepted | Deprecated by the MCP spec in favour of Streamable HTTP |
This surface adds no new SSRF exposure — precisely because stdio
is excluded. An MCP upstream URL is validated at write time and again at dial time,
exactly like every other upstream.
Where the line falls
MCP-native is free. Including the parsing.
The part that makes this more than a pipe — reading the envelope to record method and tool, and gating retries on it — is 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
Now that every call is labelled, read them back.
Method and tool are two fields on a record the proxy was already writing. The observability surface is what you do with it.