探讨 MCP 服务器暴露大量工具时如何避免让 Agent 上下文溢出,提出注册表可用性、协议发现、宿主端上下文注入分离的架构方案,并指出简单方案会引发延迟回归问题。
MCP 服务器可以暴露一个庞大而有用的工具面,但仍然会让 Agent 在当前任务上表现更差。
失败不一定出在工具本身。它可能发生在第一次工具调用之前——当每个工具的名称、描述和输入 schema 与用户请求竞争同一份模型上下文时。
我在开发 FORMLOVA 时遇到了这个问题,它是一个表单操作的 MCP 服务器。该实验使用了一个包含 142 个工具的注册快照。截至 2026 年 8 月 13 日,FORMLOVA 的公共注册表中包含 138 个工具,因此下面的测量结果应作为该历史 142 工具快照的结果来理解。我不想仅仅为了使初始 prompt 更小而移除功能。我希望宿主机保持相同的注册表可用,但只在请求需要时才加载定义。
这听起来像是一个简单的工具搜索功能。并非如此。
实现有三个独立的层面,而结果至少需要验证四件事:
以下是架构和改变了我的想法的测量结果。
我从源码级清单开始,而不是模型账单。
FORMLOVA 源码中的共享 MCP 指令、公共工具描述和参数描述总计 107,476 个字符。除以四得到约 26,869 个 token 的粗略估计。
这个数字是有意不精确的。
它不是实时的分词器测量。它不包括生成 JSON Schema 的部分结构成本。不同宿主对定义的反序列化方式不同。Prompt 缓存可以改变计费的输入量。这个值作为下界工程信号是有用的,而不是成本声明。
重要的问题不是这个估计是否精确。重要的问题是,这么多的工具材料是否需要进入每个请求的初始模型输入。
考虑三个请求:
第一个请求可能只需要两个熟悉的定义。第二个需要发现。第三个不需要。静态的全工具 prompt 将这三种情况视为都需要整个注册表。
这就是分配问题。
这个领域的大多数困惑来自于用"工具发现"来描述不同的操作。
第一层:服务器注册表
服务器拥有工具契约。它定义名称、描述、输入 schema、注解和执行行为。
FORMLOVA 在实验的两侧仍然保留了 142 个工具。渐进式发现并不意味着删除 140 个工具然后假装系统有了改进。
第二层:MCP 协议发现
MCP 客户端使用 tools/list 从服务器检索工具定义。协议定义了注册表如何暴露。它本身并不证明特定的宿主机如何将那些定义放入模型 prompt。
这个区别很重要,因为一次成功的 tools/list 响应告诉你客户端收到了定义。它不能告诉你每个 schema 都进入了模型上下文,或者都没有。
第三层:宿主机端模型暴露
宿主机决定模型看到什么以及何时看到。
宿主机可以注册大量工具集,同时最初只暴露一个发现表面。模型在注册的工具集中搜索,宿主机返回相关工具的引用或选中的 schema,然后模型调用目标工具。
这是初始上下文真正可以改变的那一层。
服务器端辅助工具(如 search_tools)仍然有用。它可以让模型在大型产品表面中进行语义导航。但它不能证明宿主机推迟了其他定义。如果所有 schema 都已经加载,服务器端搜索是在分配之后进行导航,而不是分配本身。
我测试的候选流程是这样的:
User request
-> host exposes ToolSearch
-> model searches the registered tool set
-> host returns references to relevant tools
-> model receives selected definitions
-> model calls the exact target sequence
对于一个常见的只读 fixture,预期的原生序列是:
ToolSearch
-> list_forms
-> get_form
验证没有在"最终答案看起来正确"就停止。测试工具检查了发现是否发生、引用是否包含目标、原生调用是否按要求的顺序发生,以及未经批准的工具是否被使用。
这对于任何工具搜索评估都很重要。模型可以生成一个看似合理的句子而不执行你预期的操作。它也可以找到一个返回相似外观数据的语义相近工具。仅凭功能性输出作为契约太弱了。
如果基线有 142 个工具而候选只有 12 个,你同时在测试功能移除和渐进式发现。
我在两种条件下保持相同的注册表,并在运行前后验证了工具名称和定义摘要。变量是宿主机如何向模型暴露定义。
正式的 Phase 1 能力门覆盖了三种 prompt 类型:
每种 prompt 在基线和候选条件下重复运行。隔离校准失败并重置计数后,正式产物包含 30 个预期运行、30 个观察运行,没有遗漏或跳过的运行。
校准历史很重要。一个早期的 prompt 将原生服务器工具与宿主机的 ToolSearch 表面混淆了。另一种解释将初始化名称列表作为 schema 注入的证据。两者都被拒绝,而不是混入最终数字。
如果你无法解释一个事件证明了什么,就不要用它作为指标。
候选条件下,初始输入中位数急剧下降。
相同的 142 工具注册表保持可用。候选版在常见和长尾运行中恰好使用了一次 ToolSearch。在无工具运行中,既没有使用 ToolSearch 也没有使用原生工具。
这是人们喜欢放在标题里的结果。但这也只是一半的结果。
常见工具请求在候选条件下慢了约 1.960 秒。
可能的解释并不神秘。发现增加了开销。常见请求在原生调用之前需要一个额外的搜索和额外的轮次。更小的初始 prompt 没有抹掉这个开销。
这改变了结论。
结论不是"工具搜索让 MCP 更快"。甚至不是"工具搜索总是更好"。
结论是,渐进式发现可以在保持大型能力注册表的同时分配少得多的初始上下文,但延迟权衡取决于请求类型。
对于用户频繁调用的明显操作,小型 eager 集合可能更好。对于长尾操作,渐进式发现可能值得额外的查找代价。对于无工具问题,避免整个注册表可以同时帮助上下文和时间。
测量结果指向混合策略而不是一个全局开关。
保持小型 eager 集合
根据观察到的请求频率而不是内部重要性来选择工具。一个工具可能是产品的核心,但仍可能在对话中很少见。
eager 集合应该包含那些额外发现轮次会造成明显摩擦、且定义小而稳定的操作。
保持专用工具注册但仅在发现后才加载其定义。这保留了能力,而不让每次对话都支付相同的初始上下文成本。
让无工具请求保持无工具
发现表面不应该变成强制仪式。如果请求是概念性的,Agent 应该不搜索注册表就直接回答。
保持静态回退
不是每个宿主机都支持延迟加载或暴露足够的证据来验证它。服务器应该继续与静态加载定义的客户端兼容工作。渐进式发现是宿主机的能力,而不是破坏协议兼容性的理由。
至少为每个条件记录以下内容:
registry tool count
tool-name digest
tool-definition digest
discovery call count
discovery result references
native tool names and order
functional assertions
safety assertions
initial input tokens
total input tokens
provider latency
host end-to-end latency
turn count
不要将提供商延迟与原生工具执行小计合并。不要从仅证明注册发生的 UI 事件推断 schema 注入。不要将校准失败的 prompt 计为有效性能运行。
最重要的是,区分常见、长尾和无工具请求。聚合平均值可以隐藏你真正需要做出的权衡。
工具注册表是能力清单。初始 prompt 是内存分配决策。
MCP 给服务器一种标准方式来暴露工具,但 Agent 体验取决于宿主机如何处理这些定义。大型服务器需要好的工具契约、好的发现机制,以及所选工具仍然正确执行的证据。这些都不能相互替代。
完整的 FORMLOVA 测量结果(包括源码级占用空间、宿主机边界和限制)见 More Connections Do Not Always Make AI Smarter。
如果你正在构建一个大型 MCP 服务器,在将渐进式发现变成全局策略之前,我建议测试一个常见请求、一个长尾请求和一个无工具请求。令人不安的数字可能是最有用的。
我构建了 FORMLOVA。本文中的测量结果来自 FORMLOVA 2026 年 7 月 29 日的源码审计和正式的 Claude Phase 1 能力门。它们是针对记录在案的注册表、prompt、模型和宿主机配置的条件结果,而不是每个 MCP 实现的基准。
Model Context Protocol architecture
Understanding MCP servers
Claude Code: scale with MCP Tool Search