ag-ui-protocol / ag-ui-protocol/ag-ui

ToolCallResultEvent loses MCP `structuredContent` — needs an optional structured field

Ouverte
#1,593 3 commentaires 0 réactions 0 personnes assignées Voir sur GitHub
enhancement proposal
Langage dominant
Python
Étoiles
15.9k
Forks
1.4k
Merge moyen
1 j 17 h
PR mergées (30 j)
163

Description

## 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

Guide de contribution

Ouvrir le guide de contribution

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.