MCP协议踩坑实录:我以为自己懂了,直到遇到这三个问题
分享自己在探索MCP协议过程中的三个关键踩坑经历:1)误以为进程启动就能立即通信,实际上需要等待服务端就绪;2)错误套用Function Calling参数格式,发现MCP使用不同的inputSchema结构;3)忽略JSON消息必须显式分隔的要求。文章揭示了MCP与Function Calling的本质差异,指出思维定势比技术本身更易造成障碍。作者最终实现了一个包含5个工具的实验项目,并探讨了M
2026年2月17号,Slack宣布发布官方MCP Server。紧接着Amazon Quick、People.ai、Google AI都跟进。整个Twitter上都在讨论这些大厂的动作。
我突然意识到:这东西已经从"小众协议"变成"行业标准"了。我再不研究研究,可能真的要落伍。
结果接下来的72小时,我经历了从"这很简单"到"我是不是傻了"再到"原来如此"的完整心路历程。
第一个坑:我以为进程启动了就能发消息
MCP支持多种transport,stdio是最基础的。我的理解是:启动Server进程,然后往stdin写JSON,完事。
代码写出来是这样的:
const proc = spawn('node', ['server.js']);
proc.stdin.write('{"jsonrpc":"2.0","method":"initialize"}\n');
然后就没有然后了。
Server没反应,Client一直等,最后超时。我以为是JSON格式错了,改了半天。又以为是换行符问题,试了\n和\r\n。
折腾了两个小时,我突然意识到:Server启动和Server Ready是两回事。
MCP Server启动后要先做初始化,比如加载工具定义、检查环境。这个过程可能需要几百毫秒。而我是在spawn之后立即发消息,Server还没准备好接收。
解决方法很糙,但有效:
const proc = spawn('node', ['server.js']);
// 等Server输出Ready信号,或者简单粗暴地sleep
await new Promise(resolve => {
proc.stderr.on('data', (data) => {
if (data.toString().includes('Server ready')) {
resolve();
}
});
});
// 现在才能发消息
proc.stdin.write('{"jsonrpc":"2.0","method":"initialize"}\n');
或者你可以用更粗暴的方式:
await new Promise(r => setTimeout(r, 500)); // 等500ms
这方法不优雅,但管用。
第二个坑:我以为参数定义和Function Calling一样
定义工具的时候,我直接用了OpenAI Function Calling的格式:
{
name: 'read_file',
parameters: { // ❌ 错了
type: 'object',
properties: { path: { type: 'string' } }
}
}
Client一直报错,说schema验证失败。我查了半天MCP文档,终于在一个角落里找到了:MCP用的是inputSchema,不是parameters。
而且还有一个坑:description字段是必需的。
{
name: 'read_file',
description: '读取文件内容', // ✅ 必须
inputSchema: { // ✅ 是inputSchema,不是parameters
type: 'object',
properties: {
path: {
type: 'string',
description: '文件路径' // 最好也写上,帮助AI理解
}
},
required: ['path']
}
}
这个坑花了我一晚上。不是因为多难,是因为我用旧知识的惯性去理解新协议,结果一直在错误的方向上打转。
第三个坑:我以为JSON消息不需要显式分隔
stdio transport的消息边界是靠newline(\n)分隔的。这个我知道,但我以为JSON.stringify之后默认会处理。
结果我的代码是这样的:
proc.stdin.write(JSON.stringify(request)); // ❌ 没有换行!
然后Server端接收的时候,两条消息粘在一起了:
{"jsonrpc":"2.0","id":1...}{"jsonrpc":"2.0","id":2...}
Server解析失败,直接报错。
解决方法很简单,但容易被忽略:
proc.stdin.write(JSON.stringify(request) + '\n'); // ✅ 必须加换行
这个bug让我debug了将近一个小时。因为有时候消息不粘连,能正常跑,有时候又出问题。后来发现是因为异步执行时机不确定,有时候两条消息之间刚好有时间间隔,有时候没有。
我的实验项目
折腾了三天,我终于搞出了一个能跑通的版本。项目结构是这样的:
mcp-experiment-zyt/
├── src/
│ ├── server.js # MCP Server
│ ├── client.js # MCP Client
│ └── tools/ # 工具实现
├── experiment/ # 踩坑记录
│ ├── v1-no-wait.js # 第一个坑
│ ├── v2-wrong-schema.js # 第二个坑
│ └── v3-missing-newline.js # 第三个坑
├── test/
│ └── test-connection.js
└── README.md
每个experiment/里的文件都标注了问题和解决方法。如果你也在踩类似的坑,可以去看看。
Server提供的工具
read_file- 读取文件(限制在项目目录)list_directory- 列出目录write_file- 写入文件web_search- 简单网页抓取(实验性)get_system_info- 系统信息
运行效果
$ npm run client -- --test
🧪 测试 MCP 连接...
✅ 连接测试通过
✅ 发现 5 个工具
✅ 系统信息工具调用成功
返回: linux
✅ 目录列表工具调用成功
所有测试通过!🎉
我的真实感受
一天天折腾下来,我的感受是:
MCP本身不复杂,复杂的是你对它的假设。
如果你带着Function Calling的经验去学MCP,你会处处碰壁。因为它们虽然都是"让AI调用工具",但设计理念完全不同:
- Function Calling是能力,MCP是协议
- Function Calling是应用内部的事情,MCP是跨应用的标准
- Function Calling的schema相对宽松,MCP的要求更严格
最大的坑不是技术本身,是思维定势。
折腾完这个项目,我一直在想:MCP会不会成为行业标准?
Slack、Amazon、Google都在跟进,看起来势头很猛。但协议本身还在快速迭代,SDK的API变化很大。我估计再过几个月才能稳定下来。
你怎么看?你觉得MCP会替代Function Calling,还是会长期共存?
欢迎在评论区聊聊,我也在摸索中。
完整代码在这里:📁 https://github.com/YaBoom/mcp-experiment-zyt
更多推荐

所有评论(0)