12.3 扩展:定义 MCP 未覆盖的方法 本节摘要:MCP 协议定义了标准方法(tools/call、resources/read 等),但有时候你需要协议外的东西——一个自定义的状态查询、一个非标准的批量操作、一个特定领域的专用方法。扩展(Extension) 让你定义这些方法,通过 绑定到服务端,让客户端能像调标准方法一样调用它们。本节讲透扩展的能力声明、版本兼容考量,以及它如何让你「不改协议地扩展协议」。 一、为什么需要扩展 MCP 协议的标准方法覆盖了通用场景(工具/资源/提示词),但特定领域常需要「协议外」的能力: 如果这些需求硬塞进标准方法,会很别扭——比如「批量调用」要用「循环调 tools/call」,丢失原子性;「状态查询」要做成一个工具,语义错乱。
本节摘要: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/begin、tx/commit |
跨调用状态,标准无 |
| 状态查询 | status/health |
非工具语义 |
| 领域专用 | domain/xxx |
特定业务逻辑 |
| 健康检查 | health/check |
运维需求 |
共同点:标准方法的语义/能力表达不了,且客户端愿意专门集成。两个条件都满足,才用扩展。
MethodBinding 绑定,客户端像调标准方法一样调用。Extension 子类 + @MethodBinding("方法名"),挂到 MCPServer 的 extensions。send_request 调用,无专门便捷方法。扩展清楚了,下一节讲中间件——拦截整条请求流水线。