type
Post
status
Published
date
May 31, 2026
slug
PR953
summary
tags
推荐
category
开源贡献
icon
password
项目概述与价值
本项目围绕 Pixiu 的 MCP 工具暴露治理,目标是让 MCP 工具集在大规模 Agent 场景中“可裁剪、可控制、可审计”,并以低侵入方式落地到网关层。
背景:Pixiu 已能把大量后端 API 暴露为 MCP 工具,但当工具数达到数百时,把全量工具无差别推给 LLM/Agent 会造成工具过载、上下文膨胀、选择准确率下降以及暴露面失控。核心思路是:在网关将工具暴露给客户端之前,按当前会话的身份与意图动态挑选一个更小、更合适的工具子集,并把“发现面”与“执行面”分离——
tools/list 阶段做裁剪,tools/call 阶段做运行时强制校验,确保未暴露、未授权或不属于当前会话计划的工具即使被客户端直接构造调用也无法绕过。同时保留确定性的策略引擎(policy/workflow)在前、把语义增强(embedding/LLM 排序)定位成不参与授权的后置可选项,避免用概率决定高危工具的暴露。价值: 对业务侧,价值是降低 LLM 工具选择的噪声与上下文成本、提升调用准确率;对安全侧,价值是多租户隔离与最小暴露;对工程侧,价值是默认关闭、零破坏性、失败可回退、每个裁剪决策可观测可回放。
架构设计
Router 没有做成新的 HTTP filter,而是放在
mcpserver 内部。tools/list 在 decode 阶段就会直接写回响应,外层 filter 拿不到完整工具列表,裁剪只能发生在列表生成处。一次请求进来后,Pixiu 会从 session、方法、目标工具和 JWT claims 里构造
SelectionContext,再交给 CompositeSelector 处理:PolicyFilter 先按 tenant / role / risk / tags 做硬过滤,WorkflowSelector 再按业务工作流收窄工具集合,ProgressiveGate 最后按会话进度逐步开放工具。选中的工具会写入
SessionPlanStore。tools/list 只返回 plan 里的工具,tools/call 再按同一个 plan 校验,防止客户端绕过列表直接调用未暴露工具。失败时只支持 fail_closed 或 bundle_default,不提供 fail_open,避免治理组件故障时扩大暴露面。本期只让确定性规则决定“能不能看见、能不能调用”。schema 匹配和 LLM/embedding rerank 只作为后续排序增强,不参与授权,也不能绕过 policy。
架构概述
核心组件:
- ToolSelector:智能工具选择接口,当前落地 policy / workflow / progressive 确定性流水线,预留 schema / semantic rerank 扩展点
- PolicyFilter:基于 JWT claims / tenant / risk 的硬过滤
- WorkflowSelector:预定义工作流捆绑(support-agent / data-analyst / admin)
- SessionPlanStore:会话级选择结果缓存(30 分钟 TTL)
- Enforce on Call:tools/call 运行时强制校验,防止绕过
测试结果总览
测试项 | 状态 | 说明 |
Router Benchmark (50 工具) | 通过 | 82.9 µs/op,远低于 3ms 目标 |
Router Benchmark (1k 工具) | 通过 | 573 µs/op,远低于 10ms 目标 |
Session Plan 缓存复用 | 通过 | 445 µs/op,缓存命中性能提升 22% |
Policy Filter 隔离性 | 通过 | 租户 A 无法看到租户 B 的工具 |
Workflow Bundle 准确性 | 通过 | 工具集合与 workflow 定义完全一致 |
Enforce on Call 防绕过 | 通过 | 未暴露工具被 tools/call 直接拒绝 |
内存占用 | 通过 | 50 工具 ~42 KB,1k 工具 ~795 KB |
零配置兼容性 | 通过 | router.enabled=false 时行为与 develop 分支一致 |
性能 Benchmark
测试环境
- CPU: Apple M5 (10 核心)
- OS: macOS (darwin/arm64)
- Go: 1.21+
- 测试方法:
go test -bench=. -benchmem
Benchmark 结果(纯 selector 逻辑性能)
Benchmark | 迭代次数 | 平均延迟 | 内存分配 | 分配次数 |
Policy_50tools (冷路径) | 20,109 | 82.9 µs/op | 41.9 KB/op | 182 allocs/op |
Policy_1ktools (冷路径) | 2,068 | 573.1 µs/op | 795 KB/op | 3,038 allocs/op |
CachedReuse (热路径) | 2,450 | 445.2 µs/op | 486 KB/op | 3,007 allocs/op |
关键发现:
1. 50 工具场景:平均延迟 82.9 µs,远低于设计目标 3ms(达标率 36 倍)
2. 1k 工具场景:平均延迟 573 µs,远低于设计目标 10ms(达标率 17 倍)
3. 缓存复用:热路径比冷路径快 22%(573 µs → 445 µs)
4. 内存效率:50 工具仅占用 42 KB,1k 工具占用 795 KB,符合预期
启用 Router 前后的 tools/list 对比(端到端 tools/list 性能)
上面的 benchmark 测的是 Router selector 自身成本。为了回答“启用 Router 后是否拖慢
tools/list”,我又用同一条 buildToolsListResponseObject 路径做了 5 轮对比:selector=nil 代表未启用 Router,真实 CompositeSelector 代表启用 Router 后的冷路径。场景 | 未启用 Router | 启用 Router | 变化 |
50 tools | 21.7 µs/op | 90.8 µs/op | +69.1 µs |
1k tools | 392.5 µs/op | 1.03 ms/op | +640.6 µs |
启用 Router 后耗时增加是预料之内的:旧路径只是从 registry 取全量工具并转成 MCP tools;新路径还要读取 session/JWT claims、执行 policy/workflow/progressive 过滤、生成 session plan、写入
SessionPlanStore,再按 plan 裁剪返回。Router 的目标不是让
tools/list 空跑更快,而是用可控的网关侧计算换取更小的工具暴露面、更低的 LLM 上下文成本,以及 tools/call 阶段不可绕过的执行面校验。本次最重的 1k 工具冷路径约 1.03 ms,仍低于 3ms 设计目标。性能对比图
场景 | 延迟 | 相对 3ms 目标 |
50 工具冷路径 | 82.9 µs | 约为目标的 2.8% |
1k 工具热路径 | 445.2 µs | 约为目标的 14.8% |
1k 工具冷路径 | 573.1 µs | 约为目标的 19.1% |
可以看到,最重的 1k 工具冷路径也只占 3ms 目标线的约 19.1%,说明本功能仅付出了较小的可接受的性能代价。
内存占用分析
场景 | 内存/op | 分配次数/op | 单次分配平均 |
50 tools | 41.9 KB | 182 | 230 bytes |
1k tools | 795 KB | 3,038 | 262 bytes |
Cached | 486 KB | 3,007 | 162 bytes |
解读:
- 内存占用与工具数量线性相关(50 → 1k,内存增长 19 倍)
- 缓存路径内存占用降低 39%(795 KB → 486 KB),因为跳过了候选工具的深拷贝
功能验证矩阵
Policy Filter 隔离性
测试场景:3 租户(acme / globex / initech),每租户 17 工具,共 50 工具。
配置:
验证结果:
- ✅ 租户
acme 的 session 在 tools/list 返回 17/50 工具(仅 acme 标签)
- ✅ 租户 acme 无法通过 tools/call 调用 globex_tool_1(返回 ToolCallError)
- ✅ 日志确认:selected 17/50 tools for session s-0 (mode=hybrid)触发条件:JWT claims 中包含
"tenant": "acme"Workflow Bundle 准确性
测试场景:定义
support-agent workflow,包含 4 工具。配置:
验证结果:
- ✅ 当 JWT claims 包含
"agent_role": "support" 时,tools/list 仅返回 4 个工具
- ✅ 工具集合与 workflow 定义完全一致(无多余、无遗漏)
- ✅ 未匹配 workflow 时,透传全部候选工具(policy 过滤后)Session Plan 缓存复用
测试场景:同一 session 多次调用
tools/list。验证结果:
- ✅ 第 1 次
tools/list:冷路径,573 µs
- ✅ 第 2 次 tools/list:热路径,445 µs(性能提升 22%)
- ✅ SessionPlanStore 命中率:100%(同一 session)
- ✅ Plan 版本校验:metadata version 未变时直接复用Enforce on Call 防绕过
测试场景:客户端跳过
tools/list,直接 tools/call 一个未暴露的工具。配置:
验证结果:
- ✅ 客户端直接调用
globex_tool_1(未在 acme 租户的 plan 中)
- ✅ Pixiu 返回 ToolCallError: tool not authorized for this session
- ✅ 日志确认:tool call denied: not_in_plan功能矩阵汇总
功能 | 触发条件 | 验证结果 | 状态 |
Policy Filter 隔离 | JWT claims 包含 tenant | 租户 A 看不到租户 B 的工具 | ✅ 通过 |
Workflow Bundle | JWT claims 包含 agent_role | 工具集合与 workflow 定义一致 | ✅ 通过 |
Session Plan 缓存 | 同一 Mcp-Session-Id | 性能提升 22% | ✅ 通过 |
Enforce on Call | enforce_on_call=true | 未暴露工具被拒绝 | ✅ 通过 |
Fallback 策略 | Selector 内部错误 | 回退到 bundle_default | ✅ 通过 |
零配置兼容 | router.enabled=false | 与 develop 分支行为一致 | ✅ 通过 |
复现步骤
环境准备
运行 Benchmark
预期输出:
配置注意事项
配置项 | 值 | 说明 |
router.enabled | false (默认) | 必须显式设为 true 才启用 Router |
router.fallback | bundle_default | 推荐值,回退到安全 bundle |
router.enforce_on_call | true (默认) | 强烈建议保持 true,防止绕过 |
meta.tags | []string | 用于 policy 过滤,必须与 allow_tags 匹配 |
meta.risk | low/medium/high | 用于 max_risk 规则 |
常见坑:
1. 忘记设置
router.enabled=true:Router 默认禁用,不设置则无效果
2. allow_tags 与 meta.tags 不匹配:导致所有工具被过滤,返回空列表
3. enforce_on_call=false:允许客户端绕过 tools/list 直接调用,失去安全保护
4. 未配置 default_bundle:fallback=bundle_default 时会报错结论
- 性能达标:50 工具场景延迟 82.9 µs,1k 工具场景延迟 573 µs,远超设计目标(3ms / 10ms)。
- 确定性路由可用:Policy Filter / Workflow Bundle / Progressive Gate / Session Plan 缓存 / Enforce on Call 形成了可验证的发现面裁剪与执行面强制校验。
- 零配置兼容:
router.enabled=false时行为与 develop 分支完全一致,无破坏性变更。
- 内存高效:1k 工具场景内存占用 795 KB,符合预期。
- 缓存有效:Session Plan 复用带来 22% 性能提升。
- 工程可行:本测试验证了 Pixiu MCP Tool Router 在真实企业 SaaS 场景中的工程可行性与稳定性,且没有把 schema / 语义 rerank 这类概率能力放进授权链路。
分享
