Repository navigation
Python: serialize Anthropic MCP tool result content on replay - #9972
Kip (uragoodman) wants to merge 5 commits into
Conversation
Render parsed Anthropic MCP results as JSON-serializable text blocks instead of Content objects, and cover list, top-level mapping, and persisted-history replay with regression tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ool-result-replay
| "type": "mcp_tool_result", | ||
| "tool_use_id": content.call_id, | ||
| "content": content.output if content.output is not None else "", | ||
| "content": self._prepare_mcp_tool_result_content(content.output), |
There was a problem hiding this comment.
Kip (@uragoodman) This replay path also needs to preserve the provider’s error status. The parser does not retain is_error in provider metadata, and this block never restores it, so a failed hosted MCP call is replayed as a successful-looking result; persistence also removes the only remaining copy in raw_representation. Please persist and restore is_error, with streaming and non-streaming round-trip coverage for error results. Also, please give _prepare_mcp_tool_result_content’s output parameter a concrete union matching the supported shapes instead of Any; this helper defines a replay wire contract and should be type-checked.
There was a problem hiding this comment.
Addressed both points.
is_erroris now persisted in the Anthropic provider metadata on parse and
restored when replayingmcp_server_tool_result.Falseis preserved, a
missing field is omitted, and the value survivesMessage.to_dict()/
from_dict()._prepare_mcp_tool_result_contentnow takes a concreteMcpToolResultOutput
union (str | Mapping | Sequence[Content | Mapping] | None) instead ofAny.
The genericstr(output)/default=strfallback has been removed;
unsupported shapes now raiseContentError, so the runtime contract matches
the annotation.- Note: the generic fallback being removed was introduced by this PR's initial
revision, not by existing main code, so this does not change pre-existing
behavior.
Verification on the revised commit (4d3ad2fc1):
- Focused MCP tests: 21 passed.
- Full Anthropic suite: 269 passed, 10 deselected.
- Ruff format and Ruff check on the Anthropic package: pass.
- mypy, zuban, pyrefly, ty, and Pyright source/tests: pass locally.
Pushed as 4d3ad2fc1. The main workflows are currently waiting for maintainer
approval (action_required); CLA and API compatibility have passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Persist the provider's is_error flag in Anthropic metadata so MCP tool results replay with the same error status after serialization. Tighten the replay output contract to a concrete union and reject unsupported shapes instead of stringifying them. Add streaming, non-streaming, persistence, and unsupported-output regression tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Motivation & Context
Anthropic's provider-hosted MCP connector returns
mcp_tool_resultblocks whose content is parsed intolist[Content]. On the next turn,_prepare_message_for_anthropiccopiedcontent.outputdirectly into the request body, so the Anthropic SDK failed with:The first request succeeds; every subsequent turn that replays the MCP result fails before the request is sent.
This change normalizes MCP tool result output into the shapes Anthropic accepts for
mcp_tool_result.content: a string or a list of text blocks.Description & Review Guide
_prepare_mcp_tool_result_contentto convertNone, strings, top-level mappings, andSequence[Content] | Sequence[Mapping]into Anthropic-compatible content.mcp_server_tool_resultnow uses this helper instead of emittingcontent.outputunchanged.Contentitems become{"type": "text", "text": ...}; non-text items are skipped with a debug log because the Anthropicmcp_tool_resultschema only accepts text.TypeError.function_result.Noneoutputs keep their previous wire shape.mcp_tool_result.The diff also contains one
ruff formatadjustment to an existing over-wrapped call in the same file; it is required by the repository formatter.Testing
pytest packages/anthropic/tests -m "not integration" -q— 259 passedruff format --check— passruff check --no-fix— passpyright --pythonpath .venv/Scripts/python.exe packages/anthropic/agent_framework_anthropic— 0 errorsmypy packages/anthropic/tests/test_anthropic_client.py— no issuesFull workspace build and the full unit-test suite were not run locally; CI will run them.
Related Issue
Fixes #9970
The issue is filed; opening this focused fix for maintainer review. No
ready-for-implementationlabel was present when this PR was opened.AI Assistance
AI assistance details: implementation, tests, and PR description were drafted with an AI coding assistant; I reproduced and verified the failure and the fix locally.
Contribution Checklist
ready-for-implementationlabel was present when opened, and the PR is submitted for early direction feedback.