mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
Add opt-in protocol discovery for local stdio MCP servers (#57210)
## Why Stdio servers without `CODEX_MCP_PROTOCOL_VERSION` use the legacy handshake, even when they support MCP `2026-07-28`. ## What changed - Add the disabled-by-default `mcp_connection_sharing` feature to enable discovery for eligible local stdio servers. - Recover from failed or late discovery with a fresh handshake when needed, waiting for the previous helper to release resources before restarting. - Retain a working legacy connection when an upgrade attempt fails for the same account, except on authentication errors. - Keep tool catalogs separate by protocol and invalidate legacy catalogs when discovery negotiates the modern protocol. ## Testing Add regression coverage for failed and late discovery, legacy fallback, protocol-specific catalog reuse, message limits, and Windows descendant cleanup. GitOrigin-RevId: 36c675ef3b81d32fccf06ccbddda2312cfe602c9
This commit is contained in:
@@ -1143,6 +1143,9 @@
|
||||
"mcp_2026_07_28": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mcp_connection_sharing": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mcp_oauth_refresh_coordination": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -7751,6 +7754,9 @@
|
||||
"mcp_2026_07_28": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mcp_connection_sharing": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mcp_oauth_refresh_coordination": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -124,6 +124,7 @@ impl ManagedFeatures {
|
||||
let mut values = self.get().clone();
|
||||
for feature in [
|
||||
Feature::EnableMcpApps,
|
||||
Feature::McpConnectionSharing,
|
||||
Feature::SecretAuthStorage,
|
||||
Feature::McpOAuthRefreshCoordination,
|
||||
] {
|
||||
|
||||
@@ -1957,6 +1957,7 @@ impl Config {
|
||||
Vec::new()
|
||||
},
|
||||
protocol_mode: self.mcp_protocol_mode(),
|
||||
discover_local_stdio: self.features.enabled(Feature::McpConnectionSharing),
|
||||
host_owned_apps_protocol_mode: if self.features.enabled(Feature::CodexAppsMcp20260728) {
|
||||
McpProtocolMode::V20260728
|
||||
} else {
|
||||
|
||||
@@ -67,6 +67,7 @@ impl Config {
|
||||
// These operational controls accept host defaults beneath explicit layer values.
|
||||
// Enterprise opt-in always comes from trusted layers instead.
|
||||
for feature in [
|
||||
Feature::McpConnectionSharing,
|
||||
Feature::SecretAuthStorage,
|
||||
Feature::McpOAuthRefreshCoordination,
|
||||
] {
|
||||
|
||||
@@ -40,7 +40,8 @@ async fn wait_for_process_exit_inner(pid: String) -> anyhow::Result<()> {
|
||||
|
||||
pub async fn wait_for_process_exit(pid: &str) -> anyhow::Result<()> {
|
||||
let pid = pid.to_string();
|
||||
tokio::time::timeout(Duration::from_secs(2), wait_for_process_exit_inner(pid))
|
||||
// A stdio process group can keep its leader's PID for the two-second shutdown grace.
|
||||
tokio::time::timeout(Duration::from_secs(5), wait_for_process_exit_inner(pid))
|
||||
.await
|
||||
.context("timed out waiting for process to exit")??;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ use codex_extension_api::ExtensionRegistryBuilder;
|
||||
use codex_extension_api::McpServerContribution;
|
||||
use codex_extension_api::McpServerContributionContext;
|
||||
use codex_extension_api::McpServerContributor;
|
||||
use codex_features::Feature;
|
||||
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
use codex_protocol::mcp::McpServerConnectionStatus;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
@@ -700,6 +701,11 @@ async fn cached_mcp_startup_is_eager_for_root_and_lazy_for_subagents() -> anyhow
|
||||
/*sandbox*/ None,
|
||||
)
|
||||
.await?;
|
||||
// The subagent can reuse the parent's legacy catalog even when discovery is enabled.
|
||||
let mut subagent_config = fixture.config.clone();
|
||||
subagent_config
|
||||
.features
|
||||
.enable(Feature::McpConnectionSharing)?;
|
||||
let NewThread {
|
||||
thread: second_thread,
|
||||
..
|
||||
@@ -713,7 +719,7 @@ async fn cached_mcp_startup_is_eager_for_root_and_lazy_for_subagents() -> anyhow
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
..StartThreadOptions::new(fixture.config.clone())
|
||||
..StartThreadOptions::new(subagent_config)
|
||||
})
|
||||
.await?;
|
||||
second_thread.submit(Op::Interrupt).await?;
|
||||
|
||||
Reference in New Issue
Block a user