上周我花了一个下午,从零开始写了一个 MCP Server。在这之前,我对 MCP 的理解停留在"知道它是个协议"的层面。
最终成果是一个能连接 GitHub API 的 MCP Server——可以查仓库信息、读文件内容、创建 Issue。不算复杂,但足够让我理解这个协议到底在解决什么问题。
环境准备
需要 Node.js 18+ 和 TypeScript。MCP 官方提供了 SDK,支持 TypeScript、Python 和 Java。
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
这里踩了第一个坑:SDK 版本。安装时默认拉到最新版,但文档和社区示例大多基于 0.x 版,API 差异不小。建议锁 "@modelcontextprotocol/sdk": "^0.5.0"。
理解核心概念
MCP 的通信模型很简单:Client(比如 Claude Code、Cursor)和 Server 之间走 JSON-RPC 2.0。Server 暴露三种能力:
- Tools:可被 LLM 调用的函数,像 API 端点
- Resources:暴露数据给 LLM 读取,类似只读接口
- Prompts:预设的提示模板
大多数场景下,你只需要实现 Tools。
开始写代码
SDK 提供了 Server 类,调用它的 registerToolSchemas 或 .tool() 方法就能注册工具。
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const server = new Server({
name: 'github-helper',
version: '1.0.0',
}, {
capabilities: { tools: {} },
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'get_repo_info') {
// 调用 GitHub API
return { content: [{ type: 'text', text: result }] };
}
});
const transport = new StdioServerTransport();
await server.connect(transport);
注册完让 Claude Code 使用的步骤更简单,在配置文件中加一段即可:
{
"mcpServers": {
"github-helper": {
"command": "node",
"args": ["path/to/server.js"]
}
}
}
配置完重启 Claude Code,在对话中就能直接让它"查一下这个仓库的信息"。
踩坑记录
1. 入参校验
第一个工具写好以后,传参数时总是报错。查了半天发现是 zod schema 和 MCP 的 inputSchema 格式不完全兼容。MCP 期望的是 JSON Schema 格式,zod 转换过去有时候会丢 required 字段。解决方案是手写 inputSchema:
inputSchema: {
type: 'object',
properties: {
repo: { type: 'string', description: '仓库名,如 owner/repo' },
},
required: ['repo'],
}
2. 超时
GitHub API 偶尔响应慢,MCP Server 默认超时时间很短。Claude Code 那边会显示"工具调用失败"。加了重试和超时配置才好:
const transport = new StdioServerTransport();
transport.setTimeout?.(30000); // 30 秒超时
3. Stdio 通信的限制
stdio 传输层通过标准输入输出通信,Server 不能主动推送消息给 Client。如果需要推送能力(比如实时通知),就得用 Streamable HTTP 传输层。
关于鉴权
MCP Server 的鉴权是个容易被忽略的问题。如果 Server 通过 stdio 本地运行,鉴权不是问题——它只在本机启动,能调用它的 CLI 工具自然有权限。
但如果你把 Server 部署成远程服务(比如通过 Streamable HTTP + SSE),鉴权就成了头等大事。目前 MCP 协议在鉴权这块还在演进中,常见的做法有两种:
- 在 Tool 内部做鉴权:每个请求都带 API Key 或 Token,Server 端校验
- 通过传输层鉴权:如果走 HTTP,可以依赖标准的 Bearer Token 或 OAuth
我个人倾向第一种——简单直接,而且不依赖传输层。缺点是需要每个使用这个 Server 的人配 API Key。
调试技巧
写 MCP Server 的时候调试是个痛点。因为走的是 stdio 通信,console.log 会污染 JSON-RPC 数据流。几个建议:
- 用
console.error代替console.log(stderr 不影响通信) - 开发时加一个简单的测试脚本,直接调用 Server 的 handler 函数
- 用 MCP Inspector(官方提供的调试工具)可视化查看请求和响应
这些技巧能帮你省掉大量排查时间。
总结
30 分钟能写出一个可用的 MCP Server。如果只是为了连接一个 API,SDK 抽象得足够简单。但生产环境要考虑的事情不少:鉴权怎么做、多个工具之间的状态怎么共享、长时间运行的任务怎么处理。
下一步我打算把它部署成远程服务,用 Streamable HTTP 替代 stdio,再补上 SSE 事件推送。到时候再写一篇。
关注我,不错过后续内容。