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

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐