ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

使用 TypeScript SDK 的 `registerTool` 构建 MCP Tools:从 Schema 推导到结构化输出实战

使用 TypeScript SDK 的 `registerTool` 构建 MCP Tools:从 Schema 推导到结构化输出实战 使用 TypeScript SDK 的registerTool构建 MCP Tools从 Schema 推导到结构化输出实战【免费下载链接】typescript-sdkThe official TypeScript SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/GitHub_Trending/ty/typescript-sdk在 Model Context ProtocolMCP中tool工具是客户端及其驱动的模型可以在服务器上执行的动作也是将服务器能力暴露给模型的核心原语。本文以官方 TypeScript SDK 仓库中的 examples/tools 入门示例为主线完整演示如何用McpServer.registerTool注册工具、由单一 Zod Schema 自动推导 JSON Schema 与参数校验、通过outputSchemastructuredContent产出机器可读的结构化结果以及客户端如何listTools检视 schema 与annotations、callTool调用工具并断言结构化输出。读完本文你将掌握一套可复制的「注册—检视—调用—校验」全流程写法并理解其底层的实现机制。从一个完整可运行的示例开始官方仓库的 examples/tools 被标注为Start here从这里开始它是一对配套的 server/client 程序server.ts注册了两个工具calc与echoclient.ts则负责列出工具、检视 schema 与注解、发起调用、断言结构化输出并验证「调用不存在的工具必然失败」。示例同时支持 stdio 与 Streamable HTTP 两种传输还支持现代2026-07-28与 legacy 两种协议时代。运行方式示例根目录的 README.md 给出的运行命令非常简单pnpm tsx examples/tools/client.ts默认情况下 client 会以stdio方式在本地拉起server.ts并与之通信。示例还通过mcp-examples/shared的 args.ts 提供了几个可选的命令行开关这些开关同样被仓库的示例运行器 run-examples.ts 使用参数作用默认值--http改用 Streamable HTTP 传输server 通过hono/node-server监听不传则走 stdio--port NHTTP 监听端口$PORT或3000--http url客户端连接的 HTTP 端点http://127.0.0.1:port/mcp--legacy以 legacy2025-11-25协议时代运行默认 modern自动协商 2026-07-28例如以 HTTP modern 时代运行# 终端 1启动服务器 pnpm tsx examples/tools/server.ts --http --port 3000 # 终端 2驱动客户端 pnpm tsx examples/tools/client.ts --http http://127.0.0.1:3000/mcpexamples/tools/package.json中已配置好server/client两个脚本也可以直接使用pnpm --filter mcp-examples/tools server等方式运行。示例依赖modelcontextprotocol/client、modelcontextprotocol/server、modelcontextprotocol/honoHTTP 承载与zod全部来自仓库内的 workspace 包。示例中 server/client 的分工server.ts创建McpServer注册calc带outputSchema、annotations、icons的进阶工具与echo仅返回文本的基础工具并通过 stdio 或 Hono HTTP 暴露client.tslistTools断言两个工具都在列表中检视calc的annotations.readOnlyHint、必填参数、outputSchema与iconscallTool调用calc与echo并断言结果最后验证调用不存在的工具会失败。用registerTool注册工具一个 Schema 的三种用途基本形态在 server.ts 中注册工具只需三步给工具起名、给出配置对象、编写处理函数。以示例中的calc为例server.registerTool( calc, { title: Calculator, description: Apply an arithmetic operation to two numbers, inputSchema: z.object({ op: z.enum([add, sub, mul]).describe(the operation to apply), a: z.number().describe(left operand), b: z.number().describe(right operand) }), outputSchema: z.object({ op: z.string(), result: z.number() }), annotations: { readOnlyHint: true, idempotentHint: true }, icons: [{ src: https://example.test/calc.svg, mimeType: image/svgxml, sizes: [any] }] }, async ({ op, a, b }) { const result op add ? a b : op sub ? a - b : a * b; const structuredContent { op, result }; return { content: [{ type: text, text: ${a} ${op} ${b} ${result} }], structuredContent }; } );registerTool的签名见 mcp.ts为registerTool(name, config, cb)其中config支持以下字段字段类型说明titlestring展示名供客户端界面显示descriptionstring工具用途说明是模型理解该工具的主要文案inputSchemaZod / 任意 Standard-Schema 兼容 schema输入参数 schemaoutputSchemaZod / 任意 Standard-Schema 兼容 schema输出结果 schema可选annotationsToolAnnotations行为提示如readOnlyHint、destructiveHint、idempotentHinticonsIcon[]客户端可在 UI 中渲染的图标src必填mimeType、sizes、theme可选_metaRecordstring, unknown透传的附加元数据一个 Schema 的三种用途inputSchema是你唯一需要手写的 schema。官方文档 docs/servers/tools.md 明确说明从这一个 schema 出发SDK 会替你完成三件事推导出模型看到的 JSON Schematools/list向客户端广播的输入 schema 由它转换而来在 handler 运行之前校验参数参数不合法时直接拒绝handler 不会执行推断 handler 的参数类型async ({ query, limit }) ...中query、limit的类型由 schema 静态推导全程类型安全。注意.describe()会原样保留query字段在广告出去的 JSON Schema 里带着Substring to match against product names作为description——这往往是模型能看到的关于该参数的唯一文档务必写清楚。从 v1 迁移的兼容说明在 v2 中registerTool取代了 v1 的tool()。从源码看registerTool同时保留了一个被标记为deprecated的旧形态inputSchema/outputSchema可以传裸的 Zod shape 记录如{ field: z.string() }SDK 会自动用z.object()包一层mcp.ts。新代码应直接传z.object({...})。官方迁移路径是先跑 codemod 再看 upgrade-to-v2 指南。客户端调用工具list、inspect、call建立连接client.ts 中客户端先创建Client实例并连接const client new Client( { name: tools-example-client, version: 1.0.0 }, { versionNegotiation: { mode: era modern ? auto : legacy } } ); await (transport stdio ? client.connect(new StdioClientTransport({ command: npx, args: [-y, tsx, siblingPath(import.meta.url, server.ts)] })) : client.connect(new StreamableHTTPClientTransport(new URL(url))));同一个客户端代码只需换传输层stdio 与 Streamable HTTP 的调用逻辑完全一致。siblingPath来自 args.ts负责把相对路径解析成绝对路径用于拉起同目录下的server.ts。listTools检视 schema 与 annotationsclient.listTools()返回服务器注册的全部工具随后示例对返回结果做了一系列断言const list await client.listTools(); const names new Set(list.tools.map(t t.name)); check.ok(names.has(calc) names.has(echo), tools/list should contain calc and echo); const calc list.tools.find(t t.name calc)!; check.equal(calc.annotations?.readOnlyHint, true); const required (calc.inputSchema as { required?: string[] }).required ?? []; check.ok(required.includes(op) required.includes(a) required.includes(b)); check.ok(calc.outputSchema, calc should publish an outputSchema); check.equal(calc.icons?.[0]?.src, https://example.test/calc.svg, calc should advertise its icons over the wire);从中可以看到几个关键点annotations.readOnlyHint会通过tools/list原样广播到客户端z.object({...})转换出的 JSON Schema 带有required数组必填字段op、a、b都在其中注册了outputSchema的工具其派生 JSON Schema 会出现在列表条目上供客户端自行校验注册的icons也会在线上传输示例用https://example.test/calc.svg演示。在客户端实现层面listTools会先检查服务器是否声明了tools能力未声明时直接返回空列表对于流式 HTTP 的现代协议它还会做分页聚合并排除不符合x-mcp-header约束的工具定义见 client.ts。callTool调用并断言结构化输出const result await client.callTool({ name: calc, arguments: { op: add, a: 2, b: 3 } }); check.equal((result.structuredContent as { result?: number } | undefined)?.result, 5); check.equal((result.structuredContent as { op?: string } | undefined)?.op, add); const echo await client.callTool({ name: echo, arguments: { text: hi } }); check.equal(echo.content?.[0]?.type text ? echo.content[0].text : , hi); check.equal(echo.structuredContent, undefined);calc的返回同时携带两层表达content人类可读的文本2 add 3 5structuredContent机器可读的结构化数据{ op: add, result: 5 }。而echo只返回content因此structuredContent为undefined——这就是纯文本工具与结构化输出工具在结果形态上的差别。调用不存在的工具示例最后验证了错误路径调用一个未注册的工具nope要么得到一个isError: true的工具结果要么直接抛线上错误——两种情况都被视为调用失败let unknownFailed false; try { const r await client.callTool({ name: nope, arguments: {} }); unknownFailed !!r.isError; } catch { unknownFailed true; } check.ok(unknownFailed, calling an unknown tool should fail);outputSchema 与 structuredContent机器可读的结构化输出注册侧声明与返回配对官方文档 docs/servers/tools.md 中的product-details展示了标准写法outputSchema声明输出形状handler 返回值中把结构化数据放在structuredContent字段与content并列返回server.registerTool( product-details, { description: Look up one product by its exact name, inputSchema: z.object({ name: z.string() }), outputSchema: z.object({ name: z.string(), price: z.number() }) }, async ({ name }) { const product catalog.find(candidate candidate.name name); if (!product) throw new Error(No product named ${name}); const output { name: product.name, price: product.price }; return { content: [{ type: text, text: JSON.stringify(output) }], structuredContent: output }; } );SDK 会在结果离开服务器之前对structuredContent按outputSchema做一次校验并把派生的 JSON Schema 通过tools/list广播出去让客户端也能独立校验。调用product-details传入{ name: Travel mug }会同时拿到两种渲染{ content: [ { type: text, text: {name:Travel mug,price:24} } ], structuredContent: { name: Travel mug, price: 24 } }需要留意结构化结果在线上的编码方式随协议时代而不同详见 protocol-versions.md。混合内容块一次返回多种类型一个工具的结果可以同时混排多种content块image与audio携带 base64 的data和mimeTyperesource把资源内容内联嵌入无需额外resources/read往返resource_link只按uri引用资源而不带字节。product-card示例同时返回图片、语音和资源记录三块内容server.registerTool( product-card, { description: Render one product as an image, a spoken name, and its catalog record, inputSchema: z.object({ name: z.string() }) }, async ({ name }) { const product catalog.find(candidate candidate.name name); if (!product) throw new Error(No product named ${name}); return { content: [ { type: image, data: cardPng, mimeType: image/png }, { type: audio, data: spokenNameWav, mimeType: audio/wav }, { type: resource, resource: { uri: catalog://products/${encodeURIComponent(product.name)}, mimeType: application/json, text: JSON.stringify(product) } } ] }; } );这些内容块会原样到达客户端内嵌的resource不会触发额外的resources/read往返——一个结果即是一个自包含的渲染包。参数校验失败handler 不会执行当客户端传入 schema 拒绝的参数时SDK 会在 handler 运行前拦截。把limit传成 999schema 上限是 50const rejected await client.callTool({ name: search, arguments: { query: mug, limit: 999 } }); console.log(rejected);得到的不是异常而是一个isError: true的普通工具结果{ content: [ { type: text, text: Input validation error: Invalid arguments for tool search: limit: Too big: expected number to be 50 } ], isError: true }这种设计对模型非常友好拒绝信息是一个可读的普通结果模型读到提示后可以修正参数重试。抛出的异常与协议级失败属于另一主题参见 errors.md。用 annotations 描述工具行为annotations是给客户端的行为提示title是展示名。clear-catalog示例声明自己具有破坏性且幂等server.registerTool( clear-catalog, { title: Clear the catalog, description: Remove every product from the catalog, annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true } }, async () { catalog.length 0; return { content: [{ type: text, text: Catalog cleared }] }; } );两个要点无参数的工具可以省略inputSchemaannotations永远不会改变 SDK 执行工具的方式它只影响客户端的决策例如宿主可以对只读工具自动放行auto-approve对破坏性工具强制要求用户确认。仓库中 examples/tools/client.ts 正是通过断言calc.annotations?.readOnlyHint true来验证注解确实上了线。深入源码registerTool 内部发生了什么从 mcp.ts 的实现可以看到registerTool的完整落地路径重名保护if (this._registeredTools[name]) throw new Error(\Tool ${name} is already registered)同一服务器内工具名不可重复Schema 归一化normalizeRawShapeSchema统一处理 Standard-Schema 对象与裸 Zod shape 两种输入执行器构建内部通过createToolExecutor(inputSchema, handler)生成执行器——参数校验与 handler 的类型绑定都发生在这条链路上后续若通过updateTool更新 handler 或 schema还会重建执行器能力广播注册完成后setToolRequestHandlers()挂载tools/list/tools/call请求处理并sendToolListChanged()通知客户端工具列表发生变化mcp.ts。此外registerTool返回的RegisteredTool支持后续通过updateTool动态更新callback、outputSchema、annotations、icons、enabled等并会再次触发sendToolListChanged()。实战对照官方指南示例文档页 docs/servers/tools.md 中的每段代码都是从可运行示例 tools.examples.ts 的//#region区块同步而来由pnpm sync:snippets --check校验同步。该文件底部的 harness 用内存传输InMemoryTransport.createLinkedPair()把客户端与服务器直接对接逐段产生了文档引用的输出——这意味着文档中的全部代码示例都是真实运行过、输出可复现的。你可以直接运行npx tsx examples/guides/servers/tools.examples.ts # 从 examples/ 目录来亲眼看到每次调用的实际输出。这种文档代码即运行示例的做法也意味着把 tools.examples.ts 当作最贴近文档的完整参考实现把 examples/tools 当作最小可运行的两文件示例两者互为印证。小结registerTool(name, config, handler)注册工具inputSchema是唯一需要手写的 Zod 对象 schema同一个 schema 同时产出广告给模型的 JSON Schema、参数校验规则和 handler 参数类型不通过校验的参数以isError: true的工具结果返回handler 不会执行outputSchemastructuredContent提供机器可读结果并在离开服务器前被校验content块支持text、image、audio、resource_link与内嵌resource一次返回可混排多种title与annotations描述工具行为仅供客户端决策从不影响执行从tools/list检视、tools/call调用到校验错误路径客户端侧的全流程都可以在 examples/tools/client.ts 中直接验证。【免费下载链接】typescript-sdkThe official TypeScript SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/GitHub_Trending/ty/typescript-sdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表