12.2 同一功能两种写法:高层与低层对照


文档摘要

12.2 同一功能两种写法:高层与低层对照 本节摘要:本节用并排对照表,把几个典型功能分别在 (高层)与低层 上实现,让你直观看到「装饰器糖替你做了什么」。理解了这个对照,你才算真正「看穿」两层模型——知道高层每个便捷背后,低层在干什么。这一节是理解第 2.3 节两层模型的关键深化,也是判断「何时该下沉」的依据。 一、功能一:定义一个工具 同一个「add 工具」,两层写法: 高层替你做的: 从类型注解推断 Schema(你写 ,它生成 ) 自动注册到工具管理器(你不用手动维护清单) 自动分发( 里你不用 ) 自动包装返回值成 ToolResult 二、功能二:定义一个资源 高层替你做的: 从 URI 占位符推断参数(你写 ,它对齐到函数参数) 自动解析 URI 提取参数(你不用手动 +

12.2 同一功能两种写法:高层与低层对照

本节摘要:本节用并排对照表,把几个典型功能分别在 MCPServer(高层)与低层 Server 上实现,让你直观看到「装饰器糖替你做了什么」。理解了这个对照,你才算真正「看穿」两层模型——知道高层每个便捷背后,低层在干什么。这一节是理解第 2.3 节两层模型的关键深化,也是判断「何时该下沉」的依据。

一、功能一:定义一个工具

同一个「add 工具」,两层写法:

# 高层 MCPServer(类型推断) from mcp.server import MCPServer mcp = MCPServer("Demo") @mcp.tool() def add(a: int, b: int) -> int: """Add two numbers.""" return a + b
# 低层 Server(手写一切) from mcp.server import Server from mcp.server.lowlevel import helper_types from mcp import types server = Server("demo-lowlevel") @server.on_list_tools() async def list_tools(ctx, params): return [types.Tool( name="add", description="Add two numbers.", input_schema={ "type": "object", "properties": { "a": {"type": "integer"}, "b": {"type": "integer"} }, "required": ["a", "b"] } )] @server.on_call_tool() async def call_tool(ctx, params): if params.name == "add": a = params.arguments["a"] b = params.arguments["b"] return helper_types.ToolResult( content=[types.TextContent(type="text", text=str(a + b))] ) raise ValueError(f"Unknown tool: {params.name}")

高层替你做的:

  • 从类型注解推断 Schema(你写 a: int,它生成 {"type": "integer"})
  • 自动注册到工具管理器(你不用手动维护清单)
  • 自动分发(on_call_tool 里你不用 if name == ...)
  • 自动包装返回值成 ToolResult

二、功能二:定义一个资源

# 高层 @mcp.resource("greeting://{name}") def greeting(name: str) -> str: """Greet someone.""" return f"Hello, {name}!"
# 低层 @server.on_list_resource_templates() async def list_templates(ctx, params): return [types.ResourceTemplate( uri_template="greeting://{name}", name="greeting", description="Greet someone." )] @server.on_read_resource() async def read_resource(ctx, params): if params.uri.startswith("greeting://"): name = params.uri[len("greeting://"):] # 手动解析 URI return types.ReadResourceResult( contents=[types.TextResourceContents( uri=params.uri, text=f"Hello, {name}!" )] ) raise ValueError(f"Unknown resource: {params.uri}")

高层替你做的:

  • 从 URI 占位符推断参数(你写 {name},它对齐到函数参数)
  • 自动解析 URI 提取参数(你不用手动 startswith + 切片)
  • 自动包装返回值成 ReadResourceResult

三、功能三:声明能力

# 高层(自动推断) mcp = MCPServer("Demo") @mcp.tool() def add(...): ... # 自动声明 tools 能力,无需配置
# 低层(显式声明) server = Server( "demo-lowlevel", capabilities=types.ServerCapabilities( tools=types.ToolsCapability(list_changed=True), # 必须显式声明,否则客户端不知道你支持 tools ) )

高层替你做的:

  • 从注册的处理器推断能力(注册工具→声明 tools)
  • 自动设 list_changed 等标志
  • 保证声明与实现一致

四、对照总表:高层替你做了什么

把三个功能的对照压成一张总表:

任务 高层你写的 低层你写的 高层替你做的
工具 Schema a: int, b: int 手写 JSON Schema 类型推断
工具分发 函数本身 if name == ... 手动 自动分发
工具返回 return a + b 构造 ToolResult 自动包装
资源参数 URI 占位符 手动解析 URI 自动对齐
能力声明 (无,自动) 显式 ServerCapabilities 自动推断
错误转换 抛异常 手动构造错误响应 自动转换

一句话:高层把「协议细节」整套省掉,低层把它们都还给你

五、高层与低层的关系再强调

通过对照,两者关系更清晰:

你写的高层代码 低层实际执行的 @mcp.tool() │ def add(a, b): │ return a + b │ ▼ on_list_tools 返回手写 Schema on_call_tool 手动分发到 add 构造 ToolResult 返回

高层代码「编译」成低层处理器。你用 @mcp.tool() 写的 5 行,低层要写 30 行。这就是「MCPServer 是写法,Server 是事实」(第 2.3 节)的字面含义。

六、何时下沉:基于对照的判断

基于对照表,下沉到低层的判断更清晰:

你的需求 高层能否 该不该下沉
注册标准工具/资源/提示词 不下沉
自动 Schema 推断 不下沉
自动能力声明 不下沉
自定义协议方法 不能 下沉
完全控制分发逻辑 不能 下沉
手写非标准 Schema 不能 下沉
协议级调试 不便 下沉(临时)

💡 技巧:对照表是「下沉决策」的依据。如果你的需求都在「高层能否」的「能」里,就别下沉;有「不能」的需求,才下沉。这个判断让你既享受高层便捷,又不丢失低层灵活性。

七、混合用法:高层为主,低层补充

实战中常见「混合用法」——大部分用高层,少数特殊需求用低层或扩展:

# 大部分工具用高层 mcp = MCPServer("Demo") @mcp.tool() def add(a: int, b: int) -> int: ... # 标准,高层 @mcp.tool() def search(q: str) -> list: ... # 标准,高层 # 少数特殊需求,用扩展(第 12.3 节)或低层补充 # 比如自定义协议方法,用 Extension

这种「高层为主,低层/扩展补充」的模式,让你既保持大部分代码的简洁,又能处理特殊需求。第 12.3-12.5 节会讲扩展、中间件、Apps 这些「补充手段」。

本节要点回顾

  1. 对照表看清「装饰器糖替你做了什么」:类型推断、自动分发、自动包装、自动声明、错误转换。
  2. 工具:高层写函数,低层写 on_list_tools + on_call_tool + 手写 Schema。
  3. 资源:高层写 URI 占位符,低层手动解析 URI。
  4. 能力:高层自动推断,低层显式声明。
  5. 高层代码「编译」成低层处理器——MCPServer 是写法,Server 是事实。
  6. 下沉判断:标准需求用高层,自定义方法/完全控制/非标准 Schema 才下沉。
  7. 混合用法:高层为主,低层/扩展补充,兼顾简洁与灵活。

对照清楚了,下一节讲扩展——如何在不改协议的前提下定义 MCP 未覆盖的方法。


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