FarcasterGateway

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.

shipped Apache-2.0 streamable http

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

The full boundary →

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.