Skip to content

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

Adapter

Audience:engineerarchitectsecuritynon-technicalMCP spec 2026-07-28

Summary

An adapter is a thin MCP server that translates an existing external system (a REST API, a database, a CLI, an internal RPC service) into MCP tools, resources, and prompts without modifying the underlying system. On Vercel it deploys as a single Vercel Function route in its own project. It is the default pattern for retrofitting MCP onto software that was not built with the protocol in mind.

Problem addressed

Most systems an AI application needs to reach already exist and cannot be rewritten. They speak HTTP, SQL, gRPC, or a vendor SDK. A model or host cannot call any of those directly: it needs a uniform surface (MCP), discoverable schemas, and host-mediated consent. Building MCP support into every backend is not feasible; coupling a host to every backend’s native API is not portable.

The adapter resolves this by isolating the translation layer in a small, focused server. The backend stays untouched; the model sees a normalized, typed, schema-validated MCP interface.

When to use

  • The target system has a stable API (REST, gRPC, SQL, CLI) and you cannot or should not modify it.
  • You need only a small, curated slice of the backend exposed to the model, not the whole surface.
  • A single owner is responsible for the backend and can keep the adapter in sync with API changes.
  • Per-tool input and output schemas can be defined unambiguously from the backend’s contract.
  • You want the integration shipped, versioned, and audited independently from the backend; one adapter per Vercel project makes deploys, rollbacks, and log trails per-backend for free.

When not to use

  • You are fronting many heterogeneous backends and want a single client-facing surface. Use a facade instead.
  • The integration runs untrusted or risky work that needs stricter isolation than the calling app. Combine the adapter with the sidecar shape (on Vercel: Sandbox or a separate protected project).
  • The backend itself can be modified to speak MCP natively; an adapter then adds a hop with no benefit.
  • The backend has no stable contract; an adapter built on a moving target produces silent breakage.

Architecture / flow diagram

Streamable HTTPREST or SQLHostMCP ClientAdapter FunctionUntouched backend
Streamable HTTPREST or SQLHostMCP ClientAdapter FunctionUntouched backend
Mermaid flowchartOpen in Mermaid Live Editor
Diagram source (Mermaid)
flowchart LR
    Host[Host] --> Client[MCP Client]
    Client -->|Streamable HTTP| Fn[Adapter Function]
    Fn -->|REST or SQL| Backend[Untouched backend]

Protocol implications

  • The adapter is a normal MCP server. Under MCP 2026-07-28 there is no initialize handshake to complete: every request arrives with the protocol version and client capabilities in _meta, the server advertises its identity and capabilities through the mandatory server/discover RPC (SEP-2575), and it answers discovery (tools/list, resources/list, prompts/list) over the Streamable HTTP transport like any other server. 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.
  • Tool input schemas (inputSchema) must be derived from the backend’s contract so model-generated arguments can be validated before reaching the backend. MCP schemas are JSON Schema 2020-12, and 2026-07-28 loosens inputSchema/outputSchema to accept any 2020-12 keyword, with $ref resolution requirements and resource bounds on composition keywords (SEP-2106). With the TypeScript SDK you declare the contract once in Zod (SDK v2 takes a full z.object({ ... }) schema rather than a raw shape), and bounds such as z.number().int().min(1).max(50) round-trip into the emitted inputSchema as minimum/maximum, so a conforming client can reject a bad argument before the call leaves the host. The looser schema vocabulary is expressive power, not license: keep adapter schemas as tight as the backend’s contract allows.
  • Resource URIs should encode backend identifiers in a stable scheme (for example, db://schema/table/{id}) and may be served through resource templates when the set is unbounded.
  • Long-running backend calls should surface progress notifications and honor cancellation. On Vercel the function’s maxDuration bounds the whole invocation; anything that can outlive it belongs in async jobs, not a longer-held request.
  • The adapter does not need sampling, and as of 2026-07-28 it should not adopt it: sampling is deprecated (SEP-2577). An adapter that genuinely requires a model in the loop should call an LLM provider API directly (on Vercel: the AI SDK or AI Gateway) instead of asking the client for completions.

Vercel mapping

  • One Function route per backend. app/api/mcp/route.ts exports GET/POST/DELETE from createMcpHandler(configureServer, { serverInfo: { name, version } }) (mcp-handler 2.x); configureServer in src/ registers the tools and holds all the protocol logic; the route file stays a thin shell. One adapter, one Vercel project: independent deploys, environment variables, rollbacks, and logs per backend.
  • No resident process. Fluid compute reuses instances best-effort, which helps connection reuse but is never a correctness guarantee. Keep the adapter stateless; read Serverless sessions before caching anything in module scope.
  • The credential is configuration, not code. The backend credential lives in a project-scoped, sensitive environment variable, set per environment, so preview deployments get a lower-privilege credential (or none at all) instead of production’s.
  • IP-allowlisted backends. Default Vercel egress uses shared, dynamic IPs. If the backend’s firewall requires a fixed source, Static IPs (Pro and Enterprise, $100 per month per project) give the project a static egress pool, shared with a small group of other customers, that the backend can allowlist; Secure Compute (Enterprise-only) is the step up when the backend must not be reachable from the public internet at all (dedicated egress IPs, VPC peering). Either way, authenticate the adapter to the backend with a scoped credential, not a source IP.
  • Database-backed adapters. Construct the client once at module scope and let Fluid instance reuse amortize it, but size connection pools for many concurrent instances, not one long-lived server.

Security considerations

  • The adapter holds the backend credential, so it sits on a trust boundary: the credential’s scope is the maximum blast radius of a compromise. Apply least privilege; the credential must grant only the operations exposed as tools, never the whole API surface. See Authorization & scoping.
  • Validate every tool argument against its declared schema before issuing the backend call, and parameterize queries; never interpolate model output into SQL or shell strings. See Input validation.
  • Treat every backend response as untrusted before returning it to the model: drop internal-only fields, escape control characters, and return the minimum the tool contract promises. See Output trust.
  • Destructive backend operations (DELETE, DROP, sends, payments) must be gated server-side and marked as requiring explicit user approval. See Consent & user approval.
  • Never construct backend hosts or URLs from model-supplied input. Without Secure Compute there is no per-function egress firewall on Vercel, so the adapter’s code is its own outbound allowlist. See Trust boundaries.
  • Preview deployments are public URLs unless Deployment Protection is on; an unprotected preview is a live adapter over a real credential. See Deployment posture.

Scope the credential as if the adapter were already compromised. On a public serverless URL, that is not paranoia; it is the deployment model.

Example implementation

  • examples/minimal-server (in the repository) - the smallest end-to-end skeleton an adapter is built on: one tool, the Streamable HTTP route, and the discovery and invocation flow every adapter inherits. It wraps no external backend; use it as the structural starting point when adapting a real one.
  • examples/db-adapter-server (in the repository) - a concrete adapter over an untouched, read-only backend (an in-process seeded store, so the tests run deterministic and offline). Its query tool exposes only the fields the adapter chooses to publish; both argument bounds are declared once in the zod schema (limit as z.number().int().min(1).max(50), category as z.string().max(64)) and round-trip into the emitted inputSchema as minimum/maximum and maxLength, with the same checks repeated server-side before the backend is queried; queries stay fully parameterized; and every row passes output-untrust handling (an internal-only column is dropped, control characters are escaped) before the model sees it. Caller-supplied text gets the same escaping as backend rows: the product_id echoed in a not-found payload and the category filter forwarded to the backend’s query log never carry a raw control character.
  • examples/secure-tools-server (in the repository) - the server-side controls an adapter should apply the moment it holds a credential: input validation, default-deny authorization, and output minimization.

Trade-offs

ProsCons
Decouples backend changes from the MCP surface.One adapter per backend multiplies the projects and sessions a host manages.
Small, focused codebase with a single owner; per-project deploys and rollbacks.The adapter must be kept in sync with backend API changes.
Backend stays untouched; no vendor lock-in.An adapter that exposes the whole backend API defeats least privilege.
Easy to audit, test, and version independently.Adds a hop and a serialization boundary; latency-sensitive paths pay for it.
  • facade - collapses many adapters into one server; the opposite trade-off.
  • sidecar - the isolation shape to reach for when the adapter’s work is riskier than the app calling it.
  • least-privilege - governs what the adapter’s backend credential may do.
  • query-vs-command - split adapter tools into reads and writes with different consent and idempotency semantics.
  • async-jobs - for adapters wrapping backend operations longer than one function invocation.

Vercel deployment (Terraform)

An illustrative Vercel expression of this pattern lives in terraform/patterns/adapter (in the repository): a project, a sensitive project environment variable carrying the backend credential, and a domain, 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