先认清三种原语,本文聚焦 Tools
MCP 服务器能向客户端提供三类能力:
| 原语 | 是什么 | 谁决定用 |
|---|---|---|
| Tools | 模型可调用的函数(经用户批准) | 模型 |
| Resources | 可读的类文件数据(API 响应、文件内容) | 客户端 / 应用 |
| Prompts | 预写的任务模板 | 用户 |
绝大多数第一个 server 都从 Tools 开始——官方教程也是如此:做一个查美国天气预警和预报的服务器, 暴露 get_alerts 和 get_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.tspackage.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 生产安全指南里单独讲过,上生产前务必过一遍。
上生产前检查表
- 每个工具的 description 和参数 describe 都经得起「只看文档能不能正确调用」的检验。
- 入参用 schema 强校验(范围、长度、枚举),不信任模型传来的任何值。
- 失败路径全部返回可读文本,超时设上限,外部请求带重试与降级。
- stdio 版零 stdout 日志;HTTP 版校验 Origin / Host,绑定局域网外前先加认证。
- 高风险工具(写入、删除、支付)在客户端侧保持逐次人工批准,不要为省事关掉确认。
写 MCP Server 的本质是给模型设计 API:schema 是合同、description 是文档、错误消息是排错指南。 把这三样写好,一个下午就能让 Claude 用上你自己的工具。