Skip to content

feat: include structuredContent as JSON text in every tool result - #62

Merged
marselsel merged 1 commit into
mainfrom
feat/json-text-fallback
Sep 22, 2026
Merged

marselsel merged 1 commit into
mainfrom
feat/json-text-fallback

Conversation

@marselsel

Copy link
Copy Markdown
Owner

Part 4 of 8 in the MCP best-practices stack. Stacked on #61, so merge in order.

Why

  • The spec (2026-07-28, server/tools): "For backwards compatibility, a tool that returns structured content SHOULD also return the serialized JSON in a TextContent block."
  • This server: each tool's text was a one-line summary ("Invoice X retrieved."). A client that shows the model only content never saw the document itself.
  • Anthropic's tool-design reference says: "Always include the text fallback — not all hosts read structuredContent yet." A third-party report (Claude Custom Connector cannot see structured MCP tool results yyjeqhc/webcodex#456, 2026-09-14) describes a Claude custom connector passing the model only content[0].text. Claude Code is reported to forward structuredContent, so the added text does not double what the model sees there.
  • The SDK serializes only non-object structuredContent itself, so object payloads need this.

Changes

  • withJsonText in shared.ts: appends JSON.stringify(structuredContent) (compact) as the last text block. Error results and non-object payloads pass through untouched.
  • Applied once: registerTools wraps every handler, instead of changing about 40 handlers.

Verification

  • Unit tests cover:
    • the order of blocks
    • binary results, where the embedded file is kept and the metadata JSON comes after it
    • string or missing content
    • error and scalar results, which are left untouched
  • A wiring test calls a real handler through registerTools.
  • Over the wire, get-document-link returned the summary block followed by the JSON block.
  • npm test passes all 475 tests.

The spec says a tool returning structured content SHOULD also return it
serialized in a TextContent block. Every tool's text here was a one-line
summary, so a client that shows the model only `content` never saw the
data. Wrapped once in registerTools; error results and non-object
structuredContent (which the SDK serializes itself) are untouched.
Verified over the wire with get-document-link.
@marselsel
marselsel changed the base branch from fix/plain-403-for-refused-domain to main September 22, 2026 21:57
@marselsel
marselsel merged commit 417dc18 into main Sep 22, 2026
2 checks passed
@marselsel
marselsel deleted the feat/json-text-fallback branch September 22, 2026 21:57
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.

1 participant