mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
Add turn-start cloud plugin discovery to the MCP extension (#46572)
## What changed - Support an optional cloud provider alongside executor plugins, refreshing its thread-scoped catalog at each regular turn when plugins are enabled. - Use cloud connector metadata for app attribution without starting cloud-declared MCP servers. - Scope cached catalogs to authentication and Apps availability. Retain the previous catalog on discovery failure within the same scope, clear stale catalogs on auth changes, and reject results completed under outdated credentials. - Add a cancellable regular-task startup phase that reconciles MCP before discovery and reapplies contributor state afterward without invalidating resource caches. ## Testing Add regression coverage for per-turn refresh, empty catalogs, discovery failures, plugin disabling, auth changes during discovery, MCP reconciliation and cancellation, and cloud app attribution alongside executor MCP tools. GitOrigin-RevId: 8469eb109ea316e21cbc4122c52ad8000caeef03
This commit is contained in:
committed by
copyberry
parent
d84ebff592
commit
394dadb93d
@@ -119,6 +119,7 @@ struct PublishedMcpRuntime {
|
||||
config: Option<Arc<McpConfig>>,
|
||||
auth: Option<CodexAuth>,
|
||||
auth_token: Option<String>,
|
||||
auth_generation: Arc<()>,
|
||||
plugins_available: bool,
|
||||
ready_selected_capability_roots: Vec<SelectedCapabilityRoot>,
|
||||
environment_selections: Arc<[TurnEnvironmentSelection]>,
|
||||
@@ -145,6 +146,23 @@ fn ensure_host_owned_apps_registration(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl PublishedMcpRuntime {
|
||||
// Shared by dirty detection and cache-key publication so their auth rules agree.
|
||||
fn auth_matches(&self, auth: Option<&CodexAuth>) -> bool {
|
||||
match (self.auth.as_ref(), auth) {
|
||||
(Some(previous), Some(latest)) => {
|
||||
previous == latest
|
||||
&& previous.get_account_id() == latest.get_account_id()
|
||||
&& previous.get_chatgpt_user_id() == latest.get_chatgpt_user_id()
|
||||
&& previous.is_fedramp_account() == latest.is_fedramp_account()
|
||||
&& self.auth_token == latest.get_token().ok()
|
||||
}
|
||||
(None, None) => true,
|
||||
(Some(_), None) | (None, Some(_)) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CachedMcpBinding {
|
||||
catalog_revisions: HashMap<String, BindingCatalogRevision>,
|
||||
// Reuse a frozen binding while a model step or caller still needs it.
|
||||
@@ -211,6 +229,7 @@ impl McpRuntime {
|
||||
config: None,
|
||||
auth: None,
|
||||
auth_token: None,
|
||||
auth_generation: Arc::new(()),
|
||||
plugins_available: false,
|
||||
ready_selected_capability_roots: Vec::new(),
|
||||
environment_selections: Arc::default(),
|
||||
@@ -314,6 +333,12 @@ impl McpRuntime {
|
||||
let config = Arc::clone(&input.config);
|
||||
let auth = input.auth.clone();
|
||||
let auth_token = auth.as_ref().and_then(|auth| auth.get_token().ok());
|
||||
let current = self.current.load_full();
|
||||
let auth_generation = if current.auth_matches(auth.as_ref()) {
|
||||
Arc::clone(¤t.auth_generation)
|
||||
} else {
|
||||
Arc::new(())
|
||||
};
|
||||
let plugins_available = input.plugins_available;
|
||||
let ready_selected_capability_roots = input.ready_selected_capability_roots.clone();
|
||||
let environment_selections = Arc::clone(&input.runtime_context.environment_selections);
|
||||
@@ -345,6 +370,7 @@ impl McpRuntime {
|
||||
config: Some(config),
|
||||
auth,
|
||||
auth_token,
|
||||
auth_generation,
|
||||
plugins_available,
|
||||
ready_selected_capability_roots,
|
||||
environment_selections,
|
||||
@@ -463,17 +489,18 @@ impl McpRuntime {
|
||||
|
||||
/// Returns whether the published snapshot still belongs to the current credentials.
|
||||
pub fn current_auth_matches(&self, auth: Option<&CodexAuth>) -> bool {
|
||||
self.current.load().auth_matches(auth)
|
||||
}
|
||||
|
||||
pub(crate) fn auth_cache_key_for_server(
|
||||
&self,
|
||||
server: &str,
|
||||
) -> crate::McpResourceClientAuthKey {
|
||||
let current = self.current.load();
|
||||
match (current.auth.as_ref(), auth) {
|
||||
(Some(previous), Some(latest)) => {
|
||||
previous == latest
|
||||
&& previous.get_account_id() == latest.get_account_id()
|
||||
&& previous.get_chatgpt_user_id() == latest.get_chatgpt_user_id()
|
||||
&& previous.is_fedramp_account() == latest.is_fedramp_account()
|
||||
&& current.auth_token == latest.get_token().ok()
|
||||
}
|
||||
(None, None) => true,
|
||||
(Some(_), None) | (None, Some(_)) => false,
|
||||
crate::McpResourceClientAuthKey {
|
||||
generation: Arc::clone(¤t.auth_generation),
|
||||
server: server.to_string(),
|
||||
available: current.connections.contains_server(server),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -982,6 +1009,7 @@ mod tests {
|
||||
config: Some(Arc::new(config)),
|
||||
auth: None,
|
||||
auth_token: None,
|
||||
auth_generation: Arc::new(()),
|
||||
plugins_available: false,
|
||||
ready_selected_capability_roots: Vec::new(),
|
||||
environment_selections: Arc::default(),
|
||||
@@ -1055,6 +1083,7 @@ mod tests {
|
||||
))),
|
||||
auth: None,
|
||||
auth_token: None,
|
||||
auth_generation: Arc::new(()),
|
||||
plugins_available: false,
|
||||
ready_selected_capability_roots: Vec::new(),
|
||||
environment_selections: Arc::default(),
|
||||
|
||||
Reference in New Issue
Block a user