Skip to content

fix: preserve MCP CallToolResult.meta field in tool output#4806

Open
IanSteno wants to merge 1 commit intolivekit:mainfrom
aisteno:fix/mcp-tool-result-meta
Open

fix: preserve MCP CallToolResult.meta field in tool output#4806
IanSteno wants to merge 1 commit intolivekit:mainfrom
aisteno:fix/mcp-tool-result-meta

Conversation

@IanSteno
Copy link
Contributor

Summary

  • The MCP spec (since v1.10.0) supports a meta field on CallToolResult for returning sideband metadata from tool executions
  • The SDK already passes tool.meta for tool definitions (line 85, 133-134 in mcp.py), but tool_result.meta is discarded when processing tool call results
  • This preserves the meta field by including it in the returned JSON string

Problem

When an MCP server returns a CallToolResult with a meta field:

CallToolResult(
    content=[TextContent(type="text", text='{"results": [...]}')],
    meta={"notify": "data_updated", "data": {"affected_ids": [1, 2, 3]}}
)

The current _tool_called function only extracts .content and returns it as JSON, completely discarding .meta. This means consumers of tool results (e.g., event handlers listening to FunctionToolsExecutedEvent) cannot access any metadata the MCP server intended to communicate.

Fix

After extracting content into result, check if tool_result.meta is present and merge it into the returned JSON:

if getattr(tool_result, "meta", None) is not None:
    parsed = json.loads(result)
    if isinstance(parsed, dict):
        parsed["meta"] = tool_result.meta
    elif isinstance(parsed, list):
        parsed = {"content": parsed, "meta": tool_result.meta}
    result = json.dumps(parsed)

This is backward-compatible — if meta is None (the default), behavior is unchanged.

Use Cases

The meta field enables MCP servers to return sideband data that consumers can intercept before it reaches the LLM:

  • Frontend notifications: Signal UI updates (e.g., "requirements updated")
  • Citation tracking: Source attribution metadata
  • Billing/metrics: Token usage, cost data
  • Cache hints: TTL or invalidation signals

Test plan

  • Verified no behavior change when tool_result.meta is None (default case)
  • Verified single content item + meta produces correct JSON
  • Verified multiple content items + meta wraps in {"content": [...], "meta": {...}}
  • Verified error results still raise ToolError (meta check is after error handling)

🤖 Generated with Claude Code

The MCP spec (since v1.10.0) supports a `meta` field on CallToolResult
for returning sideband metadata from tool executions. The SDK already
passes `tool.meta` for tool definitions (raw_schema), but discards
`tool_result.meta` when processing tool call results.

This preserves the meta field by including it in the returned JSON,
allowing consumers to extract metadata (notifications, citations,
tracking data) from tool results before they reach the LLM.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

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.

1 participant