5月28日 06:32

如何对 MCP Server 进行测试?测试策略与最佳实践详解

MCP(Model Context Protocol)作为 AI 应用与外部工具交互的标准协议,在 2026 年已获得广泛采用——SDK 月下载量超过 9700 万次,Anthropic、OpenAI、Google、Microsoft、AWS 等主流厂商均已支持。随着 MCP Server 数量激增,如何系统地测试 MCP Server 成为开发者必须掌握的技能。

本文将从测试层次、实战工具、代码示例和最佳实践四个维度,详细介绍 MCP Server 的测试策略。

一、MCP 测试的层次结构

有效的 MCP 测试应遵循测试金字塔原则,从底层到顶层依次覆盖:

层次目标速度覆盖范围
单元测试验证单个 Tool/Resource 函数的逻辑快(毫秒级)代码路径
协议测试验证 Server 是否正确实现 MCP 协议协议边界
集成测试验证 Client-Server 端到端通信较慢交互链路
性能测试验证并发和负载下的稳定性性能指标
安全测试验证认证、授权和注入防护安全边界

关键原则:大量单元测试打基础,适量协议测试守边界,少量端到端测试保信心。

二、单元测试:直接测试 Tool 函数

单元测试是最快的反馈环。对于 MCP Server,你无需启动真实服务器,直接测试 @mcp.tool() 装饰的函数即可。

2.1 安装依赖

bash
pip install mcp pytest pytest-asyncio

2.2 被测 Server 示例

python
# server.py from mcp.server.fastmcp import FastMCP, ToolError from typing import Optional mcp = FastMCP("calculator") @mcp.tool() def add(a: int, b: int) -> int: """Add two numbers together.""" return a + b @mcp.tool() def divide(a: float, b: float) -> float: """Divide a by b. Raises error if b is zero.""" if b == 0: raise ToolError("Division by zero is not allowed") return a / b @mcp.tool() def search_documents(query: str, max_results: Optional[int] = 10) -> list: """Search documents by query string.""" return [{"id": 1, "title": f"Result for {query}", "score": 0.95}]

2.3 单元测试编写

python
# test_unit.py import pytest from server import add, divide, search_documents def test_add(): assert add(2, 3) == 5 assert add(-1, 1) == 0 def test_add_type_coercion(): with pytest.raises(Exception): add("not_a_number", 1) def test_divide(): assert divide(10, 2) == 5.0 def test_divide_by_zero(): from mcp.server.fastmcp import ToolError with pytest.raises(ToolError, match="Division by zero"): divide(1, 0) @pytest.mark.parametrize("query,expected_len", [ ("test", 1), ("MCP protocol", 1), ]) def test_search_documents(query, expected_len): results = search_documents(query) assert len(results) == expected_len assert "title" in results[0]

要点:单元测试直接调用 Python 函数,不走 MCP 协议,速度极快。重点覆盖正常路径、边界条件和错误处理。

三、协议测试:使用 FastMCP Client 验证协议边界

单元测试无法发现协议层的问题——比如 Tool 注册是否正确、参数 Schema 是否完整、返回格式是否符合规范。这时需要用 FastMCP Client 进行协议级测试。

3.1 In-Process 协议测试

FastMCP 提供了 FastMCPTransport,允许在不启动真实服务器的情况下,通过内存中的协议层进行测试:

python
# test_protocol.py import pytest from mcp.server.fastmcp import FastMCP from fastmcp.client import Client from fastmcp.client.transports import FastMCPTransport from server import mcp @pytest.fixture async def mcp_client(): """创建连接到真实 Server 实例的 Client""" async with Client(mcp) as client: yield client @pytest.mark.asyncio async def test_tool_registration(mcp_client): """验证所有 Tool 都正确注册""" tools = await mcp_client.list_tools() tool_names = [t.name for t in tools] assert "add" in tool_names assert "divide" in tool_names assert "search_documents" in tool_names @pytest.mark.asyncio async def test_tool_schema(mcp_client): """验证 Tool 的参数 Schema 描述正确""" tools = await mcp_client.list_tools() add_tool = next(t for t in tools if t.name == "add") assert "properties" in add_tool.inputSchema assert "a" in add_tool.inputSchema["properties"] assert "b" in add_tool.inputSchema["properties"] @pytest.mark.asyncio async def test_call_tool_via_protocol(mcp_client): """通过协议层调用 Tool 并验证返回""" result = await mcp_client.call_tool("add", arguments={"a": 2, "b": 3}) assert result.data is not None @pytest.mark.asyncio async def test_tool_error_handling(mcp_client): """验证 ToolError 是否正确通过协议传播""" with pytest.raises(Exception): await mcp_client.call_tool("divide", arguments={"a": 1, "b": 0})

要点FastMCPTransport 让你在进程内完成协议级验证,无需启动子进程或网络端口,兼顾了速度和真实性。

3.2 Snapshot 测试:锁定 Tool 接口

使用 pytest-inline-snapshot 可以锁定 Tool 列表和 Schema,防止意外变更:

python
# test_snapshot.py from inline_snapshot import snapshot @pytest.mark.asyncio async def test_tool_list_unchanged(mcp_client): """Tool 列表不应发生意外变更""" tools = await mcp_client.list_tools() tool_info = [(t.name, t.description) for t in tools] assert tool_info == snapshot()

首次运行时使用 pytest --inline-snapshot=fix,create 生成快照,后续运行会自动比对。

四、集成测试:使用 MCP Inspector 交互式验证

MCP Inspector 是官方提供的交互式测试工具,适合开发阶段手动验证 Server 行为。

4.1 启动 Inspector

bash
# 方式一:通过 MCP CLI(推荐) mcp dev server.py # 方式二:直接使用 npx npx @modelcontextprotocol/inspector@latest python server.py

Inspector 会自动打开浏览器界面,提供以下功能:

  • Tools 面板:列出所有注册的 Tool,支持逐个测试调用
  • Resources 面板:查看和读取 Server 暴露的资源
  • Prompts 面板:测试预定义的 Prompt 模板
  • 消息日志:实时查看 JSON-RPC 请求和响应

4.2 使用配置文件测试

对于需要环境变量的 Server,可以创建配置文件:

json
{ "mcpServers": { "my-server": { "command": "python", "args": ["server.py"], "env": { "API_KEY": "test-key-123" } } } }

在 Inspector 中加载此配置,即可模拟真实部署环境。

五、性能测试:验证并发和负载能力

AI Agent 可能会快速连续调用 Tool,你的 Server 需要承受并发压力。

5.1 使用 locust 进行负载测试

python
# locustfile.py from locust import HttpUser, task, between class MCPLoadTest(HttpUser): wait_time = between(0.5, 2) host = "http://localhost:3001" @task(3) def call_add_tool(self): """测试 add Tool 的并发调用""" payload = { "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "add", "arguments": {"a": 1, "b": 2} }, "id": 1 } self.client.post("/mcp", json=payload) @task(1) def list_tools(self): """测试 Tool 列表查询""" payload = { "jsonrpc": "2.0", "method": "tools/list", "params": {}, "id": 2 } self.client.post("/mcp", json=payload)

运行方式:

bash
locust -f locustfile.py --users 50 --spawn-rate 10

5.2 关键性能指标

指标建议阈值说明
单次 Tool 调用延迟< 500msAI Agent 对延迟敏感
并发 50 用户 P99< 2s峰值场景下的兜底
错误率< 1%稳定性基线
内存泄漏长时间运行不增长

六、安全测试:认证、授权与注入防护

MCP 的动态交互特性引入了独特的安全挑战。根据 MCP 安全规范,需关注六大威胁向量。

6.1 认证测试

python
# test_security.py import pytest @pytest.mark.asyncio async def test_unauthorized_access(): """未认证请求应被拒绝""" # 使用无效 token 的 Client 调用受保护 Tool # 预期返回 401/403 pass # 根据实际认证中间件实现 @pytest.mark.asyncio async def test_token_validation(): """过期和无效 token 应被正确识别""" pass # 根据实际认证方案实现

6.2 输入注入测试

python
@pytest.mark.asyncio async def test_path_traversal_prevention(mcp_client): """路径遍历攻击应被阻止""" malicious_paths = [ "../../../etc/passwd", "..\\..\\windows\\system32", "/proc/self/environ" ] for path in malicious_paths: result = await mcp_client.call_tool( "read_file", arguments={"filepath": path} ) assert "error" in str(result).lower() or result.isError

6.3 速率限制测试

python
@pytest.mark.asyncio async def test_rate_limiting(): """超出速率限制应返回 429""" # 快速发送大量请求 # 验证在限制阈值后收到 429 Too Many Requests pass # 根据实际限流策略实现

七、Mock 外部依赖

当 Tool 依赖外部 API 时,使用 Mock 隔离测试:

python
# test_with_mocks.py import pytest from unittest.mock import AsyncMock, patch @pytest.mark.asyncio async def test_search_with_mocked_api(mcp_client): """使用 Mock 测试依赖外部 API 的 Tool""" mock_response = [{"id": 1, "title": "Mocked Result"}] with patch("server.httpx.AsyncClient.get") as mock_get: mock_get.return_value.json.return_value = mock_response result = await mcp_client.call_tool( "search_documents", arguments={"query": "test"} ) assert result is not None mock_get.assert_called_once()

八、CI/CD 集成

将 MCP 测试集成到持续集成流水线中:

yaml
# .github/workflows/test.yml name: MCP Server Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.12' - name: Install dependencies run: pip install mcp[cli] pytest pytest-asyncio pytest-cov - name: Run unit tests run: pytest test_unit.py -v --cov=server --cov-report=xml - name: Run protocol tests run: pytest test_protocol.py -v - name: Coverage check run: pytest --cov-fail-under=80

九、最佳实践总结

  1. 遵循测试金字塔:70% 单元测试 + 20% 协议测试 + 10% 端到端测试
  2. 优先使用 FastMCPTransport:在不启动真实服务器的情况下完成协议验证,速度快且可靠
  3. 用 MCP Inspector 辅助开发:写自动化测试前先用 Inspector 手动验证行为
  4. Snapshot 锁定接口:防止 Tool Schema 意外变更导致 Client 端故障
  5. Mock 外部依赖:隔离测试,避免因第三方 API 不稳定导致测试闪烁
  6. 覆盖安全边界:认证、授权、输入校验、速率限制必须测试
  7. CI 集成:每次提交自动运行测试,覆盖率目标 80%
  8. 性能基线:设定延迟和并发阈值,定期跑负载测试

十、测试工具速查表

工具用途安装命令
pytest单元/协议测试pip install pytest pytest-asyncio
FastMCPTransport进程内协议测试pip install mcp
MCP Inspector交互式手动测试mcp dev server.py
pytest-cov覆盖率报告pip install pytest-cov
pytest-inline-snapshot接口快照测试pip install pytest-inline-snapshot
locust负载测试pip install locust
mcpmockMock MCP Serverpip install mcpmock

通过以上分层测试策略,你可以系统性地保障 MCP Server 的质量、安全性和性能,从开发阶段到生产环境全程覆盖。

标签:MCP