实用指南:【FastMCP】中间件

news/2025/9/25 22:26:45/文章来源:https://www.cnblogs.com/yxysuanfa/p/19112199

什么是 MCP Middleware?

在这里插入图片描述

中间件让你可以在 “工具执行 / 资源读取 / 提示获取 / 列表操作” 这些执行路径里插入统一逻辑,而不需要每一个工具、资源、或提示都手动加逻辑。


工作机制与生命周期

中间件的执行模型

Hook 层次与调用顺序

FastMCP 将中间件钩子(hooks)按“通用 → 特定”分层,这样你可以选择在不同粒度插入逻辑。(gofastmcp.com)

层级钩子名称触发时机 / 语义
最通用on_message所有 MCP 消息(请求 + 通知)都会触发
通用请求 / 通知on_request / on_notification针对是请求类型还是通知类型的消息
操作级别on_call_toolon_read_resourceon_get_prompton_list_toolson_list_resourceson_list_prompts针对具体操作(调用工具/读取资源/列出工具等)

比如,当客户端调用某个工具(tools/call),中间件会依次触发:

  1. on_message
  2. on_request(因为请求型)
  3. on_call_tool

这套层次允许你在通用层做统一处理(如日志、权限检查),在特定层做工具级别的细粒度控制(如某工具禁用、修改入参、审计等)(gofastmcp.com)。

列表操作 vs 执行操作的差异

这意味着:如果你希望中间件在执行时根据某个工具的标签做判断,需要额外通过 context.fastmcp_context.fastmcp.get_tool(...) 来查这个工具对象。(gofastmcp.com)


中间件开发细节

Hook 方法签名与参数

任何钩子方法(如 on_messageon_call_tool)通常接收两个参数:

中间件的标准结构通常是:

async def on_message(self, context: MiddlewareContext, call_next):
# 前置逻辑:检查 / 修改 context.message 等
result = await call_next(context)
# 后置逻辑:检查 / 修改 result
return result

你也可以在这个过程中捕获异常、拒绝处理、短路(不调用 call_next)等。(gofastmcp.com)

控制流策略

中间件可以对请求做出如下操作:

  • 继续流程await call_next(context)
  • 修改请求:在调用 call_next 之前,修改 context.message 或相关字段
  • 修改响应:在拿到 result 后改写结果(如在返回结果里添加字段)
  • 中断流程 / 拒绝请求:直接抛出错误(如 ToolErrorResourceErrorPromptError 等)或返回错误响应
  • 捕获异常 / 统一错误处理:在 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, MiddlewareContext
class 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 result
mcp = FastMCP("MyServer")
mcp.add_middleware(LoggingMiddleware())

这样,每条 MCP 消息经过服务器时,都会被这个中间件拦截、打印前后日志。(gofastmcp.com)

多个中间件与执行顺序

多个中间件会依次添加,执行顺序如下:

  1. 最先添加的中间件的“前置”逻辑先执行
  2. 最后添加的中间件的“前置逻辑”最后执行
  3. 然后进入实际处理
  4. 响应返回时,最后添加的中间件的后置逻辑最先执行
  5. 最先添加的中间件的后置逻辑最后执行

示例:

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 到父服务器。中间件在组合服务器环境下的行为规则如下:

  1. 父服务器中间件会对所有请求生效(包括那些被路由到子服务器的请求)
  2. 子服务器中间件只对被路由到子服务器的请求生效
  3. 每个服务器内部的中间件顺序保持不变 (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 / 性能测量中间件

用于测量每次请求或每个操作的耗时:

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 result
    except Exception as e:
    print(f"Failed {context.method
    }: {e
    }")
    raise
  • 内置版本(LoggingMiddlewareStructuredLoggingMiddleware)支持更多功能,比如:
      * 是否包含 payload(消息体)
      * 最大 payload 长度
      * 结构化 JSON 日志输出(适合日志聚合与追踪系统)
      * 针对不同操作(工具调用、资源读取等)的分层日志
      (gofastmcp.com)

Rate Limiting / 限流中间件

用于控制客户端请求速率,防止滥用:

  • 简单版本(示例):

    class SimpleRateLimitMiddleware
    (Middleware):
    def __init__(self, requests_per_minute=60):
    self.requests_per_minute = requests_per_minute
    self.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 自带的 RateLimitingMiddlewareSlidingWindowRateLimitingMiddleware 支持更优秀的算法(如 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
  • 内置版本(ErrorHandlingMiddlewareRetryMiddleware)支持:
      * 是否包含 traceback
      * 错误类型转换
      * 重试机制(对特定异常自动重试)
      * 自定义错误回调逻辑
      (gofastmcp.com)

组合使用中间件

这些中间件之间可以组合使用,形成一个健全的中间件链。通常建议的顺序是:

  1. 错误处理(确保下游异常被捕获)
  2. 限流 / 防护
  3. 性能测量 / 计时
  4. 日志记录 / 可观察性
  5. 其他业务中间件

示例:

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 ToolError
class 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.arguments
if 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().hex
context.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_toolson_call_tool 等钩子实现。关键要点是:

  • 列表阶段 拦截工具、修改工具列表或屏蔽不应公开的工具
  • 执行阶段 再次校验(以防有人直接调用未公开工具)
  • 保持一致性:如果在列表阶段屏蔽掉某工具,在执行阶段也应拒绝其调用

总结与实践建议

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/917621.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Say 题选记(9.21 - 9.27)

P2048 [NOI2010] 超级钢琴 如何求长度在 \([L,R]\) 的子串中,子串和前 \(k\) 大的那些。 首先显然可以转化为前缀和。考虑 \(k = 1\) 的情况,把以 \(i(1 \le i \le n)\) 为右端点,\(j \in [i - R + 1, i - L + 1]\)…

3D 高斯训练速度和消耗 - MKT

3D 高斯训练速度和消耗 SEELE: A Unified Acceleration Framework for Real-Time Gaussian Splatting https://arxiv.org/pdf/2503.05168

做哪一类网站容易有排名中国建设银行网站会员注册信息补充

电脑崩溃之后&#xff0c;我发现维护系统还是很重要的一件事情。比如软件尽可能装D盘&#xff0c;C盘&#xff08;系统盘&#xff09;尽可能不要存储数据等等。接着&#xff0c;就是如何让系统更易用&#xff0c;因此我在这里分享我的使用方式&#xff0c;以后就可以随便重装系…

怎么设计个人网站在万网上域名了怎么做网站

目录 第一章、Java中的for循环介绍for循环for-each/增强for循环嵌套for循环 第一章、遍历List集合的几种方式简单的for循环增强型for循环Iterator迭代器ListIterator列表迭代器while循环Iterable.forEach()方法Stream.forEach()方法 第一章、Java中的for循环介绍 for循环 ①普…

完整教程:【PyTorch实战:文本分类】23、BERT文本分类实战指南:从原理到PyTorch落地

完整教程:【PyTorch实战:文本分类】23、BERT文本分类实战指南:从原理到PyTorch落地pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; …

Linux网络:运用UDP实现网络通信(网络套接字的创建绑定)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

常见进制

D:/study/C语言 devc/test9.c

9.25总结

今天是9.25,今天是星期四,今天上午上了一节数据结构和一节跆拳道课,跆拳道课上学了好多动作,还教我们劈叉,锻炼我们的韧带。下午就没有课程安排了,中午和朋友打了一会儿游戏,然后睡了一觉,醒来之后,去教室学了…

做受视频播放网站wordpress实现文章连载目录

感情是偏执的 越爱越是偏执的 不相信我看到的 硬要说裂缝不过 是皱褶 怎么先炽热的却先变冷了 慢热的却停不了还在沸腾着 看时光任性快跑随意就转折 慢冷的人啊 会自我折磨 冲动的人向来听不见挽留 这世界大得让你很难不旅游 浪漫让你温柔 也让你最惹人 泪流 …

proxifier联合burpsuite抓包小程序,但是小程序连不上网解决办法(亲测)

找了网上好多教程都没找到对应我这种情况的解决方法,我这个方法希望能帮到跟我情况一样的同学问题描述 之前看小迪的课程的时候学到过burpsuite加proxifier联合抓小程序的包,当时也复现成功了,后面在看到小程序资产…

完整教程:C语言——函数(超详细分析)

完整教程:C语言——函数(超详细分析)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Mo…

大 LCP 时代(stupid.*)

大 LCP 时代(stupid.*) 题目描述 LCP 就是传说中的最长公共前缀,至于为什么要加上一个大字,那是因为…你会知道的(有大病)。 首先,求LCP就要有字符串。既然那么需要它们,那就给出n个字符串好了。 于是你需要回答…

网站中的滑动栏怎么做的四川建设网网

虚拟技术十分热门.虚拟技术是将一台物理硬件计算机虚拟成多台软件计算机.每一台虚拟出来的软件计算机(以下叫做虚拟机)用起来都就象是在用那台被虚拟的硬件计算机(以下叫做真实机)完全一样.当然这样的说法忽略了虚拟机相对于真实机在执行效益上不可避免所存在的损失.所以如何减…

实用指南:Python实现手榴弹爆炸算法(Grenade Explosion Method, GEM)(附完整代码)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Day08-C:\Users\Lenovo\Desktop\note\code\JavaSE\Basic\src\com\David\array-ArrayDemo01~07

数组首先要声明数组变量才能使用如dataType[] arrayRefVar; java语言中使用new操作来创建数组,如dataType[ ] arrayRefVar = new dataType[arraysize] 数组通过索引访问,索引从0开始内存分析堆:存放new的对象和数组…

yolov10_float16.tflite TO yolov10_int8.tflite

使用google colab平台1. 安装ultralytics!pip install ultralytics2. 导入yolo并从ultralytics加载yolov10n.ptfrom ultralytics import YOLO model = YOLO("https://github.com/ultralytics/assets/releases/dow…

ansible注意的和错误代码分析

一、需要注意的点分清楚这个是主控节点还是被控节点的操作,有的时候是一个文件从主控到被控节点,还是被控节点的文件到主控节点上面了剧本执行报错代码 # 仔细看,报错的原因就是ansibel_lvm 未定义,写错了变量名TA…

用 Rust 和 Tesseract OCR 识别验证码

一、背景介绍 Rust 是一种系统级编程语言,以性能和安全性著称。在自动化测试和数据分析场景中,验证码识别是一个常见挑战。结合 Tesseract OCR,我们可以使用 Rust 构建一个高效的验证码识别工具。本文将介绍如何使用…

基于寄存器地址amp;标准外设库的LED流水灯

实验任务2 1.1先在工程总文件夹中创建User,Library和Startup三个文件夹,然后右键keil中的文件夹Source Group 1添加工程所需的这三个文件夹。1.2分别在Library,Startup和User三个文件夹中添加必要的头文件和.c文件。…

用 Swift 和 Tesseract OCR 实现验证码识别

一、背景介绍 Swift 是 Apple 推出的现代化编程语言,广泛应用于 iOS 和 macOS 应用开发。结合 Tesseract OCR,可以在移动和桌面应用中高效地识别验证码。本文将展示如何使用 Swift 结合 Tesseract OCR 实现验证码自动…