Skip to content

Community-maintained FDE reference. Not an official Vercel or Anthropic project. About this project

Facade

Audience:engineerarchitectsecuritynon-technicalMCP spec 2026-07-28

Summary

A facade (sometimes called a gateway) is a single MCP server that fronts many backend systems and exposes them through one unified set of tools and resources. It collapses many integrations into one client-facing surface and centralizes policy enforcement. On Vercel the facade is one project and one Vercel Function route, with rewrites and the Firewall forming its front door at the edge.

Problem addressed

When a host needs to reach a dozen backends, the per-backend adapter approach produces a dozen servers. Each one is small and isolated, but the host now manages a dozen endpoints, a dozen discovery rounds, a dozen credential rotations, and a dozen deploy cadences. Discovery latency grows linearly. Cross-cutting policy (rate limits, audit logging, naming conventions) has to be re-implemented in each server, and inevitably drifts.

The facade collapses this fanout into one server. The cost is loss of per-backend isolation; the benefit is one place to enforce policy and one connection to manage.

When to use

  • A single team owns many related backends and wants to expose them under one coherent vocabulary.
  • Cross-cutting policy (auth, rate limiting, audit, redaction, naming) must be enforced uniformly and centrally.
  • The number of backends is large enough that the operational cost of one project per backend outweighs the isolation benefit.
  • All fronted backends sit at a similar trust level; none carries a credential so sensitive that it must not share a process.
  • Client-side simplicity matters: the host should not have to discover, version, and approve many servers separately.

When not to use

  • Backends have different trust levels, blast radii, or credential sensitivity. Give each its own project (an adapter, or the sidecar shape) instead.
  • Backends are owned by different teams that ship on different cadences; a shared facade becomes a coordination bottleneck.
  • A backend can fail in ways that degrade unrelated backends sharing the same process (memory exhaustion, blocking I/O, a crashing native dependency).
  • You want per-backend revocation: with a facade, revoking one backend means redeploying the whole surface.

Architecture / flow diagram

Streamable HTTPHostMCP ClientFirewall andrewritesFacade FunctionBackend ABackend BBackend C
Streamable HTTPHostMCP ClientFirewall andrewritesFacade FunctionBackend ABackend BBackend C
Mermaid flowchartOpen in Mermaid Live Editor
Diagram source (Mermaid)
flowchart LR
    Host[Host] --> Client[MCP Client]
    Client -->|Streamable HTTP| Edge[Firewall and rewrites]
    Edge --> Fn[Facade Function]
    Fn --> A[Backend A]
    Fn --> B[Backend B]
    Fn --> C[Backend C]

Protocol implications

  • One server covers all backends: a single server/discover identity and one round of discovery span the whole surface. MCP 2026-07-28 removed protocol sessions and the initialize handshake (SEP-2567, SEP-2575), so there is no per-connection state to multiply either; every request carries the protocol version and client capabilities in _meta.
  • 2026-07-28 makes list results cacheable by contract: tools/list, prompts/list, resources/list, resources/read, and resources/templates/list results carry required ttlMs and cacheScope fields (SEP-2549). The facade’s front door can now legitimately cache discovery for its whole aggregated surface, but scope honestly: a facade that filters listings per principal must mark them "private" so no shared cache serves one user’s tool set to another; only a truly principal-independent listing may claim "public".
  • Return tools/list in deterministic order, which 2026-07-28 recommends for client caching and LLM prompt-cache hit rates. For a facade that means a stable sort across the aggregated registry, not per-backend registration accident, so the order survives backends being added or split out.
  • Tool names should be namespaced by backend so model-generated calls are unambiguous and tools/list stays navigable. Tool names must be plain identifiers, so use underscores (github_create_issue, jira_create_issue) rather than dots. MCP’s official tool-naming guidance (SEP-986) applies; follow it so the aggregated surface stays consistent and collision-free.
  • Resource URIs should carry a scheme or prefix that identifies the backend (github://..., blob://...) to avoid collisions and keep audit logs unambiguous.
  • If a backend’s tools change at runtime, the toolsListChanged notification now travels on the subscriptions/listen stream that clients opt into (2026-07-28 replaced the HTTP GET notification stream, SEP-2575). Delivery still depends on a client actually holding that stream open; see Serverless sessions before relying on it.
  • Per-backend progress notifications and cancellation travel on the originating request’s response stream; the facade must route them by the originating request id.
  • Wire status: the pinned stack (mcp-handler 2.1.1 on @modelcontextprotocol/server 2.0.0) serves the 2026-07-28 contract natively over Streamable HTTP and falls back to stateless 2025-11-25 Streamable HTTP for legacy clients; the SDK Client defaults to that legacy handshake unless you opt in to modern version negotiation, so the examples’ in-memory test suites exercise only the legacy path.

Vercel mapping

  • One project, one route, a configurable front door. Routing Middleware or vercel.json rewrites map a stable public path to the MCP handler. That indirection is worth having: it lets you later split a backend out into its own project without breaking the URL clients were approved against.
  • The Firewall attaches at the same edge. Vercel Firewall rate-limit rules and custom WAF rules run before your function is invoked, so discovery floods and brute-force invocation traffic are dropped at the edge instead of billed as compute. 2026-07-28 requires Mcp-Method and Mcp-Name headers on every Streamable HTTP POST (SEP-2243), which lets WAF and rate-limit rules key on the exact method and tool being called, per-tool throttles for the facade’s hottest backend, without inspecting request bodies. The facade is the choke point; put the throttle on the choke point.
  • The caveat that shapes everything: one process spans all backend credentials. Every backend’s environment variable is readable by the same function invocation. Vercel isolates per project, not per route, so a facade collapses the per-backend credential boundary by construction. If any credential is too sensitive to share a process, move that backend into its own project and let the host compose it via the orchestrator pattern.
  • Namespacing is code, not infrastructure. The backend registry, the tool-name prefix, and the routing table live in src/; the platform sees one handler. Keep the registry data-driven so adding a backend is a table entry plus its tools, not a rewrite.

Security considerations

  • The facade is a single credential vault for many backends: a compromise of the facade is a compromise of every credential it holds. Compensate with strict least privilege on each backend credential. See Authorization & scoping.
  • Per-user authorization must be enforced server-side from the verified token’s principal (the AuthInfo that withMcpAuth passes into handlers), never from a user identifier supplied by the client or the model. See Authorization & scoping.
  • Filter tools/list, resources/list, and prompts/list per principal so users only see what they may invoke. A facade that returns the union of every backend’s capabilities to every user has effectively no access control. See Authorization & scoping.
  • Centralize input validation and output sanitization in the facade; it is the choke point, so a gap here is a gap for every backend at once. See Input validation and Output trust.
  • There is no per-backend egress allowlist inside one function (Static IPs on Pro and Enterprise give backends a fixed source to allowlist, and Secure Compute on Enterprise adds private connectivity, but neither filters what the function may call), so the routing table in code is your allowlist: never derive a backend target from model input, or prompt injection turns the facade into a network scanner. See Trust boundaries.
  • Log every tool invocation with the resolved backend, principal, and an argument hash, never raw secrets or results, and ship the log off-platform via log drains. See Monitoring & audit.
  • Put Firewall rate limits in front of the MCP endpoint and keep preview deployments behind Deployment Protection; a public preview of a facade previews every backend at once. See Deployment posture.

A facade that fronts everything and filters nothing is not a gateway; it is a bigger attack surface with better ergonomics.

Example implementation

  • examples/facade-server (in the repository) - one namespaced surface over two in-process backends: a data-driven BACKENDS registry, underscore-namespaced tool names (weather_get, directory_lookup; tool names must be identifiers, so the dotted form above stays illustrative), a single dispatch choke point, and an audit log that records the backend and scope of every call but never keys or results. Its BackendError boundary contains an unexpected backend exception without forwarding it: dispatch mints a correlation id, hands the raw message and stack to an injectable FaultLogger (setFaultLogger; the default is console.error, which a Vercel log drain ships off-platform), and returns only the fixed text backend "<name>" failed; see server logs for correlation id <uuid> as an isError tool result. Expected failures (an unknown key, an unknown backend) pass through as ordinary BackendErrors with no id and nothing logged. The tests replaces the raw fault text with an opaque message and a correlation id and keeps the raw fault text out of the tool result and logs it server-side inject a recording logger and assert the exception text is absent from every byte of the result while the logger received it under the same id, and contains a fault as an isError result and the session survives proves the shared handler keeps serving. It is explicit that single-process exception containment is not isolation: a crashing native dependency would still take down siblings, which is what the sidecar shape is for.
  • examples/secure-tools-server (in the repository) - the hardened single-surface skeleton: input validation, default-deny authorization, and output minimization on one tool. That is the per-tool discipline a facade must replicate across every backend it fronts.

Trade-offs

ProsCons
One project to deploy, monitor, version, and authenticate.One process to lose: compromise blast radius spans every backend credential.
Central enforcement of policy, audit, and naming; one edge for Firewall and rate limits.Backends share a process; one bad backend can degrade all.
Smaller client surface; one endpoint, one discovery round.Lost per-backend revocation; removing one backend redeploys the whole surface.
One credential broker, one rotation cadence.Coordination cost when multiple teams own the backends.
  • adapter - the per-backend alternative; a facade is internally a collection of adapters.
  • sidecar - when per-backend isolation matters more than client simplicity.
  • orchestrator - the host-side counterpart that composes multiple servers without collapsing their credentials into one process.
  • least-privilege - required discipline for every credential a facade holds.
  • trust-boundaries - explains the trust collapse a facade introduces and how to compensate.

Vercel deployment (Terraform)

An illustrative Vercel expression of this pattern lives in terraform/patterns/facade (in the repository): one project whose rewrites fan out to the handler, with a Firewall configuration (rate limits and custom rules) at the front door, built with the official vercel/vercel provider. It is tofu validate-checked, never applied in CI. See terraform/README.md (in the repository) for scope and caveats.

Bibliography