fix(app-server): expose remote MCP servers in plugin read (#26453)

## Why

Remote plugin detail responses include MCP server metadata under
`release.mcp_servers`, but Codex did not deserialize or propagate that
field. As a result, `plugin/read` always returned an empty `mcpServers`
list for remote plugins, so the plugin details pane omitted the MCP
Servers section even when the remote plugin declares one.

This affects uninstalled plugins as well: the remote detail API is the
source of truth and returns MCP server keys without requiring a local
plugin bundle.

## What changed

- Deserialize MCP server entries from remote plugin detail responses.
- Normalize their keys into a sorted, deduplicated list on
`RemotePluginDetail`.
- Return those keys from app-server `plugin/read` instead of hardcoding
an empty list.
- Add regression coverage proving an uninstalled remote plugin returns
its MCP server names.

## Test plan

- `just test -p codex-core-plugins`
- `just test -p codex-app-server plugin_read`
This commit is contained in:
Eric Ning
2026-06-04 15:10:24 -07:00
committed by GitHub
Unverified
parent 59ca34206b
commit 769c231aa1
3 changed files with 44 additions and 11 deletions
+17
View File
@@ -167,6 +167,7 @@ pub struct RemotePluginDetail {
pub app_manifest: Option<JsonValue>,
pub skills: Vec<RemotePluginSkill>,
pub app_ids: Vec<String>,
pub mcp_servers: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
@@ -417,6 +418,13 @@ struct RemotePluginReleaseResponse {
interface: RemotePluginReleaseInterfaceResponse,
#[serde(default)]
skills: Vec<RemotePluginSkillResponse>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
mcp_servers: Vec<RemotePluginMcpServerResponse>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
struct RemotePluginMcpServerResponse {
key: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
@@ -948,6 +956,14 @@ async fn build_remote_plugin_detail(
enabled: !disabled_skill_names.contains(&skill.name),
})
.collect();
let mut mcp_servers = plugin
.release
.mcp_servers
.iter()
.map(|server| server.key.clone())
.collect::<Vec<_>>();
mcp_servers.sort_unstable();
mcp_servers.dedup();
Ok(RemotePluginDetail {
marketplace_name,
@@ -959,6 +975,7 @@ async fn build_remote_plugin_detail(
app_manifest: plugin.release.app_manifest,
skills,
app_ids: plugin.release.app_ids,
mcp_servers,
})
}