Remove MCP plugin provenance

This commit is contained in:
Meta Vens 2026-05-13 15:10:04 +01:00
commit d1729ff4d4
14 changed files with 22 additions and 271 deletions

View file

@ -844,6 +844,14 @@ exports `with_codex_apps_mcp`, keeps a ChatGPT-auth-keyed app tool cache, or
normalizes app callable names/namespaces/titles. MCP remains JSON-RPC at the
edge; only user-declared MCP servers are runtime MCP authority.
The plugin provenance reflex has been removed from MCP as well. `McpConfig`
does not carry plugin capability summaries, `McpConnectionManager::new` no
longer accepts `ToolPluginProvenance`, `codex-mcp` no longer depends on
`codex-plugin` or `codex-utils-plugins`, and MCP tool descriptions are not
decorated with Codex plugin product names. If Epiphany later wants MCP
attribution, it needs an Epiphany-owned typed adapter surface, not this old
product metadata graft.
The Phase 6 freshness slice is landed. It exposes read-only
`thread/epiphany/freshness` from live retrieval summaries plus graph
frontier/churn state and, for loaded threads, watcher-backed invalidation

Binary file not shown.

View file

@ -294,6 +294,10 @@ current_status:
`with_codex_apps_mcp`, keeps a ChatGPT-auth-keyed Codex Apps tool cache,
exposes `hard_refresh_codex_apps_tools_cache`, filters non-app MCP tools,
normalizes app callable names/namespaces/titles, or masks app file params at
app-specific return boundaries. MCP remains JSON-RPC at the edge; user
declared MCP servers are the only runtime MCP authority.
app-specific return boundaries. The live MCP manager/config shape also no
longer carries plugin provenance: `ToolPluginProvenance`,
`plugin_capability_summaries`, and the `codex-plugin` / `codex-utils-plugins`
dependencies were removed from `codex-mcp`, and MCP tool descriptions are no
longer decorated from plugin product metadata. MCP remains JSON-RPC at the
edge; user-declared MCP servers are the only runtime MCP authority.
next_action: Continue starving coordinator/runtime execution and operator surfaces off app-server JSON-RPC and out of `codex_message_processor.rs`; keep the vestigial Codex organ narrowed to subscription auth/model transport.

2
vendor/codex/codex-rs/Cargo.lock generated vendored
View file

@ -2884,11 +2884,9 @@ dependencies = [
"codex-exec-server",
"codex-login",
"codex-otel",
"codex-plugin",
"codex-protocol",
"codex-rmcp-client",
"codex-utils-absolute-path",
"codex-utils-plugins",
"futures",
"pretty_assertions",
"regex-lite",

View file

@ -19,10 +19,8 @@ codex-config = { workspace = true }
codex-exec-server = { workspace = true }
codex-login = { workspace = true }
codex-otel = { workspace = true }
codex-plugin = { workspace = true }
codex-protocol = { workspace = true }
codex-rmcp-client = { workspace = true }
codex-utils-plugins = { workspace = true }
futures = { workspace = true }
regex-lite = { workspace = true }
rmcp = { workspace = true, default-features = false, features = ["base64", "macros", "schemars", "server"] }

View file

@ -11,7 +11,6 @@ pub use mcp::McpOAuthScopesSource;
pub use mcp::McpServerStatusSnapshot;
pub use mcp::McpSnapshotDetail;
pub use mcp::ResolvedMcpOAuthScopes;
pub use mcp::ToolPluginProvenance;
pub use mcp::canonical_mcp_server_key;
pub use mcp::collect_mcp_server_status_snapshot;
pub use mcp::collect_mcp_server_status_snapshot_with_detail;
@ -32,7 +31,6 @@ pub use mcp::read_mcp_resource;
pub use mcp::resolve_oauth_scopes;
pub use mcp::should_retry_without_scopes;
pub use mcp::split_qualified_tool_name;
pub use mcp::tool_plugin_provenance;
pub use mcp_connection_manager::DEFAULT_STARTUP_TIMEOUT;
pub use mcp_connection_manager::MCP_SANDBOX_STATE_META_CAPABILITY;
pub use mcp_connection_manager::McpConnectionManager;

View file

@ -21,7 +21,6 @@ use codex_config::Constrained;
use codex_config::McpServerConfig;
use codex_config::types::OAuthCredentialsStoreMode;
use codex_login::CodexAuth;
use codex_plugin::PluginCapabilitySummary;
use codex_protocol::mcp::Resource;
use codex_protocol::mcp::ResourceTemplate;
use codex_protocol::mcp::Tool;
@ -120,73 +119,8 @@ pub struct McpConfig {
pub codex_linux_sandbox_exe: Option<PathBuf>,
/// Whether to use legacy Landlock behavior in the MCP sandbox state.
pub use_legacy_landlock: bool,
/// Whether the app MCP integration is enabled by config.
///
/// ChatGPT auth is checked separately at runtime before the built-in apps
/// MCP server is added.
pub apps_enabled: bool,
/// User-configured and plugin-provided MCP servers keyed by server name.
pub configured_mcp_servers: HashMap<String, McpServerConfig>,
/// Plugin metadata used to attribute MCP tools/connectors to plugin display names.
pub plugin_capability_summaries: Vec<PluginCapabilitySummary>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ToolPluginProvenance {
plugin_display_names_by_connector_id: HashMap<String, Vec<String>>,
plugin_display_names_by_mcp_server_name: HashMap<String, Vec<String>>,
}
impl ToolPluginProvenance {
pub fn plugin_display_names_for_connector_id(&self, connector_id: &str) -> &[String] {
self.plugin_display_names_by_connector_id
.get(connector_id)
.map(Vec::as_slice)
.unwrap_or(&[])
}
pub fn plugin_display_names_for_mcp_server_name(&self, server_name: &str) -> &[String] {
self.plugin_display_names_by_mcp_server_name
.get(server_name)
.map(Vec::as_slice)
.unwrap_or(&[])
}
fn from_capability_summaries(capability_summaries: &[PluginCapabilitySummary]) -> Self {
let mut tool_plugin_provenance = Self::default();
for plugin in capability_summaries {
for connector_id in &plugin.app_connector_ids {
tool_plugin_provenance
.plugin_display_names_by_connector_id
.entry(connector_id.0.clone())
.or_default()
.push(plugin.display_name.clone());
}
for server_name in &plugin.mcp_server_names {
tool_plugin_provenance
.plugin_display_names_by_mcp_server_name
.entry(server_name.clone())
.or_default()
.push(plugin.display_name.clone());
}
}
for plugin_names in tool_plugin_provenance
.plugin_display_names_by_connector_id
.values_mut()
.chain(
tool_plugin_provenance
.plugin_display_names_by_mcp_server_name
.values_mut(),
)
{
plugin_names.sort_unstable();
plugin_names.dedup();
}
tool_plugin_provenance
}
}
pub fn configured_mcp_servers(config: &McpConfig) -> HashMap<String, McpServerConfig> {
@ -200,10 +134,6 @@ pub fn effective_mcp_servers(
configured_mcp_servers(config)
}
pub fn tool_plugin_provenance(config: &McpConfig) -> ToolPluginProvenance {
ToolPluginProvenance::from_capability_summaries(&config.plugin_capability_summaries)
}
pub async fn read_mcp_resource(
config: &McpConfig,
auth: Option<&CodexAuth>,
@ -226,7 +156,6 @@ pub async fn read_mcp_resource(
tx_event,
SandboxPolicy::new_read_only_policy(),
runtime_environment,
tool_plugin_provenance(config),
)
.await;
@ -267,7 +196,6 @@ pub async fn collect_mcp_snapshot_with_detail(
detail: McpSnapshotDetail,
) -> McpListToolsResponseEvent {
let mcp_servers = effective_mcp_servers(config, auth);
let tool_plugin_provenance = tool_plugin_provenance(config);
if mcp_servers.is_empty() {
return McpListToolsResponseEvent {
tools: HashMap::new(),
@ -292,7 +220,6 @@ pub async fn collect_mcp_snapshot_with_detail(
tx_event,
SandboxPolicy::new_read_only_policy(),
runtime_environment,
tool_plugin_provenance,
)
.await;
@ -340,7 +267,6 @@ pub async fn collect_mcp_server_status_snapshot_with_detail(
detail: McpSnapshotDetail,
) -> McpServerStatusSnapshot {
let mcp_servers = effective_mcp_servers(config, auth);
let tool_plugin_provenance = tool_plugin_provenance(config);
if mcp_servers.is_empty() {
return McpServerStatusSnapshot {
tools_by_server: HashMap::new(),
@ -365,7 +291,6 @@ pub async fn collect_mcp_server_status_snapshot_with_detail(
tx_event,
SandboxPolicy::new_read_only_policy(),
runtime_environment,
tool_plugin_provenance,
)
.await;

View file

@ -1,8 +1,5 @@
use super::*;
use codex_config::Constrained;
use codex_login::CodexAuth;
use codex_plugin::AppConnectorId;
use codex_plugin::PluginCapabilitySummary;
use codex_protocol::protocol::AskForApproval;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
@ -19,9 +16,7 @@ fn test_mcp_config(codex_home: PathBuf) -> McpConfig {
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
codex_linux_sandbox_exe: None,
use_legacy_landlock: false,
apps_enabled: false,
configured_mcp_servers: HashMap::new(),
plugin_capability_summaries: Vec::new(),
}
}
@ -84,105 +79,10 @@ fn group_tools_by_server_strips_prefix_and_groups() {
assert_eq!(group_tools_by_server(&tools), expected);
}
#[test]
fn tool_plugin_provenance_collects_app_and_mcp_sources() {
let provenance = ToolPluginProvenance::from_capability_summaries(&[
PluginCapabilitySummary {
display_name: "alpha-plugin".to_string(),
app_connector_ids: vec![AppConnectorId("connector_example".to_string())],
mcp_server_names: vec!["alpha".to_string()],
..PluginCapabilitySummary::default()
},
PluginCapabilitySummary {
display_name: "beta-plugin".to_string(),
app_connector_ids: vec![
AppConnectorId("connector_example".to_string()),
AppConnectorId("connector_gmail".to_string()),
],
mcp_server_names: vec!["beta".to_string()],
..PluginCapabilitySummary::default()
},
]);
assert_eq!(
provenance,
ToolPluginProvenance {
plugin_display_names_by_connector_id: HashMap::from([
(
"connector_example".to_string(),
vec!["alpha-plugin".to_string(), "beta-plugin".to_string()],
),
(
"connector_gmail".to_string(),
vec!["beta-plugin".to_string()],
),
]),
plugin_display_names_by_mcp_server_name: HashMap::from([
("alpha".to_string(), vec!["alpha-plugin".to_string()]),
("beta".to_string(), vec!["beta-plugin".to_string()]),
]),
}
);
}
#[test]
fn codex_apps_mcp_url_for_base_url_keeps_existing_paths() {
assert_eq!(
codex_apps_mcp_url_for_base_url("https://chatgpt.com/backend-api"),
"https://chatgpt.com/backend-api/wham/apps"
);
assert_eq!(
codex_apps_mcp_url_for_base_url("https://chat.openai.com"),
"https://chat.openai.com/backend-api/wham/apps"
);
assert_eq!(
codex_apps_mcp_url_for_base_url("http://localhost:8080/api/codex"),
"http://localhost:8080/api/codex/apps"
);
assert_eq!(
codex_apps_mcp_url_for_base_url("http://localhost:8080"),
"http://localhost:8080/api/codex/apps"
);
}
#[test]
fn codex_apps_mcp_url_uses_legacy_codex_apps_path() {
let config = test_mcp_config(PathBuf::from("/tmp"));
assert_eq!(
codex_apps_mcp_url(&config),
"https://chatgpt.com/backend-api/wham/apps"
);
}
#[test]
fn codex_apps_server_config_uses_legacy_codex_apps_path() {
let mut config = test_mcp_config(PathBuf::from("/tmp"));
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let mut servers = with_codex_apps_mcp(HashMap::new(), /*auth*/ None, &config);
assert!(!servers.contains_key(CODEX_APPS_MCP_SERVER_NAME));
config.apps_enabled = true;
servers = with_codex_apps_mcp(servers, Some(&auth), &config);
let server = servers
.get(CODEX_APPS_MCP_SERVER_NAME)
.expect("codex apps should be present when apps is enabled");
let url = match &server.transport {
McpServerTransportConfig::StreamableHttp { url, .. } => url,
_ => panic!("expected streamable http transport for codex apps"),
};
assert_eq!(url, "https://chatgpt.com/backend-api/wham/apps");
}
#[tokio::test]
async fn effective_mcp_servers_preserve_user_servers_and_add_codex_apps() {
async fn effective_mcp_servers_preserve_user_servers() {
let codex_home = tempfile::tempdir().expect("tempdir");
let mut config = test_mcp_config(codex_home.path().to_path_buf());
config.apps_enabled = true;
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
config.configured_mcp_servers.insert(
"sample".to_string(),
@ -233,15 +133,13 @@ async fn effective_mcp_servers_preserve_user_servers_and_add_codex_apps() {
},
);
let effective = effective_mcp_servers(&config, Some(&auth));
let effective = effective_mcp_servers(&config, None);
let sample = effective.get("sample").expect("user server should exist");
let docs = effective
.get("docs")
.expect("configured server should exist");
let codex_apps = effective
.get(CODEX_APPS_MCP_SERVER_NAME)
.expect("codex apps server should exist");
assert_eq!(effective.len(), 2);
match &sample.transport {
McpServerTransportConfig::StreamableHttp { url, .. } => {
@ -255,10 +153,4 @@ async fn effective_mcp_servers_preserve_user_servers_and_add_codex_apps() {
}
other => panic!("expected streamable http transport, got {other:?}"),
}
match &codex_apps.transport {
McpServerTransportConfig::StreamableHttp { url, .. } => {
assert_eq!(url, "https://chatgpt.com/backend-api/wham/apps");
}
other => panic!("expected streamable http transport, got {other:?}"),
}
}

View file

@ -6,7 +6,6 @@
//! in a single aggregated map using the model-visible fully-qualified tool name
//! as the key.
use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
@ -19,11 +18,9 @@ use std::time::Instant;
use crate::McpAuthStatusEntry;
use crate::mcp::McpConfig;
use crate::mcp::ToolPluginProvenance;
use crate::mcp::configured_mcp_servers;
use crate::mcp::effective_mcp_servers;
use crate::mcp::mcp_permission_prompt_is_auto_approved;
use crate::mcp::tool_plugin_provenance;
pub(crate) use crate::mcp_tool_names::qualify_tools;
use anyhow::Context;
use anyhow::Result;
@ -319,7 +316,6 @@ impl ManagedClient {
#[derive(Clone)]
struct AsyncManagedClient {
client: Shared<BoxFuture<'static, Result<ManagedClient, StartupOutcomeError>>>,
tool_plugin_provenance: Arc<ToolPluginProvenance>,
}
impl AsyncManagedClient {
@ -333,7 +329,6 @@ impl AsyncManagedClient {
cancel_token: CancellationToken,
tx_event: Sender<Event>,
elicitation_requests: ElicitationRequestManager,
tool_plugin_provenance: Arc<ToolPluginProvenance>,
runtime_environment: McpRuntimeEnvironment,
) -> Self {
let tool_filter = ToolFilter::from_config(&config);
@ -379,10 +374,7 @@ impl AsyncManagedClient {
};
let client = fut.boxed().shared();
Self {
client,
tool_plugin_provenance,
}
Self { client }
}
async fn client(&self) -> Result<ManagedClient, StartupOutcomeError> {
@ -390,58 +382,10 @@ impl AsyncManagedClient {
}
async fn listed_tools(&self) -> Option<Vec<ToolInfo>> {
let annotate_tools = |tools: Vec<ToolInfo>| {
let mut tools = tools;
for tool in &mut tools {
let plugin_names = match tool.connector_id.as_deref() {
Some(connector_id) => self
.tool_plugin_provenance
.plugin_display_names_for_connector_id(connector_id),
None => self
.tool_plugin_provenance
.plugin_display_names_for_mcp_server_name(tool.server_name.as_str()),
};
tool.plugin_display_names = plugin_names.to_vec();
if plugin_names.is_empty() {
continue;
}
let plugin_source_note = if plugin_names.len() == 1 {
format!("This tool is part of plugin `{}`.", plugin_names[0])
} else {
format!(
"This tool is part of plugins {}.",
plugin_names
.iter()
.map(|plugin_name| format!("`{plugin_name}`"))
.collect::<Vec<_>>()
.join(", ")
)
};
let description = tool
.tool
.description
.as_deref()
.map(str::trim)
.unwrap_or("");
let annotated_description = if description.is_empty() {
plugin_source_note
} else if matches!(description.chars().last(), Some('.' | '!' | '?')) {
format!("{description} {plugin_source_note}")
} else {
format!("{description}. {plugin_source_note}")
};
tool.tool.description = Some(Cow::Owned(annotated_description));
}
tools
};
let tools = match self.client().await {
match self.client().await {
Ok(client) => Some(client.listed_tools()),
Err(_) => None,
};
tools.map(annotate_tools)
}
}
}
@ -510,10 +454,6 @@ impl McpConnectionManager {
effective_mcp_servers(config, auth)
}
pub fn tool_plugin_provenance(&self, config: &McpConfig) -> ToolPluginProvenance {
tool_plugin_provenance(config)
}
pub fn new_uninitialized(
approval_policy: &Constrained<AskForApproval>,
sandbox_policy: &Constrained<SandboxPolicy>,
@ -558,7 +498,6 @@ impl McpConnectionManager {
tx_event: Sender<Event>,
initial_sandbox_policy: SandboxPolicy,
runtime_environment: McpRuntimeEnvironment,
tool_plugin_provenance: ToolPluginProvenance,
) -> (Self, CancellationToken) {
let cancel_token = CancellationToken::new();
let mut clients = HashMap::new();
@ -566,7 +505,6 @@ impl McpConnectionManager {
let mut join_set = JoinSet::new();
let elicitation_requests =
ElicitationRequestManager::new(approval_policy.value(), initial_sandbox_policy);
let tool_plugin_provenance = Arc::new(tool_plugin_provenance);
let startup_submit_id = submit_id.clone();
let mcp_servers = mcp_servers.clone();
for (server_name, cfg) in mcp_servers.into_iter().filter(|(_, cfg)| cfg.enabled) {
@ -590,7 +528,6 @@ impl McpConnectionManager {
cancel_token.clone(),
tx_event.clone(),
elicitation_requests.clone(),
Arc::clone(&tool_plugin_provenance),
runtime_environment.clone(),
);
clients.insert(server_name.clone(), async_managed_client.clone());

View file

@ -2603,17 +2603,13 @@ async fn to_mcp_config_quarantines_codex_product_surfaces() -> std::io::Result<(
.await?;
let mcp_config = config.to_mcp_config();
assert!(!mcp_config.apps_enabled);
assert!(!mcp_config.skill_mcp_dependency_install_enabled);
assert!(mcp_config.plugin_capability_summaries.is_empty());
let _ = config.features.disable(Feature::Apps);
let mcp_config = config.to_mcp_config();
assert!(!mcp_config.apps_enabled);
assert_eq!(config.to_mcp_config().configured_mcp_servers.len(), 0);
let _ = config.features.enable(Feature::Apps);
let mcp_config = config.to_mcp_config();
assert!(!mcp_config.apps_enabled);
assert_eq!(config.to_mcp_config().configured_mcp_servers.len(), 0);
Ok(())
}

View file

@ -817,9 +817,7 @@ impl Config {
approval_policy: self.permissions.approval_policy.clone(),
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.clone(),
use_legacy_landlock: self.features.use_legacy_landlock(),
apps_enabled: false,
configured_mcp_servers: self.mcp_servers.get().clone(),
plugin_capability_summaries: Vec::new(),
}
}

View file

@ -229,7 +229,6 @@ impl Session {
.unwrap_or_else(|| self.services.environment_manager.local_environment()),
turn_context.cwd.to_path_buf(),
),
ToolPluginProvenance::default(),
)
.await;
{

View file

@ -56,7 +56,6 @@ use codex_login::default_client::originator;
use codex_mcp::McpConnectionManager;
use codex_mcp::McpRuntimeEnvironment;
use codex_mcp::ToolInfo;
use codex_mcp::ToolPluginProvenance;
#[cfg(test)]
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_models_manager::manager::ModelsManager;

View file

@ -850,7 +850,6 @@ impl Session {
.unwrap_or_else(|| sess.services.environment_manager.local_environment()),
session_configuration.cwd.to_path_buf(),
),
ToolPluginProvenance::default(),
)
.instrument(info_span!(
"session_init.mcp_manager_init",