MCP Client 规模化设计Progressive Discovery、Prompt Cache 与 Code ModeMCP 从入门到工程实践系列第 8 篇共 9 篇。本文以 MCP2026-07-28Client Best Practices 为基线。最小 MCP Client 可以每轮执行tools/list ↓ 把全部 Tool Definitions 交给模型 ↓ 模型选择 Tool ↓ Tool Result 全部回到模型 Context只有两个 Weather Tool 时完全合理。但 Host 一旦连接 GitHub、Slack、Salesforce、Database、Logging 等几十个 Server聚合几百甚至几千个 Tool就会遇到两个独立问题Tool Schema 太多模型还没读用户问题Context 已经被定义占据中间数据太多日志、列表和循环结果不断在模型与 Host 之间往返。官方 Best Practices 分别用两类思路处理Progressive Tool Discovery减少模型一次看到的 Tool DefinitionProgrammatic Tool Calling / Code Mode减少模型亲自读取的中间结果。这两者可以组合但解决的不是同一个瓶颈。一、先区分 Registry 与 Model Context最关键的认识是Host 已通过tools/list知道全部工具不等于模型当前上下文已经看见全部工具。MCP Servers ↓ tools/list Host Tool Registry / Cache ↓ 只注入少量必要定义 Model ContextHost 可以在内存或持久缓存中保存 2000 个 Tool Definition但这一轮只向模型提供最相关的 5 个。因此“Client 发现 Tool”和“模型看见 Tool”是两个时刻阶段执行者得到什么Server DiscoveryMCP Client / Host全部可用 Tool 的目录Context SelectionHost当前任务应给模型的候选Tool Selection模型本次要调用的 Tool 与参数二、什么时候需要 Progressive Discovery工具少时直接全量注入最简单。官方页面给出的经验信号是当 Tool Definitions 占到 Context Window 约 1%5% 时可以考虑 Progressive Discovery。这不是 Protocol Rule也不是所有模型的固定阈值。实际应测量Schema Token 数First-token LatencyTool Selection Accuracy错选和漏选率Provider Prompt Cache Hit搜索本身带来的额外延迟。官方图用“全量约 150K Token、按需发现约 2K Token”的极端示例说明量级数字不是性能承诺。三、Catalog、Inspect、Execute 三层一种清晰设计是把 Tool 使用拆成三层。1. Catalog搜索候选能力search_tools({query:update salesforce record})返回简短候选[{name:salesforce_updateRecord,description:Update fields on a Salesforce object},{name:salesforce_upsertRecord,description:Insert or update based on external ID}]这一阶段只需要 Name、简短 Description、Server 来源和权限标签不必加载所有 JSON Schema。2. Inspect读取完整 Schemaget_tool_details({name:salesforce_updateRecord})返回该 Tool 的Input SchemaOutput Schema详细说明Server 来源权限、风险和可用状态。3. Execute执行真实 Tool模型知道精确参数后再执行目标 Tool。底层仍由 Host 路由到正确 MCP Client并发出tools/call。search_tools和get_tool_details通常是 Host 自己提供给模型的 Meta-tool不是 MCP Core 新增的强制 Method。Host 对 Server 的协议操作仍然是tools/list和tools/call。四、Tool Catalog 怎样搜索策略优点局限Keyword / BM25 / Regex简单、便宜、可解释同义表达容易漏召回Embedding / Vector Search能处理语义相似需要 Embedding 和索引维护小模型选择能综合复杂 Description多一次模型成本和延迟Provider Tool Search集成方便依赖 Provider 能力Hybrid可结合关键词、向量、权限与重排实现复杂度更高工具目录只有几百或几千项时不一定需要大型 Vector Database进程内索引、SQLite FTS 或小型向量索引也可能足够。无论使用哪种检索权限过滤都应在候选进入模型前进行。模型不应看到当前用户无权调用的敏感 Tool再依赖它“自觉不选”。五、它和知识库 Search Tool 有什么区别这是最容易混淆的地方。名称搜索对象返回内容search_tools能力目录候选 Toolget_tool_details某个 Tool 的定义Schema 与说明search_knowledge_base文档、记录、业务数据相关内容片段call_tool不搜索执行精确 Tool真实业务结果例如用户问“公司软件退款期限”search_tools(查询公司制度) → 找到 search_knowledge_base get_tool_details(search_knowledge_base) → 得到 query、topK 等参数 Schema call_tool( namesearch_knowledge_base, args{query: 软件退款期限, topK: 5} ) → 返回制度文档片段第一层搜的是“哪个能力能解决问题”第二层真正的知识库 Tool 搜的是“业务数据里有哪些答案”。call_tool本身也不会替模型决定调用谁。模型通常先找到精确 Name再把 Name 和 Arguments 交给统一入口Host 负责映射、Schema Validation、Authorization 和执行。六、两种 Progressive Discovery 执行设计找到真实 Tool 后有两种常见做法。设计 A动态加入真实 Tool发现 salesforce_updateRecord ↓ 把完整 Schema 加入 Provider tools Array ↓ 模型原生调用 salesforce_updateRecord优点Provider 直接看到专属 Schema参数约束强模型使用标准 Tool Calling 机制。缺点ProvidertoolsArray 会变化可能降低 Prompt Cache Hit动态增删 Tool 需要管理 Conversation 一致性。设计 B保持稳定 Meta-tool模型始终只看到[search_tools, get_tool_details, call_tool]最终统一调用{name:call_tool,arguments:{name:salesforce_updateRecord,args:{recordId:123,fields:{phone:13800000000}}}}优点Provider 的 Tools Array 很稳定适合大规模、动态 Tool CatalogPrompt Prefix 更容易复用。缺点Provider 只看到通用args: objectHost 必须按真实 Schema 再校验模型更可能写错 Tool Name 或 Arguments通用 Meta-tool 可能削弱 Provider 原生 Tool Validation 的价值。设计 B 不是“call_tool自动搜索并决定调用谁”。它只是精确执行入口。七、Dynamic Server Management除了减少每台 Server 的 Tool还可以按需连接 Server。Host 先保存一个轻量 Registry[{name:github,description:管理 Repository、Issue 和 Pull Request},{name:salesforce,description:访问和修改 CRM 数据}]运行策略只连接最少的 Always-on Server当前任务需要时再连接目标 Server在合适的 Conversation Boundary 断开Skill 可以声明依赖哪些 MCP Server触发时再连接。断开 Server 不只是关闭 Socket还要从 Search Catalog 排除其 Tool把关联 Definition 和 Cache 标为 Stale阻止新 Call妥善处理正在运行的 Request向模型返回明确的 unavailable 状态。尽量在 Conversation Boundary 调整工具集合可降低一轮对话中 Provider Tools Prefix 大幅变化带来的混乱。八、Progressive Discovery 的实现清单Guideline实现方式Multiple detail levels支持 Name-only、Name Description、Full SchemaCache definitionsHost 保存tools/list结果避免重复拉取Refresh on change收到list_changed后将旧目录标为 Stale重新 List 和建索引Group by serverCatalog 保留来源便于理解和路由Namespace names如github__create_issue避免跨 Server 重名Permission-aware search搜索前按用户、租户和授权上下文过滤公开名称可加 NamespaceRegistry 内保留原始 Server Tool Name。真正执行时再映射回来模型看到 github__create_issue ↓ Registry 找到 GitHub Client ↓ 映射回 create_issue ↓ MCP tools/call九、三个容易混淆的“缓存”1. Host Tool CacheHost 保存tools/list返回的 Tool Definition避免频繁请求 Server并用于搜索和路由。在2026-07-28版本中tools/list、prompts/list、resources/list、resources/readResult 可以携带ttlMs结果在多长时间内可视为 FreshcacheScope是否可跨 User/Authorization Context 复用。TTL 不是绝对保证。若收到对应list_changedNotification相关 Cache 应在 TTL 到期前立即变为 Stale。若 Server 给出保守 TTL 或 Private ScopeClient 应按不可跨上下文复用处理。2. Model Context这是当前模型请求真正携带的 Tool Definitions。Progressive Discovery 直接减少的是这一部分。3. Provider Prompt CacheModel Provider 可能缓存 Prompt Prefix其中可能包含toolsArray。频繁增加、删除、修改或重排定义会改变 Prefix 并导致 Cache Miss。三者关系MCP Server Tool Catalog ↓ Host Cache Host 内部已知的全部工具 ↓ 选择性注入 Model Context ↓ Provider 可能缓存相同 Prefix Prompt CacheHost 缓存了 1000 个 Tool不等于模型看到 1000 个Provider 命中 Prompt Cache也不等于 Host 的 Server Definition 一定仍然 Fresh。十、“追加保持前缀稳定”到底能做到什么初始 Provider Tool Array[search_tools, get_tool_details, github_list_prs]新增 Tool 时只追加[search_tools, get_tool_details, github_list_prs, slack_send_message]已有 Prefix 和顺序得以保留更可能复用 Provider Prompt Cache。但如果前面的github_list_prs已失效就不可能靠“只追加”永远维持正确列表。常见策略是执行层立即禁用即使旧 Definition 暂时还在 Context也不得继续执行从 Search Catalog 排除新的发现不会再选到在 Conversation Boundary 整理 Array接受相应 Cache Miss若存在安全风险立即从 Context 删除正确性和安全性优先。如果模型在过渡期仍调用旧 ToolHost 应返回明确的 unavailable/stale Error。不能为了 Prompt Cache Hit让已经撤销的能力继续工作。“前缀稳定”只是一种新增和排序策略不是永不删除的承诺。十一、第二个瓶颈大量中间结果即使只让模型看到两个相关 Tool也可能出现模型调用 logging_getLogs ↓ 10,000 条日志进入模型 Context ↓ 模型阅读、过滤、去重 ↓ 模型逐次调用 ticketing_createIssue传统 Tool Loop 的每一步都需要Model → Host → Tool → Host → Model如果任务主要是循环、筛选、排序、去重或把一个 Tool 的结果传给另一个 Tool大量中间数据进入模型 Context 既昂贵也容易干扰推理。十二、Programmatic Tool Calling / Code ModeCode Mode 让模型生成一段调用 Tool 的程序而不是每次只生成一个 Tool Callconstlogsawaitlogging_getLogs({level:error,since:Date.now()-3600000});constuniquenewMap();for(constlogoflogs.entries){if(!unique.has(log.message)){unique.set(log.message,log);}}for(constlogofunique.values()){awaitticketing_createIssue({title:log.message,body:log.stackTrace,priority:high});}console.log(Filed unique.size tickets from logs.entries.length logs);10,000 条日志留在 Sandbox 内模型只看到最终摘要。这不会让模型绕开 MCP。Sandbox 中的函数只是 Host 生成的 Stub真正执行仍要经过 Host Broker 和 MCPtools/call。十三、Typed API 与字段来源Host 可以根据 MCP Schema 生成 Sandbox Function Stubfunctionlogging_getLogs(input:{level:error|warn|info;since:number;}):Promise{entries:LogEntry[]}inputSchema生成入参类型outputSchema生成准确返回类型缺少outputSchema时只能退化为any、string或额外提取。如果只在循环之外偶尔需要结构化结果Host 也可以提供extract(value, ExpectedType)把非结构化结果交给小模型抽取再按 Expected Type 校验。但这会增加延迟而且模型可能遗漏或幻觉字段。因此更根本的方案仍是推动 Server 提供可靠outputSchema。这和 Weather Server 的问题本质相同开发者不能猜properties.forecastCode Mode 也不能猜某个 Tool Result 一定有entries。字段必须来自上游 API Contract 或 Tool Output Schema。十四、Code Mode 的三层架构Model ↓ 生成代码 Sandbox ↓ 调用 Host 注入的 Typed Function Stub Host Broker ↓ MCP tools/call MCP ServersModel模型负责生成控制逻辑但不直接拿 Credential也不直接访问 Server 网络。SandboxSandbox执行模型生成的代码默认没有直接网络只能调用 Host 注入的函数限制 CPU、Memory、Time、Call Count 和 Output保存中间数据只把必要的最终结果交回模型。官方页面列出的 Runtime 只是候选示例不代表统一背书生成代码语言Runtime / Library关注点JavaScriptDeno、isolated-vmV8 权限与隔离PythonMontyexperimental面向 AI 的精简 InterpreterTypeScriptpctxearly-stageCode Mode LibraryWasm 路径WasmtimeCapability-based Isolation选择时应评估模型擅长的语言、Host 技术栈、隔离强度、启动成本、Library Maturity 和可观测性而不是看到一个名字就直接用于生产。Host BrokerBroker 才是受信任执行边界拦截 Sandbox Function Call查 Registry定位 MCP Server 与 Client按真实 Schema 校验 Arguments检查用户、租户与 Tool 权限必要时请求用户确认持有 Credential发出tools/call把 Result 或 Error 返回 Sandbox。十五、Code Mode 的安全边界Code Mode 引入了“模型生成代码”这一攻击面至少要处理1. 每个 Tool Call 单独授权批准执行脚本不等于批准脚本内部的所有操作。创建 Issue、删除文件、发送消息等有副作用的动作仍应按策略逐项授权或批量明确授权。2. Sandbox 默认禁止直接网络否则生成代码可能绕过 Broker把数据发送到未知地址自行调用未经批准的 API直接使用错误或泄露的 Credential。3. Credential 只保存在 HostSandbox 看到的是函数能力不应拿到 OAuth Token、API Key 或 Server Secret。4. 跨 Server 数据仍是不可信输入一个 Server 的 Result 传给另一个 Server并不会自动变成可信数据。Host 仍需防范 Prompt Injection、恶意字段、超大 Payload 和数据外泄。5. 限制资源至少设置Script TimeoutMemory LimitTool Call CountLoop/Execution BudgetConsole Output LimitResult Size Limit并发和速率限制。6. 正确转换 ErrorMCP Tool 的业务失败可能表现为协议 Request 成功但 ResultisError: true。Generated Wrapper 应把它转成 Sandbox 内可try/catch的 Exception而不是只捕获 Transport Exception。7. 处理部分成功脚本可能已创建三个 Ticket第四个失败。系统不能假设自动回滚而应报告哪些动作已完成哪一步失败是否可重试重试会不会重复产生副作用。十六、两种模式怎样组合几千个 Tool ↓ Progressive Discovery 只加载 logging_getLogs 与 ticketing_createIssue ↓ Code Mode Sandbox 内完成查询、过滤、去重和循环调用 ↓ 模型只收到最终摘要对应关系技术减少什么Progressive DiscoveryTool Definition TokenCode Mode中间 Tool Result Token 与模型往返一个解决“模型需要看到哪些能力”另一个解决“能力之间的数据怎样流动”。十七、落地决策建议可以按规模逐步演进阶段 1工具很少全量tools/list后注入使用 Provider 原生 Tool Calling优先保证正确性和可观测性。阶段 2定义开始挤占 Context建 Host Registry实现 Tool Namespace引入 Catalog/Inspect先用 Keyword/Hybrid Search只注入少量真实 Schema。阶段 3目录高度动态按需连接 Server监听list_changed引入 TTL、Scope 和 Stale 管理在动态真实 Tool 与稳定 Meta-tool 之间权衡。阶段 4中间数据与循环很大要求关键 Tool 提供outputSchema生成 Typed Stub引入隔离 Sandbox 和 Broker设置权限、资源与副作用边界。十八、常见误区误区 1Progressive Discovery 就是不调用tools/list不是。Host 仍需发现并维护目录只是不把全部 Schema 同时放进模型 Context。误区 2工具目录大就必须部署大型向量数据库不一定。目录规模、语言表达和召回要求决定索引方案小型内存或本地索引可能足够。误区 3call_tool会自动找到合适 Tool不会。它通常只按精确 Name 执行搜索和选择发生在之前。误区 4稳定 Meta-tool 与知识库 Search 是同一个东西不是。一个管理能力目录与执行入口另一个检索业务数据。误区 5为了 Prompt Cache失效 Tool 可以继续调用绝对不行。执行层必须立即禁用正确性和安全性高于 Cache Hit。误区 6Code Mode 让模型直接访问所有 Server不是。模型生成逻辑Sandbox 运行Host Broker 仍掌握 Credential、权限与 MCP 调用。误区 7脚本失败就表示什么都没发生不一定。跨 Tool 操作往往没有自动 Transaction必须追踪并报告 Partial Effects。十九、总结规模化 MCP Client 不是简单地“把更多工具给模型”而是建立清晰分层Server Registry → 哪些 Server 存在 Host Tool Catalog → 全部能力及其来源、状态、权限 Progressive Discovery → 当前模型需要看哪些定义 Provider Tool Calling / Meta-tool → 模型怎样表达执行意图 Sandbox Broker → 大量中间数据和循环怎样安全执行最终原则是Host 可以知道很多但模型每轮只需要知道足够完成当前任务的部分模型可以生成复杂逻辑但真正的权限、Credential 和执行边界必须留在 Host。系列最后一篇将把这些组件放进真实排错流程怎样使用 Inspector、stderr Log、Client DevTools、路径和协议错误码定位 MCP 故障。参考资料https://modelcontextprotocol.io/docs/2026-07-28/develop/clients/client-best-practiceshttps://modelcontextprotocol.io/docs/2026-07-28/develop/build-client