feat(mcp): surface response _meta on ToolCallOutputItem#3480
Open
zhang-liz wants to merge 7 commits into
Open
Conversation
Optional dict carrying CallToolResult._meta returned by MCP servers. Appended at end of dataclass to preserve positional compatibility. Populated by downstream commits; not yet wired here.
Per-call ToolContext gains _mcp_response_meta (private, not in __init__). Used to thread server-returned _meta from invoke_mcp_tool to the function-tool executor without changing the public invoker contract.
invoke_mcp_tool now deep-copies result.meta onto context._mcp_response_meta when the server returned a non-empty dict and the caller passed a ToolContext. Empty/missing _meta leaves the stash unset.
Function-tool executor reads tool_context._mcp_response_meta after invocation, keys it by id(tool_run), and threads it into the ToolCallOutputItem built in _build_function_tool_results.
Serialize ToolCallOutputItem.mcp_response_meta when non-empty and restore it on deserialize. Empty dicts normalize to None. Bumps CURRENT_SCHEMA_VERSION 1.10 -> 1.11 with matching SCHEMA_VERSION_SUMMARIES entry. Extends the released-boundary test to include 1.10 alongside the new unreleased writer.
- FakeMCPServer gains _response_meta to return _meta on CallToolResult. - Unit tests assert invoke_mcp_tool deep-copies meta onto ToolContext, ignores empty/missing meta, and does not mutate when server-side dict changes after the call. - End-to-end runner tests (streaming + non-streaming) assert the new field appears on ToolCallOutputItem; verify None when omitted.
New subsection under MCPServerStreamableHttp pairs with the existing tool_meta_resolver docs and points at ToolCallOutputItem.mcp_response_meta plus the streaming event shape.
Member
|
Thanks for sharing this idea. However, I'd like to avoid this change because this is less flexible and it is a larger change than we'd like to have for this purpose. |
2 tasks
Author
|
Thanks for the quick review @seratch. Could you point me at the direction you'd prefer? I see two ways to slim this down:
Happy to rework as (1) or (2), or close this PR if you'd rather not have either. Want to make sure I'm aiming at the right shape before sending a v2. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Capture the optional
_metafield returned by MCP servers inCallToolResultand surface it onToolCallOutputItemasmcp_response_meta. Applications can read auxiliary payloads (chart configs, trace IDs, frontend-only state) without forwarding them to the model.This is the response-side complement to PR #2375 (
tool_meta_resolver, request-side_meta). The field is a deep copy and is never injected into model context — onlycontent/structuredContentare.Implementation:
invoke_mcp_toolstashesresult.metaon the per-callToolContext; the function-tool executor moves it onto the resultingToolCallOutputItem. RunState schema bumped to 1.11 so the field round-trips through pause/resume.Test plan
tests/mcp/test_mcp_util.pycover the ToolContext stash (populated, omitted, empty-dict; verifies deep copy).tests/mcp/test_runner_calls_mcp.pyassertToolCallOutputItem.mcp_response_metais populated (streaming + non-streaming) and staysNonewhen the server omits_meta.tests/test_run_state.pyfor schema 1.11.make format,make lintclean.mypyclean on all touched files. Fullmake testsadds 8 new passing tests; only pre-existing unrelated failures (numpy/litellm/runloop optional deps) remain — verified identical to baseline (mainshows the same 12 failed / 13 errors).Issue number
Closes #3477
Checks