Skip to content

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

Getting started with MCP on Vercel

Audience:engineerarchitectsecuritynon-technicalMCP spec 2026-07-28

TL;DR: The Model Context Protocol (MCP) lets an AI application talk to outside systems through a small, standardized contract. One host (the app the user sees) runs one client per connected server, and each server exposes a fixed menu of tools, resources, and prompts. On Vercel, a server is a Vercel Function answering over Streamable HTTP, not a long-lived process, and that one fact shapes everything else in these docs. Read this page first, then follow the path for your role at the bottom.

This is the first page to read in vercel-mcp-reference. It builds the mental model the rest of the docs assume, in plain language, then points you at the right next page. You do not need to read it linearly; the reading paths below route each audience to what matters for them.

What MCP is, in one paragraph

MCP is an open standard for connecting AI applications to external systems. It plays the role for AI assistants that the browser-to-web-server contract plays for the web: a single, predictable way for a program the user trusts to reach out to many independent backends. Before MCP, every assistant integrated each tool its own way; MCP replaces those one-off integrations with one protocol, so any compliant client can talk to any compliant server. It is built on JSON-RPC, runs over a choice of transports, and, as of the 2026-07-28 revision, is stateless by design: instead of a long-lived session negotiated up front, every request itself carries the protocol version and the client’s capabilities, so both sides always know which features the other supports.

The mental model: host, client, server

Three roles do all the work. Getting them straight is most of understanding MCP.

  • Host: the AI application the user actually uses (a chat app, an IDE, a desktop assistant). It owns the screen, the user’s trust, and the language model. The host decides what to connect to and gates anything sensitive behind user consent.
  • Client: a connector that lives inside the host. There is exactly one client per server. A host connected to five servers runs five clients, each an isolated connection with its own state. The client speaks the protocol; it has no opinions about the user.
  • Server: a small backend program that knows how to do one job: read a calendar, query a database, run a build. A server exposes its abilities through the protocol and should be treated as untrusted external code. In this repo, servers are Vercel Functions behind an /api/mcp route.
Host (chat app, IDE, assistant)Streamable HTTPstdioUserLanguage modelClient 1Client 2Server on Vercel(/api/mcp)Server as localprocess
Host (chat app, IDE, assistant)Streamable HTTPstdioUserLanguage modelClient 1Client 2Server on Vercel(/api/mcp)Server as localprocess
Mermaid flowchartOpen in Mermaid Live Editor
Diagram source (Mermaid)
flowchart LR
    User((User)) --- Host
    subgraph Host["Host (chat app, IDE, assistant)"]
        Model[Language model]
        C1[Client 1]
        C2[Client 2]
    end
    C1 -- "Streamable HTTP" --> S1["Server on Vercel (/api/mcp)"]
    C2 -- "stdio" --> S2["Server as local process"]

The single most common confusion is thinking one client talks to many servers. It does not. The host runs many clients in parallel, one per server, and keeps them isolated from each other: a server cannot see the conversation, the model’s full context, or any other server’s state. That isolation is a trust boundary, and it is deliberate. When a workflow needs several servers, the host composes them; see the orchestrator pattern.

What a server exposes: the three primitives

Everything a server offers falls into three primitives. The difference that matters is who is in control:

PrimitiveWhat it isWho controls invocation
ToolsActions the model can take: query, send, create, runModel-controlled (with host/user approval for sensitive actions)
ResourcesContext the server can supply: files, records, documentsApplication-controlled (the host decides what to attach)
PromptsReusable templates a user can invokeUser-controlled (the user picks them, e.g. a slash command)

A second set of features flows the other way and is easy to miss: sampling (a server asking the host’s model to generate text, deprecated in 2026-07-28 per SEP-2577 in favor of direct provider APIs) and elicitation (a server asking the user a question mid-call), plus utilities like progress and cancellation. Since 2026-07-28 these server-initiated exchanges run as multi round-trip requests (MRTR): the server returns an input_required result naming what it needs, and the client retries the original request with the answers. The primitives page covers all of them, with the method names and who controls each.

How a connection actually runs

Under the 2026-07-28 revision there is no opening handshake. Every request is self-contained: the client puts its protocol version and capabilities in the request’s _meta field (io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities), and the server answers with its own identity in the result (io.modelcontextprotocol/serverInfo). If the server does not support the requested version, it returns an UnsupportedProtocolVersionError instead of a result. A client that wants to know what a server offers before committing MAY call server/discover, a mandatory server method that advertises supported protocol versions, capabilities, and identity; or it can go straight to discovery (tools/list, resources/list, prompts/list) and start invoking what it finds. Both sides always know what the other can do, because the information travels with every message rather than living in connection state.

ServerClientHostServerClientHostserver/discover (optional probe)versions, capabilities, identitytools/list (_meta carries version + capabilities)available tools (result carries serverInfo)model selects a tooltools/call (name, args, _meta as always)result (complete, or isError true)surface result to user
ServerClientHostServerClientHostserver/discover (optional probe)versions, capabilities, identitytools/list (_meta carries version + capabilities)available tools (result carries serverInfo)model selects a tooltools/call (name, args, _meta as always)result (complete, or isError true)surface result to user
Mermaid sequence diagramOpen in Mermaid Live Editor
Diagram source (Mermaid)
sequenceDiagram
    participant Host
    participant Client
    participant Server
    Client->>Server: server/discover (optional probe)
    Server-->>Client: versions, capabilities, identity
    Client->>Server: tools/list (_meta carries version + capabilities)
    Server-->>Client: available tools (result carries serverInfo)
    Host->>Client: model selects a tool
    Client->>Server: tools/call (name, args, _meta as always)
    Server-->>Client: result (complete, or isError true)
    Client-->>Host: surface result to user

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. In practice: a curl with the 2026-07-28 headers gets the sessionless exchange described above, while the Inspector or an SDK Client left at its default opens with the legacy initialize and notifications/initialized exchange that revision 2025-11-25 required; the message trace shows both.

The internals overview walks through this lifecycle message by message, and the message trace shows the exact JSON-RPC bodies and HTTP headers as captured against a deployed example.

The serverless twist

Everything above is standard MCP. Here is what running it on Vercel changes, and why this repo exists:

  • A Vercel Function is not a resident process. Each request may land on a fresh invocation. Fluid compute reuses warm instances when it can, but that reuse is best-effort, never a correctness guarantee. Anything your server must remember between calls has to live outside the function and travel as explicit handles in tool arguments, which is exactly the model the 2026-07-28 revision adopted when it removed protocol sessions. Serverless sessions is the deep dive on how the protocol met the platform.
  • Streamable HTTP is the primary transport. Stdio assumes the client spawned your server as a child process, which cannot happen on a serverless platform. This repo’s servers speak Streamable HTTP through mcp-handler, Vercel’s documented hosting layer, from a Next.js route handler. See transports.
  • Auth is mandatory in practice. Every deployed server is a remote server on a public URL. The security section covers OAuth 2.1 for MCP and the Vercel wiring, and the deployment section covers keeping preview deployments from becoming accidental public endpoints.

The 10-minute path

The fastest way to make all of this concrete is examples/minimal-server (in the repository): the smallest end-to-end server this repo can deploy, one echo tool over Streamable HTTP. You will run it locally, watch the handshake, and deploy it. You need Node 22 or newer; the deploy step also needs the Vercel CLI and a free account.

  1. Clone and run. From the repo root:

    Terminal window
    cd examples/minimal-server
    npm install
    npm run dev

    This starts the Next.js dev server with the MCP endpoint at http://localhost:3000/api/mcp.

  2. Connect an inspector. In a second terminal:

    Terminal window
    npx @modelcontextprotocol/inspector

    In the Inspector UI, choose the Streamable HTTP transport, enter http://localhost:3000/api/mcp, connect, and call echo. Watch the message order in the Inspector’s history pane: if your Inspector build still speaks the 2025-11-25 wire protocol, the server answers it on its stateless legacy fallback (see the connection section above) and you will see the legacy initialize, the capabilities exchange, notifications/initialized, then tools/list and tools/call; a 2026-07-28 client skips straight to server/discover or its first real request. Try the two failure classes, too: a tool name that does not exist fails with a JSON-RPC protocol error (tool not found), while schema-invalid arguments to the real echo tool come back as an isError: true tool result. That split is exactly how hosts are meant to distinguish “you called something that is not there” from “the tool ran and failed”.

  3. Deploy it. From the same directory:

    Terminal window
    vercel deploy

    No environment variables are required for the Inspector or any other non-browser client. If a browser-based client will call the endpoint, set MCP_ALLOWED_ORIGINS on the project (a comma separated list of origins): the route answers any other browser Origin with 403, and requests without an Origin header pass through unaffected. Your MCP endpoint is https://<deployment>/api/mcp; point the Inspector at it and call echo again, this time against a real Vercel Function.

The example’s README (examples/minimal-server/README.md, in the repository) explains its structure: the protocol logic lives in src/server.ts as an exported configureServer(server), and the route handler is a thin shell that wraps createMcpHandler in the Origin allowlist from src/origin.ts. Every other example in the repo copies that split.

Reading paths by role

Pick the row that fits you. Each path is ordered.

If you are a…Read in this order
Engineer building a serverthis page → internals overviewprimitivestransportsserverless sessionspatternsdeployment
Architect evaluating MCP plus serverlessthis page → internals overviewserverless sessionspatterns: adapter, sidecar, facade, orchestrator
Security / governance reviewerthis page → security checklistauthorizationpatterns: least privilege, trust boundariesclient-side consent
Non-technical stakeholderthis page → glossary → the plain-language openings of the internals pages

For the full directory map and conventions, see the docs index.

Common first-time confusions

  • “MCP is an HTTP API.” No. MCP is a JSON-RPC application protocol that runs over a transport you choose (Streamable HTTP or stdio). The semantics are identical on each; only the framing differs.
  • “A Vercel Function is a resident process.” No. It is an invocation that may be created, reused, or discarded per request. The 2026-07-28 revision stopped pretending otherwise: requests are self-contained, and anything a server must remember between calls travels as explicit handles or lives in external state. Serverless sessions explains the model, and what instance reuse does and does not promise.
  • “One client connects to several servers.” No. One client, one server. Many servers means many clients, composed by the host.
  • “Tools, resources, and prompts are basically the same.” No. They differ by who controls them: the model, the application, and the user respectively. Choosing the wrong primitive for a capability is a frequent early design mistake.
  • “A server sees the whole conversation.” No. Each client-server connection is isolated. A server receives only what it is given, by design, and you should build servers assuming the same courtesy is not extended back: treat every server as untrusted.
  • “My deployment is private until I share the URL.” No. Preview deployments are public URLs unless Deployment Protection is on. The security checklist makes this a pre-deploy gate, not an afterthought.

Where to look now

  • Internals overview - the lifecycle above, message by message, with debugging notes.
  • Serverless sessions - the flagship page on MCP state atop stateless invocations.
  • examples/minimal-server (in the repository) - the 10-minute path’s target, and the structural template for every other example.
  • Security checklist - read it before your first real deploy, not after.

Bibliography