Files
codex/codex-rs/builtin-mcps/src/lib.rs
T
b2268999fe feat: make built-in MCPs first-class runtime servers (#21356)
## DISCLAIMER
This is experimental and no production service must rely on this

## Why

Built-in MCPs are product-owned runtime capabilities, but they were
previously flattened into the same config-backed stdio path as
user-configured servers. That made them depend on a hidden `codex
builtin-mcp` re-exec path, exposed them through config-oriented CLI
flows, and erased distinctions the runtime needs to preserve—most
notably whether an MCP call should count as external context for
memory-mode pollution.

## What changed

- Model product-owned built-ins separately from config-backed MCP
servers via `BuiltinMcpServer` and `EffectiveMcpServer`.
- Launch built-ins in process through a reusable async transport instead
of the hidden `builtin-mcp` stdio subcommand.
- Keep config-oriented CLI operations such as `codex mcp
list/get/login/logout` scoped to configured servers, while merging
built-ins only into the effective runtime server set.
- Retain server metadata after launch so parallel-tool support and
context classification come from the live server set; built-in
`memories` is now classified as local Codex state rather than external
context.

## Test plan

- `cargo test -p codex-mcp`
- `cargo test -p codex-core --test suite
builtin_memories_mcp_call_does_not_mark_thread_memory_mode_polluted_when_configured`

---------

Co-authored-by: Codex <noreply@openai.com>
2026-05-07 10:36:32 +02:00

102 lines
2.8 KiB
Rust

//! Built-in MCP servers shipped with Codex.
//!
//! This crate owns the catalog of product-owned MCP servers and the small
//! amount of server-specific dispatch needed to run them. Runtime placement is
//! chosen by `codex-mcp`; built-ins should not be flattened into user-facing
//! MCP server config just to make them launchable.
use std::path::Path;
use tokio::io::AsyncRead;
use tokio::io::AsyncWrite;
pub const MEMORIES_MCP_SERVER_NAME: &str = "memories";
/// Product-owned MCP servers that Codex can provide without user config.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BuiltinMcpServer {
Memories,
}
#[derive(Debug, Clone, Copy)]
struct BuiltinMcpServerMetadata {
name: &'static str,
supports_parallel_tool_calls: bool,
pollutes_memory: bool,
}
impl BuiltinMcpServer {
const fn metadata(self) -> BuiltinMcpServerMetadata {
match self {
Self::Memories => BuiltinMcpServerMetadata {
name: MEMORIES_MCP_SERVER_NAME,
supports_parallel_tool_calls: true,
pollutes_memory: false,
},
}
}
pub const fn name(self) -> &'static str {
self.metadata().name
}
pub const fn supports_parallel_tool_calls(self) -> bool {
self.metadata().supports_parallel_tool_calls
}
pub const fn pollutes_memory(self) -> bool {
self.metadata().pollutes_memory
}
pub async fn serve<T>(self, codex_home: &Path, transport: T) -> anyhow::Result<()>
where
T: AsyncRead + AsyncWrite + Send + 'static,
{
match self {
Self::Memories => {
let codex_home = codex_utils_absolute_path::AbsolutePathBuf::try_from(codex_home)?;
codex_memories_mcp::run_server(&codex_home, transport).await
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct BuiltinMcpServerOptions {
pub memories_enabled: bool,
}
pub fn enabled_builtin_mcp_servers(options: BuiltinMcpServerOptions) -> Vec<BuiltinMcpServer> {
let mut servers = Vec::new();
if options.memories_enabled {
servers.push(BuiltinMcpServer::Memories);
}
servers
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn enabled_builtin_mcp_servers_adds_memories_when_enabled() {
assert_eq!(
enabled_builtin_mcp_servers(BuiltinMcpServerOptions {
memories_enabled: true,
}),
vec![BuiltinMcpServer::Memories]
);
}
#[test]
fn enabled_builtin_mcp_servers_omits_memories_when_disabled() {
assert_eq!(
enabled_builtin_mcp_servers(BuiltinMcpServerOptions {
memories_enabled: false,
}),
Vec::<BuiltinMcpServer>::new()
);
}
}