Lazy loaded image
开源贡献
dubbo-go-pixiu丨MCP Server Filter 内置的工具暴露治理层功能报告
字数 4596阅读时长 12 分钟
2026-5-31
type
Post
status
Published
date
May 31, 2026
slug
PR953
summary
tags
推荐
category
开源贡献
icon
password
PR位置:
feat(mcp): intelligent tool routing for MCP server filter
注:PR #953 已经实现了“数十/数百 MCP 工具的确定性收窄、会话级工具计划、调用时防绕过、基于 JWT claims 的访问控制、可解释裁剪理由”。
但还没有实现:根据用户自然语言意图自动语义判断到底该调用哪几个 MCP 服务的 Agent Planner / Semantic Router。

功能概述与价值

MCP 智能工具路由功能是为了在工具数量变多时,减少 LLM 看到的无关工具,降低上下文噪声,从而提升工具选择准确度;同时用 tools/call 强制校验保证未暴露工具不能被绕过调用。
背景Pixiu 已能把大量后端 API 暴露为 MCP 工具,但当工具数达到数百时,把全量工具无差别推给 LLM/Agent 会造成工具过载、上下文膨胀、选择准确率下降以及暴露面失控。
核心思路:在网关将工具暴露给客户端之前,按当前会话的身份与意图动态挑选一个更小、更合适的工具子集,并把“发现面”与“执行面”分离——tools/list 阶段做裁剪,tools/call 阶段做运行时强制校验,确保未暴露、未授权或不属于当前会话计划的工具即使被客户端直接构造调用也无法绕过。同时保留确定性的策略引擎(policy/workflow)在前、把语义增强(embedding/LLM 排序)定位成不参与授权的后置可选项,避免用概率决定高危工具的暴露。
价值: 对业务侧,价值是降低 LLM 工具选择的噪声与上下文成本、提升调用准确率;对安全侧,价值是多租户隔离与最小暴露;对工程侧,价值是默认关闭、零破坏性、失败可回退、每个裁剪决策可观测可回放。

核心验证结论

本次验证的重点是证明 Router 已经把 tools/list 的发现面有效收窄,而不是直接证明 LLM 准确率提升了多少:
  • 未启用 Router:返回全量工具,50/50
  • 启用 tenant policy 后:acme session 只返回 acme 相关工具, 17/50
  • 启用 workflow 后:support-agent 只返回 workflow bundle 里的 4 个工具
  • tools/call 再校验 plan,未暴露工具不能直接绕过调用。
因此,当前结论可以稳妥表述为:
Router 已经验证了“候选工具集收窄”和“执行面防绕过”;它的目标是降低 LLM 工具选择噪声,并为提升工具调用准确度提供基础。

架构图

核心组件: - 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 没有做成新的 HTTP filter,而是放在 mcpserver 内部。tools/list 在 decode 阶段就会直接写回响应,外层 filter 拿不到完整工具列表,裁剪只能发生在列表生成处。
一次请求进来后,Pixiu 会从 session、方法、目标工具和 JWT claims 里构造 SelectionContext,再交给 CompositeSelector 处理:PolicyFilter 先按 tenant / role / risk / tags 做硬过滤,WorkflowSelector 再按业务工作流收窄工具集合,ProgressiveGate 最后按会话进度逐步开放工具。
选中的工具会写入 SessionPlanStoretools/list 只返回 plan 里的工具,tools/call 再按同一个 plan 校验,防止客户端绕过列表直接调用未暴露工具。失败时只支持 fail_closedbundle_default,不提供 fail_open,避免治理组件故障时扩大暴露面。
本期只让确定性规则决定“能不能看见、能不能调用”。schema 匹配和 LLM/embedding rerank 只作为后续排序增强,不参与授权,也不能绕过 policy。

测试结果总览

测试项
状态
说明
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

性能 benchmark 只是验证:本功能仅付出了较小的可接受的性能代价,重点还是在功能验证。
3ms 设计目标原因:基于 Pixiu 的 in-memory registry + filter chain 结构,tools/list 路径额外 p50 延迟可以控制在约 1~3ms

测试环境

  • 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 的目标是用可控的网关侧计算换取更小的工具暴露面、更低的 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 fail_closed
选择产出空集
暴露 0 工具,不反向泄漏
✅ 通过
零配置兼容
router.enabled=false
与 develop 分支行为一致
✅ 通过

真实端到端场景验证(HTTP + 真实 JWT)

本节数据来自真实端到端运行:真实 Pixiu 进程(gateway start)、真实 RS256 JWT(由 test/mcp-router-validation/jwtgen 签发,公钥写入本地 JWKS 文件)、真实 HTTP 请求(curl)。

环境拓扑

  • 8 个工具,跨 3 租户(acme/globex/initech)+ shared + 特权(internal/admin)
  • policy:always-on block-privileged(拒绝 internal/admin 标签)+ 每租户 isolation 规则
  • workflows:acme-support-agentglobex-support-agent(按 tenant 匹配)
  • fallback:fail_closedenforce_on_call: true

场景一:多租户隔离(tools/list)

不同租户的 JWT 请求 tools/list,每个只看到自己的工具子集:
租户 / 角色(JWT claims)
tools/list 返回
决策
tenant=acme, agent_role=support
acme_search_kb, acme_create_ticket
workflow 命中,裁到 2
tenant=globex, agent_role=support
globex_query_orders
workflow 命中,裁到 1
tenant=initech, agent_role=support
initech_list_reports, health_check
无 workflow,纯租户策略
tenant=acme(无 agent_role)
acme_search_kb, acme_create_ticket
纯租户策略
决策日志(evidence-decision-log.txt)显示:

场景二:tools/call 运行时强制校验

acme-support 会话建立 plan 后,对 4 个工具发起 tools/call,真实结果如下:
调用工具
同租户?
在 plan 内?
结果
globex_query_orders
否,globex
denied
internal_dump
否,特权工具
denied
acme_delete_user
是,acme
否,不在 workflow
denied
acme_search_kb
是,acme
forwarded
关键点在 acme_delete_user:它和调用者同属 acme 租户,但因为不在当前 session 的 workflow plan 中,依然被拒绝。这说明强制校验是 plan 级别,不只是 tenant 级别;发现面和执行面真正分离。

场景三:Workflow 捆绑

acme-supportagent_role=support + tenant=acme)命中 acme-support-agent workflow,tools/list 精确返回 bundle 定义的 2 个工具:acme_search_kbacme_create_ticketacme_delete_user 虽然同租户,但不在 bundle 中,因此被裁掉。

场景四:Fallback — fail_closed

构造 tenant=umbrella 的 JWT(配置中有 umbrella-isolation 规则,但没有任何工具带 umbrella 标签),真实输出:
tools/list 对 umbrella 返回 tools: []。这证明当选择流水线产出空集时,fail_closed 不会反向暴露任何工具。

场景五:决策可审计(admin 端点)

audit.payload_logging: true 时,loopback 可访问 GET /__mcp/router/plan/{session_id},返回完整决策链。acme 会话真实输出节选:
每个被裁工具都带 stageruledetail,可以回放为什么被保留或裁掉。

复现步骤

环境准备

运行 Benchmark

预期输出

复现端到端验证

证据原文:
  • evidence-e2e-claims.txt:五个场景的完整 curl 输出
  • evidence-decision-log.txt:每次 tools/list 的路由决策(candidates/selected/mode)
  • evidence-call-enforcement.txttools/call 的放行/拒绝日志
  • evidence-fallback.txtfail_closed 模式表

配置文件示例

验证服务就绪


配置注意事项

配置项
说明
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 规则
meta.discovery_visibility
true (默认)
false 时工具不在 tools/list 暴露,但仍可 tools/call
常见坑: 1. 忘记设置 router.enabled=true:Router 默认禁用,不设置则无效果 2. allow_tagsmeta.tags 不匹配:导致所有工具被过滤,返回空列表 3. enforce_on_call=false:允许客户端绕过 tools/list 直接调用,失去安全保护 4. 未配置 default_bundlefallback=bundle_default 时会报错

结论

  1. 性能达标:50 工具场景延迟 82.9 µs,1k 工具场景延迟 573 µs,远超设计目标(3ms / 10ms)。
  1. 端到端可用:在真实 Pixiu 进程 + 真实 RS256 JWT + 真实 HTTP 下,多租户隔离、越权拒绝、workflow 捆绑、fail_closed、决策审计全部按预期工作。
  1. 执行面是 plan 级:同租户但不在 session plan 的工具(如 acme_delete_user)依然被 tools/call 拒绝,说明发现面与执行面真正分离,裁剪不可被绕过。
  1. 零配置兼容router.enabled=false 时行为与 develop 分支完全一致,无破坏性变更
  1. 缓存有效:Session Plan 复用带来 22% 性能提升
  1. 决策可解释:每个被裁工具都带 stage / rule / detail,可通过 admin 端点回放。
  1. 工程可行:本测试验证了 Pixiu MCP Tool Router 在真实企业 SaaS 场景中的工程可行性与稳定性,且没有把 schema / 语义 rerank 这类概率能力放进授权链路。
回到首页