字节笔记本
2026年8月29日
28 行代码写一个 MCP 服务
MCP 服务不一定要先开一个仓库。TypeScript 官方 SDK 里,起一个 McpServer、注册一个 tool、用 stdio 接到客户端,大约三十行就能在 Cursor、Claude Code、Claude Desktop、Zed 里被模型调用。下面用一个假天气工具把这条最小闭环走通:装依赖、写文件、用官方 Inspector 点一下、再写进各客户端配置。
官方入口:MCP 构建服务教程、TypeScript SDK 仓库、v1 包 @modelcontextprotocol/sdk(本稿按 1.30 线)、v2 包 @modelcontextprotocol/server。
这三十行在干什么
MCP 把「工具」从模型对话里拆出来。客户端(Cursor 等)拉起一个本地进程,双方在 stdin/stdout 上走 JSON-RPC:先 initialize,再 tools/list,需要时 tools/call。
最小服务只做四件事:
new McpServer({ name, version })报自己的身份。server.tool(...)(v1)或server.registerTool(...)(v2)挂一个函数,Zod 描述入参。- 函数返回带
content数组的结果,里面是文本块。 StdioServerTransport把协议接到标准输入输出。客户端负责启动这个进程,你平时不要自己在终端里跑它。
下面的 getWeather 故意返回固定的 sunny,用来确认协议通了。真查天气是后一步的事。
建项目
需要 Node.js 20+。官方 Inspector 网页版要求 22.19+,没有的话可以先用客户端配置来验证。
新建目录 weather-mcp,初始化 Node 项目,安装 @modelcontextprotocol/sdk 和 zod,开发依赖装 typescript、tsx、@types/node。package.json 里加上 "type": "module",否则后面的顶层 await 会报错。
最小服务(v1,约 28 行)
把下面存成 weather.ts。这是现在下载量最大的 @modelcontextprotocol/sdk 写法,也是常见的「二十八行天气服务」那条路径。
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: "Weather Service",
version: "1.0.0",
});
server.tool(
"getWeather",
{
city: z.string().describe("city name, e.g. Beijing"),
},
async ({ city }) => ({
content: [
{
type: "text",
text: `The weather in ${city} is sunny!`,
},
],
}),
);
const transport = new StdioServerTransport();
await server.connect(transport);读代码时记住三点:
- 导入走 SDK 的
mcp.js/stdio.js,不要自己实现 JSON-RPC。 city用 Zod 标成字符串。客户端会按这个 schema 生成参数表,模型填错类型会在调用前被拦住。- 返回值必须是 MCP 的
content数组。直接返回普通字符串,客户端接不住。
stdio 服务不要往标准输出打日志。stdout 被协议占用,打印会把 JSON-RPC 弄坏。调试用 console.error,它走 stderr,客户端一般会写进 MCP 日志。
用 Inspector 先点通
不要一上来就改 Cursor 配置。先用官方 Inspector 把 server 本身验证掉,避免「配了但没工具」时分不清是代码问题还是客户端问题。
包名是 @modelcontextprotocol/inspector。启动时把「用 tsx 跑 weather.ts」整条命令交给它,路径用绝对路径。终端会打出一个带一次性 token 的本地 URL。打开后:
- 确认 Transport 是 STDIO,command 指向刚才那条启动命令。
- Connect。
- 看 Tools 列表里有没有
getWeather。 - 填 city=Beijing,Execute,返回里应出现
The weather in Beijing is sunny!。
不想开浏览器时,给 Inspector 加 --cli,再带 --method tools/list。能列出工具后,再 --method tools/call,工具名 getWeather,参数 city=Beijing。说明见 MCP Inspector。
接到客户端
路径一律用绝对路径。相对路径在客户端拉起子进程时经常找不到文件。把下面 JSON 里的 /ABS/weather-mcp/weather.ts 换成你机器上的真实路径。
Cursor
在项目 .cursor/mcp.json,或 Cursor 设置里的 MCP 配置,加入:
{
"mcpServers": {
"weather": {
"command": "npx",
"args": ["-y", "tsx", "/ABS/weather-mcp/weather.ts"]
}
}
}保存后重载窗口。工具面板里应出现 getWeather。试一句:「用 weather 工具查一下 Shanghai 的天气。」模型应去调工具,而不是自己编气象。
Claude Code
用 claude mcp add,transport 选 stdio,名字叫 weather。双横线后面跟启动命令:tsx 跑那份 weather.ts 的绝对路径。双横线很重要,后面整段都是启动命令,不要让 Claude 自己的 flag 把 tsx 的参数吃掉。
加完后 claude mcp list 和 claude mcp get weather 看状态。会话里 /mcp 也能看。改完 weather.ts 后,stdio 进程要重启才加载新代码:先 remove 再 add,或在 /mcp 里 Reconnect。官方说明:Connect Claude Code to tools via MCP。
Claude Desktop
编辑 claude_desktop_config.json(macOS 一般在 ~/Library/Application Support/Claude/),mcpServers 里放和 Cursor 同一段 JSON。必须完全退出再打开 Desktop,只关窗口不够。日志在 ~/Library/Logs/Claude/ 下的 mcp 日志。
Zed
在 Zed 设置的 MCP 段用同样的 command 和 args。Zed 也是拉起本地进程走 stdio,和 Cursor 同一套 server。
Windows 把路径换成盘符加双反斜杠,或改用正斜杠。tsx 不在 PATH 时,command 写成可执行文件的绝对路径。
2026 的 v2 写法
官方 TypeScript SDK 的 main 分支已经切到 v2 包 @modelcontextprotocol/server,对应 2026-07-28 协议。最小例子几乎一样长,注册 API 换成 registerTool,Zod 走 v4:
import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import * as z from "zod/v4";
const server = new McpServer({
name: "Weather Service",
version: "1.0.0",
});
server.registerTool(
"getWeather",
{
description: "Return weather by city (demo, always sunny)",
inputSchema: z.object({
city: z.string().describe("city name, e.g. Beijing"),
}),
},
async ({ city }) => ({
content: [
{ type: "text", text: `The weather in ${city} is sunny!` },
],
}),
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});新项目可以直接用 v2。已经在跑的 v1 服务继续装 @modelcontextprotocol/sdk 的 1.x。官方说 v1 至少还会修 bug 和安全问题半年。两套不要混在同一个 package.json 里。
更想写 Python,用官方 mcp 包里的 FastMCP:装饰一个函数,再 run(transport="stdio")。完整天气示例在同一份 Build an MCP server 教程里。
从假天气换成真工具
协议通了之后,只改 handler 内部:
- 换成真实 HTTP。美国位置可以跟官方教程一样调 api.weather.gov;国内位置用你已有的天气接口,密钥放环境变量,不要写进仓库。
- 超时和错误返回成文本 content,让模型能读到失败原因,不要让进程崩溃。stdio 挂了,客户端只会显示 server 断线。
- 需要给别的机器用时,把传输换成 Streamable HTTP。本地编码助手继续用 stdio 最省事。
常见翻车:
| 现象 | 先查 |
|---|---|
| 客户端里看不到工具 | 路径是不是绝对路径;type: module 有没有加;Desktop 有没有彻底退出 |
| 一连上就断开 | 是不是往 stdout 打了日志;tsx 单独跑时应无输出、干等 |
| 改了代码没生效 | stdio 是子进程,要 Reconnect 或重加 server |
| Inspector 能调、Cursor 不能 | 两边启动命令是否一字不差 |
小结
自己写 MCP,先求一个 tool 加 stdio 通。v1 用 @modelcontextprotocol/sdk 的 server.tool,v2 用 @modelcontextprotocol/server 的 registerTool,再交给 Cursor / Claude Code / Desktop / Zed 拉起进程。Inspector 点通之后再换真接口。协议说明以 modelcontextprotocol.io 和 typescript-sdk 为准。