ag-ui-protocol / ag-ui-protocol/ag-ui
ToolCallResultEvent loses MCP `structuredContent` — needs an optional structured field
- 主要語言
- Python
- 星號
- 15.9k
- 分支
- 1.4k
- 平均合併
- 1 天 17 小時
- 30 天內合併 PR
- 163
描述
## Summary
`ToolCallResultEvent` defines `content: str` and has no field for structured tool output. MCP servers routinely return `CallToolResult` with both:
- `content` — text/blocks for the model
- `structuredContent` — a structured object for the client (MCP Apps UI trees, validated payloads, etc.)
The structured field has nowhere to go in AG-UI today, so it's dropped at the protocol boundary even when the integration layer (LangChain, Agno, Mastra, custom servers) preserves it.
This is closely related to #918 — that issue surfaced the symptom (AG-UI choking on a list-typed `content` value coming from LangChain's MCP adapter) and was closed with a stringification workaround. The underlying design point — that AG-UI's tool-result event has no place to put `structuredContent` — was never resolved. Quoting the original reporter's reply:
> Not sure this should be closed? My solution was a workaround for an underlying issue.
## Why it matters
The most visible casualty is the **MCP Apps extension**: an MCP Apps tool returns a UI tree in `structuredContent`, the host renderer needs that tree to render the iframe contents. If AG-UI is in the pipeline, the tree is gone by the time the host gets the tool result, so the renderer either:
- can't render at all, or
- has to re-invoke the tool out-of-band to recover the data we already had (current workaround in our project).
This applies to any AG-UI integration — LangChain, Agno, Mastra, anything backed by an MCP server.
## Suggested change
Add an optional structured field to `ToolCallResultEvent`:
```python
class ToolCallResultEvent(BaseEvent):
type: Literal[EventType.TOOL_CALL_RESULT] = EventType.TOOL_CALL_RESULT
message_id: str
tool_call_id: str
content: str # text for the model — unchanged
structured_content: Optional[Any] = None # NEW: arbitrary JSON for the host
role: Optional[Literal["tool"]] = None
```
Backward compatible (default `None`). Hosts that don't care can ignore it; MCP-Apps hosts can populate iframes from it directly.
If `Any` is too loose, `Optional[Dict[str, Any]]` matches MCP's spec for `structuredContent`.
## Reproduction
1. Stand up a FastMCP server with an `app=True` tool that returns a Prefab `PrefabApp` (yields `structuredContent` containing the UI JSON tree).
2. Drive it through any AG-UI integration (Agno, LangChain, etc.).
3. On the host side, observe that the `ToolCallResultEvent` `content` is the textual placeholder ("[Rendered Prefab UI]") and there is no field carrying the actual tree.
4. The iframe sits at "Waiting for content…" because `structuredContent` is empty.
## Workaround we're using
We re-invoke the MCP tool ourselves from the AG-UI event emitter, capture `structuredContent`, and stuff it into our own activity event. Cost: a duplicate sandbox/network round-trip per UI-rendering tool call.
## References
- #918 — same root cause, closed prematurely with a stringification workaround
- MCP spec — Tools & Structured Content: https://spec.modelcontextprotocol.io/specification/2025-03-26/server/tools/
- Related upstream-of-AG-UI bug in Agno: agno-agi/agno#7686
貢獻指南
評估
這個 Issue 還沒有評估資料。