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

McpAgent

当你在 Cloudflare 上构建 MCP Server 时,你扩展来自 Agents SDK 的 McpAgent class ↗:

JavaScript


import { McpAgent } from "agents/mcp";

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

import { z } from "zod";


export class MyMCP extends McpAgent {

  server = new McpServer({ name: "Demo", version: "1.0.0" });


  async init() {

    this.server.tool(

      "add",

      { a: z.number(), b: z.number() },

      async ({ a, b }) => ({

        content: [{ type: "text", text: String(a + b) }],

      }),

    );

  }

}


Explain Code

TypeScript


import { McpAgent } from "agents/mcp";

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

import { z } from "zod";


export class MyMCP extends McpAgent {

  server = new McpServer({ name: "Demo", version: "1.0.0" });


  async init() {

    this.server.tool(

      "add",

      { a: z.number(), b: z.number() },

      async ({ a, b }) => ({

        content: [{ type: "text", text: String(a + b) }],

      }),

    );

  }

}


Explain Code

这意味着你的 MCP 服务器的每个实例都有自己的持久化状态,由 Durable Object 支持,带有自己的 SQL database

你的 MCP 服务器不一定要是 Agent。你可以构建无状态的 MCP 服务器,使用 @modelcontextprotocol/sdk 包向你的 MCP 服务器添加 tools

但如果你希望你的 MCP 服务器:

  • 记住先前的工具调用以及它提供的响应
  • 向 MCP 客户端提供游戏,记住游戏棋盘状态、之前的移动和分数
  • 缓存先前外部 API 调用的状态,以便后续工具调用可以重用
  • 做 Agent 能做的任何事情,但允许 MCP 客户端与之通信

你可以使用下面的 API 来实现。

API 概览

属性/方法描述
state当前状态对象(已持久化)
initialState实例启动时的默认状态
setState(state)更新并持久化状态
onStateChanged(state)状态更改时调用
sql在嵌入式数据库上执行 SQL 查询
server用于注册工具的 McpServer 实例
props来自 OAuth 认证的用户身份和 token
elicitInput(options, context)从用户请求结构化输入
McpAgent.serve(path, options)创建 Worker handler 的静态方法

使用 McpAgent.serve() 部署

McpAgent.serve() 静态方法创建一个将请求路由到你的 MCP 服务器的 Worker handler:

JavaScript


import { McpAgent } from "agents/mcp";

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

import { z } from "zod";


export class MyMCP extends McpAgent {

  server = new McpServer({ name: "my-server", version: "1.0.0" });


  async init() {

    this.server.tool("square", { n: z.number() }, async ({ n }) => ({

      content: [{ type: "text", text: String(n * n) }],

    }));

  }

}


// Export the Worker handler

export default MyMCP.serve("/mcp");


Explain Code

TypeScript


import { McpAgent } from "agents/mcp";

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

import { z } from "zod";


export class MyMCP extends McpAgent {

  server = new McpServer({ name: "my-server", version: "1.0.0" });


  async init() {

    this.server.tool("square", { n: z.number() }, async ({ n }) => ({

      content: [{ type: "text", text: String(n * n) }],

    }));

  }

}


// Export the Worker handler

export default MyMCP.serve("/mcp");


Explain Code

这是部署 MCP 服务器最简单的方式 — 大约 15 行代码。serve() 方法自动处理 Streamable HTTP 传输。

使用 OAuth 认证

使用 OAuth Provider Library ↗ 时,将你的 MCP 服务器传递给 apiHandlers:

JavaScript


import { OAuthProvider } from "@cloudflare/workers-oauth-provider";


export default new OAuthProvider({

  apiHandlers: { "/mcp": MyMCP.serve("/mcp") },

  authorizeEndpoint: "/authorize",

  tokenEndpoint: "/token",

  clientRegistrationEndpoint: "/register",

  defaultHandler: AuthHandler,

});


TypeScript


import { OAuthProvider } from "@cloudflare/workers-oauth-provider";


export default new OAuthProvider({

  apiHandlers: { "/mcp": MyMCP.serve("/mcp") },

  authorizeEndpoint: "/authorize",

  tokenEndpoint: "/token",

  clientRegistrationEndpoint: "/register",

  defaultHandler: AuthHandler,

});


数据司法管辖区

为了 GDPR 和数据驻留合规性,指定一个司法管辖区以确保你的 MCP 服务器实例在特定区域中运行:

JavaScript


// EU jurisdiction for GDPR compliance

export default MyMCP.serve("/mcp", { jurisdiction: "eu" });


TypeScript


// EU jurisdiction for GDPR compliance

export default MyMCP.serve("/mcp", { jurisdiction: "eu" });


使用 OAuth:

JavaScript


export default new OAuthProvider({

  apiHandlers: {

    "/mcp": MyMCP.serve("/mcp", { jurisdiction: "eu" }),

  },

  // ... other OAuth config

});


TypeScript


export default new OAuthProvider({

  apiHandlers: {

    "/mcp": MyMCP.serve("/mcp", { jurisdiction: "eu" }),

  },

  // ... other OAuth config

});


当你指定 jurisdiction: "eu" 时:

  • 所有 MCP 会话数据保留在 EU 内
  • 由你的工具处理的用户数据保留在 EU 内
  • 存储在 Durable Object 中的状态保留在 EU 内

可用的司法管辖区包括 "eu"(European Union)和 "fedramp"(FedRAMP 合规位置)。有关更多选项,请参阅 Durable Objects data location

休眠支持

McpAgent 实例自动支持 WebSockets Hibernation,允许有状态的 MCP 服务器在不活动期间休眠,同时保留其状态。这意味着你的 agent 仅在主动处理请求时消耗计算资源,在保持完整上下文和会话历史的同时优化成本。

休眠默认启用,无需额外配置。

认证和授权

McpAgent 类与 OAuth Provider Library ↗ 无缝集成,用于认证和授权

当用户向你的 MCP 服务器进行身份验证时,他们的身份信息和 token 通过 props 参数提供,允许你:

  • 访问用户特定的数据
  • 在执行操作之前检查用户权限
  • 基于用户属性自定义响应
  • 使用认证 token 代表用户向外部服务发出请求

状态同步 API

McpAgent 类提供对 Agent state APIs 的完全访问:

会话结束后状态重置

目前,每个客户端会话由 McpAgent 类的一个实例支持。这是自动处理的,如入门指南中所示。这意味着当同一客户端重新连接时,他们将启动新会话,状态将被重置。

例如,以下代码实现了一个 MCP 服务器,它记住一个计数器值,并在调用 add 工具时更新计数器:

JavaScript


import { McpAgent } from "agents/mcp";

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

import { z } from "zod";


export class MyMCP extends McpAgent {

  server = new McpServer({

    name: "Demo",

    version: "1.0.0",

  });


  initialState = {

    counter: 1,

  };


  async init() {

    this.server.resource(`counter`, `mcp://resource/counter`, (uri) => {

      return {

        contents: [{ uri: uri.href, text: String(this.state.counter) }],

      };

    });


    this.server.tool(

      "add",

      "Add to the counter, stored in the MCP",

      { a: z.number() },

      async ({ a }) => {

        this.setState({ ...this.state, counter: this.state.counter + a });


        return {

          content: [

            {

              type: "text",

              text: String(`Added ${a}, total is now ${this.state.counter}`),

            },

          ],

        };

      },

    );

  }


  onStateChanged(state) {

    console.log({ stateUpdate: state });

  }

}


Explain Code

TypeScript


import { McpAgent } from "agents/mcp";

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

import { z } from "zod";


type State = { counter: number };


export class MyMCP extends McpAgent<Env, State, {}> {

  server = new McpServer({

    name: "Demo",

    version: "1.0.0",

  });


  initialState: State = {

    counter: 1,

  };


  async init() {

    this.server.resource(`counter`, `mcp://resource/counter`, (uri) => {

      return {

        contents: [{ uri: uri.href, text: String(this.state.counter) }],

      };

    });


    this.server.tool(

      "add",

      "Add to the counter, stored in the MCP",

      { a: z.number() },

      async ({ a }) => {

        this.setState({ ...this.state, counter: this.state.counter + a });


        return {

          content: [

            {

              type: "text",

              text: String(`Added ${a}, total is now ${this.state.counter}`),

            },

          ],

        };

      },

    );

  }


  onStateChanged(state: State) {

    console.log({ stateUpdate: state });

  }

}


Explain Code

Elicitation(人工参与回路)

MCP 服务器可以在工具执行期间使用 elicitation 请求额外的用户输入。MCP 客户端(如 Claude Desktop)基于你的 JSON Schema 渲染表单并返回用户的响应。

何时使用 elicitation

  • 请求最初工具调用中没有的结构化输入
  • 在继续之前确认高风险操作
  • 在执行过程中收集额外的上下文或偏好

elicitInput(options, context)

在工具执行期间从用户请求结构化输入。

参数:

参数类型描述
options.messagestring解释需要什么输入的消息
options.requestedSchemaJSON Schema定义预期输入结构的 schema
context.relatedRequestIdstring来自工具处理器的 extra.requestId

返回: Promise<{ action: "accept" | "decline", content?: object }>

JavaScript


import { McpAgent } from "agents/mcp";

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

import { z } from "zod";


export class CounterMCP extends McpAgent {

  server = new McpServer({

    name: "counter-server",

    version: "1.0.0",

  });


  initialState = { counter: 0 };


  async init() {

    this.server.tool(

      "increase-counter",

      "Increase the counter by a user-specified amount",

      { confirm: z.boolean().describe("Do you want to increase the counter?") },

      async ({ confirm }, extra) => {

        if (!confirm) {

          return { content: [{ type: "text", text: "Cancelled." }] };

        }


        // Request additional input from the user

        const userInput = await this.server.server.elicitInput(

          {

            message: "By how much do you want to increase the counter?",

            requestedSchema: {

              type: "object",

              properties: {

                amount: {

                  type: "number",

                  title: "Amount",

                  description: "The amount to increase the counter by",

                },

              },

              required: ["amount"],

            },

          },

          { relatedRequestId: extra.requestId },

        );


        // Check if user accepted or cancelled

        if (userInput.action !== "accept" || !userInput.content) {

          return { content: [{ type: "text", text: "Cancelled." }] };

        }


        // Use the input

        const amount = Number(userInput.content.amount);

        this.setState({

          ...this.state,

          counter: this.state.counter + amount,

        });


        return {

          content: [

            {

              type: "text",

              text: `Counter increased by ${amount}, now at ${this.state.counter}`,

            },

          ],

        };

      },

    );

  }

}


Explain Code

TypeScript


import { McpAgent } from "agents/mcp";

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

import { z } from "zod";


type State = { counter: number };


export class CounterMCP extends McpAgent<Env, State, {}> {

  server = new McpServer({

    name: "counter-server",

    version: "1.0.0",

  });


  initialState: State = { counter: 0 };


  async init() {

    this.server.tool(

      "increase-counter",

      "Increase the counter by a user-specified amount",

      { confirm: z.boolean().describe("Do you want to increase the counter?") },

      async ({ confirm }, extra) => {

        if (!confirm) {

          return { content: [{ type: "text", text: "Cancelled." }] };

        }


        // Request additional input from the user

        const userInput = await this.server.server.elicitInput(

          {

            message: "By how much do you want to increase the counter?",

            requestedSchema: {

              type: "object",

              properties: {

                amount: {

                  type: "number",

                  title: "Amount",

                  description: "The amount to increase the counter by",

                },

              },

              required: ["amount"],

            },

          },

          { relatedRequestId: extra.requestId },

        );


        // Check if user accepted or cancelled

        if (userInput.action !== "accept" || !userInput.content) {

          return { content: [{ type: "text", text: "Cancelled." }] };

        }


        // Use the input

        const amount = Number(userInput.content.amount);

        this.setState({

          ...this.state,

          counter: this.state.counter + amount,

        });


        return {

          content: [

            {

              type: "text",

              text: `Counter increased by ${amount}, now at ${this.state.counter}`,

            },

          ],

        };

      },

    );

  }

}


Explain Code

表单的 JSON Schema

requestedSchema 定义显示给用户的表单结构:

TypeScript


const schema = {

  type: "object",

  properties: {

    // Text input

    name: {

      type: "string",

      title: "Name",

      description: "Enter your name",

    },

    // Number input

    amount: {

      type: "number",

      title: "Amount",

      minimum: 1,

      maximum: 100,

    },

    // Boolean (checkbox)

    confirm: {

      type: "boolean",

      title: "I confirm this action",

    },

    // Enum (dropdown)

    priority: {

      type: "string",

      enum: ["low", "medium", "high"],

      title: "Priority",

    },

  },

  required: ["name", "amount"],

};


Explain Code

处理响应

JavaScript


const result = await this.server.server.elicitInput(

  { message: "Confirm action", requestedSchema: schema },

  { relatedRequestId: extra.requestId },

);


switch (result.action) {

  case "accept":

    // User submitted the form

    const { name, amount } = result.content;

    // Process the input...

    break;


  case "decline":

    // User cancelled

    return { content: [{ type: "text", text: "Operation cancelled." }] };

}


Explain Code

TypeScript


const result = await this.server.server.elicitInput(

  { message: "Confirm action", requestedSchema: schema },

  { relatedRequestId: extra.requestId },

);


switch (result.action) {

  case "accept":

    // User submitted the form

    const { name, amount } = result.content as { name: string; amount: number };

    // Process the input...

    break;


  case "decline":

    // User cancelled

    return { content: [{ type: "text", text: "Operation cancelled." }] };

}


Explain Code

MCP 客户端支持

Elicitation 需要 MCP 客户端支持。并非所有 MCP 客户端都实现 elicitation 能力。检查客户端文档以了解兼容性。

有关更多人工参与回路模式,包括基于工作流的批准,请参阅 Human-in-the-loop patterns

下一步

Build a Remote MCP server Cloudflare 上 MCP 服务器入门。

MCP Tools 设计并向你的 MCP 服务器添加工具。

Authorization 设置 OAuth 认证。

Securing MCP servers 生产环境的安全最佳实践。

createMcpHandler 构建无状态的 MCP 服务器。