用 TypeScript 从零搓一个 MCP server,比你想的简单

Claude 中文知识站 Lv4

一开始我真以为搓个 MCP server 得学一堆新概念。

看完规范以后预期是:至少要写个 transport 层、处理 JSON-RPC 路由、搞 capability handshake……结果打开 @modelcontextprotocol/sdk,发现核心代码也就十几行。40 行能出一个能用的 server。

这篇我按”我第一次做的时候希望有人告诉我”的顺序写一遍。不讲原理(原理看上一篇 MCP 协议从头撸),就讲动手。

初始化:脚手架比想的还轻

1
2
3
4
5
mkdir weather-mcp && cd weather-mcp
npm init -y
npm i @modelcontextprotocol/sdk zod
npm i -D typescript @types/node tsx
npx tsc --init

我第一次装的时候 SDK 版本是 1.0.4,现在 2026 年 4 月最新是 1.18.2,API 变化不大,主要是 transport 那边多了 streamable HTTP。

package.json 里加一行:

1
2
3
"bin": {
"weather-mcp": "./dist/index.js"
}

这个 bin 字段很关键,Claude Desktop 启动你 server 的时候就是走这个入口。我第一次漏了,配置里 command 怎么写都找不到可执行文件,折腾了 40 多分钟才反应过来。

40 行的 weather server 完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";

const server = new Server(
{ name: "weather-mcp", version: "0.1.0" },
{ capabilities: { tools: {} } }
);

const GetWeatherArgs = z.object({
city: z.string().min(1).max(64),
});

server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: "get_weather",
description: "查询指定城市当前天气(摄氏度)",
inputSchema: {
type: "object",
properties: { city: { type: "string", description: "城市中文名或拼音" } },
required: ["city"],
},
}],
}));

server.setRequestHandler(CallToolRequestSchema, async (req) => {
if (req.params.name !== "get_weather") {
throw new Error(`Unknown tool: ${req.params.name}`);
}
const { city } = GetWeatherArgs.parse(req.params.arguments);
// 真实场景这里调 API,这里偷懒返回固定数据
return { content: [{ type: "text", text: `${city} 当前 17℃,多云` }] };
});

const transport = new StdioServerTransport();
await server.connect(transport);

第一行 shebang 别忘。第一次我忘了加 #!/usr/bin/env node,Linux 下直接报 exec format error。

zod 用来校验 tool 参数。Anthropic 官方文档里其实没强调 zod,但生产环境我强烈建议加——LLM 偶尔会传奇葩参数,没校验直接炸 runtime。

在 Claude Desktop 里挂上

config 文件位置我踩过坑——Windows 和 Mac 不一样:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

内容长这样:

1
2
3
4
5
6
7
8
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["D:/code/weather-mcp/dist/index.js"]
}
}
}

注意两个坑:

  1. 路径在 Windows 下最好用正斜杠,反斜杠要转义
  2. 如果你 server 依赖环境变量(比如 API key),要在这里用 "env": {"OPENWEATHER_KEY": "xxx"} 显式传,Claude Desktop 启动子进程是干净环境

改完配置要完全退出 Claude Desktop 再打开,不是关窗口。右下角托盘点退出,不然旧进程还抓着老配置。这点 Anthropic 官方文档也没写清楚,我当时以为配置没生效,反复改了 5 遍。

调试:inspector 救命

@modelcontextprotocol/inspector 是官方调试工具,装上:

1
npx @modelcontextprotocol/inspector node dist/index.js

会弹出一个 web 界面,localhost:5173。可以手动触发 initializetools/listtools/call,看到完整 JSON-RPC 请求响应。不用开 Claude Desktop 就能测。

我现在每次改完 server 先用 inspector 过一遍,确认正常再接 Claude Desktop。节省至少 70% 的调试时间。

常见 Method not found 错误原因排名:

  • 第一,你忘了在 server 构造函数里声明对应 capability({ capabilities: { tools: {} } } 这句少了 tools 就 GG)
  • 第二,handler 注册漏了
  • 第三,method 名拼错了(比如写成 tool/call 少个 s)

一个 Jira MCP 的 token 优化故事

2025 年 10 月给一个 SaaS 客户搓过一个内部 Jira MCP server,8 个 tool:创建 issue、查询、改状态、加评论、分配、搜索、列 sprint、拉看板。

第一版直接出了。Claude 每次接 initialize 之后拿 tools/list,返回的 JSON 压缩完 3247 token。客户跑了几天发现月底 token 账单涨了 40%,问我怎么回事。

我一看——tool description 写得像技术文档,每个 tool 都有 200+ 字。inputSchema 里字段的 description 也写得巨长,还带了例子。8 个 tool 加起来 3K token 每次对话起步就吃掉。

优化思路:

  1. tool description 砍到一句话,只讲”它做什么”,不讲”怎么用”、”什么场景”
  2. inputSchema 里每个字段 description 控制在 15 字以内
  3. 非必要字段放到 “optional”,LLM 自己决定要不要用
  4. 把 8 个查询类 tool 合并成 1 个 jira_search,参数化查询条件

改完 680 token。功能没少,客户月账单降回正常水平。

这个经验后来我写进了团队内部规范,也在 MCP Server 内置工具设计 那篇里展开讲过。另外如果是 Claude Code 用户,context window 预算策略 那篇对这个话题也有补充。

几个小建议

  • 本地开发用 tsx watch src/index.ts,改代码不用手 build
  • error 分两类:参数错误返回给 LLM 让它重试、系统错误直接 throw 让框架返回 isError
  • tool description 写”做什么”,不要写”怎么实现”——LLM 不关心
  • 上生产前务必读一下 MCP server 安全最佳实践 相关章节
跑通第一个 server 之后,推荐接着看 MCP Server 工具粒度设计(决定你 token 成本)和 Claude Code 里挂 MCP(桌面端和 CLI 端配置方式不同)。对 Python 栈有兴趣的话,下一篇会讲 Python 版实现。
  • 标题: 用 TypeScript 从零搓一个 MCP server,比你想的简单
  • 作者: Claude 中文知识站
  • 创建于 : 2026-04-18 11:23:00
  • 更新于 : 2026-04-19 16:08:00
  • 链接: https://claude.cocoloop.cn/posts/mcp-server-build-typescript/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论