Skip to content

[Bug] MCP structuredContent-only tool results leave ToolResult.content empty — agent loop sees no output #8653

Description

@Sebastiao-Assuncao

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):

  1. Serialize structuredContent into ToolResult.content when content is empty (e.g. JSON string), and/or
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions