mcp-handler Documentation

repository·main·Indexed 20 days ago

https://github.com/vercel/mcp-handler

A framework-agnostic HTTP adapter for Model Context Protocol (MCP) servers in JavaScript/TypeScript. It converts MCP server definitions into standard Web Fetch handlers compatible with frameworks such as Next.js, Nuxt, SvelteKit, and Hono. Version 2.1.0 supports the 2026-07-28 protocol and 2025-era Streamable HTTP, providing tools for authorization via withMcpAuth and OAuth Protected Resource Metadata exposure.

Tokens
8.4K
Snippets
25
Records
34
Agent score
70%

What's inside mcp-handler

  1. CIMD (Client ID Metadata Documents) Support

    main

    The MCP specification is moving from Dynamic Client Registration (DCR) to CIMD.

    In the CIMD model, OAuth clients identify themselves via an HTTPS URL (client_id) that serves their metadata document. As a resource server (the MCP deployment), you do not need to implement CIMD logic directly; you simply provide the Protected Resource Metadata endpoint so clients can discover your authorization servers. The negotiation of CIMD happens between the client and the authorization server.

  2. Framework Compatibility for mcp-handler

    main

    Because createMcpHandler returns a Web-standard (Request) => Promise<Response> handler, it is compatible with any framework that exposes Fetch-compatible APIs.

    Supported Frameworks:

    • Next.js: Use Route Handlers.
    • Nuxt/Nitro: Use server handlers with fromWebHandler.
    • SvelteKit: Use server routes.
    • Hono: Use routes with c.req.raw.

    Note for Node.js-based frameworks (e.g., Express): These frameworks use IncomingMessage and ServerResponse instead of Web Standards. You must use a Web Request adapter or the official MCP framework middleware to bridge them.

  3. Understand the MCP Authorization Flow

    main

    The authorization lifecycle follows these steps:

    1. Request: The client sends a request with a Bearer token in the Authorization header.
    2. Verification: The verifyToken function is called to validate the token.
    3. Validation:
      • If authentication is required and fails $\rightarrow$ 401 Unauthorized.
      • If required scopes are missing $\rightarrow$ 403 Forbidden.
    4. Execution: On success, the tool handler executes, and authentication details are injected into ctx.http?.authInfo.
  4. Configure Claude Desktop for MCP

    main

    To use your MCP server with Claude Desktop, edit your local configuration file. If the file does not exist, you may need to enable it under Settings > Developer in the Claude Desktop application.

    Configuration File Paths:

    • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
    • Windows: %APPDATA%\Claude\claude_desktop_config.json

    After editing, restart Claude Desktop. A hammer icon should appear in the bottom right corner of the input box if the connection is successful.

    {
      "remote-example": {
        "command": "npx",
        "args": ["-y", "mcp-remote", "http://localhost:3000/api/mcp"]
      }
    }
  5. Implement authorization with withMcpAuth

    main

    To secure your MCP server, wrap your handler using the withMcpAuth function. This requires a verifyToken function that validates the incoming request and returns an AuthInfo object.

    AuthInfo includes:

    • token: The validated bearer token.
    • scopes: An array of granted scopes.
    • clientId: The unique identifier for the client.
    • extra: An object for additional metadata.

    Once wrapped, you can access the authenticated user's information within your tool handlers via ctx.http?.authInfo.

    import { createMcpHandler, withMcpAuth } from "mcp-handler";
    import type { AuthInfo } from "@modelcontextprotocol/server";
    
    const handler = createMcpHandler((server) => {
      server.registerTool(
        "example",
        { /* ... schema ... */ },
        async (input, ctx) => {
          const authInfo = ctx.http?.authInfo;
          // Use authInfo.clientId or authInfo.token
          return { content: [{ type: "text", text: `Hello ${authInfo?.clientId}` }] };
        }
      );
    }, {});
    
    // Token verification function
    const verifyToken = async (req: Request, bearerToken?: string): Promise<AuthInfo | undefined> => {
      // Implement your actual validation logic here
      if (!bearerToken) return undefined;
      return {
        token: bearerToken,
        scopes: ["read:stuff"],
        clientId: "user123",
        extra: { userId: "123" },
      };
    };
    
    // Wrap handler with authorization
    const authHandler = withMcpAuth(handler, verifyToken, {
      required: true,
      requiredScopes: ["read:stuff"],
      resourceMetadataPath: "/.well-known/oauth-protected-resource",
    });
    
    export { authHandler as GET, authHandler as POST };
  6. Connect to an MCP server using mcp-remote

    main

    For clients that only support stdio (standard input/output) instead of HTTP, use the mcp-remote package to proxy the Streamable HTTP connection. This allows you to treat an HTTP-based MCP server as a local stdio process.

    {
      "remote-example": {
        "command": "npx",
        "args": ["-y", "mcp-remote", "http://localhost:3000/api/mcp"]
      }
    }
  7. Implement dynamic routing for multi-tenant MCP servers

    main

    To support multi-tenant or dynamic MCP servers, you can use dynamic route segments (e.g., in Next.js [p]/mcp/route.ts) to capture tenant identifiers. Pass the request object to the function returned by createMcpHandler. This allows you to register tools or configure server behavior based on the dynamic parameters extracted from the URL.

    // app/dynamic/[p]/mcp/route.ts
    import { createMcpHandler } from "mcp-handler";
    import type { NextRequest } from "next/server";
    import { z } from "zod";
    
    const handler = async (
      req: NextRequest,
      { params }: { params: Promise<{ p: string }> },
    ) => {
      const { p } = await params;
    
      return createMcpHandler((server) => {
        server.registerTool(
          "roll_dice",
          {
            title: "Roll Dice",
            description: `Roll a dice for tenant ${p}.`,
            inputSchema: z.object({ sides: z.number().int().min(2) }),
          },
          async ({ sides }) => {
            const value = 1 + Math.floor(Math.random() * sides);
            return {
              content: [
                { type: "text", text: `🎲 Tenant ${p} rolled a ${value}!` },
              ],
            };
          },
        );
      })(req);
    };
    
    export { handler as GET, handler as POST };
  8. Connect to an MCP server via Direct Connection

    main

    If your MCP client supports Streamable HTTP, you can connect directly to your server by providing the endpoint URL. This is the recommended method for supported clients.

    {
      "remote-example": {
        "url": "http://localhost:3000/api/mcp"
      }
    }
  9. Migrating from mcp-handler 1.x to 2.x

    main

    When upgrading to 2.x, note the following breaking changes:

    • Dependencies: Install @modelcontextprotocol/server (v2) and zod@^4. Remove @modelcontextprotocol/sdk and redis.
    • Schema Definition: inputSchema/argsSchema now require a full Standard Schema (e.g., z.object({ ... })) instead of a raw zod shape.
    • Registration Methods: Variadic methods like server.tool(), .prompt(), and .resource() are removed. Use server.registerTool(), server.registerPrompt(), and server.registerResource() instead.
    • Context Access: In handler callbacks, extra.authInfo has changed to ctx.http?.authInfo.
    • Handler Signature: createMcpHandler(initialize, serverOptions, config) is now createMcpHandler(initialize, options). The new options object combines SDK server options with serverInfo, verboseLogs, and onEvent.
    • Configuration Removal: Route and transport config options (e.g., basePath, streamableHttpEndpoint, sseEndpoint, redisUrl, sessionIdGenerator) are removed. You must now mount the handler at the desired route within your framework.
  10. Use mcp-handler with Nuxt

    main

    To use mcp-handler in a Nuxt application, create a server route and wrap the handler returned by createMcpHandler with fromWebHandler from the h3 package.

    // server/routes/mcp.ts
    import { createMcpHandler } from "mcp-handler";
    import { fromWebHandler } from "h3";
    import { z } from "zod";
    
    const handler = createMcpHandler((server) => {
      server.registerTool(
        "roll_dice",
        {
          title: "Roll Dice",
          description: "Roll a dice with a specified number of sides.",
          inputSchema: z.object({ sides: z.number().int().min(2) }),
        },
        async ({ sides }) => {
          const value = 1 + Math.floor(Math.random() * sides);
          return {
            content: [{ type: "text", text: `🎲 You rolled a ${value}!` }],
          };
        },
      );
    });
    
    export default fromWebHandler(handler);
  11. Quick Start with Next.js

    main

    You can host an MCP server in Next.js by using createMcpHandler within a Route Handler. The handler accepts an initialization function where you register tools, prompts, or resources using the MCP SDK v2.

    Note that mcp-handler does not inspect the request pathname; you can mount the handler at any route (e.g., /api/mcp) and provide that full URL to your clients.

    // app/api/mcp/route.ts
    import { createMcpHandler } from "mcp-handler";
    import { z } from "zod";
    
    const handler = createMcpHandler((server) => {
      server.registerTool(
        "roll_dice",
        {
          title: "Roll Dice",
          description: "Roll a dice with a specified number of sides.",
          inputSchema: z.object({
            sides: z.number().int().min(2),
          }),
        },
        async ({ sides }) => {
          const value = 1 + Math.floor(Math.random() * sides);
          return {
            content: [{ type: "text", text: `🎲 You rolled a ${value}!` }],
          };
        },
      );
    });
    
    export { handler as GET, handler as POST };