chore: spawn MCP for memories (#21214)

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
jif-oai
2026-05-06 15:05:54 +02:00
committed by GitHub
co-authored by Codex
parent 8f3bb355f4
commit ca257b6ce5
16 changed files with 414 additions and 3 deletions
+174 -1
View File
@@ -195,12 +195,13 @@ async fn load_config_loads_global_agents_instructions() -> std::io::Result<()> {
"\n global instructions \n",
)?;
let config = Config::load_from_base_config_with_overrides(
let mut config = Config::load_from_base_config_with_overrides(
ConfigToml::default(),
ConfigOverrides::default(),
codex_home.abs(),
)
.await?;
let _ = config.features.enable(Feature::MemoryTool);
assert_eq!(
config.user_instructions.as_deref(),
@@ -3344,6 +3345,77 @@ async fn add_dir_override_extends_workspace_writable_roots() -> std::io::Result<
Ok(())
}
#[tokio::test]
async fn to_mcp_config_empty_mcp_requirements_preserve_builtin_mcps() -> anyhow::Result<()> {
let codex_home = TempDir::new()?;
let requirements = codex_config::ConfigRequirementsToml {
mcp_servers: Some(BTreeMap::new()),
..Default::default()
};
let mut config = ConfigBuilder::default()
.codex_home(codex_home.path().to_path_buf())
.cloud_requirements(CloudRequirementsLoader::new(async move {
Ok(Some(requirements))
}))
.build()
.await?;
config.codex_self_exe = Some(PathBuf::from("/tmp/codex"));
let _ = config.features.enable(Feature::BuiltInMcp);
let _ = config.features.enable(Feature::MemoryTool);
let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert_eq!(
mcp_config
.configured_mcp_servers
.get(codex_mcp::MEMORIES_MCP_SERVER_NAME)
.map(|server| (server.enabled, server.disabled_reason.clone())),
Some((true, None))
);
Ok(())
}
#[tokio::test]
async fn to_mcp_config_nonempty_mcp_requirements_preserve_builtin_mcps() -> anyhow::Result<()> {
let codex_home = TempDir::new()?;
let requirements = codex_config::ConfigRequirementsToml {
mcp_servers: Some(BTreeMap::from([(
"docs".to_string(),
McpServerRequirement {
identity: McpServerIdentity::Command {
command: "docs-mcp".to_string(),
},
},
)])),
..Default::default()
};
let mut config = ConfigBuilder::default()
.codex_home(codex_home.path().to_path_buf())
.cloud_requirements(CloudRequirementsLoader::new(async move {
Ok(Some(requirements))
}))
.build()
.await?;
config.codex_self_exe = Some(PathBuf::from("/tmp/codex"));
let _ = config.features.enable(Feature::BuiltInMcp);
let _ = config.features.enable(Feature::MemoryTool);
let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert_eq!(
mcp_config
.configured_mcp_servers
.get(codex_mcp::MEMORIES_MCP_SERVER_NAME)
.map(|server| (server.enabled, server.disabled_reason.clone())),
Some((true, None))
);
Ok(())
}
#[tokio::test]
async fn sqlite_home_defaults_to_codex_home_for_workspace_write() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
@@ -4166,6 +4238,107 @@ async fn to_mcp_config_preserves_apps_feature_from_config() -> std::io::Result<(
Ok(())
}
#[tokio::test]
async fn to_mcp_config_includes_enabled_builtin_mcps() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let mut config = Config::load_from_base_config_with_overrides(
ConfigToml::default(),
ConfigOverrides {
codex_self_exe: Some(PathBuf::from("/tmp/codex")),
..ConfigOverrides::default()
},
codex_home.abs(),
)
.await?;
let _ = config.features.enable(Feature::BuiltInMcp);
let _ = config.features.enable(Feature::MemoryTool);
let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert_eq!(
mcp_config
.configured_mcp_servers
.get(codex_mcp::MEMORIES_MCP_SERVER_NAME)
.map(|server| &server.transport),
Some(&McpServerTransportConfig::Stdio {
command: "/tmp/codex".to_string(),
args: vec![
"builtin-mcp".to_string(),
"memories".to_string(),
"--codex-home".to_string(),
codex_home.path().display().to_string(),
],
env: None,
env_vars: Vec::new(),
cwd: None,
})
);
Ok(())
}
#[tokio::test]
async fn to_mcp_config_omits_builtin_mcps_when_feature_is_disabled() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let mut config = Config::load_from_base_config_with_overrides(
ConfigToml::default(),
ConfigOverrides {
codex_self_exe: Some(PathBuf::from("/tmp/codex")),
..ConfigOverrides::default()
},
codex_home.abs(),
)
.await?;
let _ = config.features.enable(Feature::MemoryTool);
let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert!(
!mcp_config
.configured_mcp_servers
.contains_key(codex_mcp::MEMORIES_MCP_SERVER_NAME)
);
Ok(())
}
#[tokio::test]
async fn to_mcp_config_reserves_enabled_builtin_mcp_names() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let mut config = Config::load_from_base_config_with_overrides(
ConfigToml {
mcp_servers: HashMap::from([(
codex_mcp::MEMORIES_MCP_SERVER_NAME.to_string(),
http_mcp("https://user.example/memories"),
)]),
..ConfigToml::default()
},
ConfigOverrides {
codex_self_exe: Some(PathBuf::from("/tmp/codex")),
..ConfigOverrides::default()
},
codex_home.abs(),
)
.await?;
let _ = config.features.enable(Feature::BuiltInMcp);
let _ = config.features.enable(Feature::MemoryTool);
let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert!(matches!(
mcp_config
.configured_mcp_servers
.get(codex_mcp::MEMORIES_MCP_SERVER_NAME)
.map(|server| &server.transport),
Some(McpServerTransportConfig::Stdio { .. })
));
Ok(())
}
#[tokio::test]
async fn load_global_mcp_servers_rejects_inline_bearer_token() -> anyhow::Result<()> {
let codex_home = TempDir::new()?;
+13 -1
View File
@@ -70,7 +70,9 @@ use codex_features::FeaturesToml;
use codex_features::MultiAgentV2ConfigToml;
use codex_git_utils::resolve_root_git_project_for_trust;
use codex_login::AuthManagerConfig;
use codex_mcp::BuiltinMcpServerOptions;
use codex_mcp::McpConfig;
use codex_mcp::configured_builtin_mcp_servers;
use codex_memories_read::memory_root;
use codex_model_provider_info::LEGACY_OLLAMA_CHAT_PROVIDER_ID;
use codex_model_provider_info::ModelProviderInfo;
@@ -1087,6 +1089,13 @@ impl Config {
) -> McpConfig {
let plugins_input = self.plugins_config_input();
let loaded_plugins = plugins_manager.plugins_for_config(&plugins_input).await;
let builtin_mcp_servers = configured_builtin_mcp_servers(BuiltinMcpServerOptions {
codex_self_exe: self.codex_self_exe.as_deref(),
codex_home: self.codex_home.as_path(),
memories_enabled: self.features.enabled(Feature::BuiltInMcp)
&& self.features.enabled(Feature::MemoryTool)
&& self.memories.use_memories,
});
let mut configured_mcp_servers = self.mcp_servers.get().clone();
for plugin in loaded_plugins
.plugins()
@@ -1106,9 +1115,12 @@ impl Config {
if let Some(mcp_requirements) = self.config_layer_stack.requirements().mcp_servers.as_ref()
&& mcp_requirements.value.is_empty()
{
// A present empty allowlist bans all MCPs, including plugin MCPs merged above.
// A present empty allowlist bans configurable MCPs, including plugin MCPs merged
// above. Built-ins are product-owned and stay available regardless of admin
// allowlists.
filter_mcp_servers_by_requirements(&mut configured_mcp_servers, Some(mcp_requirements));
}
configured_mcp_servers.extend(builtin_mcp_servers);
McpConfig {
chatgpt_base_url: self.chatgpt_base_url.clone(),