diff --git a/codex-rs/core/src/mcp_connection_manager.rs b/codex-rs/core/src/mcp_connection_manager.rs index 9fc7275583..eecda78887 100644 --- a/codex-rs/core/src/mcp_connection_manager.rs +++ b/codex-rs/core/src/mcp_connection_manager.rs @@ -17,7 +17,7 @@ use anyhow::Result; use anyhow::anyhow; use codex_mcp_client::McpClient; use mcp_types::ClientCapabilities; -use mcp_types::McpClientInfo; +use mcp_types::Implementation; use mcp_types::Tool; use serde_json::json; @@ -159,10 +159,14 @@ impl McpConnectionManager { // indicates this should be an empty object. elicitation: Some(json!({})), }, - client_info: McpClientInfo { + client_info: Implementation { name: "codex-mcp-client".to_owned(), version: env!("CARGO_PKG_VERSION").to_owned(), title: Some("Codex".into()), + // This field is used by Codex when it is an MCP + // server: it should not be used when Codex is + // an MCP client. + user_agent: None, }, protocol_version: mcp_types::MCP_SCHEMA_VERSION.to_owned(), }; diff --git a/codex-rs/mcp-client/src/main.rs b/codex-rs/mcp-client/src/main.rs index a9f4e335d1..d25bca4ba3 100644 --- a/codex-rs/mcp-client/src/main.rs +++ b/codex-rs/mcp-client/src/main.rs @@ -17,10 +17,10 @@ use anyhow::Context; use anyhow::Result; use codex_mcp_client::McpClient; use mcp_types::ClientCapabilities; +use mcp_types::Implementation; use mcp_types::InitializeRequestParams; use mcp_types::ListToolsRequestParams; use mcp_types::MCP_SCHEMA_VERSION; -use mcp_types::McpClientInfo; use tracing_subscriber::EnvFilter; #[tokio::main] @@ -60,10 +60,13 @@ async fn main() -> Result<()> { sampling: None, elicitation: None, }, - client_info: McpClientInfo { + client_info: Implementation { name: "codex-mcp-client".to_owned(), version: env!("CARGO_PKG_VERSION").to_owned(), title: Some("Codex".to_string()), + // This field is used by Codex when it is an MCP server: it should + // not be used when Codex is an MCP client. + user_agent: None, }, protocol_version: MCP_SCHEMA_VERSION.to_owned(), }; diff --git a/codex-rs/mcp-server/src/message_processor.rs b/codex-rs/mcp-server/src/message_processor.rs index 6e63dee5f7..83e3d1cb75 100644 --- a/codex-rs/mcp-server/src/message_processor.rs +++ b/codex-rs/mcp-server/src/message_processor.rs @@ -234,7 +234,7 @@ impl MessageProcessor { }, instructions: None, protocol_version: params.protocol_version.clone(), - server_info: mcp_types::McpServerInfo { + server_info: mcp_types::Implementation { name: "codex-mcp-server".to_string(), version: env!("CARGO_PKG_VERSION").to_string(), title: Some("Codex".to_string()), diff --git a/codex-rs/mcp-server/tests/common/mcp_process.rs b/codex-rs/mcp-server/tests/common/mcp_process.rs index 2deb5f612d..fa50c9d86e 100644 --- a/codex-rs/mcp-server/tests/common/mcp_process.rs +++ b/codex-rs/mcp-server/tests/common/mcp_process.rs @@ -26,13 +26,13 @@ use codex_protocol::mcp_protocol::SendUserTurnParams; use mcp_types::CallToolRequestParams; use mcp_types::ClientCapabilities; +use mcp_types::Implementation; use mcp_types::InitializeRequestParams; use mcp_types::JSONRPC_VERSION; use mcp_types::JSONRPCMessage; use mcp_types::JSONRPCNotification; use mcp_types::JSONRPCRequest; use mcp_types::JSONRPCResponse; -use mcp_types::McpClientInfo; use mcp_types::ModelContextProtocolNotification; use mcp_types::ModelContextProtocolRequest; use mcp_types::RequestId; @@ -134,10 +134,11 @@ impl McpProcess { roots: None, sampling: None, }, - client_info: McpClientInfo { + client_info: Implementation { name: "elicitation test".into(), title: Some("Elicitation Test".into()), version: "0.0.0".into(), + user_agent: None, }, protocol_version: mcp_types::MCP_SCHEMA_VERSION.into(), }; diff --git a/codex-rs/mcp-types/generate_mcp_types.py b/codex-rs/mcp-types/generate_mcp_types.py index d71a6e59a2..03c82bafa2 100755 --- a/codex-rs/mcp-types/generate_mcp_types.py +++ b/codex-rs/mcp-types/generate_mcp_types.py @@ -265,8 +265,11 @@ class StructField: name: str type_name: str serde: str | None = None + comment: str | None = None def append(self, out: list[str], supports_const: bool) -> None: + if self.comment: + out.append(f" // {self.comment}\n") if self.serde: out.append(f" {self.serde}\n") if self.viz == "const": @@ -312,6 +315,18 @@ def define_struct( else: fields.append(StructField("pub", rs_prop.name, prop_type, rs_prop.serde)) + # Special-case: add Codex-specific user_agent to Implementation + if name == "Implementation": + fields.append( + StructField( + "pub", + "user_agent", + "Option", + '#[serde(default, skip_serializing_if = "Option::is_none")]', + "This is an extra field that the Codex MCP server sends as part of InitializeResult.", + ) + ) + if implements_request_trait(name): add_trait_impl(name, "ModelContextProtocolRequest", fields, out) elif implements_notification_trait(name): diff --git a/codex-rs/mcp-types/src/lib.rs b/codex-rs/mcp-types/src/lib.rs index 75778cb27c..37d790ab6a 100644 --- a/codex-rs/mcp-types/src/lib.rs +++ b/codex-rs/mcp-types/src/lib.rs @@ -482,20 +482,12 @@ pub struct ImageContent { /// Describes the name and version of an MCP implementation, with an optional title for UI representation. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)] -pub struct McpClientInfo { - pub name: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub title: Option, - pub version: String, -} - -/// Describes the name and version of an MCP implementation, with an optional title for UI representation. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, TS)] -pub struct McpServerInfo { +pub struct Implementation { pub name: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub title: Option, pub version: String, + // This is an extra field that the Codex MCP server sends as part of InitializeResult. #[serde(default, skip_serializing_if = "Option::is_none")] pub user_agent: Option, } @@ -513,7 +505,7 @@ impl ModelContextProtocolRequest for InitializeRequest { pub struct InitializeRequestParams { pub capabilities: ClientCapabilities, #[serde(rename = "clientInfo")] - pub client_info: McpClientInfo, + pub client_info: Implementation, #[serde(rename = "protocolVersion")] pub protocol_version: String, } @@ -527,7 +519,7 @@ pub struct InitializeResult { #[serde(rename = "protocolVersion")] pub protocol_version: String, #[serde(rename = "serverInfo")] - pub server_info: McpServerInfo, + pub server_info: Implementation, } impl From for serde_json::Value { diff --git a/codex-rs/mcp-types/tests/suite/initialize.rs b/codex-rs/mcp-types/tests/suite/initialize.rs index 2bd3c789c9..73ff120274 100644 --- a/codex-rs/mcp-types/tests/suite/initialize.rs +++ b/codex-rs/mcp-types/tests/suite/initialize.rs @@ -1,10 +1,10 @@ use mcp_types::ClientCapabilities; use mcp_types::ClientRequest; +use mcp_types::Implementation; use mcp_types::InitializeRequestParams; use mcp_types::JSONRPC_VERSION; use mcp_types::JSONRPCMessage; use mcp_types::JSONRPCRequest; -use mcp_types::McpClientInfo; use mcp_types::RequestId; use serde_json::json; @@ -58,10 +58,11 @@ fn deserialize_initialize_request() { sampling: None, elicitation: None, }, - client_info: McpClientInfo { + client_info: Implementation { name: "acme-client".into(), title: Some("Acme".to_string()), version: "1.2.3".into(), + user_agent: None, }, protocol_version: "2025-06-18".into(), }