第21篇-MCP-Server测试-MCP-Inspector与自动化测试
【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 输入验证防止注入攻击、工具执行前的用户授权机制、资源访问范围限制、日志审计与凭证管理。如果本篇内容对你有帮助欢迎点赞收藏有任何疑问欢迎在评论区交流。

相关新闻

OneNote 笔记如何备份才不丢数据:3 种方案完整保姆级攻略

OneNote 笔记如何备份才不丢数据:3 种方案完整保姆级攻略

OneNote 笔记如何备份才不丢数据:3 种方案完整保姆级攻略 【免费下载链接】cs-408 计算机考研专业课程408相关的复习经验,资源和OneNote笔记 项目地址: https://gitcode.com/GitHub_Trending/cs/cs-408 用 OneNote 攒了几个月笔记,某次…

2026/9/24 17:02:13 阅读更多 →
【Coze】【视频】三分钟读一本书

【Coze】【视频】三分钟读一本书

今天给大家演示一个 《三分钟读一本书》Coze 工作流。该工作流通过大模型驱动的分镜文案生成、图像合成、语音合成以及视频草稿自动创建等一整套流程,将一本书的内容浓缩为一个三分钟的视频,实现从文本到成片的全自动化制作。用户只需输入书名、作者和个人账号信息,即可得到…

2026/9/24 17:02:13 阅读更多 →
RunAnywhere Kotlin SDK 之 runanywhere-onnx:Android 端侧 STT/TTS/VAD 后端的安装、注册与语音 API 实战指南

RunAnywhere Kotlin SDK 之 runanywhere-onnx:Android 端侧 STT/TTS/VAD 后端的安装、注册与语音 API 实战指南

AI模型推理服务推理引擎本地部署多模态 【免费下载链接】runanywhere-sdks Production ready toolkit to run AI locally 项目地址: https://gitcode.com/gh_mirrors/ru/runanywhere-sdks 点击查看 免费下载 runanywhere-onnx 是 RunAnywhere Kotlin SDK 的可选语音…

2026/9/24 17:01:12 阅读更多 →

最新新闻

深度学习新闻分类推荐系统:从TextCNN到个性化推荐

深度学习新闻分类推荐系统:从TextCNN到个性化推荐

简介:这份基于深度学习的新闻分类推荐系统Python实现源码,是专为课程设计与期末大作业准备的高分项目,下载后无需修改即可运行,适用于需要快速交付完整课题的高校学生。系统涵盖新闻数据预处理、文本分类模型训练、推荐逻辑展示等…

2026/9/25 0:00:41 阅读更多 →
汽车电子底层软件开发:AUTOSAR与CAN总线实战解析

汽车电子底层软件开发:AUTOSAR与CAN总线实战解析

1. 这门“汽车电子底层软件开发就业课”到底在教什么?——不是写个LED闪烁就能上岗的很多人看到“汽车电子底层软件开发就业课”这个标题,第一反应是:不就是嵌入式C语言单片机CAN通信?刷几道LeetCode、调通一个STM32 CAN收发例程&…

2026/9/25 0:00:41 阅读更多 →
Vim基础操作全攻略:保存退出、模式切换与高频命令实战

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/25 0:00:41 阅读更多 →
Python+CNN车牌识别实战:从数据预处理到模型训练与部署

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:00:41 阅读更多 →
AI元人文:从工具使用到思维重构的深度探索

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:00:41 阅读更多 →
CVE-2025-27591深度解析:日志组件本地权限提升漏洞与防御

CVE-2025-27591深度解析:日志组件本地权限提升漏洞与防御

CVE-2025-27591 最近在安全圈里讨论度不低,核心是 Below 这个日志处理组件在权限控制上出了问题,低权限用户有机会利用日志文件、临时目录的处理流程,把自身权限抬升到管理员甚至系统级别。很多人一听到“利用脚本”就先想到怎么打&#xff0…

2026/9/24 23:59:40 阅读更多 →

日新闻

AI元人文:从工具使用到思维重构的深度探索

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:00:41 阅读更多 →
Python+CNN车牌识别实战:从数据预处理到模型训练与部署

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:00:41 阅读更多 →
Vim基础操作全攻略:保存退出、模式切换与高频命令实战

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/25 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →