diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index a9a0d6eb5c18..1176a53f3dc1 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -159,8 +159,12 @@ pub(crate) async fn handle_mcp_tool_call( .unwrap_or_else(|| JsonValue::Object(serde_json::Map::new())), }; } - let request_meta = - build_mcp_tool_call_request_meta(turn_context.as_ref(), &server, metadata.as_ref()); + let request_meta = build_mcp_tool_call_request_meta( + turn_context.as_ref(), + &server, + &call_id, + metadata.as_ref(), + ); let connector_id = metadata .as_ref() .and_then(|metadata| metadata.connector_id.clone()); @@ -693,6 +697,7 @@ fn custom_mcp_tool_approval_mode( fn build_mcp_tool_call_request_meta( turn_context: &TurnContext, server: &str, + call_id: &str, metadata: Option<&McpToolApprovalMetadata>, ) -> Option { let mut request_meta = serde_json::Map::new(); @@ -704,10 +709,14 @@ fn build_mcp_tool_call_request_meta( ); } - if server == CODEX_APPS_MCP_SERVER_NAME - && let Some(codex_apps_meta) = - metadata.and_then(|metadata| metadata.codex_apps_meta.clone()) - { + if server == CODEX_APPS_MCP_SERVER_NAME { + let mut codex_apps_meta = metadata + .and_then(|metadata| metadata.codex_apps_meta.clone()) + .unwrap_or_default(); + codex_apps_meta.insert( + "call_id".to_string(), + serde_json::Value::String(call_id.to_string()), + ); request_meta.insert( MCP_TOOL_CODEX_APPS_META_KEY.to_string(), serde_json::Value::Object(codex_apps_meta), diff --git a/codex-rs/core/src/mcp_tool_call_tests.rs b/codex-rs/core/src/mcp_tool_call_tests.rs index 522686446b01..f532053f9095 100644 --- a/codex-rs/core/src/mcp_tool_call_tests.rs +++ b/codex-rs/core/src/mcp_tool_call_tests.rs @@ -668,9 +668,13 @@ async fn mcp_tool_call_request_meta_includes_turn_metadata_for_custom_server() { ) .expect("turn metadata json"); - let meta = - build_mcp_tool_call_request_meta(&turn_context, "custom_server", /*metadata*/ None) - .expect("custom servers should receive turn metadata"); + let meta = build_mcp_tool_call_request_meta( + &turn_context, + "custom_server", + "call-custom", + /*metadata*/ None, + ) + .expect("custom servers should receive turn metadata"); assert_eq!( meta, @@ -715,11 +719,13 @@ async fn codex_apps_tool_call_request_meta_includes_turn_metadata_and_codex_apps build_mcp_tool_call_request_meta( &turn_context, CODEX_APPS_MCP_SERVER_NAME, + "call_abc123xyz789", Some(&metadata), ), Some(serde_json::json!({ crate::X_CODEX_TURN_METADATA_HEADER: expected_turn_metadata, MCP_TOOL_CODEX_APPS_META_KEY: { + "call_id": "call_abc123xyz789", "resource_uri": "connector://calendar/tools/calendar_create_event", "contains_mcp_source": true, "connector_id": "calendar", @@ -728,6 +734,33 @@ async fn codex_apps_tool_call_request_meta_includes_turn_metadata_and_codex_apps ); } +#[tokio::test] +async fn codex_apps_tool_call_request_meta_includes_call_id_without_existing_codex_apps_meta() { + let (_, turn_context) = make_session_and_context().await; + let expected_turn_metadata = serde_json::from_str::( + &turn_context + .turn_metadata_state + .current_header_value() + .expect("turn metadata header"), + ) + .expect("turn metadata json"); + + assert_eq!( + build_mcp_tool_call_request_meta( + &turn_context, + CODEX_APPS_MCP_SERVER_NAME, + "call_abc123xyz789", + /*metadata*/ None, + ), + Some(serde_json::json!({ + crate::X_CODEX_TURN_METADATA_HEADER: expected_turn_metadata, + MCP_TOOL_CODEX_APPS_META_KEY: { + "call_id": "call_abc123xyz789", + }, + })) + ); +} + #[test] fn mcp_tool_call_thread_id_meta_is_added_to_request_meta() { assert_eq!( diff --git a/codex-rs/core/tests/suite/openai_file_mcp.rs b/codex-rs/core/tests/suite/openai_file_mcp.rs index 2912b5fb0e21..3bfa264d7990 100644 --- a/codex-rs/core/tests/suite/openai_file_mcp.rs +++ b/codex-rs/core/tests/suite/openai_file_mcp.rs @@ -222,6 +222,7 @@ async fn codex_apps_file_params_upload_local_paths_before_mcp_tool_call() -> Res assert_eq!( apps_tool_call.pointer("/params/_meta/_codex_apps"), Some(&json!({ + "call_id": "extract-call-1", "resource_uri": DOCUMENT_EXTRACT_TEXT_RESOURCE_URI, "contains_mcp_source": true, "connector_id": "calendar", diff --git a/codex-rs/core/tests/suite/search_tool.rs b/codex-rs/core/tests/suite/search_tool.rs index 1e37d5ae10db..fa2eca9b22d4 100644 --- a/codex-rs/core/tests/suite/search_tool.rs +++ b/codex-rs/core/tests/suite/search_tool.rs @@ -555,6 +555,7 @@ async fn tool_search_returns_deferred_tools_without_follow_up_tool_injection() - .structured_content, Some(json!({ "_codex_apps": { + "call_id": "calendar-call-1", "resource_uri": CALENDAR_CREATE_EVENT_RESOURCE_URI, "contains_mcp_source": true, "connector_id": "calendar", @@ -586,6 +587,7 @@ async fn tool_search_returns_deferred_tools_without_follow_up_tool_injection() - assert_eq!( apps_tool_call.pointer("/params/_meta/_codex_apps"), Some(&json!({ + "call_id": "calendar-call-1", "resource_uri": CALENDAR_CREATE_EVENT_RESOURCE_URI, "contains_mcp_source": true, "connector_id": "calendar",