MCP Server上线前体检:用Inspector逐项验证协议、Tools与Resources
最近在给团队维护一个内部 MCP Server每次版本更新前我都会用官方 Inspector 做一轮“只读体检”。MCP Server 这层东西很有意思它本身不产数据也不直接执行业务逻辑而是把 Tools、Resources、Prompts 这些能力包装成标准协议让 AI 客户端能像调接口一样调用它们。也正因为多了这层协议转换很多问题不是逻辑错而是协议格式、资源定位、参数定义这些细节出状况线上暴露一个就是一个故障。所以上线前用 Inspector 逐项验证协议握手、工具定义、资源读取和提示词模板是成本最低的一道防线。这篇文章不聊太深的设计哲学就讲我实际检查时做的事怎么看协议层有没有对齐怎么逐个验证 Tools、Resources、Prompts以及这半年踩过哪些坑。适合正在写 MCP Server、或者准备把 AI 工具链接入内部系统的同学参考。内容偏实战按照“理解概念 → 动手检查 → 排查问题 → 建立清单”的顺序展开。1. 为什么要给 MCP Server 做“上线前体检”1.1 MCP Server 到底解决了什么问题先对齐一个基本认知。MCPModel Context Protocol模型上下文协议解决的是 AI 模型怎么安全、标准化地访问外部能力的问题。以前每个人做 AI Agent 都要自己设计一套工具调用规范A 项目定义callTool(name, params)B 项目定义一个/invoke接口AI 客户端接到这些五花八门的 API 根本没法统一处理。MCP 把“工具调用”“资源读取”“提示词模板”这些能力抽象成一套标准协议服务端实现这套协议任何支持 MCP 的客户端都能理解它。MCP Server 就是这套协议的服务端实现。它本身不是一个业务系统而是一个“翻译层”把内部的数据库查询、外部 API、文档检索、业务函数等能力包装成标准的 Tools、Resources、Prompts。AI 客户端通过协议发现这些能力并在合适的时机调用它们。但这带来了一个新的复杂点你的业务逻辑可能没错但只要协议层没有严格遵守规范客户端就发现不了、调用不对、甚至直接报错。这类错误在传统 API 调试里不太常见因为传统 API 是你定义好了 JSON 格式两边都按这个格式写代码而 MCP 的客户端是“自动发现”你的能力定义如果定义不规范自动化的链路就会断。1.2 检查的难点在哪协议层和应用层很多人第一次写 MCP Server 时习惯性地只测“功能能不能跑通”比如写了个查询订单的工具本地调一下返回正常就觉得可以上线了。问题是功能跑通只能说明“你的 Server 内部逻辑没问题”并不能说明“你的 Server 与任意 MCP 客户端之间的协议交互没问题”。这两个层面完全是两回事。应用层是你自己代码里的事比如查询数据库、调 API、拼装结果协议层是你和 AI 客户端之间的约定包括 handshake 时返回什么 capabilities、Tools 的 JSON Schema 怎么写、Resources 的 URI 稳不稳定、Prompts 的参数名是否和描述一致。后者才是 MCP Server 区别于普通 API 服务的核心难点。我见过不少项目Server 单独测一切正常但接到真实客户端就傻眼有的客户端要求所有 Tool 返回值里必须是content数组你在数组里塞了个自定义字段客户端就解析不出来有的客户端在发起请求时会先调用resources/list如果你的 Server 在这个方法上报错所有客户端都会认为你不可用。这些都不是“功能 bug”而是“协议 bug”必须在环境里用标准工具验证。1.3 为什么选 Inspector 做只读检查MCP 官方提供了一个调试工具就叫 Inspector。它的作用是用一个图形界面连接你的 MCP Server把协议交互过程完整暴露出来连接建立之后你能看到 Server 声明了哪些 capabilities、暴露了哪些 Tools、Resources、Prompts还可以手动触发一次协议调用并查看完整请求返回内容。我把它称为“只读体检”主要是说两件事。第一Inspector 本身不会修改你的 Server 代码也不会上线任何东西它只做一个观察者第二它对 Server 的绝大多数操作是“列表”“读取”“查看定义”不会自动执行任何危险动作。唯一可能触发真实行为的是你在界面上手动点某个 Tool 的 Call 按钮这个操作由你控制所以整体风险可控非常适合在上线前做一轮不污染数据的预检。当然你可能会问官方 SDK 里不是有测试客户端吗自己写个脚本也能调。确实可以但 Inspector 的价值在于“图形化 协议日志透明 内置验证工具”。它能让你像看浏览器开发者工具一样逐条查看协议报文定位问题在哪一个环节。脚本测试适合回归Inspector 适合排查和人工审查两者结合才最稳。2. 拆解四个检查对象协议、Tools、Resources、Prompts2.1 协议层握手、初始化、能力协商MCP 的协议层像一个“入职流程”。客户端连上 Server 之后第一件事不是直接调工具而是先打招呼客户端发送initialize请求告诉 Server 自己支持的协议版本Server 返回自己的版本、能力列表、实现信息然后双方再发一个initialized通知确认可以开始干活。这个阶段最容易出的问题就是版本不匹配。MCP 协议版本有一个字符串标识比如2024-11-05这种日期格式。如果 Client 端 SDK 比较新、Server 端 SDK 比较旧或者反过来两边在版本协商上就可能出问题。规范的 Server 应该在自己的 capabilities 里明确声明支持哪些功能还要用正确的方式告诉客户端我支持工具但不支持资源订阅我支持提示词但不支持流式响应。用 Inspector 检查时重点关注两个地方。第一是initialize响应的capabilities字段看看哪些能力被声明了第二是后续每次请求响应里是否都带有正确的 JSON-RPC 消息结构。很多问题其实不是功能逻辑问题而是协议字段不完整、类型不对、甚至返回了非 JSON 内容这类错误在 Inspector 里一眼就能看到。2.2 ToolsAI 能调用的“双手”Tools 是 MCP 里最核心的能力也是开发者最先接触的概念。简单理解Tools 就是暴露给 AI 的函数。AI 不能直接执行代码它会根据用户的指令和上下文决定“要不要调用某个工具”“传什么参数”然后由 MCP Server 去执行把结果返回给 AI。每个 Tool 由几个关键字段组成name工具名必须唯一且符合命名规范、description描述这个工具是做什么的、什么时候用、inputSchema一个 JSON Schema描述这个工具需要哪些参数、参数类型是什么、哪些必填。这仨字段任何一个写得不清楚AI 都可能“看不懂”你的工具。name别乱起尽量用下划线分隔的英文小写比如query_order不要带空格、中文、特殊符号。description要写清楚“什么时候该用、什么时候不该用”因为 AI 是靠文本来理解工具用途的描述太笼统会导致它明明能调你的工具却不调。inputSchema则是 AI 生成参数的依据字段名、类型、必填性、枚举值都要写准确否则 AI 会传错参数。上线前检查 Tools核心是看三层定义是否合规、参数能否通过 Schema 校验、实际调用是否能返回结构化结果。Inspector 会把 Server 声明的所有 Tools 列出来你可以逐个打开看定义也可以直接填参数试调用非常直观。2.3 ResourcesAI 能读取的“资料库”Resources 是 MCP 里容易被忽略、但线上出问题最多的部分。它表示可被 AI 读取的上下文数据有点像是给模型提供的“资料库”。和 Tools 不同Resources 不执行动作只是提供内容。AI 读取一个 Resource就像人打开一份文档内容被塞进上下文供模型理解。每个 Resource 一般有uri、name、description和可选的mimeType。URI 是它的唯一标识常见的有file:///path/to/file、https://example.com/doc也可以定义自定义 scheme比如doc://order/123。客户端会在需要时用resources/read请求读取这个 URI 对应的内容。Resources 最容易出问题的地方是 URI 的稳定性和读取权限。假如你的 Resource URI 里带时间戳或者临时拼接标识AI 拿到一个 URI 之后过几分钟再去读就失效了这种体验非常差。再比如同一个资源既能被 A 客户端读取、又要供 B 客户端订阅如果权限控制不清晰就会偶发“读到了但内容为空”的情况。用 Inspector 检查 Resources重点看三件事resources/list返回的列表是否完整且稳定、每个 URI 是否能正常被resources/read读取、返回的mimeType和内容格式是否一致。这个环节没有太多花哨操作核心就是“列表里有什么读取就一定能读到”。2.4 PromptsAI 的“标准话术模板”Prompts 是 MCP 里偏“保守”但很实用的能力。它不是自动运行的工具而是一组可复用的提示词模板。客户端可以根据场景向 Server 请求某个 Prompt 模板然后拿到一段结构化的对话消息再把这段消息塞给模型或对话系统。一个 Prompt 包含name、description和arguments参数定义。比如你创建一个叫summarize_doc的 Prompt参数是doc_uri客户端请求后Server 返回一段消息模板“请总结以下文档 {doc_uri} 的内容……”这样就把提示词逻辑收敛在 Server 端客户端不用硬编码提示词也方便统一管理和更新。Prompts 线上常见问题包括模板里的参数名和arguments定义不一致、缺少描述导致客户端不知道什么时候用这个模板、返回的消息格式不符合对话结构等。Inspector 的 Prompts 页签会让你看到这个 Prompt 接收哪些参数、参数是什么类型还能实际渲染一次看最终生成的消息长什么样非常方便排查模板问题。3. 实操走一遍Inspector 从启动到全绿的关键步骤3.1 启动 Inspector 并连接本地 Server先讲最常用的本地连接方式。MCP Server 最常见的是 stdio 模式Server 是一个通过标准输入输出通信的进程客户端去拉起这个进程然后在一个管道里交换消息。Inspector 支持这种模式启动之后你需要在它的界面里填上“启动 Server 的命令”和“工作目录”。具体操作是这样。项目根目录下先确保你的 MCP Server 已经能被命令行启动比如node dist/index.js或python main.py。然后用 npx 启动 Inspectornpx modelcontextprotocol/inspector启动后浏览器打开 http://localhost:6274左侧会有一个连接配置区。Transport Type 选 stdio然后在 Command 填nodeArguments 填dist/index.js如果有环境变量也可以填在 Env 里。填完后点 Connect。这里有个很容易踩的坑很多人的 Server 是 TypeScript 写的跑之前没编译导致填了node dist/index.js之后进程直接就退出。建议先手动在终端跑一次这个命令确认进程能常驻、没有立即退出的现象再填到 Inspector 里。不然你根本分不清是 Inspector 的问题还是 Server 启动的问题。如果你想检查的 Server 跑在远端还可以用 SSE 或者 HTTP 传输模式。这时 Inspector 的 Transport Type 选 SSE 或 Streamable HTTP填上远程地址就行。这个模式适合检查已经部署在测试环境的 Server不用把代码拉下来。3.2 逐步验证协议交互连接成功后Inspector 主界面会展示一个完整的请求日志列表。每次客户端发送 JSON-RPC 请求、Server 返回响应都会以时间线的方式列出来。这一步是检查协议层最关键的入口。第一次连上时你应该立刻能看到一条initialize请求和对应响应。点开响应检查几个关键点protocolVersion是否是你预期的版本capabilities里有没有声明tools、resources、prompts如果你只实现了 Tools那能力列表里就只有tools这是正常的serverInfo里name和version能不能准确标识你的服务。接着看notifications/initialized通知如果这条没有出来说明客户端和 Server 的握手流程不完整之后很多调用可能都不生效。日志区还有过滤功能你可以只看请求、只看响应、或者只看错误消息。建议把错误打开因为很多 Server 在正常流程里会夹杂一些非致命错误比如某个 resource 读取失败但 Server 并没有终止。这些错误如果不逐一排查上线后可能变成定时炸弹。3.3 逐个检查 Tools 定义并试调用协议握手没问题后切到 Tools 页签。Inspector 会自动调用tools/list把你的 Server 上所有工具列出来。这时候先别急着试调用先把列表完完整整看一遍。检查顺序我一般按三步走。第一步看数量对不对你预期暴露 5 个工具列表里是不是刚好 5 个有没有多出一些忘了删的调试工具。第二步逐个点开工具定义核对name、description、inputSchema。重点看 inputSchema 里的字段名、类型、是否 required以及 description 是否足够清晰。第三步选几个核心工具做一次真实调用。试调用时要注意参数填写。Inspector 会让你输入一个符合 Schema 的 JSON 对象比如{ order_id: 20240815001, include_detail: true }点击运行后观察返回结果。正常的 Tool 返回应该包含content数组每条 content 是一个结构化对象最常用的是{type: text, text: 查询结果...}。如果你返回了自定义格式比如直接返回{code: 0, data: ...}放在 content 外面很多客户端会解析不到这个在 Inspector 里能立刻看到。再强调一下安全Inspector 本身只读但 Tool 的 Call 是真实执行。如果这个工具会写数据库、发消息、甚至扣费上线前试调用时要小心。我的做法是给这类危险工具加一个环境开关在测试环境里用一个 mock 版本或者传入一个专门的测试参数确保不会影响真实数据。3.4 验证 Resources 的读取链路切到 Resources 页签Inspector 会调用resources/list给你展示所有 Server 声明的资源。这里有一个常见现象如果你没有实现 Resources 相关方法这个页签会显示空列表没关系说明你的 Server 不提供资源能力但如果你明明实现了列表却是空的那就要查服务端代码里listResources的返回了。列表展示后重点做两件事。第一逐个检查 URI 的格式和数量。URI 必须是稳定唯一的我建议实际用手点一遍每个资源试试能不能正常读取。第二观察读取结果。点开某个资源后Inspector 会发送resources/read请求并把返回的内容显示出来。检查返回内容的格式如果声明了application/json那内容应该是 JSON如果声明了text/plain那随意。特别提醒有些 Server 的 Resource 内容是动态生成的比如根据当前时间返回不同数据但 URI 一样。这种情况本身没问题但如果你发现“同样的 URI上一次读取成功这一次读取超时”那就要考虑是不是动态生成逻辑里有耗时的外部依赖。Inspector 日志里会显示每条请求的耗时看到耗时异常偏高的资源上线前一定要优化。3.5 检查 Prompts 的模板与参数Prompts 页签的操作逻辑和 Tools 类似但目的不同。Tools 的检查重点是“调用结果是否正确”Prompts 的检查重点是“模板渲染出来是什么样”。进入页签后你会看到prompts/list返回的所有提示词模板。打开一个模板能看到它定义的参数。比如一个 Prompt 叫generate_meeting_summary参数有meeting_id类型是 string必填。你可以在 Inspector 里填上参数然后点渲染它会发出prompts/get请求返回一组合法消息。检查这个返回消息时重点看三块。第一是消息的角色序列是否合理通常是一个 system 或 user 消息第二是模板里插值后的文本是否通顺有没有留下没替换的{xxx}占位符第三是参数类型和描述是否与模板里的用法吻合。如果模板里用了{date}但参数定义里根本没有 date客户端发送请求时不知道要传这个参数最后就渲染不出来。如果你没实现 Prompts 能力页签同样是空的。但要注意Inspector 的空列表和“方法未实现”的错误提示展示方式不太一样。看到页签为空别急着下结论去日志区看看有没有类似Method not found: prompts/list的报错如果有说明你的 Server SDK 版本可能不支持 Prompts需要升级。4. 常见问题与排查技巧实录4.1 连接不上先分清 stdio 和 HTTP要我说Inspector 的报错信息里十次有八次是“连接不上”引发的后续连锁反应。很多人看到红色错误提示就慌了其实排查思路很简单先确认你用的传输方式对不对。如果你的 Server 是本地进程默认用 stdio。这个时候检查两点命令行能不能手动启动成功启动后进程是不是一直在运行。一旦进程打印一行日志就退出Inspector 自然会显示连接失败或者一连接就断开。我遇到最多的原因就是路径不对当前工作目录不是项目根目录导致dist/index.js找不到。如果你的 Server 是远程部署确认你填的是 SSE 或 Streamable HTTP 的地址而不是业务接口地址。有些框架会把 MCP 端点挂在/mcp路径下如果你填了根路径/也会连不上。先拿 curl 试一下端点是否可达curl -X POST http://your-server/mcp \ -H Content-Type: application/json \ -d {jsonrpc:2.0,id:1,method:initialize,params:{protocolVersion:2024-11-05,capabilities:{},clientInfo:{name:test,version:1.0}}}能收到响应再去 Inspector 里连基本就没问题。4.2 工具定义“合法但不可用”schema 与序列化问题有一种很隐蔽的问题Server 端的工具定义看着没问题schema 也是标准 JSON Schema但 AI 客户端调用时总是传错参数或者调用返回后 AI 读不懂结果。这一类问题的根源往往在“类型解析”上。我给你举一个真实例子有个工具返回的数据里包含一个嵌套对象定义时写的是type: object但实际返回的时候代码把对象转成了字符串。从协议角度看字段类型和真实值不一致客户端解析自然出错。另一个常见的坑是 JSON Schema 里忘了加additionalProperties: false。如果 Server 内部对参数做了严格校验AI 多传了一个未定义的字段请求就会被拒绝。但更常见的情况是 AI 看到字段名“差不多”就传了如果 schema 不约束多余字段会被忽略导致核心逻辑拿不到正确参数。我建议在关键工具上把additionalProperties设为 false同时在描述里写明“除下列参数外不要传其他字段”。用 Inspector 排查这类问题不要只看“调用是否成功”还要把请求参数原样复制出来看 AI 实际传的参数和你 Schema 的匹配度。如果 AI 总是漏掉某个必填参数大概率是你 description 没写清楚或者参数名有歧义。改 description 比改代码更管用。4.3 资源读取偶发失败URI 稳定性与权限Resources 的偶发失败最让运维头疼。它不是 100% 失败而是时好时坏这种问题用 Inspector 反而最容易复现因为你可以反复点同一个资源的 Read多试几次看失败率。如果是“读一次成功、读一次失败”优先怀疑 Server 端的缓存或权限逻辑。有些 Server 在第一次读取时会走一次外部接口拿数据并缓存第二次直接从缓存返回但只要缓存过期下一次读取就会再次触发外部接口外部接口慢或挂了读取就失败。另一个和权限相关许多内部系统会在 Resource 读取接口上做鉴权比如要求 Header 里带 token。输入 Inspector 连接信息时没配好 header就会导致部分需要认证的资源读取失败。排查时可以看响应状态码如果返回 401、403基本就是认证问题如果返回 500 或超时才是服务端逻辑问题。最后提醒一下Resources 的mimeType要设置准确。你声明这是text/markdown结果返回内容是一段二进制乱码客户端拿去做上下文反而会污染模型理解。上线前至少要把每个资源读出来的内容用肉眼扫一遍。4.4 提示词调用报错参数名拼写与缺省值Prompts 的报错相对好排查因为它本质是“模板字符串 参数替换”。Inspector 的报错信息如果提示Missing required argument: xxx先回去看模板定义里arguments列表是不是有这个参数名是不是大小写对不上这里有个细节容易踩坑模板定义里参数名用的是meetingId驼峰但模板字符串里引用的是meeting_id下划线渲染时就会替换不到。你也许觉得这不会发生但多人协作的项目里定义模板和实现模板的人不是同一个这种低级错误真的很常见。还有一种情况是参数有默认值。Inspector 渲染时如果你不传它通常走的是“不带默认值”的逻辑所以你会看到报错。但真实客户端可能永远会带上这个参数所以遇到“Inspector 报错但线上没报错”也别觉得奇怪。更可靠的方式是如果真的设计了可选参数在模板里用条件渲染或给默认值做兜底保证缺省时消息依然通顺。4.5 上线前检查清单速查表这一套跑下来步骤有点多我整理成了一张速查表方便每次发版前对照打勾。检查项操作位置通过标准协议版本协商Inspector 日志initialize响应协议版本匹配无报错能力声明initialize响应capabilities已声明 tools/resources/prompts工具列表完整性Tools 页签工具数量、名称、描述符合预期工具 Schema 合法性Tools 每个定义的 inputSchemaJSON Schema 合法字段类型正确核心工具试调用Tools 页签填写参数并 Call返回内容为 content 数组无异常资源列表完整性Resources 页签URI 数量与格式正确资源读取成功率Resources 逐个 Read连续两次读取成功提示词参数匹配Prompts 页签渲染参数替换正确无占位符残留错误日志Inspector 日志过滤 Error无非致命错误堆积危险工具隔离代码审查写操作工具有测试开关或 mock这个表不是给老板看的是给自己看的。每次发版前花二十分钟跑一遍能挡住大部分线上问题。5. 从上线前检查到上线后监控我的几点体会5.1 把检查沉淀成脚本Inspector 适合做人工审查但纯靠人工会有两个问题一是慢二是容易漏。所以我现在把 Inspector 能发现的问题分成了两类一类是必须人工判断的比如描述是否准确、模板是否通顺另一类是可以自动化的比如工具列表数量是否变化、Schema 是否合法、Resources 能否读取。后一类我已经沉淀成了一个小的回归脚本用 MCP SDK 自带测试工具 自定义断言每天定时跑一次。脚本只做 list 和 read不执行任何 Tool 的 call所以非常安全。一旦发现某个资源读取失败或者某个工具的 Schema 有语法错误立刻告警不用等上线才发现。这么做之后Inspector 的角色变成了“发版前的人工终审”工具而不是唯一的检查手段。自动化保证基线不出错人工保证体验层面不出问题两边互补效果最好。5.2 日志设计要一开始就做对我在帮朋友排查一个线上 MCP Server 问题时最大的障碍不是它调不通而是它不打印任何有用的日志。所有 MCP 方法都包在最外层只输出一个统一异常根本不知道是哪一个方法挂了也不知道请求参数是什么。最后只能靠 Inspector 一点一点试排查效率极低。如果你的 Server 还在开发阶段趁早做好日志设计。至少在三个地方打日志请求入口哪个方法、请求 ID、参数摘要、外部 IO 调用调用了什么服务、耗时多少、异常分支异常堆栈、请求上下文。这样线上出了问题你能快速定位而不是让用户帮你抓包。Inspector 的日志区能把你本地复现的各种请求都记录下来但那是“本地诊断日志”不是线上运行日志。两者都要有但我个人觉得后者更关键因为线上的真实请求模式千奇百怪本地很难完全模拟。5.3 一些真实教训最后分享几个真实踩过坑的片段希望你能绕开。第一个教训工具的 description 真的是“AI 能不能正确使用工具”的关键。我有一个天气查询工具description 只写了“查询天气”结果 AI 在用户问“明天该穿什么”时死活不调它因为描述没告诉它“可以根据天气信息推断穿衣建议”。后来我把 description 扩写成“当用户询问天气、气温、穿衣建议、出行安排时使用输入城市名和日期”调用率立刻上来了。这个东西不是玄学是 AI 的文本理解机制决定的。第二个教训别以为 Resources 就不是代码。我曾经手写了一个返回 PDF 元数据的 Resource自测时内容正常但线上客户端一直报错最后发现是因为mimeType声明了application/pdf但返回内容实际是 base64 编码的 JSON客户端按 PDF 解析当然失败。资源链路也要讲“契约”不只是 Tools 才讲。第三个教训上线前检查不要只在发版当天做。有一次我连续改了很多代码上线前一天用 Inspector 跑了一遍发现一切正常第二天又改了一个小字段没重新跑直接发布。结果那个小字段刚好是某个 Tools 的inputSchema里必填参数的类型从 string 改成了 integerAI 客户端还按 string 传线上立刻报错。从那以后我强迫自己把“任何代码变更后都跑一遍 Inspector 全量检查”变成肌肉记忆。我个人现在的习惯是无论改动多小发版前至少跑一轮tools/listresources/list 核心工具试调用全程不超过二十分钟。这个习惯帮我挡住过很多次线上故障也让我对 MCP Server 的协议细节保持敏感。Inspector 本身不复杂复杂的是你愿不愿意在每个版本上线前花这二十分钟安静地看一遍协议日志。
