mirror of
https://github.com/openai/codex.git
synced 2026-09-13 11:47:17 +00:00
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:
@@ -141,13 +141,24 @@ impl McpRuntime {
|
||||
/// Reconciles configured servers and publishes their immutable runtime snapshot.
|
||||
pub async fn replace(&self, input: McpRuntimeInput) {
|
||||
let current = self.current.load_full();
|
||||
self.publish(input, Some(current.connections.as_ref()))
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Starts fresh connections and returns their complete, refreshed Apps catalog.
|
||||
pub async fn replace_fresh(&self, input: McpRuntimeInput) -> anyhow::Result<Vec<ToolInfo>> {
|
||||
self.publish(input, /*previous*/ None).await;
|
||||
self.latest_hard_refresh_codex_apps_tools_cache().await
|
||||
}
|
||||
|
||||
async fn publish(&self, input: McpRuntimeInput, previous: Option<&McpConnectionSet>) {
|
||||
let (publish, publication_gate) = McpPublicationGate::pending();
|
||||
let config = Arc::clone(&input.config);
|
||||
let plugins_available = input.plugins_available;
|
||||
let ready_selected_capability_roots = input.ready_selected_capability_roots.clone();
|
||||
let connections = Arc::new(
|
||||
McpConnectionSet::new(
|
||||
Some(current.connections.as_ref()),
|
||||
previous,
|
||||
publication_gate,
|
||||
input,
|
||||
self.elicitation_router.clone(),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -592,6 +592,11 @@ async fn run_remote_plugin_install_refresh_case(refreshed_tools: RefreshedAppsTo
|
||||
ev_assistant_message("msg-1", "done"),
|
||||
ev_completed("resp-2"),
|
||||
]),
|
||||
sse(vec![
|
||||
ev_response_created("resp-3"),
|
||||
ev_assistant_message("msg-2", "catalog still current"),
|
||||
ev_completed("resp-3"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
@@ -644,6 +649,19 @@ async fn run_remote_plugin_install_refresh_case(refreshed_tools: RefreshedAppsTo
|
||||
.any(|name| name == REQUEST_PLUGIN_INSTALL_TOOL_NAME),
|
||||
"the refreshed installed-plugin cache should filter the cached recommendation"
|
||||
);
|
||||
drop(requests);
|
||||
test.codex.refresh_runtime_config(test.config.clone()).await;
|
||||
test.submit_turn("check whether Calendar is still installed")
|
||||
.await?;
|
||||
let requests = mock.requests();
|
||||
assert_eq!(requests.len(), 3);
|
||||
assert_eq!(
|
||||
requests[2]
|
||||
.tool_by_name(CALENDAR_NAMESPACE, CALENDAR_CREATE_EVENT_TOOL)
|
||||
.is_some(),
|
||||
completed,
|
||||
"an unrelated runtime publication must retain the refreshed Apps catalog"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user