12.1 低层 Server:直接处理协议对象


文档摘要

12.1 低层 Server:直接处理协议对象 本节摘要:第 12 章开篇,我们回到第 2.3 节埋下的伏笔——两层服务端模型。前面十一章你用的都是高层 (装饰器层),它把协议细节藏得很好。但当你需要协议层做不到的事,就得下沉到低层 。本节讲透低层 Server:它直接处理协议对象(注册 / 处理器),没有类型推断、没有装饰器糖,是 的地基。读完本节,你理解了「装饰器糖之下是什么」,为第 12.2 节的对照与后续扩展打好基础。 一、低层 Server 是什么 回顾第 2.3 节:服务端分两层, (高层)构建于 (低层)之上。低层 是裸协议对象处理器: 低层 不读类型注解、不生成 Schema,你直接处理协议对象——收到的请求是协议参数对象,返回的响应是协议结果对象。

12.1 低层 Server:直接处理协议对象

本节摘要:第 12 章开篇,我们回到第 2.3 节埋下的伏笔——两层服务端模型。前面十一章你用的都是高层 MCPServer(装饰器层),它把协议细节藏得很好。但当你需要协议层做不到的事,就得下沉到低层 Server。本节讲透低层 Server:它直接处理协议对象(注册 on_list_tools / on_call_tool 处理器),没有类型推断、没有装饰器糖,是 MCPServer 的地基。读完本节,你理解了「装饰器糖之下是什么」,为第 12.2 节的对照与后续扩展打好基础。

一、低层 Server 是什么

回顾第 2.3 节:服务端分两层,MCPServer(高层)构建于 Server(低层)之上。低层 Server裸协议对象处理器:

高层 MCPServer(装饰器层): @mcp.tool() def add(a: int, b: int) -> int: return a + b ← 类型推断、Schema 生成、错误转换都自动 低层 Server(协议层): @server.on_call_tool() async def call_tool(ctx, params): if params.name == "add": return ToolResult(content=[...]) ← 手写分发、手写 Schema、手动包装

低层 Server 不读类型注解、不生成 Schema,你直接处理协议对象——收到的请求是协议参数对象,返回的响应是协议结果对象。

二、低层 Server 的处理器

低层 Server 通过注册处理器响应协议方法:

from mcp.server import Server from mcp.server.lowlevel import helper_types server = Server("demo-lowlevel") # 处理 tools/list:返回工具清单 @server.on_list_tools() async def list_tools(ctx, params): return [ types.Tool( name="add", description="Add two numbers.", input_schema={ # 手写 JSON Schema "type": "object", "properties": { "a": {"type": "integer"}, "b": {"type": "integer"} }, "required": ["a", "b"] } ) ] # 处理 tools/call:分发到对应工具 @server.on_call_tool() async def call_tool(ctx, params): if params.name == "add": a = params.arguments["a"] # 手动取参数 b = params.arguments["b"] result = a + b return helper_types.ToolResult( content=[types.TextContent(type="text", text=str(result))] ) raise ValueError(f"Unknown tool: {params.name}")

看到差别了吗?

维度 高层 MCPServer 低层 Server
注册工具 @mcp.tool() + 函数 注册 on_list_tools + on_call_tool
Schema 从类型推断 手写 JSON Schema
参数 函数参数(自动绑定) params.arguments 手动取
返回 函数返回值(自动包装) 手动构造 ToolResult
分发 工具管理器自动 你手动 if/elif

低层 Server 给你裸协议对象,一切自己来。

三、低层 Server 的处理器家族

低层 Server 有针对每个协议方法的处理器:

处理器 响应什么
on_list_tools tools/list
on_call_tool tools/call
on_read_resource resources/read
on_list_resources resources/list
on_list_resource_templates resources/templates/list
on_get_prompt prompts/get
on_list_prompts prompts/list
(其他) 各协议方法

每个处理器接收 (ctx, params),返回协议结果。ctx 是请求上下文(含 session、headers 等),params 是协议参数对象。

四、为什么会有低层 Server

低层 Server 的存在不是冗余,它解决几个高层搞不定的问题:

场景 为什么用低层
自定义协议方法 高层只覆盖标准方法,自定义要低层
完全控制分发逻辑 高层按工具名自动分发,低层可任意
不依赖类型推断 某些场景 Schema 无法用注解表达
协议级调试 直接看协议对象,便于排查
二次封装 SDK 在 SDK 之上再封装一层框架

最常见的场景是「自定义协议方法」——MCP 协议有标准方法(tools/call 等),但有时候你需要协议外的东西(如批量操作、自定义状态查询)。低层 Server 让你能定义这些,第 12.3 节会详讲。

五、低层 Server 与能力声明

低层 Server 的能力声明不自动——你要显式声明服务器支持哪些能力:

server = Server( "demo-lowlevel", capabilities=types.ServerCapabilities( tools=types.ToolsCapability(list_changed=True), resources=types.ResourcesCapability(...), # 显式声明每个能力 ) )

对比高层 MCPServer(从注册自动推断声明),低层要你自己声明。这给完全控制,但也意味着你要确保「声明与处理器一致」——声明了 tools 能力,就要注册 on_list_toolson_call_tool,否则客户端请求会失败。

⚠️ 注意:低层 Server 要求你自己保证「声明与实现一致」。高层 MCPServer 替你保证(注册即声明),低层要你手动维护。这是低层灵活性的代价——自由多了,责任也多了。

六、低层 Server 的启动

低层 Server 也能跑成服务,只是配置更手动:

# 低层 Server 跑 stdio(概念性) from mcp.server.stdio import stdio_server async def main(): async with stdio_server() as (read, write): await server.run(read, write, initialization_options) # 或嵌入 ASGI 应用 app = server.streamable_http_app()

启动方式与高层类似,但「初始化选项」(initialization_options)要手动构造,因为低层不自动推断能力。

七、何时该用低层

绝大多数场景用高层 MCPServer 就够。低层适合这些情况:

该用低层的判断: ├─ 需要自定义协议方法? → 低层 ├─ 需要完全控制分发逻辑? → 低层 ├─ Schema 无法用类型注解表达? → 低层 ├─ 在 SDK 之上做二次封装? → 低层 └─ 都不需要 → 高层 MCPServer(推荐)

💡 技巧:别为了「显得高级」用低层。高层 MCPServer 在 95% 的场景都够用,代码也更简洁。低层是「需要时的逃生口」,不是「默认选择」。第 12.2 节会用对照表让你看清两者,巩固「日常用高层,特殊才下沉」的判断。

本节要点回顾

  1. 低层 Server 是裸协议对象处理器,无类型推断、无装饰器糖,是 MCPServer 的地基。
  2. 处理器注册:on_list_tools / on_call_tool 等,接收 (ctx, params),返回协议结果。
  3. 手动的一切:手写 Schema、手动取参数、手动包装结果、手动分发。
  4. 存在价值:自定义方法、完全控制分发、不依赖类型推断、协议调试、二次封装。
  5. 能力声明不自动:低层要显式声明 capabilities,且自己保证声明与实现一致。
  6. 启动更手动:初始化选项要手动构造(不像高层自动推断)。
  7. 绝大多数场景用高层,低层是逃生口不是默认选择。

低层清楚了,下一节用对照表讲透「同一功能两种写法」。


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