Skip to content

feat(mcp): keep an MCP structured tool result - #3389

Merged
qbc2016 merged 2 commits into
agentscope-ai:mainfrom
ydflow:fix/mcp-structured-tool-result
Oct 21, 2026
Merged

qbc2016 merged 2 commits into
agentscope-ai:mainfrom
ydflow:fix/mcp-structured-tool-result

Conversation

@ydflow

@ydflow ydflow commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Fixes #3388

PR Title Format

fix(tool): keep an MCP structured tool result

AgentScope Version

2.0.11dev (base: main @ 58da57a)

Description

An MCP tool that declares an outputSchema may return its payload only in
CallToolResult.structuredContent. MCPTool.call() converted
CallToolResult.content only, so such a call produced a ToolChunk with no
blocks: the next request carried an empty tool result (content: "" for
OpenAI, {"output": ""} for Gemini, (empty tool output) for Anthropic) and
the model lost the result entirely, without any log line.

Change (src/agentscope/tool/_adapters.py): after converting
result.content, append json.dumps(result.structured_content, ensure_ascii=False) as a TextBlock when the converted content contains no
text block.

  • Empty content + structuredContent → the JSON is the tool result.
  • Media-only content + structuredContent → image block keeps its JSON
    companion (a text block cannot carry it, so the spec's
    backwards-compatibility workaround is not even possible here).
  • isError results keep their state and also carry the payload.
  • Server already serialized the JSON into a text block → unchanged, no
    duplicated payload.
  • No structuredContent → unchanged.

The stateful (session) and stateless (client_gen) paths both go through
call(), so both are covered by the single change.

Tests: new tests/mcp_tool_result_test.py (5 cases, fake session, no
network 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 clean
on 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):
ResourceLink and BlobResourceContents are still dropped.

Checklist

  • An issue has been created for this PR
  • I have read the CONTRIBUTING.md
  • Docstrings are in Google style
  • Related documentation has been updated (e.g. links, examples, etc.) in documentation repository — not applicable, behaviour of an existing API only
  • Code is ready for review

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.

@qbc2016 qbc2016 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@github-actions github-actions Bot removed the pr/awaiting-review Waiting for maintainers to review this PR label Oct 21, 2026
@qbc2016 qbc2016 changed the title fix(tool): keep an MCP structured tool result feat(mcp): keep an MCP structured tool result Oct 21, 2026
@qbc2016
qbc2016 merged commit 7c198c5 into agentscope-ai:main Oct 21, 2026
12 checks passed
@ChaoDiDiaoAA

Copy link
Copy Markdown

I'm working on a focused follow-up to #3389 for mixed MCP results. On main 26ec620b, an ordinary or empty TextContent still suppresses structuredContent, so fields present only in the structured payload never reach the model.

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.

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
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.

[Bug]: an MCP tool that returns only structuredContent reaches the model as an empty result

3 participants