mirror of
https://github.com/openai/codex.git
synced 2026-11-14 21:40:39 +00:00
Preserve MCP sharing discovery when deferring cached stdio startup (#60037)
## Why Subagents reusing cached MCP tool definitions were forced to use the legacy protocol, preventing connection sharing from being negotiated on first use. ## What changed Keep stdio protocol discovery enabled during deferred startup while allowing dormant subagents to advertise cached legacy tools. Track modern discovery separately from catalog updates so discovery clients reject older legacy catalogs while legacy clients can still reuse them. Keep modern tools out of the legacy catalog when joining pooled connections. ## Testing Add coverage for deferred subagents negotiating sharing on first use and reusing the same server process, while a legacy sibling retains cached definitions without starting its server. Extend cache coverage to ensure older legacy fetches cannot undo modern discovery. GitOrigin-RevId: 5f8f4deb22cfb81a08c05ff3fcd7ba5c9199551c
This commit is contained in:
@@ -59,7 +59,8 @@ struct ToolCatalogCacheEntry {
|
||||
struct ToolCatalogCacheState {
|
||||
snapshot: Option<ToolCatalogSnapshot>,
|
||||
optional_startup_deadline: Option<OptionalStartupDeadline>,
|
||||
last_accepted_generation: u64,
|
||||
last_catalog_generation: u64,
|
||||
last_modern_discovery_generation: u64,
|
||||
disabled_by_server: bool,
|
||||
}
|
||||
|
||||
@@ -78,6 +79,7 @@ struct ToolCatalogSnapshot {
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct McpToolCatalogCacheContext {
|
||||
pub(crate) track_tool_list_changes: bool,
|
||||
pub(crate) before_stdio_discovery: bool,
|
||||
entry: Arc<ToolCatalogCacheEntry>,
|
||||
}
|
||||
|
||||
@@ -110,6 +112,7 @@ impl McpToolCatalogCache {
|
||||
Some(McpToolCatalogCacheContext {
|
||||
entry,
|
||||
track_tool_list_changes: false,
|
||||
before_stdio_discovery: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -124,7 +127,16 @@ impl Default for ToolCatalogCacheEntry {
|
||||
}
|
||||
|
||||
impl McpToolCatalogCacheContext {
|
||||
fn accepts_snapshot(&self, snapshot: &ToolCatalogSnapshot) -> bool {
|
||||
fn accepts_snapshot(
|
||||
&self,
|
||||
state: &ToolCatalogCacheState,
|
||||
snapshot: &ToolCatalogSnapshot,
|
||||
) -> bool {
|
||||
if self.before_stdio_discovery
|
||||
&& state.last_modern_discovery_generation > state.last_catalog_generation
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Enabling refresh must not reuse a catalog published without notification tracking.
|
||||
snapshot
|
||||
.tool_list_revision
|
||||
@@ -147,11 +159,11 @@ impl McpToolCatalogCacheContext {
|
||||
let state = lock_unpoisoned(&self.entry.state);
|
||||
let snapshot = state.snapshot.as_ref()?;
|
||||
(!state.disabled_by_server
|
||||
&& self.accepts_snapshot(snapshot)
|
||||
&& self.accepts_snapshot(&state, snapshot)
|
||||
&& !snapshot.tools.is_empty()
|
||||
&& snapshot.published_at.elapsed() <= TOOL_CATALOG_CACHE_TTL
|
||||
&& accepts_tools(&snapshot.tools))
|
||||
.then_some(state.last_accepted_generation)
|
||||
.then_some(state.last_catalog_generation)
|
||||
}
|
||||
|
||||
pub(crate) fn optional_startup_deadline(
|
||||
@@ -162,7 +174,7 @@ impl McpToolCatalogCacheContext {
|
||||
let mut state = lock_unpoisoned(&self.entry.state);
|
||||
if state.disabled_by_server
|
||||
|| state.snapshot.as_ref().is_some_and(|snapshot| {
|
||||
self.accepts_snapshot(snapshot)
|
||||
self.accepts_snapshot(&state, snapshot)
|
||||
&& snapshot.published_at.elapsed() <= TOOL_CATALOG_CACHE_TTL
|
||||
})
|
||||
{
|
||||
@@ -207,7 +219,7 @@ impl McpToolCatalogCacheContext {
|
||||
|| state
|
||||
.snapshot
|
||||
.as_ref()
|
||||
.is_some_and(|snapshot| !self.accepts_snapshot(snapshot))
|
||||
.is_some_and(|snapshot| !self.accepts_snapshot(&state, snapshot))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
@@ -236,10 +248,17 @@ impl McpToolCatalogCacheContext {
|
||||
state.snapshot = None;
|
||||
}
|
||||
|
||||
/// An observed protocol change invalidates a legacy catalog, including any older fetch in flight.
|
||||
pub(crate) fn invalidate_if_newest(&self, ticket: &McpToolCatalogFetchTicket) {
|
||||
if let Some(mut state) = self.accept_generation(ticket.generation) {
|
||||
state.snapshot = None;
|
||||
/// A modern handshake rules out older legacy catalogs for discovery. Clients forced to use
|
||||
/// the legacy protocol can still use those catalogs, including legacy fetches in flight.
|
||||
pub(crate) fn record_modern_stdio_discovery(&self, ticket: &McpToolCatalogFetchTicket) {
|
||||
let mut state = lock_unpoisoned(&self.entry.state);
|
||||
if ticket.generation
|
||||
> state
|
||||
.last_catalog_generation
|
||||
.max(state.last_modern_discovery_generation)
|
||||
{
|
||||
state.last_modern_discovery_generation = ticket.generation;
|
||||
state.optional_startup_deadline = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,9 +268,12 @@ impl McpToolCatalogCacheContext {
|
||||
tools: &[ToolInfo],
|
||||
meta: Option<Arc<serde_json::Value>>,
|
||||
) {
|
||||
let Some(mut state) = self.accept_generation(ticket.generation) else {
|
||||
let mut state = lock_unpoisoned(&self.entry.state);
|
||||
if state.disabled_by_server || ticket.generation <= state.last_catalog_generation {
|
||||
return;
|
||||
};
|
||||
}
|
||||
state.last_catalog_generation = ticket.generation;
|
||||
state.optional_startup_deadline = None;
|
||||
let mut tools = tools.to_vec();
|
||||
for tool in &mut tools {
|
||||
// Tool annotations affect approval and parallelism decisions, so only the live
|
||||
@@ -265,16 +287,6 @@ impl McpToolCatalogCacheContext {
|
||||
tool_list_revision: ticket.tool_list_revision,
|
||||
});
|
||||
}
|
||||
|
||||
fn accept_generation(&self, generation: u64) -> Option<MutexGuard<'_, ToolCatalogCacheState>> {
|
||||
let mut state = lock_unpoisoned(&self.entry.state);
|
||||
if state.disabled_by_server || generation <= state.last_accepted_generation {
|
||||
return None;
|
||||
}
|
||||
state.last_accepted_generation = generation;
|
||||
state.optional_startup_deadline = None;
|
||||
Some(state)
|
||||
}
|
||||
}
|
||||
|
||||
fn lock_unpoisoned<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
|
||||
|
||||
Reference in New Issue
Block a user