mirror of
https://github.com/openai/codex.git
synced 2026-11-07 20:30:33 +00:00
Start dormant MCP servers for hook calls (#55263)
## Why MCP tool hooks need to invoke model-hidden tools even when a subagent's cached server has not started. Their timeout must also cover server startup. ## What changed - Let cached servers with allowed model-hidden tools remain dormant until called. - Wait for the selected server when executing an MCP hook, leaving unrelated cached servers dormant, and apply the hook timeout to the entire operation. - Capture tool namespace attribution after startup and retain it only while catalog revisions remain unchanged. - Validate the server, environment, and tool filter before starting a server or capturing attribution. ## Testing Extend integration coverage for cached subagent hooks under both startup readiness modes, startup timeouts without context injection, and namespace attribution for hook and token-budget context while keeping tools hidden from the model. Verify that a direct call starts a cached server whose tools are hidden from model exposure. GitOrigin-RevId: 7a54b69aa2515722aeb925e03e1667bf63aa67ca
This commit is contained in:
@@ -657,9 +657,9 @@ impl McpRuntime {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Calls a tool for the host, using a cached namespace only if its catalogs stay unchanged.
|
||||
/// Missing or changed catalogs leave the namespace unknown. Attribution never starts
|
||||
/// servers or refreshes their catalogs.
|
||||
/// Calls a tool for the host, retaining attribution while its catalogs stay unchanged.
|
||||
/// Waiting calls capture a binding after the selected server starts; non-waiting calls
|
||||
/// use the cached binding. Missing or changed catalogs leave the namespace unknown.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn latest_call_tool(
|
||||
&self,
|
||||
@@ -672,12 +672,36 @@ impl McpRuntime {
|
||||
wait_for_server: bool,
|
||||
) -> anyhow::Result<McpToolCallResult> {
|
||||
let current = self.current.load_full();
|
||||
let cached = current
|
||||
.cached_binding
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.as_ref()
|
||||
.and_then(|cached| Some((cached.catalog_revisions.clone(), cached.binding.upgrade()?)));
|
||||
current
|
||||
.connections
|
||||
.validate_tool_call(server, tool, environment_id)?;
|
||||
let attribution = if wait_for_server
|
||||
&& current.connections.wait_for_server_startup(server).await
|
||||
&& let Some(revisions) = current
|
||||
.connections
|
||||
.stable_catalog_revisions(
|
||||
/*required_servers*/ &[],
|
||||
/*required_plugins*/ &HashSet::new(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Self::binding_from_published_runtime(
|
||||
Arc::clone(¤t),
|
||||
/*required_servers*/ &[],
|
||||
/*required_plugins*/ &HashSet::new(),
|
||||
)
|
||||
.await
|
||||
.map(|binding| (revisions, binding))
|
||||
} else {
|
||||
current
|
||||
.cached_binding
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.as_ref()
|
||||
.and_then(|cached| {
|
||||
Some((cached.catalog_revisions.clone(), cached.binding.upgrade()?))
|
||||
})
|
||||
};
|
||||
let result = current
|
||||
.connections
|
||||
.call_tool(
|
||||
@@ -694,7 +718,7 @@ impl McpRuntime {
|
||||
// Check after dispatch so a refresh before or during the call invalidates attribution.
|
||||
// Revisions include client identity and increase on catalog changes. Reading them
|
||||
// does not start servers or refresh catalogs; all catalogs matter for name collisions.
|
||||
let namespace = if let Some((revisions, binding)) = cached
|
||||
let namespace = if let Some((revisions, binding)) = attribution
|
||||
&& current
|
||||
.connections
|
||||
.stable_catalog_revisions(
|
||||
|
||||
Reference in New Issue
Block a user