mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] Dedupe plugin MCPs by app declaration name (#27607)
## Context
This 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.
In 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.
```json
// .app.json
{
"apps": {
"foo": {
"id": "connector_abc"
}
}
}
```
```json
// .mcp.json
{
"mcpServers": {
"foo": {
"url": "https://mcp.foo.com/mcp"
},
"foo2": {
"url": "https://mcp.foo2.com/mcp"
}
}
}
```
## Stack
- PR1: #27652 seed plugin manager auth at construction.
- PR2: #27459 route plugin surfaces by auth mode.
- PR3: #27607 dedupe plugin MCP servers by App declaration name.
- PR4: #27602 preserve plugin Apps in connector listings.
- PR5: #27461 skip install-time plugin MCP OAuth for matching App
routes.
## Summary
- Preserve App declaration names in loaded plugin metadata.
- Keep public effective App outputs as deduped connector IDs for
existing callers.
- For ChatGPT/SIWC, suppress only plugin MCP servers whose names match
declared App names.
## Validation
```bash
cargo fmt --all
cargo test -p codex-core-plugins plugin_auth_projection
cargo test -p codex-core-plugins effective_apps
cargo test -p codex-core-plugins read_plugin_for_config_installed_git_source_reads_from_cache_without_cloning
cargo test -p codex-core explicit_plugin_mentions_use_apps_for_chatgpt_dual_surface_plugins
cargo test -p codex-core explicit_plugin_mentions_keep_non_conflicting_mcp_for_chatgpt_auth
cargo test -p codex-app-server --test all plugin_install_filters_disallowed_apps_needing_auth
git diff --check
```
---------
Co-authored-by: Xin Lin <xl@openai.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
0fed4497f5
commit
51316ead4a
@@ -1,5 +1,7 @@
|
||||
//! Shared plugin package models, source providers, identifiers, and telemetry summaries.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub use codex_utils_plugins::mention_syntax;
|
||||
pub use codex_utils_plugins::plugin_namespace_for_skill_path;
|
||||
|
||||
@@ -26,6 +28,26 @@ pub use provider::ResolvedPluginLocation;
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct AppConnectorId(pub String);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AppDeclaration {
|
||||
pub name: String,
|
||||
pub connector_id: AppConnectorId,
|
||||
pub category: Option<String>,
|
||||
}
|
||||
|
||||
pub fn app_connector_ids_from_declarations<'a>(
|
||||
app_declarations: impl IntoIterator<Item = &'a AppDeclaration>,
|
||||
) -> Vec<AppConnectorId> {
|
||||
let mut connector_ids = Vec::new();
|
||||
let mut seen_connector_ids = HashSet::new();
|
||||
for app in app_declarations {
|
||||
if seen_connector_ids.insert(&app.connector_id) {
|
||||
connector_ids.push(app.connector_id.clone());
|
||||
}
|
||||
}
|
||||
connector_ids
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct PluginCapabilitySummary {
|
||||
pub config_name: String,
|
||||
|
||||
@@ -5,8 +5,10 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_plugins::PluginSkillRoot;
|
||||
|
||||
use crate::AppConnectorId;
|
||||
use crate::AppDeclaration;
|
||||
use crate::PluginCapabilitySummary;
|
||||
use crate::PluginHookSource;
|
||||
use crate::app_connector_ids_from_declarations;
|
||||
|
||||
const MAX_CAPABILITY_SUMMARY_DESCRIPTION_LEN: usize = 1024;
|
||||
|
||||
@@ -22,7 +24,7 @@ pub struct LoadedPlugin<M> {
|
||||
pub disabled_skill_paths: HashSet<AbsolutePathBuf>,
|
||||
pub has_enabled_skills: bool,
|
||||
pub mcp_servers: HashMap<String, M>,
|
||||
pub apps: Vec<AppConnectorId>,
|
||||
pub apps: Vec<AppDeclaration>,
|
||||
pub hook_sources: Vec<PluginHookSource>,
|
||||
pub hook_load_warnings: Vec<String>,
|
||||
pub error: Option<String>,
|
||||
@@ -53,7 +55,7 @@ fn plugin_capability_summary_from_loaded<M>(
|
||||
description: prompt_safe_plugin_description(plugin.manifest_description.as_deref()),
|
||||
has_skills: plugin.has_enabled_skills,
|
||||
mcp_server_names,
|
||||
app_connector_ids: plugin.apps.clone(),
|
||||
app_connector_ids: app_connector_ids_from_declarations(&plugin.apps),
|
||||
};
|
||||
|
||||
(summary.has_skills
|
||||
@@ -149,18 +151,12 @@ impl<M: Clone> PluginLoadOutcome<M> {
|
||||
}
|
||||
|
||||
pub fn effective_apps(&self) -> Vec<AppConnectorId> {
|
||||
let mut apps = Vec::new();
|
||||
let mut seen_connector_ids = HashSet::new();
|
||||
|
||||
for plugin in self.plugins.iter().filter(|plugin| plugin.is_active()) {
|
||||
for connector_id in &plugin.apps {
|
||||
if seen_connector_ids.insert(connector_id.clone()) {
|
||||
apps.push(connector_id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apps
|
||||
app_connector_ids_from_declarations(
|
||||
self.plugins
|
||||
.iter()
|
||||
.filter(|plugin| plugin.is_active())
|
||||
.flat_map(|plugin| plugin.apps.iter()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn effective_plugin_hook_sources(&self) -> Vec<PluginHookSource> {
|
||||
|
||||
Reference in New Issue
Block a user