Skip to content

feat(mcp): retain structured content in the AgentTool response #528

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

dbschmigelski
Copy link
Member

@dbschmigelski dbschmigelski commented Jul 24, 2025

Description

This PR implements the retention of structured content in the AgentTool response when using the MCP client. When structured content is available from MCP tools, it will now be included as the last item in the content array of the returned ToolResult.

The changes include updating the MCP dependency to version 1.11.0, modifying the MCPClient class to handle structured content in both synchronous and asynchronous tool calls. The echo server in integration tests has been enhanced to include a new tool that returns structured content for testing purposes.

The implementation ensures backward compatibility while adding the ability to access structured data returned by MCP tools. This enhancement improves the flexibility of the SDK by allowing tools to return both unstructured and structured responses in a consistent format.

The changes have been tested with both unit tests and integration tests to verify proper handling of structured content in various scenarios.

Related to: https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content

Design Decision Discussion:

We faced two potential approaches for handling structured content:

Option A:
We could have mirrored the MCP Tool Response structure by having separate 'content' and 'structuredContent' fields in our AgentTool response. This would have introduced structured content support uniformly across all tools, not just MCP tools. However, this would require a more significant change to our existing tool response interface.

Option B (Chosen Approach):
We decided to leverage our existing 'json' content type capability, which already provides a mechanism for handling structured data. This approach maintains consistency with our current content type system while still allowing us to represent structured data.

A key design decision was whether to append the structured content to existing content items or replace them entirely. We chose to append the structured content as the last item in the content array for several reasons:

  1. It preserves all information returned by the tool
  2. It maintains backward compatibility
  3. It allows consumers to access both human-readable and structured representations of the data

While appending was chosen, it's worth noting this wasn't a straightforward decision. Replacing existing content with structured content could have provided a cleaner, more direct approach to structured data handling, may result in existing applications breaking because an expected text block is no longer present.

Type of Change

New feature

Testing

How have you tested the change? Verify that the changes do not break functionality or introduce warnings in consuming repositories: agents-docs, agents-tools, agents-cli

  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • [no, open to adding if desired] I have updated the documentation accordingly
  • [n/a] I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@@ -252,6 +270,9 @@ def _handle_tool_result(self, tool_use_id: str, call_tool_result: MCPCallToolRes
if (mapped_content := self._map_mcp_content_to_tool_result_content(content)) is not None
]

if call_tool_result.structuredContent:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting that the alternative to this is adding a new content type "structured". This is large decision point of this PR, should json be leveraged as an implied structured content or can json be different

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if there is an advantage to use "json" as an implied structure content field? If not, it may be more readable to add a new content type "structured" for readability

Copy link
Member Author

@dbschmigelski dbschmigelski Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, there is a third option too where we can do

ToolResult(
   status:...
    toolUseId:..
    content: ...
    structuredContent: ...
)

In this case structuredContent is not exposed to the Agent but is present to be usable by the tool caller

Copy link
Member Author

@dbschmigelski dbschmigelski Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comes down to if we view structuredContent as something that is just intended to be used by the user or not. The main concern is with the MCP spec

For backwards compatibility, a tool that returns structured content SHOULD also return the serialized JSON in a TextContent block.

meaning the content block may not be used. We could take the opinionated view that if a text block is not present we stringify the json and add it though.

After writing this I am leaning more toward the following but open to discussion

ToolResult(
   status:...
    toolUseId:..
    content: ...
    structuredContent: ...
)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above proposed approach sounds good to me

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had a offline chat with @dbschmigelski about the ToolResult object, I'd vote for content block with json appended into results which follows existing patterns today.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chatted offline with a few folks on this as well.

I would like to avoid adding this to the tool result content array. MCP currently recommends that if a tool previously returned json in its result as serialized text, it SHOULD continue doing so. By appending the structuredContent to the content array, we are duplicating the number of tokens provided to the model.

Im thinking we extend the ToolResult type with an MCPToolResult, and when we call the bedrock model, we filter out only the required keys needed. This way we can include additional information like structuredContent in the ToolResult, while also not impacting how the model uses it.

poshinchen
poshinchen previously approved these changes Jul 25, 2025
Comment on lines 255 to 256
expected by the agent framework. If structured content is available in the MCP tool result,
it will be appended as the last item in the content array of the returned ToolResult.
Copy link

@padmak30 padmak30 Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will duplicate the response in content blocks, since both the structured and unstructured results are returned. Just a concern if it would break any existing implementations.

@dbschmigelski dbschmigelski deployed to auto-approve July 25, 2025 22:42 — with GitHub Actions Active
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants