Skip to main content
Glama
TLiu2014

nextjs-mcp-streamable-http

by TLiu2014

nextjs-mcp-streamable-http

A minimal, stateless MCP server on the Next.js App Router over the Streamable HTTP transport, plus a loopback MCP client that forwards per-request context in a header.

It's the setup I wanted when building RealityAnchor, an Alexa+ MCP add-on. I pulled it out into a small template so you don't have to work through the same transport and bundling issues yourself.

  • One route (/api/mcp) speaks MCP over Streamable HTTP (spec 2025-11-25)

  • Stateless: a fresh server per request, so it runs on any serverless host

  • Works with Claude Code, Claude Desktop, MCP Inspector, Cursor, Gemini, and others

  • A loopback client (/api/demo) shows how your app can call its own MCP server

  • No model, no API keys, no database. Three example tools you can replace

Quick start

pnpm install
pnpm dev                 # http://localhost:3000
pnpm smoke               # in a second terminal: list tools + call one

Connect a real MCP client:

# Claude Code
claude mcp add --transport http nextjs-mcp http://localhost:3000/api/mcp
claude mcp list          # → nextjs-mcp … ✔ Connected

# MCP Inspector (GUI)
npx @modelcontextprotocol/inspector
# Transport: Streamable HTTP · URL: http://localhost:3000/api/mcp

Try the loopback client: http://localhost:3000/api/demo?a=2&b=3&caller=alice

{ "tools": ["add", "get_time", "whoami"], "add": { "sum": 5 }, "whoami": { "caller": "alice" } }

Related MCP server: OAuth 2.1 MCP Server

Layout

src/
  app/api/mcp/route.ts    MCP endpoint (Streamable HTTP, stateless)
  app/api/demo/route.ts   Loopback MCP client + header forwarding
  lib/mcp/server.ts       Builds the MCP Server, registers tools/list + tools/call
  lib/mcp/tools.ts        Example tools: add, get_time, whoami  ← replace these
  lib/mcp/context.ts      Per-request context header helpers
scripts/smoke.mjs         External-client smoke test

To add a tool, add a definition to TOOL_DEFINITIONS and a case in callTool in src/lib/mcp/tools.ts. Nothing else needs to change.

How it works and why

Streamable HTTP, not the old SSE transport. The SDK's SSEServerTransport is deprecated and needs a raw Node http.ServerResponse, which App Router handlers never get. WebStandardStreamableHTTPServerTransport works directly on Web-standard Request/Response, which is what a route handler receives. It still streams over SSE, so you get streaming with the current transport.

Stateless by default. sessionIdGenerator: undefined means no session state between requests. Each request builds a fresh Server and closes it when the response finishes. Any instance can serve any request, so it fits serverless hosting. If you need server-to-client push or long-lived sessions, switch to stateful mode and keep sessions in a shared store.

One handler for three verbs. Streamable HTTP uses POST (JSON-RPC), GET (the SSE stream), and DELETE (end a session), so the route exports the same handler for all three.

Route config that matters.

  • runtime = "nodejs": the SDK doesn't run on the Edge runtime.

  • dynamic = "force-dynamic": MCP responses must never be cached.

  • serverExternalPackages: ["@modelcontextprotocol/sdk"] in next.config.mjs: stops Next from bundling the SDK's optional transports and schema validator into the route.

Loopback client + header forwarding. An in-app agent can be a real MCP client of your own server (new URL("/api/mcp", origin)), so the tool calls it shows are actual MCP round-trips. StreamableHTTPClientTransport accepts requestInit.headers, so you can pass per-request context such as a user id or a browser-supplied API key (BYOK). The server reads it for that one request and never stores it. RealityAnchor uses this to send a user's own Amazon Bedrock key through its in-app agent's MCP call.

Only trust the context header from callers you control. External MCP clients can set any header they want. If the context carries anything sensitive or privileged, authenticate the caller first.

Gotchas I ran into

  • Route files can only export handlers and route config. Put shared constants (like the context header name) in lib/, or next build will fail.

  • A stateless server has no push channel. Tool calls from an external client (say, in a terminal) can't update a browser UI by themselves. Use a separate path, such as a shared store plus SSE, if you need that.

  • Check which port you're on. If another app is already on :3000, the client connects to it, gets HTML back, and the error doesn't make that clear. PORT=3030 pnpm dev avoids it.

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A template for deploying Model Context Protocol servers as Next.js routes using the mcp-handler adapter, enabling easy integration of MCP tools, prompts, and resources into any Next.js application.
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    A Next.js-based MCP server template that implements OAuth 2.1 authentication with support for both SSE and Streamable HTTP transports. Provides a reference implementation for building authenticated MCP servers that work with Claude, Cursor, VSCode, and other MCP clients.
    12 npm
    6
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    A minimal Next.js starter for building MCP Apps, enabling MCP hosts to render interactive UIs alongside tool calls via an MCP server endpoint and a React widget UI.
    -