Emit AG-UI events for hosted MCP tool calls and results - #1121
PratikDhanave (PratikDhanave) wants to merge 2 commits into
Conversation
contentToEvents had no case for MCPServerToolCallContent or MCPServerToolResultContent, so hosted MCP tool interactions fell through to the default and were dropped. Emit them as AG-UI tool-call start/args/end and tool-call result events, matching the Python host.
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The implementation is covered; remaining feedback is a non-blocking test-coverage nit.
Review effort: Lite
Findings: 1
What changed in this PR
Adds AG-UI event emission for hosted MCP tool calls and results.
Changes:
- Maps MCP calls to tool-call lifecycle events.
- Maps MCP results to
TOOL_CALL_RESULT. - Adds hosted MCP event coverage.
| File | Description |
|---|---|
provider/aguiprovider/hosting_test.go |
Tests hosted MCP event emission. |
provider/aguiprovider/hosting_events.go |
Converts MCP content into AG-UI events. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if !strings.Contains(content, "TOOL_CALL_START") || !strings.Contains(content, "mcp_tool") { | ||
| t.Fatalf("expected MCP tool-call start event, got %q", content) | ||
| } | ||
| if !strings.Contains(content, "TOOL_CALL_RESULT") { | ||
| t.Fatalf("expected MCP tool-call result event, got %q", content) | ||
| } |
This comment has been minimized.
This comment has been minimized.
Assert TOOL_CALL_ARGS, TOOL_CALL_END, and the serialized result payload in addition to start/result, so a regression dropping any event or the payload is caught.
|
Scope: user-visible behavior Changed Go contract: Upstream evidence reviewed:
Result: findings reported Finding: MCP tool-call start event omits
|
There was a problem hiding this comment.
Generated by Go API Consistency Review Agent · copilot · auto · 101.3 AIC · ⌖ 8.92 AIC · ⊞ 9.2K
| args = "{}" | ||
| } | ||
| return []aguiEvents.Event{ | ||
| aguiEvents.NewToolCallStartEvent(callID, c.Name), |
There was a problem hiding this comment.
MCP tool-call events lose the parentMessageId association that the sibling FunctionCallContent case sets a few lines above (WithParentMessageID(messageID), with the comment "Associate the tool call with its assistant message ... Matches the Python host").
Upstream, both _emit_tool_call (regular function calls) and _emit_mcp_tool_call — the function this PR's own description cites as the Python source — call _track_tool_call_segment(flow, tool_call_id) and pass the result as parent_message_id to ToolCallStartEvent (python/packages/ag-ui/agent_framework_ag_ui/_run_common.py, _emit_mcp_tool_call ~L1002-1008, _track_tool_call_segment ~L574). Python does not special-case MCP calls to omit it.
Because toAgentMessages (hosting_convert.go) coalesces consecutive assistant messages by keying off ToolCallStartEvent.parentMessageId (see agui.go's pendingToolCall{MessageID: cmp.Or(deref(e.ParentMessageID), e.ToolCallID)}), an MCP tool call streamed without parentMessageId won't coalesce with the surrounding assistant message the way a regular function call does — risking the same "two consecutive single-tool-call assistant messages without intervening tool results" HTTP 400 the FunctionCallContent fix was meant to prevent, now specifically for hosted MCP tool calls.
Suggested fix: pass WithParentMessageID(messageID) (guarded on messageID != "") to NewToolCallStartEvent here too, matching the FunctionCallContent case and the Python _emit_mcp_tool_call/_track_tool_call_segment behavior.

On the AG-UI host path,
contentToEventshad no case for*message.MCPServerToolCallContentor*message.MCPServerToolResultContent, so a hosted MCP tool invocation echoed back by the model fell through to thedefaultand was silently dropped.The Python host emits these via
_emit_mcp_tool_call(→ToolCallStartEvent+ToolCallArgsEvent) and_emit_mcp_tool_result(→ToolCallResultEvent).Change
MCPServerToolCallContent→TOOL_CALL_START/TOOL_CALL_ARGS/TOOL_CALL_ENDandMCPServerToolResultContent→TOOL_CALL_RESULT, mirroring the existingFunctionCallContent/FunctionResultContentcases (including theresult-<callID>unique result id). The mixed-invocation server-tool filter only touchesFunctionCallContent, so MCP content reaches this converter.Test
TestHandler_MCPToolCallAndResultEmitEvents: an MCP call + result now produceTOOL_CALL_START(withmcp_tool) andTOOL_CALL_RESULT. Fails before (both dropped), passes after.