Community-maintained FDE reference. Not an official Vercel or Anthropic project. About this project
Adapter
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
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
initializehandshake to complete: every request arrives with the protocol version and client capabilities in_meta, the server advertises its identity and capabilities through the mandatoryserver/discoverRPC (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-handler2.1.1 on@modelcontextprotocol/server2.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 SDKClientdefaults 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 loosensinputSchema/outputSchemato accept any 2020-12 keyword, with$refresolution requirements and resource bounds on composition keywords (SEP-2106). With the TypeScript SDK you declare the contract once in Zod (SDK v2 takes a fullz.object({ ... })schema rather than a raw shape), and bounds such asz.number().int().min(1).max(50)round-trip into the emittedinputSchemaasminimum/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
maxDurationbounds 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.tsexports GET/POST/DELETE fromcreateMcpHandler(configureServer, { serverInfo: { name, version } })(mcp-handler 2.x);configureServerinsrc/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 (limitasz.number().int().min(1).max(50),categoryasz.string().max(64)) and round-trip into the emittedinputSchemaasminimum/maximumandmaxLength, 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: theproduct_idechoed in a not-found payload and thecategoryfilter 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
| Pros | Cons |
|---|---|
| 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. |
Related patterns
- 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
- Model Context Protocol Specification, Architecture overview, version 2026-07-28 - https://modelcontextprotocol.io/specification/2026-07-28/architecture
- Model Context Protocol Specification, Server features, version 2026-07-28 - https://modelcontextprotocol.io/specification/2026-07-28/server
- Model Context Protocol Specification, Transports, version 2026-07-28 - https://modelcontextprotocol.io/specification/2026-07-28/basic/transports
- Vercel Documentation, Deploy MCP servers to Vercel - https://vercel.com/docs/mcp/deploy-mcp-servers-to-vercel
- Vercel Documentation, Vercel Functions - https://vercel.com/docs/functions
- Vercel Documentation, Environment variables - https://vercel.com/docs/environment-variables
- Vercel Documentation, Secure Compute - https://vercel.com/docs/secure-compute
- Vercel Documentation, Static IPs - https://vercel.com/docs/networking/static-ips
- OWASP Top 10 for Large Language Model Applications - https://owasp.org/www-project-top-10-for-large-language-model-applications/