# Adapter

Canonical URL: https://vercel-mcp-reference.vercel.app/patterns/adapter/
Markdown: https://vercel-mcp-reference.vercel.app/patterns/adapter.md
Audience: engineer, architect, security, non-technical. MCP spec version: 2026-07-28. Last reviewed: 2026-08-26. Status: stable.

## Summary

An [adapter](https://vercel-mcp-reference.vercel.app/glossary/#adapter) is a thin MCP [server](https://vercel-mcp-reference.vercel.app/glossary/#server) that translates an existing external system (a REST API, a database, a CLI, an internal RPC service) into MCP [tools](https://vercel-mcp-reference.vercel.app/glossary/#tool), [resources](https://vercel-mcp-reference.vercel.app/glossary/#resource), and [prompts](https://vercel-mcp-reference.vercel.app/glossary/#prompt) without modifying the underlying system. On Vercel it deploys as a single [Vercel Function](https://vercel-mcp-reference.vercel.app/glossary/#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](https://vercel-mcp-reference.vercel.app/glossary/#host) cannot call any of those directly: it needs a uniform surface (MCP), discoverable schemas, and host-mediated [consent](https://vercel-mcp-reference.vercel.app/glossary/#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](https://vercel-mcp-reference.vercel.app/patterns/facade/) instead.
- The integration runs untrusted or risky work that needs stricter isolation than the calling app. Combine the adapter with the [sidecar](https://vercel-mcp-reference.vercel.app/patterns/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

```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](https://vercel-mcp-reference.vercel.app/glossary/#discovery) (`tools/list`, `resources/list`, `prompts/list`) over the [Streamable HTTP transport](https://vercel-mcp-reference.vercel.app/glossary/#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](https://vercel-mcp-reference.vercel.app/glossary/#resource-template) when the set is unbounded.
- Long-running backend calls should surface [progress notifications](https://vercel-mcp-reference.vercel.app/glossary/#progress-notification) and honor [cancellation](https://vercel-mcp-reference.vercel.app/glossary/#cancellation). On Vercel the function's `maxDuration` bounds the whole invocation; anything that can outlive it belongs in [async jobs](https://vercel-mcp-reference.vercel.app/patterns/async-jobs/), not a longer-held request.
- The adapter does not need [sampling](https://vercel-mcp-reference.vercel.app/glossary/#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](https://vercel-mcp-reference.vercel.app/glossary/#fluid-compute) reuses instances best-effort, which helps connection reuse but is never a correctness guarantee. Keep the adapter stateless; read [Serverless sessions](https://vercel-mcp-reference.vercel.app/internals/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](https://vercel.com/docs/environment-variables), set per environment, so [preview deployments](https://vercel-mcp-reference.vercel.app/glossary/#preview-deployment) 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](https://vercel-mcp-reference.vercel.app/glossary/#trust-boundary): the credential's scope is the maximum blast radius of a compromise. Apply [least privilege](https://vercel-mcp-reference.vercel.app/patterns/least-privilege/); the credential must grant only the operations exposed as tools, never the whole API surface. See [Authorization & scoping](https://vercel-mcp-reference.vercel.app/security/checklist/#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](https://vercel-mcp-reference.vercel.app/security/checklist/#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](https://vercel-mcp-reference.vercel.app/security/checklist/#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](https://vercel-mcp-reference.vercel.app/security/checklist/#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](https://vercel-mcp-reference.vercel.app/security/checklist/#trust-boundaries).
- Preview deployments are public URLs unless [Deployment Protection](https://vercel-mcp-reference.vercel.app/glossary/#deployment-protection) is on; an unprotected preview is a live adapter over a real credential. See [Deployment posture](https://vercel-mcp-reference.vercel.app/security/checklist/#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

| 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](https://vercel-mcp-reference.vercel.app/patterns/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](https://vercel-mcp-reference.vercel.app/patterns/facade/) - collapses many adapters into one server; the opposite trade-off.
- [sidecar](https://vercel-mcp-reference.vercel.app/patterns/sidecar/) - the isolation shape to reach for when the adapter's work is riskier than the app calling it.
- [least-privilege](https://vercel-mcp-reference.vercel.app/patterns/least-privilege/) - governs what the adapter's backend credential may do.
- [query-vs-command](https://vercel-mcp-reference.vercel.app/patterns/query-vs-command/) - split adapter tools into reads and writes with different consent and idempotency semantics.
- [async-jobs](https://vercel-mcp-reference.vercel.app/patterns/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/>
