027、MCP 协议入门:架构设计与第一个 MCP Server

发布时间:2026/6/19 11:27:40
027、MCP 协议入门:架构设计与第一个 MCP Server 027、MCP 协议入门架构设计与第一个 MCP Server上周五凌晨两点我盯着终端里一行诡异的报错发呆Error: Tool execution failed: Cannot read properties of undefined (reading schema)Claude Code 调用我写的自定义工具时schema 字段丢了。排查半天发现是 MCP 协议里 tool 定义少了个inputSchema的嵌套层级。这种低级错误放在白天可能一眼就看出但凌晨的代码审查总是容易漏掉细节。MCP 协议看着简单真写起来坑不少。为什么需要 MCP先说说背景。Claude Code 的能力扩展靠的是工具Tool但早期每个工具都得自己写 HTTP 接口、自己处理认证、自己定义参数格式。不同工具之间没有统一规范Claude Code 调用时得针对每个工具写不同的适配代码。MCPModel Context Protocol就是来解决这个问题的。它定义了一套标准协议让 Claude Code 和外部工具之间能通过统一的格式通信。你可以把它理解成 AI 世界的“USB 接口”——不管背后是什么设备插上就能用。MCP 的核心架构分三层Transport Layer负责数据传输支持 stdio本地进程通信和 SSEServer-Sent Events远程通信Protocol Layer定义消息格式、请求/响应模式、错误处理Application Layer具体的能力定义比如 tool、resource、promptClaude Code 用的是 stdio 模式启动一个子进程通过标准输入输出交换 JSON 消息。远程场景用 SSE但开发阶段用 stdio 调试更方便。协议消息格式MCP 的消息格式借鉴了 JSON-RPC 2.0但做了扩展。每条消息必须包含jsonrpc、method、id三个字段{jsonrpc:2.0,method:tools/call,id:req-001,params:{name:get_weather,arguments:{city:北京}}}响应格式{jsonrpc:2.0,id:req-001,result:{content:[{type:text,text:北京当前温度22°C晴}]}}注意content是个数组可以返回多个内容块支持 text、image、resource 等类型。这里踩过坑如果只返回一个字符串Claude Code 会报解析错误必须包在数组里。第一个 MCP Server直接上代码。我用 Node.js 写一个最简单的 MCP Server实现一个文件搜索工具。// mcp-server.js// 别这样写用 express 起 HTTP 服务MCP 协议不认// 正确姿势用 modelcontextprotocol/sdkimport{Server}frommodelcontextprotocol/sdk/server/index.js;import{StdioServerTransport}frommodelcontextprotocol/sdk/server/stdio.js;constservernewServer({name:file-search-server,version:1.0.0},{capabilities:{tools:{}// 声明支持工具能力}});// 定义工具列表server.setRequestHandler(tools/list,async(){return{tools:[{name:search_files,description:在指定目录搜索文件支持通配符,inputSchema:{type:object,properties:{pattern:{type:string,description:搜索模式如 *.js 或 **/*.ts},directory:{type:string,description:搜索目录默认当前目录}},required:[pattern]}}]};});// 处理工具调用server.setRequestHandler(tools/call,async(request){// 这里踩过坑request.params 才是参数不是 request.argumentsconst{name,arguments:args}request.params;if(namesearch_files){const{pattern,directory.}args;// 实际搜索逻辑这里简化constresultsawaitsearchFiles(pattern,directory);return{content:[{type:text,text:JSON.stringify(results,null,2)}]};}thrownewError(Unknown tool:${name});});// 启动服务consttransportnewStdioServerTransport();awaitserver.connect(transport);关键点capabilities 必须声明不声明 tools 能力Claude Code 不会调用你的工具inputSchema 嵌套层级inputSchema是 tool 对象的属性不是直接放在 tool 里。我凌晨踩的坑就是这个请求处理区分tools/list返回工具列表tools/call执行具体调用在 Claude Code 中配置写好的 MCP Server 需要在 Claude Code 的配置文件中注册。配置文件位置macOS/Linux:~/.claude/claude_desktop_config.jsonWindows:%APPDATA%\Claude\claude_desktop_config.json{mcpServers:{file-search:{command:node,args:[/path/to/mcp-server.js],env:{NODE_ENV:production}}}}配置完重启 Claude Code在对话中输入“搜索当前目录下所有 .md 文件”Claude Code 会自动调用你的工具。调试技巧MCP Server 的调试比普通服务麻烦因为走的是 stdio没有 HTTP 请求可以抓包。我的调试三板斧日志重定向把日志写到文件不要往 stdout 打否则会污染协议消息constfsrequire(fs);constlogfs.createWriteStream(/tmp/mcp-debug.log,{flags:a});log.write([${newDate().toISOString()}] 收到请求:${JSON.stringify(request)}\n);手动模拟请求用 echo 命令模拟 Claude Code 的请求echo{jsonrpc:2.0,method:tools/list,id:test-001}|nodemcp-server.js检查返回格式Claude Code 对返回格式要求严格少个字段就报错。写个测试脚本验证所有工具调用的返回格式常见坑点超时问题MCP 协议默认超时 60 秒长时间运行的任务要拆成异步 进度通知错误处理工具执行出错要返回 error 对象不是直接抛异常参数校验Claude Code 传的参数可能不符合 schema服务端要做防御性校验并发调用Claude Code 可能同时调用多个工具服务端要做好并发控制个人经验MCP 协议的设计思路很清晰——把 AI 和工具的交互标准化。但实际开发中协议细节的坑不少。我的建议是先写一个最简单的工具比如返回当前时间跑通整个链路再逐步加复杂逻辑。调试阶段用 stdio 模式日志一定要写到文件。schema 定义要严格Claude Code 的 LLM 有时候会脑补参数服务端校验能拦住大部分问题。另外别想着一次把所有工具都写完。MCP 支持动态注册工具可以先暴露两三个核心功能跑起来再迭代。协议版本目前还在快速演进保持 SDK 更新关注 breaking changes。下篇会讲 MCP 的进阶用法如何实现流式响应、工具链编排、以及和现有 API 网关的集成方案。