Try the live demo
https://demo.fold.run/mcp is a real fold gateway — the unmodified release binary — federating three public MCP servers. No signup, no key: point any MCP client (or curl) at it. Rate-limited, unauthenticated, no warranty.
| Namespace | Upstream | Notes |
|---|---|---|
cfdocs__* | Cloudflare’s MCP docs server | A real third-party upstream |
git__* | GitMCP | A public upstream two revisions behind — behind the gateway, just another namespace |
jobs__* | fold-demo-tasks | A task-minting server (Go, on the official SDK); where the federated-tasks story runs live |
Every example below is plain curl. Responses arrive as a one-event SSE body — read the data: line.
0. Open a session
Section titled “0. Open a session”fold’s client side is the official Go SDK’s streamable HTTP server, so requests ride a session: initialize once, capture the Mcp-Session-Id response header, send it on everything after.
DEMO=https://demo.fold.run/mcpSID=$(curl -s -D - -o /dev/null -X POST $DEMO \ -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2026-07-28","capabilities":{},"clientInfo":{"name":"me","version":"0"}}}' \ | grep -i mcp-session-id | tr -d '\r' | cut -d' ' -f2)
curl -s -o /dev/null -X POST $DEMO -H "mcp-session-id: $SID" \ -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'The initialize result already shows the gateway working: serverInfo.name is fold, and the instructions line names the three namespaces.
1. One virtual server
Section titled “1. One virtual server”curl -s -X POST $DEMO -H "mcp-session-id: $SID" \ -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'One merged tool list — cfdocs__*, git__*, and jobs__* side by side. fold fanned the request out to all three upstreams, namespaced the names, and merged the results in one page (deterministic order, cursor-paginated past routing.pageSize). If an upstream were down you’d still get the other two, with the failure named in _meta["run.fold/partialFailure"] instead of a dead endpoint. Repeat calls inside the TTL are served from the list cache.
2. Start a long-running job
Section titled “2. Start a long-running job”curl -s -X POST $DEMO -H "mcp-session-id: $SID" \ -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"jobs__start_job","arguments":{"label":"my job","seconds":15}}}'The minted task rides the result’s _meta, origin-tagged by the gateway:
{ "result": { "_meta": { "task": { "taskId": "demo-job-2", "status": "working", "label": "my job", "remainingMs": 14999, "createdAt": "…" }, "pollIntervalMs": 1000, "run.fold/upstream": "demo-tasks" }, "content": [{ "type": "text", "text": "started demo-job-2 (\"my job\", 15s) — poll it with tasks/get" }] } }Because the mint is visible in _meta, fold pinned taskId → upstream affinity as the response passed through — and a tool call is a tool call, so deny-by-default policy would have applied at mint and the call is in the audit trail.
3. Poll it — fold routes to the owner
Section titled “3. Poll it — fold routes to the owner”curl -s -X POST $DEMO -H "mcp-session-id: $SID" \ -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":4,"method":"tasks/get","params":{"taskId":"demo-job-2"}}'tasks/list shows the merged view across the whole federation — your job in task-id order, with _meta["run.fold/partialFailure"] naming the two upstreams that don’t speak tasks at all:
curl -s -X POST $DEMO -H "mcp-session-id: $SID" \ -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":5,"method":"tasks/list"}'No tool name, no routing hint — fold resolves the owner from its affinity index, or by a read-only probe across upstreams for a task it never saw minted (try it: task ids survive sessions, so tasks/get from a brand-new session still finds the owner). Mutating methods are never fanned out; tasks/cancel and tasks/result locate first, then act on the owner alone. The mechanism is the subject of the launch post.
4. The older upstream you address the same way
Section titled “4. The older upstream you address the same way”curl -s -X POST $DEMO -H "mcp-session-id: $SID" \ -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"git__fetch_generic_documentation","arguments":{"owner":"modelcontextprotocol","repo":"go-sdk"}}}'GitMCP still speaks MCP 2025-03-26 — two revisions behind the 2025-11-25 this gateway negotiates, whatever you ask for at initialize. fold — built on the official Go SDK on both sides of the proxy — holds its own client session to it at the version it speaks, so from where you’re standing it’s just another namespace in the same tool list, behind the same governance.
5. Watch it in the console
Section titled “5. Watch it in the console”Open demo.fold.run/console — the read-only fold console, enabled in the demo’s config. The upstreams table shows a row per upstream — namespace, source, auth, connected, breaker, latency — and the map view draws the federation itself: one gateway node fanning out to cfdocs, git, and jobs, each route labelled with the latency that upstream is answering in, over a footer reading 3 of 3 connected · every route crosses auth, policy and audit. It’s generated from /api/federation, so it’s this demo’s real topology, not a picture of one. The test console is a plain MCP client against the same /mcp endpoint you’ve been curling, governed and audited like any other caller.
The same federation, governed
Section titled “The same federation, governed”Everything above is unauthenticated on purpose. https://enterprise.fold.run is the same binary over the same three upstreams with auth.mode: required — a separate deployment because auth.mode is gateway-wide, so making one governed would otherwise take the copy-pasteable one away.
An anonymous initialize there answers 401 with an RFC 9728 challenge, which is the correct answer rather than a failure:
curl -si -X POST https://enterprise.fold.run/mcp \ -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2026-07-28","capabilities":{},"clientInfo":{"name":"me","version":"0"}}}' \ | grep -i www-authenticate# www-authenticate: Bearer resource_metadata="https://enterprise.fold.run/.well-known/oauth-protected-resource"Follow that metadata and you get the issuer a client should sign in to — the discovery path an MCP client walks on its own:
curl -s https://enterprise.fold.run/.well-known/oauth-protected-resource# {"resource":"https://enterprise.fold.run","authorization_servers":["https://oauth.work"],...}Two tenants sit on it, differing on every axis a tenant governs:
| Tenant | Budget | Rate limit | Upstreams |
|---|---|---|---|
acme | 5,000 upstream calls/day | 120 req/min | all three |
globex | 1,000 upstream calls/day | 60 req/min | cf-docs, demo-tasks — never reaches GitMCP |
globex’s subset filters the fan-out before policy runs, so GitMCP is not asked, not billed, and not a partial failure when it is down. Policy is deny-by-default and splits each tenant by actor_type, so a signed-in person and that person’s agent get different surfaces through the same gateway. The console reads it all back live, behind sign-in.
Run this yourself
Section titled “Run this yourself”The demo is assembled from one config — the same shape as any fold deployment (configuration reference):
{ "upstreams": [ { "id": "cf-docs", "url": "https://docs.mcp.cloudflare.com/mcp", "namespace": "cfdocs" }, { "id": "gitmcp", "url": "https://gitmcp.io/docs", "namespace": "git" }, { "id": "demo-tasks", "url": "https://tasks.fold.run/mcp", "namespace": "jobs" } ], "server": { "rateLimit": { "requestsPerMinute": 300 }, "introspection": { "enabled": true }, "console": { "enabled": true } }}go run github.com/fold-run/fold/cmd/fold@latest --config fold.config.json gives you the same thing in front of your own servers — see Getting started.