← The record

fold

One governed endpoint between every MCP client and every MCP server. Forty out of forty on the official conformance suite, on every merge.

Year
2026
Language
Go
Release
v1.14.0
Conformance
40/40
Image
~22 MB

What it is

A gateway that sits in front of any number of upstream MCP servers, in any language, on any SDK, from any team or vendor, and presents them as one virtual server with namespaced tools. Nobody rewrites anything to get behind it.

It is built on the official MCP Go SDK, so the wire protocol is the SDK’s own implementation on both the client-facing and upstream-facing sides rather than a reimplementation that drifts.

The conformance claim is checkable rather than asserted: the official suite runs against fold fronting the reference everything-server on every merge, and a weekly job re-runs it against the latest unpinned SDK and opens a tracking issue the moment anything drifts.

The request pipeline

fold request pipelineA POST to the MCP endpoint passes through nine ordered stages: host validation, authentication, rate limiting, routing, visibility, authorization, per-upstream guards, proxying and egress filtering. Every request leaves through a single audit stage, including the ones that were denied, so there is one exit door rather than one per failure branch.POST /mcphost validationDNS-rebinding protection: Host and Origin allowlistauthenticateBearer → issuer allowlist → JWKS → audience → Principalrate limitglobal → tenant → per-principal windows → 429 + Retry-Afterroutefederated fan-out for lists, namespaced routing otherwisevisibilitytenant upstream subset, so a fan-out never reaches what it excludesauthorizedeny-by-default policy, per invocationper-upstream guardsrate limit · circuit breaker · timeout · budgetsproxycredentials attached, held SDK session per upstreamegressper-principal list filtering, namespace rewritingauditone event per request, including denials: a single exit door
Visibility sits before authorization, not after: a tenant’s upstream subset is resolved first so a federated fan-out never reaches a server that tenant cannot see. Filtering the results afterwards would have already made the call.

A federated configuration

fold.config.jsonJSON
{
  "upstreams": [
    {
      "id": "github-tools",
      "url": "https://mcp.platform.acme.com/mcp",
      "namespace": "gh",
      "owner": { "org": "acme-platform", "team": "devex" }
    },
    {
      "id": "ml-search",
      "url": "https://mcp.ml.acquired-co.com/mcp",
      "namespace": "search",
      "rateLimit": { "requestsPerMinute": 600 },
      "circuitBreaker": { "failureThreshold": 5, "halfOpenAfterMs": 30000 }
    }
  ],
  "server": { "rateLimit": { "requestsPerMinute": 6000 } }
}

The problems federation actually creates

  • Two teams building from the same template collide. MCP Apps points a tool at an interface through a ui:// resource URI that only has to be unique within one server, and the published starter templates ship with no server segment. Federate two of them and a host rendering one team’s tool could get another team’s app, depending on what some other client had done first. fold mints those URIs per namespace. It is the one documented exception to never rewriting a URI, and it is narrow on purpose.
  • Task ids are opaque and clients persist them. So fold never rewrites them and remembers ownership instead. A task-scoped call from a different principal answers the same error as an unknown id, with no existence leak and no probe. The ownership index is an authorization record rather than a routing hint, which is why it lives in shared state: a caller must not reach another principal’s task by landing on an instance that did not serve the mint.
  • A rejected config reload must not take anything down. The upstream set and the policy engine swap atomically, in-flight requests finish against the snapshot they started on, and unchanged upstreams keep their live sessions. Sections that cannot hot-swap fail the reload loudly and keep the running configuration.
  • A capability profile is computed, never trusted. Root sessions and list-cache entries are keyed by a normalised profile derived from the extension identifiers fold implements, not from the client’s raw map, so a caller inventing extension ids cannot mint sessions or cache entries.
  • Degradation is explicit rather than silent. A fan-out with a dead upstream returns what it has and names the failures in _meta. A circuit breaker short-circuits an unhealthy upstream. Replicated upstreams round-robin per session and eject a dead endpoint on an active health check before a client request pays for the discovery.

Operationally

A distroless container around 22 MB, prebuilt binaries for linux and darwin on both architectures, or one call to embed it in an existing Go service. Set a Redis URL and cache, rate-limit, circuit-breaker and task-ownership state are shared, so a fleet of gateways behaves as one.

One JSON document, validated on startup, with a JSON Schema shipped for editor completion and CI linting.

Ask about it

The assistant on the home page answers from this same record, and will tell you what is not on it.

Ask the recordTalk to me