mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Preserve session MCP config on refresh (#21055)
# Overview MCP refreshes were rebuilding active threads from fresh disk-backed config only, which dropped thread-start session overlays such as app-injected MCP servers. This keeps refreshes current with disk config while preserving the thread-local config that only the active thread knows about. # Changes - Rebuild refreshed config per active thread using that thread's current `cwd`, rather than fanning out one app-server config to every thread. - Preserve each thread's `SessionFlags` layer while replacing reloadable config layers with freshly loaded config, then derive the MCP refresh payload from the rebuilt result. - Move MCP refresh orchestration into app-server so manual refreshes fail loudly while background refreshes remain best-effort, and route plugin-triggered refreshes through the same per-thread reload path. - Add regression coverage for session overlays, fresh project config, plugin-derived MCP config, current requirements, and strict vs best-effort refresh behavior. # Verification - Passed focused Rust coverage for the thread-config rebuild behavior and deferred MCP refresh flow, plus `cargo test -p codex-app-server --lib`. - Verified end to end in the Codex dev app against the locally built CLI: registered an MCP via thread config, verified that it could be used successfully before refresh, manually triggered MCP refresh, and verified that it continued to be available afterward.
This commit is contained in:
@@ -77,8 +77,9 @@ impl McpRequestProcessor {
|
||||
&self,
|
||||
_params: Option<()>,
|
||||
) -> Result<McpServerRefreshResponse, JSONRPCErrorError> {
|
||||
let config = self.load_latest_config(/*fallback_cwd*/ None).await?;
|
||||
Self::queue_mcp_server_refresh_for_config(&self.thread_manager, &config).await?;
|
||||
crate::mcp_refresh::queue_strict_refresh(&self.thread_manager, &self.config_manager)
|
||||
.await
|
||||
.map_err(|err| internal_error(format!("failed to refresh MCP servers: {err}")))?;
|
||||
Ok(McpServerRefreshResponse {})
|
||||
}
|
||||
|
||||
@@ -108,44 +109,6 @@ impl McpRequestProcessor {
|
||||
Ok((thread_id, thread))
|
||||
}
|
||||
|
||||
pub(super) async fn queue_mcp_server_refresh_for_config(
|
||||
thread_manager: &Arc<ThreadManager>,
|
||||
config: &Config,
|
||||
) -> Result<(), JSONRPCErrorError> {
|
||||
let configured_servers = thread_manager
|
||||
.mcp_manager()
|
||||
.configured_servers(config)
|
||||
.await;
|
||||
let mcp_servers = match serde_json::to_value(configured_servers) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
return Err(internal_error(format!(
|
||||
"failed to serialize MCP servers: {err}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let mcp_oauth_credentials_store_mode =
|
||||
match serde_json::to_value(config.mcp_oauth_credentials_store_mode) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
return Err(internal_error(format!(
|
||||
"failed to serialize MCP OAuth credentials store mode: {err}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let refresh_config = McpServerRefreshConfig {
|
||||
mcp_servers,
|
||||
mcp_oauth_credentials_store_mode,
|
||||
};
|
||||
|
||||
// Refresh requests are queued per thread; each thread rebuilds MCP connections on its next
|
||||
// active turn to avoid work for threads that never resume.
|
||||
thread_manager.refresh_mcp_servers(refresh_config).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn mcp_server_oauth_login_response(
|
||||
&self,
|
||||
params: McpServerOauthLoginParams,
|
||||
|
||||
Reference in New Issue
Block a user