Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

createMcpHandler

createMcpHandler 函数用于创建一个 fetch handler 来对外提供你的 MCP server。当你需要在普通 Worker(没有 Durable Object)中运行无状态的 MCP server 时使用它。如果需要跨请求保留状态的有状态 MCP server,请改用 McpAgent 类。

它使用基于 Web 标准实现的 MCP Transport 接口 WorkerTransport,该实现符合 streamable-http ↗ 传输规范。

TypeScript


import { createMcpHandler, type CreateMcpHandlerOptions } from "agents/mcp";

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";


function createMcpHandler(

  server: McpServer,

  options?: CreateMcpHandlerOptions,

): (request: Request, env: Env, ctx: ExecutionContext) => Promise<Response>;


参数

返回值

一个 Worker fetch handler 函数,签名为 (request: Request, env: unknown, ctx: ExecutionContext) => Promise<Response>

CreateMcpHandlerOptions

创建 MCP handler 的配置选项。

TypeScript


interface CreateMcpHandlerOptions extends WorkerTransportOptions {

  /**

   * The route path that this MCP handler should respond to.

   * If specified, the handler will only process requests that match this route.

   * @default "/mcp"

   */

  route?: string;


  /**

   * An optional auth context to use for handling MCP requests.

   * If not provided, the handler will look for props in the execution context.

   */

  authContext?: McpAuthContext;


  /**

   * An optional transport to use for handling MCP requests.

   * If not provided, a WorkerTransport will be created with the provided WorkerTransportOptions.

   */

  transport?: WorkerTransport;


  // Inherited from WorkerTransportOptions:

  sessionIdGenerator?: () => string;

  enableJsonResponse?: boolean;

  onsessioninitialized?: (sessionId: string) => void;

  corsOptions?: CORSOptions;

  storage?: MCPStorageApi;

}


Explain Code

选项

route

MCP handler 响应的 URL 路径。请求其他路径将返回 404。

默认值: "/mcp"

JavaScript


const handler = createMcpHandler(server, {

  route: "/api/mcp", // Only respond to requests at /api/mcp

});


TypeScript


const handler = createMcpHandler(server, {

  route: "/api/mcp", // Only respond to requests at /api/mcp

});


authContext

一个认证上下文对象,会通过 getMcpAuthContext() 提供给 MCP 工具使用。

如果你使用 @cloudflare/workers-oauth-provider 中的 OAuthProvider,认证上下文会自动从 OAuth 流程中填充,通常无需手动设置。

transport

自定义的 WorkerTransport 实例。如果未提供,每次请求都会创建一个新的 transport。

JavaScript


import { createMcpHandler, WorkerTransport } from "agents/mcp";


const transport = new WorkerTransport({

  sessionIdGenerator: () => `session-${crypto.randomUUID()}`,

  storage: {

    get: () => myStorage.get("transport-state"),

    set: (state) => myStorage.put("transport-state", state),

  },

});


const handler = createMcpHandler(server, { transport });


Explain Code

TypeScript


import { createMcpHandler, WorkerTransport } from "agents/mcp";


const transport = new WorkerTransport({

  sessionIdGenerator: () => `session-${crypto.randomUUID()}`,

  storage: {

    get: () => myStorage.get("transport-state"),

    set: (state) => myStorage.put("transport-state", state),

  },

});


const handler = createMcpHandler(server, { transport });


Explain Code

无状态 MCP 服务器

许多 MCP 服务器是无状态的,即在请求之间不维护任何会话状态。createMcpHandlerMcpAgent 类的轻量级替代方案,可直接从 Worker 提供 MCP 服务。完整示例请见 GitHub ↗

MCP SDK 1.26.0 的破坏性变更

重要: 如果你正从 1.26.0 之前的 MCP SDK 升级,必须更新无状态服务器中创建 McpServer 实例的方式。

MCP SDK 1.26.0 引入了一项保护机制,禁止将服务器实例连接到一个已经连接过的 transport。此举修复了一个安全漏洞 (CVE ↗):共享 server 或 transport 实例可能导致跨客户端响应数据泄露。

如果你的无状态 MCP 服务器在全局作用域中声明了 McpServer 或 transport 实例,你必须改为按请求创建新的实例。

详细内容请参阅下面的 MCP SDK 1.26.0 迁移指南

JavaScript


import { createMcpHandler } from "agents/mcp";

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

import { z } from "zod";


function createServer() {

  const server = new McpServer({

    name: "Hello MCP Server",

    version: "1.0.0",

  });


  server.tool(

    "hello",

    "Returns a greeting message",

    { name: z.string().optional() },

    async ({ name }) => {

      return {

        content: [

          {

            text: `Hello, ${name ?? "World"}!`,

            type: "text",

          },

        ],

      };

    },

  );


  return server;

}


export default {

  fetch: async (request, env, ctx) => {

    // Create new server instance per request

    const server = createServer();

    return createMcpHandler(server)(request, env, ctx);

  },

};


Explain Code

TypeScript


import { createMcpHandler } from "agents/mcp";

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

import { z } from "zod";


function createServer() {

  const server = new McpServer({

    name: "Hello MCP Server",

    version: "1.0.0",

  });


  server.tool(

    "hello",

    "Returns a greeting message",

    { name: z.string().optional() },

    async ({ name }) => {

      return {

        content: [

          {

            text: `Hello, ${name ?? "World"}!`,

            type: "text",

          },

        ],

      };

    },

  );


  return server;

}


export default {

  fetch: async (request: Request, env: Env, ctx: ExecutionContext) => {

    // Create new server instance per request

    const server = createServer();

    return createMcpHandler(server)(request, env, ctx);

  },

} satisfies ExportedHandler<Env>;


Explain Code

每次请求该 MCP 服务器都会创建一个新的会话与 server 实例。服务器在请求之间不保留任何状态。这是实现 MCP 服务器最简单的方式。

有状态 MCP 服务器

对于需要跨多个请求维护会话状态的 MCP 服务器,你可以在 Agent 中直接使用 createMcpHandler 配合 WorkerTransport 实例。这在你需要使用诸如 elicitation、sampling 等高级客户端特性时非常有用。

提供一个具备持久化存储的自定义 WorkerTransport。完整示例请见 GitHub ↗

JavaScript


import { Agent } from "agents";

import { createMcpHandler, WorkerTransport } from "agents/mcp";

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";


const STATE_KEY = "mcp-transport-state";


export class MyStatefulMcpAgent extends Agent {

  server = new McpServer({

    name: "Stateful MCP Server",

    version: "1.0.0",

  });


  transport = new WorkerTransport({

    sessionIdGenerator: () => this.name,

    storage: {

      get: () => {

        return this.ctx.storage.get(STATE_KEY);

      },

      set: (state) => {

        this.ctx.storage.put(STATE_KEY, state);

      },

    },

  });


  async onRequest(request) {

    return createMcpHandler(this.server, {

      transport: this.transport,

    })(request, this.env, this.ctx);

  }

}


Explain Code

TypeScript


import { Agent } from "agents";

import {

  createMcpHandler,

  WorkerTransport,

  type TransportState,

} from "agents/mcp";

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";


const STATE_KEY = "mcp-transport-state";


type State = { counter: number };


export class MyStatefulMcpAgent extends Agent<Env, State> {

  server = new McpServer({

    name: "Stateful MCP Server",

    version: "1.0.0",

  });


  transport = new WorkerTransport({

    sessionIdGenerator: () => this.name,

    storage: {

      get: () => {

        return this.ctx.storage.get<TransportState>(STATE_KEY);

      },

      set: (state: TransportState) => {

        this.ctx.storage.put(STATE_KEY, state);

      },

    },

  });


  async onRequest(request: Request) {

    return createMcpHandler(this.server, {

      transport: this.transport,

    })(request, this.env, this.ctx as unknown as ExecutionContext);

  }

}


Explain Code

在该例中我们将 sessionIdGenerator 定义为返回 Agent 名称作为 session ID。为了确保路由到正确的 Agent,可以在 Worker handler 中使用 getAgentByName:

JavaScript


import { getAgentByName } from "agents";


export default {

  async fetch(request, env, ctx) {

    // Extract session ID from header or generate a new one

    const sessionId =

      request.headers.get("mcp-session-id") ?? crypto.randomUUID();


    // Get the Agent instance by name/session ID

    const agent = await getAgentByName(env.MyStatefulMcpAgent, sessionId);


    // Route the MCP request to the agent

    return await agent.onRequest(request);

  },

};


Explain Code

TypeScript


import { getAgentByName } from "agents";


export default {

  async fetch(request: Request, env: Env, ctx: ExecutionContext) {

    // Extract session ID from header or generate a new one

    const sessionId =

      request.headers.get("mcp-session-id") ?? crypto.randomUUID();


    // Get the Agent instance by name/session ID

    const agent = await getAgentByName(env.MyStatefulMcpAgent, sessionId);


    // Route the MCP request to the agent

    return await agent.onRequest(request);

  },

} satisfies ExportedHandler<Env>;


Explain Code

通过持久化存储,transport 会保留:

  • 跨重连的 Session ID
  • 协议版本协商状态
  • 初始化状态

这让 MCP 客户端在连接丢失后能够重新连接并恢复其会话。

MCP SDK 1.26.0 迁移指南

MCP SDK 1.26.0 为无状态 MCP 服务器引入了破坏性变更,以解决一个严重的安全漏洞:在共享 server 或 transport 实例的情况下,一个客户端的响应可能泄露到另一个客户端。

谁会受影响?

服务器类型是否受影响?需要的操作
使用 Agent/Durable Object 的有状态服务器无需更改
使用 createMcpHandler 的无状态服务器按请求创建新的 McpServer
使用原生 SDK transport 的无状态服务器按请求创建新的 McpServer 与 transport

为什么必须这样做?

之前那种在全局作用域声明 McpServer 实例的写法可能让一个客户端的响应泄露给另一个客户端,这是一个安全漏洞。新版本 SDK 通过在你尝试连接一个已连接的 server 时抛出错误来阻止这种情况。

之前的写法(在 SDK 1.26.0 下会失败)

JavaScript


import { createMcpHandler } from "agents/mcp";

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";


// INCORRECT: Global server instance

const server = new McpServer({

  name: "Hello MCP Server",

  version: "1.0.0",

});


server.tool("hello", "Returns a greeting", {}, async () => {

  return {

    content: [{ text: "Hello, World!", type: "text" }],

  };

});


export default {

  fetch: async (request, env, ctx) => {

    // This will fail on second request with MCP SDK 1.26.0+

    return createMcpHandler(server)(request, env, ctx);

  },

};


Explain Code

TypeScript


import { createMcpHandler } from "agents/mcp";

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";


// INCORRECT: Global server instance

const server = new McpServer({

  name: "Hello MCP Server",

  version: "1.0.0",

});


server.tool("hello", "Returns a greeting", {}, async () => {

  return {

    content: [{ text: "Hello, World!", type: "text" }],

  };

});


export default {

  fetch: async (request: Request, env: Env, ctx: ExecutionContext) => {

    // This will fail on second request with MCP SDK 1.26.0+

    return createMcpHandler(server)(request, env, ctx);

  },

} satisfies ExportedHandler<Env>;


Explain Code

修复后的写法(正确)

JavaScript


import { createMcpHandler } from "agents/mcp";

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";


// CORRECT: Factory function to create server instance

function createServer() {

  const server = new McpServer({

    name: "Hello MCP Server",

    version: "1.0.0",

  });


  server.tool("hello", "Returns a greeting", {}, async () => {

    return {

      content: [{ text: "Hello, World!", type: "text" }],

    };

  });


  return server;

}


export default {

  fetch: async (request, env, ctx) => {

    // Create new server instance per request

    const server = createServer();

    return createMcpHandler(server)(request, env, ctx);

  },

};


Explain Code

TypeScript


import { createMcpHandler } from "agents/mcp";

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";


// CORRECT: Factory function to create server instance

function createServer() {

  const server = new McpServer({

    name: "Hello MCP Server",

    version: "1.0.0",

  });


  server.tool("hello", "Returns a greeting", {}, async () => {

    return {

      content: [{ text: "Hello, World!", type: "text" }],

    };

  });


  return server;

}


export default {

  fetch: async (request: Request, env: Env, ctx: ExecutionContext) => {

    // Create new server instance per request

    const server = createServer();

    return createMcpHandler(server)(request, env, ctx);

  },

} satisfies ExportedHandler<Env>;


Explain Code

直接使用原生 SDK transport 的用户

如果你直接使用原生 SDK transport(没有走 createMcpHandler),也必须按请求创建新的 transport 实例:

JavaScript


import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";


function createServer() {

  const server = new McpServer({

    name: "Hello MCP Server",

    version: "1.0.0",

  });


  // Register tools...


  return server;

}


export default {

  async fetch(request) {

    // Create new transport and server per request

    const transport = new WebStandardStreamableHTTPServerTransport();

    const server = createServer();

    server.connect(transport);

    return transport.handleRequest(request);

  },

};


Explain Code

TypeScript


import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";


function createServer() {

  const server = new McpServer({

    name: "Hello MCP Server",

    version: "1.0.0",

  });


  // Register tools...


  return server;

}


export default {

  async fetch(request: Request) {

    // Create new transport and server per request

    const transport = new WebStandardStreamableHTTPServerTransport();

    const server = createServer();

    server.connect(transport);

    return transport.handleRequest(request);

  },

} satisfies ExportedHandler<Env>;


Explain Code

WorkerTransport

WorkerTransport 类实现了 MCP Transport 接口,负责处理 HTTP 请求/响应循环、Server-Sent Events (SSE) 流式传输、会话管理与 CORS。

TypeScript


class WorkerTransport implements Transport {

  sessionId?: string;

  started: boolean;

  onclose?: () => void;

  onerror?: (error: Error) => void;

  onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void;


  constructor(options?: WorkerTransportOptions);


  async handleRequest(

    request: Request,

    parsedBody?: unknown,

  ): Promise<Response>;

  async send(

    message: JSONRPCMessage,

    options?: TransportSendOptions,

  ): Promise<void>;

  async start(): Promise<void>;

  async close(): Promise<void>;

}


Explain Code

构造函数选项

TypeScript


interface WorkerTransportOptions {

  /**

   * Function that generates a unique session ID.

   * Called when a new session is initialized.

   */

  sessionIdGenerator?: () => string;


  /**

   * Enable traditional Request/Response mode, disabling streaming.

   * When true, responses are returned as JSON instead of SSE streams.

   * @default false

   */

  enableJsonResponse?: boolean;


  /**

   * Callback invoked when a session is initialized.

   * Receives the generated or restored session ID.

   */

  onsessioninitialized?: (sessionId: string) => void;


  /**

   * CORS configuration for cross-origin requests.

   * Configures Access-Control-* headers.

   */

  corsOptions?: CORSOptions;


  /**

   * Optional storage API for persisting transport state.

   * Use this to store session state in Durable Object/Agent storage

   * so it survives hibernation/restart.

   */

  storage?: MCPStorageApi;

}


Explain Code

sessionIdGenerator

提供自定义的会话标识符。该会话标识符用于在 MCP 客户端中识别会话。

JavaScript


const transport = new WorkerTransport({

  sessionIdGenerator: () => `user-${Date.now()}-${Math.random()}`,

});


TypeScript


const transport = new WorkerTransport({

  sessionIdGenerator: () => `user-${Date.now()}-${Math.random()}`,

});


enableJsonResponse

禁用 SSE 流式传输,以标准 JSON 形式返回响应。

JavaScript


const transport = new WorkerTransport({

  enableJsonResponse: true, // Disable streaming, return JSON responses

});


TypeScript


const transport = new WorkerTransport({

  enableJsonResponse: true, // Disable streaming, return JSON responses

});


onsessioninitialized

会话初始化时触发的回调,无论是新建会话还是从存储恢复都会触发。

JavaScript


const transport = new WorkerTransport({

  onsessioninitialized: (sessionId) => {

    console.log(`MCP session initialized: ${sessionId}`);

  },

});


TypeScript


const transport = new WorkerTransport({

  onsessioninitialized: (sessionId) => {

    console.log(`MCP session initialized: ${sessionId}`);

  },

});


corsOptions

为跨域请求配置 CORS 头。

TypeScript


interface CORSOptions {

  origin?: string;

  methods?: string;

  headers?: string;

  maxAge?: number;

  exposeHeaders?: string;

}


JavaScript


const transport = new WorkerTransport({

  corsOptions: {

    origin: "https://example.com",

    methods: "GET, POST, OPTIONS",

    headers: "Content-Type, Authorization",

    maxAge: 86400,

  },

});


TypeScript


const transport = new WorkerTransport({

  corsOptions: {

    origin: "https://example.com",

    methods: "GET, POST, OPTIONS",

    headers: "Content-Type, Authorization",

    maxAge: 86400,

  },

});


storage

持久化 transport 状态,使其在 Durable Object 休眠或重启时仍可恢复。

TypeScript


interface MCPStorageApi {

  get(): Promise<TransportState | undefined> | TransportState | undefined;

  set(state: TransportState): Promise<void> | void;

}


interface TransportState {

  sessionId?: string;

  initialized: boolean;

  protocolVersion?: ProtocolVersion;

}


Explain Code

JavaScript


// Inside an Agent or Durable Object class method:

const transport = new WorkerTransport({

  storage: {

    get: async () => {

      return await this.ctx.storage.get("mcp-state");

    },

    set: async (state) => {

      await this.ctx.storage.put("mcp-state", state);

    },

  },

});


Explain Code

TypeScript


// Inside an Agent or Durable Object class method:

const transport = new WorkerTransport({

  storage: {

    get: async () => {

      return await this.ctx.storage.get<TransportState>("mcp-state");

    },

    set: async (state) => {

      await this.ctx.storage.put("mcp-state", state);

    },

  },

});


Explain Code

认证上下文

当通过 OAuth 认证createMcpHandler 配合使用时,用户信息会通过 getMcpAuthContext() 提供给你的 MCP 工具。其底层使用 AsyncLocalStorage 将请求传递到工具处理器,从而保持认证上下文可用。

TypeScript


interface McpAuthContext {

  props: Record<string, unknown>;

}


getMcpAuthContext

在 MCP 工具处理器内部获取当前的认证上下文。它返回的是 OAuth provider 填充的用户信息。注意:如果你使用的是 McpAgent,这些信息可以直接通过 this.props 访问。

TypeScript


import { getMcpAuthContext } from "agents/mcp";


function getMcpAuthContext(): McpAuthContext | undefined;


JavaScript


import { getMcpAuthContext } from "agents/mcp";

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";


function createServer() {

  const server = new McpServer({ name: "Auth Server", version: "1.0.0" });


  server.tool("getProfile", "Get the current user's profile", {}, async () => {

    const auth = getMcpAuthContext();

    const username = auth?.props?.username;

    const email = auth?.props?.email;


    return {

      content: [

        {

          type: "text",

          text: `User: ${username ?? "anonymous"}, Email: ${email ?? "none"}`,

        },

      ],

    };

  });


  return server;

}


Explain Code

TypeScript


import { getMcpAuthContext } from "agents/mcp";

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";


function createServer() {

  const server = new McpServer({ name: "Auth Server", version: "1.0.0" });


  server.tool("getProfile", "Get the current user's profile", {}, async () => {

    const auth = getMcpAuthContext();

    const username = auth?.props?.username as string | undefined;

    const email = auth?.props?.email as string | undefined;


    return {

      content: [

        {

          type: "text",

          text: `User: ${username ?? "anonymous"}, Email: ${email ?? "none"}`,

        },

      ],

    };

  });


  return server;

}


Explain Code

注意

完整介绍如何为 MCP 服务器配置 OAuth 认证,请参阅 MCP Authorization 文档。完整的“Worker 中的认证 MCP 服务器“示例请见 GitHub ↗

错误处理

createMcpHandler 会自动捕获错误,并返回错误码为 -32603(Internal error)的 JSON-RPC 错误响应。

JavaScript


server.tool("riskyOperation", "An operation that might fail", {}, async () => {

  if (Math.random() > 0.5) {

    throw new Error("Random failure occurred");

  }

  return {

    content: [{ type: "text", text: "Success!" }],

  };

});


// Errors are automatically caught and returned as:

// {

//   "jsonrpc": "2.0",

//   "error": {

//     "code": -32603,

//     "message": "Random failure occurred"

//   },

//   "id": <request_id>

// }


Explain Code

TypeScript


server.tool("riskyOperation", "An operation that might fail", {}, async () => {

  if (Math.random() > 0.5) {

    throw new Error("Random failure occurred");

  }

  return {

    content: [{ type: "text", text: "Success!" }],

  };

});


// Errors are automatically caught and returned as:

// {

//   "jsonrpc": "2.0",

//   "error": {

//     "code": -32603,

//     "message": "Random failure occurred"

//   },

//   "id": <request_id>

// }


Explain Code

相关资源

Building MCP Servers 在 Cloudflare 上构建并部署 MCP 服务器。

MCP Tools 为你的 MCP 服务器添加工具。

MCP Authorization 通过 OAuth 认证用户。

McpAgent API 构建有状态的 MCP 服务器。