Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions artifacts/github/bundles/openai-codex-pr-27607.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{
"analysis_mode": "pr_first",
"commits": [
{
"author": "felixxia-oai",
"committed_at": "2026-06-11T13:39:30Z",
"message": "Dedupe plugin MCPs by app declaration name",
"sha": "6090e9d2498277d9f8720d860e6e89142d9e45a9",
"url": "https://github.com/openai/codex/commit/6090e9d2498277d9f8720d860e6e89142d9e45a9"
},
{
"author": "felixxia-oai",
"committed_at": "2026-06-12T10:09:46Z",
"message": "Use app declarations for plugin app parsing",
"sha": "97a3f22d9eaa499ee384674a4f47788a2c0e219e",
"url": "https://github.com/openai/codex/commit/97a3f22d9eaa499ee384674a4f47788a2c0e219e"
},
{
"author": "xl-openai",
"committed_at": "2026-06-13T03:39:58Z",
"message": "cleanup",
"sha": "8235bab021b5b77dd1999487ab3082fafca3069a",
"url": "https://github.com/openai/codex/commit/8235bab021b5b77dd1999487ab3082fafca3069a"
}
],
"default_branch": "main",
"docs_refs": [],
"examples_refs": [],
"extracted_flags": [
"MCP",
"SIWC",
"PR1",
"PR2",
"PR3",
"PR4",
"PR5",
"--all",
"--test",
"--check",
"LOCAL_FS",
"CONFIG_TOML_FILE",
"SAMPLE_PLUGIN_CONFIG_NAME",
"MAX_CAPABILITY_SUMMARY_DESCRIPTION_LEN"
],
"files": [
{
"additions": 53,
"deletions": 48,
"patch_excerpt": "@@ -1442,7 +1442,9 @@ impl PluginRequestProcessor {\n .await;\n }\n \n- let plugin_apps = load_plugin_apps(result.installed_path.as_path()).await;\n+ let plugin_app_declarations = load_plugin_apps(result.installed_path.as_path()).await;\n+ let plugin_apps =\n+ codex_plugin::app_connector_ids_from_declarations(&plugin_app_declarations);\n let auth = self.auth_manager.auth().await;\n let apps_needing_auth = self\n .plugin_apps_needing_auth_for_install(\n@@ -1559,52 +1561,55 @@ impl PluginRequestProcessor {\n }\n \n let is_chatgpt_auth = auth.as_ref().is_some_and(CodexAuth::is_chatgpt_auth);\n- let apps_needing_auth =\n- if let Some(app_ids_needing_auth) = install_result.app_ids_needing_auth {\n- if app_ids_needing_auth.is_empty()\n- || !config.features.apps_...",
"path": "codex-rs/app-server/src/request_processors/plugins.rs",
"status": "modified"
},
{
"additions": 32,
"deletions": 42,
"patch_excerpt": "@@ -25,13 +25,15 @@ use codex_exec_server::LOCAL_FS;\n use codex_mcp::PluginMcpServerPlacement;\n use codex_mcp::parse_plugin_mcp_config;\n use codex_plugin::AppConnectorId;\n+use codex_plugin::AppDeclaration;\n use codex_plugin::LoadedPlugin;\n use codex_plugin::PluginCapabilitySummary;\n use codex_plugin::PluginHookSource;\n use codex_plugin::PluginId;\n use codex_plugin::PluginIdError;\n use codex_plugin::PluginLoadOutcome;\n use codex_plugin::PluginTelemetryMetadata;\n+use codex_plugin::app_connector_ids_from_declarations;\n use codex_protocol::protocol::Product;\n use codex_protocol::protocol::SkillScope;\n use codex_utils_absolute_path::AbsolutePathBuf;\n@@ -63,12 +65,6 @@ pub struct PluginHookLoadOutcome {\n pub hook_load_warnings: Vec<String>,\n }\n \n-#[derive(Debug, Clone, PartialEq, Eq)]\n-pub struct PluginAppMetadata {\n- pub id: AppConnectorId,\n- pub category: Option<String>,\n-}\n-\n enum...",
"path": "codex-rs/core-plugins/src/loader.rs",
"status": "modified"
},
{
"additions": 21,
"deletions": 8,
"patch_excerpt": "@@ -5,7 +5,7 @@ use crate::loader::PluginHookLoadOutcome;\n use crate::loader::configured_curated_plugin_ids_from_codex_home;\n use crate::loader::curated_plugin_cache_version;\n use crate::loader::installed_plugin_telemetry_metadata;\n-use crate::loader::load_plugin_app_metadata;\n+use crate::loader::load_plugin_apps;\n use crate::loader::load_plugin_hooks;\n use crate::loader::load_plugin_hooks_from_layer_stack;\n use crate::loader::load_plugin_mcp_servers;\n@@ -63,6 +63,7 @@ use codex_plugin::AppConnectorId;\n use codex_plugin::PluginCapabilitySummary;\n use codex_plugin::PluginId;\n use codex_plugin::PluginIdError;\n+use codex_plugin::app_connector_ids_from_declarations;\n use codex_plugin::prompt_safe_plugin_description;\n use codex_protocol::protocol::HookEventName;\n use codex_protocol::protocol::Product;\n@@ -214,7 +215,14 @@ fn project_plugin_load_outcome_for_auth(\n for plugin in &mut plugin...",
"path": "codex-rs/core-plugins/src/manager.rs",
"status": "modified"
},
{
"additions": 189,
"deletions": 22,
"patch_excerpt": "@@ -30,6 +30,7 @@ use codex_config::McpServerOAuthConfig;\n use codex_config::McpServerToolConfig;\n use codex_config::types::McpServerTransportConfig;\n use codex_login::CodexAuth;\n+use codex_plugin::AppDeclaration;\n use codex_protocol::protocol::HookEventName;\n use codex_protocol::protocol::Product;\n use codex_utils_absolute_path::test_support::PathBufExt;\n@@ -94,10 +95,27 @@ fn write_auth_projection_plugin(codex_home: &Path, name: &str, include_app: bool\n ),\n );\n if include_app {\n- write_file(\n- &plugin_root.join(\".app.json\"),\n- &format!(r#\"{{\"apps\":{{\"{name}\":{{\"id\":\"connector_{name}\"}}}}}}\"#),\n- );\n+ write_auth_projection_app(codex_home, name, name);\n+ }\n+}\n+\n+fn write_auth_projection_app(codex_home: &Path, plugin_name: &str, app_name: &str) {\n+ let plugin_root = codex_home\n+ .join(\"plugins/cache\")\n+ .join(\"...",
"path": "codex-rs/core-plugins/src/manager_tests.rs",
"status": "modified"
},
{
"additions": 92,
"deletions": 6,
"patch_excerpt": "@@ -86,16 +86,22 @@ fn write_plugin_mcp_plugin(home: &TempDir, command: &str) {\n }\n \n fn write_plugin_app_plugin(home: &TempDir) {\n+ write_plugin_app_plugin_with_name(home, \"sample\");\n+}\n+\n+fn write_plugin_app_plugin_with_name(home: &TempDir, app_name: &str) {\n let plugin_root = write_sample_plugin_manifest_and_config(home);\n std::fs::write(\n plugin_root.join(\".app.json\"),\n- r#\"{\n- \"apps\": {\n- \"calendar\": {\n+ format!(\n+ r#\"{{\n+ \"apps\": {{\n+ \"{app_name}\": {{\n \"id\": \"calendar\"\n- }\n- }\n-}\"#,\n+ }}\n+ }}\n+}}\"#\n+ ),\n )\n .expect(\"write plugin app config\");\n }\n@@ -316,6 +322,86 @@ async fn explicit_plugin_mentions_use_apps_for_chatgpt_dual_surface_plugins() ->\n Ok(())\n }\n \n+#[tokio::test(flavor = \"multi_thread\", worker_threads = 2)]\n+async fn explicit_plugin_mentions_keep_non_conflicting_mcp_for_chatgpt_auth() ->...",
"path": "codex-rs/core/tests/suite/plugins.rs",
"status": "modified"
},
{
"additions": 22,
"deletions": 0,
"patch_excerpt": "@@ -1,5 +1,7 @@\n //! Shared plugin package models, source providers, identifiers, and telemetry summaries.\n \n+use std::collections::HashSet;\n+\n pub use codex_utils_plugins::mention_syntax;\n pub use codex_utils_plugins::plugin_namespace_for_skill_path;\n \n@@ -26,6 +28,26 @@ pub use provider::ResolvedPluginLocation;\n #[derive(Debug, Clone, PartialEq, Eq, Hash)]\n pub struct AppConnectorId(pub String);\n \n+#[derive(Debug, Clone, PartialEq, Eq)]\n+pub struct AppDeclaration {\n+ pub name: String,\n+ pub connector_id: AppConnectorId,\n+ pub category: Option<String>,\n+}\n+\n+pub fn app_connector_ids_from_declarations<'a>(\n+ app_declarations: impl IntoIterator<Item = &'a AppDeclaration>,\n+) -> Vec<AppConnectorId> {\n+ let mut connector_ids = Vec::new();\n+ let mut seen_connector_ids = HashSet::new();\n+ for app in app_declarations {\n+ if seen_connector_ids.insert(&app.connector_i...",
"path": "codex-rs/plugin/src/lib.rs",
"status": "modified"
},
{
"additions": 10,
"deletions": 14,
"patch_excerpt": "@@ -5,8 +5,10 @@ use codex_utils_absolute_path::AbsolutePathBuf;\n use codex_utils_plugins::PluginSkillRoot;\n \n use crate::AppConnectorId;\n+use crate::AppDeclaration;\n use crate::PluginCapabilitySummary;\n use crate::PluginHookSource;\n+use crate::app_connector_ids_from_declarations;\n \n const MAX_CAPABILITY_SUMMARY_DESCRIPTION_LEN: usize = 1024;\n \n@@ -22,7 +24,7 @@ pub struct LoadedPlugin<M> {\n pub disabled_skill_paths: HashSet<AbsolutePathBuf>,\n pub has_enabled_skills: bool,\n pub mcp_servers: HashMap<String, M>,\n- pub apps: Vec<AppConnectorId>,\n+ pub apps: Vec<AppDeclaration>,\n pub hook_sources: Vec<PluginHookSource>,\n pub hook_load_warnings: Vec<String>,\n pub error: Option<String>,\n@@ -53,7 +55,7 @@ fn plugin_capability_summary_from_loaded<M>(\n description: prompt_safe_plugin_description(plugin.manifest_description.as_deref()),\n has_skills: pl...",
"path": "codex-rs/plugin/src/load_outcome.rs",
"status": "modified"
}
],
"linked_issues": [
"#27652",
"#27459",
"#27607",
"#27602",
"#27461"
],
"notes": [
"Built from GitHub pull-request, commits, files, and repo endpoints."
],
"primary_pr": {
"body": "## Context\n\nThis is the next step in the plugin auth-routing stack. The earlier PRs make `PluginsManager` auth-aware and move the broad App/MCP surface decision into that layer. This PR narrows the ChatGPT/SIWC behavior so we only hide a plugin MCP server when it conflicts with an App declaration of the same name.\n\nIn product terms: if a plugin exposes both an App route and MCP route for `foo`, ChatGPT/SIWC sessions should use the App route for `foo`. If the same plugin also exposes a separate MCP server like `foo2`, that MCP server should remain available.\n\n```json\n// .app.json\n{\n \"apps\": {\n \"foo\": {\n \"id\": \"connector_abc\"\n }\n }\n}\n```\n\n```json\n// .mcp.json\n{\n \"mcpServers\": {\n \"foo\": {\n \"url\": \"https://mcp.foo.com/mcp\"\n },\n \"foo2\": {\n \"url\": \"https://mcp.foo2.com/mcp\"\n }\n }\n}\n```\n\n## Stack\n\n- PR1: #27652 seed plugin manager auth at construction.\n- PR2: #27459 route plugin surfaces by auth mode.\n- PR3: #27607 dedupe plugin MCP servers by App declaration name.\n- PR4: #27602 preserve plugin Apps in connector listings.\n- PR5: #27461 skip install-time plugin MCP OAuth for matching App routes.\n\n## Summary\n\n- Preserve App declaration names in loaded plugin metadata.\n- Keep public effective App outputs as deduped connector IDs for existing callers.\n- For ChatGPT/SIWC, suppress only plugin MCP servers whose names match declared App names.\n\n## Validation\n\n```bash\ncargo fmt --all\ncargo test -p codex-core-plugins plugin_auth_projection\ncargo test -p codex-core-plugins effective_apps\ncargo test -p codex-core-plugins read_plugin_for_config_installed_git_source_reads_from_cache_without_cloning\ncargo test -p codex-core explicit_plugin_mentions_use_apps_for_chatgpt_dual_surface_plugins\ncargo test -p codex-core explicit_plugin_mentions_keep_non_conflicting_mcp_for_chatgpt_auth\ncargo test -p codex-app-server --test all plugin_install_filters_disallowed_apps_needing_auth\ngit diff --check\n```",
"labels": [],
"merged_at": "2026-06-14T00:53:09Z",
"number": 27607,
"state": "merged",
"title": "[codex] Dedupe plugin MCPs by app declaration name",
"url": "https://github.com/openai/codex/pull/27607"
},
"repo": "openai/codex",
"schema": "github_change_bundle/v1"
}
Loading