Files
codex/codex-rs/codex-mcp/src/server.rs
jif-oai 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

109 lines
3.2 KiB
Rust

use codex_builtin_mcps::BuiltinMcpServer;
use codex_config::McpServerConfig;
use codex_config::McpServerTransportConfig;
/// The runtime launch strategy for an effective MCP server.
#[derive(Debug, Clone)]
pub(crate) enum McpServerLaunch {
Configured(Box<McpServerConfig>),
Builtin(BuiltinMcpServer),
}
/// MCP server after product-owned runtime additions have been applied.
#[derive(Debug, Clone)]
pub struct EffectiveMcpServer {
launch: McpServerLaunch,
}
impl EffectiveMcpServer {
pub fn configured(config: McpServerConfig) -> Self {
Self {
launch: McpServerLaunch::Configured(Box::new(config)),
}
}
pub fn builtin(server: BuiltinMcpServer) -> Self {
Self {
launch: McpServerLaunch::Builtin(server),
}
}
pub(crate) fn launch(&self) -> &McpServerLaunch {
&self.launch
}
pub fn configured_config(&self) -> Option<&McpServerConfig> {
match &self.launch {
McpServerLaunch::Configured(config) => Some(config.as_ref()),
McpServerLaunch::Builtin(_) => None,
}
}
pub fn enabled(&self) -> bool {
match &self.launch {
McpServerLaunch::Configured(config) => config.enabled,
McpServerLaunch::Builtin(_) => true,
}
}
pub fn required(&self) -> bool {
match &self.launch {
McpServerLaunch::Configured(config) => config.required,
McpServerLaunch::Builtin(_) => false,
}
}
}
/// Transport origin retained for metrics and diagnostics after server launch.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum McpServerOrigin {
InProcess,
Stdio,
StreamableHttp(String),
}
impl McpServerOrigin {
pub fn as_str(&self) -> &str {
match self {
Self::InProcess => "in_process",
Self::Stdio => "stdio",
Self::StreamableHttp(origin) => origin,
}
}
fn from_transport(transport: &McpServerTransportConfig) -> Option<Self> {
match transport {
McpServerTransportConfig::StreamableHttp { url, .. } => {
let parsed = url::Url::parse(url).ok()?;
Some(Self::StreamableHttp(parsed.origin().ascii_serialization()))
}
McpServerTransportConfig::Stdio { .. } => Some(Self::Stdio),
}
}
}
/// Semantic metadata that must survive after the server is launched.
#[derive(Debug, Clone)]
pub(crate) struct McpServerMetadata {
pub pollutes_memory: bool,
pub origin: Option<McpServerOrigin>,
pub supports_parallel_tool_calls: bool,
}
impl From<&EffectiveMcpServer> for McpServerMetadata {
fn from(server: &EffectiveMcpServer) -> Self {
match server.launch() {
McpServerLaunch::Configured(config) => Self {
pollutes_memory: true,
origin: McpServerOrigin::from_transport(&config.transport),
supports_parallel_tool_calls: config.supports_parallel_tool_calls,
},
McpServerLaunch::Builtin(server) => Self {
pollutes_memory: server.pollutes_memory(),
origin: Some(McpServerOrigin::InProcess),
supports_parallel_tool_calls: server.supports_parallel_tool_calls(),
},
}
}
}