【MCP 全栈教程】第 21 篇MCP Server 测试——MCP Inspector 与自动化测试本系列定位从协议原理到 Server 开发、Client 开发、再到各大平台实战集成系统化掌握 MCPModel Context Protocol全栈技术体系。本篇你将学到MCP Inspector 可视化调试工具的使用方法手动测试流程Claude Desktop 配置与验证Python pytest 集成测试框架搭建TypeScript 测试框架与测试用例编写覆盖率要求与常见 Bug 排查学完本篇你将为 MCP Server 建立完整的测试体系确保每次变更都有质量保障。一、测试体系总览MCP Server 的测试分为三个层次测试层次工具目的执行频率可视化调试MCP Inspector交互式验证工具和资源开发阶段手动测试Claude Desktop真实环境验证发布前自动化测试pytest / vitest回归保障每次提交测试金字塔┌───────────┐ │ 手动测试 │ ← 少量验证端到端体验 ├───────────┤ │ 集成测试 │ ← 中等验证 Client-Server 交互 ├───────────┤ │ 单元测试 │ ← 大量验证单个工具逻辑 └───────────┘二、MCP Inspector 使用MCP Inspector 是官方提供的交互式调试工具可以在可视化界面中连接 Server、列出工具、调用工具、查看完整的 JSON-RPC 消息。启动 Inspector# 方式一通过 npx 启动无需安装npx modelcontextprotocol/inspector# 方式二指定要调试的 Server 命令npx modelcontextprotocol/inspector python server.py# 方式三调试 TypeScript Servernpx modelcontextprotocol/inspectornodedist/server.js启动后浏览器自动打开 Inspector 界面。Inspector 核心功能功能区域说明连接配置配置 Server 启动命令、环境变量、传输方式Tools 面板列出所有工具点击调用填写参数Resources 面板浏览和读取资源Prompts 面板选择并获取 Prompt消息日志完整的 JSON-RPC 请求/响应记录通知面板查看收到的通知如 list_changed调试流程1. 在连接配置中输入 Server 启动命令 2. 点击 Connect 连接 Server 3. 查看 Tools 面板确认工具列表 4. 选择一个工具填写参数 5. 点击 Run Tool 执行 6. 查看返回结果和消息日志 7. 如有问题在消息日志中检查 JSON-RPC 消息Inspector 调试技巧技巧说明检查 discover 响应确认 capabilities 声明正确对比参数 Schema确认 inputSchema 与实际期望一致查看原始消息切换到原始 JSON 视图排查格式问题测试通知触发工具列表变更观察通知面板多版本测试切换协议版本测试兼容性三、手动测试Claude Desktop 配置配置 Server编辑 Claude Desktop 配置文件macOS:~/Library/Application Support/Claude/claude_desktop_config.json{mcpServers:{weather-server:{command:python,args:[/path/to/server.py],env:{API_KEY:your-api-key}}}}验证步骤步骤操作预期结果1重启 Claude Desktop加载新配置2打开对话窗口左侧显示已连接的 Server3输入查询北京天气Claude 调用get_weather工具4观察工具调用过程显示工具名和参数5检查返回结果结果正确显示6测试异常场景如输入空城市名查看错误处理手动测试检查清单检查项说明Server 正常启动Claude Desktop 日志无报错工具列表正确所有工具在对话中可用参数校验生效非法参数返回明确错误中文支持中文参数和返回正常环境变量env 中的变量正确传递多工具协作连续调用多个工具无异常四、Python pytest 集成测试项目结构weather-server/ ├── pyproject.toml ├── server.py └── tests/ ├── __init__.py ├── conftest.py ← 测试夹具 ├── test_tools.py ← 工具测试 └── test_resources.py ← 资源测试安装测试依赖uvadd--devpytest pytest-asyncio pytest-covconftest.py测试夹具importpytestimportasynciofrommcp.client.sessionimportClientSessionfrommcp.client.stdioimportstdio_client,StdioServerParametersfromserverimportmcpasmcp_serverpytest.fixtureasyncdefmcp_client():创建一个连接到 Server 的测试 Client。# 方式一通过 STDIO 连接真实 Server 进程server_paramsStdioServerParameters(commandpython,args[server.py],env{API_KEY:test-key})asyncwithstdio_client(server_params)as(read,write):asyncwithClientSession(read,write)assession:# 初始化会话awaitsession.initialize()yieldsessionpytest.fixtureasyncdefmcp_client_inprocess():方式二进程内连接更快不启动子进程。frommcp.shared.memoryimportcreate_connected_server_and_clientasyncwithcreate_connected_server_and_client(mcp_server)as(client,server):awaitclient.initialize()yieldclienttest_tools.py工具测试importpytestpytest.mark.asyncioasyncdeftest_list_tools(mcp_client_inprocess):测试工具列表。toolsawaitmcp_client_inprocess.list_tools()# 验证工具存在tool_names[t.namefortintools]assertget_weatherintool_namesassertget_forecastintool_names# 验证工具结构weather_toolnext(tfortintoolsift.nameget_weather)assertweather_tool.descriptionisnotNoneassertcityinweather_tool.inputSchema[properties]assertcityinweather_tool.inputSchema[required]pytest.mark.asyncioasyncdeftest_call_weather_valid(mcp_client_inprocess):测试正常调用天气工具。resultawaitmcp_client_inprocess.call_tool(get_weather,{city:北京})assertresult.isErrorisFalseassertlen(result.content)0assertresult.content[0].typetextassert北京inresult.content[0].textpytest.mark.asyncioasyncdeftest_call_weather_empty_city(mcp_client_inprocess):测试空城市名应返回错误。resultawaitmcp_client_inprocess.call_tool(get_weather,{city:})# 应返回 isErrorassertresult.isErrorisTrueassert城市inresult.content[0].textor无效inresult.content[0].textpytest.mark.asyncioasyncdeftest_call_weather_missing_param(mcp_client_inprocess):测试缺少必需参数。withpytest.raises(Exception)asexc_info:awaitmcp_client_inprocess.call_tool(get_weather,{})# 应返回参数错误assertcityinstr(exc_info.value)or参数instr(exc_info.value)pytest.mark.asyncioasyncdeftest_unknown_tool(mcp_client_inprocess):测试调用不存在的工具。withpytest.raises(Exception):awaitmcp_client_inprocess.call_tool(nonexistent_tool,{})pytest.mark.asyncioasyncdeftest_weather_chinese_city(mcp_client_inprocess):测试中文城市名。resultawaitmcp_client_inprocess.call_tool(get_weather,{city:乌鲁木齐})assertresult.isErrorisFalse测试 Elicitation 流程pytest.mark.asyncioasyncdeftest_elicitation_flow(mcp_client_inprocess):测试需要用户输入的工具流程。resultawaitmcp_client_inprocess.call_tool(book_flight,{origin:北京,destination:上海,date:2026-08-15})# 第一次调用应返回 input_requiredassertresult.resultTypeinput_requiredassertlen(result.inputRequests)0# 验证请求结构reqresult.inputRequests[0]assertreq[method]elicitation/createassertreq[params][mode]formassertrequestedSchemainreq[params]assertrequestStateinstr(result)# requestState 存在# 模拟用户选择后重试result2awaitmcp_client_inprocess.call_tool(book_flight,{origin:北京,destination:上海,date:2026-08-15},input_responses[{selectedFlight:CA1501}],request_stateresult.requestState)# 最终应完成assertresult2.resultTypecomplete运行测试# 运行所有测试uv run pytest tests/-v# 运行并查看覆盖率uv run pytest tests/--covserver --cov-reportterm-missing# 只运行工具相关测试uv run pytest tests/test_tools.py-v五、TypeScript 测试框架项目结构weather-server/ ├── package.json ├── tsconfig.json ├── src/ │ └── server.ts └── tests/ ├── tools.test.ts └── resources.test.ts安装依赖npminstall--save-dev vitest modelcontextprotocol/sdk测试代码import{describe,test,expect,beforeAll,afterAll}fromvitest;import{Client}frommodelcontextprotocol/sdk/client/index.js;import{StdioClientTransport,}frommodelcontextprotocol/sdk/client/stdio.js;import{spawn}fromchild_process;describe(Weather Server,(){letclient:Client;lettransport:StdioClientTransport;beforeAll(async(){// 启动 Server 子进程并连接transportnewStdioClientTransport({command:node,args:[dist/server.js],env:{...process.env,API_KEY:test-key},});clientnewClient({name:test-client,version:1.0.0},{capabilities:{}});awaitclient.connect(transport);});afterAll(async(){awaittransport.close();});test(tools/list 返回正确的工具列表,async(){constresultawaitclient.request({method:tools/list},{});expect(result.tools).toBeDefined();consttoolNamesresult.tools.map((t:any)t.name);expect(toolNames).toContain(get_weather);});test(get_weather 正常返回天气,async(){constresultawaitclient.request({method:tools/call,params:{name:get_weather,arguments:{city:北京},},},{});expect(result.content[0].type).toBe(text);expect(result.content[0].text).toContain(北京);});test(空城市名返回 isError,async(){constresultawaitclient.request({method:tools/call,params:{name:get_weather,arguments:{city:},},},{});expect(result.isError).toBe(true);});test(不存在的工具返回错误,async(){awaitexpect(client.request({method:tools/call,params:{name:nonexistent,arguments:{},},},{})).rejects.toThrow();});test(中文城市名正常工作,async(){constresultawaitclient.request({method:tools/call,params:{name:get_weather,arguments:{city:哈尔滨},},},{});expect(result.content[0].text).toContain(哈尔滨);});});package.json 测试脚本{scripts:{build:tsc,test:vitest run,test:watch:vitest,test:coverage:vitest run --coverage}}运行测试# 先构建npmrun build# 运行测试npmtest# 覆盖率报告npmrun test:coverage六、覆盖率要求推荐覆盖率标准指标最低要求推荐值说明行覆盖率70%85%执行的代码行比例分支覆盖率60%80%if/else 分支覆盖比例函数覆盖率75%90%调用的函数比例工具覆盖率100%100%每个工具至少有一个测试配置覆盖率门槛Pythonpyproject.toml[tool.pytest.ini_options] addopts --covserver --cov-fail-under80 [tool.coverage.report] exclude_lines [ if __name__, pass, ... ]TypeScriptvitest.config.tsimport{defineConfig}fromvitest/config;exportdefaultdefineConfig({test:{coverage:{provider:v8,reporter:[text,html],thresholds:{lines:80,branches:75,functions:85,},},},});七、常见 Bug 排查Bug 1stdout 污染症状Server 启动后 Client 连接失败JSON 解析错误原因Server 用print()输出到 stdout破坏了 JSON-RPC 消息流排查检查代码中所有print()和sys.stdout.write()修复将所有输出改为logging输出到 stderr# ❌ 错误污染 stdoutprint(Server 启动)# ✅ 正确输出到 stderrimportlogging logging.basicConfig(levellogging.INFO)loggerlogging.getLogger(__name__)logger.info(Server 启动)Bug 2工具参数类型不匹配症状工具调用结果异常或报错原因LLM 传来的参数类型与代码期望不符如数字传成字符串排查在工具处理器入口打印arguments的类型修复显式类型转换 JSON Schema 校验mcp.tool()asyncdefsearch(page:int,keyword:str)-str:# 防御性类型转换pageint(page)ifpageelse1keywordstr(keyword).strip()...Bug 3异步函数未 await症状工具返回 coroutine 对象而非结果原因异步调用忘记await排查检查返回值是否为coroutine object修复添加awaitBug 4inputSchema 定义错误症状Inspector 中工具参数表单渲染异常原因inputSchema 不符合 JSON Schema 规范排查在 Inspector 中查看参数区域是否正常渲染修复校验 Schema 格式# ❌ 错误缺少 typeinputSchema{properties:{city:{}}}# 缺少 type: object# ✅ 正确inputSchema{type:object,properties:{city:{type:string}},required:[city]}Bug 5环境变量未传递症状Server 在测试环境工作在 Claude Desktop 中失败原因配置文件的env字段缺少必需环境变量排查在 Server 启动时打印环境变量到 stderr修复在claude_desktop_config.json的env中补充Bug 排查决策树连接失败 ├── Server 是否启动 → 检查进程/日志 ├── stdout 是否被污染 → 检查 print 语句 ├── 命令路径是否正确 → 检查 command/args 配置 └── 协议版本是否匹配 → 检查 _meta.protocolVersion 工具调用失败 ├── 工具是否存在 → 检查 tools/list ├── 参数是否合法 → 检查 inputSchema ├── 是否异步问题 → 检查 await └── 外部依赖是否正常 → 检查网络/数据库 通知收不到 ├── listChanged 是否声明 → 检查 capabilities ├── 是否已订阅 → 检查 subscriptions/listen └── 连接是否存活 → 检查长连接状态本篇小结知识点核心内容MCP Inspector官方可视化调试工具支持工具调用、消息查看Claude Desktop手动测试的真实环境验证Python 测试pytest pytest-asyncio 进程内 Client 测试TypeScript 测试vitest StdioClientTransport 子进程测试测试夹具create_connected_server_and_client实现进程内快速测试覆盖率要求行覆盖 80%工具覆盖 100%stdout 污染最常见 Bug改用 logging 输出 stderr参数类型LLM 传参可能不符合类型需防御性转换排查决策树连接失败 → 工具失败 → 通知问题的分层排查下篇预告第 22 篇Server 安全最佳实践——输入验证与权限控制JSON Schema 输入验证防止注入攻击、工具执行前的用户授权机制、资源访问范围限制、日志审计与凭证管理。如果本篇内容对你有帮助欢迎点赞收藏有任何疑问欢迎在评论区交流。