ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

FastMCP Provider 测试模式:直接调用 Server 方法而非包装 Client 的实践与原理

FastMCP Provider 测试模式:直接调用 Server 方法而非包装 Client 的实践与原理 FastMCP Provider 测试模式直接调用 Server 方法而非包装 Client 的实践与原理【免费下载链接】fastmcp The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp导读本篇文章围绕 FastMCP 仓库中的设计笔记 provider-test-pattern.md 展开讲解 Provider提供者测试为何从把 Server 包进 Client 再调用迁移为直接调用 Server 方法以及两种方式在返回类型、异常语义和测试职责划分上的本质差异。读完本文你将掌握 FastMCP 中 Provider 测试的标准写法await mcp.call_tool(...)直接调用模式、Tool/Resource/Prompt 三类组件的规范结果访问路径以及如何通过源码与测试用例验证这套模式的底层实现。一、背景旧测试模式的两个痛点在引入直接调用方案之前Provider 测试统一采用 Client 包装模式即先创建Client(mcp)上下文管理器再经由客户端通道发起调用async with Client(mcp) as client: result await client.call_tool(add, {x: 1, y: 2}) assert result.data 3这种写法虽然端到端却同时引入了两个独立的测试关注点Provider/Server 自身的功能是否正确组件注册、参数校验、结果序列化、可见性过滤等Client 与 Server 之间的交互是否正确协议编解码、传输层、请求/响应消息往返。一旦测试失败无法快速定位问题究竟出在 Provider 实现还是 Client-Server 协议层。更糟糕的是test_server_interactions.py中约 1,200 行测试与 Provider 测试高度重复既增加了维护成本也让失败定位变得更加困难。从当前仓库源码看test_server_interactions.py已不存在于 tests 目录中其职责已完全被分散到各 Provider 测试文件与集成测试中这正对应了文档记载的合并重复测试演进方向。二、解决方案直接调用 Server 方法设计决策的核心是Provider 测试不再包装 Client而是直接调用 Server 上公开的异步方法。result await mcp.call_tool(add, {x: 1, y: 2}) assert result.structured_content {result: 3}这一改动建立了清晰的测试所有权划分测试层级验证目标Provider 测试验证 Server 功能组件查找、执行、结果构造集成测试验证 Client-Server 交互协议、传输、消息往返底层实现佐证call_tool是 FastMCP Server 的公开 API定义在 fastmcp_slim/fastmcp/server/server.py#L1364-L1490。其文档字符串明确指出返回ToolResult抛出NotFoundError工具不存在或禁用、ToolError执行失败、ValidationError参数校验失败支持version参数选择调用特定版本组件默认经run_middlewareTrue走完整的中间件链中间件内部递归调用时置run_middlewareFalse以避免重复套用。也就是说直接调用并不是绕过了 Server 逻辑而是完整触达了组件路由、中间件、鉴权与执行全链路只是跳过了 MCP 协议编码与传输层这正是 Provider 测试想要的边界。三、结果访问模式返回的是 FastMCP 规范类型直接调用 Server 方法返回的是FastMCP 自身的规范类型canonical types而非 MCP 协议类型。文档给出的三类组件访问路径如下组件访问模式Toolresult.structured_content或result.textResourceresult.contents[0].contentPromptresult.messages[0].content.textToolResult 的字段语义ToolResult定义在 fastmcp_slim/fastmcp/tools/base.py#L95-L150核心字段包括content内容块列表list[ContentBlock]structured_content匹配工具输出 schema 的结构化数据dict[str, Any] | None这是 Provider 测试最常用的断言对象meta工具执行的运行时元数据is_error是否代表执行错误为True时映射到CallToolResult.is_error以错误结果返回给客户端而非抛出异常。构造函数还约束content与structured_content至少提供一个否则抛出ValueErrorstructured_content会经过 JSON 可序列化转换若序列化失败且未将工具的output_schema设为None禁用自动序列化则报错——这解释了为什么直接调用的断言形式是result.structured_content {result: 3}。测试用例中的真实形态在 tests/server/providers/local_provider_tools/test_context.py#L67-L68 中可以见到与文档完全一致的写法result await mcp.call_tool(tool_with_context, {x: 42}) assert result.structured_content {result: Got context with x42}同文件 L97-L111 还展示了 Resource 模式的访问工具内部读取资源后返回result.contents[0].content并带上mime_type验证了文档中Resource →result.contents[0].content的路径。Prompt 模式则在 tests/server/providers/proxy/test_proxy_server.py#L877-L879 中得到印证result.messages[0].content.text用于断言提示词的输出文本。四、错误语义FastMCP 异常 vs MCP 协议错误直接调用与 Client 调用在错误行为上存在本质差异这是选择测试模式时必须理解的关键点调用方式错误表现直接调用 Server 方法抛出 FastMCP 异常NotFoundError组件不存在、DisabledError组件被可见性规则禁用Client 调用抛出 MCP 协议错误统一包装在McpError中在 fastmcp_slim/fastmcp/exceptions.py#L70-L76 中可以确认这两个异常的定位class NotFoundError(Exception): Object not found. class DisabledError(Exception): Object is disabled.从 Server 实现看NotFoundError不仅覆盖名称不存在的情况还包括鉴权未通过时的归一化处理——server.py#L1482-L1489 中AuthorizationError会被捕获并重新抛出为NotFoundError从而对客户端隐藏组件是否存在的细节。资源与提示词的查找路径server.py#L1664-L1667、server.py#L1777同样遵循该语义。因此Provider 测试可以直接断言组件不存在/被禁用时抛出NotFoundError/DisabledError而不必解析协议错误体Client-Server 层是否能把这类异常正确转换为McpError协议错误则交由集成测试负责两边各司其职。五、落地实现测试合并与文件瘦身文档记载了 PR #2748 的具体实施结果将test_server_interactions.py中重复的 Provider 测试合并进各 Provider 测试文件test_server_interactions.py从1,455 行缩减至 179 行缩减超过 87%交互文件中仅保留TestMeta相关测试——它们必须依赖 Client因为元数据注入需要完整的上下文context装配。从当前仓库的测试布局可以进一步印证这一分工tests/server/providers 目录按 Provider 类型组织测试例如 test_fastmcp_provider.py、test_local_provider.py、test_skills_provider.py、test_transforming_provider.py 等同一个文件中通常同时包含两种测试直接调用如provider.get_prompt(my_prompt)见 test_fastmcp_provider.py#L253-L265与 Client 交互测试如 test_fastmcp_provider.py#L167-L181 中经client.read_resource(...)验证资源读取并断言返回TextResourceContents直观展示了功能归 Provider 测试、交互归集成测试的边界。六、实践指引如何编写规范化的 Provider 测试综合文档与源码编写 Provider 测试时建议遵循以下模式创建 Server 实例在测试内直接mcp FastMCP()并按需注册工具/资源/提示词或从 fixture 获取待测 Provider 挂载的实例直接调用公开方法await mcp.call_tool(name, args)、读取资源、get_prompt(name)等不要包进Client用规范类型断言工具断言result.structured_content资源断言result.contents[0].content提示词断言result.messages[0].content.text用 FastMCP 异常断言错误路径pytest.raises(NotFoundError)/pytest.raises(DisabledError)验证组件缺失与可见性禁用把 Client 留给集成测试只有真正需要验证协议交互如 context 注入、消息往返、McpError包装的场景才使用async with Client(mcp)。一个完整的对照示例组合自文档与 test_context.py 的既有写法import pytest from fastmcp import FastMCP from fastmcp.exceptions import NotFoundError pytest.mark.asyncio async def test_direct_call_pattern(): mcp FastMCP(demo) mcp.tool def add(x: int, y: int) - int: return x y # Provider 测试直接调用断言规范类型 result await mcp.call_tool(add, {x: 1, y: 2}) assert result.structured_content {result: 3} # 错误路径直接断言 FastMCP 异常 with pytest.raises(NotFoundError): await mcp.call_tool(missing_tool, {})七、总结Provider 测试从Client 包装迁移到直接调用 Server 方法本质上是把组件功能正确性与协议交互正确性两个关注点彻底解耦前者由 Provider 测试通过call_tool等公开方法直接验证后者由集成测试经由 Client 验证。这套模式同时带来了三方面收益——测试失败定位更精准、重复代码大幅削减test_server_interactions.py从 1,455 行降到 179 行、断言与错误语义更贴合 FastMCP 自身的规范类型ToolResult、NotFoundError、DisabledError。如需进一步理解 Provider 在 FastMCP 架构中的位置与挂载机制可继续参阅仓库笔记 provider-architecture.md并结合 tests/server/providers 下的各测试文件对照学习。【免费下载链接】fastmcp The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表