diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index 49cbf0a85..c0983a3db 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -3863,6 +3863,32 @@ async fn turn_event_counts_completed_tool_items() { ) .await; + let mcp_tool_call_item = |status, duration_ms| ThreadItem::McpToolCall { + id: "mcp-1".to_string(), + server: "server".to_string(), + tool: "search".to_string(), + status, + arguments: json!({}), + mcp_app_resource_uri: None, + plugin_id: Some("sample@test".to_string()), + result: None, + error: None, + duration_ms, + }; + reducer + .ingest( + AnalyticsFact::Notification(Box::new(ServerNotification::ItemStarted( + ItemStartedNotification { + thread_id: "thread-2".to_string(), + turn_id: "turn-2".to_string(), + started_at_ms: 998, + item: mcp_tool_call_item(McpToolCallStatus::InProgress, None), + }, + ))), + &mut out, + ) + .await; + let completed_tool_items = vec![ sample_command_execution_item(CommandExecutionStatus::Completed, Some(0), Some(1)), ThreadItem::FileChange { @@ -3870,18 +3896,7 @@ async fn turn_event_counts_completed_tool_items() { changes: Vec::new(), status: PatchApplyStatus::Completed, }, - ThreadItem::McpToolCall { - id: "mcp-1".to_string(), - server: "server".to_string(), - tool: "search".to_string(), - status: McpToolCallStatus::Completed, - arguments: json!({}), - mcp_app_resource_uri: None, - plugin_id: None, - result: None, - error: None, - duration_ms: Some(2), - }, + mcp_tool_call_item(McpToolCallStatus::Completed, Some(2)), ThreadItem::DynamicToolCall { id: "dynamic-1".to_string(), namespace: None, @@ -3939,6 +3954,13 @@ async fn turn_event_counts_completed_tool_items() { .await; } + let mcp_tool_call_event = out + .iter() + .find(|event| matches!(event, TrackEventRequest::McpToolCall(_))) + .expect("MCP tool call event should be emitted"); + let payload = serde_json::to_value(mcp_tool_call_event).expect("serialize MCP tool call event"); + assert_eq!(payload["event_params"]["plugin_id"], json!("sample@test")); + reducer .ingest( AnalyticsFact::Notification(Box::new(sample_turn_completed_notification( diff --git a/codex-rs/analytics/src/events.rs b/codex-rs/analytics/src/events.rs index 997ee51c5..2df113a43 100644 --- a/codex-rs/analytics/src/events.rs +++ b/codex-rs/analytics/src/events.rs @@ -633,6 +633,7 @@ pub(crate) struct CodexMcpToolCallEventParams { pub(crate) mcp_server_name: String, pub(crate) mcp_tool_name: String, pub(crate) mcp_error_present: bool, + pub(crate) plugin_id: Option, } #[derive(Serialize)] diff --git a/codex-rs/analytics/src/reducer.rs b/codex-rs/analytics/src/reducer.rs index fef1621c9..a621051fb 100644 --- a/codex-rs/analytics/src/reducer.rs +++ b/codex-rs/analytics/src/reducer.rs @@ -1751,6 +1751,7 @@ fn tool_item_event(input: ToolItemEventInput<'_>) -> Option { status, error, duration_ms, + plugin_id, .. } => { let (terminal_status, failure_kind) = mcp_tool_call_outcome(status)?; @@ -1780,6 +1781,7 @@ fn tool_item_event(input: ToolItemEventInput<'_>) -> Option { mcp_server_name: server.clone(), mcp_tool_name: tool.clone(), mcp_error_present: error.is_some(), + plugin_id: plugin_id.clone(), }, }, ))