【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 输入验证防止注入攻击、工具执行前的用户授权机制、资源访问范围限制、日志审计与凭证管理。如果本篇内容对你有帮助欢迎点赞收藏有任何疑问欢迎在评论区交流。
企业数字化 ERP 产品动态
相关推荐
OneNote 笔记如何备份才不丢数据:3 种方案完整保姆级攻略 OneNote 笔记如何备份才不丢数据:3 种方案完整保姆级攻略 【免费下载链接】cs-408 计算机考研专业课程408相关的复习经验,资源和OneNote笔记 项目地址: https://gitcode.com/GitHub_Trending/cs/cs-408
用 OneNote 攒了几个月笔记,某次… · 2026/9/24 17:01:59
使用 AWS SDK for C++ 编写 Hello SNS:通过 ListTopics 入门 Amazon SNS 示例工程教程后端 【免费下载链接】aws-doc-sdk-examples Welcome to the AWS Code Examples Repository. This repo contains code examples used in the AWS documentation, AWS SDK Developer Guides, and more. For more information, see the Readme.md file below. 项目地… · 2026/9/24 17:01:53
Ansible Playbook 实战:批量配置 Linux 主机 前言
在云原生运维、服务器集群管理场景中,我们经常需要对多台 Linux 主机做统一初始化、基线配置、环境标准化。
如果采用手动逐台操作,会出现大量重复劳动,同时极易产生配置不一致、漏配置、配置错误等问题,后续集群运维、K8s 部… · 2026/9/24 17:34:11
快速上手 mongoose web 服务器:用 TaoToken 统一 Key 打通 RESTful 接口调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 17:34:11
Yarn 常驻TM 容器与常驻Flink 任务 常驻 TM 容器:是 YARN 层面的资源容器(进程)Yarn Session / Per-Job:是 Flink 跑在 YARN 上的两种部署模式,决定了 AM、TM 容器的生命周期两种模式下,只要流任务在持续运行,TM 容器就会常驻不退… · 2026/9/24 17:34:11
为什么 AI 写数据要先分级?R0-R5 工具风险模型 上一篇讲了「Runtime over Prompt:为什么 System Prompt 不是安全边界」——安全边界要落在 Tool 真正执行之前。这一篇往深一层:边界既然落在执行路径上,Runtime 凭什么判断一个 Tool 该不该放行?
AI 真正让企业犹豫的ÿ… · 2026/9/24 17:33:53
Unicode与UTF-8编码深度解析:rune、字节与国际化 Unicode与UTF-8编码深度解析:rune、字节与国际化为什么"hello"是5个字符,"你好"也是2个字符,但len("你好")却是6?为什么用range遍历字符串不会乱码,用下标访问却会?本文从Un… · 2026/9/24 17:33:47
Go方法集规则深度解析:值接收者与指针接收者 Go方法集规则深度解析:值接收者与指针接收者在Go开发中,方法接收者使用值类型还是指针类型是一个高频面试题,也是实际开发中最容易踩坑的地方。本文从Go规范的方法集规则出发,结合interface赋值、嵌入字段、方法调用等实际场景&am… · 2026/9/24 17:33:47
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44