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

工具

MCP 工具是 MCP 服务器 暴露出来供客户端调用的函数。当 LLM 决定要执行某个动作时 —— 查询数据、运行计算、调用 API —— 它就会调用一个工具。MCP 服务器执行工具并返回结果。

工具使用 @modelcontextprotocol/sdk 包来定义。Agents SDK 负责传输和生命周期;无论你使用 createMcpHandler 还是 McpAgent,工具的定义方式都一样。

定义工具

使用 server.tool()McpServer 实例上注册工具。每个工具都有名字、描述(LLM 用它来决定何时调用)、用 Zod ↗ 定义的输入 schema,以及一个处理函数。

JavaScript


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

import { z } from "zod";


function createServer() {

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


  server.tool(

    "add",

    "Add two numbers together",

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

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

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

    }),

  );


  return server;

}


Explain Code

TypeScript


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

import { z } from "zod";


function createServer() {

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


  server.tool(

    "add",

    "Add two numbers together",

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

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

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

    }),

  );


  return server;

}


Explain Code

工具的处理函数会拿到经过校验的输入,并必须返回一个带 content 数组的对象。每个 content 元素都有一个 type(通常是 "text")和对应的数据。

工具结果

工具的结果以 content 数组形式返回。最常见的类型是 text,你也可以返回图片和嵌入资源(embedded resource)。

JavaScript


server.tool(

  "lookup",

  "Look up a user by ID",

  { userId: z.string() },

  async ({ userId }) => {

    const user = await db.getUser(userId);


    if (!user) {

      return {

        isError: true,

        content: [{ type: "text", text: `User ${userId} not found` }],

      };

    }


    return {

      content: [{ type: "text", text: JSON.stringify(user, null, 2) }],

    };

  },

);


Explain Code

TypeScript


server.tool(

  "lookup",

  "Look up a user by ID",

  { userId: z.string() },

  async ({ userId }) => {

    const user = await db.getUser(userId);


    if (!user) {

      return {

        isError: true,

        content: [{ type: "text", text: `User ${userId} not found` }],

      };

    }


    return {

      content: [{ type: "text", text: JSON.stringify(user, null, 2) }],

    };

  },

);


Explain Code

isError 设为 true 表示工具调用失败。LLM 会拿到错误信息,自行决定下一步怎么做。

工具描述

description 参数至关重要 —— 这是 LLM 用来判断是否以及何时调用你工具的依据。写描述时要做到:

  • 具体说明工具做什么:“Get the current weather for a city” 比 “Weather tool” 好得多
  • 明确输入要求:“Requires a city name as a string” 能帮助 LLM 正确地构造调用
  • 诚实说明限制:“Only supports US cities” 可以避免 LLM 用不支持的输入调用它

用 Zod 校验输入

工具的输入定义为 Zod schema,在 handler 运行之前会被自动校验。使用 Zod 的 .describe() 方法,为每个参数提供给 LLM 的上下文。

JavaScript


server.tool(

  "search",

  "Search for documents by query",

  {

    query: z.string().describe("The search query"),

    limit: z

      .number()

      .min(1)

      .max(100)

      .default(10)

      .describe("Maximum number of results to return"),

    category: z

      .enum(["docs", "blog", "api"])

      .optional()

      .describe("Filter by content category"),

  },

  async ({ query, limit, category }) => {

    const results = await searchIndex(query, { limit, category });

    return {

      content: [{ type: "text", text: JSON.stringify(results) }],

    };

  },

);


Explain Code

TypeScript


server.tool(

  "search",

  "Search for documents by query",

  {

    query: z.string().describe("The search query"),

    limit: z

      .number()

      .min(1)

      .max(100)

      .default(10)

      .describe("Maximum number of results to return"),

    category: z

      .enum(["docs", "blog", "api"])

      .optional()

      .describe("Filter by content category"),

  },

  async ({ query, limit, category }) => {

    const results = await searchIndex(query, { limit, category });

    return {

      content: [{ type: "text", text: JSON.stringify(results) }],

    };

  },

);


Explain Code

配合 createMcpHandler 使用工具

对于无状态的 MCP 服务器,在工厂函数里定义工具,然后把 server 传给 createMcpHandler:

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: "My Tools", version: "1.0.0" });


  server.tool("ping", "Check if the server is alive", {}, async () => ({

    content: [{ type: "text", text: "pong" }],

  }));


  return server;

}


export default {

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

    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: "My Tools", version: "1.0.0" });


  server.tool("ping", "Check if the server is alive", {}, async () => ({

    content: [{ type: "text", text: "pong" }],

  }));


  return server;

}


export default {

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

    const server = createServer();

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

  },

} satisfies ExportedHandler<Env>;


Explain Code

配合 McpAgent 使用工具

对于有状态的 MCP 服务器,在 McpAgentinit() 方法中定义工具。工具可以通过 this 访问 agent 实例,意味着它们能够读写状态。

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: "Stateful Tools", version: "1.0.0" });


  async init() {

    this.server.tool(

      "incrementCounter",

      "Increment and return a counter",

      {},

      async () => {

        const count = (this.state?.count ?? 0) + 1;

        this.setState({ count });

        return {

          content: [{ type: "text", text: `Counter: ${count}` }],

        };

      },

    );

  }

}


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: "Stateful Tools", version: "1.0.0" });


  async init() {

    this.server.tool(

      "incrementCounter",

      "Increment and return a counter",

      {},

      async () => {

        const count = (this.state?.count ?? 0) + 1;

        this.setState({ count });

        return {

          content: [{ type: "text", text: `Counter: ${count}` }],

        };

      },

    );

  }

}


Explain Code

下一步

Build a remote MCP server 在 Cloudflare 上部署 MCP 服务器的逐步教程。

createMcpHandler API 无状态 MCP 服务器的参考。

McpAgent API 有状态 MCP 服务器的参考。

MCP authorization 为你的 MCP 服务器添加 OAuth 认证。