Skip to content

Python: fix(mcp): do not duplicate structuredContent when content present - #8274

Merged
Eduard van Valkenburg (eavanvalkenburg) merged 4 commits into
microsoft:mainfrom
ktz03:fix/mcp-no-duplicate-structured-content
Sep 15, 2026
Merged

Eduard van Valkenburg (eavanvalkenburg) merged 4 commits into
microsoft:mainfrom
ktz03:fix/mcp-no-duplicate-structured-content

Conversation

@ktz03

@ktz03 LI (ktz03) commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Motivation & Context

MCP CallToolResult may include both content and structuredContent. Many servers duplicate the same payload in both fields, which doubles model-visible tokens (#7866). Servers may also use the fields as complementary data. Maintainers asked for an explicit selection policy rather than a hard-coded preference.

Fixes #7866.

Description & Review Guide

  • What are the major changes? Add tool_result_content on MCPTool / transport subclasses: structured_first (default), content_first, content_only, structured_only, both. Host payload retention is unchanged. Custom parse_tool_results still overrides.
  • What is the impact of these changes? Callers can choose how model-visible text is selected when both fields are present; default prefers structured content per Python: [Bug]: MCP tools return double output if CallToolResult contains both content and structuredContent #7866 discussion.
  • What do you want reviewers to focus on? _parse_tool_result_from_mcp selection modes and the new unit coverage.

Related Issue

Fixes #7866

No other open PR targets this issue.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Fixes microsoft#7866. Prefer content blocks for model-visible tool results; structuredContent remains on the Host payload. Serialize structuredContent only when content is empty.
@ktz03

Copy link
Copy Markdown
Contributor Author

Fork CI for this PR may need a maintainer to Approve workflows before Python tests run (same as other community PRs). Happy to address review feedback once checks appear.

@agent-framework-automation agent-framework-automation Bot added the python Usage: [Issues, PRs], Target: Python label Sep 10, 2026
@ktz03
LI (ktz03) deployed to github-app-auth September 10, 2026 23:27 — with GitHub Actions Active

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Existing test expectations still require the old duplicate-output behavior.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Fixes duplicate MCP tool output when both content and structuredContent are present.

Changes:

  • Use structuredContent as model-visible content only when regular content is empty.
  • Preserve the complete structured payload for host transports.
  • Update parser regression coverage.
File summaries
File Summary
python/packages/core/agent_framework/_mcp.py Prevents duplicate structured content. Moderate finding (3 votes): remaining test expectations must be updated.
python/packages/core/tests/core/test_mcp.py Updates structured-content parsing expectations; several existing expectations still require the old duplicate behavior.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread python/packages/core/agent_framework/_mcp.py Outdated
Address Copilot review on microsoft#8274: update suites that still expected the old duplicated model-visible content shape.
@ktz03
LI (ktz03) deployed to github-app-auth September 11, 2026 05:10 — with GitHub Actions Active
@ktz03

Copy link
Copy Markdown
Contributor Author

Follow-up for Copilot review: aligned the three suites that still expected duplicated content+structuredContent items with the single-item contract. See 6d58210.

@FOWEPJF255

Copy link
Copy Markdown
Contributor

CI is green on this branch. The scoped MCP change (skip dumping structuredContent when content already has items) looks review-ready and matches the duplication report in #7866.

Happy to help with any follow-up nits if maintainers want a second look.

@Ricky-7-Yan Ricky-7-Yan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think this should hard-code content-first behavior yet.

The issue discussion identified two distinct cases: servers that duplicate the same value in content and structuredContent, and servers where the two fields are complementary. This implementation fixes the former by dropping structuredContent whenever any content block exists, but it also silently removes complementary structured data from the model-visible result.

That is the ambiguity maintainers called out in #7866, where the direction discussed was an explicit selection policy (structured first, content first, content only, structured only, or both), likely with a documented default. Could this expose that policy instead of choosing content-first globally? The tests should cover structured-only, content-only, equivalent-both, complementary-both, and retention of the full payload in HostMessageContent.

Replace hard-coded content-first behavior with structured_first/content_first/content_only/structured_only/both, defaulting to structured_first per microsoft#7866 discussion.
@ktz03

Copy link
Copy Markdown
Contributor Author

Ricky-7-Yan Addressed on the latest commit: replaced the hard-coded content-first behavior with an explicit tool_result_content policy on MCPTool / transport subclasses:

  • structured_first (default)
  • content_first
  • content_only
  • structured_only
  • both

This matches the selection options discussed on #7866. Host payload retention is unchanged. Tests cover structured-only, content-only, either-first, complementary both, and the null sentinel.

@ktz03

Copy link
Copy Markdown
Contributor Author

Ricky-7-Yan Eduard van Valkenburg (@eavanvalkenburg) Updated the PR body to the repository template. The tool_result_content selection policy is on dbec565 — please re-review when convenient.

@ktz03
LI (ktz03) deployed to github-app-auth September 14, 2026 13:56 — with GitHub Actions Active
@FOWEPJF255

Copy link
Copy Markdown
Contributor

Ricky-7-Yan LI (@ktz03) Thanks for landing the explicit tool_result_content policy on dbec565 鈥?that addresses the hard-coded content-first concern from the earlier review.

I checked the branch locally against the #7866 cases. The five modes + complementary-both coverage look good. Two small follow-ups that would fully match the review ask:

  1. equivalent-both 鈥?when content is a JSON serialization of the same structuredContent value (the common duplicate-server case)
  2. Host payload retention per mode 鈥?assert _mcp_tool_result_host_payload / HostMessageContent still keeps the full MCP result under every tool_result_content value

I have those two tests ready locally on top of this PR head. Happy to open a tiny follow-up PR into @ktz03's branch if useful.

Please re-review when convenient; the selection-policy direction looks right.

@FOWEPJF255

Copy link
Copy Markdown
Contributor

Follow-up test coverage for the review ask is ready here: ktz03#1

It adds equivalent-both and Host payload retention assertions for every tool_result_content mode on top of dbec565. LI (@ktz03) feel free to merge into this branch if useful.

@eavanvalkenburg

Copy link
Copy Markdown
Member

Thanks for the contribution. The approval-gated workflows completed, but the current head is not green: Python - Tests, Package Checks, Test Typing Checks, and Merge Gatekeeper failed, with additional cancellations across the Python test matrix. Could you please get the checks passing, then re-request review?

Add undocumented-param docstring entries on MCP transport constructors and assert tool result text before json.loads.
@ktz03
LI (ktz03) deployed to github-app-auth September 15, 2026 08:32 — with GitHub Actions Active
@ktz03

Copy link
Copy Markdown
Contributor Author

Addressed on c451f7ce:

  • Document tool_result_content on the MCP transport constructors (ruff undocumented-param).
  • Assert tool-result text is non-None before json.loads in the new mode tests.

Workflow runs for this head are currently waiting on approval-gated Actions.

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.

Reviewed the complete diff against main; no blocking issues found. All current checks are green.

Merged via the queue into microsoft:main with commit e584a9f Sep 15, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: MCP tools return double output if CallToolResult contains both content and structuredContent

5 participants