Please read this first
Feature Request: Read _meta from MCP Tool Call Responses
This is a follow-up to #2367 / PR #2375, which added support for sending _meta in MCP tool call requests.
This request is for the complementary feature: reading _meta from MCP tool call responses.
Problem
PR #2375 enabled passing _meta from the client (Agents SDK) to the MCP server via tool_meta_resolver. This is great for request tracing and context propagation.
However, the reverse direction is not supported: when an MCP server returns _meta in its CallToolResult, the Agents SDK currently ignores it entirely.
Looking at the source code (agents/mcp/util.py, invoke_mcp_tool, lines 666-689):
# Only these fields are read from the result:
if server.use_structured_content and result.structuredContent:
tool_output = json.dumps(result.structuredContent)
else:
for item in result.content:
# ... handles text/image content
# result._meta is NEVER accessed
The MCP protocol spec defines CallToolResult._meta as:
interface CallToolResult {
_meta?: { [key: string]: unknown }; // ← present in the spec, ignored by SDK
content: ContentBlock[];
structuredContent?: { [key: string]: unknown };
isError?: boolean;
}
Our Use Case
We are building a data analysis assistant using the Agents SDK. The architecture involves:
- A main Agent that handles user conversations
- Sub-agents (as tools) that perform data analysis (SQL queries, chart generation)
- An MCP server providing prompts and tools for database interaction
The Core Need: Dual-Output Tools
Our tools need to return two different outputs:
- For the LLM: A compact, token-efficient summary (e.g., "Query returned 1,245 rows")
- For the frontend: The full data payload (e.g., chart configuration JSON, raw data for rendering)
The _meta field in the MCP response is the natural place for this frontend-facing data:
{
"content": [
{ "type": "text", "text": "Sales increased 23% in Q1." }
],
"_meta": {
"tool_meta": {
"type": "chart",
"vis_config": {
"type": "line",
"data": [{"month": "Jan", "sales": 120000}, ...]
}
}
}
}
Why Not Put It in content?
If we put the full vis_config in result.content, it gets fed back to the LLM as context, wasting tokens on data the LLM doesn't need (and shouldn't see in full). The _meta field is explicitly defined in the MCP spec as metadata separate from the primary content.
Why Not Use RunContextWrapper?
RunContextWrapper works for local tools (via @function_tool), but our tools run in an MCP server process (stdio or HTTP). The MCP server cannot access the caller's RunContextWrapper because:
- stdio MCP runs in a separate process
- HTTP MCP runs on a remote host
The _meta field is the MCP-native mechanism for passing auxiliary data back to the client.
Current Workaround and Its Limitations
Our current workaround is to not use MCP for these tools at all — we register them as local FunctionTools so they can write to ctx.context. This means:
- We cannot leverage MCP for tool distribution across services
- We lose the MCP ecosystem benefits (standardized discovery, transport abstraction)
- We have to maintain two separate tool registration paths (MCP for prompts, local for data tools)
Proposed Solution
Expose the response _meta through the SDK's tool execution pipeline. A few possible approaches:
Option A: Attach to ToolContext / RunContextWrapper
After invoke_mcp_tool receives the result, write result._meta into the current run context:
# In invoke_mcp_tool, after receiving result:
if result._meta and hasattr(context, 'context') and isinstance(context.context, dict):
context.context.setdefault("_mcp_tool_meta", {})[tool.name] = result._meta
Option B: Attach to RunItem / ToolCallOutputItem
Extend ToolCallOutputItem (or the internal tool result representation) to carry _meta:
@dataclass
class ToolCallOutputItem:
output: Any
mcp_meta: dict[str, Any] | None = None # ← new field
This would allow event stream consumers to access _meta via event.item.mcp_meta.
Option C: Return Tuple from invoke_mcp_tool
Change invoke_mcp_tool to return both the tool output and the meta:
# Current signature:
async def invoke_mcp_tool(...) -> ToolOutput
# Could be extended to return meta alongside:
async def invoke_mcp_tool(...) -> ToolOutput
# But meta is captured and stored somewhere accessible
Option B feels the cleanest as it aligns with the existing event stream architecture.
Related
Would the maintainers be open to a PR implementing Option B? We're happy to contribute.
Please read this first
Feature Request: Read
_metafrom MCP Tool Call ResponsesProblem
PR #2375 enabled passing
_metafrom the client (Agents SDK) to the MCP server viatool_meta_resolver. This is great for request tracing and context propagation.However, the reverse direction is not supported: when an MCP server returns
_metain itsCallToolResult, the Agents SDK currently ignores it entirely.Looking at the source code (
agents/mcp/util.py,invoke_mcp_tool, lines 666-689):The MCP protocol spec defines
CallToolResult._metaas:Our Use Case
We are building a data analysis assistant using the Agents SDK. The architecture involves:
The Core Need: Dual-Output Tools
Our tools need to return two different outputs:
The
_metafield in the MCP response is the natural place for this frontend-facing data:{ "content": [ { "type": "text", "text": "Sales increased 23% in Q1." } ], "_meta": { "tool_meta": { "type": "chart", "vis_config": { "type": "line", "data": [{"month": "Jan", "sales": 120000}, ...] } } } }Why Not Put It in
content?If we put the full
vis_configinresult.content, it gets fed back to the LLM as context, wasting tokens on data the LLM doesn't need (and shouldn't see in full). The_metafield is explicitly defined in the MCP spec as metadata separate from the primary content.Why Not Use
RunContextWrapper?RunContextWrapperworks for local tools (via@function_tool), but our tools run in an MCP server process (stdio or HTTP). The MCP server cannot access the caller'sRunContextWrapperbecause:The
_metafield is the MCP-native mechanism for passing auxiliary data back to the client.Current Workaround and Its Limitations
Our current workaround is to not use MCP for these tools at all — we register them as local
FunctionTools so they can write toctx.context. This means:Proposed Solution
Expose the response
_metathrough the SDK's tool execution pipeline. A few possible approaches:Option A: Attach to
ToolContext/RunContextWrapperAfter
invoke_mcp_toolreceives the result, writeresult._metainto the current run context:Option B: Attach to
RunItem/ToolCallOutputItemExtend
ToolCallOutputItem(or the internal tool result representation) to carry_meta:This would allow event stream consumers to access
_metaviaevent.item.mcp_meta.Option C: Return Tuple from
invoke_mcp_toolChange
invoke_mcp_toolto return both the tool output and the meta:Option B feels the cleanest as it aligns with the existing event stream architecture.
Related
CallToolResulttool_meta_resolverfor request_meta_metaWould the maintainers be open to a PR implementing Option B? We're happy to contribute.