Menu

AI

How to Deploy an MCP Server to Vercel with mcp-handler and Next.js (2026)

Deploy a remote MCP server to Vercel with mcp-handler v2, MCP SDK v2 and Next.js. Route code, local Inspector test, vercel --prod, OAuth and common mistakes.

By: Irina ShvayaNovember 2, 2026

How to Deploy an MCP Server to Vercel with mcp-handler and Next.js (2026)

This tutorial is part of Build an MCP Server, within The MCP Guide. Want it built for you? See MCP development.

To deploy an MCP server to Vercel, add one Next.js route that calls createMcpHandler from the mcp-handler package, test it locally with the MCP Inspector, then deploy with vercel --prod or a Git push. Clients connect to https://<your-app>.vercel.app/api/mcp over Streamable HTTP.

The important change in 2026 is the package version. mcp-handler 2.x is built on the official MCP TypeScript SDK v2 (@modelcontextprotocol/server), not the v1 package @modelcontextprotocol/sdk. It drops the old SSE transport and the Redis requirement. Tutorials from 2025 that use server.tool(...), redisUrl or /sse routes describe version 1.

Checked against: Vercel's Deploy MCP servers to Vercel guide (last updated 18 September 2026), the mcp-handler README and npm, on 2 November 2026. The route below ran locally with next dev and built with next build on 2 November 2026 (Node.js 24, Next.js 16.4.4, mcp-handler 2.3.0, @modelcontextprotocol/server 2.4.0, zod 4.6.7). Nothing was deployed to a Vercel account.

Key Takeaways

  • Use mcp-handler 2.x with @modelcontextprotocol/server 2.x and Zod 4. The README says SDK v1 users should stay on mcp-handler 1.x.
  • On 2 November 2026 npm's latest mcp-handler was 2.3.0, while Vercel's docs pinned 2.1.1 in the install command.
  • The handler is a Web-standard (Request) => Response function, so it also works in Nuxt, SvelteKit and Hono, not only Next.js.
  • One handler serves the 2026-07-28 protocol and falls back to stateless Streamable HTTP for 2025-era clients.
  • withMcpAuth checks bearer tokens and protectedResourceHandler publishes OAuth metadata, but you still need your own authorization server.

Prerequisites

  • Node.js 20 or later. Both the Vercel guide and the README set this floor.
  • A Next.js App Router project. The README lists Next.js 13 or later as the requirement for Next.js users.
  • A Vercel account and the Vercel CLI for the deploy step. Everything before that runs locally.

If you are new to remote MCP servers, Lesson 21 of our MCP course explains the moving parts, and the full course covers hosts, clients and tools from the start.

Step 1: Install the packages

In your Next.js project:

npm i mcp-handler@2 @modelcontextprotocol/server@2 zod@4

Vercel's guide uses mcp-handler@2.1.1, and the README uses mcp-handler@^2. On 2 November 2026 npm showed 2.3.0 as the latest release, with @modelcontextprotocol/server ^2.0.0 and next >=13.0.0 as peer dependencies. The README adds that 2.x needs Zod 4.2.0 or later.

One more thing worth knowing: npm lists the repository as vercel-labs/mcp-handler, and the README carries a "Vercel Labs Product" badge. Treat it as a supported open-source package from Vercel's labs team, and pin the version you test.

Step 2: Create the MCP route

Create app/api/mcp/route.ts. This version adapts the dice example from Vercel's guide, adds a title, an argument description and a read-only hint, and sets the server name:

import { createMcpHandler } from 'mcp-handler';
import { z } from 'zod';

const handler = createMcpHandler(
  (server) => {
    server.registerTool(
      'roll_dice',
      {
        title: 'Roll Dice',
        description: 'Roll an N-sided die and return the result.',
        inputSchema: z.object({
          sides: z.number().int().min(2).max(100).describe('Number of sides'),
        }),
        annotations: { readOnlyHint: true },
      },
      async ({ sides }) => {
        const value = 1 + Math.floor(Math.random() * sides);
        return { content: [{ type: 'text', text: `You rolled a ${value}.` }] };
      },
    );
  },
  { serverInfo: { name: 'dice-server', version: '1.0.0' } },
);

export { handler as GET, handler as POST };

How it works:

  • createMcpHandler(initialize, options) receives a function that registers your tools. The README's 2.x migration notes say the options object combines the SDK's server options with serverInfo, verboseLogs and onEvent.
  • serverInfo is the name clients see. Without it, the local run reported a default name of "mcp-typescript server on vercel".
  • registerTool replaces the variadic server.tool(...) from 1.x. The inputSchema is a full z.object(...), not a raw shape. Our registerTool guide goes deeper on the config fields.
  • /api/mcp is only a convention. The README says mcp-handler does not inspect the path, so any route works as long as clients get the full URL.

The route showed up in next build output as a dynamic (ƒ) route, which is what you want for an API endpoint.

Step 3: Test it locally

Start Next.js with npm run dev, then open the Inspector:

npx @modelcontextprotocol/inspector@latest

Vercel's guide walks through the browser UI. Open http://127.0.0.1:6274, select Streamable HTTP, enter http://localhost:3000/api/mcp, paste the Proxy Session Token printed in your terminal under Configuration, and select Connect. Then list tools and call roll_dice. Those steps describe the older Inspector proxy. Inspector v2 has no separate proxy, so open the exact URL the launcher prints, which carries the session token.

For a scripted check, the Inspector CLI works without the browser:

npx @modelcontextprotocol/inspector --cli http://localhost:3000/api/mcp \
  --protocol-era modern --method initialize --format json

In the local run, this reported "protocolVersion":"2026-07-28" and the dice-server name. A tools/list call with --protocol-era legacy returned the same tool, which matches the README's dual-era claim. Calling the tool with {"sides":1} returned an isError result with "Too small: expected number to be >=2" before the handler ran, so Zod validation is doing its job.

Our MCP Inspector lesson explains how to read these results.

Step 4: Deploy to Vercel

You have two options:

  1. Git. Import the project's Git repository into Vercel, and pushes build new deployments.
  2. CLI. From the project root, run vercel for a deployment and vercel --prod for production. The vercel deploy reference notes that a new project's first deployment is always a production deployment, even without --prod.

The guide says you will get a URL such as https://my-mcp-server.vercel.app, so the endpoint is https://my-mcp-server.vercel.app/api/mcp. Run the Inspector against that URL before you connect a real client.

Vercel's guide lists the platform features it sees as useful for MCP: Vercel Functions with Fluid compute for bursty traffic, Instant Rollback, preview deployments with Deployment Protection, Vercel Firewall and Rolling Releases. Those are Vercel's descriptions; check your plan's limits before relying on them.

Step 5: Connect a client

Clients that support Streamable HTTP take the URL directly. Vercel's Cursor example goes in .cursor/mcp.json:

{
  "mcpServers": {
    "dice": {
      "url": "https://my-mcp-server.vercel.app/api/mcp"
    }
  }
}

For stdio-only clients, the README suggests bridging with npx -y mcp-remote <url> as the command. Our MCP config generator builds the block for Claude, VS Code and other clients.

The guide also covers agents you build yourself: the AI SDK (createMCPClient from @ai-sdk/mcp), eve and TanStack AI can all connect to the same endpoint.

Step 6: Add authorization

The dice server is public. mcp-handler includes two helpers for protecting it, and the guide is clear that the package "does not issue tokens or run an authorization server." You bring that part, from an identity provider or your own service.

First, wrap the handler:

import type { AuthInfo } from '@modelcontextprotocol/server';
import { withMcpAuth } from 'mcp-handler';

const verifyToken = async (
  _req: Request,
  bearerToken?: string,
): Promise<AuthInfo | undefined> => {
  // Demo only: replace with real checks of issuer, audience, expiry and scopes.
  if (!bearerToken || bearerToken !== process.env.MCP_DEMO_TOKEN) return undefined;
  return { token: bearerToken, scopes: ['read:dice'], clientId: 'demo-client' };
};

const authHandler = withMcpAuth(handler, verifyToken, {
  required: true,
  requiredScopes: ['read:dice'],
  resourceMetadataPath: '/.well-known/oauth-protected-resource',
});

export { authHandler as GET, authHandler as POST };

The guide's version compares the token with an MCP_DEMO_TOKEN environment variable for local testing only. Per the guide, requests without a valid token get 401, and a valid token missing a required scope gets 403.

Second, publish the OAuth protected resource metadata at app/.well-known/oauth-protected-resource/route.ts using protectedResourceHandler, with authServerUrls set to your authorization server's issuer and resourceUrl set to the full /api/mcp URL. Export the handler as GET and metadataCorsOptionsRequestHandler() as OPTIONS, as the guide shows. That document is how clients discover where to sign in.

The README notes that the 2026-07-28 spec deprecates Dynamic Client Registration in favor of Client ID Metadata Documents, and that CIMD support is the authorization server's job.

Common pitfalls

  • Mixing v1 and v2. mcp-handler 2.x with @modelcontextprotocol/sdk 1.x will not work. Match the major versions.
  • Old 1.x options. The README says basePath, sseEndpoint, redisUrl, maxDuration and sessionIdGenerator were removed. Mount the handler at the route you want instead.
  • Expecting SSE. HTTP+SSE (2024-11-05) support is removed in 2.x. Session GET and DELETE requests answer 405, because serving is stateless.
  • Protected previews. Deployment Protection controls who can open preview URLs, so a remote MCP client may be blocked from a protected preview. Test previews with your own session or use production for client tests.
  • Reading extra.authInfo. In 2.x, auth info moved to ctx.http?.authInfo inside handler callbacks.

For a comparison of hosting platforms, read Cloudflare Workers vs Vercel for MCP Hosting.

Next step

Once the dice server works end to end, swap in a tool that reads real data and add authorization before you share the URL. If you want a team to design the tools, auth and rollout for a production MCP server, see our MCP development service.

More in the MCP Guide

Free tools: server.json Validator · MCP Error Code Lookup

Latest research: State of MCP: October 2026

Frequently asked questions

Does mcp-handler support MCP SDK v2?

Yes. The README says mcp-handler 2.x is built on MCP SDK v2 and requires @modelcontextprotocol/server ^2.0.0. If you are still on @modelcontextprotocol/sdk 1.x, the README says to use mcp-handler 1.x.

Do I need Redis to run an MCP server on Vercel?

Not with mcp-handler 2.x. The README says Redis "is no longer needed or used," because the SSE transport it supported was removed and the handler is stateless.

Can I use mcp-handler without Next.js?

Yes. It returns a Web-standard request handler, and the README lists Nuxt/Nitro, SvelteKit, Hono and other Fetch-compatible frameworks. Express needs a Web Request adapter or the official SDK middleware.

Is this the same as the Vercel MCP server?

No. This guide hosts your own MCP server on Vercel. The Vercel MCP server is Vercel's own hosted server for managing projects and deployments from an AI client; see our Vercel MCP server profile.

How do older MCP clients connect?

The README says 2025-era Streamable HTTP clients are served through the SDK's stateless legacy fallback from the same handler. Clients that only speak HTTP+SSE need to upgrade or use a bridge.

You Might Also like to Read

Get a FREE Website Audit

Enter your website and get a 20–30 page SEO, GEO, AEO & CRO audit within 24 hours. Completely free.

Don't have a site yet? Get in touch →

Ready to speak with a marketing expert?
Give us a call! (916) 866-7893

Websites Launched
1407
Websites Launched
Marketing Experts
29
Marketing Experts
Hours of Dedicated Work
204533
Hours of Dedicated Work
Irina S.Benjamin G.Jeanette M.David G.