Skip to content

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

Trust Boundaries

Audience:engineerarchitectsecurityMCP spec 2026-07-28

Summary

The host is the only component in an MCP system that holds direct user trust. Every other component (clients, servers, backends) sits across a trust boundary and must be given only what it needs. A server must not receive the full conversation, must not see other servers’ state, and must not be able to chain to another server without the host mediating. On Vercel the abstract discipline becomes three concrete, enforceable boundaries: internet to edge, project to project, and function to downstream.

Problem addressed

It is tempting to treat the model and its tools as one fabric: a server that “knows the conversation” can plan smarter, a “shared context” between servers can avoid duplicate work, a server that calls another server can compose richer behaviors. Every one of these convenience moves collapses a trust boundary.

In the MCP threat model, servers are mutually distrustful and any of them can be hostile. The user trusts the host; the host trusts the user. Everything else is negotiation. Serverless raises the stakes: every MCP server on Vercel is a remote server on a public URL by default, so the boundaries are not an internal architecture nicety, they are the perimeter. A trust-boundary discipline is what keeps a compromised server (or a successful prompt injection inside one server) from contaminating other servers, leaking the user’s transcript, or making decisions the user did not approve.

When to use

Always. Trust boundaries are not opt-in. The pattern names the boundaries explicitly so engineers and reviewers can enforce them.

Apply the discipline deliberately when:

  • Designing what context the host forwards to a server on each request.
  • Composing the output of one server’s tool into the input of another.
  • Reviewing what a server logs, persists, or sends upstream.
  • Considering whether two servers (two Vercel projects) should share state, storage, or credentials.
  • Building any “shared memory” or “shared context” feature across servers.
  • Granting one project access to another’s protected deployments.

When not to use

There is no opt-out. Variants that collapse the boundary and should be rejected:

  • Forwarding the full host transcript to every server “so it has context.”
  • Letting a server call another server directly (one project fetching another’s MCP route) to avoid a host round-trip.
  • Sharing a credential vault, database, or environment variable set between servers in the same host.
  • Handing every caller the same protection-bypass secret because per-caller rules felt like friction.
  • Inferring user consent for server B from a prior approval for server A.

Architecture / flow diagram

trustminimal contextminimal contextStreamable HTTPStreamable HTTPUserHostMCP Client AMCP Client BProject A FunctionProject B FunctionBackend ABackend B
trustminimal contextminimal contextStreamable HTTPStreamable HTTPUserHostMCP Client AMCP Client BProject A FunctionProject B FunctionBackend ABackend B
Mermaid flowchartOpen in Mermaid Live Editor
Diagram source (Mermaid)
flowchart LR
    User[User] -->|trust| Host[Host]
    Host -->|minimal context| ClientA[MCP Client A]
    Host -->|minimal context| ClientB[MCP Client B]
    ClientA -->|Streamable HTTP| ServerA[Project A Function]
    ClientB -->|Streamable HTTP| ServerB[Project B Function]
    ServerA x-.-x ServerB
    ServerA --> BackendA[Backend A]
    ServerB --> BackendB[Backend B]

The dashed line between Project A and Project B is the boundary that must not be crossed directly. Any composition between them is mediated by the host; on Vercel there is no built-in server-to-server path, and you should not add one out-of-band.

Protocol implications

  • MCP 2026-07-28 removed protocol sessions and the initialize handshake (SEP-2567, SEP-2575): each request carries the protocol version and client capabilities in _meta over the Streamable HTTP transport, and each server only ever learns what the host’s requests tell it. Anything a server must remember across calls is an explicit server-minted handle the host passes back as an ordinary tool argument, and a handle is a routing capability, not an identity: authorization must come from the verified token, never from the handle.
  • Sampling is deprecated as of 2026-07-28 (SEP-2577), and server-initiated requests are replaced by Multi Round-Trip Requests (SEP-2322): a server that needs model output or user input returns an input_required result, and the host decides whether to fulfill it before retrying the original request. The boundary holds in both the old shape and the new: the server never sees the host’s model selection, system prompt, or other servers’ contributions to the conversation, and the host may deny, edit, or require consent for every input_required round trip, exactly as it did for sampling requests.
  • List and read results carry required ttlMs and cacheScope fields under 2026-07-28 (SEP-2549). cacheScope is a trust-boundary contract for every intermediary: a shared cache (edge, gateway, or host-side) must never serve a "private"-scoped result, one filtered per principal, to a different principal, and a server whose listings vary per principal must not claim "public". Treat the scope declaration as part of the authorization surface, not a performance hint.
  • Tool inputs are constructed by the host (often from a model decision plus user-approved arguments). Tool outputs are returned to the host, which decides what to do with them, including whether any portion is forwarded to another server.
  • Progress notifications and cancellation flow on the originating request’s response stream only.
  • MCP gives servers no primitive to discover or call one another. That absence is deliberate: composition is the host’s job (see orchestrator), and any out-of-band channel you add is a new attack surface the protocol’s threat model does not account for.
  • 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

Vercel has no security groups or VPC-per-service to lean on by default. The boundaries map onto platform primitives instead:

  • Boundary 1: internet to edge. The Vercel Firewall fronts every deployment with DDoS mitigation, and the WAF adds custom rules and rate limiting. An MCP endpoint is a machine-discoverable POST target; give it a rate limit and, where traffic patterns allow, WAF rules before it ever reaches a Vercel Function. Since 2026-07-28 every Streamable HTTP POST must carry Mcp-Method and Mcp-Name headers (SEP-2243), so edge rules can tell discovery from invocation and rate-limit specific tools without inspecting request bodies. Authentication still happens in the function; the edge boundary buys you survivable abuse, not identity.
  • Boundary 2: project to project. Run each MCP server as its own Vercel project: disjoint environment variables, logs, deploys, and rollbacks. The isolation equivalent of a security group is Deployment Protection plus Trusted Sources: protect the deployment, then authorize specific callers by verified OIDC token (sent as x-vercel-trusted-oidc-idp-token, obtained with getVercelOidcToken() from @vercel/oidc). By default no other project can reach a protected project; access exists only where you wrote an explicit rule scoped to a calling project and environment. The missing bypass rule is the feature. Reviewing the boundary means reading a short list of rules, and note that claims you do not configure are not checked, so scope every rule as tightly as the dashboard allows.
  • Boundary 3: function to downstream. Default egress leaves from shared, dynamic IPs with no per-function egress firewall, so the backend must authenticate the caller with a scoped credential; the function’s code is its own outbound allowlist. Static IPs (Pro and Enterprise, priced per project) give the project a shared static egress pool a downstream firewall can allowlist, and Secure Compute (Enterprise-only) hardens this boundary further with a dedicated private network, dedicated egress IPs, and VPC peering into your cloud; either way a fixed IP is an allowlist ingredient, not authentication: keep the credential. For untrusted or model-generated code, Vercel Sandbox (GA) runs the work in a Firecracker microVM with its own filesystem and network, away from the function’s environment variables; see sidecar.

If a genuine project-to-project call is unavoidable (a shared internal service, not MCP composition), make it an explicit Trusted Sources rule from one named project and environment to another, and treat the rule as a reviewed trust edge. A shared x-vercel-protection-bypass secret handed to every caller is the anti-pattern: one static value, no caller identity, no per-edge revocation.

Security considerations

  • The server must receive only the conversation context strictly required for the current request; it must not receive the full host transcript. See Trust boundaries.
  • The server must not read, log, or persist context that originated from other servers in the same host. See Trust boundaries.
  • Cross-server tool chaining is mediated by the host, not by direct server-to-server calls. See Trust boundaries.
  • Each MCP server runs in its own project with no shared environment variables, storage, or bypass secrets across projects; untrusted work moves into a Sandbox. See Trust boundaries.
  • Tool outputs are the primary prompt-injection vector and must be treated as untrusted data when re-injected into model context, regardless of which upstream system produced them. See Output trust.
  • Approval state is scoped per server; do not infer approval across servers from prior grants. See Consent & user approval.
  • Per-user authorization is enforced server-side using the authenticated principal; the server must never trust a user identifier supplied only by the client or model. See Authorization & scoping.
  • A handle is not authentication: bind each server-minted handle to the principal it was issued to, give it an expiry, and reject presentation by any other principal. See Session handling.
  • Preview deployments are public URLs unless Deployment Protection is on, and a preview crosses the same three boundaries production does; protect it the same way. See Deployment posture.

Boundaries you can enumerate are boundaries you can review. On Vercel there are exactly three; keep it that way.

Example implementation

  • examples/secure-tools-server (in the repository) - the server-side controls that enforce a trust boundary at the tool layer: input validation of every argument, default-deny authorization, and output minimization. It is the template for the in-process checks any server holding a credential should apply; the two examples below carry the boundary into the credential and runtime layers.
  • examples/least-privilege-server (in the repository) - per-principal authorization and scopes, refuse-to-start configuration validation, and call-time enforcement: the credential-layer half of the boundaries this page describes.
  • examples/sandbox-isolation-server (in the repository) - the runtime realization: untrusted work dispatched into a Vercel Sandbox with a deny-by-default egress networkPolicy allowlist and no env passed in, keeping the function’s environment out of the untrusted code’s reach.

Trade-offs

ProsCons
One compromised server does not become every server compromised.“Shared context” optimizations are off the table; every cross-server composition routes through the host.
User consent remains coherent: the host is the only consent surface.Per-request context shaping is work the host must do explicitly.
Project-per-server makes the boundary auditable: disjoint env vars, logs, and a short list of Trusted Sources rules.More projects to operate; fleet-level conventions (Terraform, shared config discipline) become worth their cost sooner.
The threat model is simple enough to reason about.A motivated developer can still build cross-project side channels (shared databases, shared bypass secrets); review for them.
  • least-privilege - least privilege is the mechanism; trust boundaries are the architecture it enforces.
  • sidecar - the runtime realization of the host-to-server boundary; on Vercel, Sandbox or a separate protected project.
  • orchestrator - the host pattern that owns cross-server mediation and consent.
  • adapter - defines a server’s external trust boundary onto its backend.
  • facade - collapses multiple backends behind one server; trust boundaries between those backends become an internal concern of the facade.

Vercel deployment (Terraform)

An illustrative Vercel expression of this pattern lives in terraform/patterns/trust-boundaries (in the repository): two projects with disjoint environment variables, firewall configuration on both, and deliberately no cross-project bypass, because the missing rule is the feature. It is tofu validate-checked, never applied in CI. See terraform/README.md (in the repository) for scope and caveats.

Bibliography