mirror of
https://github.com/openai/codex.git
synced 2026-11-09 20:50:33 +00:00
Expose deferred MCP servers in resource listings (#60423)
## Why Aggregate resource listings omitted dormant MCP servers, leaving callers without their names for explicit queries. ## What changed Include a sorted `deferredServers` list in aggregate `list_mcp_resources` and `list_mcp_resource_templates` results alongside resources from ready servers. Apply the listing filters to deferred servers and omit the field when empty. Discovery leaves dormant servers unstarted; callers can query them by passing `server` explicitly. ## Testing Extend the resource discovery scenario for direct calls and Code Mode to verify deferred server names, confirm discovery leaves the server unstarted, and then list and read resources by server name. GitOrigin-RevId: f95b8beb64bb1329f4ae3245e8c375614fcb3360
This commit is contained in:
@@ -31,6 +31,7 @@ use serde_json::Value as JsonValue;
|
||||
use crate::McpConfig;
|
||||
use crate::McpServerPermissions;
|
||||
use crate::binding_clients::McpBindingClients;
|
||||
use crate::binding_clients::McpResourceListing;
|
||||
use crate::client_tool_catalog::ToolCatalogSnapshot;
|
||||
use crate::connection_manager::McpConnectionSet;
|
||||
use crate::rmcp_client::ManagedClient;
|
||||
@@ -52,7 +53,7 @@ impl McpBinding {
|
||||
pub fn empty(config: Arc<McpConfig>) -> Self {
|
||||
Self::new(
|
||||
Arc::new(McpConnectionSet::empty(config.prefix_mcp_tool_names)),
|
||||
Arc::new(McpBindingClients::new(HashMap::new())),
|
||||
Arc::new(McpBindingClients::default()),
|
||||
config,
|
||||
/*plugins_available*/ false,
|
||||
Vec::new(),
|
||||
@@ -130,7 +131,7 @@ impl McpBinding {
|
||||
pub async fn list_all_resources(
|
||||
&self,
|
||||
include_server: impl Fn(&str) -> bool,
|
||||
) -> HashMap<String, Vec<Resource>> {
|
||||
) -> McpResourceListing<Resource> {
|
||||
self.clients.list_all_resources(include_server).await
|
||||
}
|
||||
|
||||
@@ -154,7 +155,7 @@ impl McpBinding {
|
||||
pub async fn list_all_resource_templates(
|
||||
&self,
|
||||
include_server: impl Fn(&str) -> bool,
|
||||
) -> HashMap<String, Vec<ResourceTemplate>> {
|
||||
) -> McpResourceListing<ResourceTemplate> {
|
||||
self.clients
|
||||
.list_all_resource_templates(|server| {
|
||||
include_server(server) && !self.connections.skips_resource_template_listing(server)
|
||||
|
||||
@@ -17,14 +17,37 @@ use tracing::warn;
|
||||
use crate::pagination::collect_paginated;
|
||||
use crate::rmcp_client::ManagedClient;
|
||||
|
||||
/// The ready clients captured for one model step.
|
||||
/// Items listed by ready servers and names of deferred servers available to query explicitly.
|
||||
pub struct McpResourceListing<T> {
|
||||
pub by_server: HashMap<String, Vec<T>>,
|
||||
pub deferred_servers: Vec<String>,
|
||||
}
|
||||
|
||||
/// The ready clients and dormant server names captured for one model step.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct McpBindingClients {
|
||||
clients: HashMap<String, Arc<ManagedClient>>,
|
||||
deferred_servers: Vec<String>,
|
||||
}
|
||||
|
||||
impl McpBindingClients {
|
||||
pub(crate) fn new(clients: HashMap<String, Arc<ManagedClient>>) -> Self {
|
||||
Self { clients }
|
||||
pub(crate) fn new(
|
||||
clients: HashMap<String, Arc<ManagedClient>>,
|
||||
mut deferred_servers: Vec<String>,
|
||||
) -> Self {
|
||||
deferred_servers.sort_unstable();
|
||||
Self {
|
||||
clients,
|
||||
deferred_servers,
|
||||
}
|
||||
}
|
||||
|
||||
fn deferred_servers(&self, include_server: impl Fn(&str) -> bool) -> Vec<String> {
|
||||
self.deferred_servers
|
||||
.iter()
|
||||
.filter(|name| include_server(name))
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn client(&self, server: &str) -> Option<Arc<ManagedClient>> {
|
||||
@@ -77,7 +100,7 @@ impl McpBindingClients {
|
||||
pub(crate) async fn list_all_resources(
|
||||
&self,
|
||||
include_server: impl Fn(&str) -> bool,
|
||||
) -> HashMap<String, Vec<Resource>> {
|
||||
) -> McpResourceListing<Resource> {
|
||||
let mut join_set = JoinSet::new();
|
||||
for (server_name, managed) in self
|
||||
.clients
|
||||
@@ -99,13 +122,16 @@ impl McpBindingClients {
|
||||
(server_name, resources)
|
||||
});
|
||||
}
|
||||
collect_resource_results(&mut join_set, "resources").await
|
||||
McpResourceListing {
|
||||
by_server: collect_resource_results(&mut join_set, "resources").await,
|
||||
deferred_servers: self.deferred_servers(include_server),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn list_all_resource_templates(
|
||||
&self,
|
||||
include_server: impl Fn(&str) -> bool,
|
||||
) -> HashMap<String, Vec<ResourceTemplate>> {
|
||||
) -> McpResourceListing<ResourceTemplate> {
|
||||
let mut join_set = JoinSet::new();
|
||||
for (server_name, managed) in self
|
||||
.clients
|
||||
@@ -127,7 +153,10 @@ impl McpBindingClients {
|
||||
(server_name, templates)
|
||||
});
|
||||
}
|
||||
collect_resource_results(&mut join_set, "resource templates").await
|
||||
McpResourceListing {
|
||||
by_server: collect_resource_results(&mut join_set, "resource templates").await,
|
||||
deferred_servers: self.deferred_servers(include_server),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,10 +107,10 @@ async fn test_step(
|
||||
client_supports_network_policy_meta_capability: false,
|
||||
codex_apps_tools_cache_context: None,
|
||||
});
|
||||
let clients = Arc::new(McpBindingClients::new(HashMap::from([(
|
||||
SERVER_NAME.to_string(),
|
||||
Arc::clone(&managed_client),
|
||||
)])));
|
||||
let clients = Arc::new(McpBindingClients::new(
|
||||
HashMap::from([(SERVER_NAME.to_string(), Arc::clone(&managed_client))]),
|
||||
Vec::new(),
|
||||
));
|
||||
let connections = Arc::new(McpConnectionSet::empty(/*prefix_mcp_tool_names*/ true));
|
||||
let mut config = crate::mcp::tests::test_mcp_config(std::env::temp_dir());
|
||||
config.host_cwd = config.host_cwd.join(label);
|
||||
|
||||
@@ -547,11 +547,21 @@ impl McpConnectionSet {
|
||||
call,
|
||||
);
|
||||
}
|
||||
let deferred_servers = self
|
||||
.servers
|
||||
.iter()
|
||||
.filter(|(_, view)| {
|
||||
view.connection.startup_is_dormant()
|
||||
&& !view.connection.client.cancel_token.is_cancelled()
|
||||
})
|
||||
.map(|(name, _)| name.clone())
|
||||
.collect();
|
||||
let clients = Arc::new(McpBindingClients::new(
|
||||
clients
|
||||
.into_iter()
|
||||
.map(|(server_name, (client, _))| (server_name, client))
|
||||
.collect(),
|
||||
deferred_servers,
|
||||
));
|
||||
McpBinding::new(
|
||||
Arc::clone(self),
|
||||
|
||||
@@ -2341,7 +2341,10 @@ async fn resource_template_listings_skip_only_hosted_apps() -> anyhow::Result<()
|
||||
expected
|
||||
);
|
||||
let all = binding.list_all_resource_templates(|_| true).await;
|
||||
assert_eq!(all.get(name), (!expected.is_empty()).then_some(&expected));
|
||||
assert_eq!(
|
||||
all.by_server.get(name),
|
||||
(!expected.is_empty()).then_some(&expected)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -6139,11 +6142,11 @@ async fn reconciliation_reuses_connection_without_relisting_regular_tools() -> a
|
||||
.expect("refresh must use the reconciled timeout");
|
||||
assert_eq!(failed.tools(), old_step.tools());
|
||||
assert_eq!(
|
||||
failed.list_all_resources(|_| true).await,
|
||||
failed.list_all_resources(|_| true).await.by_server,
|
||||
HashMap::from([("docs".to_string(), vec![])])
|
||||
);
|
||||
assert_eq!(
|
||||
failed.list_all_resource_templates(|_| true).await,
|
||||
failed.list_all_resource_templates(|_| true).await.by_server,
|
||||
HashMap::from([("docs".to_string(), vec![])])
|
||||
);
|
||||
let retry_started = tokio::time::Instant::now();
|
||||
|
||||
@@ -514,10 +514,10 @@ async fn shared_startup_survives_first_chat_cancellation_and_rejects_released_bi
|
||||
/*selected_plugin_server*/ false,
|
||||
)
|
||||
.unwrap();
|
||||
let bindings = McpBindingClients::new(HashMap::from([(
|
||||
"test_server".to_string(),
|
||||
Arc::new(client.clone()),
|
||||
)]));
|
||||
let bindings = McpBindingClients::new(
|
||||
HashMap::from([("test_server".to_string(), Arc::new(client.clone()))]),
|
||||
Vec::new(),
|
||||
);
|
||||
server.block_listing.store(true, Ordering::Release);
|
||||
let waiting = async {
|
||||
tokio::join!(
|
||||
|
||||
@@ -4,6 +4,7 @@ pub use binding::PreparedMcpCall;
|
||||
pub use binding::SEARCH_CONNECTOR;
|
||||
pub use binding::SearchDiscovery;
|
||||
pub use binding::is_model_search_tool;
|
||||
pub use binding_clients::McpResourceListing;
|
||||
pub use client_capabilities::client_mcp_extensions;
|
||||
pub use client_tool_catalog::CodexAppsToolSnapshot;
|
||||
pub use codex_rmcp_client::McpProtocolMode;
|
||||
|
||||
Reference in New Issue
Block a user