12.3 扩展:定义 MCP 未覆盖的方法


文档摘要

12.3 扩展:定义 MCP 未覆盖的方法 本节摘要:MCP 协议定义了标准方法(tools/call、resources/read 等),但有时候你需要协议外的东西——一个自定义的状态查询、一个非标准的批量操作、一个特定领域的专用方法。扩展(Extension) 让你定义这些方法,通过 绑定到服务端,让客户端能像调标准方法一样调用它们。本节讲透扩展的能力声明、版本兼容考量,以及它如何让你「不改协议地扩展协议」。 一、为什么需要扩展 MCP 协议的标准方法覆盖了通用场景(工具/资源/提示词),但特定领域常需要「协议外」的能力: 如果这些需求硬塞进标准方法,会很别扭——比如「批量调用」要用「循环调 tools/call」,丢失原子性;「状态查询」要做成一个工具,语义错乱。

12.3 扩展:定义 MCP 未覆盖的方法

本节摘要:MCP 协议定义了标准方法(tools/call、resources/read 等),但有时候你需要协议外的东西——一个自定义的状态查询、一个非标准的批量操作、一个特定领域的专用方法。扩展(Extension) 让你定义这些方法,通过 MethodBinding 绑定到服务端,让客户端能像调标准方法一样调用它们。本节讲透扩展的能力声明、版本兼容考量,以及它如何让你「不改协议地扩展协议」。

一、为什么需要扩展

MCP 协议的标准方法覆盖了通用场景(工具/资源/提示词),但特定领域常需要「协议外」的能力:

标准方法搞不定的场景: - 批量工具调用(一次调多个工具,原子性) - 自定义状态查询(如「当前服务负载」) - 领域专用方法(如「执行事务」「提交变更」) - 健康检查、指标暴露

如果这些需求硬塞进标准方法,会很别扭——比如「批量调用」要用「循环调 tools/call」,丢失原子性;「状态查询」要做成一个工具,语义错乱。扩展让你定义协议外的方法,优雅地满足这些需求。

二、扩展的定义

扩展用 Extension 类定义自定义方法,通过 MethodBinding 绑定:

from mcp.server.extension import Extension, MethodBinding from mcp.server import MCPServer class MyExtension(Extension): """自定义扩展:提供协议外方法。""" @MethodBinding("batch/call_tools") async def batch_call(self, ctx, params): """一次调用多个工具(原子性)。""" results = [] for call in params.calls: result = await self.dispatch_tool(call.name, call.arguments) results.append(result) return {"results": results} @MethodBinding("status/load") async def get_load(self, ctx, params): """查询当前服务负载。""" return {"load": get_current_load(), "connections": count_connections()} # 把扩展挂到服务端 mcp = MCPServer("Demo", extensions=[MyExtension()])

每个 @MethodBinding("方法名") 定义一个自定义方法:

  • 方法名:自定义(如 batch/call_tools),通常用 域/动作 格式
  • 处理器:async def (ctx, params),与低层处理器类似

三、扩展的能力声明

扩展会自动产生能力声明,让客户端知道「这些自定义方法可用」:

服务端连接时声明: 标准能力:tools、resources、prompts 扩展能力:batch/call_tools、status/load ← 自定义方法 客户端据此知道: 除了标准方法,还能调 batch/call_tools、status/load

这与第 3.3 节的「注册即声明」一脉相承——扩展方法注册后,自动出现在能力声明里,客户端能看到。

四、客户端调用扩展方法

客户端用通用方法调用扩展方法:

async with Client(url) as client: # 调用标准方法 result = await client.call_tool("add", {...}) # 调用扩展方法(用通用 send_request) result = await client.send_request( "batch/call_tools", {"calls": [{"name": "add", "arguments": {"a": 1, "b": 2}}]} ) # result = {"results": [{content: ..., structuredContent: {...}}]}

扩展方法没有专门的客户端便捷方法(像 call_tool 那样),用通用的 send_request 调用。这是因为扩展方法是协议外的,SDK 无法预先知道它们的存在。

💡 技巧:扩展方法的设计要慎重。每加一个扩展方法,就增加客户端的集成成本(客户端要知道这个方法、专门调用它)。优先考虑「能否用标准方法表达」,实在不行才用扩展。扩展是「必要时的逃生口」,不是「默认选择」。

五、扩展的版本兼容考量

扩展是「协议外」的,这带来一个风险——版本兼容:

标准方法(tools/call 等): - 由协议定义,所有版本都支持 - 客户端与服务端版本一致就有保证 扩展方法(batch/call_tools): - 你自定义,协议未定义 - 客户端必须知道这个扩展 - 服务端升级可能改/删扩展

设计扩展时要考虑:

  • 命名空间:用清晰的域前缀(mycompany/batch 而非 batch),避免冲突
  • 稳定性:扩展方法一旦发布给客户端,改动要谨慎(可能破坏兼容)
  • 文档:扩展方法必须文档化,否则客户端不知道怎么用
  • 降级:如果可能,提供标准方法替代(让不支持扩展的客户端也能用)

⚠️ 注意:扩展方法是「协议外的契约」,稳定性由你保证。协议标准方法有 MCP 规范背书,你的扩展只有你背书。如果服务端升级改了扩展行为,所有依赖的客户端可能挂掉。所以扩展要谨慎设计、充分文档、谨慎变更。

六、扩展与中间件的协作

扩展常与中间件(第 12.4 节)协作,实现复杂逻辑:

class TransactionalExtension(Extension): @MethodBinding("tx/execute") async def execute_tx(self, ctx, params): """事务性执行多个操作。""" async with self.transaction(): # 中间件提供的事务上下文 for op in params.operations: await self.dispatch(op) # 全部成功才提交

扩展定义「做什么」(新方法),中间件提供「怎么保障」(事务、日志、限流)。两者结合,让你在不改协议的前提下,构建复杂的领域逻辑。

七、扩展的典型应用

几个适合用扩展的场景:

场景 扩展方法 为什么用扩展
批量操作 batch/call_tools 原子性,标准方法做不到
事务 tx/begintx/commit 跨调用状态,标准无
状态查询 status/health 非工具语义
领域专用 domain/xxx 特定业务逻辑
健康检查 health/check 运维需求

共同点:标准方法的语义/能力表达不了,且客户端愿意专门集成。两个条件都满足,才用扩展。

本节要点回顾

  1. 扩展定义 MCP 未覆盖的方法,通过 MethodBinding 绑定,客户端像调标准方法一样调用。
  2. 定义:Extension 子类 + @MethodBinding("方法名"),挂到 MCPServer 的 extensions。
  3. 能力声明自动产生,客户端能看到扩展方法。
  4. 客户端用通用 send_request 调用,无专门便捷方法。
  5. 版本兼容风险:扩展是协议外契约,稳定性由你保证。
  6. 设计要慎重:命名空间、稳定性、文档、降级替代。
  7. 与中间件协作:扩展定义「做什么」,中间件保障「怎么做」。
  8. 适用:标准方法表达不了 + 客户端愿集成,两者都满足才用。

扩展清楚了,下一节讲中间件——拦截整条请求流水线。


发布者: 作者: 灏天文库 转发
评论区 (0)
U