MCP 连接、发现与恢复
确认客户端角色,拆解 OAuth、工具命名、能力发现、状态合并与重连
本页解决的问题
先给结论「MCP 连接、发现与恢复」要解决的关键问题是什么?
确认客户端角色,拆解 OAuth、工具命名、能力发现、状态合并与重连
让这个结论先证明自己值得留下。 把这一页当成决策工具,而不是需要背下来的定义。把概念连到一个真实任务、一个可观察结果,以及一个能改变你判断的失败上。
写下一个问题:试完这个方法后,你能用什么证据回答它?
结论听起来很完整,却没有检查最关键的假设。
MCP:连接只是起点
真正的客户端还要完成配置合并、OAuth、能力发现、命名隔离、模型可见性控制、状态推送和断线恢复。源码将这些责任拆在 MCP crate 与 Session Actor 周边。
课程目标
核对协议角色
从调用方向判断客户端与服务端,避免把内部 Hub Server 等同于 MCP Server。
追踪可见性
解释工具如何从 tools/list 进入快照、搜索索引与模型注册表。
设计恢复状态机
把 OAuth、状态合并、客户端身份和重启退避放进同一连接生命周期。
从外部 Server 到模型工具
客户端与服务端:按源码措辞落位
McpClient 启动 stdio 或 Streamable HTTP 连接,执行初始化、list_tools 与 call_tool。Computer Hub MCP Adapter 也描述为把 MCP Server 的工具桥接进 Hub 路由。
xai-grok-workspace 的 Hub Server 属于 xAI Computer Hub 协议。当前快照未找到将 Grok Build 自身通过 MCP 传输暴露给任意 MCP Client 的入口,因此本课只确认客户端角色。
OAuth 与真实凭据落点
配置字段
oauth_client_id
oauth_client_secret_env_var
oauth_scopescrates/codegen/xai-grok-config-types/src/mcp.rs本地 JSON 文件
let path = grok_home
.join("mcp_credentials.json");
// lock + load + insert + atomic save源码采用该文件存储,并通过文件锁与原子保存处理并发写入。
crates/codegen/xai-grok-mcp/src/credentials.rs · oauth.rs工具如何获得模型可见性
server__tool
注册名由服务端名、保留分隔符 __ 和原始工具名组成。源码要求完整名称中恰好出现一次分隔符,避免解析歧义,也让两个 Server 的同名工具拥有不同 ToolId。
模型工具与 App 工具分流
禁用工具会存入 disabled_tool_registrations;model_visible 为真才进入模型侧 Tool Bridge;带 ui.resourceUri 的工具可单独进入 UI 通知。
大量 MCP 工具不必全部常驻提示词
ToolMetadataSnapshot 保存工具与服务端元数据,BM25 索引支持按 qualified name 或裸工具名精确命中,再提供搜索结果。mcp_initialized 告诉搜索层能力发现是否完成。
pub struct ToolMetadataSnapshot {
pub tools: Vec<ToolMetadata>,
pub servers: Vec<ServerMetadata>,
pub mcp_initialized: bool,
}crates/codegen/xai-grok-shell/src/session/tool_index.rs状态合并与重启保护
同键保留最新事件
mcp_dispatcher 以 (server_name, event_kind) 为键,在 50 ms tumbling window 内 last-write-wins。高频 tools/list_changed 最终只推一次 ACP 状态。
旧断线不能误删新连接
移除 dead client 前比较 client_id。如果断线事件属于已被替换的旧客户端,保持当前客户端,并丢弃过期状态。
不同传输采用不同恢复动作
stdio 自动重启使用固定退避 1s → 4s → 16s,并检查关闭中、已禁用、配置移除等护栏。HTTP 先尝试客户端内恢复,并使用独立退避。成功重连后重新发现与注册工具,随后刷新快照。
课堂练习:画出可恢复客户端
提交物
状态图与 6 条测试
- 画出配置载入、连接、OAuth、能力发现、注册、搜索和调用的状态图。
- 加入 disabled、app-only 与 model-visible 三种工具路径。
- 设计两个同名工具,验证 qualified name 可消除冲突。
- 模拟 100 条
tools/list_changed,写出 50 ms 合并后的预期通知数。 - 模拟旧客户端断线事件晚到,说明
client_id护栏如何保护新连接。 - 分别为 stdio 与 HTTP 写一条可恢复测试和一条停止重试条件。
MCP 集成的工程量集中在协议外围。命名、可见性、身份、状态合并和恢复策略共同决定一条连接能否长期稳定工作。
源码快照说明:本页依据本地 grok-build-main 的 MCP、config-types、shell session 与 computer-hub adapter 源码整理。代码片段为教学截取。关于 MCP 服务端角色的结论采用保守口径,内部 Hub Server 不作为通用 MCP Server 证据。
「MCP: 连接只是起点」如何改变一次回答
「真正的客户端还要完成配置合并、OAuth、能力发现、命名隔离、模型可见性控制、状态推送和断线恢复。源码将这些责任拆在 MCP crate 与 Session Actor 周边」说明,模型处理的不是我们眼中的“字数”,而是一段段 Token。Token 的切分方式会影响输入长度、上下文能放下多少内容,以及一次请求要花多少计算。
长度、信息量和上下文不是一回事
当「从调用方向判断客户端与服务端,避免把内部 Hub Server 等同于 MCP Server」变长时,先要分清三件事:文字被切成多少 Token、哪些内容真正参与当前判断、以及旧内容是否已经超出上下文窗口。删掉重复说明通常比单纯把窗口开得更大更有效。
- 画出配置载入、连接、OAuth、能力发现、注册、搜索和调用的状态图
- 加入 disabled、app-only 与 model-visible 三种工具路径
- 设计两个同名工具,验证 qualified name 可消除冲突
先保留会改变判断的内容
可以用「源码快照说明: 本页依据本地 grok-build-main 的 MCP、config-types、shell session 与 computer-hub adapter 源码整理。代码片段为教学截取。关于 MCP 服务端角色的结论采用保守口径,内部 Hub Server 不作为通用 MCP Server 证据」做一次对照:保留同样的问题,分别删掉重复背景、压缩格式和移除无关历史,比较答案质量、延迟与 Token 数量。
从「MCP: 连接只是起点」走到「核对协议角色」
「MCP: 连接只是起点」先把问题落在「真正的客户端还要完成配置合并、OAuth、能力发现、命名隔离、模型可见性控制、状态推送和断线恢复。源码将这些责任拆在 MCP crate 与 Session Actor 周边」上;到了「核对协议角色」,讨论继续推进到「从调用方向判断客户端与服务端,避免把内部 Hub Server 等同于 MCP Server」。两段连起来,重点就不只是记住一个结论,而是看清它成立所依赖的条件。
把这条判断带到下一个场景
处理长文本时,先保留会改变结论的内容,再决定如何压缩格式和历史;上下文更长只有在新增信息真正有用时才值得付出代价。
- 「MCP: 连接只是起点」:真正的客户端还要完成配置合并、OAuth、能力发现、命名隔离、模型可见性控制、状态推送和断线恢复。源码将这些责任拆在 MCP crate 与 Session Actor 周边
- 「核对协议角色」:从调用方向判断客户端与服务端,避免把内部 Hub Server 等同于 MCP Server
- 「最后的要点」:模拟旧客户端断线事件晚到,说明 client_id 护栏如何保护新连接
最后的「最后的要点」把讨论落到「模拟旧客户端断线事件晚到,说明 client_id 护栏如何保护新连接」。回看这条线索时,最值得保留的是:当输入、规模或风险改变,哪些判断需要重新做一遍。
我把这篇文章里的一个判断改写成了今天可以验证的小实验。比记住结论更有用的是,知道下一步要观察什么。
读完以后我先回头找它成立的条件,而不是直接把方法搬进项目。这个顺序让后面的取舍清楚很多。
如果把这个判断放到真实工作里,最先需要补的约束是什么?我想知道从阅读到第一次实践之间,哪一步最值得先做。
还没有这篇文章的讨论。