Reflex Enterprise 自定义 MCP 资源(rxe.mcp.resource)实战指南:把只读会话状态安全暴露给 AI Agent
【免费下载链接】reflex🕸️ Web apps in pure Python 🐍项目地址: https://gitcode.com/GitHub_Trending/re/reflex
rxe.MCPPlugin让 Reflex 应用摇身一变成为 MCP 服务器,事件处理器变成 Agent 可调用的"动作"(通过queue_event)。而本指南聚焦 MCP 的读侧:如何用rxe.mcp.resource把一个普通状态方法发布成var 式的只读 MCP 资源——一个可以携带参数、绑定调用方会话状态、并受与rxe.var相同授权体系保护的"计算值"。读完本文,你将掌握自定义 MCP 资源的定义、URI 寻址规则、auth=授权控制与全部约束,并理解它与内置reflex://资源、queue_event动作如何协同,为 Agent 提供安全的"查询接口"。
本文基于仓库 docs/enterprise/mcp/custom-resources.md 展开,并结合
reflex-enterpriseMCP 插件相关文档(Auto MCP、认证、扩展)及 Reflex 开源核心的 computed var 实现进行深度佐证。
读侧与写侧:queue_event与rxe.mcp.resource的分工
在 Reflex Enterprise 的 MCP 集成(reflex-enterprise v0.9.4 起提供)中,Agent 与你的应用交互存在两个方向:
- 写侧(动作):
queue_event工具把事件应用到调用方的会话上,驱动事件处理器执行,并返回状态 delta。它操作的是"行为"。 - 读侧(查询):
rxe.mcp.resource装饰的状态方法发布为只读 MCP 资源——一个"计算值"(computed value),可接收参数,并绑定到调用方的实时会话状态(live session state)。
这种"读/写分离"与 Reflex 核心中 computed var 与事件处理器的分离一脉相承。在 packages/reflex-base/src/reflex_base/vars/base.py 中可以看到,ComputedVar是一个带 getter、支持缓存(_cache)、依赖追踪(_static_deps/_auto_deps)与后端计算(_backend)的 Var 描述器,它天然就是"只读计算值";自定义 MCP 资源正是把这一语义延伸到 MCP 协议层——方法在调用方会话上运行,返回值就是资源内容。
关键区别在于:资源绝不会注册成事件处理器。也就是说它不会出现在search_events的结果里,也不能被queue_event调用,Agent 只能通过"读资源"的方式访问它。
快速上手:把一个状态方法变成 MCP 资源
在需要暴露只读视图的状态类上,用@rxe.mcp.resource装饰一个普通方法即可:
import reflex as rx import reflex_enterprise as rxe def admins_only(ctx) -> bool: return ctx.auth_user_state.userinfo.get("role") == "admin" class DashboardState(rx.State): orders: list[dict] = [] @rxe.mcp.resource def order_count(self) -> int: """How many orders the current user has.""" return len(self.orders) @rxe.mcp.resource(auth=admins_only) def revenue(self, quarter: str) -> dict: """Revenue for a quarter (admins only).""" return {"quarter": quarter, "total": self._revenue_for(quarter)}要点:
- 方法在调用方(Agent)的实时会话状态上运行,因此
self.orders读到的是该 Agent 会话中真实存在的数据——链式多次queue_event造成的状态变化,资源读取时立即可见。 - 返回值即资源内容;资源在服务端被注册为顶层资源(top-level resource),绝非事件处理器。
- 装饰后的方法依旧可以从你自己的代码中以
self.method(...)调用,不会破坏原有语义。 @rxe.mcp.resource(auth=...)支持与rxe.var相同的三种取值(见下文"授权控制"一节)。
参数化资源:输入即 URI 模板变量
每个方法参数都会成为URI 模板变量(一个必需的路径段)。上面的示例会被寻址为:
state-resource://<state>/order_count state-resource://<state>/revenue/{quarter}也就是说,order_count无参直接读;revenue需要客户端在 URI 中填充quarter段(例如state-resource://my_app___my_app____dashboard_state/revenue/Q3)。这符合 MCP 资源模板(resource template)的标准语义,客户端可以通过resources/templates/list发现确切的 URI 模式。
URI 命名规则:<state>到底长什么样
<state>是状态在search_events中上报的名称——即去掉根State前缀后的模块前缀名,嵌套子状态的点号会被替换为斜杠。对于定义在my_app/my_app.py中的DashboardState,其寻址 URI 为:
state-resource://my_app___my_app____dashboard_state/order_count可以看到,模块路径中的点号被映射为___(三个下划线)分隔符,这与 Auto MCP 中事件名(如tickets___tickets____ticket_state.create_ticket)以及reflex://state/vars/...资源中的状态全限定名使用同一套命名约定(详见 docs/enterprise/mcp/index.md)。
你不需要手写这些 URI。资源会同时通过两种途径对客户端广播:
- 服务端的 instructions(MCP 连接时自动生成的说明文本);
- 标准的
resources/templates/list(MCP 资源模板列表)。
客户端据此即可精确发现所有可用资源的确切 URI,无需人工记录。
Options 配置参考
@rxe.mcp.resource接受以下关键字选项:
| 选项 | 默认值 | 用途 |
|---|---|---|
auth | 应用的 secure-default | True(任何已认证用户)、False(公开),或一个check(ctx) -> bool可调用对象。 |
name | <state>.<method> | 向客户端广告的资源名称。 |
description | docstring 摘要 | 资源描述。 |
mime_type | application/json | 返回内容的 MIME 类型。 |
其中auth=的取值与rxe.var完全一致,它的ctx携带:
ctx.auth_user_state——当前用户(AuthUserState);ctx.surface/ctx.token_scopes——请求到达的"表面"("browser"/"event_api"/"mcp")与代理该请求的令牌所携带的作用域(详见 认证文档的 surface-aware auth checks)。
授权语义与匿名会话
auth=在存在AuthPlugin提供身份时始终强制执行。具体行为:
- 匿名会话(通过
POST /_reflex/auth/token获取的匿名 bearer 令牌所绑定的会话)没有用户,因此只能读取auth=False的资源;受保护资源对匿名会话一律不可读。 - 这与 secure-by-default 体系中"受保护 var 对未解析用户 withheld(扣留)"的行为一致——在 docs/enterprise/auth/secure-by-default.md 中可以看到,受保护字段/var 在用户解析前会被从 delta 中剔除,自定义资源沿用同一套"先认证、后授权"的检查管线。
- 注意:MCP 会话的令牌基于服务端生成的 Reflex 会话 token,该 token 永不离开服务端,因此一个凭据只能寻址其专属会话,无法冒用浏览器会话或其他 Agent 的会话(详见 docs/enterprise/mcp/authentication.md)。
硬性规则(Rules)
以下规则由装饰器在导入期强制校验,违反即抛出TypeError:
- 只能装饰普通方法。签名必须是
def name(self, ...)——不能是rx.event处理器,也不能是rx.var。除此之外的任何形式都会在导入时报TypeError。装饰后的方法仍可从你自己的代码中以self.method(...)正常调用。 - 异步完全支持。
async def资源方法会被正确 await,返回值即资源内容。 - 参数必须带类型注解。参数注解会成为资源的输入 schema;未注解的参数一律按
str处理。 - 契约上只读。与 computed var 相同,资源不允许驱动状态变更;需要产生状态变化时请通过
queue_event完成。 rxe_request_context是保留参数名。包装器会向方法追加一个名为rxe_request_context的参数,用于接收 MCP 请求上下文——因此你不能自行声明同名参数。
第 4 条与 Reflex 核心 computed var 的设计一致:在 packages/reflex-base/src/reflex_base/vars/base.py 中,ComputedVar通过_fget只暴露 getter 语义,本身没有任何 setter 路径;自定义 MCP 资源把"读会话状态"这一职责固化到协议层,把"改状态"的职责明确留给queue_event事件链路。
与内置reflex://资源、expose_events=False的组合
自定义资源并非孤立功能,它与 Auto MCP 的完整资源体系协同工作:
- 内置资源以
reflex://为协议前缀,包括reflex://state(状态名枚举)、reflex://event(处理器枚举)、reflex://state/vars/<state_name>(会话实时状态)、reflex://state/vars/<state_name>/<var_name>(单个 var 值,computed var 会被标记为 dirty 并重新计算而非走缓存)等(见 docs/enterprise/mcp/index.md)。 - 自定义资源使用独立的
state-resource://协议前缀,用于发布你自己的、带参数的只读会话视图——两者互为补充:内置资源"开箱即读",自定义资源"按需定制"。 - 如果你希望完全不暴露任何事件动作表面,只保留状态读取、
rxe.mcp.resource方法以及通过configure=添加的自定义内容,可以设置rxe.MCPPlugin(expose_events=False)(见 docs/enterprise/mcp/index.md)。此时 Agent 面对的应用就是一个纯粹的"只读查询服务 + 自定义工具集"。
更进一步:在configure=钩子或生命周期任务中注册资源
rxe.mcp.resource是声明式的最简路径。当需要直接操控底层FastMCP服务器(例如注册任意工具、资源或 prompt)时,MCPPlugin提供两条入口,两条入口拿到的是同一个服务器实例:
- 构建期:
MCPPlugin(configure=...)传入可调用对象或"module.function"导入路径,在内置工具/资源注册完成后、服务器挂载前执行,因此从第一个请求起即生效:def customize(server): @server.resource("config://version") def version() -> str: """The deployed app version.""" return "1.0.0" config = rxe.Config( app_name="my_app", plugins=[rxe.MCPPlugin(configure=customize)], ) - 运行期:
rxe.get_mcp_server()返回当前服务器。注意它会在应用编译完成前抛出RuntimeError,因此不能在模块顶层(包括裸@rxe.get_mcp_server().tool()装饰器)调用,而应放在 生命周期任务 中,例如app.register_lifespan_task(register_mcp_tools)。若在传输层已开始服务后注册,已连接的客户端会在下一次tools/list时看到新增内容(详见 docs/enterprise/mcp/extending.md)。
相关文档
- Auto MCP:内置
reflex://资源,用于读取状态与枚举处理器;以及queue_event/search_events等工具。 - 扩展 MCP 服务器:通过
configure=与get_mcp_server()注册任意FastMCP工具、资源与 prompt。 - MCP 认证:身份、作用域与 surface-aware 授权检查。
- Secure by default:
auth=检查在页面、处理器、字段与 var 上的完整行为。
实践要点回顾:把"读"声明为rxe.mcp.resource、把"写"留在queue_event;让auth=覆盖资源以保证匿名会话不可读敏感视图;依赖参数注解与rxe_request_context保留名的约束,避免导入期TypeError;最后让客户端通过 instructions 与resources/templates/list自动发现 URI,而不是硬编码状态名。
【免费下载链接】reflex🕸️ Web apps in pure Python 🐍项目地址: https://gitcode.com/GitHub_Trending/re/reflex
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考