【FastMCP】中间件
什么是 MCP Middleware?
-
MCP Middleware 是 FastMCP 在 2.9.0 版本之后 引入的一种机制,用于在服务器层面插入“跨切面”逻辑(cross-cutting logic),拦截、修改、控制所有的 MCP 请求和响应。(gofastmcp.com)
-
它并不是 MCP 协议本身的一部分,而是 FastMCP 的扩展机制。也就是说,其他 MCP 实现可能没有或不兼容这个中间件系统。(gofastmcp.com)
-
典型用例包括:
- 身份验证 / 权限校验
- 日志记录与监控
- 限流 / 频率控制
- 请求/响应转换
- 缓存
- 错误处理
中间件让你可以在 “工具执行 / 资源读取 / 提示获取 / 列表操作” 这些执行路径里插入统一逻辑,而不需要每一个工具、资源、或提示都手动加逻辑。
工作机制与生命周期
中间件的执行模型
-
当一个 MCP 请求流入服务器时,它会按你添加中间件的顺序,依次通过各个中间件(在进入处理器之前),然后在响应返回时再反向通过中间件(后加进来的最先被响应处理)。(gofastmcp.com)
-
每个中间件的责任通常包括:
- 预处理:在进入下一级之前检查或修改请求
- 调用下一个环节(
call_next(context)
) - 后处理:检查或修改响应
- 出错处理:捕获异常、生成错误响应或抛出错误
-
如果某个中间件选择不调用
call_next
,那么这个请求链就可能在这里被拦截、终止或直接响应。要慎用这种中断行为。(gofastmcp.com)
Hook 层次与调用顺序
FastMCP 将中间件钩子(hooks)按“通用 → 特定”分层,这样你可以选择在不同粒度插入逻辑。(gofastmcp.com)
层级 | 钩子名称 | 触发时机 / 语义 |
---|---|---|
最通用 | on_message | 所有 MCP 消息(请求 + 通知)都会触发 |
通用请求 / 通知 | on_request / on_notification | 针对是请求类型还是通知类型的消息 |
操作级别 | on_call_tool 、on_read_resource 、on_get_prompt 、on_list_tools 、on_list_resources 、on_list_prompts 等 | 针对具体操作(调用工具/读取资源/列出工具等) |
比如,当客户端调用某个工具(tools/call
),中间件会依次触发:
on_message
on_request
(因为请求型)on_call_tool
这套层次允许你在通用层做统一处理(如日志、权限检查),在特定层做工具级别的细粒度控制(如某工具禁用、修改入参、审计等)(gofastmcp.com)。
列表操作 vs 执行操作的差异
- 列表操作(如列出工具、资源、提示)
中间件会收到的是具体的 FastMCP 组件对象(带有 metadata、tags 等属性)→ 你可以读取、过滤、修饰这些对象。(gofastmcp.com) - 执行操作(如
on_call_tool
、on_read_resource
、on_get_prompt
)
中间件拿到的并不是完整组件对象,而是 context + message。组件的 metadata 需要你再通过fastmcp_context
去查。(gofastmcp.com)
这意味着:如果你希望中间件在执行时根据某个工具的标签做判断,需要额外通过 context.fastmcp_context.fastmcp.get_tool(...)
来查这个工具对象。(gofastmcp.com)
中间件开发细节
Hook 方法签名与参数
任何钩子方法(如 on_message
、on_call_tool
)通常接收两个参数:
context: MiddlewareContext
— 包含当前请求的元信息(方法名、来源、消息体、时间戳等)call_next
— 一个可调用(async 函数),用于继续执行下一个中间件或最终处理器
中间件的标准结构通常是:
async def on_message(self, context: MiddlewareContext, call_next):# 前置逻辑:检查 / 修改 context.message 等result = await call_next(context)# 后置逻辑:检查 / 修改 resultreturn result
你也可以在这个过程中捕获异常、拒绝处理、短路(不调用 call_next
)等。(gofastmcp.com)
控制流策略
中间件可以对请求做出如下操作:
- 继续流程:
await call_next(context)
- 修改请求:在调用
call_next
之前,修改context.message
或相关字段 - 修改响应:在拿到
result
后改写结果(如在返回结果里添加字段) - 中断流程 / 拒绝请求:直接抛出错误(如
ToolError
、ResourceError
、PromptError
等)或返回错误响应 - 捕获异常 / 统一错误处理:在 try / except 块里包裹
call_next
,做错误转换、日志记录、重试等
状态管理(state)
在 2.11.0 版本之后,FastMCP 支持从上下文中存取状态(state),中间件可以 set_state
/ get_state
,让工具、后续中间件或处理器能够共享上下文状态。(gofastmcp.com)
这个特性对于跨中间件共享数据、在一个请求周期内保留中间状态非常有用。
如何在服务器上使用中间件
单个中间件
使用非常简单:
from fastmcp import FastMCP
from fastmcp.server.middleware import Middleware, MiddlewareContextclass LoggingMiddleware(Middleware):async def on_message(self, context: MiddlewareContext, call_next):print(f"Processing {context.method} from {context.source}")result = await call_next(context)print(f"Completed {context.method}")return resultmcp = FastMCP("MyServer")
mcp.add_middleware(LoggingMiddleware())
这样,每条 MCP 消息经过服务器时,都会被这个中间件拦截、打印前后日志。(gofastmcp.com)
多个中间件与执行顺序
多个中间件会依次添加,执行顺序如下:
- 最先添加的中间件的“前置”逻辑先执行
- 最后添加的中间件的“前置逻辑”最后执行
- 然后进入实际处理
- 响应返回时,最后添加的中间件的后置逻辑最先执行
- 最先添加的中间件的后置逻辑最后执行
示例:
mcp = FastMCP("MyServer")
mcp.add_middleware(AuthenticationMiddleware("token"))
mcp.add_middleware(PerformanceMiddleware())
mcp.add_middleware(LoggingMiddleware())
执行序列:
- 入站阶段:Auth → Perf → Logging → 处理器
- 出站阶段:Logging → Perf → Auth (gofastmcp.com)
在组合服务器中的中间件行为(Server Composition)
FastMCP 支持把子服务器 mount 到父服务器。中间件在组合服务器环境下的行为规则如下:
- 父服务器中间件会对所有请求生效(包括那些被路由到子服务器的请求)
- 子服务器中间件只对被路由到子服务器的请求生效
- 每个服务器内部的中间件顺序保持不变 (gofastmcp.com)
举例:
parent = FastMCP("Parent")
parent.add_middleware(AuthenticationMiddleware("token"))child = FastMCP("Child")
child.add_middleware(LoggingMiddleware())@child.tool
def child_tool():return "from child"parent.mount(child, prefix="child")
当 client 调用 child_tool
时,请求先经过 parent 的 AuthenticationMiddleware
,再进入子服务器,然后经过 child 的 LoggingMiddleware
。(gofastmcp.com)
内置中间件示例与使用
FastMCP 自带一些插件级别的中间件,是实用且推荐的组合。下面是几个经典示例,以及它们的实现思路/作用。
Timing / 性能测量中间件
用于测量每次请求或每个操作的耗时:
-
示例代码(简化版):
class SimpleTimingMiddleware(Middleware):async def on_request(self, context: MiddlewareContext, call_next):start = time.perf_counter()result = await call_next(context)duration_ms = (time.perf_counter() - start) * 1000print(f"Request {context.method} took {duration_ms:.2f} ms")return result
-
FastMCP 内置版本(如
TimingMiddleware
、DetailedTimingMiddleware
)提供更完整的日志、配置选项、粒度更细的钩子(如on_call_tool
、on_read_resource
)等。(gofastmcp.com)
Logging / 日志中间件
用于记录请求、响应、异常等:
-
简单版本:
class SimpleLoggingMiddleware(Middleware):async def on_message(self, context: MiddlewareContext, call_next):print(f"Processing {context.method}")try:result = await call_next(context)print(f"Completed {context.method}")return resultexcept Exception as e:print(f"Failed {context.method}: {e}")raise
-
内置版本(
LoggingMiddleware
、StructuredLoggingMiddleware
)支持更多功能,比如:
* 是否包含 payload(消息体)
* 最大 payload 长度
* 结构化 JSON 日志输出(适合日志聚合与追踪系统)
* 针对不同操作(工具调用、资源读取等)的分层日志
(gofastmcp.com)
Rate Limiting / 限流中间件
用于控制客户端请求速率,防止滥用:
-
简单版本(示例):
class SimpleRateLimitMiddleware(Middleware):def __init__(self, requests_per_minute=60):self.requests_per_minute = requests_per_minuteself.client_requests = defaultdict(list)async def on_request(self, context: MiddlewareContext, call_next):now = time.time()client_id = "default" # 实际场景应从 context 中提取 client 标识# 删除过旧请求记录self.client_requests[client_id] = [t for t in self.client_requests[client_id] if t > now - 60]if len(self.client_requests[client_id]) >= self.requests_per_minute:raise McpError(ErrorData(code=-32000, message="Rate limit exceeded"))self.client_requests[client_id].append(now)return await call_next(context)
-
FastMCP 自带的
RateLimitingMiddleware
或SlidingWindowRateLimitingMiddleware
支持更优秀的算法(如 token bucket、滑动窗口)、并支持客户端识别、自定义行为等。(gofastmcp.com)
错误处理中间件
用于统一拦截、记录、转换错误,保持错误响应一致性:
-
简单示例:
class SimpleErrorHandlingMiddleware(Middleware):async def on_message(self, context: MiddlewareContext, call_next):try:return await call_next(context)except Exception as error:# 记录错误、统计、转换错误响应等logger.error(f"Error in {context.method}: {error}")raise
-
内置版本(
ErrorHandlingMiddleware
、RetryMiddleware
)支持:
* 是否包含 traceback
* 错误类型转换
* 重试机制(对特定异常自动重试)
* 自定义错误回调逻辑
(gofastmcp.com)
组合使用中间件
这些中间件之间可以组合使用,形成一个健全的中间件链。通常建议的顺序是:
- 错误处理(确保下游异常被捕获)
- 限流 / 防护
- 性能测量 / 计时
- 日志记录 / 可观察性
- 其他业务中间件
示例:
mcp = FastMCP("ProdServer")
mcp.add_middleware(ErrorHandlingMiddleware())
mcp.add_middleware(RateLimitingMiddleware(max_requests_per_second=50))
mcp.add_middleware(TimingMiddleware())
mcp.add_middleware(LoggingMiddleware())
自定义中间件示例及进阶用法
自定义中间件(基础)
假设你想做一个中间件,用于拦截某些工具调用,并在调用前后记录一些自定义信息:
from fastmcp.server.middleware import Middleware, MiddlewareContext
from fastmcp.exceptions import ToolErrorclass CustomAuthMiddleware(Middleware):async def on_call_tool(self, context: MiddlewareContext, call_next):tool_name = context.message.name# 拒绝某些工具调用if tool_name in ["admin_delete", "config_modify"]:raise ToolError("Access denied: insufficient privileges")# 修改调用参数(例如限制某个字段为非负数)if tool_name == "compute":args = context.message.argumentsif args.get("value", 0) < 0:args["value"] = abs(args["value"])# 调用下一阶段result = await call_next(context)# 对返回结果进行增强if tool_name == "get_data" and result.structured_content is not None:result.structured_content["processed_by"] = "CustomAuthMiddleware"return result
你可以将这个中间件加到链中:
mcp.add_middleware(CustomAuthMiddleware())
中间件共享状态(state)
如果你有多个中间件之间或中间件与工具之间需要共享某些中间状态(比如某次请求的上下文标志、验证 token、追踪 ID 等),可以使用 context 的 state 接口(在 2.11.0+ 版本支持):
class StateMiddleware(Middleware):async def on_request(self, context: MiddlewareContext, call_next):# 存储一个随机请求 ID 到状态request_id = uuid.uuid4().hexcontext.set_state("request_id", request_id)return await call_next(context)class UseStateMiddleware(Middleware):async def on_message(self, context: MiddlewareContext, call_next):request_id = context.get_state("request_id")print("Request ID is", request_id)return await call_next(context)
这样你就可以在同一个请求流中跨多个中间件或工具访问共享的 request_id
。
高级用例:动态工具替换 / 中间件注入
对于复杂场景,你可能希望:
- 根据条件动态禁用、替换或代理工具
- 变更工具行为(例如 wrap 一个工具,使其先跑预处理逻辑再调用真正工具)
- 在某些请求中跳过某些中间件
这些高级用法可以通过组合 on_list_tools
、on_call_tool
等钩子实现。关键要点是:
- 在 列表阶段 拦截工具、修改工具列表或屏蔽不应公开的工具
- 在 执行阶段 再次校验(以防有人直接调用未公开工具)
- 保持一致性:如果在列表阶段屏蔽掉某工具,在执行阶段也应拒绝其调用
总结与实践建议
- MCP Middleware 是 FastMCP 提供的强大机制,用于在 MCP 请求/响应流程中插入跨切面逻辑。(gofastmcp.com)
- 它有一个清晰的钩子层次结构(从
on_message
→on_request
→ 操作级别钩子),能让你在合适的粒度插入逻辑。(gofastmcp.com) - 列表操作与执行操作在中间件中访问组件 metadata 的方式不同,需要特别注意。(gofastmcp.com)
- 通过 state 机制(2.11.0+)可以让中间件和工具之间共享上下文状态。(gofastmcp.com)
- 利用 FastMCP 自带的中间件(如计时、日志、限流、错误处理),通常能满足大多数需求;你也可以在其基础上扩展或组合出更复杂的行为。(gofastmcp.com)