先认清三种原语,本文聚焦 Tools

MCP 服务器能向客户端提供三类能力:

原语是什么谁决定用
Tools模型可调用的函数(经用户批准)模型
Resources可读的类文件数据(API 响应、文件内容)客户端 / 应用
Prompts预写的任务模板用户

绝大多数第一个 server 都从 Tools 开始——官方教程也是如此:做一个查美国天气预警和预报的服务器, 暴露 get_alertsget_forecast 两个工具。

v2 拆包:写在动手之前

网上大量 MCP 教程还在教 @modelcontextprotocol/sdk 单包——那是 v1。当前稳定线是v2,随 2026-07-28 版规范发布,变化三点:

  • 拆成 @modelcontextprotocol/server(写服务器)和 @modelcontextprotocol/client(写客户端)两个包,跑在 Node / Bun / Deno。
  • 工具与提示的 schema 走 Standard Schema 标准——Zod v4、Valibot、ArkType 任选。
  • 另有薄适配层中间件包(如 @modelcontextprotocol/express、/node),把 Streamable HTTP 接进具体框架。

v1 在 v2 发布后至少维护 6 个月,但新项目没有理由再从 v1 开始。

环境与骨架

# Node.js 20+
mkdir weather && cd weather
npm init -y
npm install @modelcontextprotocol/server zod
npm install -D @types/node typescript
mkdir src && touch src/index.ts

package.json 要点:ES Module、bin 指向构建产物、build 时赋可执行权限:

{
  "type": "module",
  "bin": { "weather": "./build/index.js" },
  "scripts": { "build": "tsc && chmod 755 build/index.js" },
  "files": ["build"]
}

注册工具:registerTool + Zod

import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import { z } from "zod";

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

server.registerTool(
  "get_alerts",
  {
    description: "Get weather alerts for a state",
    inputSchema: z.object({
      state: z.string().length(2).describe("Two-letter state code (e.g. CA, NY)"),
    }),
  },
  async ({ state }) => {
    const data = await fetchAlerts(state.toUpperCase()); // 业务逻辑
    if (!data) {
      return { content: [{ type: "text", text: "Failed to retrieve alerts data" }] };
    }
    return { content: [{ type: "text", text: formatAlerts(data) }] };
  },
);

三个值得抄的细节:

  • schema 里写 describe:工具名、描述和参数说明就是模型的「API 文档」,写得越具体,模型传参越准。
  • 返回值统一 content 数组{ content: [{ type: "text", text: ... }] },多块可并列。
  • 失败返回文本而不是抛异常:「Failed to retrieve alerts data」这类明确说明能让模型理解失败原因、向用户解释或换参数重试。

接线 stdio 与日志红线

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Weather MCP Server running on stdio"); // stderr,安全
}

main().catch((error) => {
  console.error("Fatal error in main():", error);
  process.exit(1);
});

官方教程用加粗强调的一条:stdio 服务器绝不能往 stdout 写日志。stdio 传输用 stdout 传 JSON-RPC 消息,一个 console.log("Server started") 就能让消息流解析失败、连接中断。 日志一律 console.error(stderr),Python 同理——不用 print(),用写往 stderr 的 logging 模块。HTTP 传输的服务器则没有这个限制。

写完 npm run build——客户端启动的是 build 产物,忘构建是「配置了但连不上」的第二大原因。

调试:先 Inspector,再接客户端

不要一上来就接 Claude 调试。MCP Inspector 是官方调试器,有浏览器、CLI、终端三种形态, 可以直接列出工具、手动传参调用、看原始请求响应。工具级问题在 Inspector 里定位,比在 Claude 对话里猜快得多。CLI 形态还能进 CI 做冒烟测试。

接入 Claude Code 与 Claude Desktop

Claude Code

# 本地 stdio 服务器(-- 之后是启动命令)
claude mcp add --transport stdio weather -- node /绝对路径/weather/build/index.js

# 验证与管理
claude mcp list
claude mcp get weather
claude mcp remove weather

要在团队内共享,用 --scope project 写进项目的 .mcp.json;作用域与 OAuth 细节见我们的 MCP 配置教程。

Claude Desktop

编辑 ~/Library/Application Support/Claude/claude_desktop_config.json(Windows 为 %AppData%\Claude 下同名文件):

{
  "mcpServers": {
    "weather": {
      "command": "node",
      "args": ["/ABSOLUTE/PATH/TO/weather/build/index.js"]
    }
  }
}

保存后重启 Claude Desktop,路径必须是绝对路径。之后问「加州现在有什么天气预警」,Claude 会请求调用你的工具并展示结果。

走向远程:Streamable HTTP

stdio 适合「随客户端在本机启动、单用户」的场景。要远程部署、多用户共享,就换 Streamable HTTP 传输:SDK 的中间件包可以直接挂进 Express / Fastify / Hono,再按规范加 OAuth 2.1 授权。 远程服务器的安全要求(PKCE、audience 校验、禁止 token passthrough、SSRF 防护)我们在 MCP 生产安全指南里单独讲过,上生产前务必过一遍。

上生产前检查表

  1. 每个工具的 description 和参数 describe 都经得起「只看文档能不能正确调用」的检验。
  2. 入参用 schema 强校验(范围、长度、枚举),不信任模型传来的任何值。
  3. 失败路径全部返回可读文本,超时设上限,外部请求带重试与降级。
  4. stdio 版零 stdout 日志;HTTP 版校验 Origin / Host,绑定局域网外前先加认证。
  5. 高风险工具(写入、删除、支付)在客户端侧保持逐次人工批准,不要为省事关掉确认。

写 MCP Server 的本质是给模型设计 API:schema 是合同、description 是文档、错误消息是排错指南。 把这三样写好,一个下午就能让 Claude 用上你自己的工具。