结论先说:写 MCP Server 最容易翻车的不是代码,是两个工程细节
用 TypeScript 写一个能跑的 MCP Server 只要几十行,但绝大多数「连不上」的故障来自两个非代码问题:package.json 里没开 type: "module",以及忘跑 npm run build。官方快速上手里把「记得构建」单列成一条强调事项,就是因为这一步被跳过的概率极高。
本文按「能连上 → 能用 → 能发布」三段讲,把每一步的坑标出来。
环境准备
- Node.js 20 及以上:官方明确要求,低版本会在依赖安装或运行时报错。
- 依赖:运行时依赖 MCP SDK 与 zod(用于入参校验),开发依赖 typescript 与 @types/node 。
- 目录:
src/index.ts作为入口,构建产物输出到build/。
三段配置:每段都有必填项
- 配置 package.json:必须加
"type": "module";加bin指向./build/index.js;build 脚本写成tsc && chmod 755 build/index.js。缺了type: module,ESM 语法会直接报模块错误。 - 配置 tsconfig.json:target 设 ES2022,module 与 moduleResolution 设 Node16,outDir 指向
./build,rootDir 指向./src,开启 strict 。这套配置是官方示例验证过的,自己改容易踩模块解析的坑。 - 写服务端:创建 McpServer 实例并声明 name 与 version,用 zod 定义入参 schema 注册工具,最后用 StdioServerTransport 连接。注册工具时描述要写清楚「这个工具做什么、什么情况下用」,模型靠这段文本决定要不要调。
- 构建:跑
npm run build。这一步不做,客户端永远连不上你的服务——这是最常见的故障原因。 - 接入宿主验证:在客户端配置文件的
mcpServers键下加一条,指向构建后的绝对路径。 UI 里能出现 MCP 相关入口,才说明至少有一个服务配置成功。 - 发布(可选):先发到 npm 包仓库,再用官方发布工具把元数据提交到 MCP Registry 。 Registry 只托管元数据不托管产物,所以 npm 发布是前置步骤。
日志是 TypeScript MCP Server 的隐形杀手:stdio 传输下 绝对不要用
console.log(),它写的是标准输出,会和 JSON-RPC 消息混在一起直接把协议搞坏。要打日志一律用 console.error()(写 stderr)或写文件。 HTTP 传输下标准输出才安全。工具设计:三条决定好不好用
| 要点 | 反例 | 正例 |
|---|---|---|
| 名称 | do、tool1 | get_weather、list_orders:动词+对象 |
| 描述 | 「获取信息」 | 「按城市名或邮编查询当前天气,返回温度与天气状况」 |
| 入参 | 一个大 JSON | 每个字段都写 description,必填项标进 required |
模型唯一能看到的就是工具名、描述和 JSON Schema 。描述写得含糊,模型要么不调用,要么瞎传参数。
错误处理要分两种
协议错误与执行错误是两套机制,别混在一起:
- 协议错误:未知工具名、参数格式不对、服务端异常。用 JSON-RPC 标准错误返回。
- 执行错误:外部 API 失败、限流、业务校验不通过。结果里带
isError: true返回,同时把可读的原因放进文本内容。
第二种尤其重要:把失败原因写清楚,模型才有可能自己换个参数重试;只返回一个 null,它只会原地打转。
发布到 MCP Registry 的要点
- 先发 npm:Registry 只存元数据,包本身必须在 npm 上可获取。
- 加
mcpName:在 package.json 里加这个字段用于校验,命名形如io.github.<用户名>/<服务名>。 - 两处名字要一致:server.json 的 name 必须与 package.json 的 mcpName 完全匹配。
- 版本号同步:改版本时两个文件里的版本号都要改,否则校验失败。
- 预览期注意:Registry 目前处于预览阶段,可能出现破坏性变更或数据重置,别把关键流程强依赖在上面。
SDK 怎么选
官方 SDK 按功能完整度和维护承诺分了档:TypeScript 、 Python 、 C#、 Go 是第一档(Tier 1),协议支持与维护最完整;Java 、 Rust 是第二档;Swift 、 Ruby 、 PHP 、 Kotlin 相对靠后。选型的现实标准是生态与团队语言栈,不是协议能力——所有 SDK 都支持创建服务端、连接任意服务、本地与远程传输。
客户端连不上我的服务,最可能是什么原因?
先查两件事:package.json 有没有
type: "module",以及有没有跑过 npm run build。这两条占了绝大多数「连不上」的故障。为什么服务启动后协议报错?
大概率是在 stdio 传输下用了
console.log()。标准输出被日志占用会破坏 JSON-RPC 消息,改成 console.error() 即可。一定要用 TypeScript 吗?
不。 Python 、 C#、 Go 都是同档支持的 SDK 。选你团队最熟的语言,协议能力与传输方式没有差别。
工具描述可以随便写吗?
不能。规范明确要求客户端把工具注解视为不可信输入,而对模型来说描述是唯一的调用依据。写清楚用途、入参含义和返回结构,比堆砌形容词有用得多。










评论 (0)