Preserve refreshed Apps tools across MCP runtime updates (#35028)

## Why

After a remote plugin install refreshes the Apps tool catalog, a later MCP
runtime publication must not restore the catalog from the previous connection.

## What changed

- Rebuild the MCP runtime with fresh connections when hard-refreshing Apps
  tools, using the latest desired runtime configuration.
- Refresh the Apps catalog on the newly published connection so subsequent
  runtime updates retain it.

## Testing

Extend the remote plugin install test to verify that both newly available and
missing Apps tools remain consistent after an unrelated runtime configuration
refresh.

GitOrigin-RevId: 5b675d53d56379ab67fab09512fc2ad0ffcb8535
This commit is contained in:
jif
2026-07-23 23:10:00 +00:00
committed by copyberry
parent 1ee8f49175
commit 091e4a5d7c
4 changed files with 70 additions and 7 deletions
+39 -5
View File
@@ -3,6 +3,7 @@ use codex_exec_server::ExecutorCapabilityDiscoveryCache;
use codex_exec_server::ExecutorCapabilityDiscoverySnapshot;
use codex_exec_server::MAX_SELECTED_CAPABILITY_ROOTS;
use codex_exec_server::ResolvedSelectedCapabilityRoot;
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
use codex_mcp::ElicitationReviewRequest;
use codex_mcp::ElicitationReviewer;
use codex_mcp::ElicitationReviewerHandle;
@@ -202,7 +203,7 @@ impl Session {
}
}
/// Refreshes the future Apps catalog without making an exact step a refresh owner.
/// Reconnects the runtime so refreshed Apps tools belong to their new exact client.
pub(crate) async fn hard_refresh_latest_codex_apps_tools(
self: &Arc<Self>,
) -> anyhow::Result<Vec<codex_mcp::ToolInfo>> {
@@ -212,10 +213,43 @@ impl Session {
.acquire()
.await
.map_err(|_| anyhow::anyhow!("MCP runtime refresh semaphore closed"))?;
self.services
.mcp_runtime
.latest_hard_refresh_codex_apps_tools_cache()
.await
let desired = self.latest_mcp_desired_state().await;
let selected_capability_roots = self
.resolve_selected_capability_roots_for_step(&desired.environments)
.await;
let ready_selected_capability_roots =
Self::ready_selected_capability_roots(&selected_capability_roots);
let executor_capability_discovery = self
.executor_capability_discovery_for_step(
&desired.config,
&ready_selected_capability_roots,
)
.await;
let mcp_projection = self
.services
.mcp_manager
.runtime_config_for_step(
&desired.config,
&self.services.mcp_thread_init,
&self.services.thread_extension_data,
&desired.originator,
&ready_selected_capability_roots,
executor_capability_discovery.as_deref(),
)
.await;
let input = self
.build_mcp_runtime_input(
&desired,
mcp_projection,
&ready_selected_capability_roots,
Some(self.mcp_elicitation_reviewer()),
)
.await;
anyhow::ensure!(
input.mcp_servers.contains_key(CODEX_APPS_MCP_SERVER_NAME),
"unknown MCP server '{CODEX_APPS_MCP_SERVER_NAME}'"
);
self.services.mcp_runtime.replace_fresh(input).await
}
pub(super) fn mark_mcp_runtime_dirty(&self) {
+1 -1
View File
@@ -100,7 +100,7 @@ impl Session {
self.services.mcp_runtime.replace(input).await;
}
async fn build_mcp_runtime_input(
pub(super) async fn build_mcp_runtime_input(
&self,
desired: &McpDesiredState,
mcp_projection: McpRuntimeProjection,