diff --git a/codex-rs/mcp-server/src/codex_message_processor.rs b/codex-rs/mcp-server/src/codex_message_processor.rs index 2495feb9e0..54a418fd7a 100644 --- a/codex-rs/mcp-server/src/codex_message_processor.rs +++ b/codex-rs/mcp-server/src/codex_message_processor.rs @@ -42,6 +42,8 @@ use crate::wire_format::RemoveConversationListenerParams; use crate::wire_format::RemoveConversationSubscriptionResponse; use crate::wire_format::SendUserMessageParams; use crate::wire_format::SendUserMessageResponse; +use crate::wire_format::SendUserTurnParams; +use crate::wire_format::SendUserTurnResponse; use codex_core::protocol::InputItem as CoreInputItem; use codex_core::protocol::Op; @@ -78,6 +80,9 @@ impl CodexMessageProcessor { ClientRequest::SendUserMessage { request_id, params } => { self.send_user_message(request_id, params).await; } + ClientRequest::SendUserTurn { request_id, params } => { + self.send_user_turn(request_id, params).await; + } ClientRequest::InterruptConversation { request_id, params } => { self.interrupt_conversation(request_id, params).await; } @@ -169,6 +174,58 @@ impl CodexMessageProcessor { .await; } + async fn send_user_turn(&self, request_id: RequestId, params: SendUserTurnParams) { + let SendUserTurnParams { + conversation_id, + items, + cwd, + approval_policy, + sandbox_policy, + model, + effort, + summary, + } = params; + + let Ok(conversation) = self + .conversation_manager + .get_conversation(conversation_id.0) + .await + else { + let error = JSONRPCErrorError { + code: INVALID_REQUEST_ERROR_CODE, + message: format!("conversation not found: {conversation_id}"), + data: None, + }; + self.outgoing.send_error(request_id, error).await; + return; + }; + + let mapped_items: Vec = items + .into_iter() + .map(|item| match item { + WireInputItem::Text { text } => CoreInputItem::Text { text }, + WireInputItem::Image { image_url } => CoreInputItem::Image { image_url }, + WireInputItem::LocalImage { path } => CoreInputItem::LocalImage { path }, + }) + .collect(); + + let _ = conversation + .submit(Op::UserTurn { + items: mapped_items, + cwd, + approval_policy, + sandbox_policy, + model, + effort, + summary, + }) + .await; + + self.outgoing + .send_response(request_id, SendUserTurnResponse {}) + .await; + } + async fn interrupt_conversation( &mut self, request_id: RequestId, diff --git a/codex-rs/mcp-server/src/wire_format.rs b/codex-rs/mcp-server/src/wire_format.rs index 034c72a1c2..5df3344216 100644 --- a/codex-rs/mcp-server/src/wire_format.rs +++ b/codex-rs/mcp-server/src/wire_format.rs @@ -2,8 +2,12 @@ use std::collections::HashMap; use std::fmt::Display; use std::path::PathBuf; +use codex_core::config_types::ReasoningEffort; +use codex_core::config_types::ReasoningSummary; +use codex_core::protocol::AskForApproval; use codex_core::protocol::FileChange; use codex_core::protocol::ReviewDecision; +use codex_core::protocol::SandboxPolicy; use mcp_types::RequestId; use serde::Deserialize; use serde::Serialize; @@ -36,6 +40,11 @@ pub enum ClientRequest { request_id: RequestId, params: SendUserMessageParams, }, + SendUserTurn { + #[serde(rename = "id")] + request_id: RequestId, + params: SendUserTurnParams, + }, InterruptConversation { #[serde(rename = "id")] request_id: RequestId, @@ -116,6 +125,23 @@ pub struct SendUserMessageParams { pub items: Vec, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SendUserTurnParams { + pub conversation_id: ConversationId, + pub items: Vec, + pub cwd: PathBuf, + pub approval_policy: AskForApproval, + pub sandbox_policy: SandboxPolicy, + pub model: String, + pub effort: ReasoningEffort, + pub summary: ReasoningSummary, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SendUserTurnResponse {} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct InterruptConversationParams {