Make the optional MCP startup grace configurable (#41199)

## What changed

- Add `mcp_optional_startup_grace_ms` with a default of 1,000 ms to control how long tool catalog capture waits for optional MCP servers.
- Treat a value of `0` as disabling the shared grace, so optional servers use their configured `startup_timeout_sec` instead.
- Apply updated grace values during runtime and MCP configuration refreshes, and reset cached startup deadlines when the configured duration changes.

## Testing

- Cover custom, disabled, refreshed, and shared startup grace behavior in MCP connection and core integration tests.

GitOrigin-RevId: 936196215ed6e0a29c9ec72cfee17663978df8ef
This commit is contained in:
trolle-oai authored and copyberry committed 2026-08-27 20:31:40 +00:00
1 parent 035295b46e
commit 124e560b93
14 files changed
+495 -17

No files matched your search

+19
View File
@@ -228,6 +228,25 @@ async fn load_config_normalizes_relative_cwd_override() -> std::io::Result<()> {
Ok(())
}
#[tokio::test]
async fn load_config_applies_optional_mcp_startup_grace() -> std::io::Result<()> {
let codex_home = tempdir()?;
let config_toml: ConfigToml = toml::from_str("mcp_optional_startup_grace_ms = 2500")
.expect("optional MCP startup grace should parse from config.toml");
let config = Config::load_from_base_config_with_overrides(
config_toml,
ConfigOverrides::default(),
codex_home.abs(),
)
.await?;
assert_eq!(
config.mcp_optional_startup_grace,
Duration::from_millis(2500)
);
Ok(())
}
#[tokio::test]
async fn test_toml_parsing() {
let history_with_persistence = r#"
+10
View File
@@ -80,6 +80,7 @@ use codex_http_client::OutboundProxyPolicy;
use codex_install_context::InstallContext;
use codex_login::AuthManagerConfig;
use codex_login::AuthRouteConfig;
use codex_mcp::DEFAULT_OPTIONAL_MCP_STARTUP_GRACE;
use codex_mcp::McpConfig;
use codex_mcp::McpPluginAttribution;
use codex_mcp::McpProtocolMode;
@@ -140,6 +141,7 @@ use std::num::NonZeroUsize;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use crate::config::permissions::BUILT_IN_READ_ONLY_PROFILE;
use crate::config::permissions::BUILT_IN_WORKSPACE_PROFILE;
@@ -847,6 +849,9 @@ pub struct Config {
/// to 127.0.0.1 (using `mcp_oauth_callback_port` when provided).
pub mcp_oauth_callback_url: Option<String>,
/// How long to wait for optional MCP servers while building the initial tool catalog.
pub mcp_optional_startup_grace: Duration,
/// Combined provider map (defaults plus user-defined providers).
pub model_providers: HashMap<String, ModelProviderInfo>,
@@ -1731,6 +1736,7 @@ impl Config {
auth_keyring_backend_kind: self.auth_keyring_backend_kind(),
mcp_oauth_callback_port: self.mcp_oauth_callback_port,
mcp_oauth_callback_url: self.mcp_oauth_callback_url.clone(),
optional_mcp_startup_grace: self.mcp_optional_startup_grace,
skill_mcp_dependency_install_enabled: self
.features
.enabled(Feature::SkillMcpDependencyInstall),
@@ -4165,6 +4171,10 @@ impl Config {
),
mcp_oauth_callback_port: cfg.mcp_oauth_callback_port,
mcp_oauth_callback_url: cfg.mcp_oauth_callback_url.clone(),
mcp_optional_startup_grace: cfg
.mcp_optional_startup_grace_ms
.map(Duration::from_millis)
.unwrap_or(DEFAULT_OPTIONAL_MCP_STARTUP_GRACE),
model_providers,
project_doc_max_bytes: cfg.project_doc_max_bytes.unwrap_or(AGENTS_MD_MAX_BYTES),
project_doc_fallback_filenames: cfg