diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index ced01539bb..ff660167d5 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -61,7 +61,7 @@ pub async fn run_codex_tool_session( is_error: Some(true), structured_content: None, }; - outgoing.send_response(id.clone(), result.into()).await; + outgoing.send_response(id.clone(), result).await; return; } }; @@ -235,9 +235,7 @@ async fn run_codex_tool_session_inner( is_error: None, structured_content: None, }; - outgoing - .send_response(request_id.clone(), result.into()) - .await; + outgoing.send_response(request_id.clone(), result).await; // unregister the id so we don't keep it in the map running_requests_id_to_codex_uuid .lock() @@ -296,9 +294,7 @@ async fn run_codex_tool_session_inner( // structured way. structured_content: None, }; - outgoing - .send_response(request_id.clone(), result.into()) - .await; + outgoing.send_response(request_id.clone(), result).await; break; } } diff --git a/codex-rs/mcp-server/src/error_code.rs b/codex-rs/mcp-server/src/error_code.rs new file mode 100644 index 0000000000..1ffd889d40 --- /dev/null +++ b/codex-rs/mcp-server/src/error_code.rs @@ -0,0 +1,2 @@ +pub(crate) const INVALID_REQUEST_ERROR_CODE: i64 = -32600; +pub(crate) const INTERNAL_ERROR_CODE: i64 = -32603; diff --git a/codex-rs/mcp-server/src/lib.rs b/codex-rs/mcp-server/src/lib.rs index f6d0838efc..abbe3b94a0 100644 --- a/codex-rs/mcp-server/src/lib.rs +++ b/codex-rs/mcp-server/src/lib.rs @@ -18,6 +18,7 @@ use tracing_subscriber::EnvFilter; mod codex_tool_config; mod codex_tool_runner; mod conversation_loop; +mod error_code; mod exec_approval; mod json_to_toml; pub mod mcp_protocol; diff --git a/codex-rs/mcp-server/src/message_processor.rs b/codex-rs/mcp-server/src/message_processor.rs index a3349aeb35..98beb15460 100644 --- a/codex-rs/mcp-server/src/message_processor.rs +++ b/codex-rs/mcp-server/src/message_processor.rs @@ -7,6 +7,7 @@ use crate::codex_tool_config::CodexToolCallParam; use crate::codex_tool_config::CodexToolCallReplyParam; use crate::codex_tool_config::create_tool_for_codex_tool_call_param; use crate::codex_tool_config::create_tool_for_codex_tool_call_reply_param; +use crate::error_code::INVALID_REQUEST_ERROR_CODE; use crate::mcp_protocol::ToolCallRequestParams; use crate::mcp_protocol::ToolCallResponse; use crate::mcp_protocol::ToolCallResponseResult; @@ -191,7 +192,7 @@ impl MessageProcessor { if self.initialized { // Already initialised: send JSON-RPC error response. let error = JSONRPCErrorError { - code: -32600, // Invalid Request + code: INVALID_REQUEST_ERROR_CODE, message: "initialize called more than once".to_string(), data: None, }; @@ -230,9 +231,6 @@ impl MessageProcessor { where T: ModelContextProtocolRequest, { - // result has `Serialized` instance so should never fail - #[expect(clippy::unwrap_used)] - let result = serde_json::to_value(result).unwrap(); self.outgoing.send_response(id, result).await; } @@ -533,9 +531,7 @@ impl MessageProcessor { is_error: Some(true), structured_content: None, }; - outgoing - .send_response(request_id, serde_json::to_value(result).unwrap_or_default()) - .await; + outgoing.send_response(request_id, result).await; return; } }; diff --git a/codex-rs/mcp-server/src/outgoing_message.rs b/codex-rs/mcp-server/src/outgoing_message.rs index e7b0b9b63c..3a77cbf2a0 100644 --- a/codex-rs/mcp-server/src/outgoing_message.rs +++ b/codex-rs/mcp-server/src/outgoing_message.rs @@ -18,6 +18,8 @@ use tokio::sync::mpsc; use tokio::sync::oneshot; use tracing::warn; +use crate::error_code::INTERNAL_ERROR_CODE; + /// Sends messages to the client and manages request callbacks. pub(crate) struct OutgoingMessageSender { next_request_id: AtomicI64, @@ -74,9 +76,24 @@ impl OutgoingMessageSender { } } - pub(crate) async fn send_response(&self, id: RequestId, result: Result) { - let outgoing_message = OutgoingMessage::Response(OutgoingResponse { id, result }); - let _ = self.sender.send(outgoing_message).await; + pub(crate) async fn send_response(&self, id: RequestId, response: T) { + match serde_json::to_value(response) { + Ok(result) => { + let outgoing_message = OutgoingMessage::Response(OutgoingResponse { id, result }); + let _ = self.sender.send(outgoing_message).await; + } + Err(err) => { + self.send_error( + id, + JSONRPCErrorError { + code: INTERNAL_ERROR_CODE, + message: format!("failed to serialize response: {err}"), + data: None, + }, + ) + .await; + } + } } pub(crate) async fn send_event_as_notification(