MCP 运行时、资源与会话集成
这一阶段是把 MCP(让 Codex 接外部工具和资料的协议)真正接进系统,属于运行中一直在幕后支撑的部分。配置和插件先登记哪些服务器能用,连接管理器像总接线员一样启动它们、收集工具和资源。工具整理层负责改名、去重、决定哪些给模型看。会话层在模型调用时检查参数、上传文件、必要时问用户或处理登录过期。资源工具则负责列清单、读内容。扩展层最后把这些能力和事件统一送到应用服务器。
扩展注册
这些文件注册 MCP 支持的扩展,并在运行时被其他位置使用前加载 executor-plugin 服务器声明。
app-server/src/extensions.rs源码 ↗
可以把这个文件理解成应用服务器的“扩展插线板”。服务器启动时,各个扩展不能自己乱接进来,需要统一登记、统一拿到它们需要的依赖,比如登录信息、线程管理器、环境管理器和统计客户端。thread_extensions 就是组装这些扩展的地方:它先建一个扩展注册表,然后按功能把不同插件接上去,有些功能还会看配置开关决定是否启用。另一个重点是 AppServerExtensionEventSink,它像一个消息转接员。扩展里发生了“线程目标更新”这类事件时,它优先把消息塞进当前线程监听器的队列,这样消息顺序更可靠;如果没有监听器可用,就退而求其次,直接发服务器通知。这个文件还提供守护代理的启动器,让扩展可以通过 ThreadManager 创建子代理线程。
thread_extensions44–94 ↗
fn thread_extensions(
guardian_agent_spawner: S,
dependencies: ThreadExtensionDependencies,
) -> Arc<ExtensionRegistry<Config>>
作用:把应用服务器支持的扩展一次性装进一个注册表里。有人启动服务器、需要准备扩展系统时,就会用它来完成统一接线。
数据流:进去的是一个守护代理启动器,以及一包依赖对象,比如事件出口、登录管理器、状态数据库、线程管理器、环境管理器和技能提供者。它先拆开这些依赖,创建扩展注册表构建器,然后把目标、守护代理、记忆、MCP、网页搜索、图片生成、技能等扩展逐个安装进去;目标扩展只有在有状态数据库时才会安装,并且还会看配置里的功能开关。最后出来的是一个共享的 ExtensionRegistry,也就是已经装好各种扩展的注册表。
调用关系:它处在扩展系统的启动阶段,像总装工一样把多个外部扩展模块接到同一个注册表上。它会调用各个扩展包自己的 install 或 install_with_backend 函数,把具体功能交给对应扩展模块实现。
调用图:调用 2 个内部函数(new, new);外部调用 11 个(new, with_event_sink, install_with_backend, install, install, install, install_executor_plugins, install, global, install_with_providers (+1 more))。
app_server_extension_event_sink96–104 ↗
fn app_server_extension_event_sink(
outgoing: Arc<OutgoingMessageSender>,
thread_state_manager: ThreadStateManager,
) -> Arc<dyn ExtensionEventSink>
作用:创建一个扩展事件接收器。扩展产生事件后,需要有一个地方把这些事件转成应用服务器内部或对外的通知,这个函数就是做这个接收器的工厂。
数据流:进去的是对外发消息用的 OutgoingMessageSender,以及记录线程监听状态的 ThreadStateManager。它把这两个对象放进 AppServerExtensionEventSink 里,再包装成共享对象返回。出来的是一个 ExtensionEventSink 接口对象,扩展系统只需要把事件交给它,不必知道里面怎么转发。
调用关系:它给扩展注册系统提供事件出口。在测试 app_server_event_sink_uses_listener_fifo_for_goal_updates_and_clears 里会直接创建这个接收器,验证它处理目标更新事件时会先走线程监听队列。
调用图:被 1 处调用(app_server_event_sink_uses_listener_fifo_for_goal_updates_and_clears);外部调用 1 个(new)。
AppServerExtensionEventSink::emit112–150 ↗
fn emit(&self, event: Event)
作用:接收扩展发来的事件,并把服务器认识的事件送到合适的地方。目前它重点处理“线程目标更新”,其他不支持的事件会被记录后丢弃。
数据流:进去的是一个 Event,里面有事件编号和事件内容。它先看事件内容是不是 ThreadGoalUpdated,也就是某个线程的目标发生变化;如果是,就取出线程编号、轮次编号和目标内容,先询问 ThreadStateManager 当前这个线程有没有监听器队列。有的话,它把更新命令送进这个队列,成功后就结束;如果没有队列,或队列已经关了,它会另起一个异步任务,通过 OutgoingMessageSender 发出 ServerNotification。出来没有直接返回值,但它可能改变监听器队列里的消息,或触发一条对外服务器通知。
调用关系:它是扩展事件进入应用服务器后的转接点。扩展系统调用它的 emit 后,它要么把事情交给当前线程监听器处理,要么交给 outgoing 发送服务器通知;遇到不能处理的事件,它只写调试日志,避免错误事件影响主流程。
调用图:调用 1 个内部函数(current_listener_command_tx);外部调用 5 个(clone, ThreadGoalUpdated, spawn, debug!, warn!)。
guardian_agent_spawner153–169 ↗
fn guardian_agent_spawner(
thread_manager: Weak<ThreadManager>,
) -> impl AgentSpawner<StartThreadOptions, Spawned = NewThread, Error = CodexErr>
作用:生成一个“守护代理启动器”。守护代理需要从现有线程分叉出子代理线程时,会通过这个启动器去找 ThreadManager 干活。
数据流:进去的是一个弱引用形式的 ThreadManager。弱引用可以理解成“不强行留住对象的指针”,如果线程管理器已经没了,就不能再启动代理。函数返回一个闭包;以后这个闭包收到原线程编号和启动选项时,会先尝试把弱引用升级成可用的 ThreadManager,失败就返回错误,成功就调用 spawn_subagent 创建子代理线程。出来的是一个符合 AgentSpawner 接口的启动器,真正执行时会产出 NewThread 或 CodexErr。
调用关系:它通常被 thread_extensions 传给守护代理扩展安装逻辑。守护代理扩展不直接知道 ThreadManager 的细节,只拿到这个启动器;等它需要开子线程时,再通过启动器把活交给 ThreadManager。
tests::app_server_event_sink_uses_listener_fifo_for_goal_updates_and_clears185–229 ↗
async fn app_server_event_sink_uses_listener_fifo_for_goal_updates_and_clears()
作用:这是一个测试,检查扩展目标更新事件会不会按顺序进入线程监听器队列。它防止以后改代码时,把消息顺序或清除消息的位置弄乱。
数据流:测试先造一个假的 outgoing 通道、一个 ThreadStateManager、一个线程编号和一个监听器命令队列。它把监听器登记到线程状态管理器里,然后创建事件接收器,连续发两个目标更新事件,再手动发一个清除目标的命令。之后它从监听器队列里取三条消息,确认顺序正好是 turn-1、turn-2、cleared。测试本身不产生业务结果,但会用断言证明队列先进先出的行为没有坏掉。
调用关系:它调用 app_server_extension_event_sink 创建被测对象,也调用 tests::thread_goal_updated_event 构造测试事件。它模拟扩展系统向 AppServerExtensionEventSink::emit 发事件,验证 emit 会优先使用监听器队列,而不是直接走对外通知。
调用图:调用 5 个内部函数(disabled, app_server_extension_event_sink, new, new, default);外部调用 9 个(new, from_secs, new, thread_goal_updated_event, assert_eq!, channel, unbounded_channel, panic!, timeout)。
tests::thread_goal_updated_event231–249 ↗
fn thread_goal_updated_event(thread_id: ThreadId, turn_id: &str) -> Event
作用:这是测试用的小工具,用来快速造一个“线程目标已更新”的事件。这样测试不用每次手写一大坨事件字段。
数据流:进去的是线程编号和轮次编号字符串。它把这些值填进 Event 结构里,并附上一个固定的测试目标,比如目标描述、状态、预算、已用 token 和时间戳。出来的是一个完整的 Event,内容类型是 ThreadGoalUpdated。
调用关系:它只服务于同文件里的测试 app_server_event_sink_uses_listener_fifo_for_goal_updates_and_clears。测试用它造出两条目标更新事件,再交给 AppServerExtensionEventSink::emit 来验证转发顺序。
调用图:外部调用 1 个(ThreadGoalUpdated)。
ext/mcp/src/lib.rs源码 ↗
MCP 可以理解成一种让程序和外部工具/服务对话的协议。这个文件像一个“登记处”:启动时,系统会问它有哪些 MCP 服务器要加入。它先看配置里的 Apps 功能开关,如果没开,就明确要求移除名叫 Codex Apps 的 MCP 服务器;如果开了,就按当前的 ChatGPT 基础地址和产品 SKU 生成一份服务器配置并交回去。除此之外,它还提供两个安装函数:一个安装内置的 hosted plugin runtime 贡献者,另一个安装“当前线程选中的执行器插件”贡献者。最后还有一个初始化函数,用来提前准备每个线程自己的插件快照,避免后面发现插件 MCP 服务时拿不到上下文。
HostedPluginRuntimeExtension::id15–17 ↗
fn id(&self) -> &'static str
作用:给这个 MCP 贡献者取一个固定名字:hosted_plugin_runtime。系统用这个名字识别它,就像给登记员挂一个工牌。
数据流:进去的是这个贡献者本身,不需要读取外部数据 → 它直接返回一段固定文本 → 出来的是标识符 "hosted_plugin_runtime",不改动任何状态。
调用关系:这是扩展系统识别该贡献者时会用到的小函数。它不把工作交给别人,只提供一个稳定的名字,方便注册和排查。
HostedPluginRuntimeExtension::contribute19–38 ↗
fn contribute(
&'a self,
context: McpServerContributionContext<'a, Config>,
) -> ExtensionFuture<'a, Vec<McpServerContribution>>
作用:根据当前配置决定 Codex Apps 的 MCP 服务器应该被添加还是移除。有人会在收集 MCP 服务器配置时调用它。
数据流:进去的是一个贡献上下文,里面能拿到全局配置 → 它读取配置里的功能开关、ChatGPT 基础地址和 Apps MCP 产品 SKU → 如果 Apps 功能没开,就输出一个“移除这个服务器”的贡献;如果开了,就生成并输出一个“设置这个服务器配置”的贡献。它本身不直接启动服务器,只告诉系统该怎么登记。
调用关系:扩展框架在发现 MCP 服务器时会调用它。它先从 context.config() 拿配置;需要添加服务器时,会调用 hosted_plugin_runtime_mcp_server_config 生成真正的服务器配置;最后把结果包装成 McpServerContribution 返回给框架。
install41–43 ↗
fn install(builder: &mut ExtensionRegistryBuilder<Config>)
作用:把 HostedPluginRuntimeExtension 注册到扩展登记表里。没有这一步,系统就不会来问它要不要启用 Codex Apps 的 MCP 服务器。
数据流:进去的是一个可修改的 ExtensionRegistryBuilder,也就是扩展登记表的建造器 → 它创建一个 HostedPluginRuntimeExtension,并用 Arc 包起来共享使用 → 出来没有普通返回值,但 builder 被改动了:多了一个 MCP 服务器贡献者。
调用关系:启动或装配扩展时会调用这个函数。它把新建的 HostedPluginRuntimeExtension 交给 builder.mcp_server_contributor,后续 MCP 发现流程才会调用 HostedPluginRuntimeExtension::id 和 HostedPluginRuntimeExtension::contribute。
调用图:调用 1 个内部函数(mcp_server_contributor);外部调用 1 个(new)。
install_executor_plugins46–53 ↗
fn install_executor_plugins(
builder: &mut ExtensionRegistryBuilder<Config>,
environment_manager: std::sync::Arc<codex_exec_server::EnvironmentManager>,
)
作用:把“执行器插件里声明的 MCP 服务器发现逻辑”注册进去。这样,用户或线程选中的执行器插件也能贡献自己的 MCP 服务。
数据流:进去的是扩展登记表建造器,以及一个 EnvironmentManager(环境管理器,用来了解执行环境)→ 它用这个环境管理器创建 SelectedExecutorPluginMcpContributor → 然后把这个贡献者注册到 builder 里 → 出来没有普通返回值,但登记表增加了一类新的 MCP 发现来源。
调用关系:这个函数通常也在扩展装配阶段被调用。它不自己查找插件,而是把环境管理器交给 executor_plugin::SelectedExecutorPluginMcpContributor::new 创建专门的贡献者,再交给 builder.mcp_server_contributor,让后续 MCP 发现流程统一调度。
调用图:调用 2 个内部函数(mcp_server_contributor, new);外部调用 1 个(new)。
initialize_executor_plugin_thread_data56–60 ↗
fn initialize_executor_plugin_thread_data(
thread_init: &mut codex_extension_api::ExtensionDataInit,
)
作用:为“按线程选择执行器插件”这件事提前准备线程级数据。通俗说,就是给每条工作线先放好一份它该看到的插件信息。
数据流:进去的是 ExtensionDataInit,也就是扩展系统初始化每个线程数据时用的对象 → 它把这个对象交给 executor_plugin::seed_thread_state → 出来没有普通返回值,但线程相关的插件快照初始化逻辑被种进去,后面发现执行器插件 MCP 服务时可以读取。
调用关系:它应该在扩展数据初始化阶段调用,早于真正发现插件 MCP 服务器。它把具体工作交给 seed_thread_state;之后 install_executor_plugins 注册的贡献者才能依赖这些线程数据来判断当前线程选中了哪些插件。
调用图:调用 1 个内部函数(seed_thread_state)。
ext/mcp/src/executor_plugin/provider.rs源码 ↗
MCP 可以简单理解成一套“插件把工具告诉主程序”的协议。这个文件像一个门卫:先看插件清单里有没有指定 MCP 配置文件;没有的话,就去插件根目录找默认的 .mcp.json。它通过执行器提供的文件系统去读文件,这样读文件仍然受执行环境控制。默认文件不存在时不算错,只表示插件没有声明 MCP 服务;但如果插件明确指定的文件读不了,就会报清楚是哪个插件、哪个路径出问题。读到内容后,它交给解析器变成真正的服务器配置。解析中发现单个服务写坏了,会记警告并跳过。最后它只留下 stdio(标准输入输出管道,像两根线一样和子进程说话)的 MCP 服务;HTTP 形式的服务会被忽略并警告。
ExecutorPluginMcpProvider::load42–49 ↗
async fn load(
&self,
plugin: &ResolvedExecutorPlugin,
) -> Result<Vec<(String, McpServerConfig)>, ExecutorPluginMcpProviderError>
作用:这是给外部流程调用的入口,用来从一个已经选中的执行器插件里加载 MCP 服务声明。调用者不用关心配置文件在哪、怎么读,只拿到可用的服务器配置列表或明确的错误。
数据流:进去的是一个 ResolvedExecutorPlugin,也就是已经确定位置和执行环境的插件。函数从插件里取出真实插件信息、插件根目录,以及执行器提供的文件系统,然后把这些交给 load_from_file_system。出来的是一组“服务名 + MCP 配置”;如果读文件或解析失败,就把错误原样带回给调用者。
调用关系:它被 resolve_snapshot 调用,通常发生在系统整理插件快照、决定当前可用能力的时候。它自己不做复杂判断,而是先向 plugin 和 file_system 取到必要材料,再把真正的读取、解析、筛选工作交给 load_from_file_system。
调用图:调用 3 个内部函数(file_system, plugin, load_from_file_system);被 1 处调用(resolve_snapshot)。
load_from_file_system52–116 ↗
async fn load_from_file_system(
plugin: &ResolvedPlugin,
plugin_root: &AbsolutePathBuf,
file_system: &dyn ExecutorFileSystem,
) -> Result<Vec<(String, McpServerConfig)>, ExecutorPluginMcpP
作用:这个函数是真正干活的地方:找到插件的 MCP 配置文件,读出来,解析成配置,并过滤掉当前不接受的服务类型。它保证“没有默认配置文件”不会把正常插件变成错误。
数据流:进去的是插件信息、插件根目录,以及一个能读文件的 ExecutorFileSystem。它先查看插件清单:如果清单指定了 MCP 配置路径就用那个,否则拼出默认的 .mcp.json;接着把本地路径变成文件 URI,再调用 read_file_text 读取文本。读不到默认文件时直接返回空列表;其他读取失败会变成 ReadConfig 错误。读成功后,它用 parse_plugin_mcp_config 解析内容,把无效服务记警告并丢掉,最后只输出 stdio 类型的服务配置,HTTP 类型会被警告并忽略。
调用关系:它只由 ExecutorPluginMcpProvider::load 调用,是加载链路里的核心工序。它会向插件对象询问 location、manifest、selected_root_id 等信息,用 join 拼默认路径,用 from_abs_path 转成可读的 URI,再把文件内容交给 MCP 配置解析器;最终把整理好的结果交还给 load。
调用图:调用 7 个内部函数(read_file_text, location, manifest, selected_root_id, as_path, join, from_abs_path);被 1 处调用(load);外部调用 3 个(new, parse_plugin_mcp_config, warn!)。
MCP 运行时基础
这些文件定义 MCP crate 接口、运行时服务器/配置模型、Apps 专用行为、连接管理,以及基于刷新后管理器的会话范围资源客户端。
codex-mcp/src/lib.rs源码 ↗
这个文件本身不做具体工作,也没有函数。它更像一家商场的一楼导览牌:真正的店铺在各个模块里,比如连接 MCP 服务器、读取资源、处理 OAuth 登录、管理工具列表、解析插件配置等;而这里负责告诉外部“哪些东西可以从这个库直接拿来用”。如果没有它,使用这个库的人就得知道内部每个模块的具体路径,既麻烦又容易依赖到不该依赖的内部细节。这里用 pub use 把重要类型和函数重新导出,比如连接管理器、资源客户端、服务器配置、认证状态、权限自动批准判断等;同时用 mod 声明这些模块确实属于这个库。带 pub(crate) 的模块只允许本库内部使用,说明作者有意把内部零件和公开接口分开,避免外部代码随便碰内部实现。
codex-mcp/src/server.rs源码 ↗
MCP 可以理解成一种让程序接入外部工具或服务的协议。这个文件不真正去启动服务器,而是把“服务器配置”包装成运行时可用的形态,并提取一些启动后还必须记住的信息。比如:这个服务器是通过本地标准输入输出连接,还是通过 HTTP 地址连接;它支持不支持并行调用工具;某个工具调用前默认要不要审批。这样做的好处是,后面的连接、诊断、统计、权限判断都不用反复翻原始配置。主要部件有三个:EffectiveMcpServer 表示最终生效的服务器;McpServerOrigin 记录连接来源;McpServerMetadata 保存启动后仍然重要的语义信息。它像给每台外接设备贴标签:设备怎么接、能不能用、用它前要不要请示,都写清楚。
EffectiveMcpServer::configured20–24 ↗
fn configured(config: McpServerConfig) -> Self
作用:把一份用户配置好的 MCP 服务器配置,包装成程序运行时使用的服务器对象。有人想把配置正式纳入“生效服务器列表”时会用它。
数据流:进去的是一份 McpServerConfig,也就是服务器的原始配置;函数把它装进 Configured 这个启动方式里,并用 Box 包起来放到堆上保存;出来的是一个 EffectiveMcpServer,表示这台服务器已经成为运行时可识别的对象。
调用关系:测试里会用它构造服务器样本,比如验证本地标准输入输出服务器失败时是否还能保留 HTTP 服务器,以及验证工具审批策略能不能保存下来。它本身不继续调用本项目里的其他函数,只负责把配置打包。
调用图:被 2 处调用(no_local_runtime_fails_local_stdio_but_keeps_local_http_server, server_metadata_preserves_tool_approval_policy);外部调用 2 个(new, Configured)。
EffectiveMcpServer::launch26–28 ↗
fn launch(&self) -> &McpServerLaunch
作用:取出这台服务器的启动方式。其他代码需要知道它是从配置来的、该按什么方式处理时,会问这个函数。
数据流:进去的是一个已经存在的 EffectiveMcpServer;函数只读取里面的 launch 字段,不修改任何东西;出来的是对 McpServerLaunch 的引用,让调用者继续判断具体启动策略。
调用关系:创建真正的 MCP 客户端时会读取它;把服务器转成 McpServerMetadata 时也会读取它。它相当于打开服务器档案袋,但不改档案内容。
调用图:被 2 处调用(make_rmcp_client, from)。
EffectiveMcpServer::configured_config30–34 ↗
fn configured_config(&self) -> Option<&McpServerConfig>
作用:如果这台服务器确实来自配置文件,就把那份配置拿出来给别人看。这样调用者可以安全地访问配置,而不用自己拆内部结构。
数据流:进去的是一个 EffectiveMcpServer;函数检查它的启动方式,发现是 Configured 就返回配置引用;如果以后有别的启动方式,这里可以返回空值。当前代码里配置型服务器会得到 Some 配置引用,不会改动原对象。
调用关系:构建更上层对象的新建流程会调用它,用来读取配置细节。它和 launch 类似,都是对外提供受控访问,不让外部代码直接碰内部字段。
调用图:被 1 处调用(new)。
EffectiveMcpServer::enabled36–40 ↗
fn enabled(&self) -> bool
作用:告诉别人这台 MCP 服务器现在是不是启用状态。这样系统可以跳过被关掉的服务器,避免白白尝试连接。
数据流:进去的是 EffectiveMcpServer;函数读取内部配置里的 enabled 标记;出来的是 true 或 false,表示启用或不启用,不改变任何数据。
调用关系:它是一个小的判断入口,供后续调度或连接流程决定是否要考虑这台服务器。当前调用图里没有本地调用者,但它暴露的是运行时常用的状态问题。
EffectiveMcpServer::required42–46 ↗
fn required(&self) -> bool
作用:告诉别人这台服务器是不是“必须成功”的服务器。如果它是必需的,启动失败通常就不能当作普通小问题忽略。
数据流:进去的是 EffectiveMcpServer;函数读取配置里的 required 标记;出来的是一个布尔值,表示这台服务器是否必需,不修改对象。
调用关系:它给启动和错误处理流程提供决策依据:普通服务器失败可能只是少一个能力,必需服务器失败则可能要让整个流程报错或停止。
McpServerOrigin::as_str57–62 ↗
fn as_str(&self) -> &str
作用:把服务器来源转换成一段可打印、可记录的文字。日志、统计或诊断信息需要展示来源时会用它。
数据流:进去的是一个 McpServerOrigin;如果来源是本地标准输入输出,就返回字符串 stdio;如果来源是 HTTP,就返回保存好的站点来源字符串;函数不分配新对象,也不修改数据。
调用关系:它位于诊断和指标展示这一层,帮助把内部枚举值变成人能看懂或系统能记录的文本。
McpServerOrigin::from_transport64–72 ↗
fn from_transport(transport: &McpServerTransportConfig) -> Option<Self>
作用:根据服务器的连接方式推断它的来源。这样启动之后,即使不再看完整配置,也还能知道服务器大概是从哪里接进来的。
数据流:进去的是 McpServerTransportConfig,也就是连接方式配置;如果是 HTTP,它会解析 URL,并只保留协议、域名、端口这类“来源”信息;如果是标准输入输出,就记为 Stdio;如果 HTTP 地址解析失败,就返回空值。
调用关系:McpServerMetadata::from 会调用它,把原始连接配置变成更轻量的来源信息。它依赖 URL 解析功能来保证 HTTP 来源不是随便截字符串,而是按网址规则得到。
McpServerMetadata::tool_approval_mode86–92 ↗
fn tool_approval_mode(&self, tool_name: &str) -> AppToolApproval
作用:查询某个工具调用前应该采用什么审批方式。它用来决定是可以直接调用,还是需要先问用户。
数据流:进去的是工具名;函数先查这个工具有没有单独设置审批模式,有就用单独设置;没有就看服务器的默认审批模式;再没有就使用系统默认值;出来的是最终的 AppToolApproval,不修改任何配置。
调用关系:它服务于真正发起工具调用前的权限判断。可以把它理解成查公司报销规则:先看这个项目有没有特殊规定,没有再看部门默认规定,最后用公司通用规定。
McpServerMetadata::from96–114 ↗
fn from(server: &EffectiveMcpServer) -> Self
作用:从一台生效的 MCP 服务器中提取启动后还必须保留的关键信息。这样后续流程不用拿着完整配置,也能做诊断、并行能力判断和工具审批判断。
数据流:进去的是 EffectiveMcpServer;函数读取它的启动方式,再从配置里取出是否污染记忆、连接来源、是否支持并行工具调用、默认审批模式,以及每个工具自己的审批模式;出来的是 McpServerMetadata,其中工具审批表会整理成按工具名查找的映射表。
调用关系:上层新建流程和相关测试会调用它。它会先通过 EffectiveMcpServer::launch 打开服务器启动信息,再把 transport 交给 McpServerOrigin::from_transport 推断来源,最后组装成后续运行时使用的元数据。
调用图:调用 2 个内部函数(launch, from_transport);被 2 处调用(new, server_metadata_preserves_tool_approval_policy)。
codex-mcp/src/codex_apps.rs源码 ↗
Codex Apps 的工具来自远端连接器,启动时如果每次都重新拉取,会慢,也可能在网络不好时没法马上显示。这个文件就像给每个登录用户准备一个单独的小抽屉:把工具列表和服务器信息写到磁盘缓存里,下次启动先拿出来用。它还会按账号生成不同缓存文件,避免 A 用户看到 B 用户的工具。除了缓存,它还会过滤不允许的连接器,并把工具名、命名空间整理干净,比如去掉连接器名前缀,避免名字又长又重复。这里特别重要的一点是:只有 Codex Apps 这个特殊 MCP 服务器会走这些逻辑,普通 MCP 服务器不会被改名或缓存到这里。MCP 可以理解成“模型调用外部工具的一套接口规则”。
codex_apps_tools_cache_key32–38 ↗
fn codex_apps_tools_cache_key(auth: Option<&CodexAuth>) -> CodexAppsToolsCacheKey
作用:根据当前登录信息做出一个“缓存归谁所有”的钥匙。这样不同账号、不同 ChatGPT 用户、工作区账号和个人账号不会混用同一份工具缓存。
数据流:进去的是可选的登录身份信息;它从里面取账号 ID、ChatGPT 用户 ID,以及是不是工作区账号;出来的是一个 CodexAppsToolsCacheKey,后面会用它决定缓存文件名。
调用关系:在收集 MCP 服务器状态或读取 MCP 资源时会先调用它,拿到用户专属钥匙。后面的缓存路径会基于这个钥匙生成,所以它是“按用户隔离缓存”的第一步。
调用图:被 2 处调用(collect_mcp_server_status_snapshot_with_detail, read_mcp_resource)。
CodexAppsToolsCacheContext::tools_cache_path47–49 ↗
fn tools_cache_path(&self) -> PathBuf
作用:算出当前用户的 Codex Apps 工具列表缓存文件放在哪里。调用者不用自己拼路径,避免不同地方拼出来不一致。
数据流:进去的是缓存上下文,里面有 Codex 主目录和用户钥匙;它把工作交给 cache_path_in,并指定工具缓存目录;出来的是一个磁盘文件路径。
调用关系:load_cached_codex_apps_tools 读工具缓存时会用它,write_cached_codex_apps_tools 写工具缓存时也会用它。它是工具缓存读写共同使用的“地址簿”。
调用图:调用 1 个内部函数(cache_path_in);被 2 处调用(load_cached_codex_apps_tools, write_cached_codex_apps_tools)。
CodexAppsToolsCacheContext::server_info_cache_path51–53 ↗
fn server_info_cache_path(&self) -> PathBuf
作用:算出当前用户的 Codex Apps 服务器信息缓存文件放在哪里。服务器信息和工具列表分开存,方便各自更新和校验版本。
数据流:进去的是缓存上下文;它调用 cache_path_in,并指定服务器信息缓存目录;出来的是对应的 JSON 文件路径。
调用关系:load_cached_codex_apps_server_info 用它找文件读取,write_cached_codex_apps_server_info 用它找位置写入。它和 tools_cache_path 作用类似,只是服务对象不同。
调用图:调用 1 个内部函数(cache_path_in);被 2 处调用(load_cached_codex_apps_server_info, write_cached_codex_apps_server_info)。
CodexAppsToolsCacheContext::cache_path_in55–61 ↗
fn cache_path_in(&self, cache_dir: &str) -> PathBuf
作用:把“缓存目录”和“用户身份钥匙”合成一个稳定、安全的缓存文件路径。它不会直接把用户信息当文件名,而是先做哈希。
数据流:进去的是缓存目录名,以及上下文里的 Codex 主目录和用户钥匙;它先把用户钥匙转成 JSON 字符串,再用 sha1_hex 算出一串短标识,最后拼成类似某目录/哈希.json 的路径;出来的是完整路径。
调用关系:tools_cache_path 和 server_info_cache_path 都把实际拼路径的活交给它。它再调用 sha1_hex,把可能很长、带特殊字符的用户信息变成适合做文件名的字符串。
调用图:调用 1 个内部函数(sha1_hex);被 2 处调用(server_info_cache_path, tools_cache_path);外部调用 3 个(join, format!, to_string)。
normalize_codex_apps_tool_title70–94 ↗
fn normalize_codex_apps_tool_title(
server_name: &str,
connector_name: Option<&str>,
value: &str,
) -> String
作用:把 Codex Apps 工具的显示标题整理得更清爽。比如标题已经带了连接器名前缀时,就把重复前缀去掉。
数据流:进去的是服务器名、可选连接器名和原始标题;如果不是 Codex Apps 服务器,就原样返回;如果是,并且标题以“连接器名_”开头,就去掉这段前缀;出来的是更适合展示给人的标题。
调用关系:它是名字整理流程的一部分,专门处理“给人看的标题”。它不会碰普通 MCP 服务器,避免误改其他来源的工具名称。
调用图:外部调用 1 个(format!)。
normalize_codex_apps_callable_name96–129 ↗
fn normalize_codex_apps_callable_name(
server_name: &str,
tool_name: &str,
connector_id: Option<&str>,
connector_name: Option<&str>,
) -> String
作用:把模型实际调用工具时用的名字整理成更短、更安全的形式。它会先清洗名字,再尝试去掉连接器名或连接器 ID 这类重复前缀。
数据流:进去的是服务器名、原始工具名、可选连接器 ID 和可选连接器名;如果不是 Codex Apps 服务器,就直接返回原工具名;如果是,就用 sanitize_name 清理非法或不合适的字符,再去掉已经包含在前面的连接器名或 ID;出来的是模型可调用的工具名。
调用关系:它服务于工具暴露给模型前的命名规范化。它依赖 sanitize_name 做基础清洗,确保最终名字更像一个稳定的“函数名”。
调用图:调用 1 个内部函数(sanitize_name)。
normalize_codex_apps_callable_namespace131–142 ↗
fn normalize_codex_apps_callable_namespace(
server_name: &str,
connector_name: Option<&str>,
) -> String
作用:给 Codex Apps 工具生成调用时的命名空间。命名空间可以理解成“工具属于哪个柜台”,用来避免不同连接器里的工具撞名。
数据流:进去的是服务器名和可选连接器名;如果是 Codex Apps 且有连接器名,就把服务器名和清洗后的连接器名拼成 server__connector 的形式;否则只返回服务器名;出来的是调用工具时使用的命名空间字符串。
调用关系:它和 normalize_codex_apps_callable_name 配合:一个整理“柜台名”,一个整理“工具名”。这样模型看到的工具地址既短又不容易冲突。
调用图:外部调用 1 个(format!)。
write_cached_codex_apps_tools_if_needed144–166 ↗
fn write_cached_codex_apps_tools_if_needed(
server_name: &str,
cache_context: Option<&CodexAppsToolsCacheContext>,
server_info: &McpServerInfo,
tools: &[ToolInfo],
)
作用:在合适的时候把 Codex Apps 的工具列表和服务器信息写入本地缓存。它会先确认这是 Codex Apps 服务器,避免把别的服务器数据写进专用缓存。
数据流:进去的是服务器名、可选缓存上下文、服务器信息和工具列表;如果服务器名不匹配或没有缓存上下文,就什么也不做;否则写工具缓存,再写服务器信息缓存,并记录写缓存花了多久;出来没有返回值,但磁盘上可能多了或更新了缓存文件。
调用关系:它在服务器启动、硬刷新工具缓存,以及相关测试流程中被调用。它把实际写工具的活交给 write_cached_codex_apps_tools,把写服务器信息的活交给 write_cached_codex_apps_server_info,并用 emit_duration 记录耗时;服务器信息写失败时只打警告,不中断主流程。
调用图:调用 3 个内部函数(write_cached_codex_apps_server_info, write_cached_codex_apps_tools, emit_duration);被 4 处调用(hard_refresh_codex_apps_tools_cache, codex_apps_server_info_cache_survives_legacy_tools_cache_write, startup_cached_codex_apps_tools_loads_from_disk_cache, start_server_task);外部调用 2 个(now, warn!)。
load_startup_cached_codex_apps_tools_snapshot168–182 ↗
fn load_startup_cached_codex_apps_tools_snapshot(
server_name: &str,
cache_context: Option<&CodexAppsToolsCacheContext>,
) -> Option<Vec<ToolInfo>>
作用:启动时尝试从本地缓存拿一份 Codex Apps 工具快照。这样即使远端还没拉完,程序也能先有一份旧工具列表可用。
数据流:进去的是服务器名和可选缓存上下文;如果不是 Codex Apps 或没有上下文,就返回空;如果能读到有效缓存,就返回工具列表;如果文件不存在或坏了,也返回空。
调用关系:它在创建相关对象时用于启动加速,也被测试验证。它把真正读文件和校验的工作交给 load_cached_codex_apps_tools,然后只把命中结果转成 Option。
调用图:调用 1 个内部函数(load_cached_codex_apps_tools);被 3 处调用(startup_cached_codex_apps_tools_loads_from_disk_cache, startup_cached_codex_apps_tools_loads_without_server_info_cache, new)。
load_startup_cached_codex_apps_server_info184–193 ↗
fn load_startup_cached_codex_apps_server_info(
server_name: &str,
cache_context: Option<&CodexAppsToolsCacheContext>,
) -> Option<McpServerInfo>
作用:启动时尝试读取 Codex Apps 的服务器信息缓存。服务器信息能帮助程序在远端连接完成前了解这个服务器的基本情况。
数据流:进去的是服务器名和可选缓存上下文;如果不是 Codex Apps 或没有上下文,就返回空;否则读取并解析服务器信息缓存;出来是可选的 McpServerInfo。
调用关系:它和 load_startup_cached_codex_apps_tools_snapshot 是一对启动缓存读取入口。它把具体读文件的动作交给 load_cached_codex_apps_server_info。
调用图:调用 1 个内部函数(load_cached_codex_apps_server_info);被 3 处调用(startup_cached_codex_apps_tools_loads_from_disk_cache, startup_cached_codex_apps_tools_loads_without_server_info_cache, new)。
read_cached_codex_apps_tools196–203 ↗
fn read_cached_codex_apps_tools(
cache_context: &CodexAppsToolsCacheContext,
) -> Option<Vec<ToolInfo>>
作用:这是测试用的便捷读取函数,用来检查磁盘里的工具缓存最后到底是什么。它把“命中、缺失、无效”简化成“有工具列表或没有”。
数据流:进去的是缓存上下文;它调用 load_cached_codex_apps_tools;如果读到有效工具,就返回工具列表;如果没有文件或文件无效,就返回空。
调用关系:它只在测试配置下编译,测试会用它验证缓存是否按用户隔离、是否覆盖旧内容、是否过滤不允许的连接器。真正业务读取通常走 load_startup_cached_codex_apps_tools_snapshot 或 load_cached_codex_apps_tools。
调用图:调用 1 个内部函数(load_cached_codex_apps_tools);被 3 处调用(codex_apps_tools_cache_filters_disallowed_connectors, codex_apps_tools_cache_is_overwritten_by_last_write, codex_apps_tools_cache_is_scoped_per_user)。
load_cached_codex_apps_tools205–224 ↗
fn load_cached_codex_apps_tools(
cache_context: &CodexAppsToolsCacheContext,
) -> CachedCodexAppsToolsLoad
作用:从磁盘读取 Codex Apps 工具缓存,并判断这份缓存能不能用。它会区分三种情况:读到了、文件不存在、文件坏了或版本不对。
数据流:进去的是缓存上下文;它先通过 tools_cache_path 找到文件,再读字节、按 JSON 解析、检查缓存格式版本;通过后还会过滤掉不允许的连接器工具;出来是 Hit、Missing 或 Invalid 三种结果之一。
调用关系:启动读取、测试读取,以及列工具流程都会用到它。它依赖 filter_disallowed_codex_apps_tools 做最后一道安全过滤,保证就算旧缓存里有不该出现的连接器,读出来也会被清掉。
调用图:调用 2 个内部函数(tools_cache_path, filter_disallowed_codex_apps_tools);被 3 处调用(load_startup_cached_codex_apps_tools_snapshot, read_cached_codex_apps_tools, listed_tools);外部调用 3 个(Hit, from_slice, read)。
write_cached_codex_apps_tools226–244 ↗
fn write_cached_codex_apps_tools(
cache_context: &CodexAppsToolsCacheContext,
tools: &[ToolInfo],
)
作用:把 Codex Apps 工具列表写到当前用户自己的磁盘缓存里。写之前会过滤不允许的连接器,避免把不该保留的工具存下来。
数据流:进去的是缓存上下文和工具列表;它先算缓存路径,确保父目录存在;再过滤工具,包上缓存格式版本,序列化成漂亮的 JSON;最后写入文件。它没有返回值,失败时会安静放弃。
调用关系:write_cached_codex_apps_tools_if_needed 会在刷新或启动流程中调用它,测试也会直接调用它。它使用 tools_cache_path 找位置,并调用 filter_disallowed_codex_apps_tools 保证写入内容合规。
调用图:调用 2 个内部函数(tools_cache_path, filter_disallowed_codex_apps_tools);被 4 处调用(write_cached_codex_apps_tools_if_needed, codex_apps_tools_cache_filters_disallowed_connectors, codex_apps_tools_cache_is_overwritten_by_last_write, codex_apps_tools_cache_is_scoped_per_user);外部调用 4 个(to_vec, to_vec_pretty, create_dir_all, write)。
load_cached_codex_apps_server_info246–253 ↗
fn load_cached_codex_apps_server_info(
cache_context: &CodexAppsToolsCacheContext,
) -> Option<McpServerInfo>
作用:从磁盘读取 Codex Apps 的服务器信息缓存。它只接受当前格式版本的缓存,旧格式或坏文件都会被当成没有缓存。
数据流:进去的是缓存上下文;它通过 server_info_cache_path 找文件,读取 JSON,解析出缓存对象,并检查版本号;版本匹配就返回里面的 McpServerInfo,否则返回空。
调用关系:load_startup_cached_codex_apps_server_info 会调用它,用于启动时恢复服务器信息。它和工具缓存读取逻辑类似,但返回值更简单:能用就给,不能用就不给。
调用图:调用 1 个内部函数(server_info_cache_path);被 1 处调用(load_startup_cached_codex_apps_server_info);外部调用 2 个(from_slice, read)。
write_cached_codex_apps_server_info255–280 ↗
fn write_cached_codex_apps_server_info(
cache_context: &CodexAppsToolsCacheContext,
server_info: &McpServerInfo,
) -> anyhow::Result<()>
作用:把 Codex Apps 的服务器信息写到磁盘缓存里。和工具缓存不同,它会把失败原因带出来,方便上层记录警告。
数据流:进去的是缓存上下文和服务器信息;它算出缓存路径,创建父目录,把服务器信息连同版本号序列化成 JSON,再写入文件;成功返回 Ok,失败返回带上下文说明的错误。
调用关系:只有 write_cached_codex_apps_tools_if_needed 会调用它。上层在写失败时不会让整个流程失败,只会记录一条警告,因为缓存只是加速手段,不是核心功能。
调用图:调用 1 个内部函数(server_info_cache_path);被 1 处调用(write_cached_codex_apps_tools_if_needed);外部调用 4 个(clone, to_vec_pretty, create_dir_all, write)。
filter_disallowed_codex_apps_tools282–291 ↗
fn filter_disallowed_codex_apps_tools(tools: Vec<ToolInfo>) -> Vec<ToolInfo>
作用:从工具列表里删掉来自不允许连接器的工具。可以把它理解成门卫:只有连接器 ID 在允许名单里,或者工具没有连接器 ID,才放行。
数据流:进去的是一整组 ToolInfo;它逐个查看工具的 connector_id,并用 is_connector_id_allowed 判断是否允许;出来的是过滤后的工具列表。
调用关系:读缓存、写缓存、以及给客户端列工具时都会用它。这样不管数据来自远端还是旧缓存,都要过同一套允许名单检查。
调用图:被 3 处调用(load_cached_codex_apps_tools, write_cached_codex_apps_tools, list_tools_for_client_uncached)。
sha1_hex311–316 ↗
fn sha1_hex(s: &str) -> String
作用:把一段字符串变成 SHA-1 十六进制哈希。哈希可以理解成“固定长度的指纹”,这里用来把用户缓存钥匙变成适合当文件名的字符串。
数据流:进去的是任意字符串;它用 SHA-1 算法生成摘要,再转成十六进制文本;出来的是一串稳定的哈希字符串。
调用关系:cache_path_in 会调用它生成缓存文件名。这样缓存路径不会直接暴露完整用户信息,也避免用户名里有特殊字符导致文件路径出问题。
调用图:被 1 处调用(cache_path_in);外部调用 2 个(new, format!)。
codex-mcp/src/connection_manager.rs源码 ↗
MCP 可以理解成一种让 Codex 接入外部工具、资源和应用的接口。一个用户可能配置多个 MCP 服务器,如果每次都让上层代码自己判断哪个服务器启动好了、哪个工具属于谁、失败该怎么报错,就会很乱。这个文件里的 McpConnectionManager 把这些事集中起来:启动所有启用的服务器,发送“正在启动、已就绪、失败”等状态事件;记录每个服务器的来源和权限信息;把所有服务器的工具整理成模型能看懂的名字;调用工具时先检查工具是否允许,再转给对应客户端;关闭时统一取消启动和停止连接。它还处理“elicitation”(服务器向用户追问确认或信息)的状态,让权限变化能及时生效。整体上,它像一个前台总机:外面只要说找哪个服务器、哪个工具,总机会负责接通、拦截不该做的事,并把结果带回来。
tool_is_model_visible88–104 ↗
fn tool_is_model_visible(tool: &ToolInfo) -> bool
作用:判断某个 MCP 工具要不要展示给模型看。工具如果没有特别标注,就默认可见;如果写了可见范围,只有明确包含 model 时才给模型用。
数据流:输入是一个工具信息 → 它读取工具元数据里的 ui.visibility 字段 → 输出 true 或 false,表示这个工具是否能进入模型可见的工具列表,不会改动原工具。
调用关系:这是一个独立的小判断器,用来配合工具发现流程。它不依赖连接管理器本身,适合在过滤或整理工具清单时被调用。
McpConnectionManager::new120–281 ↗
async fn new(
mcp_servers: &HashMap<String, EffectiveMcpServer>,
store_mode: OAuthCredentialsStoreMode,
keyring_backend_kind: AuthKeyringBackendKind,
auth_entries: Hash
作用:创建真正可用的 MCP 连接管理器,并开始启动所有启用的 MCP 服务器。它还会不断发出启动进度,让上层界面知道哪些服务器正在启动、成功或失败。
数据流:输入是一组服务器配置、认证方式、权限策略、事件发送通道、运行环境和缓存信息 → 它筛出启用的服务器,为每个服务器建立异步客户端,记录元数据,发送 Starting 事件,并在后台等待启动结果 → 输出一个管理器对象;同时后台任务会继续发送 Ready、Failed、Cancelled 和最终汇总事件。
调用关系:这是这个文件最重要的装配入口,被刷新 MCP 服务器、安装 Codex Apps 管理器、读取 MCP 资源等上层流程调用。它会借助 emit_update 发状态,启动失败时用 mcp_init_error_display 把技术错误改成用户能看懂的话。
调用图:调用 6 个内部函数(emit_update, mcp_init_error_display, new, new, from, value);被 7 处调用(no_local_runtime_fails_local_stdio_but_keeps_local_http_server, collect_mcp_server_status_snapshot_with_detail, read_mcp_resource, list_accessible_connectors_from_mcp_tools_with_mcp_manager, install_host_owned_codex_apps_manager, refresh_mcp_servers_inner, new);外部调用 15 个(clone, new, child_token, clone, clone, new, new, clone, clone, send (+5 more))。
McpConnectionManager::validate_required_servers287–327 ↗
async fn validate_required_servers(&self) -> Result<()>
作用:检查那些被标记为“必须可用”的 MCP 服务器是否真的启动成功。这样可以避免会话继续跑下去后才发现关键外部工具根本没连上。
数据流:输入是管理器当前保存的必需服务器名单和客户端集合 → 它逐个等待对应客户端启动结果,把失败原因收集起来 → 如果都成功就返回成功;如果有失败,就返回一条汇总错误。
调用关系:通常在会话初始化阶段使用,但调用方必须先让管理器可被请求处理器访问,因为服务器启动期间可能会向用户追问信息。它内部会用 startup_outcome_error_message 把启动结果转成简短错误文字。
调用图:调用 1 个内部函数(startup_outcome_error_message);外部调用 4 个(new, anyhow!, format!, info_span!)。
McpConnectionManager::new_uninitialized_with_permission_profile329–348 ↗
fn new_uninitialized_with_permission_profile(
approval_policy: &Constrained<AskForApproval>,
permission_profile: &PermissionProfile,
prefix_mcp_tool_names: bool,
) -> Self
作用:创建一个没有任何 MCP 服务器连接的空管理器,但保留权限和追问处理能力。它适合测试,或者在没有配置 MCP 服务器时让系统仍有一个统一对象可用。
数据流:输入是审批策略、权限档案和工具名前缀设置 → 它建立空的客户端表、空的服务器元数据、默认的插件来源记录和追问管理器 → 输出一个不会主动连接服务器的 McpConnectionManager。
调用关系:它被测试和会话构造辅助流程使用,也被测试专用的 new_uninitialized 包了一层。它避免上层代码到处判断“有没有连接管理器”。
调用图:调用 2 个内部函数(new, value);被 3 处调用(new, make_session_and_context, make_session_and_context_with_auth_config_home_and_rx);外部调用 6 个(new, new, new, new, default, clone)。
McpConnectionManager::has_servers350–352 ↗
fn has_servers(&self) -> bool
作用:快速回答当前管理器里有没有任何 MCP 服务器。上层可以用它决定是否显示 MCP 相关功能。
数据流:输入是管理器内部的客户端表 → 它检查这张表是不是空的 → 输出布尔值,不修改任何状态。
调用关系:这是一个轻量查询函数。它通常被外层流程用来做分支判断,不会触发网络连接或等待服务器启动。
McpConnectionManager::contains_server354–356 ↗
fn contains_server(&self, server_name: &str) -> bool
作用:判断某个名字的 MCP 服务器是否登记在这个管理器里。它只看配置和启动时建立的客户端表,不代表服务器一定已经启动成功。
数据流:输入是服务器名 → 它在内部客户端表里查这个键 → 输出 true 或 false,不改动状态。
调用关系:这是 crate 内部使用的小查询,用来在需要按名字确认服务器存在时避免发起真实调用。
McpConnectionManager::shutdown359–364 ↗
async fn shutdown(&self)
作用:主动关闭这个管理器拥有的所有 MCP 客户端,并让正在启动的服务器停止继续启动。没有它,后台进程或 stdio 子进程可能残留。
数据流:输入是当前管理器状态 → 它先取消启动令牌,再逐个调用客户端的 shutdown → 输出为空,但会让连接和相关子进程进入关闭流程。
调用关系:这是收尾阶段的统一关机按钮。它和 Drop 的自动取消互相兜底,但 shutdown 是异步的,能真正等待每个客户端执行关闭。
调用图:外部调用 1 个(cancel)。
McpConnectionManager::server_origin366–371 ↗
fn server_origin(&self, server_name: &str) -> Option<&str>
作用:查询某个服务器的来源说明,比如它来自哪里或由什么配置产生。展示状态、审计来源时会用到。
数据流:输入是服务器名 → 它查服务器元数据里的 origin 字段,并转成字符串 → 输出可选字符串引用;如果没有记录就输出空。
调用关系:这是给上层展示或判断用的只读接口。它依赖 new 启动时保存的 McpServerMetadata。
McpConnectionManager::server_pollutes_memory373–377 ↗
fn server_pollutes_memory(&self, server_name: &str) -> bool
作用:判断某个服务器的结果是否可能污染模型记忆或上下文。没有元数据时,它保守地认为会污染。
数据流:输入是服务器名 → 它查元数据里的 pollutes_memory 标记 → 输出 true 或 false;找不到服务器元数据时输出 true。
调用关系:这是安全和上下文管理会用的查询。它把“未知情况按风险处理”的规则集中在这里。
McpConnectionManager::plugin_id_for_mcp_server_name379–382 ↗
fn plugin_id_for_mcp_server_name(&self, server_name: &str) -> Option<&str>
作用:根据 MCP 服务器名查它对应的插件 ID。这样系统能把工具和它来自的插件对应起来。
数据流:输入是服务器名 → 它询问 tool_plugin_provenance 这份来源映射 → 输出可选插件 ID 字符串引用。
调用关系:它把问题交给 ToolPluginProvenance,也就是插件来源记录器。上层需要按插件展示或授权时会用这个接口。
McpConnectionManager::is_selected_plugin_mcp_server384–387 ↗
fn is_selected_plugin_mcp_server(&self, server_name: &str) -> bool
作用:判断某个 MCP 服务器是不是当前被选中插件带来的服务器。它用于区分用户主动选中的插件和其他服务器。
数据流:输入是服务器名 → 它查询插件来源记录里的选择状态 → 输出 true 或 false。
调用关系:它同样依赖 ToolPluginProvenance。上层在展示插件相关工具或做筛选时,可以通过它判断服务器身份。
McpConnectionManager::tool_approval_mode389–398 ↗
fn tool_approval_mode(
&self,
server_name: &str,
tool_name: &str,
) -> codex_config::AppToolApproval
作用:查询某个服务器上的某个工具需要怎样审批。比如是否每次都问用户,或按配置自动处理。
数据流:输入是服务器名和工具名 → 它找到服务器元数据,并让元数据判断这个工具的审批模式 → 输出审批模式;如果没有元数据,就输出默认模式。
调用关系:这是工具调用前后权限流程会依赖的查询点。具体规则放在服务器元数据里,这里负责按服务器和工具名取出来。
McpConnectionManager::is_host_owned_codex_apps_server400–402 ↗
fn is_host_owned_codex_apps_server(&self, server_name: &str) -> bool
作用:判断某个服务器是不是由宿主 Codex 自己拥有的 Codex Apps MCP 服务器。这个身份会影响认证、缓存和信任处理。
数据流:输入是服务器名 → 它同时检查全局开关是否启用,以及名字是否等于 Codex Apps 的固定服务器名 → 输出 true 或 false。
调用关系:这是 Codex Apps 特殊路径的身份判断。new 在启动 Codex Apps 服务器时也会按这个固定名字做特殊缓存和认证设置。
McpConnectionManager::set_approval_policy404–408 ↗
fn set_approval_policy(&self, approval_policy: &Constrained<AskForApproval>)
作用:更新后续追问和确认使用的审批策略。用户或系统切换权限模式后,不需要重建整个连接管理器。
数据流:输入是新的审批策略 → 它尝试锁住追问管理器里的策略字段,并写入策略值 → 输出为空;如果锁失败,就静默不改。
调用关系:它把新的策略传给 ElicitationRequestManager 保存。后续服务器发起 elicitation,也就是向用户追问确认时,会按新策略处理。
调用图:调用 1 个内部函数(value)。
McpConnectionManager::set_permission_profile410–414 ↗
fn set_permission_profile(&self, permission_profile: PermissionProfile)
作用:更新当前权限档案。权限档案可以理解成一组规则,告诉系统哪些操作更安全、哪些需要谨慎。
数据流:输入是新的 PermissionProfile → 它尝试锁住追问管理器里的权限档案并替换 → 输出为空;锁失败时不做修改。
调用关系:它服务于运行中权限变化。后续 elicitation 审核会读取这个新档案。
McpConnectionManager::elicitations_auto_deny416–418 ↗
fn elicitations_auto_deny(&self) -> bool
作用:查询当前是否自动拒绝所有服务器追问。这个开关可用于会话结束、取消或不希望再弹确认的场景。
数据流:输入是管理器内部的追问管理器 → 它读取 auto_deny 状态 → 输出 true 或 false。
调用关系:它把读取动作交给 ElicitationRequestManager 的 auto_deny。上层可以用它展示或确认当前追问处理状态。
调用图:调用 1 个内部函数(auto_deny)。
McpConnectionManager::set_elicitations_auto_deny420–422 ↗
fn set_elicitations_auto_deny(&self, auto_deny: bool)
作用:设置是否自动拒绝后续所有服务器追问。开启后,服务器再问用户要信息或许可时会被直接拒绝。
数据流:输入是 auto_deny 布尔值 → 它写入追问管理器的对应开关 → 输出为空,并改变后续追问的处理方式。
调用关系:它调用 ElicitationRequestManager 的 set_auto_deny。通常在取消流程或需要快速阻断交互时使用。
调用图:调用 1 个内部函数(set_auto_deny)。
McpConnectionManager::resolve_elicitation424–433 ↗
async fn resolve_elicitation(
&self,
server_name: String,
id: RequestId,
response: ElicitationResponse,
) -> Result<()>
作用:把用户对某个服务器追问的回答送回去。比如服务器问“是否允许访问某资源”,这个函数负责交还答案。
数据流:输入是服务器名、请求 ID 和用户回答 → 它交给追问管理器匹配对应的待处理请求并完成它 → 输出成功或错误。
调用关系:它调用 ElicitationRequestManager::resolve。请求处理层收到用户回复时会走到这里,让正在等待的 MCP 客户端继续执行。
调用图:调用 1 个内部函数(resolve)。
McpConnectionManager::wait_for_server_ready435–444 ↗
async fn wait_for_server_ready(&self, server_name: &str, timeout: Duration) -> bool
作用:在限定时间内等待某个服务器准备好。它适合“最多等几秒,等不到就先放弃”的场景。
数据流:输入是服务器名和超时时长 → 它先查客户端,再用 timeout 包住客户端启动等待 → 输出 true 表示按时可用,false 表示不存在、失败或超时。
调用关系:这是给上层做温和等待的工具。它不会抛出详细错误,只给一个简单是否就绪的答案。
调用图:外部调用 1 个(timeout)。
McpConnectionManager::list_all_tools448–485 ↗
async fn list_all_tools(&self) -> Vec<ToolInfo>
作用:汇总所有 MCP 服务器提供的工具,并把工具名整理成模型能安全使用的形式。模型调用工具前,通常需要这份清单。
数据流:输入是当前所有客户端和前缀配置 → 它逐个等待或读取每个服务器的工具快照,把服务器元数据补到工具上 → 输出一个已规范化的 ToolInfo 列表。
调用关系:它被 list_accessible_and_enabled_connectors_from_manager 等上层能力发现流程调用。内部会用 with_server_metadata 补信息,最后交给 normalize_tools_for_model_with_prefix 统一命名。
调用图:调用 1 个内部函数(normalize_tools_for_model_with_prefix);被 1 处调用(list_accessible_and_enabled_connectors_from_manager);外部调用 3 个(new, trace!, trace_span!)。
McpConnectionManager::hard_refresh_codex_apps_tools_cache492–540 ↗
async fn hard_refresh_codex_apps_tools_cache(&self) -> Result<Vec<ToolInfo>>
作用:强制刷新 Codex Apps 的工具缓存,不用内存里的旧清单。它用于用户需要立刻看到最新应用工具时。
数据流:输入是当前管理器里的 Codex Apps 服务器连接 → 它取得客户端,直接向服务器重新拉工具,记录耗时,把成功结果写入缓存,再按过滤规则和模型可见格式整理 → 输出最新工具列表;失败时旧缓存不被覆盖。
调用关系:它只针对 CODEX_APPS_MCP_SERVER_NAME。它会调用 list_tools_for_client_uncached 拉新数据、write_cached_codex_apps_tools_if_needed 写缓存、filter_tools 过滤,再用 normalize_tools_for_model_with_prefix 输出给上层。
调用图:调用 5 个内部函数(write_cached_codex_apps_tools_if_needed, list_tools_for_client_uncached, emit_duration, filter_tools, normalize_tools_for_model_with_prefix);外部调用 1 个(now)。
McpConnectionManager::list_all_resources544–605 ↗
async fn list_all_resources(&self) -> HashMap<String, Vec<Resource>>
作用:并发收集所有服务器暴露的资源。资源可以理解成服务器提供给 Codex 读取的文件、数据或对象。
数据流:输入是所有可用客户端 → 它为每个已启动客户端开一个异步任务,按分页游标一页页拉资源,防止服务器返回重复游标导致死循环 → 输出按服务器名分组的资源表;失败的服务器只记录警告,不阻断其他服务器。
调用关系:这是全局资源浏览功能的汇总器。它直接调用每个底层客户端的 list_resources,并用 JoinSet 让多个服务器同时工作。
McpConnectionManager::list_all_resource_templates609–674 ↗
async fn list_all_resource_templates(&self) -> HashMap<String, Vec<ResourceTemplate>>
作用:并发收集所有服务器提供的资源模板。资源模板像“可填参数的资源地址样板”,告诉系统哪些资源可以按规则生成。
数据流:输入是所有可用客户端 → 它给每个服务器启动异步任务,按分页游标拉取模板,检查重复游标 → 输出按服务器名分组的模板表;某个服务器失败只写日志,不影响其他结果。
调用关系:它和 list_all_resources 结构相似,但调用的是 list_resource_templates。上层需要展示或发现可参数化资源时会用它。
McpConnectionManager::call_tool677–712 ↗
async fn call_tool(
&self,
server: &str,
tool: &str,
arguments: Option<serde_json::Value>,
meta: Option<serde_json::Value>,
) -> Result<CallToolResult>
作用:调用指定服务器上的指定工具,并把 MCP 客户端返回的结果转换成 Codex 协议能传递的格式。它是实际“执行外部工具”的入口。
数据流:输入是服务器名、工具名、参数和元数据 → 它先通过 client_by_name 找到客户端,再检查该工具是否被过滤器允许,随后发起 call_tool → 输出 CallToolResult;如果服务器未知、工具被禁用或调用失败,就返回错误。
调用关系:请求处理层要执行 MCP 工具时会走这里。它把找客户端的工作交给 client_by_name,把真正网络或进程通信交给底层 MCP client。
调用图:调用 1 个内部函数(client_by_name);外部调用 1 个(anyhow!)。
McpConnectionManager::server_supports_sandbox_state_meta_capability714–722 ↗
async fn server_supports_sandbox_state_meta_capability(
&self,
server: &str,
) -> Result<bool>
作用:查询某个服务器是否支持“沙箱状态元信息”能力。沙箱可以理解成受限制的运行环境,这个能力表示服务器能理解相关状态。
数据流:输入是服务器名 → 它通过 client_by_name 取得已启动客户端,再读取客户端保存的能力标记 → 输出 true 或 false,或在服务器不可用时返回错误。
调用关系:它是能力查询接口,依赖 client_by_name 保证服务器存在且客户端可用。上层决定是否发送沙箱相关元数据时会用它。
调用图:调用 1 个内部函数(client_by_name)。
McpConnectionManager::list_resources725–738 ↗
async fn list_resources(
&self,
server: &str,
params: Option<PaginatedRequestParams>,
) -> Result<ListResourcesResult>
作用:从某一个指定服务器列出一页资源。和 list_all_resources 不同,它只针对一个服务器,并保留分页参数。
数据流:输入是服务器名和可选分页参数 → 它通过 client_by_name 找客户端,取出超时时间,然后调用底层 list_resources → 输出该服务器返回的资源列表结果或带上下文的错误。
调用关系:资源请求处理器按服务器浏览资源时会调用它。它把查客户端的公共步骤交给 client_by_name。
调用图:调用 1 个内部函数(client_by_name)。
McpConnectionManager::list_resource_templates741–754 ↗
async fn list_resource_templates(
&self,
server: &str,
params: Option<PaginatedRequestParams>,
) -> Result<ListResourceTemplatesResult>
作用:从某一个指定服务器列出一页资源模板。它用于按服务器查看可生成的资源类型。
数据流:输入是服务器名和分页参数 → 它取得对应客户端和超时时间,调用底层 list_resource_templates → 输出模板列表结果;失败时错误里会说明是哪台服务器失败。
调用关系:它服务于单服务器资源模板查询。公共的服务器查找和启动等待由 client_by_name 处理。
调用图:调用 1 个内部函数(client_by_name)。
McpConnectionManager::read_resource757–771 ↗
async fn read_resource(
&self,
server: &str,
params: ReadResourceRequestParams,
) -> Result<ReadResourceResult>
作用:读取某个服务器上的具体资源内容。比如前面列出了一个资源 URI,这里负责把内容取回来。
数据流:输入是服务器名和读取参数,其中包含资源 URI → 它找到客户端,记下 URI 用于错误说明,然后调用底层 read_resource → 输出资源内容结果或带服务器名和 URI 的错误。
调用关系:资源读取请求会走这里。它依赖 client_by_name 找到正确连接,再把真正读取交给底层 MCP client。
调用图:调用 1 个内部函数(client_by_name)。
McpConnectionManager::list_available_server_infos775–796 ↗
async fn list_available_server_infos(&self) -> HashMap<String, McpServerInfo>
作用:列出当前可用于展示的服务器信息,但尽量不等待还没启动完的服务器。这样状态页可以很快显示已有信息。
数据流:输入是所有客户端状态和缓存信息 → 对还在启动的服务器,它优先用 cached_server_info;对启动完成的服务器,它尝试读取真实 server_info,失败时再退回缓存 → 输出服务器名到信息的映射表。
调用关系:它被 collect_mcp_server_status_snapshot_from_manager 调用,用于生成状态快照。它的重点是“不为了展示信息卡住界面”。
调用图:被 1 处调用(collect_mcp_server_status_snapshot_from_manager);外部调用 1 个(new)。
McpConnectionManager::with_server_metadata798–811 ↗
fn with_server_metadata(&self, mut tool: ToolInfo) -> ToolInfo
作用:把服务器级别的元数据补到单个工具上,比如是否支持并行工具调用、服务器来源是什么。没有这些信息,上层就难以正确展示和调度工具。
数据流:输入是一个 ToolInfo → 它按工具里的 server_name 查服务器元数据,并写入 supports_parallel_tool_calls 和 server_origin → 输出补完信息后的 ToolInfo;查不到元数据时采用保守默认值。
调用关系:它是 list_all_tools 和刷新 Codex Apps 工具时的内部加工步骤。它让工具清单不只是“工具本身”,还带上“来自哪台服务器、能怎样调用”。
McpConnectionManager::client_by_name813–820 ↗
async fn client_by_name(&self, name: &str) -> Result<ManagedClient>
作用:按服务器名取得一个已经可用的托管客户端。很多操作都要先找到正确服务器,这个函数把重复检查集中起来。
数据流:输入是服务器名 → 它在客户端表里查找,找不到就报 unknown MCP server;找到后等待客户端启动结果 → 输出 ManagedClient,或返回“获取客户端失败”的错误。
调用关系:它被 call_tool、list_resources、list_resource_templates、read_resource 和 server_supports_sandbox_state_meta_capability 共用。它是这些单服务器操作的门卫。
调用图:被 5 处调用(call_tool, list_resource_templates, list_resources, read_resource, server_supports_sandbox_state_meta_capability)。
McpConnectionManager::new_uninitialized823–833 ↗
fn new_uninitialized(
approval_policy: &Constrained<AskForApproval>,
permission_profile: &Constrained<PermissionProfile>,
prefix_mcp_tool_names: bool,
) -> Self
作用:测试专用的空管理器构造函数。它接收测试里常用的受约束权限档案,然后转成普通权限档案再创建空管理器。
数据流:输入是审批策略、受约束的权限档案和工具名前缀设置 → 它读取权限档案的实际值,再调用 new_uninitialized_with_permission_profile → 输出一个不连接服务器的管理器。
调用关系:它只在测试编译时存在,被多个 list_all_tools 和 server_info 缓存相关测试调用。真正的构造逻辑复用 new_uninitialized_with_permission_profile。
调用图:调用 1 个内部函数(get);被 9 处调用(list_all_tools_accepts_canonical_namespaced_tool_names, list_all_tools_adds_server_metadata_to_cached_tools, list_all_tools_applies_legacy_mcp_prefix_by_default, list_all_tools_blocks_while_client_is_pending_without_cached_tool_info_snapshot, list_all_tools_does_not_block_when_cached_tool_info_snapshot_is_empty, list_all_tools_uses_cached_tool_info_snapshot_when_client_startup_fails, list_all_tools_uses_cached_tool_info_snapshot_while_client_is_pending, list_available_server_infos_uses_cache_while_client_is_pending, shutdown_cancels_pending_tool_listing);外部调用 1 个(new_uninitialized_with_permission_profile)。
McpConnectionManager::drop837–840 ↗
fn drop(&mut self)
作用:当管理器对象被销毁时,自动取消还在进行的启动,并清空客户端表。它是最后一道防漏清理。
数据流:输入是即将被释放的管理器 → 它取消 startup_cancellation_token,然后清空 clients → 没有返回值,但会阻止后台启动继续推进。
调用关系:这是 Rust 的 Drop 清理钩子,会在对象生命周期结束时自动运行。它不能等待异步关闭,所以正式关机仍应优先调用 shutdown。
调用图:外部调用 1 个(cancel)。
emit_update843–854 ↗
async fn emit_update(
submit_id: &str,
tx_event: &Sender<Event>,
update: McpStartupUpdateEvent,
) -> Result<(), async_channel::SendError<Event>>
作用:发送一条 MCP 启动状态更新事件。它把“某服务器正在启动、成功或失败”的消息送到事件通道里。
数据流:输入是提交 ID、事件发送器和启动更新内容 → 它包装成 EventMsg::McpStartupUpdate,并通过通道发送 → 输出发送成功或发送失败错误。
调用关系:McpConnectionManager::new 在每台服务器开始启动和得到启动结果时调用它。它是启动状态从后台任务传到上层界面的出口。
mcp_init_error_display856–897 ↗
fn mcp_init_error_display(
server_name: &str,
entry: Option<&McpAuthStatusEntry>,
err: &StartupOutcomeError,
) -> String
作用:把 MCP 启动失败的技术错误改写成更适合用户看的提示。比如告诉用户该登录、该配置 token,或者该调大启动超时时间。
数据流:输入是服务器名、可选认证状态条目和启动错误 → 它先识别 GitHub MCP 的特殊无 token 情况,再判断是否认证缺失或启动超时,最后兜底输出通用失败信息 → 输出一段错误说明文字。
调用关系:它被 McpConnectionManager::new 在服务器启动失败时调用。内部会询问 is_mcp_client_auth_required_error 和 is_mcp_client_startup_timeout_error 来做分类。
调用图:调用 2 个内部函数(is_mcp_client_auth_required_error, is_mcp_client_startup_timeout_error);被 1 处调用(new);外部调用 1 个(format!)。
startup_outcome_error_message899–904 ↗
fn startup_outcome_error_message(error: StartupOutcomeError) -> String
作用:把启动结果里的错误变成简短文字。它主要用于汇总必需服务器失败原因。
数据流:输入是 StartupOutcomeError → 如果是取消,输出“MCP startup cancelled”;如果是失败,输出里面保存的错误字符串 → 不修改任何状态。
调用关系:validate_required_servers 用它给每个失败的必需服务器生成说明,然后再合成一条总错误。
调用图:被 1 处调用(validate_required_servers)。
is_mcp_client_auth_required_error906–911 ↗
fn is_mcp_client_auth_required_error(error: &StartupOutcomeError) -> bool
作用:判断启动错误是不是因为需要认证。它通过查找错误文字里的“Auth required”来识别。
数据流:输入是启动错误引用 → 如果错误是 Failed 并且文字包含 Auth required,就输出 true;其他情况输出 false。
调用关系:mcp_init_error_display 调用它来决定是否提示用户运行 codex mcp login <server>。
调用图:被 1 处调用(mcp_init_error_display);外部调用 1 个(contains)。
is_mcp_client_startup_timeout_error913–921 ↗
fn is_mcp_client_startup_timeout_error(error: &StartupOutcomeError) -> bool
作用:判断启动错误是不是超时。超时通常表示服务器太慢、没响应,或者配置的等待时间太短。
数据流:输入是启动错误引用 → 它检查失败文字是否包含 request timed out 或 timed out handshaking with MCP server → 输出 true 或 false。
调用关系:mcp_init_error_display 调用它来决定是否提示用户在 config.toml 里调整 startup_timeout_sec。
调用图:被 1 处调用(mcp_init_error_display);外部调用 1 个(contains)。
codex-mcp/src/resource_client.rs源码 ↗
MCP 可以理解成一种让外部服务把资料、文件片段或其他上下文暴露给 Codex 的协议。这个文件的重点是:会话里的 MCP 连接管理器可能会在启动或刷新时被替换,所以客户端不能抱着旧连接不放。它用 ArcSwap(一种可以安全替换共享对象的指针盒子)每次取当前最新的 McpConnectionManager。McpResourceClient 负责检查某个服务器是否存在、分页列资源、读取单个资源。拿到 rmcp 库返回的原始对象后,它再转成 codex_protocol 里统一使用的 Resource 和 ResourceContent。McpResourceClientCacheKey 则像“当前连接管理器的身份证”,用于判断缓存还能不能用:管理器一换,身份证也变。
McpResourceClientCacheKey::eq44–46 ↗
fn eq(&self, other: &Self) -> bool
作用:判断两个缓存钥匙是不是指向同一个 MCP 连接管理器。它不比较里面的数据内容,只看是不是同一个对象。
数据流:进去的是两个 McpResourceClientCacheKey,里面各自装着一个 Weak 弱引用(不拥有对象、只记住对象位置的引用)→ 函数用指针身份比较它们是不是同一份管理器 → 出来一个 true 或 false,不改动任何东西。
调用关系:它配合 McpResourceClient::cache_key 使用。cache_key 造出代表当前管理器身份的钥匙,eq 则让缓存系统能判断“这把钥匙还是不是原来那把”,从而知道旧缓存还能不能信。
McpResourceClient::fmt52–56 ↗
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
作用:给 McpResourceClient 提供调试打印方式。它故意只打印结构名,不把内部连接管理器的细节展开。
数据流:进去的是一个客户端和一个格式化器 → 函数调用 debug_struct 生成一个简短的调试结构 → 输出格式化结果,外部看到的是不暴露内部细节的 McpResourceClient 调试信息。
调用关系:当日志、调试器或 {:?} 这类调试打印需要展示客户端时会走到这里。它把实际格式化工作交给标准调试工具 debug_struct,避免泄露或打印一大堆底层连接状态。
调用图:外部调用 1 个(debug_struct)。
McpResourceClient::new61–63 ↗
fn new(manager: Arc<ArcSwap<McpConnectionManager>>) -> Self
作用:创建一个新的资源客户端,并让它使用会话里那份可替换的 MCP 连接管理器。
数据流:进去的是 Arc<ArcSwap<McpConnectionManager>>,也就是一个多人共享、还能被安全替换的管理器入口 → 函数把它存进 McpResourceClient → 出来一个可以查询资源的客户端。
调用关系:它在外部初始化流程里被名为 new 的调用方使用,用来把已有的 MCP 管理器包装成更好用的资源访问接口。后续 has_server、list_resources、read_resource 都靠这里保存的管理器入口工作。
调用图:被 1 处调用(new)。
McpResourceClient::cache_key66–68 ↗
fn cache_key(&self) -> McpResourceClientCacheKey
作用:生成一个代表“当前 MCP 管理器身份”的缓存钥匙。只要管理器被换掉,这个钥匙就会变。
数据流:进去的是客户端自己 → 它先从 ArcSwap 里取出当前的 McpConnectionManager,再用 downgrade 变成 Weak 弱引用 → 出来一个 McpResourceClientCacheKey,表示当前管理器的身份,但不会强行延长管理器生命。
调用关系:缓存资源列表或资源内容时会用到它。它把生成弱引用的细节交给外部库的 downgrade;之后 McpResourceClientCacheKey::eq 会用这个身份来判断缓存是否仍对应当前管理器。
调用图:外部调用 1 个(downgrade)。
McpResourceClient::has_server73–75 ↗
async fn has_server(&self, server: &str) -> bool
作用:快速看看当前 MCP 管理器里有没有某个名字的服务器。它只是查登记表,不保证服务器已经启动成功。
数据流:进去的是服务器名字 → 函数取出当前最新的连接管理器,询问它是否包含这个服务器 → 出来一个布尔值 true 或 false,不等待网络启动,也不修改状态。
调用关系:调用者在尝试列资源或读资源前,可以先用它做一个轻量检查。它的位置像门口的名单检查员,只回答“名单上有没有”,真正通信仍由 list_resources 和 read_resource 完成。
McpResourceClient::list_resources78–99 ↗
async fn list_resources(
&self,
server: &str,
cursor: Option<String>,
) -> Result<McpResourcePage>
作用:向指定 MCP 服务器要一页资源列表。它支持分页,也就是资源太多时可以拿着游标继续翻下一页。
数据流:进去的是服务器名字和可选 cursor 游标(像翻页书签)→ 如果有游标,就包装成分页请求参数;然后取当前最新管理器去请求服务器;拿到 rmcp 返回的资源后逐个转成项目内部统一的 Resource → 出来 McpResourcePage,里面有资源列表和下一页游标;如果请求或转换失败,就返回错误。
调用关系:它是外部代码浏览 MCP 资源时的主要入口。它把网络请求交给 McpConnectionManager 的 list_resources,把单个资源格式转换交给 resource_from_rmcp,最后把结果整理成这个文件定义的 McpResourcePage。
McpResourceClient::read_resource102–114 ↗
async fn read_resource(&self, server: &str, uri: &str) -> Result<McpResourceReadResult>
作用:读取某个 MCP 资源的具体内容。调用者给服务器名和 URI,它返回文本或二进制内容列表。
数据流:进去的是服务器名字和资源 URI(资源地址)→ 函数用外部的 new 构造读取请求参数,再从当前管理器发起 read_resource 请求;返回内容逐个转成项目内部的 ResourceContent → 出来 McpResourceReadResult;如果服务器读取失败或格式转换失败,就带着错误返回。
调用关系:它通常在用户或上层逻辑选中某个资源后被调用。它把真正的读取交给 McpConnectionManager,把内容格式转换交给 resource_content_from_rmcp,自己负责把这些步骤串起来并返回统一结果。
调用图:外部调用 1 个(new)。
resource_from_rmcp117–120 ↗
fn resource_from_rmcp(resource: rmcp::model::Resource) -> Result<Resource>
作用:把 rmcp 库里的资源对象转换成 Codex 自己协议里使用的 Resource。这样项目其他地方不用直接依赖 rmcp 的内部格式。
数据流:进去的是 rmcp::model::Resource → 函数先用 to_value 把它转成 JSON 这种中间格式,再调用 Resource::from_mcp_value 解析成内部 Resource → 出来统一格式的 Resource;序列化或转换失败时,会附上更清楚的错误说明。
调用关系:它服务于 McpResourceClient::list_resources。list_resources 从服务器拿到一批 rmcp 资源后,会靠它逐个翻译成项目内部能理解、能继续传递的资源对象。
调用图:调用 1 个内部函数(from_mcp_value);外部调用 1 个(to_value)。
resource_content_from_rmcp122–126 ↗
fn resource_content_from_rmcp(content: rmcp::model::ResourceContents) -> Result<ResourceContent>
作用:把 rmcp 库返回的资源内容转换成 Codex 自己使用的 ResourceContent。它负责把外部协议对象翻译成内部统一对象。
数据流:进去的是 rmcp::model::ResourceContents → 函数先用 to_value 转成 JSON 中间值,再用 from_value 反序列化成 ResourceContent → 出来资源内容;如果任何一步失败,就返回带上下文的错误。
调用关系:它服务于 McpResourceClient::read_resource。read_resource 拿到服务器返回的内容后,会把每一项交给它转换,最后组成 McpResourceReadResult 给上层使用。
调用图:外部调用 2 个(from_value, to_value)。
codex-mcp/src/mcp/mod.rs源码 ↗
MCP 可以理解成一种“外部工具接线板”:不同服务器提供工具、资源和登录能力,Codex 要决定哪些能用、怎么连、是否需要用户批准。这个文件就做这层统筹。它先把配置里的服务器整理成运行时真正可见的服务器,比如 ChatGPT 托管的 apps 服务器只有在对应登录可用时才保留;再计算 OAuth 登录状态,OAuth 是一种让用户授权第三方服务的登录方式;然后创建连接管理器去列工具、读资源、收集服务器状态。它还负责把插件和工具的来源关系记下来,方便界面说明“这个工具来自哪个插件”。另外,它会清洗工具名字,避免用户自定义名称里有接口不允许的字符。
McpSnapshotDetail::include_resources61–63 ↗
fn include_resources(self) -> bool
作用:判断这次收集 MCP 状态时,要不要连资源和资源模板也一起查。简单说,就是决定“只看工具和登录状态”,还是“完整盘点”。
数据流:输入是一个快照详细程度枚举值 → 它检查这个值是不是 Full → 输出 true 或 false,告诉后续流程是否需要读取资源信息,不改动任何外部状态。
调用关系:它被收集快照的内部流程用到;当 collect_mcp_server_status_snapshot_from_manager 准备列资源时,会用这个判断来跳过不必要的请求。
调用图:外部调用 1 个(matches!)。
qualified_mcp_tool_name_prefix66–70 ↗
fn qualified_mcp_tool_name_prefix(server_name: &str) -> String
作用:给某个 MCP 服务器生成工具名前缀,让模型看到的工具名能带上服务器来源。这样不同服务器里同名工具不容易撞车。
数据流:输入服务器名 → 拼成类似 mcp__服务器名__ 的前缀 → 再交给 sanitize_responses_api_tool_name 清洗非法字符 → 输出一个接口可接受的安全前缀。
调用关系:它依赖 sanitize_responses_api_tool_name 做最后把关;通常在把 MCP 工具展示给模型前使用,保证命名既有来源信息又符合接口规则。
调用图:调用 1 个内部函数(sanitize_responses_api_tool_name);外部调用 1 个(format!)。
mcp_permission_prompt_is_auto_approved74–93 ↗
fn mcp_permission_prompt_is_auto_approved(
approval_policy: AskForApproval,
permission_profile: &PermissionProfile,
context: McpPermissionPromptAutoApproveContext,
) -> bool
作用:判断一次 MCP 权限提示能不能自动通过,而不是弹给用户确认。它保护用户,同时也让明确安全的场景少打扰用户。
数据流:输入批准策略、权限配置和上下文里的工具批准模式 → 先看工具是否明确设置为自动批准 → 再看全局策略是否允许不询问 → 最后检查沙盒权限是否足够宽松 → 输出是否自动批准。
调用关系:它位于 MCP 调工具前的安全判断环节;连接管理器或调用流程需要决定是否弹权限提示时,会参考这个规则。
ToolPluginProvenance::plugin_display_names_for_connector_id159–164 ↗
fn plugin_display_names_for_connector_id(&self, connector_id: &str) -> &[String]
作用:按连接器 ID 查这个连接器来自哪些插件,并返回给人看的插件名字。连接器 ID 可以理解成某个外部服务入口的编号。
数据流:输入 connector_id 字符串 → 到内部表里查匹配的插件显示名列表 → 找到就返回列表切片,找不到就返回空列表,不复制也不修改数据。
调用关系:它会被 with_app_plugin_sources 使用,用来给 app 工具补充“来源插件”的说明,方便用户知道工具是谁带来的。
调用图:被 1 处调用(with_app_plugin_sources)。
ToolPluginProvenance::plugin_display_names_for_mcp_server_name166–171 ↗
fn plugin_display_names_for_mcp_server_name(&self, server_name: &str) -> &[String]
作用:按 MCP 服务器名查它对应的插件显示名。这样界面或日志可以说明某个服务器是哪个插件注册出来的。
数据流:输入 server_name → 到按服务器名建立的来源表中查找 → 输出插件显示名列表;如果没有来源记录,就输出空列表。
调用关系:它是 ToolPluginProvenance 这个来源账本的查询口之一;其他展示层或决策层需要按服务器解释插件来源时会用它。
ToolPluginProvenance::plugin_id_for_mcp_server_name173–177 ↗
fn plugin_id_for_mcp_server_name(&self, server_name: &str) -> Option<&str>
作用:按 MCP 服务器名查插件的内部 ID。显示名给人看,插件 ID 更像系统内部用来准确识别插件的身份证号。
数据流:输入 server_name → 在服务器名到插件 ID 的表里查 → 找到就返回字符串引用,找不到就返回 None,不改动任何内容。
调用关系:它服务于需要精确识别插件的流程;和显示名查询函数一起,把“给人看”和“给系统认”的来源信息分开。
ToolPluginProvenance::is_selected_plugin_mcp_server179–181 ↗
fn is_selected_plugin_mcp_server(&self, server_name: &str) -> bool
作用:判断某个 MCP 服务器是不是用户或配置选中的插件服务器。它用于区分“只是存在”和“当前被选用”。
数据流:输入 server_name → 在已选插件服务器集合里检查是否包含 → 输出 true 或 false。
调用关系:它是来源账本的内部辅助判断;后续在筛选或标注插件服务器时,可以用它确认服务器是否属于当前选择范围。
ToolPluginProvenance::from_config183–231 ↗
fn from_config(config: &McpConfig) -> Self
作用:从 MCP 配置里整理出一份“工具和插件来源账本”。这份账本回答:连接器、服务器分别来自哪些插件,哪些插件服务器被选中了。
数据流:输入 McpConfig → 遍历插件摘要和服务器目录里的插件归属 → 填充连接器到插件名、服务器到插件名、服务器到插件 ID、已选服务器集合 → 对名字排序去重 → 输出 ToolPluginProvenance。
调用关系:它由 tool_plugin_provenance 包一层调用;read_mcp_resource 和收集状态快照时都会需要这份来源信息,再交给连接管理器使用。
调用图:被 1 处调用(tool_plugin_provenance);外部调用 2 个(default, vec!)。
host_owned_codex_apps_enabled234–236 ↗
fn host_owned_codex_apps_enabled(config: &McpConfig, auth: Option<&CodexAuth>) -> bool
作用:判断 ChatGPT 托管的 Codex apps MCP 服务器当前能不能启用。它不只看配置开关,还要看用户登录是否是合适的 Codex 后端登录。
数据流:输入 McpConfig 和可选登录信息 → 检查 apps_enabled 是否为真,并确认 auth 存在且使用 Codex 后端 → 输出是否允许启用。
调用关系:effective_mcp_servers_from_configured、read_mcp_resource 和 collect_mcp_server_status_snapshot_with_detail 都会调用它,用来避免在没登录或登录类型不对时暴露托管 apps 服务器。
调用图:被 3 处调用(collect_mcp_server_status_snapshot_with_detail, effective_mcp_servers_from_configured, read_mcp_resource)。
configured_mcp_servers238–240 ↗
fn configured_mcp_servers(config: &McpConfig) -> HashMap<String, McpServerConfig>
作用:拿出配置目录里已经注册好的 MCP 服务器列表。它是从“配置视角”进入“运行视角”的第一步。
数据流:输入 McpConfig → 读取 mcp_server_catalog 中的 configured_servers → 输出服务器名到服务器配置的映射。
调用关系:它被 effective_mcp_servers 调用;后者会在这个基础上继续做登录过滤,得到真正运行时可用的服务器。
调用图:被 1 处调用(effective_mcp_servers)。
effective_mcp_servers242–247 ↗
fn effective_mcp_servers(
config: &McpConfig,
auth: Option<&CodexAuth>,
) -> HashMap<String, EffectiveMcpServer>
作用:算出当前真正应该启用的 MCP 服务器。它会把静态配置和当前登录状态合起来看。
数据流:输入 McpConfig 和可选登录信息 → 先调用 configured_mcp_servers 取出已配置服务器 → 再交给 effective_mcp_servers_from_configured 做运行时过滤 → 输出服务器名到 EffectiveMcpServer 的映射。
调用关系:read_mcp_resource 和 collect_mcp_server_status_snapshot_with_detail 都从它开始确定要连接哪些服务器;它把具体过滤工作交给 effective_mcp_servers_from_configured。
调用图:调用 2 个内部函数(configured_mcp_servers, effective_mcp_servers_from_configured);被 2 处调用(collect_mcp_server_status_snapshot_with_detail, read_mcp_resource)。
effective_mcp_servers_from_configured253–266 ↗
fn effective_mcp_servers_from_configured(
configured_servers: HashMap<String, McpServerConfig>,
config: &McpConfig,
auth: Option<&CodexAuth>,
) -> HashMap<String, EffectiveMcpServer>
作用:把一份已经物化好的服务器配置表,转换成当前运行时可用的服务器表。特别是会根据登录状态移除不能用的内置 apps 服务器。
数据流:输入已配置服务器表、McpConfig 和可选登录信息 → 把每个 McpServerConfig 包成 EffectiveMcpServer → 调用 host_owned_codex_apps_enabled 判断 apps 服务器是否可用 → 必要时删除 codex_apps → 输出过滤后的服务器表。
调用关系:它被 effective_mcp_servers 调用;这个拆分让调用者也可以传入已经合并过的服务器配置,而不用重新从目录读取。
调用图:调用 1 个内部函数(host_owned_codex_apps_enabled);被 1 处调用(effective_mcp_servers)。
tool_plugin_provenance268–270 ↗
fn tool_plugin_provenance(config: &McpConfig) -> ToolPluginProvenance
作用:生成插件来源信息的公开入口。调用者不用知道内部怎么整理,只要给配置就能拿到账本。
数据流:输入 McpConfig → 调用 ToolPluginProvenance::from_config → 输出整理好的 ToolPluginProvenance。
调用关系:read_mcp_resource 和 collect_mcp_server_status_snapshot_with_detail 会调用它,然后把结果传给 McpConnectionManager,让连接过程能携带插件来源信息。
调用图:调用 1 个内部函数(from_config);被 2 处调用(collect_mcp_server_status_snapshot_with_detail, read_mcp_resource)。
read_mcp_resource272–319 ↗
async fn read_mcp_resource(
config: &McpConfig,
auth: Option<&CodexAuth>,
runtime_context: McpRuntimeContext,
server: &str,
uri: &str,
) -> anyhow::Result<ReadResourceResult>
作用:从指定 MCP 服务器读取一个指定 URI 的资源。URI 可以理解成资源地址,比如“去这个服务器读这份材料”。
数据流:输入配置、登录、运行上下文、服务器名和资源 URI → 计算当前有效服务器并只保留目标服务器 → 计算登录状态 → 创建连接管理器 → 请求读取资源 → 取消临时连接任务 → 输出读取结果或错误。
调用关系:它是资源读取请求的高层入口;它会调用 effective_mcp_servers、compute_auth_statuses、tool_plugin_provenance,并把实际联网和读取工作交给 McpConnectionManager。
调用图:调用 7 个内部函数(codex_apps_tools_cache_key, new, compute_auth_statuses, effective_mcp_servers, host_owned_codex_apps_enabled, tool_plugin_provenance, default);外部调用 4 个(new, new, new, unbounded)。
collect_mcp_server_status_snapshot_with_detail331–399 ↗
async fn collect_mcp_server_status_snapshot_with_detail(
config: &McpConfig,
auth: Option<&CodexAuth>,
submit_id: String,
runtime_context: McpRuntimeContext,
detail: McpSnapshotDet
作用:收集当前 MCP 服务器的状态快照,比如有哪些服务器、有哪些工具、登录状态怎样,必要时还包括资源列表。它像给 MCP 系统一次体检。
数据流:输入配置、登录、提交 ID、运行上下文和快照详细程度 → 算出有效服务器;如果没有服务器就返回空快照 → 计算认证状态 → 创建连接管理器 → 调用 collect_mcp_server_status_snapshot_from_manager 收集详情 → 取消连接任务 → 输出 McpServerStatusSnapshot。
调用关系:它是状态展示或诊断流程的高层入口;它负责准备环境和连接管理器,具体列工具、列资源和格式转换交给 collect_mcp_server_status_snapshot_from_manager。
调用图:调用 8 个内部函数(codex_apps_tools_cache_key, new, compute_auth_statuses, collect_mcp_server_status_snapshot_from_manager, effective_mcp_servers, host_owned_codex_apps_enabled, tool_plugin_provenance, default);外部调用 4 个(new, new, new, unbounded)。
sanitize_responses_api_tool_name404–419 ↗
fn sanitize_responses_api_tool_name(name: &str) -> String
作用:把工具名清洗成 Responses API 能接受的格式。Responses API 是模型调用工具的接口,它要求名字只能包含英文字母、数字和下划线等安全字符。
数据流:输入任意字符串 → 逐个字符检查,允许 ASCII 字母、数字和下划线,其他字符替换成下划线 → 如果结果为空就用一个下划线兜底 → 输出安全名字。
调用关系:qualified_mcp_tool_name_prefix 会用它清洗前缀;normalize_tools_for_model_with_prefix 也会用它处理给模型看的工具名,避免接口因为非法名称报错。
调用图:被 2 处调用(qualified_mcp_tool_name_prefix, normalize_tools_for_model_with_prefix);外部调用 1 个(with_capacity)。
codex_apps_mcp_bearer_token_env_var421–428 ↗
fn codex_apps_mcp_bearer_token_env_var() -> Option<String>
作用:检查环境变量里是否有 Codex connectors 的令牌变量可用。Bearer token 是 HTTP 请求里常见的“通行证”。
数据流:读取 CODEX_CONNECTORS_TOKEN 环境变量 → 如果变量存在且不是空白,返回变量名;如果不存在或是空字符串,返回 None;如果内容不是合法 Unicode,也返回变量名让下游按环境变量处理。
调用关系:mcp_server_config_for_url 会调用它,把令牌环境变量名写进 HTTP 传输配置,之后连接服务器时就能从环境变量拿认证信息。
调用图:被 1 处调用(mcp_server_config_for_url);外部调用 1 个(var)。
normalize_codex_apps_base_url430–439 ↗
fn normalize_codex_apps_base_url(base_url: &str) -> String
作用:把 ChatGPT 相关的基础网址整理成后续拼接接口地址时更稳定的形式。它主要补齐 backend-api 这段路径。
数据流:输入基础 URL → 去掉末尾多余斜杠 → 如果是 chatgpt.com 或 chat.openai.com 且还没有 backend-api,就补上 /backend-api → 输出标准化后的 URL。
调用关系:codex_apps_mcp_url_for_base_url 和 hosted_plugin_runtime_mcp_server_config 都会先调用它,避免同一个服务因为 URL 写法不同而拼出错误地址。
调用图:被 2 处调用(codex_apps_mcp_url_for_base_url, hosted_plugin_runtime_mcp_server_config);外部调用 1 个(format!)。
codex_apps_mcp_url_for_base_url441–451 ↗
fn codex_apps_mcp_url_for_base_url(base_url: &str) -> String
作用:根据基础网址拼出 Codex apps MCP 服务的完整地址。它处理不同部署路径,让调用者不用关心 URL 细节。
数据流:输入基础 URL → 先调用 normalize_codex_apps_base_url 标准化 → 根据 URL 是否含 backend-api 或 api/codex 选择正确路径 → 输出完整 MCP endpoint 地址。
调用关系:codex_apps_mcp_server_config 调用它得到服务 URL,再交给 mcp_server_config_for_url 生成完整服务器配置。
调用图:调用 1 个内部函数(normalize_codex_apps_base_url);被 1 处调用(codex_apps_mcp_server_config);外部调用 1 个(format!)。
codex_apps_mcp_server_config453–461 ↗
fn codex_apps_mcp_server_config(
chatgpt_base_url: &str,
apps_mcp_product_sku: Option<&str>,
) -> McpServerConfig
作用:生成 ChatGPT 托管 Codex apps MCP 服务器的配置。调用者只要给基础网址和可选产品 SKU,就能得到可连接的服务器设置。
数据流:输入 chatgpt_base_url 和可选 apps_mcp_product_sku → 调用 codex_apps_mcp_url_for_base_url 拼服务地址 → 调用 mcp_server_config_for_url 填充 HTTP 传输、超时、请求头等配置 → 输出 McpServerConfig。
调用关系:它是创建内置 codex_apps 服务器配置的入口;底层通用配置细节交给 mcp_server_config_for_url。
调用图:调用 2 个内部函数(codex_apps_mcp_url_for_base_url, mcp_server_config_for_url)。
hosted_plugin_runtime_mcp_server_config464–475 ↗
fn hosted_plugin_runtime_mcp_server_config(
chatgpt_base_url: &str,
apps_mcp_product_sku: Option<&str>,
) -> McpServerConfig
作用:生成 ChatGPT 托管插件运行时的 MCP 服务器配置。这个运行时由 plugin-service 提供,用来承载托管插件能力。
数据流:输入基础 URL 和可选产品 SKU → 标准化 URL → 确保路径落在 backend-api 或 api/codex 体系下 → 拼出 /ps/mcp 地址 → 调用 mcp_server_config_for_url 生成配置 → 输出 McpServerConfig。
调用关系:它和 codex_apps_mcp_server_config 类似,都是内置托管 MCP 服务的配置工厂;共同复用 mcp_server_config_for_url 来保持传输参数一致。
调用图:调用 2 个内部函数(mcp_server_config_for_url, normalize_codex_apps_base_url);外部调用 1 个(format!)。
mcp_server_config_for_url477–504 ↗
fn mcp_server_config_for_url(url: String, apps_mcp_product_sku: Option<&str>) -> McpServerConfig
作用:把一个 MCP 服务 URL 包装成完整的 McpServerConfig。它统一设置 HTTP 连接方式、认证环境变量、产品请求头和默认超时。
数据流:输入完整 URL 和可选产品 SKU → 如果有 SKU 就生成 X-OpenAI-Product-Sku 请求头 → 调用 codex_apps_mcp_bearer_token_env_var 查令牌环境变量 → 填入 StreamableHttp 传输、默认环境 ID、启用状态、30 秒启动超时等字段 → 输出服务器配置。
调用关系:codex_apps_mcp_server_config 和 hosted_plugin_runtime_mcp_server_config 都把最终 URL 交给它;它是两个托管服务配置生成流程的共同底座。
调用图:调用 1 个内部函数(codex_apps_mcp_bearer_token_env_var);被 2 处调用(codex_apps_mcp_server_config, hosted_plugin_runtime_mcp_server_config);外部调用 2 个(from_secs, new)。
protocol_tool_from_rmcp_tool506–520 ↗
fn protocol_tool_from_rmcp_tool(name: &str, tool: &rmcp::model::Tool) -> Option<Tool>
作用:把 rmcp 库里的工具对象转换成 Codex 协议里的 Tool。rmcp 是底层 MCP 客户端库,Codex 自己还需要统一的协议格式。
数据流:输入工具名和 rmcp::model::Tool → 先转成 JSON 值 → 再用 Tool::from_mcp_value 转成 Codex 协议工具 → 成功返回 Some(Tool),失败就写警告日志并返回 None。
调用关系:collect_mcp_server_status_snapshot_from_manager 在整理工具清单时调用它;转换失败的工具会被跳过,避免一个坏工具破坏整个快照。
调用图:调用 1 个内部函数(from_mcp_value);被 1 处调用(collect_mcp_server_status_snapshot_from_manager);外部调用 2 个(to_value, warn!)。
auth_statuses_from_entries522–529 ↗
fn auth_statuses_from_entries(
auth_status_entries: &HashMap<String, crate::mcp::auth::McpAuthStatusEntry>,
) -> HashMap<String, McpAuthStatus>
作用:把详细认证状态条目压缩成对外快照需要的认证状态表。它只保留每个服务器当前是什么登录状态。
数据流:输入服务器名到 McpAuthStatusEntry 的映射 → 遍历每一项,取出 entry.auth_status → 输出服务器名到 McpAuthStatus 的新映射。
调用关系:collect_mcp_server_status_snapshot_from_manager 在构造最终快照时调用它,把 compute_auth_statuses 得到的内部条目整理成协议层能展示的数据。
调用图:被 1 处调用(collect_mcp_server_status_snapshot_from_manager)。
convert_mcp_resources531–568 ↗
fn convert_mcp_resources(
resources: HashMap<String, Vec<rmcp::model::Resource>>,
) -> HashMap<String, Vec<Resource>>
作用:把底层 rmcp 资源列表转换成 Codex 协议里的 Resource 列表。资源可以理解成 MCP 服务器提供给模型读取的资料或对象。
数据流:输入服务器名到 rmcp 资源列表的映射 → 对每个资源先转 JSON,再转 Codex Resource → 成功的留下,失败的记录警告并丢弃 → 输出服务器名到 Resource 列表的映射。
调用关系:collect_mcp_server_status_snapshot_from_manager 在收集资源快照后调用它;它负责把底层库格式翻译成 Codex 协议格式,并把坏数据隔离掉。
调用图:被 1 处调用(collect_mcp_server_status_snapshot_from_manager)。
convert_mcp_resource_templates570–608 ↗
fn convert_mcp_resource_templates(
resource_templates: HashMap<String, Vec<rmcp::model::ResourceTemplate>>,
) -> HashMap<String, Vec<ResourceTemplate>>
作用:把底层 rmcp 的资源模板转换成 Codex 协议里的 ResourceTemplate。资源模板像“可填参数的资源地址格式”。
数据流:输入服务器名到 rmcp 资源模板列表的映射 → 每个模板先序列化成 JSON,再转成 Codex ResourceTemplate → 成功的加入结果,失败的记录 URI 模板和名称等线索后跳过 → 输出转换后的映射。
调用关系:collect_mcp_server_status_snapshot_from_manager 在整理资源模板时调用它;它让快照使用统一协议格式,同时避免异常模板影响其他服务器数据。
调用图:被 1 处调用(collect_mcp_server_status_snapshot_from_manager)。
collect_mcp_server_status_snapshot_from_manager610–656 ↗
async fn collect_mcp_server_status_snapshot_from_manager(
mcp_connection_manager: &McpConnectionManager,
auth_status_entries: HashMap<String, crate::mcp::auth::McpAuthStatusEntry>,
server_
作用:从已经建好的 MCP 连接管理器里真正收集服务器快照。它负责同时问服务器要工具、资源、资源模板和服务器信息,再整理成一份结果。
数据流:输入连接管理器、认证状态条目、服务器名列表和详细程度 → 并发列出所有工具;如果需要完整详情,也列出资源和资源模板 → 读取可用服务器信息 → 把 rmcp 工具和资源转换成 Codex 协议格式 → 合并认证状态 → 输出 McpServerStatusSnapshot。
调用关系:它由 collect_mcp_server_status_snapshot_with_detail 调用;上层负责创建和关闭连接管理器,它负责向管理器要数据,并调用 protocol_tool_from_rmcp_tool、convert_mcp_resources、convert_mcp_resource_templates、auth_statuses_from_entries 做格式整理。
调用图:调用 5 个内部函数(list_available_server_infos, auth_statuses_from_entries, convert_mcp_resource_templates, convert_mcp_resources, protocol_tool_from_rmcp_tool);被 1 处调用(collect_mcp_server_status_snapshot_with_detail);外部调用 2 个(new, join!)。
工具暴露与调用
这些文件整理 MCP 工具以便模型可见,将其适配到核心工具运行时,并执行已批准的 MCP 工具调用,包括文件参数改写。
codex-mcp/src/tools.rs源码 ↗
MCP 可以理解成“外部工具接入协议”,不同服务器会报上来一批工具。问题是:服务器里的真实名字要保留,因为真正执行时还得按原名找回去;但给模型看的名字又必须符合接口限制,比如不能太长、字符要安全、不能重名。这个文件就像一个“工具登记处”:先按配置筛掉不允许用的工具,再把工具名和命名空间清洗成模型能用的样子,遇到重名就加一小段哈希(像指纹一样的短编号)区分。它还会检查工具说明里的特殊标记,把“文件路径参数”改成更清楚的字符串说明,避免模型误以为要上传文件内容。核心结构是 ToolInfo,保存原始信息和模型可见信息;ToolFilter 负责白名单、黑名单;normalize_tools_for_model_with_prefix 负责最终改名和去重。
ToolInfo::canonical_tool_name59–61 ↗
fn canonical_tool_name(&self) -> ToolName
作用:把一个工具的“模型可见命名空间”和“模型可见工具名”合成一个标准工具名。别人需要稳定地引用这个工具时会用它。
数据流:进去的是 ToolInfo 自己,里面有 callable_namespace 和 callable_name → 它调用 ToolName::namespaced 把这两段拼成统一格式 → 出来的是一个 ToolName,不改动原来的 ToolInfo。
调用关系:它是工具名字的统一出口。tool_name、build_mcp_search_text 和 create_tool_spec 在需要展示、搜索或生成工具规格时,会向它要这个标准名字。
调用图:调用 1 个内部函数(namespaced);被 3 处调用(tool_name, build_mcp_search_text, create_tool_spec)。
declared_openai_file_input_param_names64–79 ↗
fn declared_openai_file_input_param_names(
meta: Option<&Map<String, JsonValue>>,
) -> Vec<String>
作用:从工具的附加信息里找出哪些输入参数其实代表“本地文件路径”。这样后面可以把这些参数的说明改得更适合模型理解。
数据流:进去的是可选的 JSON 元信息 meta → 它查找 openai/fileParams 这个数组,过滤掉不是字符串或空字符串的项目 → 出来是一串参数名;如果没有元信息或没有声明,就返回空列表。
调用关系:tool_with_model_visible_input_schema 会先调用它,判断工具有没有文件路径参数;如果有,才继续去修改输入说明。
调用图:被 1 处调用(tool_with_model_visible_input_schema);外部调用 1 个(new)。
ToolFilter::from_config91–103 ↗
fn from_config(cfg: &McpServerConfig) -> Self
作用:根据某个 MCP 服务器的配置,做出一张“哪些工具能用、哪些工具不能用”的过滤规则表。
数据流:进去的是 McpServerConfig,里面可能有 enabled_tools 和 disabled_tools → 它把这些列表变成 HashSet,也就是方便快速查名字的集合 → 出来的是 ToolFilter,里面保存白名单和黑名单。
调用关系:它通常在读取服务器配置后使用,生成的 ToolFilter 会交给 filter_tools,真正筛掉不该暴露的工具。
ToolFilter::allows105–113 ↗
fn allows(&self, tool_name: &str) -> bool
作用:判断某个工具名是否允许使用。它同时考虑白名单和黑名单:没进白名单不行,被黑名单点名也不行。
数据流:进去的是一个工具名字符串 → 如果配置了 enabled 白名单,它先检查这个名字是否在白名单里;再检查是否在 disabled 黑名单里 → 出来是 true 或 false,表示放行或拦下。
调用关系:filter_tools 会对每个 ToolInfo 调用它。它是实际筛选过程里的“门卫”。
tool_with_model_visible_input_schema119–132 ↗
fn tool_with_model_visible_input_schema(tool: &Tool) -> Tool
作用:给模型返回工具信息前,把文件路径参数的输入说明改成模型更容易正确使用的样子。没有文件参数时,它会尽量原样返回。
数据流:进去的是一个 MCP Tool → 它先读取 meta 里声明的文件路径参数名;如果没有,就克隆原工具返回;如果有,就复制输入 schema,并把对应参数改成字符串或字符串数组说明 → 出来的是一个新的 Tool,原始工具不被破坏。
调用关系:它调用 declared_openai_file_input_param_names 找文件参数,再调用 mask_input_schema_for_file_path_params 修改 schema。相关测试会检查:没有文件参数时不乱改,有文件参数时确实被遮罩成模型可见形式。
调用图:调用 2 个内部函数(declared_openai_file_input_param_names, mask_input_schema_for_file_path_params);被 2 处调用(tool_with_model_visible_input_schema_leaves_tools_without_file_params_unchanged, tool_with_model_visible_input_schema_masks_file_params);外部调用 3 个(new, Object, clone)。
filter_tools134–139 ↗
fn filter_tools(tools: Vec<ToolInfo>, filter: &ToolFilter) -> Vec<ToolInfo>
作用:把一批工具按配置过滤掉,只留下允许给模型或系统使用的工具。
数据流:进去的是 ToolInfo 列表和 ToolFilter → 它逐个拿工具的原始 tool.name 去问 filter.allows → 出来的是过滤后的 ToolInfo 列表,被禁用或不在白名单里的工具会消失。
调用关系:hard_refresh_codex_apps_tools_cache、listed_tools、start_server_task 等流程在刷新缓存、列工具或启动服务器任务时会用它。测试 filter_tools_applies_per_server_filters 会验证它按每个服务器的规则工作。
调用图:被 4 处调用(hard_refresh_codex_apps_tools_cache, filter_tools_applies_per_server_filters, listed_tools, start_server_task)。
normalize_tools_for_model_with_prefix149–249 ↗
fn normalize_tools_for_model_with_prefix(
tools: I,
prefix_mcp_tool_names: bool,
) -> Vec<ToolInfo>
作用:把 MCP 工具整理成模型可见的最终名字:清洗非法字符、可选加 mcp__ 前缀、处理重名、限制总长度,并保留执行时需要的原始名字。
数据流:进去的是一批 ToolInfo 和一个是否加旧式 mcp__ 前缀的开关 → 它先去掉完全重复的原始工具,再清洗命名空间和工具名;如果不同来源清洗后撞名,就加哈希后缀;最后确保命名空间加工具名不超过 64 字节且全局唯一 → 出来的是排序稳定、名字安全的 ToolInfo 列表。
调用关系:这是本文件最核心的流水线。刷新工具缓存和列出全部工具时会调用它;它把具体小活交给 sanitize_responses_api_tool_name、callable_namespace_with_prefix、append_hash_suffix、append_namespace_hash_suffix 和 unique_callable_parts。多组测试专门覆盖重名、非法字符、超长名字和重复工具。
调用图:调用 5 个内部函数(sanitize_responses_api_tool_name, append_hash_suffix, append_namespace_hash_suffix, callable_namespace_with_prefix, unique_callable_parts);被 9 处调用(hard_refresh_codex_apps_tools_cache, list_all_tools, test_normalize_tools_disambiguates_sanitized_namespace_collisions, test_normalize_tools_disambiguates_sanitized_tool_name_collisions, test_normalize_tools_duplicated_names_skipped, test_normalize_tools_keeps_hyphenated_mcp_tools_callable, test_normalize_tools_long_names_same_server, test_normalize_tools_sanitizes_invalid_characters, test_normalize_tools_short_non_duplicated_names);外部调用 6 个(new, new, new, new, format!, warn!)。
callable_namespace_with_prefix265–271 ↗
fn callable_namespace_with_prefix(namespace: &str, prefix_mcp_tool_names: bool) -> String
作用:按需要给工具命名空间加上历史兼容用的 mcp__ 前缀。这样旧格式的工具名还可以被保留。
数据流:进去的是一个命名空间和 prefix_mcp_tool_names 开关 → 如果开关没开,或命名空间已经以 mcp__ 开头,就原样返回;否则在前面加 mcp__ → 出来的是处理后的命名空间字符串。
调用关系:normalize_tools_for_model_with_prefix 在清洗每个工具的命名空间时调用它,保证是否使用旧前缀这件事集中在一个地方处理。
调用图:被 1 处调用(normalize_tools_for_model_with_prefix);外部调用 1 个(format!)。
mask_input_schema_for_file_path_params273–288 ↗
fn mask_input_schema_for_file_path_params(input_schema: &mut JsonValue, file_params: &[String])
作用:在一个工具输入 schema 里,找到那些被声明为文件路径的字段,并把它们的字段说明改掉。
数据流:进去的是可修改的 JSON schema 和文件参数名列表 → 它进入 schema.properties,逐个查找对应字段 → 找到的字段会交给 mask_input_property_schema 改写;找不到 properties 或字段名不存在就跳过。
调用关系:tool_with_model_visible_input_schema 在确认有文件路径参数后调用它。它自己不决定哪些参数是文件参数,只负责按名单改 schema。
调用图:调用 1 个内部函数(mask_input_property_schema);被 1 处调用(tool_with_model_visible_input_schema);外部调用 1 个(as_object_mut)。
mask_input_property_schema290–317 ↗
fn mask_input_property_schema(schema: &mut JsonValue)
作用:把单个输入字段改成“这里要填本地文件的绝对路径”的清楚说明。它避免模型把这个参数理解成别的文件上传形式。
数据流:进去的是某个字段的 JSON schema → 它保留或补充 description,并加入一句固定提示:这里需要绝对本地文件路径;然后清空旧结构,改成 string,或者如果原来像数组就改成字符串数组 → 出来是被就地改写后的字段 schema。
调用关系:mask_input_schema_for_file_path_params 会对每个文件路径字段调用它。它是实际改写字段说明的最小零件。
调用图:被 1 处调用(mask_input_schema_for_file_path_params);外部调用 4 个(String, as_object_mut, format!, json!)。
sha1_hex319–324 ↗
fn sha1_hex(s: &str) -> String
作用:把一段文字算成 SHA-1 哈希的十六进制字符串。哈希可以理解成给文字做一个短而稳定的“指纹”。
数据流:进去的是字符串 → 它用 SHA-1 算法读取这些字节并生成摘要 → 出来的是十六进制格式的哈希文本,不改动输入。
调用关系:callable_name_hash_suffix 会调用它,再截取前面一小段作为工具名后缀,用来区分撞名的工具。
调用图:被 1 处调用(callable_name_hash_suffix);外部调用 2 个(new, format!)。
callable_name_hash_suffix326–329 ↗
fn callable_name_hash_suffix(raw_identity: &str) -> String
作用:根据原始身份信息生成一个短后缀,比如 _ 后面跟 12 个哈希字符,用来给重名工具做区分。
数据流:进去的是 raw_identity,也就是能代表工具来源和原名的一串信息 → 它调用 sha1_hex 生成哈希,再取前 12 位并加上下划线 → 出来的是一个稳定的短后缀。
调用关系:fit_callable_parts_with_hash 会用它生成安全后缀。normalize_tools_for_model_with_prefix 间接依赖它来解决名字冲突。
调用图:调用 1 个内部函数(sha1_hex);被 1 处调用(fit_callable_parts_with_hash);外部调用 1 个(format!)。
append_hash_suffix331–333 ↗
fn append_hash_suffix(value: &str, raw_identity: &str) -> String
作用:给某个名字后面追加哈希后缀。它常用于两个清洗后的工具名看起来一样时,把它们重新区分开。
数据流:进去的是原名字 value 和原始身份 raw_identity → 它根据身份生成哈希后缀,然后拼到 value 后面 → 出来的是带短指纹的新名字。
调用关系:normalize_tools_for_model_with_prefix 在工具名撞车时会调用它;append_namespace_hash_suffix 在处理命名空间时也可能借用它。
调用图:被 2 处调用(append_namespace_hash_suffix, normalize_tools_for_model_with_prefix);外部调用 1 个(format!)。
append_namespace_hash_suffix335–346 ↗
fn append_namespace_hash_suffix(namespace: &str, raw_identity: &str) -> String
作用:专门给命名空间追加哈希后缀,同时尽量照顾以双下划线结尾的旧式命名习惯。
数据流:进去的是 namespace 和 raw_identity → 如果 namespace 以 __ 结尾,它会把哈希插到结尾分隔符前;否则直接追加哈希 → 出来的是更不容易重名的命名空间。
调用关系:normalize_tools_for_model_with_prefix 在发现多个不同来源的命名空间清洗后变成同一个名字时调用它。它会在普通情况转交 append_hash_suffix 完成拼接。
调用图:调用 1 个内部函数(append_hash_suffix);被 1 处调用(normalize_tools_for_model_with_prefix);外部调用 1 个(format!)。
truncate_name348–350 ↗
fn truncate_name(value: &str, max_len: usize) -> String
作用:把名字截短到指定长度。这里用字符来截,避免简单按字节截坏文字。
数据流:进去的是一个字符串和最大长度 → 它从开头取最多 max_len 个字符 → 出来的是截短后的字符串。
调用关系:fit_callable_parts_with_hash 会调用它,在工具名或命名空间太长时保留前面一段,再给哈希后缀留位置。
调用图:被 1 处调用(fit_callable_parts_with_hash)。
fit_callable_parts_with_hash352–370 ↗
fn fit_callable_parts_with_hash(
namespace: &str,
tool_name: &str,
raw_identity: &str,
reserved_len: usize,
) -> (String, String)
作用:在 64 字节限制里,尽量塞下命名空间、工具名和哈希后缀。名字太长时,它会舍弃一部分原名,但保留哈希来保证可区分。
数据流:进去的是 namespace、tool_name、raw_identity 和预留长度 reserved_len → 它先生成哈希后缀;如果工具名还有空间,就截短工具名前缀再加后缀;如果空间更紧,就连命名空间也截短,只把工具名变成哈希后缀 → 出来是一对可用的 namespace 和 tool_name。
调用关系:unique_callable_parts 在发现名字太长或已被占用时调用它。它是“既要短、又要不撞名”的具体计算步骤。
调用图:调用 2 个内部函数(callable_name_hash_suffix, truncate_name);被 1 处调用(unique_callable_parts);外部调用 1 个(format!)。
unique_callable_parts372–399 ↗
fn unique_callable_parts(
namespace: &str,
tool_name: &str,
raw_identity: &str,
used_names: &mut HashSet<String>,
reserved_len: usize,
) -> (String, String)
作用:确保最终给模型看的完整工具名既不超长,也没有和前面的工具重复。它是最后一道保险。
数据流:进去的是候选 namespace、tool_name、raw_identity、已用名字集合和预留长度 → 它先尝试原样使用;如果太长或重复,就调用 fit_callable_parts_with_hash 加哈希;如果极少数情况下哈希后仍重复,就换一个尝试编号继续算 → 出来是一对真正可用且已登记占用的 namespace 和 tool_name,同时 used_names 会被更新。
调用关系:normalize_tools_for_model_with_prefix 在最终生成每个模型工具名前调用它。它把名字唯一性这件事落实到全局已用名字集合里。
调用图:调用 1 个内部函数(fit_callable_parts_with_hash);被 1 处调用(normalize_tools_for_model_with_prefix);外部调用 1 个(format!)。
core/src/mcp_tool_exposure.rs源码 ↗
MCP 可以理解成一套“外部工具菜单”,模型能通过它调用别的能力,比如应用、搜索或服务。这个文件做的事,就是整理这份菜单:先挑出模型允许看到的工具,再按来源分成普通 MCP 工具和 Codex Apps 里的工具。Codex Apps 工具还要多过一道关:只有当前已连接的应用、并且符合配置里的工具策略,才会被放行。最后它会看搜索工具是否开启、工具数量是否太多、以及功能开关是否要求延后展示。如果不需要延后,就把工具直接交给模型;如果需要延后,就先不给模型直接看,而是放进 deferred_tools,之后通过工具搜索再找出来。这样既减少模型一开始要读的“工具说明书”数量,也降低误用危险工具的机会。
build_mcp_tool_exposure22–54 ↗
fn build_mcp_tool_exposure(
all_mcp_tools: &[McpToolInfo],
connectors: Option<&[connectors::AppInfo]>,
config: &Config,
search_tool_enabled: bool,
) -> McpToolExposure
作用:这是本文件的总入口,用来生成最终的“工具展示方案”。它决定工具是直接给模型看,还是先延后保存,等需要搜索时再提供。
数据流:输入是一整批 MCP 工具、可选的应用连接信息、全局配置,以及搜索工具是否开启。它先收集普通 MCP 工具,再在有应用连接信息时收集符合条件的 Codex Apps 工具;接着根据功能开关、搜索是否开启、工具数量是否达到阈值来判断是否延后展示。输出是 McpToolExposure:里面要么有 direct_tools,要么把工具放进 deferred_tools,同时不会修改原始工具列表。
调用关系:它由 built_tools 在准备可用工具时调用,像一个总调度员。它把筛选普通工具的活交给 filter_non_codex_apps_mcp_tools_only,把筛选 Codex Apps 工具的活交给 filter_codex_apps_mcp_tools,并在需要检查应用工具策略时触发外部的策略评估器创建。
调用图:调用 2 个内部函数(filter_codex_apps_mcp_tools, filter_non_codex_apps_mcp_tools_only);被 1 处调用(built_tools);外部调用 1 个(new)。
filter_non_codex_apps_mcp_tools_only56–64 ↗
fn filter_non_codex_apps_mcp_tools_only(mcp_tools: &[McpToolInfo]) -> Vec<McpToolInfo>
作用:这个函数专门挑出“不是 Codex Apps 服务器提供的”普通 MCP 工具,并且只保留模型允许看到的工具。
数据流:输入是一批 MCP 工具。它逐个查看工具的服务器名字,排除来自 Codex Apps 专用服务器的工具,再检查这个工具是否对模型可见;符合条件的工具会被复制到一个新列表里。输出是过滤后的普通 MCP 工具列表,原列表不变。
调用关系:它只被 build_mcp_tool_exposure 调用,负责第一轮基础筛选。它不判断连接器和策略,只做简单的来源和可见性检查,好让总入口后面再处理更复杂的应用工具。
调用图:被 1 处调用(build_mcp_tool_exposure);外部调用 1 个(iter)。
filter_codex_apps_mcp_tools66–105 ↗
fn filter_codex_apps_mcp_tools(
mcp_tools: &[McpToolInfo],
connectors: &[connectors::AppInfo],
config: &Config,
) -> Vec<McpToolInfo>
作用:这个函数专门筛选 Codex Apps 提供的 MCP 工具。它确保只有当前真的连接上的应用工具,并且配置策略允许的工具,才会给模型使用。
数据流:输入是一批 MCP 工具、当前可用的应用连接信息和配置。它先把允许的连接器 id 做成一个集合,方便快速查找;再创建应用工具策略评估器。随后逐个检查工具:必须来自 Codex Apps 服务器、必须对模型可见、必须带有连接器 id、连接器必须在当前允许列表里,并且策略评估结果必须是启用。通过所有检查的工具会被复制到新列表里输出。
调用关系:它由 build_mcp_tool_exposure 在存在应用连接信息时调用,是应用工具的安全门卫。它会使用配置层创建 AppToolPolicyEvaluator,再把每个工具的名字、标题和风险提示交给策略判断,最后把合格工具交回给总入口。
调用图:调用 1 个内部函数(new);被 1 处调用(build_mcp_tool_exposure);外部调用 2 个(iter, iter)。
core/src/tools/handlers/mcp.rs源码 ↗
MCP 可以理解成一种“外接工具插座”:别的服务把工具插到这里,Codex 就能使用。这个文件里的 McpHandler 就是插座转换头。它先把外部工具的信息改造成 Codex 认识的工具规格,给工具名补上 mcp__ 前缀,避免和内置工具撞名。真正调用时,它只接受函数式参数,把参数交给 MCP 调用函数去跑,再把结果、耗时、图片细节支持情况等包装成统一输出。它还会在调用前后准备钩子数据,方便安全检查、审计或改写参数。另一个重要点是并行调用:如果服务器明确允许,或工具标注为只读,它就允许同时跑多个调用;否则会更保守,避免多个任务一起改坏同一份外部数据。
McpHandler::new38–41 ↗
fn new(tool_info: ToolInfo) -> Result<Self, serde_json::Error>
作用:创建一个 MCP 工具处理器。外部 MCP 工具刚被注册进系统时,会用它把原始工具信息变成 Codex 能执行、能展示的形式。
数据流:输入是一份 ToolInfo,也就是 MCP 工具的名字、服务器、参数说明等信息;它调用 create_tool_spec 生成工具规格;最后产出一个 McpHandler,里面保存原始工具信息和转换后的规格。如果规格转换失败,就把 JSON 错误返回出去。
调用关系:它是这个处理器的入口。运行时注册 MCP 工具时会调用它,测试里也用它搭建处理器;它把具体的规格生成工作交给 create_tool_spec。
调用图:调用 1 个内部函数(create_tool_spec);被 7 处调用(mcp_post_tool_use_payload_uses_prefixed_tool_name_args_and_result, mcp_pre_tool_use_payload_keeps_builtin_like_tool_names_namespaced, mcp_pre_tool_use_payload_uses_prefixed_tool_name_and_raw_args, mcp_updated_input_rewrites_builtin_like_tool_names_as_mcp, search_info_uses_connector_name_for_output_namespace_description, search_info_uses_mcp_tool_metadata_and_parameter_names, add_mcp_runtime_tools)。
McpHandler::hook_tool_name43–45 ↗
fn hook_tool_name(&self) -> HookToolName
作用:生成给钩子系统使用的 MCP 工具名。这样调用前检查、调用后记录时,大家看到的是稳定且不会撞名的名字。
数据流:它先读取当前工具的标准名字,再把命名空间和工具名拼起来,最后确保前面有 mcp__ 前缀;输出是 HookToolName,也就是钩子系统专用的工具名。
调用关系:调用前、真正执行、调用后都会用到它。它把取名这件小事交给 tool_name、join_tool_name 和 ensure_mcp_prefix,自己负责组合成钩子能用的对象。
调用图:调用 4 个内部函数(tool_name, ensure_mcp_prefix, join_tool_name, new);被 3 处调用(handle_call, post_tool_use_payload, pre_tool_use_payload)。
join_tool_name48–57 ↗
fn join_tool_name(tool_name: &ToolName) -> String
作用:把工具的命名空间和真实名字拼成一个扁平名字。这样“filesystem 的 read_file”会变成类似 filesystem__read_file 的形式。
数据流:输入是 ToolName;如果有命名空间,它会去掉命名空间尾部多余下划线、工具名前面多余下划线,再用双下划线连接;如果没有命名空间,就直接返回工具名。
调用关系:它只被 McpHandler::hook_tool_name 使用,是命名流程里的拼接步骤,避免不同来源的下划线造成奇怪名字。
调用图:被 1 处调用(hook_tool_name);外部调用 1 个(format!)。
ensure_mcp_prefix59–65 ↗
fn ensure_mcp_prefix(name: &str) -> String
作用:确保工具名一定带有 mcp__ 前缀。这个前缀像标签一样告诉系统:这是外部 MCP 工具,不是内置工具。
数据流:输入是一个工具名字符串;如果它已经以 mcp__ 开头,就原样返回;否则就在前面补上 mcp__;输出是安全统一的名字。
调用关系:它只被 McpHandler::hook_tool_name 调用,是防止 MCP 工具和内置工具重名的最后一道保险。
调用图:被 1 处调用(hook_tool_name);外部调用 1 个(format!)。
McpHandler::tool_name68–70 ↗
fn tool_name(&self) -> ToolName
作用:返回这个 MCP 工具在 Codex 里的标准名字。别的系统需要识别工具身份时会用它。
数据流:它从保存的 ToolInfo 里读取 MCP 工具信息,并调用 canonical_tool_name 得到规范化后的 ToolName;不改动任何状态。
调用关系:这是 ToolExecutor 接口的一部分。hook_tool_name 会借它拿到标准名字,再加工成钩子系统使用的名字。
调用图:调用 1 个内部函数(canonical_tool_name);被 1 处调用(hook_tool_name)。
McpHandler::spec72–74 ↗
fn spec(&self) -> ToolSpec
作用:返回这个工具的规格说明。规格说明告诉模型和系统:这个工具叫什么、参数长什么样、属于哪个工具命名空间。
数据流:它读取处理器里已经保存好的 ToolSpec,并复制一份返回;不会重新生成,也不会修改原数据。
调用关系:这是 ToolExecutor 接口的一部分。search_info 会用它来生成可搜索信息,工具注册和展示时也会依赖它。
调用图:被 1 处调用(search_info);外部调用 1 个(clone)。
McpHandler::supports_parallel_tool_calls76–87 ↗
fn supports_parallel_tool_calls(&self) -> bool
作用:判断这个 MCP 工具能不能并行调用。简单说,就是能不能让多个任务同时用它,而不容易互相踩到。
数据流:它读取服务器是否明确允许并行调用;如果没有,再看工具有没有标注 read_only_hint,也就是“只读,不会改数据”的提示;只要其中一个成立,就返回 true,否则返回 false。
调用关系:这是调度工具调用时会看的能力声明。它不调用别的本地函数,但会影响上层运行器是放心并发执行,还是保守地排队执行。
McpHandler::search_info89–113 ↗
fn search_info(&self) -> Option<ToolSearchInfo>
作用:生成工具搜索用的信息。这样当系统需要从一堆工具里找合适工具时,可以根据名字、描述、参数名等内容匹配到它。
数据流:它从 ToolInfo 里挑出来源名称和来源描述,再调用 build_mcp_search_text 汇总可搜索文字,并拿当前工具规格一起交给 ToolSearchInfo::from_spec;输出是可选的搜索信息。
调用关系:这是工具发现流程的一部分。它调用 spec 取得工具规格,调用 build_mcp_search_text 收集搜索关键词,最后交给搜索信息构造器。
调用图:调用 3 个内部函数(spec, build_mcp_search_text, from_spec)。
McpHandler::handle115–117 ↗
fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_>
作用:接收一次工具调用,并把它变成异步任务。异步任务就是可以先交出去执行、之后再等结果的任务。
数据流:输入是一份 ToolInvocation,里面有会话、轮次、调用编号和参数;它把真正的执行交给 handle_call,并把返回的未来任务装箱返回。
调用关系:这是 ToolExecutor 接口里真正被上层运行器调用的方法。它本身很薄,只负责把同步入口接到异步的 McpHandler::handle_call。
调用图:调用 1 个内部函数(handle_call);外部调用 1 个(pin)。
McpHandler::handle_call121–161 ↗
async fn handle_call(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError>
作用:真正执行一次 MCP 工具调用。它检查参数格式,调用外部 MCP 工具,然后把结果包装成 Codex 统一认识的输出。
数据流:输入是 ToolInvocation;它拆出会话、当前轮次、调用编号和载荷,只接受函数参数形式。如果载荷不对,就返回给模型看的错误。载荷正确时,它记录开始时间,调用 handle_mcp_tool_call,把服务器名、工具名、钩子名和参数传过去;拿到结果后,再补上耗时、图片细节支持情况和截断策略,最后输出 boxed_tool_output 包好的 McpToolOutput。
调用关系:它被 McpHandler::handle 调用,是执行链路的核心。它向下委托 handle_mcp_tool_call 去和 MCP 工具打交道,向旁边调用 hook_tool_name 做命名,最后把结果交给统一工具输出系统。
调用图:调用 3 个内部函数(handle_mcp_tool_call, boxed_tool_output, hook_tool_name);被 1 处调用(handle);外部调用 4 个(clone, now, can_request_original_image_detail, RespondToModel)。
McpHandler::telemetry_tags165–176 ↗
fn telemetry_tags(
&'a self,
_invocation: &'a ToolInvocation,
) -> futures::future::BoxFuture<'a, ToolTelemetryTags>
作用:准备日志和监控用的标签。这样之后看统计时,能知道这次调用来自哪个 MCP 服务器,必要时也能知道服务器来源。
数据流:它读取 tool_info 里的 server_name 和可选的 server_origin;生成一个异步返回的标签列表,至少包含 mcp_server,如果有来源再加 mcp_server_origin。
调用关系:这是 CoreToolRuntime 接口的一部分。工具运行框架会在记录遥测信息时调用它,它不参与执行结果,只给观察和排查问题提供上下文。
调用图:外部调用 2 个(pin, vec!)。
McpHandler::pre_tool_use_payload178–187 ↗
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload>
作用:准备“工具调用前”的钩子数据。钩子可以理解成门口安检:工具真的执行前,先把名字和输入拿去检查或记录。
数据流:输入是一次 ToolInvocation;如果载荷不是函数参数,就返回 None;如果是,它生成钩子工具名,并把原始参数字符串转成 JSON 值,最后输出 PreToolUsePayload。
调用关系:它是 CoreToolRuntime 的调用前扩展点。它使用 hook_tool_name 统一命名,用 mcp_hook_tool_input 把参数变成钩子更容易处理的格式。
调用图:调用 2 个内部函数(hook_tool_name, mcp_hook_tool_input)。
McpHandler::with_updated_hook_input189–210 ↗
fn with_updated_hook_input(
&self,
mut invocation: ToolInvocation,
updated_input: Value,
) -> Result<ToolInvocation, FunctionCallError>
作用:把钩子改写后的输入放回工具调用里。比如安全策略或中间层决定修改参数时,就靠它更新实际要执行的参数。
数据流:输入是原 ToolInvocation 和新的 JSON 输入;如果原载荷是函数参数,它把新 JSON 序列化成字符串,替换 invocation.payload;如果序列化失败或载荷类型不支持,就返回给模型看的错误;成功时返回更新后的调用。
调用关系:这是 CoreToolRuntime 的输入改写接口。它通常发生在 pre_tool_use_payload 之后、handle 之前,让钩子系统有机会改变真正传给 MCP 工具的参数。
McpHandler::post_tool_use_payload211–228 ↗
fn post_tool_use_payload(
&self,
invocation: &ToolInvocation,
result: &dyn crate::tools::context::ToolOutput,
) -> Option<PostToolUsePayload>
作用:准备“工具调用后”的钩子数据。它把工具输入和工具响应整理出来,方便记录、审计或后处理。
数据流:输入是原调用和工具输出;如果原载荷不是函数参数,就返回 None;否则它生成钩子工具名,取出调用编号、工具输入和工具响应,组成 PostToolUsePayload 返回。
调用关系:这是 CoreToolRuntime 的调用后扩展点。它在工具执行完成后被运行框架调用,并依赖输出对象提供 post_tool_use_input 和 post_tool_use_response。
调用图:调用 3 个内部函数(hook_tool_name, post_tool_use_input, post_tool_use_response)。
create_tool_spec231–255 ↗
fn create_tool_spec(tool_info: &ToolInfo) -> Result<ToolSpec, serde_json::Error>
作用:把 MCP 的工具描述转换成 Codex Responses API 能理解的工具规格。没有这一步,模型就不知道这个外部工具该怎么叫、参数该怎么填。
数据流:输入是 ToolInfo;它先取标准工具名,再调用 mcp_tool_to_responses_api_tool 把 MCP 工具结构转成 Responses API 的函数工具;然后挑选命名空间描述,优先用 namespace_description,其次用 connector_name 生成一句描述;最后输出 ToolSpec::Namespace。
调用关系:它只被 McpHandler::new 调用,是注册 MCP 工具时的规格转换工厂。它把外部 MCP 世界的格式翻译成 Codex 内部工具注册表能用的格式。
调用图:调用 1 个内部函数(canonical_tool_name);被 1 处调用(new);外部调用 3 个(mcp_tool_to_responses_api_tool, Namespace, vec!)。
mcp_hook_tool_input257–263 ↗
fn mcp_hook_tool_input(raw_arguments: &str) -> Value
作用:把 MCP 工具的原始参数字符串整理成钩子系统好读的 JSON。这样钩子不用自己猜参数是不是 JSON。
数据流:输入是原始参数字符串;如果全是空白,就返回一个空 JSON 对象;如果能按 JSON 解析,就返回解析后的值;如果解析失败,就把原字符串当成 JSON 字符串返回。
调用关系:它被 pre_tool_use_payload 调用。它负责在调用前钩子和 MCP 原始参数之间做一个温和转换:能结构化就结构化,不能也不丢内容。
调用图:被 1 处调用(pre_tool_use_payload);外部调用 3 个(new, Object, from_str)。
build_mcp_search_text265–311 ↗
fn build_mcp_search_text(info: &ToolInfo) -> String
作用:拼出一大段用于搜索匹配的文字。它把工具可能被人想到的名字、描述、来源和参数名都放进去,提高找对工具的机会。
数据流:输入是 ToolInfo;它收集标准工具名、可调用名、原始工具名、服务器名、标题、描述、连接器名、命名空间描述、插件显示名,以及输入 schema 里的参数名;参数名会排序,最后所有片段用空格拼成一个字符串。
调用关系:它被 McpHandler::search_info 调用,是搜索信息生成流程里的关键词收集器。search_info 再把这段文字和工具规格合成真正的搜索对象。
调用图:调用 1 个内部函数(canonical_tool_name);被 1 处调用(search_info);外部调用 1 个(vec!)。
tests::mcp_pre_tool_use_payload_uses_prefixed_tool_name_and_raw_args332–366 ↗
async fn mcp_pre_tool_use_payload_uses_prefixed_tool_name_and_raw_args()
作用:测试调用前钩子会使用带 mcp__ 前缀的工具名,并且能把 JSON 参数原样变成结构化输入。
数据流:它准备一段 create_entities 的 JSON 参数,创建测试会话和 McpHandler;然后调用 pre_tool_use_payload;最后断言输出里的工具名是 mcp__memory__create_entities,输入内容和原 JSON 一致。
调用关系:这是对 pre_tool_use_payload、hook_tool_name 和 mcp_hook_tool_input 的行为验证。它通过 McpHandler::new 搭建测试对象,确保真实注册路径也能工作。
调用图:调用 2 个内部函数(make_session_and_context, new);外部调用 3 个(assert_eq!, tool_info, json!)。
tests::mcp_pre_tool_use_payload_keeps_builtin_like_tool_names_namespaced369–393 ↗
async fn mcp_pre_tool_use_payload_keeps_builtin_like_tool_names_namespaced()
作用:测试即使 MCP 工具名看起来像内置工具,也会保留 MCP 命名空间,不会被误当成内置工具。
数据流:它创建一个命名空间叫 mcp__foo、工具名叫 exec_command 的调用;执行 pre_tool_use_payload;最后检查钩子名是 mcp__foo__exec_command,参数仍是 message 的 JSON。
调用关系:这个测试保护 hook_tool_name、join_tool_name 和 ensure_mcp_prefix 的组合行为。它防止外部工具用类似内置工具的名字时发生身份混淆。
调用图:调用 2 个内部函数(make_session_and_context, new);外部调用 3 个(assert_eq!, tool_info, json!)。
tests::mcp_updated_input_rewrites_builtin_like_tool_names_as_mcp396–424 ↗
async fn mcp_updated_input_rewrites_builtin_like_tool_names_as_mcp()
作用:测试钩子改写输入时,长得像内置工具的 MCP 工具仍然按 MCP 工具处理。
数据流:它先准备 message 为 hello 的函数参数,再调用 with_updated_hook_input 把输入改成 rewritten;最后拆开 payload,确认里面的参数字符串已经变成改写后的 JSON。
调用关系:这是对 with_updated_hook_input 的验证。它沿用 McpHandler::new 创建处理器,并确保改写流程不会因为工具名像内置工具而走错路。
调用图:调用 4 个内部函数(make_session_and_context, new, new, namespaced);外部调用 7 个(new, new, assert_eq!, tool_info, json!, panic!, new)。
tests::mcp_post_tool_use_payload_uses_prefixed_tool_name_args_and_result427–482 ↗
async fn mcp_post_tool_use_payload_uses_prefixed_tool_name_args_and_result()
作用:测试调用后钩子会拿到正确的 MCP 工具名、实际工具输入和工具返回结果。
数据流:它构造一个 read_file 调用和一份假的 McpToolOutput,里面有文本内容、结构化结果、耗时和截断策略;调用 post_tool_use_payload;最后断言输出包含 mcp__filesystem__read_file、调用编号、工具输入和整理后的响应。
调用关系:这个测试覆盖 post_tool_use_payload 和 hook_tool_name 的配合,也验证 McpToolOutput 提供给调用后钩子的输入、响应格式符合预期。
调用图:调用 4 个内部函数(make_session_and_context, new, new, namespaced);外部调用 9 个(new, from_millis, new, assert_eq!, tool_info, json!, Bytes, new, vec!)。
tests::mcp_read_only_hint_supports_parallel_calls_without_server_opt_in485–494 ↗
fn mcp_read_only_hint_supports_parallel_calls_without_server_opt_in()
作用:测试只读工具即使服务器没有明确说支持并行,也会被允许并行调用。
数据流:它创建一个工具信息,把工具注解设成 read_only true;再创建 McpHandler 并调用 supports_parallel_tool_calls;最后断言结果为 true。
调用关系:这是对 supports_parallel_tool_calls 的一个关键规则验证。它保证“只读”这个安全提示能被调度器利用,提高并发效率。
tests::mcp_parallel_calls_require_read_only_hint_or_server_opt_in497–520 ↗
fn mcp_parallel_calls_require_read_only_hint_or_server_opt_in()
作用:测试并行调用必须有明确理由:要么工具只读,要么服务器主动允许。否则系统要保守一点。
数据流:它分别构造三种工具:没有只读提示、明确不是只读、服务器明确允许并行;然后逐个创建 McpHandler 并检查 supports_parallel_tool_calls 的返回值,前两种为 false,最后一种为 true。
调用关系:这个测试和前一个测试一起锁定并行调用策略。它保护上层调度逻辑,避免可写工具被错误地同时执行。
tests::tool_info522–541 ↗
fn tool_info(server_name: &str, callable_namespace: &str, tool_name: &str) -> ToolInfo
作用:给测试快速造一份最小可用的 MCP 工具信息。这样每个测试不用重复写一大堆 ToolInfo 字段。
数据流:输入是服务器名、可调用命名空间和工具名;它填充默认字段,创建一个带简单 object 输入 schema 的 rmcp 工具;输出是一份 ToolInfo。
调用关系:它是本文件测试里的小工厂函数。多个测试用它生成基础工具信息,再按需要修改只读提示或并行能力。
core/src/mcp_tool_call.rs源码 ↗
MCP 可以理解成一套“让模型调用外部工具”的协议,比如调用某个应用、插件或远程服务。这个文件像机场安检加调度员:先把工具参数从文字解析成 JSON(一种结构化数据格式),再查这个工具的说明和安全提示,判断是否需要用户、Guardian 审核器或钩子程序批准。批准后,它会补上线程号、沙箱状态等元信息,把请求发给 MCP 服务器执行。执行前后,它会向 Session 发出“开始”和“完成”事件,让界面能显示进度;还会截断过大的结果,避免把几兆内容塞进历史记录;同时记录耗时、成功失败、连接器信息等指标。它还处理 Codex Apps 的特殊情况,比如授权登录提示、文件参数改写、连接器使用统计,以及“以后别再问我”的审批记忆和配置持久化。
handle_mcp_tool_call111–307 ↗
async fn handle_mcp_tool_call(
sess: Arc<Session>,
turn_context: &Arc<TurnContext>,
call_id: String,
server: String,
tool_name: String,
hook_tool_name: HookToolName,
argume
作用:这是一次 MCP 工具调用的入口函数。它决定这个工具能不能跑、要不要先问用户或审核器,并把开始、跳过、完成这些状态串起来。
数据流:输入 Session、当前回合上下文、调用编号、服务器名、工具名和参数字符串 → 先把参数解析成 JSON,查工具元数据和审批策略,必要时发起审批 → 输出 HandledMcpToolCall,里面有给模型看的工具结果和实际使用的工具输入;如果被拒绝或配置禁用,也会发出跳过事件并返回错误结果。
调用关系:它由上层的 handle_call 在模型请求调用工具时触发。它会把元数据查询交给 lookup_mcp_tool_metadata,把审批交给 maybe_request_mcp_tool_approval;如果通过,就交给 handle_approved_mcp_tool_call,若不能执行则用 notify_mcp_tool_call_skip 收尾,并用 emit_mcp_call_metrics 记账。
调用图:调用 9 个内部函数(default, new, custom_mcp_tool_approval_mode, emit_mcp_call_metrics, handle_approved_mcp_tool_call, lookup_mcp_tool_metadata, maybe_request_mcp_tool_approval, notify_mcp_tool_call_skip, notify_mcp_tool_call_started);被 1 处调用(handle_call);外部调用 6 个(Object, error!, format!, from_error_text, from_result, new)。
handle_approved_mcp_tool_call320–415 ↗
async fn handle_approved_mcp_tool_call(
sess: &Session,
turn_context: &TurnContext,
call_id: &str,
invocation: McpInvocation,
metadata: Option<&McpToolApprovalMetadata>,
item_m
作用:这个函数只处理“已经允许执行”的工具调用。它真正把请求送出去,并负责执行前后的记录、事件、指标和特殊清理。
数据流:输入已批准的调用信息、元数据和事件元数据 → 标记可能污染记忆的外部上下文,改写 OpenAI 文件参数,建立追踪 span,调用 execute_mcp_tool_call 执行,再截断事件里的大结果 → 输出 HandledMcpToolCall,并向会话发送完成事件、记录耗时和应用使用情况。
调用关系:它只在 handle_mcp_tool_call 判断可以执行后被调用。它把底层执行交给 execute_mcp_tool_call,把事件发送交给 notify_mcp_tool_call_completed,把指标交给 emit_mcp_call_metrics,并用 mcp_tool_call_span 包住整个调用以便追踪。
调用图:调用 10 个内部函数(rewrite_mcp_tool_arguments_for_openai_files, build_mcp_tool_call_request_meta, emit_mcp_call_metrics, execute_mcp_tool_call, maybe_mark_thread_memory_mode_polluted, maybe_track_codex_app_used, mcp_tool_call_span, notify_mcp_tool_call_completed, record_mcp_result_span_telemetry, truncate_mcp_tool_result_for_event);被 1 处调用(handle_mcp_tool_call);外部调用 4 个(now, current, from_result, warn!)。
emit_mcp_call_metrics417–440 ↗
fn emit_mcp_call_metrics(
turn_context: &TurnContext,
status: &str,
tool_name: &str,
connector_id: Option<&str>,
connector_name: Option<&str>,
duration: Option<Duration>,
)
作用:这个函数给每次 MCP 调用记一笔统计。它记录调用次数,也在有耗时时记录调用花了多久。
数据流:输入当前回合、成功或失败状态、工具名、连接器信息和可选耗时 → 先生成干净的指标标签,再写入计数器和耗时指标 → 输出为空,但遥测系统里会多出一条统计记录。
调用关系:handle_mcp_tool_call 在工具被跳过时会用它记结果;handle_approved_mcp_tool_call 在工具执行完后也会用它。标签准备工作由 mcp_call_metric_tags 完成。
调用图:调用 1 个内部函数(mcp_call_metric_tags);被 2 处调用(handle_approved_mcp_tool_call, handle_mcp_tool_call)。
mcp_call_metric_tags442–460 ↗
fn mcp_call_metric_tags(
status: &str,
tool_name: &str,
connector_id: Option<&str>,
connector_name: Option<&str>,
) -> Vec<(&'static str, String)>
作用:这个函数把工具调用的统计标签整理成安全格式。标签就是统计系统里用来筛选的字段,比如工具名、状态、连接器名。
数据流:输入状态、工具名和可选连接器信息 → 去掉统计系统不喜欢的字符,并忽略空连接器字段 → 输出一组键值标签。
调用关系:它是 emit_mcp_call_metrics 的小助手,专门负责把原始文字变成可用于指标上报的标签。
调用图:被 1 处调用(emit_mcp_call_metrics);外部调用 2 个(sanitize_metric_tag_value, vec!)。
mcp_tool_call_span462–495 ↗
fn mcp_tool_call_span(
session: &Session,
turn_context: &TurnContext,
fields: McpToolCallSpanFields<'_>,
) -> Span
作用:这个函数创建一段追踪记录。追踪记录可以理解成给一次工具调用套上的“行程单”,方便排查它去了哪里、用了多久。
数据流:输入会话、回合上下文和服务器/工具/连接器字段 → 判断传输方式,创建 tracing span,并从服务器地址里提取主机和端口 → 输出一个 Span,后续执行会挂在这个 Span 下面。
调用关系:handle_approved_mcp_tool_call 在真正执行工具前调用它。它会请 record_server_fields 帮忙把 URL 里的服务器信息填进追踪字段。
调用图:调用 1 个内部函数(record_server_fields);被 1 处调用(handle_approved_mcp_tool_call);外部调用 1 个(info_span!)。
record_server_fields506–519 ↗
fn record_server_fields(span: &Span, url: Option<&str>)
作用:这个函数从服务器 URL 里提取主机名和端口,写进追踪记录。这样日志里不只知道“调用了某服务”,还知道大致连到了哪里。
数据流:输入一个 Span 和可选 URL 字符串 → 尝试解析 URL,拿到 host 和 port 后写入 Span → 输出为空,只改变追踪记录里的字段。
调用关系:它由 mcp_tool_call_span 调用,是追踪信息的补充步骤;如果 URL 缺失或解析失败,它会安静地什么也不做。
调用图:被 1 处调用(mcp_tool_call_span);外部调用 2 个(record, parse)。
record_mcp_result_span_telemetry521–553 ↗
fn record_mcp_result_span_telemetry(span: &Span, result: Option<&CallToolResult>)
作用:这个函数从工具返回结果里的特殊遥测信息中,挑出可以写进追踪记录的字段。比如目标 ID,或者服务器是否触发了用户流程。
数据流:输入 Span 和可选工具结果 → 在结果的 _meta/codex/telemetry/span 里查字段,目标 ID 太长就截短,然后写入 Span → 输出为空,只更新追踪记录。
调用关系:handle_approved_mcp_tool_call 在 execute_mcp_tool_call 返回后调用它。它会用 truncate_str_to_char_boundary 安全截断文字,避免切坏中文或表情这类多字节字符。
调用图:调用 1 个内部函数(truncate_str_to_char_boundary);被 1 处调用(handle_approved_mcp_tool_call);外部调用 1 个(record)。
truncate_str_to_char_boundary555–560 ↗
fn truncate_str_to_char_boundary(value: &str, max_chars: usize) -> &str
作用:这个函数按“字符”而不是按“字节”截短字符串。这样不会把中文、emoji 等字符从中间切坏。
数据流:输入一段字符串和最大字符数 → 找到第 max_chars 个字符的安全边界 → 输出截短后的字符串切片;如果原文不够长,就原样返回。
调用关系:它被 record_mcp_result_span_telemetry 使用,用来限制写入追踪字段的目标 ID 长度。
调用图:被 1 处调用(record_mcp_result_span_telemetry)。
execute_mcp_tool_call562–610 ↗
async fn execute_mcp_tool_call(
sess: &Session,
turn_context: &TurnContext,
call_id: &str,
invocation: &McpInvocation,
rewritten_arguments: Option<JsonValue>,
metadata: Option<
作用:这个函数是真正发起 MCP 工具调用的地方。它负责把请求元信息补齐、调用服务器,并把结果整理成模型能安全接收的样子。
数据流:输入会话、回合、调用编号、调用内容、改写后的参数、元数据和请求 meta → 加线程号、沙箱状态、调用追踪信息,然后调用 sess.call_tool;结果会按模型能力去掉不支持的图片内容,并可能触发 Codex Apps 授权提示 → 输出成功的 CallToolResult 或错误文字。
调用关系:它由 handle_approved_mcp_tool_call 调用。它把 meta 补充交给 with_mcp_tool_call_thread_id_meta 和 augment_mcp_tool_request_meta_with_sandbox_state,把结果清理交给 sanitize_mcp_tool_result_for_model,把授权补救交给 maybe_request_codex_apps_auth_elicitation。
调用图:调用 4 个内部函数(augment_mcp_tool_request_meta_with_sandbox_state, maybe_request_codex_apps_auth_elicitation, sanitize_mcp_tool_result_for_model, with_mcp_tool_call_thread_id_meta);被 1 处调用(handle_approved_mcp_tool_call);外部调用 1 个(call_tool)。
maybe_request_codex_apps_auth_elicitation612–683 ↗
async fn maybe_request_codex_apps_auth_elicitation(
sess: &Session,
turn_context: &TurnContext,
call_id: &str,
server: &str,
metadata: Option<&McpToolApprovalMetadata>,
result:
作用:这个函数在 Codex Apps 工具因为未授权而失败时,可能向用户发起登录或授权提示。它不是每次都问,只在服务器、功能开关和审批策略都允许时才问。
数据流:输入会话、回合、调用编号、服务器名、工具元数据和原始结果 → 判断是不是 Codex Apps、功能是否开启、当前审批策略是否允许;如果结果里能构造授权计划,就向用户发起 elicitation(让服务器请求用户补充信息的流程)→ 如果用户接受,刷新工具缓存并返回“授权已完成”的结果;否则原样返回旧结果。
调用关系:它由 execute_mcp_tool_call 在工具调用后调用。授权成功后,它会交给 refresh_codex_apps_after_connector_auth 更新连接器和工具缓存。
调用图:调用 1 个内部函数(refresh_codex_apps_after_connector_auth);被 1 处调用(execute_mcp_tool_call);外部调用 4 个(String, auth_elicitation_completed_result, build_auth_elicitation_plan, request_mcp_server_elicitation)。
refresh_codex_apps_after_connector_auth685–704 ↗
async fn refresh_codex_apps_after_connector_auth(sess: &Session, turn_context: &TurnContext)
作用:这个函数在用户完成连接器授权后,刷新 Codex Apps 能看到的工具列表和连接器缓存。这样刚授权的应用马上能被后续调用使用。
数据流:输入会话和回合上下文 → 要求 MCP 连接管理器强制刷新 Codex Apps 工具缓存,再用当前认证信息更新可访问连接器缓存 → 输出为空;失败时只写警告日志,不中断主流程。
调用关系:它由 maybe_request_codex_apps_auth_elicitation 在用户接受授权提示后调用,是授权补救流程的收尾动作。
调用图:调用 1 个内部函数(refresh_accessible_connectors_cache_from_mcp_tools);被 1 处调用(maybe_request_codex_apps_auth_elicitation);外部调用 1 个(warn!)。
augment_mcp_tool_request_meta_with_sandbox_state706–751 ↗
async fn augment_mcp_tool_request_meta_with_sandbox_state(
sess: &Session,
turn_context: &TurnContext,
server: &str,
mut meta: Option<serde_json::Value>,
) -> anyhow::Result<Option<ser
作用:这个函数把当前沙箱状态塞进工具请求的 meta 里。沙箱可以理解成“限制工具能碰哪些文件和网络”的安全盒子。
数据流:输入会话、回合、服务器名和已有 meta → 先问服务器是否支持接收沙箱状态;支持的话,把权限配置、沙箱策略、工作目录等序列化成 JSON 并插入 meta → 输出更新后的 meta。
调用关系:它由 execute_mcp_tool_call 在真正 call_tool 前调用。这样支持该能力的 MCP 服务器能知道当前运行环境的限制。
调用图:调用 2 个内部函数(permission_profile, sandbox_policy);被 1 处调用(execute_mcp_tool_call);外部调用 3 个(new, Object, to_value)。
maybe_mark_thread_memory_mode_polluted753–775 ↗
async fn maybe_mark_thread_memory_mode_polluted(
sess: &Session,
turn_context: &TurnContext,
server: &str,
)
作用:这个函数在某些外部 MCP 服务器可能带来外部上下文时,标记当前线程的记忆模式已被“污染”。意思是以后不要轻易把这段对话内容当成可长期记忆的干净资料。
数据流:输入会话、回合和服务器名 → 检查配置是否要求外部上下文禁用记忆,再问连接管理器该服务器是否会污染记忆 → 如果会,就在状态数据库里给线程打标记。
调用关系:它由 handle_approved_mcp_tool_call 在工具执行前调用,属于安全和隐私方面的预防措施。
调用图:调用 1 个内部函数(mark_thread_memory_mode_polluted);被 1 处调用(handle_approved_mcp_tool_call)。
sanitize_mcp_tool_result_for_model777–806 ↗
fn sanitize_mcp_tool_result_for_model(
supports_image_input: bool,
result: Result<CallToolResult, String>,
) -> Result<CallToolResult, String>
作用:这个函数确保工具结果不会包含模型看不懂的内容。最典型的是:如果模型不支持图片输入,就把图片块替换成一段说明文字。
数据流:输入模型是否支持图片、以及工具结果或错误 → 如果支持图片就原样返回;如果不支持,就遍历结果内容,把 type 为 image 的块替换为 text → 输出整理后的结果或原错误。
调用关系:它由 execute_mcp_tool_call 在服务器返回后调用,防止不兼容的结果直接送回模型。
调用图:被 1 处调用(execute_mcp_tool_call)。
truncate_mcp_tool_result_for_event808–850 ↗
fn truncate_mcp_tool_result_for_event(
result: &Result<CallToolResult, String>,
) -> Result<CallToolResult, String>
作用:这个函数把要写入事件历史的工具结果压到合理大小。否则一个工具返回几兆数据,可能把界面、存储或回放记录拖垮。
数据流:输入成功或失败的工具结果 → 成功时先序列化检查长度;太大就把整个结果压成一段截断预览文本;失败时也截短错误消息 → 输出适合放进事件里的结果副本。
调用关系:handle_approved_mcp_tool_call 在发送完成事件前会用它;notify_mcp_tool_call_skip 在记录被跳过的调用时也会用它。
调用图:被 2 处调用(handle_approved_mcp_tool_call, notify_mcp_tool_call_skip);外部调用 4 个(truncate_text, Bytes, to_string, vec!)。
notify_mcp_tool_call_started852–877 ↗
async fn notify_mcp_tool_call_started(
sess: &Session,
turn_context: &TurnContext,
call_id: &str,
invocation: McpInvocation,
item_metadata: McpToolCallItemMetadata,
)
作用:这个函数告诉会话系统:某个 MCP 工具调用开始了。界面可以据此显示“正在运行”。
数据流:输入会话、回合、调用编号、调用信息和显示用元数据 → 组装一个状态为 InProgress 的 McpToolCallItem → 通过 Session 发出 started 事件。
调用关系:handle_mcp_tool_call 在正式进入审批后会调用它;notify_mcp_tool_call_skip 如果发现还没发过开始事件,也会先补发。
调用图:被 2 处调用(handle_mcp_tool_call, notify_mcp_tool_call_skip);外部调用 2 个(McpToolCall, emit_turn_item_started)。
notify_mcp_tool_call_completed879–917 ↗
async fn notify_mcp_tool_call_completed(
sess: &Session,
turn_context: &TurnContext,
call_id: &str,
invocation: McpInvocation,
item_metadata: McpToolCallItemMetadata,
duration:
作用:这个函数告诉会话系统:某个 MCP 工具调用结束了。它会区分成功、工具自己报告失败、以及调用过程出错。
数据流:输入会话、回合、调用编号、调用信息、耗时和结果 → 根据结果设置 Completed 或 Failed 状态,并填入结果或错误消息 → 通过 Session 发出 completed 事件。
调用关系:handle_approved_mcp_tool_call 在真实执行完成后调用它;notify_mcp_tool_call_skip 在拒绝、取消或配置阻止时也用它生成一个失败完成事件。
调用图:被 2 处调用(handle_approved_mcp_tool_call, notify_mcp_tool_call_skip);外部调用 2 个(McpToolCall, emit_turn_item_completed)。
maybe_track_codex_app_used924–961 ↗
async fn maybe_track_codex_app_used(
sess: &Session,
turn_context: &TurnContext,
server: &str,
tool_name: &str,
)
作用:这个函数记录 Codex Apps 里的某个应用是否被用到了。它还能区分用户明确点名的应用和系统顺带选择的应用。
数据流:输入会话、回合、服务器名和工具名 → 只有服务器是 Codex Apps 时继续;查应用连接器元数据,再看用户是否明确选择过该连接器 → 向分析系统发送 app_used 事件。
调用关系:它由 handle_approved_mcp_tool_call 在工具执行完成后调用。它会用 lookup_mcp_app_usage_metadata 找连接器和应用名,再用分析客户端上报。
调用图:调用 1 个内部函数(lookup_mcp_app_usage_metadata);被 1 处调用(handle_approved_mcp_tool_call);外部调用 2 个(build_track_events_context, get_connector_selection)。
custom_mcp_tool_approval_mode990–1037 ↗
async fn custom_mcp_tool_approval_mode(
sess: &Session,
turn_context: &TurnContext,
server: &str,
tool_name: &str,
) -> AppToolApproval
作用:这个函数找出普通自定义 MCP 工具的审批模式。审批模式决定这个工具是自动允许、总是询问,还是按默认规则处理。
数据流:输入会话、回合、服务器名和工具名 → 先从合并后的配置里找该服务器和工具的设置;找不到再从启用的插件配置里找 → 输出 AppToolApproval,找不到时返回默认值。
调用关系:handle_mcp_tool_call 在非 Codex Apps、非 selected-plugin 的 MCP 服务器上调用它,用来决定后续 maybe_request_mcp_tool_approval 是否要弹审批。
调用图:被 1 处调用(handle_mcp_tool_call)。
build_mcp_tool_call_request_meta1039–1081 ↗
fn build_mcp_tool_call_request_meta(
turn_context: &TurnContext,
server: &str,
call_id: &str,
metadata: Option<&McpToolApprovalMetadata>,
) -> Option<serde_json::Value>
作用:这个函数为即将发出的 MCP 工具请求准备附加信息。meta 就像信封上的附加说明,服务器可以用它理解这次调用来自哪里、属于哪个回合。
数据流:输入回合上下文、服务器名、调用编号和工具元数据 → 放入当前 turn metadata;如果是 Codex Apps,还加入 call_id 和应用专用 meta;如果有插件 ID,也一起放入 → 输出 JSON meta,若没有任何内容则返回 None。
调用关系:handle_approved_mcp_tool_call 在执行前调用它,生成的 meta 会继续传给 execute_mcp_tool_call。
调用图:调用 1 个内部函数(effective_reasoning_effort);被 1 处调用(handle_approved_mcp_tool_call);外部调用 3 个(new, Object, String)。
with_mcp_tool_call_thread_id_meta1083–1105 ↗
fn with_mcp_tool_call_thread_id_meta(
meta: Option<serde_json::Value>,
thread_id: &str,
) -> Option<serde_json::Value>
作用:这个函数保证 MCP 请求 meta 里带有线程 ID。线程 ID 就是当前对话的身份标识,方便服务端把调用和对话对应起来。
数据流:输入已有 meta 和线程 ID → 如果 meta 是对象,就插入 threadId;如果没有 meta,就新建一个只含 threadId 的对象;如果 meta 不是对象,就保持原样 → 输出更新后的 meta。
调用关系:execute_mcp_tool_call 在发送请求前调用它,是所有真实 MCP 调用都会经过的元信息补充步骤。
调用图:被 1 处调用(execute_mcp_tool_call);外部调用 3 个(new, Object, String)。
is_mcp_tool_approval_question_id1134–1138 ↗
fn is_mcp_tool_approval_question_id(question_id: &str) -> bool
作用:这个函数判断一个问题 ID 是否属于 MCP 工具审批问题。它用于把普通用户问题和工具审批问题区分开。
数据流:输入一个 question_id 字符串 → 检查它是否以固定前缀加下划线开头 → 输出 true 或 false。
调用关系:它是一个公开给同模块其他流程使用的小判断函数,方便外部识别 MCP 审批提示。
mcp_tool_approval_prompt_options1147–1157 ↗
fn mcp_tool_approval_prompt_options(
session_approval_key: Option<&McpToolApprovalKey>,
persistent_approval_key: Option<&McpToolApprovalKey>,
tool_call_mcp_elicitation_enabled: bool,
) ->
作用:这个函数决定审批弹窗里要不要显示“本会话记住”和“以后都记住”。不是所有工具或服务器都允许永久记住。
数据流:输入会话级审批 key、持久审批 key 和功能开关 → 根据这些值设置两个布尔选项 → 输出 McpToolApprovalPromptOptions。
调用关系:maybe_request_mcp_tool_approval 在构造审批问题前调用它,用它控制 build_mcp_tool_approval_question 和 build_mcp_tool_approval_elicitation_meta 里的选项。
调用图:被 1 处调用(maybe_request_mcp_tool_approval)。
maybe_request_mcp_tool_approval1159–1341 ↗
async fn maybe_request_mcp_tool_approval(
sess: &Arc<Session>,
turn_context: &Arc<TurnContext>,
call_id: &str,
invocation: &McpInvocation,
hook_tool_name: &HookToolName,
metada
作用:这是审批流程的核心函数。它决定是否能自动放行,还是要问钩子、Guardian 审核器或用户。
数据流:输入会话、回合、调用编号、工具调用、钩子工具名、元数据和审批模式 → 先检查自动批准和工具安全标注,再查本会话是否已记住;然后运行权限钩子;必要时走 Guardian;否则构造用户审批问题或 MCP elicitation 表单 → 输出审批决定;如果完全不需要审批则返回 None。
调用关系:它由 handle_mcp_tool_call 调用。内部会调用 requires_mcp_tool_approval 判断风险,调用 run_permission_request_hooks 给扩展机会,调用 build_guardian_mcp_tool_review_request 和 mcp_tool_approval_decision_from_guardian 处理 Guardian,或调用 build_mcp_tool_approval_question、parse_mcp_tool_approval_response 等处理用户回答,最后用 apply_mcp_tool_approval_decision 记住选择。
调用图:调用 16 个内部函数(run_permission_request_hooks, render_mcp_tool_approval_template, apply_mcp_tool_approval_decision, build_guardian_mcp_tool_review_request, build_mcp_tool_approval_elicitation_request, build_mcp_tool_approval_question, mcp_approvals_reviewer, mcp_tool_approval_decision_from_guardian, mcp_tool_approval_is_remembered, mcp_tool_approval_prompt_options (+6 more));被 1 处调用(handle_mcp_tool_call);外部调用 8 个(String, mcp_permission_prompt_is_auto_approved, clone, new_guardian_review_id, review_approval_request, routes_approval_to_guardian_with_reviewer, format!, vec!)。
mcp_approvals_reviewer1343–1353 ↗
fn mcp_approvals_reviewer(
turn_context: &TurnContext,
server_name: &str,
metadata: Option<&McpToolApprovalMetadata>,
) -> ApprovalsReviewer
作用:这个函数决定 MCP 工具审批应该交给哪个审核者策略。简单说,就是查配置,看这类调用由谁来把关。
数据流:输入回合上下文、服务器名和工具元数据 → 从配置和连接器 ID 中解析审批 reviewer → 输出 ApprovalsReviewer。
调用关系:maybe_request_mcp_tool_approval 用它决定是否路由到 Guardian;maybe_auto_review_mcp_request_user_input 这类兼容流程也会用它保持审批归属一致。
调用图:调用 1 个内部函数(mcp_approvals_reviewer);被 2 处调用(maybe_auto_review_mcp_request_user_input, maybe_request_mcp_tool_approval)。
session_mcp_tool_approval_key1355–1374 ↗
fn session_mcp_tool_approval_key(
invocation: &McpInvocation,
metadata: Option<&McpToolApprovalMetadata>,
approval_mode: AppToolApproval,
) -> Option<McpToolApprovalKey>
作用:这个函数为“本会话记住审批”生成一把钥匙。钥匙包含服务器、连接器和工具名,用来判断同一个工具下次是否还要问。
数据流:输入工具调用、元数据和审批模式 → 只有审批模式是 Auto 时才生成;Codex Apps 没有连接器 ID 时不生成 → 输出可选的 McpToolApprovalKey。
调用关系:maybe_request_mcp_tool_approval 用它查和存本会话审批记忆;persistent_mcp_tool_approval_key 也直接复用它的规则。
调用图:被 2 处调用(maybe_request_mcp_tool_approval, persistent_mcp_tool_approval_key)。
persistent_mcp_tool_approval_key1376–1382 ↗
fn persistent_mcp_tool_approval_key(
invocation: &McpInvocation,
metadata: Option<&McpToolApprovalMetadata>,
approval_mode: AppToolApproval,
) -> Option<McpToolApprovalKey>
作用:这个函数为“以后都记住审批”生成钥匙。目前它使用和本会话记住相同的识别规则。
数据流:输入工具调用、元数据和审批模式 → 调用 session_mcp_tool_approval_key → 输出可选的持久审批 key。
调用关系:maybe_request_mcp_tool_approval 用它判断审批弹窗能不能提供“以后都记住”。真正写入配置时会由 maybe_persist_mcp_tool_approval 使用这个 key。
调用图:调用 1 个内部函数(session_mcp_tool_approval_key);被 1 处调用(maybe_request_mcp_tool_approval)。
build_guardian_mcp_tool_review_request1384–1407 ↗
fn build_guardian_mcp_tool_review_request(
call_id: &str,
invocation: &McpInvocation,
metadata: Option<&McpToolApprovalMetadata>,
) -> GuardianApprovalRequest
作用:这个函数把 MCP 工具调用包装成 Guardian 审核器能看懂的请求。Guardian 可以理解成一个自动或外部安全审查员。
数据流:输入调用编号、调用内容和工具元数据 → 收集服务器、工具、参数、连接器说明、工具说明和安全标注 → 输出 GuardianApprovalRequest::McpToolCall。
调用关系:maybe_request_mcp_tool_approval 在需要 Guardian 审核时调用它;maybe_auto_review_mcp_request_user_input 也会用它构造同一种审核请求。
调用图:被 2 处调用(maybe_auto_review_mcp_request_user_input, maybe_request_mcp_tool_approval)。
mcp_tool_approval_decision_from_guardian1409–1427 ↗
async fn mcp_tool_approval_decision_from_guardian(
sess: &Session,
review_id: &str,
decision: ReviewDecision,
) -> McpToolApprovalDecision
作用:这个函数把 Guardian 的审核结果翻译成本文件内部使用的 MCP 审批决定。
数据流:输入会话、review_id 和 Guardian 返回的 ReviewDecision → Approved 类结果变成允许,ApprovedForSession 变成本会话允许,Denied/TimedOut/Abort 变成拒绝并附上合适消息 → 输出 McpToolApprovalDecision。
调用关系:maybe_request_mcp_tool_approval 在 Guardian 审核结束后调用它,然后把决定交给 apply_mcp_tool_approval_decision 处理记忆或持久化。
调用图:被 1 处调用(maybe_request_mcp_tool_approval);外部调用 2 个(guardian_rejection_message, guardian_timeout_message)。
lookup_mcp_tool_metadata1429–1489 ↗
async fn lookup_mcp_tool_metadata(
sess: &Session,
turn_context: &TurnContext,
server: &str,
tool_name: &str,
) -> Option<McpToolApprovalMetadata>
作用:这个函数查找某个 MCP 工具的说明资料。审批、界面展示、文件参数改写和连接器统计都依赖这些资料。
数据流:输入会话、回合、服务器名和工具名 → 从连接管理器列出所有工具,找到匹配项;如果是 Codex Apps,还尝试查连接器描述;再提取注解、连接器、插件、标题、描述、资源 URI 和文件参数声明 → 输出 McpToolApprovalMetadata,找不到则 None。
调用关系:handle_mcp_tool_call 在每次工具调用开始时调用它;maybe_auto_review_mcp_request_user_input 也会用它补全审核资料。它内部用 get_mcp_app_resource_uri 和 openai_file_input_params_for_server 提取细节。
调用图:调用 4 个内部函数(list_accessible_connectors_from_mcp_tools, list_cached_accessible_connectors_from_mcp_tools, get_mcp_app_resource_uri, openai_file_input_params_for_server);被 2 处调用(maybe_auto_review_mcp_request_user_input, handle_mcp_tool_call)。
openai_file_input_params_for_server1491–1498 ↗
fn openai_file_input_params_for_server(
server: &str,
meta: Option<&serde_json::Map<String, serde_json::Value>>,
) -> Option<Vec<String>>
作用:这个函数判断哪些工具参数代表 OpenAI 文件输入。为了安全,它只允许 Codex Apps 声明这类文件参数。
数据流:输入服务器名和工具 meta → 如果服务器是 Codex Apps,就读取 meta 中声明的文件参数名;如果为空或不是 Codex Apps,就返回 None → 输出可选的参数名列表。
调用关系:lookup_mcp_tool_metadata 用它填充 openai_file_input_params,之后 handle_approved_mcp_tool_call 会据此改写文件参数。
调用图:被 1 处调用(lookup_mcp_tool_metadata);外部调用 1 个(declared_openai_file_input_param_names)。
get_mcp_app_resource_uri1500–1518 ↗
fn get_mcp_app_resource_uri(
meta: Option<&serde_json::Map<String, serde_json::Value>>,
) -> Option<String>
作用:这个函数从工具 meta 里找 UI 资源地址。这个地址可以让界面知道某个应用工具对应的展示资源在哪里。
数据流:输入可选 meta 对象 → 依次尝试 ui.resourceUri、ui/resourceUri、openai/outputTemplate 这些位置 → 输出找到的 URI 字符串,找不到则 None。
调用关系:lookup_mcp_tool_metadata 调用它,把结果放进 McpToolApprovalMetadata 和后续的工具调用事件元数据里。
调用图:被 1 处调用(lookup_mcp_tool_metadata)。
lookup_mcp_app_usage_metadata1520–1542 ↗
async fn lookup_mcp_app_usage_metadata(
sess: &Session,
server: &str,
tool_name: &str,
) -> Option<McpAppUsageMetadata>
作用:这个函数查 Codex Apps 某个工具对应的连接器 ID 和应用名。它用于后续做使用统计。
数据流:输入会话、服务器名和工具名 → 从 MCP 连接管理器列出所有工具,找到匹配的工具 → 输出 McpAppUsageMetadata,里面有 connector_id 和 app_name。
调用关系:maybe_track_codex_app_used 调用它,然后根据用户是否明确选择过连接器来上报显式或隐式使用。
调用图:被 1 处调用(maybe_track_codex_app_used)。
build_mcp_tool_approval_question1544–1588 ↗
fn build_mcp_tool_approval_question(
question_id: String,
server: &str,
tool_name: &str,
connector_name: Option<&str>,
prompt_options: McpToolApprovalPromptOptions,
question_ov
作用:这个函数生成给用户看的审批问题。它会包含“允许”“本会话允许”“以后都允许”“取消”等选项。
数据流:输入问题 ID、服务器、工具名、连接器名、提示选项和可选自定义问题文案 → 生成问题标题、问题正文和可选答案列表 → 输出 RequestUserInputQuestion。
调用关系:maybe_request_mcp_tool_approval 在需要问用户时调用它。它通常会配合 build_mcp_tool_approval_fallback_message 生成默认文案。
调用图:被 1 处调用(maybe_request_mcp_tool_approval);外部调用 2 个(format!, vec!)。
build_mcp_tool_approval_fallback_message1590–1607 ↗
fn build_mcp_tool_approval_fallback_message(
server: &str,
tool_name: &str,
connector_name: Option<&str>,
) -> String
作用:这个函数生成默认审批提示语。没有模板文案时,它会用服务器名、连接器名和工具名拼出一句人能看懂的问题。
数据流:输入服务器名、工具名和可选连接器名 → 优先用连接器名作为执行者;否则 Codex Apps 显示 this app,普通 MCP 显示服务器名 → 输出类似“Allow xxx to run tool yyy?”的字符串。
调用关系:build_mcp_tool_approval_question 在没有 question_override 时会使用它。
调用图:外部调用 1 个(format!)。
build_mcp_tool_approval_elicitation_request1609–1640 ↗
fn build_mcp_tool_approval_elicitation_request(
sess: &Session,
turn_context: &TurnContext,
request: McpToolApprovalElicitationRequest<'_>,
) -> McpServerElicitationRequestParams
作用:这个函数把审批问题包装成 MCP elicitation 请求。elicitation 是 MCP 服务器向宿主请求用户输入的一种表单式流程。
数据流:输入会话、回合和审批请求材料 → 选择显示消息,构造线程号、回合号、服务器名和一个空对象表单 schema,并附上审批 meta → 输出 McpServerElicitationRequestParams。
调用关系:maybe_request_mcp_tool_approval 在启用 ToolCallMcpElicitation 功能时调用它。它把 meta 细节交给 build_mcp_tool_approval_elicitation_meta。
调用图:调用 1 个内部函数(build_mcp_tool_approval_elicitation_meta);被 1 处调用(maybe_request_mcp_tool_approval);外部调用 1 个(new)。
build_mcp_tool_approval_elicitation_meta1642–1738 ↗
fn build_mcp_tool_approval_elicitation_meta(
server: &str,
metadata: Option<&McpToolApprovalMetadata>,
tool_params: Option<&serde_json::Value>,
tool_params_display: Option<&[RenderedMc
作用:这个函数给 MCP 审批表单准备隐藏但重要的 meta 信息。界面或服务器可以用这些信息显示工具标题、参数、连接器来源和记住选项。
数据流:输入服务器、工具元数据、工具参数、展示参数和提示选项 → 写入审批类型、可持久化选项、工具标题描述、连接器信息、原始参数和展示参数 → 输出 JSON meta,若没有内容则 None。
调用关系:build_mcp_tool_approval_elicitation_request 调用它来补齐表单附加信息;这些信息随后会随 request_mcp_server_elicitation 发给用户交互层。
调用图:被 1 处调用(build_mcp_tool_approval_elicitation_request);外部调用 5 个(new, Object, String, json!, to_value)。
build_mcp_tool_approval_display_params1740–1756 ↗
fn build_mcp_tool_approval_display_params(
tool_params: Option<&serde_json::Value>,
) -> Option<Vec<crate::mcp_tool_approval_templates::RenderedMcpToolApprovalParam>>
作用:这个函数把工具参数整理成适合审批界面显示的列表。它给每个参数配上名字、值和显示名。
数据流:输入可选 JSON 参数 → 只有参数是对象时继续;把每个键值对转换成 RenderedMcpToolApprovalParam,并按参数名排序 → 输出展示参数列表或 None。
调用关系:maybe_request_mcp_tool_approval 在没有专门渲染模板时会用它,让审批界面仍能显示工具将使用哪些参数。
parse_mcp_tool_approval_elicitation_response1758–1794 ↗
fn parse_mcp_tool_approval_elicitation_response(
response: Option<ElicitationResponse>,
question_id: &str,
) -> McpToolApprovalDecision
作用:这个函数解析 MCP elicitation 审批表单的回答。它把用户在表单里的动作翻译成允许、拒绝或取消。
数据流:输入可选 ElicitationResponse 和问题 ID → 没有响应就是取消;Accept 时优先读取 meta 里的记住选项,否则把表单内容转成普通用户输入响应再解析;Decline 和 Cancel 分别变成拒绝和取消 → 输出 McpToolApprovalDecision。
调用关系:maybe_request_mcp_tool_approval 在 elicitation 审批返回后调用它。它会用 request_user_input_response_from_elicitation_content 和 parse_mcp_tool_approval_response 复用普通审批解析逻辑。
调用图:调用 2 个内部函数(parse_mcp_tool_approval_response, request_user_input_response_from_elicitation_content);被 1 处调用(maybe_request_mcp_tool_approval)。
request_user_input_response_from_elicitation_content1796–1821 ↗
fn request_user_input_response_from_elicitation_content(
content: Option<serde_json::Value>,
) -> Option<RequestUserInputResponse>
作用:这个函数把 elicitation 表单内容转换成老式 RequestUserInputResponse 格式。这样同一套答案解析逻辑可以复用。
数据流:输入可选 JSON content → 没有 content 时返回空答案;如果是对象,就把字符串或字符串数组字段转换成每个问题的答案列表 → 输出可选 RequestUserInputResponse。
调用关系:parse_mcp_tool_approval_elicitation_response 在 Accept 情况下调用它,然后继续交给 parse_mcp_tool_approval_response 判断具体选择。
调用图:被 1 处调用(parse_mcp_tool_approval_elicitation_response);外部调用 1 个(new)。
parse_mcp_tool_approval_response1823–1860 ↗
fn parse_mcp_tool_approval_response(
response: Option<RequestUserInputResponse>,
question_id: &str,
) -> McpToolApprovalDecision
作用:这个函数解析普通用户输入审批的答案。它识别“允许”“本会话允许”“以后都允许”“拒绝”和“取消”。
数据流:输入可选用户输入响应和问题 ID → 找到该问题的答案列表;按优先级检查特殊拒绝、本会话允许、永久允许、普通允许 → 输出对应 McpToolApprovalDecision;缺答案则取消。
调用关系:maybe_request_mcp_tool_approval 在普通 request_user_input 返回后调用它;parse_mcp_tool_approval_elicitation_response 也会借用它解析表单内容。
调用图:被 2 处调用(maybe_request_mcp_tool_approval, parse_mcp_tool_approval_elicitation_response)。
normalize_approval_decision_for_mode1862–1876 ↗
fn normalize_approval_decision_for_mode(
decision: McpToolApprovalDecision,
approval_mode: AppToolApproval,
) -> McpToolApprovalDecision
作用:这个函数根据审批模式修正用户决定。某些模式下,即使用户界面返回“记住”,也只能当作本次允许。
数据流:输入审批决定和审批模式 → 如果模式是 Prompt,且决定是本会话或永久记住,就降级为普通 Accept → 输出规范化后的决定。
调用关系:maybe_request_mcp_tool_approval 在解析用户或 elicitation 回答后调用它,保证 UI 选项不会突破当前审批策略。
调用图:被 1 处调用(maybe_request_mcp_tool_approval);外部调用 1 个(matches!)。
mcp_tool_approval_is_remembered1878–1881 ↗
async fn mcp_tool_approval_is_remembered(sess: &Session, key: &McpToolApprovalKey) -> bool
作用:这个函数检查某个工具审批是否已经在本会话记住。记住后,同一个工具下次可以少问一次。
数据流:输入会话和审批 key → 锁住 tool_approvals 存储,查 key 是否对应 ApprovedForSession → 输出 true 或 false。
调用关系:maybe_request_mcp_tool_approval 在真正弹审批前调用它;如果已记住,就直接返回允许。
调用图:被 1 处调用(maybe_request_mcp_tool_approval);外部调用 1 个(matches!)。
remember_mcp_tool_approval1883–1886 ↗
async fn remember_mcp_tool_approval(sess: &Session, key: McpToolApprovalKey)
作用:这个函数把某个工具审批记到本会话里。它不会改配置文件,只在当前会话有效。
数据流:输入会话和审批 key → 锁住 tool_approvals 存储,把 key 写成 ApprovedForSession → 输出为空,但会话内审批缓存被更新。
调用关系:apply_mcp_tool_approval_decision 在用户选择本会话允许时调用它;maybe_persist_mcp_tool_approval 在持久化成功或降级时也会调用它。
调用图:被 2 处调用(apply_mcp_tool_approval_decision, maybe_persist_mcp_tool_approval)。
apply_mcp_tool_approval_decision1888–1912 ↗
async fn apply_mcp_tool_approval_decision(
sess: &Session,
turn_context: &TurnContext,
decision: &McpToolApprovalDecision,
session_approval_key: Option<McpToolApprovalKey>,
persist
作用:这个函数把审批决定真正落地。也就是说,用户选择“记住”后,它负责存到会话缓存或配置文件里。
数据流:输入会话、回合、审批决定、会话 key 和持久 key → 对 AcceptForSession 写会话记忆;对 AcceptAndRemember 优先写配置,不能写配置时退回会话记忆;拒绝、取消、普通允许不存 → 输出为空,但可能改变审批缓存或配置。
调用关系:maybe_request_mcp_tool_approval 在拿到 Guardian 或用户决定后调用它。它会根据情况调用 remember_mcp_tool_approval 或 maybe_persist_mcp_tool_approval。
调用图:调用 2 个内部函数(maybe_persist_mcp_tool_approval, remember_mcp_tool_approval);被 1 处调用(maybe_request_mcp_tool_approval)。
maybe_persist_mcp_tool_approval1914–1944 ↗
async fn maybe_persist_mcp_tool_approval(
sess: &Session,
turn_context: &TurnContext,
key: McpToolApprovalKey,
)
作用:这个函数尝试把“以后都允许这个工具”的选择写进配置文件。写入失败时,它会退而求其次,只在本会话记住。
数据流:输入会话、回合和审批 key → 如果是 Codex Apps,就按 apps 配置写;否则按自定义 MCP 或插件配置写;成功后重载用户配置层并记入会话缓存;失败则记录错误并仅记入会话缓存 → 输出为空。
调用关系:apply_mcp_tool_approval_decision 在用户选择永久允许时调用它。它把具体写配置交给 persist_codex_app_tool_approval 或 persist_non_app_mcp_tool_approval。
调用图:调用 3 个内部函数(persist_codex_app_tool_approval, persist_non_app_mcp_tool_approval, remember_mcp_tool_approval);被 1 处调用(apply_mcp_tool_approval_decision);外部调用 2 个(reload_user_config_layer, error!)。
persist_codex_app_tool_approval1946–1964 ↗
async fn persist_codex_app_tool_approval(
config: &Config,
connector_id: &str,
tool_name: &str,
) -> anyhow::Result<()>
作用:这个函数把 Codex Apps 连接器工具的永久审批写入配置。写完后,以后同一个连接器工具可以按 approve 模式处理。
数据流:输入配置、连接器 ID 和工具名 → 构造配置路径 apps.<connector>.tools.<tool>.approval_mode,并写入 approve → 输出成功或错误。
调用关系:maybe_persist_mcp_tool_approval 在 key 指向 Codex Apps 时调用它。
调用图:被 1 处调用(maybe_persist_mcp_tool_approval);外部调用 3 个(for_config, value, vec!)。
persist_custom_mcp_tool_approval1967–1978 ↗
async fn persist_custom_mcp_tool_approval(
config: &Config,
server: &str,
tool_name: &str,
) -> anyhow::Result<()>
作用:这个测试专用函数把自定义 MCP 工具审批写入配置。它主要用于测试持久化逻辑是否能正确找到配置位置。
数据流:输入配置、服务器名和工具名 → 找配置编辑器;找不到就报错;找到后调用 persist_custom_mcp_tool_approval_with 写入 approve → 输出成功或错误。
调用关系:它只在测试编译时存在。它复用 custom_mcp_tool_approval_config_builder 和 persist_custom_mcp_tool_approval_with。
调用图:调用 2 个内部函数(custom_mcp_tool_approval_config_builder, persist_custom_mcp_tool_approval_with);外部调用 1 个(bail!)。
persist_non_app_mcp_tool_approval1980–2021 ↗
async fn persist_non_app_mcp_tool_approval(
sess: &Session,
config: &Config,
server: &str,
tool_name: &str,
) -> anyhow::Result<()>
作用:这个函数把非 Codex Apps 的 MCP 工具永久审批写入配置。它支持用户/项目里的 mcp_servers,也支持启用插件里的 MCP 服务器。
数据流:输入会话、配置、服务器名和工具名 → 先找自定义 MCP 配置位置,找到就写;找不到再查启用插件,若插件包含该服务器就写到插件配置路径;都找不到则报错 → 输出成功或错误。
调用关系:maybe_persist_mcp_tool_approval 在普通 MCP 工具选择永久允许时调用它。它会用 custom_mcp_tool_approval_config_builder 和 persist_custom_mcp_tool_approval_with 处理自定义服务器。
调用图:调用 2 个内部函数(custom_mcp_tool_approval_config_builder, persist_custom_mcp_tool_approval_with);被 1 处调用(maybe_persist_mcp_tool_approval);外部调用 5 个(bail!, plugins_config_input, for_config, value, vec!)。
custom_mcp_tool_approval_config_builder2023–2033 ↗
fn custom_mcp_tool_approval_config_builder(
config: &Config,
server: &str,
) -> anyhow::Result<Option<ConfigEditsBuilder>>
作用:这个函数决定自定义 MCP 工具审批应该写到哪个配置文件。它会优先选择项目配置,其次选择用户配置。
数据流:输入配置和服务器名 → 先查项目配置层里是否配置了该服务器;如果有,就返回指向项目配置目录的编辑器;否则检查用户配置是否有该服务器,有则返回用户配置编辑器 → 输出可选 ConfigEditsBuilder 或解析错误。
调用关系:persist_custom_mcp_tool_approval 和 persist_non_app_mcp_tool_approval 都用它来定位配置文件。它内部调用 project_mcp_tool_approval_config_folder 和 user_mcp_server_is_configured。
调用图:调用 3 个内部函数(new, project_mcp_tool_approval_config_folder, user_mcp_server_is_configured);被 2 处调用(persist_custom_mcp_tool_approval, persist_non_app_mcp_tool_approval)。
persist_custom_mcp_tool_approval_with2035–2053 ↗
async fn persist_custom_mcp_tool_approval_with(
config_edits_builder: ConfigEditsBuilder,
server: &str,
tool_name: &str,
) -> anyhow::Result<()>
作用:这个函数执行实际的自定义 MCP 审批写入。它只负责把某个配置编辑器按固定路径写成 approve。
数据流:输入配置编辑器、服务器名和工具名 → 写入 mcp_servers.<server>.tools.<tool>.approval_mode = approve → 输出成功或错误。
调用关系:persist_custom_mcp_tool_approval 和 persist_non_app_mcp_tool_approval 在找到正确配置位置后调用它。
调用图:被 2 处调用(persist_custom_mcp_tool_approval, persist_non_app_mcp_tool_approval);外部调用 3 个(with_edits, value, vec!)。
user_mcp_server_is_configured2055–2068 ↗
fn user_mcp_server_is_configured(config: &Config, server: &str) -> anyhow::Result<bool>
作用:这个函数检查用户配置里是否声明了某个 MCP 服务器。它用来判断能不能把永久审批写到用户配置。
数据流:输入配置和服务器名 → 从有效用户配置里取 mcp_servers,反序列化成服务器配置表,再检查是否包含该服务器 → 输出 true、false 或解析错误。
调用关系:custom_mcp_tool_approval_config_builder 在项目配置找不到服务器时调用它。
调用图:被 1 处调用(custom_mcp_tool_approval_config_builder);外部调用 1 个(deserialize)。
project_mcp_tool_approval_config_folder2070–2097 ↗
fn project_mcp_tool_approval_config_folder(
config: &Config,
server: &str,
) -> Option<AbsolutePathBuf>
作用:这个函数查找项目配置层中配置了某个 MCP 服务器的文件夹。项目配置优先,避免把项目专属服务器的审批写到全局用户配置里。
数据流:输入配置和服务器名 → 从高优先级到低优先级扫描配置层,只看 Project 类型层;解析其中的 mcp_servers,找到包含该服务器的层就返回它的配置文件夹 → 输出可选绝对路径。
调用关系:custom_mcp_tool_approval_config_builder 首先调用它,决定是否使用项目级 ConfigEditsBuilder。
调用图:被 1 处调用(custom_mcp_tool_approval_config_builder)。
requires_mcp_tool_approval2099–2116 ↗
fn requires_mcp_tool_approval(annotations: Option<&ToolAnnotations>) -> bool
作用:这个函数根据工具安全标注判断是否必须审批。比如工具声明自己可能有破坏性,或者没有明确只读,就倾向于要求确认。
数据流:输入可选 ToolAnnotations → destructive_hint 为 true 时必须审批;read_only_hint 为 true 时可不强制;否则如果破坏性未知或 open_world_hint 默认为 true,就要求审批 → 输出 true 或 false。
调用关系:maybe_request_mcp_tool_approval 用它做风险判断,再结合配置里的审批模式决定是否真的弹窗或放行。
调用图:被 1 处调用(maybe_request_mcp_tool_approval)。
notify_mcp_tool_call_skip2118–2149 ↗
async fn notify_mcp_tool_call_skip(
sess: &Session,
turn_context: &TurnContext,
call_id: &str,
invocation: McpInvocation,
item_metadata: McpToolCallItemMetadata,
message: Strin
作用:这个函数处理“工具没有真正执行”的情况,比如被配置阻止、用户拒绝或取消。它仍然会发出完整的开始/完成事件,让界面和历史记录不缺一段。
数据流:输入会话、回合、调用信息、失败消息和是否已经发过开始事件 → 如果还没开始就补发 started;然后用零耗时和截断后的错误结果发 completed → 输出 Err(message),供上层变成工具调用失败结果。
调用关系:handle_mcp_tool_call 在工具被禁用、拒绝或取消时调用它。它内部复用 notify_mcp_tool_call_started、notify_mcp_tool_call_completed 和 truncate_mcp_tool_result_for_event。
调用图:调用 3 个内部函数(notify_mcp_tool_call_completed, notify_mcp_tool_call_started, truncate_mcp_tool_result_for_event);被 1 处调用(handle_mcp_tool_call);外部调用 2 个(clone, clone)。
core/src/mcp_openai_file.rs源码 ↗
这个文件专门解决一个很实际的问题:有些 MCP 工具会在元数据里声明某些参数是文件输入,比如 file 或 files。用户调用工具时给的是本地文件路径,但下游 OpenAI Apps 风格的工具需要的是已经上传后的文件对象,里面有文件编号、下载地址、文件名、大小等信息。它的做法像“快递中转站”:先看哪些参数被声明为文件,再到当前主要运行环境里找到这些文件,检查它们真的是文件、没有超过上传大小限制,然后用 ChatGPT/Codex 的认证信息上传到 OpenAI 文件存储。上传成功后,它只替换那些明确声明过的字段,其他参数原样保留。这样既不会乱改普通参数,也能保证工具真正拿到可下载的文件。
rewrite_mcp_tool_arguments_for_openai_files22–59 ↗
async fn rewrite_mcp_tool_arguments_for_openai_files(
sess: &Session,
turn_context: &TurnContext,
arguments_value: Option<JsonValue>,
openai_file_input_params: Option<&[String]>,
) ->
作用:这是整个文件对外的主入口。它检查一次工具调用的参数,把被声明为文件输入的字段从“文件路径”改成“上传后的文件信息”。
数据流:进去的是当前会话、当前回合上下文、原始 JSON 参数,以及一份“哪些字段是文件”的名单。它先确认有没有这份名单、参数是不是对象;如果不需要处理,就原样返回。需要处理时,它取出登录认证,逐个文件字段交给下层函数上传并改写,最后返回新的 JSON 参数;如果什么都没变,就返回原参数。
调用关系:它会在真正执行 MCP 工具前被 handle_approved_mcp_tool_call 调用,像工具调用前的检查和打包步骤。它把单个字段的细活交给 rewrite_argument_value_for_openai_files,测试也会直接调用它来确认“不声明文件参数就不改写”和“上传失败会冒出来”。
调用图:调用 1 个内部函数(rewrite_argument_value_for_openai_files);被 3 处调用(openai_file_argument_rewrite_requires_declared_file_params, rewrite_mcp_tool_arguments_for_openai_files_surfaces_upload_failures, handle_approved_mcp_tool_call);外部调用 1 个(Object)。
rewrite_argument_value_for_openai_files61–99 ↗
async fn rewrite_argument_value_for_openai_files(
turn_context: &TurnContext,
auth: Option<&CodexAuth>,
field_name: &str,
value: &JsonValue,
) -> Result<Option<JsonValue>, String>
作用:这个函数只处理某一个参数字段。它判断这个字段是单个文件路径,还是一组文件路径,然后分别上传并换成文件信息。
数据流:进去的是当前回合上下文、认证信息、字段名和这个字段的 JSON 值。如果值是字符串,就当成一个文件路径上传;如果值是数组,就要求数组里每一项都是字符串路径,并逐个上传;如果类型不对,就返回“不改写”。出来的是一个新的 JSON 值,可能是单个上传文件对象,也可能是上传文件对象数组。
调用关系:它是 rewrite_mcp_tool_arguments_for_openai_files 的助手,负责把一个字段拆开处理。真正读文件和上传的工作,它继续交给 build_uploaded_argument_value。测试会分别用单个路径和路径数组来验证它。
调用图:调用 1 个内部函数(build_uploaded_argument_value);被 3 处调用(rewrite_mcp_tool_arguments_for_openai_files, rewrite_argument_value_for_openai_files_rewrites_array_paths, rewrite_argument_value_for_openai_files_rewrites_scalar_path);外部调用 2 个(Array, with_capacity)。
build_uploaded_argument_value101–179 ↗
async fn build_uploaded_argument_value(
turn_context: &TurnContext,
auth: Option<&CodexAuth>,
field_name: &str,
index: Option<usize>,
file_path: &str,
) -> Result<JsonValue, String
作用:这个函数负责完成最关键的一步:根据一个文件路径找到文件、检查文件、上传文件,然后拼出下游工具需要的文件描述对象。
数据流:进去的是当前回合上下文、认证信息、字段名、可选的数组下标,以及文件路径字符串。它先把错误信息包装得更清楚,比如指出是 file 还是 files[0] 出错;然后确认有 ChatGPT/Codex 可用认证,找到主要运行环境的当前目录,把相对路径拼成真实路径。接着它通过文件系统取元数据,确认目标是普通文件且大小没有超过限制,再读取文件流并调用 OpenAI 文件上传接口。出来的是一个 JSON 对象,包含下载地址、文件 ID、MIME 类型、文件名、URI 和文件大小。
调用关系:它被 rewrite_argument_value_for_openai_files 调用,是整个改写流程里真正碰磁盘和网络的地方。它会使用 auth_provider_from_auth 把认证转成上传接口能用的形式,再调用外部的 upload_openai_file。测试会直接验证它能上传文件,也会验证超大文件会在读取前被拒绝。
调用图:调用 1 个内部函数(from_abs_path);被 3 处调用(rewrite_argument_value_for_openai_files, build_uploaded_argument_value_rejects_oversized_file_before_reading, build_uploaded_argument_value_uploads_environment_file);外部调用 4 个(upload_openai_file, auth_provider_from_auth, format!, json!)。
tests::set_primary_environment_cwd193–207 ↗
fn set_primary_environment_cwd(turn_context: &mut TurnContext, cwd: &Path)
作用:这是测试用的小帮手,用来把测试里的“主要运行环境”的当前目录改成临时目录。这样测试可以把文件放在临时目录里,再模拟用户传相对路径。
数据流:进去的是一个可修改的回合上下文和一个目录路径。它把目录转成项目内部使用的绝对路径格式,关闭权限限制相关设置,然后替换主要环境里的当前目录信息。出来没有返回值,但测试上下文已经被改好。
调用关系:它只在测试里使用,常在创建临时文件之后调用。后面的 build_uploaded_argument_value 或 rewrite_argument_value_for_openai_files 就会从这个新目录里找文件。
调用图:调用 3 个内部函数(new, try_from, from_abs_path);外部调用 1 个(clone)。
tests::openai_file_argument_rewrite_requires_declared_file_params210–226 ↗
async fn openai_file_argument_rewrite_requires_declared_file_params()
作用:这个测试确认:只有工具明确声明了哪些参数是文件时,代码才会改写参数。没有声明时,即使参数看起来像文件路径,也不能乱动。
数据流:进去的是一个测试会话、测试上下文,以及包含 file 字段的 JSON 参数。它调用主入口时故意不给文件参数名单。出来的结果应该和原参数完全一样,测试用断言确认这一点。
调用关系:它直接覆盖 rewrite_mcp_tool_arguments_for_openai_files 的早退分支,防止以后有人把所有看起来像路径的字符串都拿去上传。
调用图:调用 2 个内部函数(rewrite_mcp_tool_arguments_for_openai_files, make_session_and_context);外部调用 3 个(new, assert_eq!, json!)。
tests::build_uploaded_argument_value_uploads_environment_file229–307 ↗
async fn build_uploaded_argument_value_uploads_environment_file()
作用:这个测试确认:给定一个真实存在的小文件时,底层上传函数会按预期走完整上传流程,并返回正确的文件信息。
数据流:它先启动一个假的 HTTP 服务器,模拟 OpenAI 文件接口;再创建测试上下文、测试认证和临时 CSV 文件,并把当前目录指向临时目录。然后调用 build_uploaded_argument_value。出来的 JSON 应该包含假的服务器返回的文件 ID、下载地址、文件名、类型和大小,测试用断言逐项比较。
调用关系:它直接测试 build_uploaded_argument_value,同时用假服务器拦住外部网络请求。这样不用真的连 OpenAI,也能验证创建上传、PUT 文件内容、确认上传完成这几步都发生了。
调用图:调用 3 个内部函数(build_uploaded_argument_value, make_session_and_context, create_dummy_chatgpt_auth_for_testing);外部调用 10 个(new, given, start, new, assert_eq!, set_primary_environment_cwd, format!, json!, tempdir, write)。
tests::build_uploaded_argument_value_rejects_oversized_file_before_reading310–332 ↗
async fn build_uploaded_argument_value_rejects_oversized_file_before_reading()
作用:这个测试确认:文件太大时会被提前拒绝,不会先把大文件读进来再失败。这样可以避免浪费内存和时间。
数据流:它创建一个比上传限制大 1 字节的稀疏文件,也就是看起来很大但不必真的写满内容的测试文件。然后设置测试环境目录,调用 build_uploaded_argument_value。出来应该是错误信息,里面要说明文件太大并包含实际大小。
调用关系:它直接测试 build_uploaded_argument_value 的大小检查分支。这个测试保护的是安全和性能边界:先查元数据,再决定是否读取。
调用图:调用 3 个内部函数(build_uploaded_argument_value, make_session_and_context, create_dummy_chatgpt_auth_for_testing);外部调用 4 个(assert!, set_primary_environment_cwd, create, tempdir)。
tests::rewrite_argument_value_for_openai_files_rewrites_scalar_path335–411 ↗
async fn rewrite_argument_value_for_openai_files_rewrites_scalar_path()
作用:这个测试确认:当某个文件参数是一个字符串路径时,会被改写成一个上传后的文件对象。
数据流:它搭好假的上传服务器,创建一个临时文件,把上下文的当前目录指向临时目录,并设置测试用的后端地址。然后把 JSON 字符串 file_report.csv 传给 rewrite_argument_value_for_openai_files。出来应该是 Some(...),里面是上传后的文件信息 JSON。
调用关系:它测试的是 rewrite_argument_value_for_openai_files 的“单个文件”分支。这个分支内部会调用 build_uploaded_argument_value 完成实际上传。
调用图:调用 3 个内部函数(rewrite_argument_value_for_openai_files, make_session_and_context, create_dummy_chatgpt_auth_for_testing);外部调用 10 个(new, given, start, new, assert_eq!, set_primary_environment_cwd, format!, json!, tempdir, write)。
tests::rewrite_argument_value_for_openai_files_rewrites_array_paths414–535 ↗
async fn rewrite_argument_value_for_openai_files_rewrites_array_paths()
作用:这个测试确认:当某个文件参数是一组路径时,每个路径都会分别上传,并按原顺序变成一组文件对象。
数据流:它准备两个临时 CSV 文件和一个假的上传服务器,服务器分别返回 file_1 和 file_2。然后把 JSON 数组 ["one.csv", "two.csv"] 传给 rewrite_argument_value_for_openai_files。出来应该是一个 JSON 数组,两个元素分别是两个文件上传后的信息,而且顺序不变。
调用关系:它覆盖 rewrite_argument_value_for_openai_files 的“数组文件”分支。这个分支会循环调用 build_uploaded_argument_value,每个数组元素上传一次。
调用图:调用 3 个内部函数(rewrite_argument_value_for_openai_files, make_session_and_context, create_dummy_chatgpt_auth_for_testing);外部调用 10 个(new, given, start, new, assert_eq!, set_primary_environment_cwd, format!, json!, tempdir, write)。
tests::rewrite_mcp_tool_arguments_for_openai_files_surfaces_upload_failures538–556 ↗
async fn rewrite_mcp_tool_arguments_for_openai_files_surfaces_upload_failures()
作用:这个测试确认:如果声明的文件参数上传失败,主入口不会把错误吞掉,而是把带上下文的错误返回给调用方。
数据流:它创建一个带测试认证的会话,然后传入一个明显不存在的文件路径,并声明 file 字段是文件输入。调用 rewrite_mcp_tool_arguments_for_openai_files 后,出来应该是错误。测试检查错误里包含“上传失败”和字段名,方便用户知道哪里出了问题。
调用关系:它测试主入口 rewrite_mcp_tool_arguments_for_openai_files 和下层上传流程的错误传递。真实运行中,这类错误会阻止 MCP 工具继续拿着无效文件参数执行。
调用图:调用 4 个内部函数(rewrite_mcp_tool_arguments_for_openai_files, make_session_and_context, auth_manager_from_auth, create_dummy_chatgpt_auth_for_testing);外部调用 2 个(assert!, json!)。
资源工具处理器
这些文件定义 MCP 资源工具规范、共享辅助层,以及通过 MCP 资源路径调用的具体列表/读取处理器。
core/src/tools/handlers/mcp_resource_spec.rs源码 ↗
MCP 是 Model Context Protocol,可以简单理解成一种让外部服务把文件、数据库结构、业务资料等上下文交给模型看的协议。这个文件不是真的去连接 MCP 服务器,也不读取资源本身;它做的是给这些能力写“菜单”和“点菜单规则”。比如模型想看有哪些资源,就用 list_mcp_resources;想看可带参数的资源模板,就用 list_mcp_resource_templates;已经知道服务器名和资源地址后,就用 read_mcp_resource。文件用 JSON Schema(一种描述 JSON 参数长什么样的规则)说明每个工具能传什么字段、哪些字段必填。这样后面的工具注册流程就能把这些说明交给 Responses API,避免模型乱传参数或调用不存在的工具。
create_list_mcp_resources_tool6–31 ↗
fn create_list_mcp_resources_tool() -> ToolSpec
作用:创建“列出 MCP 资源”这个工具的说明书。别人调用它,是为了告诉系统:有一个叫 list_mcp_resources 的工具,可以按服务器名和分页游标来查询资源列表。
数据流:进去时没有业务输入;函数自己写好两个可选参数:server 表示只看某个 MCP 服务器,cursor 表示接着上一页继续看。它把这些参数包装成 JSON Schema,再放进一个 ResponsesApiTool,最后返回一个 ToolSpec,供工具注册系统使用;它不访问服务器,也不产生资源列表。
调用关系:它会在更大的 spec 构建流程中被调用,用来把“列资源”这个能力加入工具清单。函数内部把具体参数交给 JsonSchema::string 和 JsonSchema::object 这类构造器来描述,再用 ToolSpec::Function 包成一个可注册的函数工具。
调用图:调用 2 个内部函数(object, string);被 1 处调用(spec);外部调用 2 个(from, Function)。
create_list_mcp_resource_templates_tool33–59 ↗
fn create_list_mcp_resource_templates_tool() -> ToolSpec
作用:创建“列出 MCP 资源模板”这个工具的说明书。资源模板可以理解成带参数的资料入口,比如某类文件或某个数据库表结构要按名字查询。
数据流:进去时没有外部数据;函数固定写出 server 和 cursor 两个可选字段,分别表示筛选服务器和分页继续读取。它把这些字段变成 JSON Schema 参数规则,再生成名为 list_mcp_resource_templates 的工具规格;出来的是工具定义,不是模板数据本身。
调用关系:它同样由 spec 构建流程调用,用来把“列资源模板”这项能力登记进去。它依赖 JsonSchema 的 string 和 object 来描述参数形状,并通过 ToolSpec::Function 把说明包装成 Responses API 能认识的工具。
调用图:调用 2 个内部函数(object, string);被 1 处调用(spec);外部调用 2 个(from, Function)。
create_read_mcp_resource_tool61–93 ↗
fn create_read_mcp_resource_tool() -> ToolSpec
作用:创建“读取某个 MCP 资源”这个工具的说明书。这个工具要求调用者明确说出服务器名和资源 URI,也就是资源地址,才能读取具体内容。
数据流:进去时没有运行时输入;函数预先定义两个参数:server 和 uri,而且这两个都是必填。它把这些要求写进 JSON Schema,说明 server 必须和之前列资源返回的服务器名一致,uri 必须是之前列出的资源地址之一;最后返回 read_mcp_resource 的工具规格。本函数只规定调用格式,不实际读取资源。
调用关系:它在 spec 构建工具清单时被调用,通常和“列资源”工具配套使用:先列出可用资源,再拿返回的 server 和 uri 来读。内部同样借助 JsonSchema 构造参数规则,并用 vec! 标出必填字段,最后交给 ToolSpec::Function 形成可注册工具。
调用图:调用 2 个内部函数(object, string);被 1 处调用(spec);外部调用 3 个(from, Function, vec!)。
core/src/tools/handlers/mcp_resource.rs源码 ↗
MCP 可以理解成一种让模型访问外部工具和资料的协议;这里的“资源”就是外部服务器提供的文件、数据或模板。这个文件不直接完成所有读取动作,而是把几件关键杂活统一起来:把某个服务器返回的资源加上服务器名字,避免混在一起分不清来源;把多个服务器的结果排好序后合成一份清单;把结果转成 JSON 文本,并按长度限制裁掉太长的内容;把用户传来的参数从字符串或 JSON 变成程序能用的结构;还会在工具调用开始和结束时通知会话系统,好让外界看到“正在执行、成功、失败、耗时多久”。它像资源工具的公共工具箱,下面的 list、read 具体处理器都会拿这些零件来用。
ResourceWithServer::new68–70 ↗
fn new(server: String, resource: Resource) -> Self
作用:把一个资源和它来自哪个服务器绑在一起。这样最后展示资源列表时,读者不会只看到资源本身,却不知道它是从哪台 MCP 服务器来的。
数据流:进去的是服务器名字和一个资源对象 → 它把两者放进同一个小包装里 → 出来的是带有 server 字段的资源记录,不会改动别的东西。
调用关系:当需要把很多服务器的资源合成一个总列表时,ListResourcesPayload::from_all_servers 会反复调用它;测试里也会用它确认序列化后确实带着服务器字段。
调用图:被 2 处调用(from_all_servers, resource_with_server_serializes_server_field)。
ResourceTemplateWithServer::new81–83 ↗
fn new(server: String, template: ResourceTemplate) -> Self
作用:把一个资源模板和它所属的服务器绑在一起。资源模板像“可按规则生成资源地址的说明书”,加上服务器名后才知道该去哪里用。
数据流:进去的是服务器名字和一个资源模板 → 它生成一个包含这两样信息的新记录 → 出来的是带 server 字段的模板记录。
调用关系:ListResourceTemplatesPayload::from_all_servers 在汇总多个服务器的模板时会用它;相关测试也会检查它序列化后是否保留服务器来源。
调用图:被 2 处调用(from_all_servers, template_with_server_serializes_server_field)。
ListResourcesPayload::from_single_server97–108 ↗
fn from_single_server(server: String, result: ListResourcesResult) -> Self
作用:把单个服务器返回的资源列表整理成最终要交给模型看的格式。它会给每条资源补上服务器名字,还保留分页用的 next cursor(下一页标记)。
数据流:进去的是一个服务器名和该服务器的列表结果 → 它遍历每个资源,为资源加上同一个服务器名,并复制下一页游标 → 出来的是一份标准的资源列表响应。
调用关系:具体的资源列表处理流程 handle_call 在只查某一个服务器时会用它;测试会确认它不会丢掉 next_cursor。
调用图:被 2 处调用(handle_call, list_resources_payload_from_single_server_copies_next_cursor)。
ListResourcesPayload::from_all_servers110–126 ↗
fn from_all_servers(resources_by_server: HashMap<String, Vec<Resource>>) -> Self
作用:把多个服务器的资源合成一份总清单。它还会按服务器名字排序,让输出稳定,不会每次顺序乱跳。
数据流:进去的是“服务器名 → 该服务器资源列表”的表 → 它先按服务器名排序,再把每条资源包装成带服务器名的记录 → 出来的是一份不限定单个服务器、也没有分页游标的总资源列表。
调用关系:handle_call 在用户没有指定服务器、需要查全部服务器时会用它;它内部会调用 ResourceWithServer::new 来给每条资源贴来源标签,测试会验证排序行为。
调用图:调用 1 个内部函数(new);被 2 处调用(handle_call, list_resources_payload_from_all_servers_is_sorted);外部调用 1 个(new)。
ListResourceTemplatesPayload::from_single_server140–151 ↗
fn from_single_server(server: String, result: ListResourceTemplatesResult) -> Self
作用:把单个服务器返回的资源模板列表整理成统一响应。它会给每个模板补上服务器名字,并保留分页信息。
数据流:进去的是服务器名和该服务器返回的模板列表结果 → 它把每个模板包成带服务器名的记录,并带上 next_cursor → 出来的是标准的模板列表响应。
调用关系:资源模板列表的 handle_call 在只查询某一个服务器时会用它,把底层 MCP 返回值变成此项目统一的输出格式。
调用图:被 1 处调用(handle_call)。
ListResourceTemplatesPayload::from_all_servers153–170 ↗
fn from_all_servers(templates_by_server: HashMap<String, Vec<ResourceTemplate>>) -> Self
作用:把所有服务器的资源模板汇总成一份清单。它保证按服务器名排序,方便阅读,也方便测试和日志对比。
数据流:进去的是“服务器名 → 模板列表”的表 → 它排序服务器,再把每个模板加上服务器来源 → 出来的是一份跨服务器的模板总列表。
调用关系:handle_call 在没有指定服务器、需要列出所有模板时会用它;它内部调用 ResourceTemplateWithServer::new 来生成带来源的模板记录。
调用图:调用 1 个内部函数(new);被 1 处调用(handle_call);外部调用 1 个(new)。
call_tool_result_from_content181–188 ↗
fn call_tool_result_from_content(content: &str, success: Option<bool>) -> CallToolResult
作用:把一段普通文本包装成 MCP 工具调用结果。调用方只要给它内容和是否成功,它就能产出协议需要的标准结果形状。
数据流:进去的是文本内容和一个可选的成功标记 → 它把文本放进 MCP 规定的 content 数组里,并把 success 反过来转成 is_error → 出来的是 CallToolResult。
调用关系:它是给资源工具构造返回值用的小工具,通常在某个工具已经拿到文字结果后,把结果变成 MCP 能理解的格式。
调用图:外部调用 1 个(vec!)。
emit_tool_call_begin190–214 ↗
async fn emit_tool_call_begin(
session: &Arc<Session>,
turn: &TurnContext,
call_id: &str,
invocation: McpInvocation,
)
作用:在 MCP 工具刚开始执行时,向当前会话发一条“开始了”的消息。这样界面或日志可以立刻显示这个工具正在跑,而不是等它结束才有动静。
数据流:进去的是会话、当前轮次、调用编号和调用信息 → 它拆出服务器、工具名、参数,组装成状态为 InProgress 的记录 → 它把这条开始事件发给 session,本身不返回业务结果。
调用关系:资源工具处理流程在真正调用 MCP 之前会用它;它把状态交给 Session 的事件发送机制,让外部能看到工具调用的开始。
调用图:外部调用 1 个(McpToolCall)。
emit_tool_call_end216–253 ↗
async fn emit_tool_call_end(
session: &Arc<Session>,
turn: &TurnContext,
call_id: &str,
invocation: McpInvocation,
duration: Duration,
result: Result<CallToolResult, String>,
)
作用:在 MCP 工具执行结束后,向当前会话发一条“结束了”的消息。它会区分成功、协议里标记的失败、以及真正的错误,并记录耗时。
数据流:进去的是会话、轮次、调用编号、调用信息、耗时和调用结果 → 它根据结果决定状态是 Completed 还是 Failed,并填入结果或错误消息 → 它把完成事件发给 session。
调用关系:资源工具处理流程在 MCP 调用完成或失败后会用它;它和 emit_tool_call_begin 配成一对,一个报开始,一个报结束。
调用图:外部调用 1 个(McpToolCall)。
normalize_optional_string255–264 ↗
fn normalize_optional_string(input: Option<String>) -> Option<String>
作用:清理一个可有可无的字符串参数。它会把前后空格去掉,并把空字符串当成“没填”。
数据流:进去的是 Option<String>,也就是可能有值也可能没有 → 如果有值就 trim 去空格,空了就变成 None → 出来的是清理后的可选字符串。
调用关系:normalize_required_string 会调用它,先统一做清理,再判断必填字段是不是确实填了。
调用图:被 1 处调用(normalize_required_string)。
normalize_required_string266–273 ↗
fn normalize_required_string(field: &str, value: String) -> Result<String, FunctionCallError>
作用:检查一个必填字符串参数是否真的有内容。比如 server 或 uri 这种参数,如果只传空格,也会被当成没填并返回给模型看的错误。
数据流:进去的是字段名和字符串值 → 它先交给 normalize_optional_string 去空格和判空 → 如果有内容就返回清理后的字符串;如果没有,就返回 FunctionCallError,提示某个字段必须提供。
调用关系:具体工具在解析用户参数后会用它做最后的必填检查;它把错误包装成 RespondToModel,意思是这个错误要直接告诉模型来修正调用。
调用图:调用 1 个内部函数(normalize_optional_string);外部调用 2 个(format!, RespondToModel)。
serialize_function_output275–292 ↗
fn serialize_function_output(
payload: T,
truncation_policy: TruncationPolicy,
) -> Result<FunctionToolOutput, FunctionCallError>
作用:把资源工具的结构化结果变成最终输出文本,并防止内容太长。它相当于把一盒整理好的数据装成 JSON 包裹,再按允许长度剪短。
数据流:进去的是任意可序列化的数据和截断策略 → 它先转成 JSON 字符串,失败就生成给模型看的错误;成功后按长度限制截断 → 出来的是 FunctionToolOutput,并标记这次输出成功。
调用关系:各个资源处理器在准备把结果交给模型时会用它;它内部用 serde_json 做 JSON 转换,用 truncate_text 控制长度,再用 FunctionToolOutput::from_text 包成工具输出。
调用图:调用 1 个内部函数(from_text);外部调用 2 个(truncate_text, to_string)。
parse_arguments294–307 ↗
fn parse_arguments(raw_args: &str) -> Result<Option<Value>, FunctionCallError>
作用:把原始参数字符串解析成 JSON 值。它也会把空字符串和 JSON 的 null 当成“没有参数”。
数据流:进去的是一段原始字符串 → 如果全是空白就返回 None;否则按 JSON 解析,解析失败就返回给模型看的错误;如果解析出 null 也返回 None → 出来的是可选的 JSON 值。
调用关系:工具入口拿到模型传来的原始参数时会先用它,把字符串形式的参数变成后面 parse_args 能继续处理的数据。
调用图:外部调用 1 个(from_str)。
parse_args309–321 ↗
fn parse_args(arguments: Option<Value>) -> Result<T, FunctionCallError>
作用:把一个 JSON 值变成某个具体参数结构,比如列资源参数或读资源参数。它要求必须有参数,缺了就报错。
数据流:进去的是可选 JSON 值和目标类型 T → 如果有 JSON,就尝试反序列化成 T;失败会返回清楚的参数解析错误;如果没有 JSON,也返回“需要参数”的错误 → 出来的是类型安全的参数对象。
调用关系:parse_args_with_default 在有参数时会把活交给它;具体工具也可以用它解析那些不能缺省的参数。
调用图:被 1 处调用(parse_args_with_default);外部调用 2 个(from_value, RespondToModel)。
parse_args_with_default323–331 ↗
fn parse_args_with_default(arguments: Option<Value>) -> Result<T, FunctionCallError>
作用:解析参数,但允许完全不传参数。适合“列全部资源”这种场景:用户不传 server,也有一个默认行为。
数据流:进去的是可选 JSON 值和目标类型 T → 如果有值就调用 parse_args 正常解析;如果没有值,就创建 T 的默认值 → 出来的是参数对象,不会因为没传参数就失败。
调用关系:列资源、列资源模板这类带默认行为的工具会用它;它把“有参数”和“没参数”两条路合在一起,减少各处理器重复判断。
调用图:调用 1 个内部函数(parse_args);外部调用 1 个(default)。
core/src/tools/handlers/mcp_resource/list_mcp_resource_templates.rs源码 ↗
这个文件像一个“前台接待员”:模型发来一次工具调用,说想列出 MCP(Model Context Protocol,一种让模型连接外部服务的协议)资源模板,它负责检查请求、整理参数、真正去问服务器,然后把结果包装成模型能读懂的输出。它先确认收到的是函数调用参数,再解析出服务器名和分页游标。游标可以理解成“上次看到第几页”的书签;这里有个重要限制:只有指定某台服务器时才能用游标。如果指定了服务器,它就调用会话里的 list_resource_templates 去查那一台;如果没指定,就从 MCP 连接管理器里汇总所有服务器的模板。整个过程还会发出“开始调用”和“结束调用”的事件,并记录耗时,这样外部系统能追踪工具到底做了什么、成功还是失败。
ListMcpResourceTemplatesHandler::tool_name30–32 ↗
fn tool_name(&self) -> ToolName
作用:告诉工具系统:这个处理器对外叫什么名字。这里返回的名字就是模型调用时使用的 list_mcp_resource_templates。
数据流:进去的是这个处理器本身,不需要读取外部数据 → 它创建一个普通工具名 → 出来的是工具注册系统能识别的名字。
调用关系:工具注册或调度时会问它的名字,好把一次 list_mcp_resource_templates 调用分派到这个处理器。它内部只借助 plain 把字符串变成标准工具名。
调用图:调用 1 个内部函数(plain)。
ListMcpResourceTemplatesHandler::spec34–36 ↗
fn spec(&self) -> ToolSpec
作用:提供这个工具的说明书。说明书会告诉模型这个工具能收哪些参数、该怎么调用。
数据流:进去的是这个处理器本身 → 它调用 create_list_mcp_resource_templates_tool 生成工具规格 → 出来的是一份 ToolSpec,也就是工具的参数和说明定义。
调用关系:工具系统在向模型展示可用工具时会用到它。它把具体规格的创建工作交给 create_list_mcp_resource_templates_tool,本文件只负责把那份规格交出去。
调用图:调用 1 个内部函数(create_list_mcp_resource_templates_tool)。
ListMcpResourceTemplatesHandler::supports_parallel_tool_calls38–40 ↗
fn supports_parallel_tool_calls(&self) -> bool
作用:声明这个工具可以并行调用。也就是说,多个这样的查询可以同时跑,不必一个等一个。
数据流:进去的是这个处理器本身 → 它直接返回 true → 出来的是一个允许并行的信号,不改动任何数据。
调用关系:工具调度器会根据这个回答决定能不能同时安排多个调用。因为这个工具主要是读取资源模板,不是在修改共享数据,所以这里允许并行。
ListMcpResourceTemplatesHandler::handle42–44 ↗
fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_>
作用:这是工具系统真正交给处理器一笔调用时的入口。它把同步接口包装成异步任务,让后面的查询可以等待网络或服务返回。
数据流:进去的是一次 ToolInvocation,里面有会话、调用编号、参数等信息 → 它把这些信息交给 handle_call,并用 pin 固定成可等待的异步任务 → 出来的是一个未来会完成的工具结果。
调用关系:工具调度器调用它来启动处理流程。它自己不做细活,只负责把请求转交给 ListMcpResourceTemplatesHandler::handle_call,后者完成解析参数、查询 MCP、记录事件和返回结果。
调用图:调用 1 个内部函数(handle_call);外部调用 1 个(pin)。
ListMcpResourceTemplatesHandler::handle_call48–166 ↗
async fn handle_call(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError>
作用:这是本文件的核心流程:接住模型传来的参数,去 MCP 服务器查询资源模板,并把成功结果或错误信息都整理好返回。它还负责记录调用开始、结束和耗时,方便排查问题。
数据流:进去的是一次完整工具调用,包含会话、当前轮次、调用 ID 和原始参数 → 它先确认参数格式正确,再解析服务器名和游标;随后发出“开始调用”事件并计时;如果指定服务器,就带着可选游标去查那台服务器的模板;如果没指定服务器,就汇总所有服务器的模板,但会拒绝带游标的请求;查到结果后,它把数据序列化成模型能接收的输出,并抽出文本内容用于记录;如果中间出错,它会把错误写进“结束调用”事件并返回错误 → 出来的是一个装箱后的工具输出,或者一个会反馈给模型的错误,同时外部观察者也能看到这次调用的结束状态和耗时。
调用关系:ListMcpResourceTemplatesHandler::handle 会调用它来完成实际工作。它串起了很多小零件:用参数解析函数读懂输入,用 emit_tool_call_begin 和 emit_tool_call_end 发事件,用会话里的 MCP 查询能力拿数据,用 from_single_server 或 from_all_servers 组装结果,用 serialize_function_output 和 boxed_tool_output 生成最终输出。
调用图:调用 4 个内部函数(boxed_tool_output, from_all_servers, from_single_server, function_call_output_content_items_to_text);被 1 处调用(handle);外部调用 10 个(now, clone, call_tool_result_from_content, emit_tool_call_begin, emit_tool_call_end, normalize_optional_string, parse_args_with_default, parse_arguments, serialize_function_output, RespondToModel)。
core/src/tools/handlers/mcp_resource/list_mcp_resources.rs源码 ↗
MCP 可以简单理解成一种“外部工具和资料接入协议”,资源就是这些外部服务器愿意提供给系统看的资料条目。这个文件就是 list_mcp_resources 的执行器:先声明工具名字和工具说明,再在真正被调用时解析传进来的参数,比如要查哪个服务器、是否带分页游标。它会先发出“工具开始执行”的记录,方便日志和界面知道发生了什么;然后如果指定了某个服务器,就只向这个服务器请求资源列表;如果没指定服务器,就把所有已连接 MCP 服务器的资源汇总起来。它还特别限制了一个行为:分页游标只能在指定服务器时使用,因为跨多个服务器分页没有明确含义。最后,它把结果整理成函数调用输出,并发出“工具结束执行”的记录,成功和失败都会记录耗时和结果。
ListMcpResourcesHandler::tool_name30–32 ↗
fn tool_name(&self) -> ToolName
作用:告诉工具系统:这个执行器对应的工具名字叫 list_mcp_resources。这样外部请求点名调用这个工具时,系统才能找到它。
数据流:进去的是这个处理器本身,不需要读取复杂数据 → 它把固定的文字名字包装成工具名对象 → 出来的是一个可被工具注册表识别的工具名字。
调用关系:这是工具注册和匹配时用的小入口。它把名字交给底层的 plain 来生成标准工具名,后续真正执行时才会进入 handle。
调用图:调用 1 个内部函数(plain)。
ListMcpResourcesHandler::spec34–36 ↗
fn spec(&self) -> ToolSpec
作用:提供这个工具的“说明书”,比如它接受哪些参数、展示给模型时该怎么描述。没有这份说明,调用方就不知道该怎样正确使用它。
数据流:进去的是这个处理器本身 → 它调用 create_list_mcp_resources_tool 生成工具规格 → 出来的是一份 ToolSpec,也就是工具的结构化说明。
调用关系:它通常在工具被注册或暴露给模型时使用。真正的规格内容由 create_list_mcp_resources_tool 负责生成,这个函数只是把说明书取出来交给工具系统。
调用图:调用 1 个内部函数(create_list_mcp_resources_tool)。
ListMcpResourcesHandler::supports_parallel_tool_calls38–40 ↗
fn supports_parallel_tool_calls(&self) -> bool
作用:告诉系统这个工具可以并行调用。也就是说,多个资源列表查询可以同时跑,不必排队等前一个结束。
数据流:进去的是这个处理器本身 → 它直接返回固定答案 true → 出来的结果会影响调度器是否允许同时执行多个该工具调用。
调用关系:这是工具调度阶段会看的开关。它不参与查资源本身,但会影响上层什么时候、能不能并发地调用 handle。
ListMcpResourcesHandler::handle42–44 ↗
fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_>
作用:这是工具被真正调用时的公开入口。它把一次工具调用包装成异步任务,让系统可以一边等 MCP 服务器返回,一边继续处理别的事情。
数据流:进去的是一次 ToolInvocation,里面有会话、调用编号、参数等信息 → 它把实际工作交给 handle_call,并用 pin 固定这个异步任务,方便运行时安全地等待它完成 → 出来的是一个将来会产出工具结果或错误的异步对象。
调用关系:上层工具运行器会调用它。它自己不细做业务,只是把活儿转交给 handle_call,相当于前台接单后把单子递给后厨处理。
调用图:调用 1 个内部函数(handle_call);外部调用 1 个(pin)。
ListMcpResourcesHandler::handle_call48–164 ↗
async fn handle_call(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError>
作用:这是这个文件的核心流程:解析调用参数,去一个或多个 MCP 服务器拿资源列表,记录开始和结束事件,再把结果变成模型能读的输出。
数据流:进去的是一次完整工具调用,里面带着当前会话、轮次信息、调用 ID 和参数文本 → 它先确认参数确实是函数调用参数,再解析出 server 和 cursor;空字符串会被当成没有填写。接着它记录工具开始执行,并计时。如果指定了服务器,它会向该服务器请求资源列表,可选地带上分页游标;如果没指定服务器,它会从 MCP 连接管理器拿到所有服务器的资源并合并;如果没指定服务器却传了游标,它会返回错误。拿到数据后,它把资源列表序列化成函数输出,抽取文本内容用于日志,再记录工具结束执行 → 出来的是包装好的工具输出;如果解析、请求或序列化失败,出来的是会反馈给模型的错误,并且同样会记录结束事件和耗时。
调用关系:handle 会调用它来完成真正工作。它把不同小零件串起来:用解析函数读参数,用 emit_tool_call_begin 和 emit_tool_call_end 记录工具生命周期,用会话对象请求 MCP 资源,用 from_single_server 或 from_all_servers 统一结果形状,用 serialize_function_output 和 boxed_tool_output 生成最终返回值。
调用图:调用 4 个内部函数(boxed_tool_output, from_all_servers, from_single_server, function_call_output_content_items_to_text);被 1 处调用(handle);外部调用 10 个(now, clone, call_tool_result_from_content, emit_tool_call_begin, emit_tool_call_end, normalize_optional_string, parse_args_with_default, parse_arguments, serialize_function_output, RespondToModel)。
core/src/tools/handlers/mcp_resource/read_mcp_resource.rs源码 ↗
MCP 可以理解成一种让模型向外部工具或数据源要东西的协议。这个文件专门处理“读取 MCP 资源”这件事:模型传进来服务器名和资源地址 uri,它先确认这确实是函数调用参数,再把参数解析出来,并检查 server、uri 不能为空。接着它记录一次工具调用开始,真正去 session 里调用 read_resource 读取资源。拿到结果后,它会包装成统一的工具输出;如果中途失败,比如参数错、服务器读不到、结果没法序列化,它会把错误也用模型能看懂的方式返回。它还会记录开始和结束事件以及耗时,方便外部看到这次工具调用发生了什么。这个文件重要在于:没有它,模型虽然知道有 read_mcp_resource 这个工具名,但没有一条可靠的执行通道去读资源、报错和留下调用记录。
ReadMcpResourceHandler::tool_name30–32 ↗
fn tool_name(&self) -> ToolName
作用:告诉工具系统:这个处理器对应的工具名字叫 read_mcp_resource。这样当模型请求这个名字时,系统才能找到它。
数据流:它不接收额外业务数据,只生成一个普通工具名 read_mcp_resource → 交给工具注册或匹配流程使用 → 输出一个 ToolName。
调用关系:这是工具处理器的身份牌。工具系统会先用它识别这个处理器;它内部调用 plain 来把普通字符串变成系统认可的工具名格式。
调用图:调用 1 个内部函数(plain)。
ReadMcpResourceHandler::spec34–36 ↗
fn spec(&self) -> ToolSpec
作用:给出这个工具的说明书,也就是模型该怎么调用它、需要哪些参数。没有这个说明书,模型就不容易正确填写 server 和 uri。
数据流:它不读取调用时的具体参数 → 调用 create_read_mcp_resource_tool 生成工具规格 → 返回给工具系统,用于展示和约束调用方式。
调用关系:它处在工具注册或准备阶段。工具系统会问每个处理器“你的工具长什么样”,这个函数就把创建规格的工作交给 create_read_mcp_resource_tool。
调用图:调用 1 个内部函数(create_read_mcp_resource_tool)。
ReadMcpResourceHandler::supports_parallel_tool_calls38–40 ↗
fn supports_parallel_tool_calls(&self) -> bool
作用:声明这个工具允许并行调用。也就是说,多次读取不同 MCP 资源时,不必强行一个等一个。
数据流:它没有输入 → 直接返回 true → 告诉调度器这个工具可以同时跑多个调用。
调用关系:这是给工具调度器看的开关。调度器安排工具执行时,会用这个结果判断能不能把多个 read_mcp_resource 请求并排执行。
ReadMcpResourceHandler::handle42–44 ↗
fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_>
作用:这是外部真正触发工具执行时进入的门口。它把一次工具调用包装成异步任务,让系统可以等待读取结果而不堵住整个流程。
数据流:输入是一份 ToolInvocation,也就是一次工具调用的上下文和参数 → 它把工作交给 handle_call,并用 Box::pin 固定成系统需要的异步返回形式 → 输出一个未来会完成的工具执行任务。
调用关系:工具运行时调用它来启动 read_mcp_resource。它自己不做细活,只负责把请求转交给 handle_call;handle_call 才负责解析参数、读取资源、记录事件和返回结果。
调用图:调用 1 个内部函数(handle_call);外部调用 1 个(pin)。
ReadMcpResourceHandler::handle_call48–147 ↗
async fn handle_call(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError>
作用:这是读取 MCP 资源的核心流程。它检查模型传来的参数,去指定服务器读取资源,把成功结果或失败原因整理成统一格式,并记录这次调用的开始、结束和耗时。
数据流:输入是一整次工具调用,里面有会话 session、当前轮次 turn、调用编号 call_id 和参数 payload → 它先确认 payload 是函数参数,解析出 server 和 uri,并检查它们不是空字符串 → 生成一份 McpInvocation 作为调用记录,发出“开始调用”事件 → 通过 session.read_resource 向指定 MCP 服务器读取 uri 对应的资源 → 成功时把 server、uri 和读取结果打包,再序列化成工具输出;失败时把错误转成模型能看到的消息 → 最后无论成功失败都会发出“结束调用”事件,带上耗时和结果,然后返回工具输出或错误。
调用关系:它由 ReadMcpResourceHandler::handle 调用,是这个文件里真正干活的函数。它会借助 parse_arguments、parse_args 和 normalize_required_string 处理输入,调用 session.read_resource 去拿资源,用 serialize_function_output 和 boxed_tool_output 包装输出,并通过 emit_tool_call_begin、emit_tool_call_end 告诉外部这次工具调用的生命周期。
调用图:调用 2 个内部函数(boxed_tool_output, function_call_output_content_items_to_text);被 1 处调用(handle);外部调用 11 个(now, new, clone, call_tool_result_from_content, emit_tool_call_begin, emit_tool_call_end, normalize_required_string, parse_args, parse_arguments, serialize_function_output (+1 more))。
会话集成与认证流程
这些文件处理 auth-elicitation 解码、会话端 MCP 刷新与审批编排,以及由技能驱动的所需 MCP 依赖安装。
codex-mcp/src/auth_elicitation.rs源码 ↗
当 Codex 调用某个外部应用连接器,比如日历或网盘时,可能会失败,不是工具坏了,而是用户需要重新登录、补权限,或者还没绑定账号。这个文件就是这类情况的“翻译员”。它先从 MCP(Model Context Protocol,一种让程序和工具互相传消息的协议)的工具返回结果里,检查这是不是一个可信的认证失败;然后提取连接器编号、名字、安装链接、错误原因等信息;最后生成一份“认证提示”,里面有给用户看的文案、要打开的网址、以及一个能追踪这次提示的编号。这里很重要的一点是:它不轻信工具自己报的连接器名字和链接,而是优先使用调用方传进来的可信信息,避免被工具结果里的假数据骗到。
connector_auth_failure_from_tool_result63–122 ↗
fn connector_auth_failure_from_tool_result(
result: &CallToolResult,
connector_id: Option<&str>,
connector_name: Option<&str>,
install_url: Option<String>,
) -> Option<CodexAppsConnect
作用:从一次工具调用的返回结果里判断:这次失败是不是因为某个连接器需要重新认证。如果是,它会整理出一份干净、可信的认证失败信息;如果不是,就返回空。
数据流:进去的是工具返回的 CallToolResult、调用方认为可信的连接器编号、连接器名称和安装网址。它先确认结果确实是错误,再到 meta 这块附加信息里找认证失败标记;然后检查连接器编号是否存在且匹配,顺手把空字符串、前后空格这些脏数据过滤掉。最后出来的是 CodexAppsConnectorAuthFailure,里面包含连接器 id、显示名称、登录网址、失败原因和错误码等;如果任何关键条件不满足,就出来 None,表示“不当作认证失败处理”。
调用关系:这是后续流程的入口筛子。build_auth_elicitation_plan 会先调用它,只有它确认“这是可信的认证问题”后,才会继续生成用户提示。测试里的 builds_url_elicitation_payload 也直接用它来验证解析是否正确。它把具体字段清洗的杂活交给 string_auth_failure_field。
调用图:调用 1 个内部函数(string_auth_failure_field);被 2 处调用(build_auth_elicitation_plan, builds_url_elicitation_payload)。
build_auth_elicitation_plan124–138 ↗
fn build_auth_elicitation_plan(
call_id: &str,
result: &CallToolResult,
connector_id: Option<&str>,
connector_name: Option<&str>,
install_url: Option<String>,
) -> Option<CodexApps
作用:把“识别认证失败”和“生成用户提示”连成一步。调用者只要给它工具结果和连接器信息,它就尝试做出一整套认证提示方案。
数据流:进去的是一次调用的 call_id、工具返回结果、连接器 id、连接器名称和安装网址。它先调用 connector_auth_failure_from_tool_result 解析并确认认证失败;如果成功,再把这份失败信息交给 build_auth_elicitation 生成提示内容。出来的是 CodexAppsAuthElicitationPlan,里面同时放着原始整理后的失败信息和要展示给用户的提示;如果不是认证失败,就出来 None。
调用关系:它像一个小装配线,把解析器 connector_auth_failure_from_tool_result 和生成器 build_auth_elicitation 串起来。外层会在工具调用失败后用它决定是否需要让用户去登录。测试 builds_auth_elicitation_plan 用它确认整条链路能连通。
调用图:调用 2 个内部函数(build_auth_elicitation, connector_auth_failure_from_tool_result);被 1 处调用(builds_auth_elicitation_plan)。
build_auth_elicitation140–164 ↗
fn build_auth_elicitation(
call_id: &str,
auth_failure: &CodexAppsConnectorAuthFailure,
) -> CodexAppsAuthElicitation
作用:根据已经确认的认证失败信息,生成真正给前端或上层系统使用的“请去登录/重新连接”的提示包。
数据流:进去的是 call_id 和 CodexAppsConnectorAuthFailure。它把认证失败信息重新包装进 meta,把用户要看的文字交给 auth_elicitation_message 生成,把提示编号交给 auth_elicitation_id 生成,并把安装网址复制到 url 字段。出来的是 CodexAppsAuthElicitation,里面有附加信息、提示文案、跳转网址和唯一提示 id。
调用关系:它通常由 build_auth_elicitation_plan 调用,处在“已经确定要提示用户”之后。它负责把内部的失败信息变成外部能展示、能追踪的形状,并借助 auth_elicitation_message 和 auth_elicitation_id 做文案和编号。
调用图:调用 2 个内部函数(auth_elicitation_id, auth_elicitation_message);被 1 处调用(build_auth_elicitation_plan);外部调用 1 个(json!)。
auth_elicitation_completed_result166–182 ↗
fn auth_elicitation_completed_result(
auth_failure: &CodexAppsConnectorAuthFailure,
meta: Option<serde_json::Value>,
) -> CallToolResult
作用:在用户接受了认证提示之后,生成一个返回给工具调用流程的结果,告诉它:用户已经同意去认证了,现在可以重试。
数据流:进去的是认证失败信息,以及可选的 meta 附加信息。它生成一段文本内容,例如“某某应用的认证请求已接受,请现在重试这个工具调用”,并把它放进 CallToolResult。出来的结果仍标记为 is_error: true,因为原来的工具调用本身还没有真正成功,只是认证步骤已经被接受,需要再试一次。
调用关系:它不参与解析认证失败,而是在用户完成或接受认证提示后的收尾阶段使用。它把状态反馈给上层流程,让上层知道下一步不是继续报错,而是重新发起工具调用。
调用图:外部调用 1 个(vec!)。
auth_elicitation_id184–186 ↗
fn auth_elicitation_id(call_id: &str) -> String
作用:给一次认证提示生成一个稳定的编号,方便系统后面知道这条提示对应哪次工具调用。
数据流:进去的是 call_id,也就是这次工具调用的编号。它在前面加上固定前缀 codex_apps_auth_。出来的是一个字符串,比如 call_123 会变成 codex_apps_auth_call_123。
调用关系:build_auth_elicitation 会调用它来填 elicitation_id。它很小,但有用,因为提示被发出去后,系统需要一个不会混淆的名字来追踪它。
调用图:被 1 处调用(build_auth_elicitation);外部调用 1 个(format!)。
string_auth_failure_field188–198 ↗
fn string_auth_failure_field(
auth_failure: &serde_json::Map<String, serde_json::Value>,
key: &str,
) -> Option<String>
作用:从认证失败的 JSON 数据里安全地取出一个字符串字段,并把空值和只有空格的值丢掉。
数据流:进去的是一张 JSON 对象表和要读取的字段名。它先按字段名查找,再确认这个值真的是字符串,然后去掉前后空格;如果结果不是空字符串,就转成 String 返回。出来的是 Some(干净字符串) 或 None。
调用关系:connector_auth_failure_from_tool_result 多次调用它来读取 auth_reason、link_id、error_code 等字段。它把“字段可能不存在、类型可能不对、内容可能是空的”这些小坑统一处理掉,让主解析逻辑更清楚。
调用图:被 1 处调用(connector_auth_failure_from_tool_result);外部调用 1 个(get)。
auth_elicitation_message200–219 ↗
fn auth_elicitation_message(auth_failure: &CodexAppsConnectorAuthFailure) -> String
作用:根据认证失败的原因,挑一句更贴近情况的用户提示文案。这样用户看到的不是生硬错误码,而是知道自己该做什么。
数据流:进去的是 CodexAppsConnectorAuthFailure,里面可能带有 auth_reason。它根据原因分几种情况:需要升级 OAuth 权限、需要重新认证、缺少绑定,或者普通登录。出来的是一条自然语言消息,里面会带上连接器名称。
调用关系:build_auth_elicitation 调用它来生成 message 字段。它把机器能懂的 auth_reason 翻译成人能懂的话,是认证提示里最贴近用户的一环。
调用图:被 1 处调用(build_auth_elicitation);外部调用 1 个(format!)。
tests::auth_failure_result226–249 ↗
tests::parses_auth_failure_from_trusted_connector_metadata252–272 ↗
fn parses_auth_failure_from_trusted_connector_metadata()
作用:测试解析函数能不能从工具返回结果里正确识别认证失败,并且使用调用方传入的可信连接器名称和网址。
数据流:进去的是测试代码里准备好的假结果和可信连接器信息。它调用 connector_auth_failure_from_tool_result 后,把实际输出和预期的 CodexAppsConnectorAuthFailure 做比较。测试通过就说明解析出的 id、名称、链接、原因、错误码等都符合预期。
调用关系:它验证 connector_auth_failure_from_tool_result 的主成功路径,尤其确认工具 meta 里那个不可信的 connector_name 不会覆盖调用方提供的 Google Calendar。
调用图:外部调用 1 个(assert_eq!)。
tests::rejects_missing_or_mismatched_connector_ids275–294 ↗
fn rejects_missing_or_mismatched_connector_ids()
作用:测试解析函数在连接器编号缺失或不匹配时会拒绝处理,避免把错误认证信息套到别的连接器上。
数据流:进去的是同一份假认证失败结果,但第一次不传 connector_id,第二次传一个不匹配的 connector_drive。它分别调用 connector_auth_failure_from_tool_result,并检查返回值都是 None。出来的测试结论是:缺少可信 id 或 id 对不上时,不会生成认证失败对象。
调用关系:它补上 connector_auth_failure_from_tool_result 的安全边界测试。这个测试很关键,因为认证提示里有登录链接和应用身份,不能让一个连接器的失败冒充另一个连接器。
调用图:外部调用 1 个(assert_eq!)。
tests::builds_url_elicitation_payload297–331 ↗
fn builds_url_elicitation_payload()
作用:测试 build_auth_elicitation 能不能把认证失败信息变成完整的用户提示包,包括 meta、文案、网址和提示编号。
数据流:进去的是 auth_failure_result 造出的假结果,以及可信连接器信息。测试先用 connector_auth_failure_from_tool_result 得到认证失败对象,再调用 build_auth_elicitation。出来的提示包会被拿来和预期值比较,确认文案是“重新连接 Google Calendar”、网址正确、elicitation_id 正确、meta 内容也正确。
调用关系:它覆盖了 connector_auth_failure_from_tool_result 和 build_auth_elicitation 的组合效果,重点看最终要交给上层展示的 payload 是否长得对。
调用图:调用 1 个内部函数(connector_auth_failure_from_tool_result);外部调用 2 个(assert_eq!, auth_failure_result)。
tests::builds_auth_elicitation_plan334–346 ↗
fn builds_auth_elicitation_plan()
作用:测试一站式函数 build_auth_elicitation_plan 能不能从工具失败结果直接做出完整认证提示方案。
数据流:进去的是 call_123、假工具结果和可信连接器信息。它调用 build_auth_elicitation_plan,得到 plan 后检查连接器名称和提示编号。出来的测试结论是:计划里既有正确的认证失败信息,也有正确生成的提示。
调用关系:它验证最高层的串联函数 build_auth_elicitation_plan,确保解析和生成两步能顺利接起来。这个测试更接近真实使用方式:外层流程通常不想分两步做,而是直接要一个可执行的提示方案。
调用图:调用 1 个内部函数(build_auth_elicitation_plan);外部调用 2 个(assert_eq!, auth_failure_result)。
core/src/session/mcp.rs源码 ↗
可以把这个文件看成“会话”和“外部工具服务器”之间的前台接待加安全门卫。MCP 服务器可能会提供工具、资源,也可能在执行前向用户发起 elicitation(大意是“请用户确认或补充信息”)。这个文件一边把这些请求转成前端能看到的事件,一边记录待回复的请求,等用户或系统给答案后再送回 MCP 服务器。更重要的是,它会识别某些带特殊元数据的 MCP 审批请求,把它们交给 Guardian(项目里的安全审批系统)判断是否允许。它还负责刷新 MCP 服务器列表、重建连接管理器、取消启动中的 MCP 连接,并提供列资源、读资源、调工具这些会话级入口。整体作用是:让外部工具能用,但不能绕过会话状态、用户确认和安全审批。
GuardianMcpElicitationReviewer::new54–58 ↗
fn new(session: &Arc<Session>) -> Self
作用:创建一个 Guardian 用的 MCP 请求审核员。它只保存会话的弱引用,意思是“我知道会话在哪,但不会因为我存在就强行让会话活着”。
数据流:输入一个共享的 Session → 把它降级成弱引用保存起来 → 输出一个 GuardianMcpElicitationReviewer,用来以后审核 MCP 的确认请求。
调用关系:Session::mcp_elicitation_reviewer 会调用它,把当前会话包装成一个可交给 MCP 系统使用的审核器。
调用图:被 1 处调用(mcp_elicitation_reviewer);外部调用 1 个(downgrade)。
GuardianMcpElicitationReviewer::review62–73 ↗
fn review(
&self,
request: ElicitationReviewRequest,
) -> BoxFuture<'static, anyhow::Result<Option<ElicitationResponse>>>
作用:当 MCP 系统拿到一个需要审核的请求时,这个函数负责把请求转交给当前会话里的 Guardian 审批流程。会话已经结束时,它就什么也不做。
数据流:输入一个 MCP elicitation 审核请求 → 先尝试从弱引用找回 Session → 找得到就调用 review_guardian_mcp_elicitation,找不到就返回没有回复 → 输出可能的 ElicitationResponse。
调用关系:它是 ElicitationReviewer 接口的实现,被 MCP 连接层在需要安全审核时调用;真正的判断交给 review_guardian_mcp_elicitation。
调用图:调用 1 个内部函数(review_guardian_mcp_elicitation);外部调用 2 个(pin, clone)。
Session::runtime_mcp_config77–82 ↗
async fn runtime_mcp_config(&self, config: &Config) -> McpConfig
作用:算出当前会话实际要用的 MCP 配置。它不是只读静态配置,而是结合线程初始化状态后得到运行时版本。
数据流:输入全局 Config → 读取会话里的 mcp_manager 和 mcp_thread_init → 异步生成当前线程/会话可用的 McpConfig → 返回给后续连接和刷新流程使用。
调用关系:Session::runtime_mcp_servers 和 Session::refresh_mcp_servers_inner 都会用它,因为它们需要先知道当前真正生效的 MCP 设置。
调用图:被 2 处调用(refresh_mcp_servers_inner, runtime_mcp_servers)。
Session::runtime_mcp_servers84–89 ↗
async fn runtime_mcp_servers(
&self,
config: &Config,
) -> HashMap<String, McpServerConfig>
作用:列出当前运行时配置里实际启用的 MCP 服务器。外部想知道“现在有哪些 MCP 服务器可用”时会用它。
数据流:输入 Config → 先调用 Session::runtime_mcp_config 得到运行时 MCP 配置 → 再从配置里提取服务器清单 → 输出服务器名到服务器配置的表。
调用关系:它建立在 runtime_mcp_config 之上,把更完整的配置缩小成服务器列表,方便其他地方直接使用。
调用图:调用 1 个内部函数(runtime_mcp_config);外部调用 1 个(configured_mcp_servers)。
Session::mcp_elicitation_reviewer91–93 ↗
fn mcp_elicitation_reviewer(self: &Arc<Self>) -> ElicitationReviewerHandle
作用:给当前会话生成一个 MCP elicitation 审核器句柄。简单说,就是把“遇到 MCP 审批请求时找谁判断”这件事告诉 MCP 系统。
数据流:输入当前 Session 的 Arc 共享指针 → 创建 GuardianMcpElicitationReviewer → 再包成 ElicitationReviewerHandle → 输出给 MCP 连接管理器使用。
调用关系:它调用 GuardianMcpElicitationReviewer::new,通常在创建或刷新 MCP 连接时把审核能力塞给连接管理器。
Session::request_mcp_server_elicitation99–211 ↗
async fn request_mcp_server_elicitation(
&self,
turn_context: &TurnContext,
request_id: RequestId,
params: McpServerElicitationRequestParams,
) -> McpServerElicitat
作用:处理 MCP 服务器向用户或客户端发起的“请确认/请填写”请求。它会把请求发成会话事件,并等用户回复后再把结果交回服务器。
数据流:输入当前轮次上下文、请求编号和 MCP 服务器请求参数 → 如果系统设置了自动拒绝/拦截,就直接返回一个本地结果且不发送事件 → 否则把表单或 URL 请求转成协议事件,登记一个一次性回复通道,发给前端,并可能记录插件安装提示的遥测 → 最后等待回复并输出 McpServerElicitationOutcome,同时更新待处理请求表和轮次元数据。
调用关系:这是 MCP 服务器要向外界发问时的主入口。它会调用 plugin_install_elicitation_telemetry_metadata 判断是否要记遥测;后续用户回复会由 Session::resolve_elicitation 找到这里登记的通道并送回来。
调用图:调用 1 个内部函数(plugin_install_elicitation_telemetry_metadata);外部调用 8 个(Integer, String, clone, channel, ElicitationRequest, json!, to_value, warn!)。
Session::resolve_elicitation217–245 ↗
async fn resolve_elicitation(
&self,
server_name: String,
id: RequestId,
response: ElicitationResponse,
) -> anyhow::Result<()>
作用:把用户或客户端对 MCP elicitation 的回答送回正确的等待方。它解决的是“这个回复应该回到哪个请求”的配对问题。
数据流:输入服务器名、请求编号和回复内容 → 先在当前活跃轮次的待处理 elicitation 表里查找 → 找到就通过一次性通道发给等待中的 request_mcp_server_elicitation → 找不到就交给 MCP 连接管理器处理 → 成功返回空结果,失败返回错误。
调用关系:它通常在前端或上层收到用户回答后被调用;优先处理当前会话内登记的请求,兜底再交给 mcp_connection_manager。
Session::list_resources247–257 ↗
async fn list_resources(
&self,
server: &str,
params: Option<PaginatedRequestParams>,
) -> anyhow::Result<ListResourcesResult>
作用:向指定 MCP 服务器询问它有哪些资源可用。资源可以理解成外部服务器暴露出来的文件、数据项或信息入口。
数据流:输入服务器名和可选分页参数 → 从当前 MCP 连接管理器取连接 → 调用对应服务器的 list_resources → 输出资源列表结果或错误。
调用关系:这是 Session 层提供的转发入口,本身不解析资源内容,只把请求交给 mcp_connection_manager。
Session::list_resource_templates259–269 ↗
async fn list_resource_templates(
&self,
server: &str,
params: Option<PaginatedRequestParams>,
) -> anyhow::Result<ListResourceTemplatesResult>
作用:向指定 MCP 服务器询问它支持哪些“资源模板”。模板可以理解成带参数的资源地址格式,比如按某个 ID 读取数据。
数据流:输入服务器名和可选分页参数 → 找到当前 MCP 连接管理器 → 转发 list_resource_templates 请求 → 输出模板列表或错误。
调用关系:它和 list_resources 类似,都是会话层的薄封装,真正通信由 mcp_connection_manager 完成。
Session::read_resource271–281 ↗
async fn read_resource(
&self,
server: &str,
params: ReadResourceRequestParams,
) -> anyhow::Result<ReadResourceResult>
作用:从指定 MCP 服务器读取某个资源的具体内容。外部调用者不需要直接碰 MCP 连接细节。
数据流:输入服务器名和读取资源的参数 → 通过当前 MCP 连接管理器转发 read_resource → 输出读到的资源内容或错误。
调用关系:它位于会话 API 和 MCP 连接层之间,只负责把请求送到正确服务器。
Session::call_tool283–295 ↗
async fn call_tool(
&self,
server: &str,
tool: &str,
arguments: Option<serde_json::Value>,
meta: Option<serde_json::Value>,
) -> anyhow::Result<CallToolResu
作用:调用某个 MCP 服务器提供的工具。比如外部服务器暴露了搜索、查询、操作类能力,就通过这里发起调用。
数据流:输入服务器名、工具名、可选参数和可选元数据 → 交给当前 MCP 连接管理器调用对应工具 → 输出工具执行结果或错误。
调用关系:这是会话层调用 MCP 工具的入口,真正的网络/进程通信和协议处理交给 mcp_connection_manager。
Session::refresh_mcp_servers_inner297–368 ↗
async fn refresh_mcp_servers_inner(
&self,
turn_context: &TurnContext,
mcp_servers: HashMap<String, McpServerConfig>,
store_mode: OAuthCredentialsStoreMode,
key
作用:真正重建 MCP 服务器连接管理器的核心流程。配置变化、认证状态变化或会话环境变化后,都靠它让 MCP 连接重新按新状态工作。
数据流:输入轮次上下文、服务器配置、OAuth 凭据保存方式、钥匙串后端类型和可选审核器 → 读取当前认证和运行时 MCP 配置 → 计算实际启用的服务器、认证状态、工作目录、运行环境、取消令牌等 → 创建新的 McpConnectionManager → 继承旧管理器的自动拒绝设置 → 把新管理器存回服务里。
调用关系:Session::refresh_mcp_servers_if_requested 和 Session::refresh_mcp_servers_now 都把具体刷新工作交给它。它也会调用 Session::runtime_mcp_config,并把 Guardian 审核器传给新的连接管理器。
调用图:调用 4 个内部函数(new, new, runtime_mcp_config, permission_profile);被 2 处调用(refresh_mcp_servers_if_requested, refresh_mcp_servers_now);外部调用 3 个(new, new, tool_plugin_provenance)。
Session::refresh_mcp_servers_if_requested370–420 ↗
async fn refresh_mcp_servers_if_requested(
&self,
turn_context: &TurnContext,
elicitation_reviewer: Option<ElicitationReviewerHandle>,
)
作用:如果之前有人提交了“刷新 MCP 服务器配置”的请求,这个函数就取出来并执行;没有请求就直接返回。
数据流:输入轮次上下文和可选审核器 → 从 pending_mcp_server_refresh_config 里取走待刷新配置 → 把 JSON 形式的服务器、OAuth 保存方式、钥匙串类型解析成真正类型 → 解析失败就记录警告并停止 → 解析成功就调用 refresh_mcp_servers_inner。
调用关系:它是“延迟刷新”的入口,负责把暂存的刷新配置变成 refresh_mcp_servers_inner 能用的参数。
调用图:调用 1 个内部函数(refresh_mcp_servers_inner);外部调用 1 个(warn!)。
Session::refresh_mcp_servers_now422–438 ↗
async fn refresh_mcp_servers_now(
&self,
turn_context: &TurnContext,
mcp_servers: HashMap<String, McpServerConfig>,
store_mode: OAuthCredentialsStoreMode,
keyri
作用:立刻按给定配置刷新 MCP 服务器,不等待暂存请求。适合上层已经拿到完整配置并希望马上生效的场景。
数据流:输入轮次上下文、服务器配置、凭据保存模式、钥匙串类型和可选审核器 → 原样转交给 refresh_mcp_servers_inner → 刷新完成后当前会话使用新的 MCP 连接管理器。
调用关系:它是“立即刷新”的公开入口,所有复杂工作都交给 refresh_mcp_servers_inner。
调用图:调用 1 个内部函数(refresh_mcp_servers_inner)。
Session::mcp_startup_cancellation_token441–447 ↗
async fn mcp_startup_cancellation_token(&self) -> CancellationToken
作用:在测试里取出当前 MCP 启动取消令牌。取消令牌可以理解成一个“停止信号”,用来让正在启动的任务停下来。
数据流:不需要业务输入 → 加锁读取 services.mcp_startup_cancellation_token → 克隆并返回当前取消令牌,不改变实际状态。
调用关系:这个函数只在测试编译时存在,用来检查或控制 MCP 启动取消相关行为。
Session::cancel_mcp_startup449–455 ↗
async fn cancel_mcp_startup(&self)
作用:取消当前正在进行的 MCP 启动流程。比如配置刷新或会话结束时,不希望旧的启动任务继续跑。
数据流:不需要额外输入 → 加锁拿到当前 MCP 启动取消令牌 → 调用 cancel 发出停止信号 → 没有返回业务数据,但会影响正在监听这个令牌的启动任务。
调用关系:它提供一个会话级的停止按钮;refresh_mcp_servers_inner 在刷新时也会替换取消令牌,避免旧启动流程干扰新连接。
review_guardian_mcp_elicitation458–507 ↗
async fn review_guardian_mcp_elicitation(
session: Arc<Session>,
request: ElicitationReviewRequest,
) -> anyhow::Result<Option<ElicitationResponse>>
作用:把 MCP 发来的特定审批请求交给 Guardian 做安全判断。它决定这个请求是不需要审、要拒绝,还是要走完整审批。
数据流:输入 Session 和 ElicitationReviewRequest → 找当前活跃轮次,没有就返回无回复 → 根据配置判断这个服务器/连接器是否应该走 Guardian → 解析请求元数据生成 Guardian 审批请求 → 如果格式不合规则直接拒绝 → 否则创建审核 ID,调用 Guardian 审批,最后把 Guardian 决策转成 MCP 能懂的回复。
调用关系:GuardianMcpElicitationReviewer::review 会调用它。它会用 elicitation_connector_id、guardian_elicitation_review_request、mcp_elicitation_decline_without_message 和 mcp_elicitation_response_from_guardian_decision 串起整条审批链。
调用图:调用 5 个内部函数(mcp_approvals_reviewer, elicitation_connector_id, guardian_elicitation_review_request, mcp_elicitation_decline_without_message, mcp_elicitation_response_from_guardian_decision);被 1 处调用(review);外部调用 4 个(new_guardian_review_id, review_approval_request, routes_approval_to_guardian_with_reviewer, warn!)。
guardian_elicitation_review_request509–586 ↗
fn guardian_elicitation_review_request(
request: &ElicitationReviewRequest,
) -> GuardianElicitationReview
作用:检查一个 MCP elicitation 是否真的是 Guardian 支持的审批请求,并把它改造成 Guardian 能审核的格式。它像安检员,先看证件和表格是不是合规。
数据流:输入 MCP 的 ElicitationReviewRequest → 只接受带特定元数据的表单请求,URL 请求如果伪装成审批会被拒绝 → 检查请求类型、审批种类、表单是否为空、工具名和参数是否合法 → 合法则输出 GuardianApprovalRequest::McpToolCall,不合法则输出拒绝原因或不需要审核。
调用关系:review_guardian_mcp_elicitation 调用它来决定下一步。它内部用 meta_requests_approval_request、metadata_str 和 metadata_owned_string 读取元数据。
调用图:调用 3 个内部函数(meta_requests_approval_request, metadata_owned_string, metadata_str);被 1 处调用(review_guardian_mcp_elicitation);外部调用 6 个(new, new, Object, ApprovalRequest, Decline, format!)。
elicitation_connector_id588–595 ↗
fn elicitation_connector_id(elicitation: &CreateElicitationRequestParams) -> Option<&str>
作用:从 MCP elicitation 的元数据里取出连接器 ID。连接器 ID 用来判断这个请求来自哪个外部集成。
数据流:输入表单或 URL 类型的 elicitation 参数 → 查看它的 meta 字段 → 如果里面有 connector_id 字符串就返回它的引用,否则返回空。
调用关系:review_guardian_mcp_elicitation 用它配合配置判断这个连接器的审批应该由谁处理。
调用图:被 1 处调用(review_guardian_mcp_elicitation)。
meta_requests_approval_request597–601 ↗
fn meta_requests_approval_request(meta: &Option<Meta>) -> bool
作用:判断一段 MCP 元数据是否声明“这是一个审批请求”。它只做一个很小但关键的分类判断。
数据流:输入可选 Meta → 从里面读取 request_type → 如果等于 approval_request 就返回 true,否则返回 false。
调用关系:guardian_elicitation_review_request 在遇到 URL elicitation 时用它判断是不是有人发来了 Guardian 不支持的审批形式。
调用图:被 1 处调用(guardian_elicitation_review_request)。
metadata_str603–605 ↗
fn metadata_str(meta: &'a Map<String, Value>, key: &str) -> Option<&'a str>
作用:从 JSON 元数据里按键取一个字符串值。它是这个文件里读取元数据的基础小工具。
数据流:输入一个 JSON 对象和键名 → 查找对应值 → 如果值存在且确实是字符串,就返回字符串引用;否则返回空。
调用关系:guardian_elicitation_review_request、metadata_owned_string 和 plugin_install_elicitation_telemetry_metadata 都靠它安全读取元数据字段。
调用图:被 3 处调用(guardian_elicitation_review_request, metadata_owned_string, plugin_install_elicitation_telemetry_metadata);外部调用 1 个(get)。
metadata_owned_string607–612 ↗
fn metadata_owned_string(meta: &Map<String, Value>, key: &str) -> Option<String>
作用:从元数据里取一个干净的字符串副本。它会去掉前后空白,并把空字符串当作没有值。
数据流:输入 JSON 元数据和键名 → 调用 metadata_str 取字符串 → trim 去掉空白 → 非空就复制成 String 返回,空或类型不对就返回空。
调用关系:guardian_elicitation_review_request 用它提取工具名、连接器信息等;plugin_install_elicitation_telemetry_metadata 用它提取遥测字段。
调用图:调用 1 个内部函数(metadata_str);被 2 处调用(guardian_elicitation_review_request, plugin_install_elicitation_telemetry_metadata)。
plugin_install_elicitation_telemetry_metadata614–639 ↗
fn plugin_install_elicitation_telemetry_metadata(
event: &EventMsg,
) -> Option<PluginInstallElicitationTelemetryMetadata>
作用:识别一次 elicitation 是否是在建议安装插件工具,并提取遥测需要的工具信息。遥测就是记录“发生了什么”的统计信息。
数据流:输入一个事件 → 只有当事件是 ElicitationRequest,且请求是带对象元数据的表单时才继续 → 检查 approval_kind 是否是工具建议、suggest_type 是否是 install → 提取 tool_type、tool_id、tool_name → 成功则输出 PluginInstallElicitationTelemetryMetadata,否则返回空。
调用关系:Session::request_mcp_server_elicitation 在发出请求事件后调用它;如果识别成功,就记录插件安装 elicitation 已发送的遥测。
调用图:调用 2 个内部函数(metadata_owned_string, metadata_str);被 1 处调用(request_mcp_server_elicitation)。
mcp_elicitation_request_id641–646 ↗
fn mcp_elicitation_request_id(id: &RequestId) -> String
作用:把 MCP 请求 ID 统一转成字符串。因为请求 ID 可能本来是数字,也可能本来就是字符串。
数据流:输入 RequestId → 如果是字符串就复制字符串,如果是数字就转成数字文本 → 输出统一的 String。
调用关系:它用于生成日志和 Guardian 审批 ID,让不同类型的请求编号都能稳定显示和拼接。
mcp_elicitation_response_from_guardian_decision648–660 ↗
async fn mcp_elicitation_response_from_guardian_decision(
session: &Session,
review_id: &str,
decision: ReviewDecision,
) -> ElicitationResponse
作用:把 Guardian 的审批结果变成 MCP elicitation 的回复。特别是拒绝时,它会先拿到适合展示给用户或服务器的拒绝消息。
数据流:输入 Session、审核 ID 和 Guardian 决策 → 如果决策是拒绝,就异步获取 Guardian 拒绝说明 → 然后调用 mcp_elicitation_response_from_guardian_decision_parts 组装最终 ElicitationResponse → 输出给 MCP 服务器。
调用关系:review_guardian_mcp_elicitation 在 Guardian 审批完成后调用它;具体每种决策如何映射由 mcp_elicitation_response_from_guardian_decision_parts 处理。
调用图:调用 1 个内部函数(mcp_elicitation_response_from_guardian_decision_parts);被 1 处调用(review_guardian_mcp_elicitation);外部调用 1 个(guardian_rejection_message)。
mcp_elicitation_response_from_guardian_decision_parts662–687 ↗
fn mcp_elicitation_response_from_guardian_decision_parts(
decision: ReviewDecision,
denial_message: Option<String>,
) -> ElicitationResponse
作用:按 Guardian 决策类型生成 MCP 能理解的接受、拒绝或取消回复。它是审批结果到协议回复的映射表。
数据流:输入 Guardian 决策和可选拒绝消息 → 批准类决策转成 Accept,并附上自动审核元数据 → Denied 转成带消息的 Decline → TimedOut 转成带超时消息的 Decline → Abort 转成 Cancel → 输出 ElicitationResponse。
调用关系:mcp_elicitation_response_from_guardian_decision 调用它。它会根据情况调用 mcp_elicitation_auto_meta、mcp_elicitation_decline_with_message 和 Guardian 的超时消息函数。
调用图:调用 2 个内部函数(mcp_elicitation_auto_meta, mcp_elicitation_decline_with_message);被 1 处调用(mcp_elicitation_response_from_guardian_decision);外部调用 2 个(guardian_timeout_message, json!)。
mcp_elicitation_decline_with_message689–698 ↗
fn mcp_elicitation_decline_with_message(message: String) -> ElicitationResponse
作用:生成一个带原因说明的 MCP 拒绝回复。适合告诉对方“为什么这次不允许”。
数据流:输入一段拒绝消息 → 构造 ElicitationResponse,action 设为 Decline,content 为空,meta 里放入 message 和 AutoReview 标记 → 输出这个拒绝回复。
调用关系:mcp_elicitation_response_from_guardian_decision_parts 在 Guardian 拒绝或超时时调用它。
调用图:被 1 处调用(mcp_elicitation_response_from_guardian_decision_parts);外部调用 1 个(json!)。
mcp_elicitation_decline_without_message700–706 ↗
fn mcp_elicitation_decline_without_message() -> ElicitationResponse
作用:生成一个不带具体说明的 MCP 拒绝回复。适合格式不支持或不想暴露细节的自动拒绝。
数据流:不需要输入 → 构造 action 为 Decline、content 为空的 ElicitationResponse → meta 使用 mcp_elicitation_auto_meta → 输出回复。
调用关系:review_guardian_mcp_elicitation 在请求不符合 Guardian 审核要求但需要拒绝时调用它。
调用图:调用 1 个内部函数(mcp_elicitation_auto_meta);被 1 处调用(review_guardian_mcp_elicitation)。
mcp_elicitation_auto_meta708–712 ↗
fn mcp_elicitation_auto_meta() -> serde_json::Value
作用:生成一小段元数据,标明这次 elicitation 回复来自自动审核器。这样接收方能知道不是用户手填的普通表单结果。
数据流:不需要输入 → 创建一个 JSON 对象,里面写入 approvals_reviewer 为 AutoReview → 输出这段 JSON 元数据。
调用关系:mcp_elicitation_decline_without_message 和 mcp_elicitation_response_from_guardian_decision_parts 都用它给自动接受、拒绝或取消的回复打标。
调用图:被 2 处调用(mcp_elicitation_decline_without_message, mcp_elicitation_response_from_guardian_decision_parts);外部调用 1 个(json!)。
core/src/mcp_skill_dependencies.rs源码 ↗
有些技能不是单独就能工作的,它们要依赖外部的 MCP 服务器,就像手机应用要先装好配套插件。这个文件做的事就是:看当前提到的技能需要哪些 MCP;和已经安装的 MCP 对比;发现缺少的就提示用户“要不要安装”;用户同意后,把缺少的服务器加入全局配置文件。它还会处理一些细节:同一轮会话里已经问过的服务器不反复问;只对官方客户端启用;如果服务器支持 OAuth(网页登录授权),安装后会尝试登录;最后刷新当前会话里的 MCP 列表,让新装的工具马上能用。没有它,技能可能会被选中但缺少工具,用户只会看到功能失效或报错。
maybe_prompt_and_install_mcp_dependencies34–79 ↗
async fn maybe_prompt_and_install_mcp_dependencies(
sess: &Session,
turn_context: &TurnContext,
cancellation_token: &CancellationToken,
mentioned_skills: &[SkillMetadata],
elicitat
作用:这是自动安装 MCP 依赖的第一道入口。它先判断现在是否适合打扰用户:是不是官方客户端、功能开关是否打开、技能是否真的缺服务器;都满足才继续询问和安装。
数据流:进去的是当前会话、这一轮对话上下文、取消信号、被提到的技能列表,以及可选的权限复核器。它读取客户端来源、功能开关和当前已运行的 MCP 服务器,算出缺哪些,再过滤掉本会话已经问过的项。出来没有直接返回值;如果用户同意,它会触发后续安装,并可能改动全局 MCP 配置和当前会话里的 MCP 状态。
调用关系:它由 build_skills_and_plugins 在准备技能和插件时调用。它自己不直接写配置,而是先交给 collect_missing_mcp_dependencies 找缺口,再交给 filter_prompted_mcp_dependencies 避免重复提问,接着用 should_install_mcp_dependencies 问用户,最后把真正安装的活交给 maybe_install_mcp_dependencies。
调用图:调用 6 个内部函数(collect_missing_mcp_dependencies, filter_prompted_mcp_dependencies, maybe_install_mcp_dependencies, should_install_mcp_dependencies, is_first_party_originator, originator);被 1 处调用(build_skills_and_plugins);外部调用 2 个(is_empty, runtime_mcp_servers)。
maybe_install_mcp_dependencies81–211 ↗
async fn maybe_install_mcp_dependencies(
sess: &Session,
turn_context: &TurnContext,
config: &crate::config::Config,
mentioned_skills: &[SkillMetadata],
elicitation_reviewer: Optio
作用:这个函数真正把缺少的 MCP 服务器装进配置里。它还会在需要时尝试 OAuth 登录,也就是打开网页登录授权,让新服务器能正常访问受保护的资源。
数据流:进去的是会话、对话上下文、配置、被提到的技能,以及可选的权限复核器。它先重新检查功能开关和缺失项,然后读取全局 MCP 配置,把缺少的服务器加入进去并保存。保存成功后,它会对支持登录的服务器尝试授权;最后把更新后的服务器列表刷新到当前会话中。出来没有普通返回值;主要结果是全局配置被更新,当前运行中的 MCP 列表被刷新。
调用关系:它通常由 maybe_prompt_and_install_mcp_dependencies 在用户同意后调用。它会复用 collect_missing_mcp_dependencies 找缺项,调用 load_global_mcp_servers 读旧配置,用 ConfigEditsBuilder 写新配置,还会调用 oauth_login_support、perform_oauth_login 等外部能力完成登录,最后交给 Session 的刷新方法让新服务器立即生效。
调用图:调用 2 个内部函数(new, collect_missing_mcp_dependencies);被 1 处调用(maybe_prompt_and_install_mcp_dependencies);外部调用 12 个(new, auth_keyring_backend_kind, clone, is_empty, load_global_mcp_servers, oauth_login_support, resolve_oauth_scopes, should_retry_without_scopes, perform_oauth_login, refresh_mcp_servers_now (+2 more))。
should_install_mcp_dependencies213–287 ↗
async fn should_install_mcp_dependencies(
sess: &Session,
turn_context: &TurnContext,
missing: &HashMap<String, McpServerConfig>,
cancellation_token: &CancellationToken,
) -> bool
作用:这个函数决定要不要安装缺少的 MCP 服务器。能自动批准时它直接放行;不能自动批准时,它会向用户弹出一个选择题。
数据流:进去的是会话、对话上下文、缺少的服务器表,以及取消信号。它先看当前权限策略是否允许自动同意;如果不行,就把缺少的服务器名字整理成一段文字,向用户询问“安装”还是“继续但跳过”。如果等待期间被取消,它会按空回答处理。最后返回 true 或 false,并把这批服务器标记为“已经问过”。
调用关系:它由 maybe_prompt_and_install_mcp_dependencies 调用,位置在“发现确实缺服务器”之后、“真正安装”之前。它会用 format_missing_mcp_dependencies 生成给用户看的列表,用 sess.request_user_input 发起提问,并用 sess.record_mcp_dependency_prompted 记录本会话已经提示过,避免后面反复打扰。
调用图:调用 2 个内部函数(format_missing_mcp_dependencies, permission_profile);被 1 处调用(maybe_prompt_and_install_mcp_dependencies);外部调用 7 个(default, mcp_permission_prompt_is_auto_approved, record_mcp_dependency_prompted, request_user_input, format!, select!, vec!)。
filter_prompted_mcp_dependencies289–303 ↗
async fn filter_prompted_mcp_dependencies(
sess: &Session,
missing: &HashMap<String, McpServerConfig>,
) -> HashMap<String, McpServerConfig>
作用:这个函数把本会话里已经提示过的 MCP 依赖过滤掉。这样用户选择跳过后,系统不会在同一次会话里一直追问同一件事。
数据流:进去的是会话和当前缺少的服务器表。它从会话里读出“已经问过”的服务器标识;如果没有记录,就原样返回全部缺少项;如果有记录,就只留下还没问过的服务器。出来的是一个新的缺失服务器表。
调用关系:它由 maybe_prompt_and_install_mcp_dependencies 在准备弹窗前调用。它依赖 Session 保存的提示记录,并用和其他函数一致的服务器标识规则来判断“是不是同一个服务器”。
调用图:被 1 处调用(maybe_prompt_and_install_mcp_dependencies);外部调用 1 个(mcp_dependency_prompted)。
format_missing_mcp_dependencies305–309 ↗
fn format_missing_mcp_dependencies(missing: &HashMap<String, McpServerConfig>) -> String
作用:这个小函数把缺少的 MCP 服务器名字整理成一串好读的文字,方便放进用户提示里。
数据流:进去的是缺少服务器的表。它取出所有名字,按字母顺序排好,再用逗号连起来。出来的是一段字符串,比如“a, b, c”。
调用关系:它由 should_install_mcp_dependencies 调用,只负责把机器内部的数据变成用户能看懂的列表文字,不参与判断和安装。
调用图:被 1 处调用(should_install_mcp_dependencies)。
canonical_mcp_key311–318 ↗
fn canonical_mcp_key(transport: &str, identifier: &str, fallback: &str) -> String
作用:这个函数给 MCP 服务器生成一个稳定的“身份证号”。它用传输方式和关键地址或命令来识别同一个服务器,而不是只靠显示名称。
数据流:进去的是传输方式、服务器的关键标识,以及备用名字。它先去掉标识两边空白;如果标识为空,就用备用名字;否则拼成类似“mcp__传输方式__标识”的字符串。出来的是统一格式的键。
调用关系:它是底层辅助函数,被 canonical_mcp_server_key 和 canonical_mcp_dependency_key 使用。这样已安装服务器和技能声明的依赖能用同一把尺子比较,避免名字不同但其实是同一个服务器时被重复安装。
调用图:被 2 处调用(canonical_mcp_dependency_key, canonical_mcp_server_key);外部调用 1 个(format!)。
canonical_mcp_server_key320–329 ↗
fn canonical_mcp_server_key(name: &str, config: &McpServerConfig) -> String
作用:这个函数把已经配置好的 MCP 服务器转成统一标识。它会根据服务器是本地命令方式还是 HTTP 地址方式,选出最能代表它的字段。
数据流:进去的是服务器名字和服务器配置。它查看传输配置:如果是 stdio(通过本地命令启动),就用 command;如果是 streamable_http(通过网络地址访问),就用 url。然后交给 canonical_mcp_key 生成统一键。出来的是这个已安装服务器的稳定标识。
调用关系:它服务于缺失检测和去重流程。collect_missing_mcp_dependencies 会用它整理已安装服务器,其他提示过滤逻辑也依靠同样的标识,确保“已装过”和“已问过”的判断一致。
调用图:调用 1 个内部函数(canonical_mcp_key)。
canonical_mcp_dependency_key331–348 ↗
fn canonical_mcp_dependency_key(dependency: &SkillToolDependency) -> Result<String, String>
作用:这个函数把技能声明的 MCP 依赖转成统一标识。它还会检查声明是否完整,比如 HTTP 依赖必须有 url,本地命令依赖必须有 command。
数据流:进去的是一个技能工具依赖。它读取依赖的 transport(传输方式;没写时默认当作 streamable_http),再按类型取 url 或 command,生成统一键。如果缺少必要字段或传输方式不支持,就返回错误信息。出来要么是标识字符串,要么是说明哪里不对的错误。
调用关系:它由 collect_missing_mcp_dependencies 调用,用来把技能里的需求和已经安装的服务器放到同一种格式下比较。它内部会调用 canonical_mcp_key 完成最终拼接。
调用图:调用 1 个内部函数(canonical_mcp_key);被 1 处调用(collect_missing_mcp_dependencies);外部调用 1 个(format!)。
mcp_dependency_to_server_config350–414 ↗
fn mcp_dependency_to_server_config(
dependency: &SkillToolDependency,
) -> Result<McpServerConfig, String>
作用:这个函数把技能里简单写的 MCP 依赖,转换成系统真正能保存和启动的服务器配置。
数据流:进去的是一个技能工具依赖。它看依赖是 HTTP 地址方式还是本地命令方式,取出 url 或 command,然后填充一份 McpServerConfig:默认启用、使用默认环境、不要求必装、没有额外工具限制等。出来要么是一份可写入配置文件的服务器配置,要么是错误信息。
调用关系:它由 collect_missing_mcp_dependencies 在确认某个依赖确实缺失后调用。前面的 canonical_mcp_dependency_key 负责“认出是谁”,它负责“造出能安装的配置”。
调用图:被 1 处调用(collect_missing_mcp_dependencies);外部调用 3 个(new, new, format!)。
collect_missing_mcp_dependencies416–471 ↗
fn collect_missing_mcp_dependencies(
mentioned_skills: &[SkillMetadata],
installed: &HashMap<String, McpServerConfig>,
) -> HashMap<String, McpServerConfig>
作用:这个函数负责找出“技能需要但当前还没安装”的 MCP 服务器。它是整个文件的核心对比器。
数据流:进去的是被提到的技能列表,以及当前已安装的 MCP 服务器表。它先把已安装服务器转成统一标识,再逐个查看技能依赖,只关心类型为 mcp 的工具。每个依赖会先生成统一标识;如果已经安装或本次已经见过,就跳过;否则转换成服务器配置并加入缺失表。出来的是缺少服务器名字到配置的映射;遇到写错或不支持的依赖时,它只记录警告并继续处理其他项。
调用关系:它被 maybe_prompt_and_install_mcp_dependencies 和 maybe_install_mcp_dependencies 都会调用:前者用它决定要不要问用户,后者用它在真正写配置前再确认一次。它依赖 canonical_mcp_dependency_key、canonical_mcp_server_key 和 mcp_dependency_to_server_config,把“识别、去重、生成配置”串起来。
调用图:调用 2 个内部函数(canonical_mcp_dependency_key, mcp_dependency_to_server_config);被 2 处调用(maybe_install_mcp_dependencies, maybe_prompt_and_install_mcp_dependencies);外部调用 3 个(new, new, warn!)。