MCP 调试指南
开发 MCP 服务器时,有效的调试技巧和工具能够帮助你快速定位问题。本章介绍 MCP 开发中常用的调试方法和工具。
MCP Inspector
MCP Inspector 是官方提供的调试工具,可以独立运行,用于测试和调试 MCP 服务器。
安装和使用
使用 npx 直接运行 Inspector:
npx @modelcontextprotocol/inspector python server.py
或者指定完整的命令:
npx @modelcontextprotocol/inspector uv --directory /path/to/project run server.py
Inspector 功能
Inspector 提供以下功能:
- 连接测试:验证服务器能否正常启动和连接
- 工具测试:查看可用工具列表并测试调用
- 资源浏览:查看和读取服务器提供的资源
- 提示模板:测试提示模板的参数和输出
- 消息日志:查看所有 JSON-RPC 消息的详细内容
使用场景
当服务器无法正常工作时,首先使用 Inspector 进行基本测试:
- 确认服务器可以正常启动
- 验证初始化流程是否正确
- 测试各个工具是否按预期工作
- 检查资源读取是否正常
日志处理
STDIO 服务器的日志
STDIO 传输的服务器必须将日志输出到 stderr,因为 stdout 用于传输 MCP 消息。
Python 示例:
import sys
import logging
# 配置日志输出到 stderr
logging.basicConfig(
stream=sys.stderr,
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("mcp-server")
@app.call_tool()
async def call_tool(name: str, arguments: dict | None):
logger.debug(f"Received tool call: {name} with args: {arguments}")
try:
result = await process_tool(name, arguments)
logger.info(f"Tool {name} executed successfully")
return result
except Exception as e:
logger.error(f"Tool {name} failed: {e}", exc_info=True)
raise
TypeScript 示例:
// 使用 console.error 输出日志
console.error("Server starting...");
// 或使用专门的日志库
import pino from 'pino';
const logger = pino({ level: 'debug' });
server.tool("example", async (args) => {
logger.debug({ args }, "Tool called");
// ...
});
常见日志问题
问题:服务器无响应或崩溃
检查日志输出:
# 直接运行服务器查看错误
python server.py
# 或使用 Claude Desktop 的日志
# macOS: ~/Library/Logs/Claude/mcp*.log
# Windows: %APPDATA%\Claude\Logs\mcp*.log
问题:stdout 污染导致协议错误
确保没有代码写入 stdout:
# ❌ 错误 - 会破坏 MCP 协议
print("Debug message")
# ✅ 正确
print("Debug message", file=sys.stderr)
logging.info("Debug message")
错误处理
协议错误
MCP 定义了一组标准错误码:
| 错误码 | 名称 | 说明 |
|---|---|---|
| -32700 | Parse error | JSON 解析错误 |
| -32600 | Invalid Request | 无效的请求对象 |
| -32601 | Method not found | 方法不存在 |
| -32602 | Invalid params | 无效的参数 |
| -32603 | Internal error | 服务器内部错误 |
在 Python SDK 中抛出协议错误:
from mcp.types import McpError
# 工具不存在
raise McpError(
code=-32601,
message=f"Tool '{name}' not found"
)
# 参数无效
raise McpError(
code=-32602,
message=f"Missing required parameter: {param}"
)
工具执行错误
工具执行中的错误应该返回带有 isError: true 的结果:
@app.call_tool()
async def call_tool(name: str, arguments: dict | None):
try:
result = await execute_tool(name, arguments)
return [TextContent(type="text", text=result)]
except ValueError as e:
# 业务逻辑错误
return [TextContent(
type="text",
text=f"错误: {str(e)}"
)]
except Exception as e:
# 意外错误
logger.exception("Unexpected error in tool execution")
return [TextContent(
type="text",
text=f"执行失败: {str(e)}"
)]
测试策略
单元测试
为 MCP 服务器的核心逻辑编写单元测试:
import pytest
from unittest.mock import AsyncMock, patch
@pytest.mark.asyncio
async def test_create_note_tool():
"""测试创建笔记工具"""
# 模拟工具调用
arguments = {
"title": "测试笔记",
"content": "这是测试内容"
}
result = await handle_create_note(arguments)
# 验证返回格式
assert len(result) == 1
assert result[0].type == "text"
assert "创建成功" in result[0].text
@pytest.mark.asyncio
async def test_search_notes_empty():
"""测试搜索空结果"""
with patch('server.list_all_notes', return_value=[]):
result = await handle_search_notes({"query": "不存在的内容"})
assert "未找到" in result[0].text
集成测试
使用 MCP 客户端进行集成测试:
import pytest
from mcp import ClientSession
from mcp.client.stdio import stdio_client
@pytest.mark.asyncio
async def test_server_integration():
"""集成测试:测试完整的服务器流程"""
server_params = StdioServerParameters(
command="python",
args=["server.py"]
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# 初始化
await session.initialize()
# 测试工具列表
tools = await session.list_tools()
assert len(tools.tools) > 0
# 测试工具调用
result = await session.call_tool(
"create_note",
{"title": "测试", "content": "内容"}
)
assert result.isError is False
测试运行
# 运行所有测试
pytest tests/
# 运行特定测试文件
pytest tests/test_tools.py
# 显示详细输出
pytest tests/ -v -s
常见问题排查
服务器无法启动
症状:Claude Desktop 显示服务器连接失败
排查步骤:
- 检查 Python 环境:
which python # 确认使用正确的 Python
python --version
- 检查依赖安装:
pip list | grep mcp
- 手动运行服务器:
python server.py
# 观察是否有错误输出
- 检查配置文件路径:
// 确保使用绝对路径
{
"mcpServers": {
"my-server": {
"command": "/full/path/to/python",
"args": ["/full/path/to/server.py"]
}
}
}
工具调用失败
症状:工具调用返回错误或无响应
排查步骤:
- 使用 Inspector 测试工具
- 检查参数格式是否符合 inputSchema
- 检查服务器日志中的错误信息
- 验证工具逻辑是否正确
# 添加详细日志
@app.call_tool()
async def call_tool(name: str, arguments: dict | None):
logger.debug(f"Tool call: name={name}, args={arguments}")
try:
result = await process(name, arguments)
logger.debug(f"Tool result: {result}")
return result
except Exception as e:
logger.error(f"Tool error: {e}", exc_info=True)
raise
资源读取问题
症状:资源列表为空或读取失败
排查步骤:
- 检查
list_resources是否正确返回 - 验证 URI 格式是否正确
- 检查
read_resource中的 URI 解析逻辑
@app.read_resource()
async def read_resource(uri: str) -> list[TextContent]:
logger.debug(f"Reading resource: {uri}")
# 添加详细的 URI 解析日志
if not uri.startswith("notes://"):
logger.warning(f"Invalid URI scheme: {uri}")
raise ValueError(f"Invalid URI: {uri}")
note_id = uri[8:] # 移除前缀
logger.debug(f"Extracted note_id: {note_id}")
# ...
性能问题
症状:工具调用响应缓慢
排查步骤:
- 识别耗时操作:
import time
@app.call_tool()
async def call_tool(name: str, arguments: dict | None):
start = time.time()
result = await process(name, arguments)
elapsed = time.time() - start
if elapsed > 1.0:
logger.warning(f"Slow tool call: {name} took {elapsed:.2f}s")
return result
- 优化 I/O 操作:
- 使用异步 I/O
- 添加缓存
- 批量处理请求
- 检查网络请求:
# 设置合理的超时
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(url)
调试技巧
使用断点调试
在 VS Code 中配置调试:
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug MCP Server",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/server.py",
"console": "internalConsole",
"logToFile": true
}
]
}
消息追踪
记录所有 MCP 消息:
import sys
import json
class MessageLogger:
def __init__(self, original_stream, log_file):
self.original = original_stream
self.log_file = log_file
def write(self, data):
# 记录消息
with open(self.log_file, 'a') as f:
f.write(f"[{time.time()}] {data}\n")
# 原始写入
return self.original.write(data)
def flush(self):
return self.original.flush()
# 在服务器启动前替换 stdout
sys.stdout = MessageLogger(sys.stdout, "mcp_messages.log")
环境变量调试
使用环境变量控制调试输出:
import os
DEBUG = os.getenv("MCP_DEBUG", "false").lower() == "true"
if DEBUG:
logging.basicConfig(
stream=sys.stderr,
level=logging.DEBUG
)
运行时启用调试:
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["server.py"],
"env": {
"MCP_DEBUG": "true"
}
}
}
}