Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/strands/tools/mcp/mcp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,15 +443,21 @@ def _map_mcp_content_to_tool_result_content(
"""
if isinstance(content, MCPTextContent):
self._log_debug_with_thread("mapping MCP text content")
return {"text": content.text}
result = {"text": content.text}
if content.meta is not None:
result["_meta"] = content.meta
return result
elif isinstance(content, MCPImageContent):
self._log_debug_with_thread("mapping MCP image content with mime type: %s", content.mimeType)
return {
result = {
"image": {
"format": MIME_TO_FORMAT[content.mimeType],
"source": {"bytes": base64.b64decode(content.data)},
}
}
if content.meta is not None:
result["_meta"] = content.meta
return result
else:
self._log_debug_with_thread("unhandled content type: %s - dropping content", content.__class__.__name__)
return None
Expand Down
4 changes: 3 additions & 1 deletion src/strands/types/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Callable, Literal, Protocol, Union
from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Callable, Literal, Protocol, Union, NotRequired, Dict

from typing_extensions import TypedDict

Expand Down Expand Up @@ -68,12 +68,14 @@ class ToolResultContent(TypedDict, total=False):
document: Document content returned by the tool.
image: Image content returned by the tool.
json: JSON-serializable data returned by the tool.
_meta: meta content returned by the tool.
text: Text content returned by the tool.
"""

document: DocumentContent
image: ImageContent
json: Any
_meta: NotRequired[Dict[str, Any]]
Copy link
Member

Choose a reason for hiding this comment

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

Hi, in the current state, we are going to need to do something similar to https://github.com/strands-agents/sdk-python/pull/818/files since if we try to pass _meta to certain models we will get a validation exception.

After #836 this may change. So let's wait until 836 is merged and then I'll re review.

Copy link
Member

@dbschmigelski dbschmigelski Sep 19, 2025

Choose a reason for hiding this comment

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

So #818 has since been merged as has #836.

I want to understand your needs though.

I see that you stated the following, indicating that you are aware that MCPToolResult has optional fields like structuredContent.

# strands
class ToolResultContent(TypedDict, total=False):

    document: DocumentContent
    image: ImageContent
    json: Any
    meta: NotRequired[Dict[str, Any]] # new field
    text: str

class MCPToolResult(ToolResult):
    content: list[ToolResultContent]
    status: ToolResultStatus
    toolUseId: str
    structuredContent: NotRequired[Dict[str, Any]]

Does adding meta to MCPToolResult not satisfy your needs? I could entertain meta being added more generally to the base ToolResult but I'm trying to understand the desire for meta to be a ContentType.

This comes down to the following question: Is meta used by the application, in things like Hooks or other AgentTools, or do we want this exposed to the LLM?

class MCPToolResult(ToolResult):
    content: list[ToolResultContent]
    status: ToolResultStatus
    toolUseId: str
    structuredContent: NotRequired[Dict[str, Any]]
    meta: NotRequired[Dict[str, Any]]

Copy link
Member

Choose a reason for hiding this comment

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

Additionally, our ContentType is a discriminated union. We could change that with a meta special case - would need some convincing. But lets chat more about your needs and we can work back from that

Copy link
Author

Choose a reason for hiding this comment

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

So #818 has since been merged as has #836.

I want to understand your needs though.

I see that you stated the following, indicating that you are aware that MCPToolResult has optional fields like structuredContent.

# strands
class ToolResultContent(TypedDict, total=False):

    document: DocumentContent
    image: ImageContent
    json: Any
    meta: NotRequired[Dict[str, Any]] # new field
    text: str

class MCPToolResult(ToolResult):
    content: list[ToolResultContent]
    status: ToolResultStatus
    toolUseId: str
    structuredContent: NotRequired[Dict[str, Any]]

Does adding meta to MCPToolResult not satisfy your needs? I could entertain meta being added more generally to the base ToolResult but I'm trying to understand the desire for meta to be a ContentType.

This comes down to the following question: Is meta used by the application, in things like Hooks or other AgentTools, or do we want this exposed to the LLM?

class MCPToolResult(ToolResult):
    content: list[ToolResultContent]
    status: ToolResultStatus
    toolUseId: str
    structuredContent: NotRequired[Dict[str, Any]]
    meta: NotRequired[Dict[str, Any]]
  1. Here’s the situation: I found that the official MCP’s CallToolResult content is a list, where each item is a TextContent object that contains a _meta field. This indicates that the tool results in MCP can be a list, and each item in the list may include its own meta information. So my idea is to implement it according to the MCP protocol.

  2. Putting it into MCPToolResult would work, but I think it should be a list: NotRequired[List[Dict[str, Any]]].

  3. I don’t think this should be exposed to the LLM, since it’s an MCP reserved field 🤔 meant to carry business parameters like token_usage. I believe a hook can carry such business-specific information in a non-intrusive way, so there’s no need for the LLM to be aware of it.

Copy link
Author

Choose a reason for hiding this comment

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

@dbschmigelski I’d like to know the latest updates about this _meta. I think my idea aligns with the MCP standards.

Copy link
Author

@Mashiro2000 Mashiro2000 Sep 26, 2025

Choose a reason for hiding this comment

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

Does adding meta to MCPToolResult not satisfy your needs? I could entertain meta being added more generally to the base ToolResult but I'm trying to understand the desire for meta to be a ContentType.

I believe my business needs have already been met, because CallToolResult also has a meta field.

The meta field in CallToolResult corresponds to the one you added:

class MCPToolResult(ToolResult):
content: list[ToolResultContent]
status: ToolResultStatus
toolUseId: str
structuredContent: NotRequired[Dict[str, Any]]
meta: NotRequired[Dict[str, Any]]

text: str


Expand Down
Loading