diff --git a/codex-rs/exec/src/event_processor_with_jsonl_output.rs b/codex-rs/exec/src/event_processor_with_jsonl_output.rs index 045baacc7b51..89e41eacb16f 100644 --- a/codex-rs/exec/src/event_processor_with_jsonl_output.rs +++ b/codex-rs/exec/src/event_processor_with_jsonl_output.rs @@ -223,6 +223,7 @@ impl EventProcessorWithJsonOutput { arguments, result: result.map(|result| McpToolCallItemResult { content: result.content, + meta: result.meta, structured_content: result.structured_content, }), error: error.map(|error| McpToolCallItemError { diff --git a/codex-rs/exec/src/event_processor_with_jsonl_output_tests.rs b/codex-rs/exec/src/event_processor_with_jsonl_output_tests.rs index 88fd042f7c89..6bd358f6bebd 100644 --- a/codex-rs/exec/src/event_processor_with_jsonl_output_tests.rs +++ b/codex-rs/exec/src/event_processor_with_jsonl_output_tests.rs @@ -1,5 +1,6 @@ use super::*; use pretty_assertions::assert_eq; +use serde_json::json; use tempfile::tempdir; #[test] @@ -57,3 +58,53 @@ fn failed_turn_does_not_overwrite_output_last_message_file() { "keep existing contents" ); } + +#[test] +fn mcp_tool_call_result_preserves_meta_in_jsonl_event() { + let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None); + + let collected = processor.collect_thread_events(ServerNotification::ItemCompleted( + codex_app_server_protocol::ItemCompletedNotification { + item: ThreadItem::McpToolCall { + id: "mcp-1".to_string(), + server: "search service".to_string(), + tool: "web_run".to_string(), + status: McpToolCallStatus::Completed, + arguments: json!({"search_query": [{"q": "OpenAI Codex CLI documentation"}]}), + mcp_app_resource_uri: None, + result: Some(Box::new(codex_app_server_protocol::McpToolCallResult { + content: vec![json!({"type": "text", "text": "search result"})], + structured_content: None, + meta: Some(json!({"raw_messages": [{"ref_id": "turn0search0"}]})), + })), + error: None, + duration_ms: Some(42), + }, + thread_id: "thread-1".to_string(), + turn_id: "turn-1".to_string(), + completed_at_ms: 0, + }, + )); + + assert_eq!(collected.status, CodexStatus::Running); + assert_eq!(collected.events.len(), 1); + + let ThreadEvent::ItemCompleted(ItemCompletedEvent { item }) = &collected.events[0] else { + panic!("expected item.completed event"); + }; + let ThreadItemDetails::McpToolCall(item) = &item.details else { + panic!("expected MCP tool call item"); + }; + let result = item.result.as_ref().expect("expected MCP tool result"); + assert_eq!( + result.meta, + Some(json!({"raw_messages": [{"ref_id": "turn0search0"}]})) + ); + + let serialized = serde_json::to_value(&collected.events[0]).expect("serialize event"); + assert_eq!( + serialized["item"]["result"]["_meta"], + json!({"raw_messages": [{"ref_id": "turn0search0"}]}) + ); + assert!(serialized["item"]["result"].get("meta").is_none()); +} diff --git a/codex-rs/exec/src/exec_events.rs b/codex-rs/exec/src/exec_events.rs index 4a84ef7494bd..078a876ef488 100644 --- a/codex-rs/exec/src/exec_events.rs +++ b/codex-rs/exec/src/exec_events.rs @@ -266,6 +266,9 @@ pub struct McpToolCallItemResult { // representations). Using `JsonValue` keeps the payload wire-shaped and // easy to export. pub content: Vec, + #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub meta: Option, pub structured_content: Option, } diff --git a/codex-rs/exec/tests/event_processor_with_json_output.rs b/codex-rs/exec/tests/event_processor_with_json_output.rs index efda58f412da..acbac8254c76 100644 --- a/codex-rs/exec/tests/event_processor_with_json_output.rs +++ b/codex-rs/exec/tests/event_processor_with_json_output.rs @@ -540,6 +540,7 @@ fn mcp_tool_call_begin_and_end_emit_item_events() { arguments: json!({ "key": "value" }), result: Some(McpToolCallItemResult { content: Vec::new(), + meta: None, structured_content: None, }), error: None, @@ -681,6 +682,7 @@ fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() { "type": "text", "text": "done", })], + meta: None, structured_content: Some(json!({ "status": "ok" })), }), error: None, diff --git a/sdk/typescript/src/items.ts b/sdk/typescript/src/items.ts index 182878f55bd1..8fd3b2c7ff5b 100644 --- a/sdk/typescript/src/items.ts +++ b/sdk/typescript/src/items.ts @@ -60,6 +60,7 @@ export type McpToolCallItem = { /** Result payload returned by the MCP server for successful calls. */ result?: { content: McpContentBlock[]; + _meta?: unknown; structured_content: unknown; }; /** Error message reported for failed calls. */