Give hosted Codex Apps an independent MCP protocol opt-in (#44318)

## Why

Hosted Codex Apps shared the `mcp_2026_07_28` protocol setting with other MCP servers, preventing independent protocol selection.

## What changed

- Add `features.codex_apps_mcp_2026_07_28`, disabled by default, for the host-owned HTTP `codex_apps` server. Enabling it attempts 2026-07-28 discovery with Legacy fallback.
- Keep other servers, including third-party registrations with Apps-like names or URLs and local stdio servers, governed by `mcp_2026_07_28`.
- Support the new flag through `experimentalFeature/enablement/set` and refresh protocol settings in existing sessions. Track protocol mode per server when deciding whether to reuse connections.

## Testing

Add coverage for independent feature and protocol selection, hosted event-stream protocol selection, standalone Legacy defaults and discovery fallback, and runtime enablement refreshing an existing app-server thread.

GitOrigin-RevId: 640e8e251f0c45e5f4829cc2b73c3082cf42f715
This commit is contained in:
thomas authored and copyberry committed 2026-09-09 22:22:08 +00:00
1 parent e1b23086ac
commit eb680c0558
18 files changed
+431 -19

No files matched your search

+33 -4
View File
@@ -7018,7 +7018,7 @@ async fn to_mcp_config_flows_mcp_tool_prefix_from_feature() -> std::io::Result<(
}
#[tokio::test]
async fn to_mcp_config_flows_mcp_2026_feature_from_config() -> std::io::Result<()> {
async fn to_mcp_config_flows_independent_mcp_2026_features() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let mut config = Config::load_from_base_config_with_overrides(
ConfigToml::default(),
@@ -7030,13 +7030,42 @@ async fn to_mcp_config_flows_mcp_2026_feature_from_config() -> std::io::Result<(
plugins_manager_for_config(&config, auth_manager_from_optional_auth(/*auth*/ None));
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert_eq!(mcp_config.protocol_mode, codex_mcp::McpProtocolMode::Legacy);
assert_eq!(
(
mcp_config.protocol_mode,
mcp_config.host_owned_apps_protocol_mode,
),
(
codex_mcp::McpProtocolMode::Legacy,
codex_mcp::McpProtocolMode::Legacy,
)
);
let _ = config.features.enable(Feature::Mcp20260728);
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert_eq!(
mcp_config.protocol_mode,
codex_mcp::McpProtocolMode::V20260728
(
mcp_config.protocol_mode,
mcp_config.host_owned_apps_protocol_mode,
),
(
codex_mcp::McpProtocolMode::V20260728,
codex_mcp::McpProtocolMode::Legacy,
)
);
let _ = config.features.disable(Feature::Mcp20260728);
let _ = config.features.enable(Feature::CodexAppsMcp20260728);
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert_eq!(
(
mcp_config.protocol_mode,
mcp_config.host_owned_apps_protocol_mode,
),
(
codex_mcp::McpProtocolMode::Legacy,
codex_mcp::McpProtocolMode::V20260728,
)
);
Ok(())
+5
View File
@@ -1792,6 +1792,11 @@ impl Config {
Vec::new()
},
protocol_mode: self.mcp_protocol_mode(),
host_owned_apps_protocol_mode: if self.features.enabled(Feature::CodexAppsMcp20260728) {
McpProtocolMode::V20260728
} else {
McpProtocolMode::Legacy
},
client_elicitation_capability: if self.features.enabled(Feature::AuthElicitation) {
ElicitationCapability::new()
.with_form(FormElicitationCapability::new())