Problem
Some production MCP servers (e.g. Brex at https://api.brex.com/mcp) return successful tool results where the payload lives entirely in structuredContent, with an empty content array:
{
"content": [],
"structuredContent": {
"id": "cuuser_...",
"first_name": "...",
"email": "...",
"role": "EMPLOYEE",
"status": "ACTIVE"
},
"isError": false
}
The MCP call succeeds (isError: false), but agents behave as if the tool returned nothing: empty tool messages to the model, empty observability spans, and user-facing “no data” responses despite a valid structured payload.
What seems fixed already (thank you)
Follow-ups to #7686 landed in #7715 and #8580: structuredContent is no longer discarded and is preserved on ToolResult (via metadata["structured_content"] in recent releases).
That addresses data loss on the ToolResult object — e.g. tool hooks or direct inspection can read the structured payload.
What still appears broken
Even on releases that preserve metadata["structured_content"], ToolResult.content stays empty when content: [].
The model / run pipeline still appears to read only .content when building tool messages (e.g. in models/base.py: function_call_output = tool_result.content).
So for structuredContent-only responses:
| Field |
Observed |
ToolResult.metadata["structured_content"] |
Populated (2.6.20+) |
ToolResult.content |
"" |
| Tool message fed back to the LLM |
Empty |
| Typical tracing (tool output string) |
Empty |
On older Agno versions (e.g. 2.6.9), structuredContent is not preserved at all — only the empty .content path runs.
PR #8580 notes that metadata fields are “not propagated into the run/stream/message pipeline” — which matches the gap above for agents that rely on the standard tool-result → message flow.
Use case that would benefit from a fix
Vendor MCP integrations for business APIs (expense management, banking, billing, etc.) where the server exposes typed structured objects rather than plain text blocks in content.
Concrete example: calling get_user_myself on Brex MCP returns the authenticated user as structured JSON. An agent should be able to answer “who am I in Brex?” or chain follow-up tool calls using that identity — without the developer re-implementing MCP result adapters in every app.
The same pattern likely applies to other MCP servers adopting structured output (Stripe and similar), and to MCP Apps / AG-UI flows where structured data must reach either the LLM or the client through the normal Agno tool path.
Expected behavior (for discussion)
When isError == false, content is empty, and structuredContent is present, Agno should make the payload visible through the primary agent loop, not only via direct ToolResult.metadata access. Possible directions (Agno’s call):
- Serialize
structuredContent into ToolResult.content when content is empty (e.g. JSON string), and/or
- Fall back to
metadata["structured_content"] in the model tool-message path when .content is blank.
When both content and structuredContent are present, existing behavior should remain (text in .content, structured data additionally in metadata).
Temporary workaround on our side (not proposed as upstream design)
We added a small integration-layer shim: a forwarding proxy around the MCP session’s call_tool that injects a synthetic TextContent JSON block when content is empty and structuredContent is present, then feed that session into Agno’s existing get_entrypoint_for_tool.
It unblocks our production path but feels like something that belongs in Agno’s MCP adapter and/or message pipeline rather than in every consumer. We’re reporting this so upstream can choose the right long-term behavior.
Environment
- Agno: 2.6.9 (also verified gap on 2.6.20+ for LLM-visible output despite metadata preservation)
- MCP Python SDK: 1.28.x, streamable HTTP transport
- MCP server: Brex (
https://api.brex.com/mcp), OAuth Bearer auth
Happy to provide a minimal repro script or additional traces if useful.
Problem
Some production MCP servers (e.g. Brex at
https://api.brex.com/mcp) return successful tool results where the payload lives entirely instructuredContent, with an emptycontentarray:{ "content": [], "structuredContent": { "id": "cuuser_...", "first_name": "...", "email": "...", "role": "EMPLOYEE", "status": "ACTIVE" }, "isError": false }The MCP call succeeds (
isError: false), but agents behave as if the tool returned nothing: empty tool messages to the model, empty observability spans, and user-facing “no data” responses despite a valid structured payload.What seems fixed already (thank you)
Follow-ups to #7686 landed in #7715 and #8580:
structuredContentis no longer discarded and is preserved onToolResult(viametadata["structured_content"]in recent releases).That addresses data loss on the
ToolResultobject — e.g. tool hooks or direct inspection can read the structured payload.What still appears broken
Even on releases that preserve
metadata["structured_content"],ToolResult.contentstays empty whencontent: [].The model / run pipeline still appears to read only
.contentwhen building tool messages (e.g. inmodels/base.py:function_call_output = tool_result.content).So for structuredContent-only responses:
ToolResult.metadata["structured_content"]ToolResult.content""On older Agno versions (e.g. 2.6.9),
structuredContentis not preserved at all — only the empty.contentpath runs.PR #8580 notes that metadata fields are “not propagated into the run/stream/message pipeline” — which matches the gap above for agents that rely on the standard tool-result → message flow.
Use case that would benefit from a fix
Vendor MCP integrations for business APIs (expense management, banking, billing, etc.) where the server exposes typed structured objects rather than plain text blocks in
content.Concrete example: calling
get_user_myselfon Brex MCP returns the authenticated user as structured JSON. An agent should be able to answer “who am I in Brex?” or chain follow-up tool calls using that identity — without the developer re-implementing MCP result adapters in every app.The same pattern likely applies to other MCP servers adopting structured output (Stripe and similar), and to MCP Apps / AG-UI flows where structured data must reach either the LLM or the client through the normal Agno tool path.
Expected behavior (for discussion)
When
isError == false,contentis empty, andstructuredContentis present, Agno should make the payload visible through the primary agent loop, not only via directToolResult.metadataaccess. Possible directions (Agno’s call):structuredContentintoToolResult.contentwhencontentis empty (e.g. JSON string), and/ormetadata["structured_content"]in the model tool-message path when.contentis blank.When both
contentandstructuredContentare present, existing behavior should remain (text in.content, structured data additionally in metadata).Temporary workaround on our side (not proposed as upstream design)
We added a small integration-layer shim: a forwarding proxy around the MCP session’s
call_toolthat injects a syntheticTextContentJSON block whencontentis empty andstructuredContentis present, then feed that session into Agno’s existingget_entrypoint_for_tool.It unblocks our production path but feels like something that belongs in Agno’s MCP adapter and/or message pipeline rather than in every consumer. We’re reporting this so upstream can choose the right long-term behavior.
Environment
https://api.brex.com/mcp), OAuth Bearer authHappy to provide a minimal repro script or additional traces if useful.