agentmentoragentmentor

第 03 节:写出第一个 TypeScript tool server

本节 objectives:

  • 能创建一个最小 Node.js MCP server 项目。
  • 能用 TypeScript SDK 注册一个带输入 schema 的 tool。
  • 能解释 stdio server 为什么不能随便往 stdout 打日志。

先修:会运行 npm 命令;知道 tool 是可执行动作 | 上一节 << 02 | 下一节 04 >>

先让一条工具调用真的跑起来

MCP 的概念很多,但第一步可以很小:让 host 通过 stdio 连接一个 Node 进程,列出一个 tool,再调用它。

本节使用官方教程仍在采用的 v1 SDK 包路径 @modelcontextprotocol/sdkzod@3。官方 TypeScript SDK 仓库的 main 分支在 2026-06-30 已经转向 v2 pre-alpha,并提示 v1.x 仍是生产推荐版本直到 v2 稳定发布。1 所以入门时先跑通 v1.x,比追 pre-alpha API 更稳。

讲解

一个最小 tool server 需要四块:

  1. McpServer:声明 server 名称和版本。
  2. registerTool:注册 tool 名称、描述、输入 schema、handler。
  3. StdioServerTransport:让 host 通过 stdin/stdout 跟 server 说 JSON-RPC。
  4. server.connect(transport):把 server 挂到 transport 上。

stdio transport 有一个初学者很容易踩的坑:stdout 是协议通道。你往 stdout 随便 console.log("debug"),host 可能会把它当成坏掉的 JSON-RPC 消息。调试日志应该走 stderr,也就是 console.error。MCP spec 的消息层要求请求、响应、通知遵守 JSON-RPC 结构。2

跟我做一遍(worked example)

在一个空目录里创建项目:

bash
mkdir mcp-notes-labcd mcp-notes-labnpm init -ynpm install @modelcontextprotocol/sdk zod@3npm pkg set type=module

创建 server.mjs:

js
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";import { z } from "zod";
const server = new McpServer({  name: "notes-lab",  version: "0.1.0",});
server.registerTool(  "summarize_note",  {    title: "Summarize Note",    description: "Return a short, plain-language summary of one note.",    inputSchema: {      note: z.string().min(1),    },  },  async ({ note }) => {    const firstLine = note.split(/\r?\n/).find(Boolean) ?? "";    return {      content: [        {          type: "text",          text: `First line: ${firstLine.slice(0, 160)}`,        },      ],    };  });
const transport = new StdioServerTransport();await server.connect(transport);

这不是一个聪明摘要器,只是一个可验证的 tool。初学阶段先证明协议和 schema 能跑,再替换 handler 里的业务逻辑。

你可以先只检查 Node 是否能启动:

bash
node server.mjs

它会停在那里等待 stdio 消息。按 Ctrl+C 退出。不要期待它在终端打印"启动成功",因为 stdout 要留给协议。

换你补全(faded example)

把下面 tool 补完整:输入 ab,返回它们的和。

js
server.registerTool(  "add_numbers",  {    title: "Add Numbers",    description: "Add two numbers and return the result.",    inputSchema: {      a: __________,      b: __________,    },  },  async ({ a, b }) => {    return {      content: [        {          type: "text",          text: __________,        },      ],    };  });

参考答案:

js
server.registerTool(  "add_numbers",  {    title: "Add Numbers",    description: "Add two numbers and return the result.",    inputSchema: {      a: z.number(),      b: z.number(),    },  },  async ({ a, b }) => {    return {      content: [        {          type: "text",          text: String(a + b),        },      ],    };  });

关键不是加法,而是 schema 和 handler 对齐:输入 schema 说有两个 number,handler 就按两个 number 处理。

小结 + 通向下一节

你已经有了一个最小 tool server:server 声明身份,tool 声明输入和行为,stdio 负责协议连接。下一节我们把"上下文"和"交互模板"补进来,让 server 不再只有函数。

Footnotes

  1. MCP TypeScript SDK — https://github.com/modelcontextprotocol/typescript-sdk

  2. MCP Base Protocol Overview — https://modelcontextprotocol.io/specification/2025-03-26/basic

练习

Level 1: 在你自己的机器上创建 mcp-notes-lab,复制 worked example,确认 node server.mjs 不报错。

提示 1

用正则数 /- \[ \]/g/- \[x\]/gi

提示 2

先在普通 Node 脚本里测函数,再放回 handler。

提示 3

输出 text 用 JSON 字符串也可以,但要让人能读懂。

自评