Skip to content

feat: add structured-only tool results that skip the text mirror#1046

Open
olaservo wants to merge 2 commits into
modelcontextprotocol:mainfrom
olaservo:structured-content-mirror-optout
Open

feat: add structured-only tool results that skip the text mirror#1046
olaservo wants to merge 2 commits into
modelcontextprotocol:mainfrom
olaservo:structured-content-mirror-optout

Conversation

@olaservo

@olaservo olaservo commented Jul 24, 2026

Copy link
Copy Markdown
Member

CallToolResult::structured and structured_error always mirror the structured value into content as serialized text, doubling large payloads. The spec marks that mirror as a SHOULD, not a MUST.

This PR makes the mirror optional. Opting out is explicit, since the mirror is the compatibility path for clients that do not read structuredContent:

  • CallToolResult::structured_only / structured_error_only — constructors that leave content empty
  • StructuredOnly<T> — return-type wrapper, a sibling of Json<T>, for #[tool] methods; the tool macro derives outputSchema from it the same way it does for Json<T>
  • CallToolResult::with_content — replaces the content blocks, so a structured result can pair structuredContent with a custom rendering (e.g. a short text summary) instead of the verbatim mirror; the two fields need not carry the same data

CallToolResult::structured and structured_error always mirror the
structured value into content as serialized text, doubling large
payloads. Add explicit opt-outs, never the default since the mirror is
the only compatibility path for clients that do not read
structuredContent:

- CallToolResult::structured_only / structured_error_only constructors
  that leave content empty
- StructuredOnly<T> return-type wrapper, a sibling of Json<T>, for use
  with #[tool] methods; the tool macro derives outputSchema from it the
  same way it does for Json<T>

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@olaservo
olaservo requested a review from a team as a code owner July 24, 2026 03:21
@github-actions github-actions Bot added T-test Testing related changes T-core Core library changes T-handler Handler implementation changes T-macros Macro changes labels Jul 24, 2026
…endering

content and structuredContent need not match: with_content replaces the
content blocks on any result, so a structured_only result can carry a
short text summary while structuredContent holds the full value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alexhancock

Copy link
Copy Markdown
Contributor

Thanks for posting this @olaservo I agree if we want to make an opt-out path explicit and do it by default this looks like a good way to do it!

Given the spec says

For backwards compatibility, a tool that returns structured content SHOULD also return the serialized JSON in a TextContent block.

I'd ask the question how often is this backwards compatibility relevant for callers out there in the ecosystem? How long has it been the case that tools could have relevant info in structured content.

Wondering when it would be reasonable to drop the automatic mirroring all together from an SDK perspective...

@DaleSeo

DaleSeo commented Jul 24, 2026

Copy link
Copy Markdown
Member

@alexhancock I was wondering the same thing. One thing we should consider here is that the mirror itself is only a month old. structured shipped without it for about a year until #927 added it, so I doubt many servers depend on it yet.

Given that, and with 3.0 GA targeted for 7/28, what do we think about flipping the default in this PR instead of adding an opt-out? structured and Json<T> would stop mirroring, and the mirror becomes the explicit opt-in. If we add _only variants now and flip the default later anyway, we end up shipping API that's legacy the day it stabilizes. FWIW, this is also where the TypeScript SDK already is. It never had automatic mirroring, and its docs just show tool authors including the text block themselves.

It would also make the PR smaller since the second wrapper and the macro change wouldn't be needed. Happy to help with the rework if that makes sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-core Core library changes T-handler Handler implementation changes T-macros Macro changes T-test Testing related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants