Repository navigation
feat(mcp): keep an MCP structured tool result - #3389
Merged
qbc2016 merged 2 commits intoOct 21, 2026
Merged
Conversation
An MCP tool that declares an outputSchema may return its payload only in structuredContent. MCPTool.call() read content only, so such a call produced a ToolChunk with no blocks and the model received an empty tool result. Serialize structuredContent into a text block when the converted content holds no text block, which also covers media-only and error results while avoiding duplicate payloads for servers that already serialize the JSON themselves.
This was referenced Oct 21, 2026
|
I'm working on a focused follow-up to #3389 for mixed MCP results. On main The local fix preserves existing content and appends JSON unless an identical serialized text block already carries it. Regression tests cover summaries, empty text, error details, empty objects, duplicate JSON, and the real Toolkit-to-OpenAI-formatter path. They reproduced the loss before the fix and now pass locally. This scope excludes ResourceLink/blob support in #2799 and connection cleanup in #3402. Differently formatted equivalent JSON may still be duplicated; feedback on that compatibility tradeoff is welcome. |
This was referenced Oct 23, 2026
celanwang
pushed a commit
to celanwang/agentscope-go
that referenced
this pull request
Oct 26, 2026
Tool responses carrying MCP structuredContent without any text block were dropped: ToolResponse.Content is built only from wire content blocks, so a structured-only result surfaced as an empty text block on all three clients. Parse structuredContent off the JSON-RPC wire into toolCallResult and, when the converted result has no text block, append it as a JSON text block in convertToolResult (StdioClient/HttpClient) and in the Streamable HTTP convertResult (re-marshaled without HTML escaping). Results that already include a text block are unchanged. Mirrors upstream AgentScope (agentscope-ai/agentscope#3389); the stdio/HTTP path passes the wire JSON through verbatim instead of re-serializing. Refs agentscope-ai#73
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.
Fixes #3388
PR Title Format
fix(tool): keep an MCP structured tool resultAgentScope Version
2.0.11dev (base:
main@ 58da57a)Description
An MCP tool that declares an
outputSchemamay return its payload only inCallToolResult.structuredContent.MCPTool.call()convertedCallToolResult.contentonly, so such a call produced aToolChunkwith noblocks: the next request carried an empty tool result (
content: ""forOpenAI,
{"output": ""}for Gemini,(empty tool output)for Anthropic) andthe model lost the result entirely, without any log line.
Change (
src/agentscope/tool/_adapters.py): after convertingresult.content, appendjson.dumps(result.structured_content, ensure_ascii=False)as aTextBlockwhen the converted content contains notext block.
content+structuredContent→ the JSON is the tool result.content+structuredContent→ image block keeps its JSONcompanion (a text block cannot carry it, so the spec's
backwards-compatibility workaround is not even possible here).
isErrorresults keep their state and also carry the payload.duplicated payload.
structuredContent→ unchanged.The stateful (
session) and stateless (client_gen) paths both go throughcall(), so both are covered by the single change.Tests: new
tests/mcp_tool_result_test.py(5 cases, fake session, nonetwork or server process needed). Before the change the three payload cases
fail (
0 != 1); after the change all 5 pass. Existing MCP suite still passes:tests/mcp_client_reconnect_test.py,tests/mcp_sse_client_test.py,tests/mcp_streamable_http_client_test.py→ 11 passed.Checks:
black --line-length=79,flake8 --extend-ignore=E203,pylint(10.00/10 with the repo's disable list),mypy(repo args) all cleanon the two touched files.
Note on overlap: my open #3372 also edits
src/agentscope/tool/_adapters.py,but in the name-composition hunk (~line 240); this change is in
MCPTool.call(~line 350). They are independent and merge in either order.
Out of scope here (same conversion function, reported in #3388):
ResourceLinkandBlobResourceContentsare still dropped.Checklist