From 5c1416d99b9cc71f33a0a81c67caf92872e0381c Mon Sep 17 00:00:00 2001 From: Gabriel Peal Date: Mon, 8 Sep 2025 10:30:13 -0700 Subject: [PATCH] Add a getUserAgent MCP method (#3320) This will allow the extension to pass this user agent + a suffix for its requests --- .../mcp-server/src/codex_message_processor.rs | 11 +++++ .../mcp-server/tests/common/mcp_process.rs | 5 +++ codex-rs/mcp-server/tests/suite/mod.rs | 1 + codex-rs/mcp-server/tests/suite/user_agent.rs | 45 +++++++++++++++++++ codex-rs/protocol-ts/src/lib.rs | 1 + codex-rs/protocol/src/mcp_protocol.rs | 10 +++++ 6 files changed, 73 insertions(+) create mode 100644 codex-rs/mcp-server/tests/suite/user_agent.rs diff --git a/codex-rs/mcp-server/src/codex_message_processor.rs b/codex-rs/mcp-server/src/codex_message_processor.rs index d7502b23..923ebbec 100644 --- a/codex-rs/mcp-server/src/codex_message_processor.rs +++ b/codex-rs/mcp-server/src/codex_message_processor.rs @@ -19,6 +19,7 @@ use codex_core::config::Config; use codex_core::config::ConfigOverrides; use codex_core::config::ConfigToml; use codex_core::config::load_config_as_toml; +use codex_core::default_client::get_codex_user_agent; use codex_core::exec::ExecParams; use codex_core::exec_env::create_env; use codex_core::get_platform_sandbox; @@ -48,6 +49,7 @@ use codex_protocol::mcp_protocol::ExecArbitraryCommandResponse; use codex_protocol::mcp_protocol::ExecCommandApprovalParams; use codex_protocol::mcp_protocol::ExecCommandApprovalResponse; use codex_protocol::mcp_protocol::ExecOneOffCommandParams; +use codex_protocol::mcp_protocol::GetUserAgentResponse; use codex_protocol::mcp_protocol::GetUserSavedConfigResponse; use codex_protocol::mcp_protocol::GitDiffToRemoteResponse; use codex_protocol::mcp_protocol::InputItem as WireInputItem; @@ -169,6 +171,9 @@ impl CodexMessageProcessor { ClientRequest::GetUserSavedConfig { request_id } => { self.get_user_saved_config(request_id).await; } + ClientRequest::GetUserAgent { request_id } => { + self.get_user_agent(request_id).await; + } ClientRequest::ExecOneOffCommand { request_id, params } => { self.exec_one_off_command(request_id, params).await; } @@ -384,6 +389,12 @@ impl CodexMessageProcessor { self.outgoing.send_response(request_id, response).await; } + async fn get_user_agent(&self, request_id: RequestId) { + let user_agent = get_codex_user_agent(Some(&self.config.responses_originator_header)); + let response = GetUserAgentResponse { user_agent }; + self.outgoing.send_response(request_id, response).await; + } + async fn get_user_saved_config(&self, request_id: RequestId) { let toml_value = match load_config_as_toml(&self.config.codex_home) { Ok(val) => val, diff --git a/codex-rs/mcp-server/tests/common/mcp_process.rs b/codex-rs/mcp-server/tests/common/mcp_process.rs index 34b24ac1..cebc332a 100644 --- a/codex-rs/mcp-server/tests/common/mcp_process.rs +++ b/codex-rs/mcp-server/tests/common/mcp_process.rs @@ -247,6 +247,11 @@ impl McpProcess { self.send_request("getUserSavedConfig", None).await } + /// Send a `getUserAgent` JSON-RPC request. + pub async fn send_get_user_agent_request(&mut self) -> anyhow::Result { + self.send_request("getUserAgent", None).await + } + /// Send a `listConversations` JSON-RPC request. pub async fn send_list_conversations_request( &mut self, diff --git a/codex-rs/mcp-server/tests/suite/mod.rs b/codex-rs/mcp-server/tests/suite/mod.rs index 138e11a4..4a9220da 100644 --- a/codex-rs/mcp-server/tests/suite/mod.rs +++ b/codex-rs/mcp-server/tests/suite/mod.rs @@ -8,3 +8,4 @@ mod interrupt; mod list_resume; mod login; mod send_message; +mod user_agent; diff --git a/codex-rs/mcp-server/tests/suite/user_agent.rs b/codex-rs/mcp-server/tests/suite/user_agent.rs new file mode 100644 index 00000000..050bea7a --- /dev/null +++ b/codex-rs/mcp-server/tests/suite/user_agent.rs @@ -0,0 +1,45 @@ +use codex_core::default_client::DEFAULT_ORIGINATOR; +use codex_core::default_client::get_codex_user_agent; +use codex_protocol::mcp_protocol::GetUserAgentResponse; +use mcp_test_support::McpProcess; +use mcp_test_support::to_response; +use mcp_types::JSONRPCResponse; +use mcp_types::RequestId; +use pretty_assertions::assert_eq; +use tempfile::TempDir; +use tokio::time::timeout; + +const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_user_agent_returns_current_codex_user_agent() { + let codex_home = TempDir::new().unwrap_or_else(|err| panic!("create tempdir: {err}")); + + let mut mcp = McpProcess::new(codex_home.path()) + .await + .expect("spawn mcp process"); + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) + .await + .expect("initialize timeout") + .expect("initialize request"); + + let request_id = mcp + .send_get_user_agent_request() + .await + .expect("send getUserAgent"); + let response: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(request_id)), + ) + .await + .expect("getUserAgent timeout") + .expect("getUserAgent response"); + + let received: GetUserAgentResponse = + to_response(response).expect("deserialize getUserAgent response"); + let expected = GetUserAgentResponse { + user_agent: get_codex_user_agent(Some(DEFAULT_ORIGINATOR)), + }; + + assert_eq!(received, expected); +} diff --git a/codex-rs/protocol-ts/src/lib.rs b/codex-rs/protocol-ts/src/lib.rs index cdc63e4e..c414b3e7 100644 --- a/codex-rs/protocol-ts/src/lib.rs +++ b/codex-rs/protocol-ts/src/lib.rs @@ -47,6 +47,7 @@ pub fn generate_ts(out_dir: &Path, prettier: Option<&Path>) -> Result<()> { codex_protocol::mcp_protocol::ExecCommandApprovalParams::export_all_to(out_dir)?; codex_protocol::mcp_protocol::ExecCommandApprovalResponse::export_all_to(out_dir)?; codex_protocol::mcp_protocol::GetUserSavedConfigResponse::export_all_to(out_dir)?; + codex_protocol::mcp_protocol::GetUserAgentResponse::export_all_to(out_dir)?; codex_protocol::mcp_protocol::ServerNotification::export_all_to(out_dir)?; generate_index_ts(out_dir)?; diff --git a/codex-rs/protocol/src/mcp_protocol.rs b/codex-rs/protocol/src/mcp_protocol.rs index a8d4b180..c3263d4d 100644 --- a/codex-rs/protocol/src/mcp_protocol.rs +++ b/codex-rs/protocol/src/mcp_protocol.rs @@ -137,6 +137,10 @@ pub enum ClientRequest { #[serde(rename = "id")] request_id: RequestId, }, + GetUserAgent { + #[serde(rename = "id")] + request_id: RequestId, + }, /// Execute a command (argv vector) under the server's sandbox. ExecOneOffCommand { #[serde(rename = "id")] @@ -339,6 +343,12 @@ pub struct GetAuthStatusResponse { pub auth_token: Option, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)] +#[serde(rename_all = "camelCase")] +pub struct GetUserAgentResponse { + pub user_agent: String, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)] #[serde(rename_all = "camelCase")] pub struct GetUserSavedConfigResponse {