ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

新手避坑指南:MCP开发中最常见的10个错误

新手避坑指南:MCP开发中最常见的10个错误 摘要总结MCP开发中最常见的10个错误包括传输配置问题、工具定义格式错误、生命周期管理遗漏、参数校验缺失等每个错误附复现场景和解决方案帮助开发者避坑。新手避坑指南MCP开发中最常见的10个错误我把过去几个月写MCP Server踩过的坑整理成了这篇文章。这10个错误都是我亲自踩过的有些花了几分钟解决有些折腾了大半天。每个错误我都会给出错误现象、原因分析和解决方案希望能帮你少走弯路。按类别分成环境配置、协议理解、代码实现和调试排查四组。错误一 stdout输出污染协议消息错误现象Server连上Claude Desktop后没有任何响应Claude提示服务器连接失败。终端单独运行server也看不到任何错误但Inspector连接后报JSON解析错误。原因分析stdio传输模式下server的stdout是JSON-RPC消息的唯一通道。你在代码里用console.log()TypeScript或print()Python输出调试信息这些文字混进了stdout的消息流里。客户端收到这些非JSON内容后解析失败直接判定连接异常。我自己犯过一次特别蠢的错误。我在工具回调里加了一行console.log(收到请求, params)想看参数对不对结果加了这行之后所有工具调用都失败了。因为stdout里多了一段收到请求 …的纯文本紧跟在正常的JSON-RPC响应后面客户端把两段内容拼在一起解析当然报错。解决方案TypeScript里所有日志用console.error()写到stderr。Python里用print(..., filesys.stderr)或者配置logging库输出到stderr。stderr的内容不会干扰stdout上的协议消息客户端可以选择捕获或忽略stderr日志。// 错误写法会破坏协议console.log(处理请求,params);// 正确写法输出到stderrconsole.error(处理请求,params);# 错误写法print(处理请求,params)# 正确写法print(处理请求,params,filesys.stderr)# 或者用loggingimportlogging logging.info(处理请求 %s,params)如果你用的是HTTP传输模式stdout随便写都没事因为HTTP模式的响应走HTTP body不走stdout。但养成用stderr的习惯总没错。错误二 package.json缺少ESM声明错误现象TypeScript项目编译没问题运行时直接报错Error [ERR_REQUIRE_ESM]: require() of ES Module。原因分析MCP SDK的导入路径是modelcontextprotocol/sdk/server/mcp.js以.js结尾的ESM模块。Node.js判断一个模块是CommonJS还是ESM看package.json里的type字段。没声明type: module时Node默认按CommonJS处理遇到ESM导入就报错。解决方案package.json里加上type: module。{name:my-mcp-server,version:1.0.0,type:module,scripts:{build:tsc,start:node build/index.js}}同时tsconfig.json的module和moduleResolution都要设成Node16或NodeNext跟ESM声明保持一致。错误三 zod版本与SDK不匹配错误现象安装依赖后TypeScript编译报一堆类型错误错误信息涉及zod的内部类型比如Type ZodString is not assignable to type ...。原因分析modelcontextprotocol/sdk内部依赖zod 3.x的API。如果你装了zod 4.x两个版本的类型定义不兼容TypeScript编译器就炸了。zod 4对类型系统做了大改很多内部类型结构变了。解决方案锁死zod 3.x版本。npminstallmodelcontextprotocol/sdk zod3如果你项目里其他依赖已经引入了zod 4用npm overrides强制降级。{overrides:{zod:3.23.8}}或者把MCP Server单独拆成一个子项目避免依赖冲突。错误四 工具返回格式不符合MCP规范错误现象工具在Inspector里能调用也返回了结果但Claude Desktop里调用后显示工具执行出错或者结果为空。原因分析MCP规范要求工具返回一个特定结构的对象content字段是数组每个元素包含type和text或其他类型。很多人直接返回字符串或自定义对象格式客户端解析不到content就判定失败。我见过三种典型错误写法。第一种直接返回字符串。第二种返回{ text: 结果 }忘了包content数组。第三种content数组里的元素少了type字段。解决方案严格按规范返回。content数组里每个元素必须有type字段文本结果用type: text。// 错误写法1 直接返回字符串return查询结果;// 错误写法2 忘了content数组return{text:查询结果};// 错误写法3 少了type字段return{content:[{text:查询结果}]};// 正确写法return{content:[{type:text,text:查询结果}]};如果工具执行失败加上isError: true字段客户端会据此判断是否把错误信息展示给用户。return{content:[{type:text,text:ID不存在}],isError:true};错误五 initialize握手未完成就发请求错误现象自己写MCP客户端时连接server后立即调用tools/callserver返回错误码-32600 Invalid Request或者直接没响应。原因分析MCP协议规定握手必须按顺序完成。客户端先发initialize请求等server返回initialize响应后客户端必须发一个notifications/initialized通知然后才能进入正常操作阶段。在server收到initialized通知之前它不应该处理任何业务请求。很多人写客户端时漏掉了发initialized通知这一步或者initialize响应还没回来就迫不及待地发tools/list。解决方案严格按三步走。// 第一步 发送initialize请求send({jsonrpc:2.0,id:1,method:initialize,params:{protocolVersion:2025-06-18,capabilities:{},clientInfo:{name:my-client,version:1.0.0}}});// 第二步 等到收到initialize响应后发送initialized通知// 通知没有id字段不需要响应send({jsonrpc:2.0,method:notifications/initialized});// 第三步 现在才能发业务请求send({jsonrpc:2.0,id:2,method:tools/list});用官方SDK的话这些都帮你处理好了但了解底层流程有助于排查问题。错误六 环境变量未在配置中传递错误现象Server在终端直接运行一切正常配到Claude Desktop或Inspector里就连不上数据库报认证失败。原因分析stdio模式下host应用启动server子进程时只继承有限的环境变量。你终端里有DATABASE_URL、API_KEY这些变量但子进程不一定能继承到。具体继承哪些变量跟操作系统和host实现有关。解决方案在host的配置文件里显式声明环境变量。Claude Desktop的配置如下。{mcpServers:{myserver:{command:node,args:[/absolute/path/to/build/index.js],env:{DATABASE_URL:postgresql://localhost/mydb,API_KEY:your-api-key}}}}Inspector里在连接面板的Environment区域添加键值对。我建议用.env文件管理环境变量开发时用dotenv库加载。但部署到host时host的env配置优先级更高因为子进程可能读不到.env文件工作目录不确定。错误七 Windows路径反斜杠导致连接失败错误现象Windows上开发MCP Server配置到Claude Desktop后死活连不上报找不到文件或模块。原因分析Claude Desktop的配置文件是JSON格式。Windows路径用反斜杠分隔比如C:\Users\name\project。但JSON里反斜杠是转义字符C:\Users里的\U会被当成转义序列处理导致路径损坏。解决方案两种写法都行。第一种用双反斜杠。{mcpServers:{myserver:{command:node,args:[C:\\Users\\name\\project\\build\\index.js]}}}第二种用正斜杠Windows的Node.js和Python都支持正斜杠。{mcpServers:{myserver:{command:node,args:[C:/Users/name/project/build/index.js]}}}我推荐用正斜杠简单不容易出错。command字段里的可执行文件路径也要注意uv和node最好写完整路径因为host的PATH环境可能跟终端不一样。错误八 能力协商不匹配导致-32602错误错误现象Server运行正常但调用特定功能时客户端返回错误码-32602 Invalid params。比如server想请求客户端做sampling报错说不支持。原因分析MCP协议在initialize握手时做能力协商。客户端和server各自声明自己支持的能力只有双方都声明的能力才能使用。如果server发了sampling请求但客户端没在initialize里声明sampling能力就会报-32602错误。这个错误很隐蔽因为握手本身是成功的server也正常启动了只有用到特定功能时才暴露。解决方案检查initialize握手时双方声明的能力。用Inspector连接server在通知面板里查看initialize请求和响应的完整内容。客户端能力包括roots提供文件系统根目录、sampling支持LLM采样、elicitation支持服务端向用户提问。Server能力包括prompts、resources、tools、logging、completions。如果你的server需要用sampling确保客户端声明了这个能力。Claude Desktop支持sampling但一些轻量客户端可能不支持。代码里做防御性处理。// 检查客户端是否声明了sampling能力if(!clientCapabilities?.sampling){// 降级处理用本地逻辑代替samplingreturnfallbackResult;}错误九 stdio消息包含嵌入换行符错误现象Server返回包含多行文本的工具结果时客户端偶尔解析失败。短文本没问题长文本或包含换行的文本容易出问题。原因分析stdio传输的消息按换行符分隔。协议规范明确要求消息MUST NOT包含嵌入的换行符。如果你在JSON-RPC消息的文本内容里有\n并且消息序列化时这些换行符没被正确转义就会把一条消息截断成两条客户端解析第二条时失败。通常JSON.stringify会正确转义字符串里的换行符为\n字面量所以大多数情况没问题。但如果你手动拼接JSON字符串或者用了某些会保留原始换行符的序列化方式就会出问题。解决方案永远用JSON.stringify序列化消息别手动拼JSON字符串。确保消息在写入stdout时是单行的。// 正确做法 用JSON.stringify自动转义换行符constmessageJSON.stringify({jsonrpc:2.0,id:1,result:{content:[{type:text,text:第一行\n第二行\n第三行}]}});// JSON.stringify会把\n转义成\\n输出是单行process.stdout.write(message\n);# Python同理importjson messagejson.dumps({jsonrpc:2.0,id:1,result:{content:[{type:text,text:第一行\n第二行}]}})# json.dumps默认转义换行符sys.stdout.write(message\n)sys.stdout.flush()错误十 请求超时未处理导致连接挂起错误现象工具执行时间较长比如调用外部API客户端等了很久没响应最终连接卡死或超时断开。Server这边其实还在执行但结果发回去时客户端已经不听了。原因分析MCP协议建议所有请求都设置超时。客户端等不到响应会认为请求失败可能发送取消通知或直接断开连接。如果你的工具执行一个耗时30秒的API调用而客户端超时设为10秒就会出问题。解决方案两方面处理。第一工具内部做好超时控制别让单个请求卡太久。asyncfunctioncallExternalAPI(url:string):Promisestring{constcontrollernewAbortController();// 设置10秒超时consttimeoutsetTimeout(()controller.abort(),10000);try{constresponseawaitfetch(url,{signal:controller.signal});returnawaitresponse.text();}catch(err){if(errinstanceofErrorerr.nameAbortError){return请求超时请稍后重试;}throwerr;}finally{clearTimeout(timeout);}}第二长时间任务用进度通知。MCP支持在工具执行过程中发送notifications/progress通知告诉客户端进度。客户端收到进度通知可以重置超时计时器。server.registerTool(long_task,{description:执行耗时任务,inputSchema:{steps:z.number().int().positive().describe(执行步数),},},async({steps},context){constresults[];for(leti0;isteps;i){// 执行每一步awaitdoStep(i);results.push(步骤${i}完成);// 发送进度通知// context里可以访问session发通知// progressToken从请求的_meta字段获取}return{content:[{type:text,text:results.join(\n)}],};});错误归类总结把上面10个错误按类别归一下。类别错误编号共性问题环境配置二、三、六、七依赖版本、模块系统、环境变量、路径格式协议理解五、八、九握手顺序、能力协商、消息格式代码实现一、四输出通道、返回格式调试排查十超时处理、进度反馈环境配置类的错误最多占了4个。这些错误的特点是代码逻辑没问题但因为运行环境差异导致失败。解决办法是固定开发规范zod锁版本、ESM必声明、路径用绝对正斜杠、环境变量显式传。协议理解类的错误最隐蔽因为握手看起来成功了只有用到特定功能才暴露。养成用Inspector查看完整initialize握手消息的习惯能提前发现能力不匹配的问题。小结这10个错误覆盖了MCP开发中最常见的翻车场景。环境配置类注意zod锁版本、ESM声明、环境变量传递和路径格式。协议理解类注意握手三步走、能力协商和消息换行符。代码实现类注意stdout只发协议消息、工具返回用标准content数组。调试排查类注意超时控制和进度通知。核心原则就是别跟规范较劲严格按协议文档来能避开绝大部分坑。相关推荐5分钟跑通你的第一个MCP ServerPython版安全防护基础认证授权、输入消毒、权限控制测试与调试MCP Inspector、单元测试、集成测试
返回列表