Skip to content

fix(tools): put body inside StructuredContent so it reaches the consumer#15

Merged
WiktorStarczewski merged 1 commit intomainfrom
wiktor/body-in-structured
Apr 27, 2026
Merged

fix(tools): put body inside StructuredContent so it reaches the consumer#15
WiktorStarczewski merged 1 commit intomainfrom
wiktor/body-in-structured

Conversation

@WiktorStarczewski
Copy link
Copy Markdown
Owner

Summary

Follow-up to #14, which turned out to be only half the fix.

PR #14 made AskPeerClaudeOutput / SendPeerMessageOutput empty on the theory that MCP consumers preferring StructuredContent would fall back to Content when StructuredContent was just {}. Empirical test against Claude Code showed they don't: the structured channel is treated as the source of truth whenever it's present, even if empty, and Content is silently dropped. Net effect: every successful ask_peer_claude returned a literal {} to the calling model — body invisible.

This PR puts the body inside StructuredContent (where the consumer actually looks) and keeps it in Content as a redundant fallback for any consumer that does the opposite. The body is now reachable through whichever channel the consumer prefers.

Wire-shape changes

AskPeerClaudeOutput / SendPeerMessageOutput:

 type AskPeerClaudeOutput struct {
+    Body          string `json:"body,omitempty"`
+    TurnCount     int    `json:"turnCount,omitempty"`
+    ToolCallCount int    `json:"toolCallCount,omitempty"`
+    StopReason    string `json:"stopReason,omitempty"`
+    ElapsedMs     int64  `json:"elapsedMs,omitempty"`
+    ErrorSummary  string `json:"errorSummary,omitempty"`
 }

ReadSessionOutput gains the same body field (only populated for format="full"; format="json" still omits it).

The transcriptHeaderLine / withTranscriptHeader helpers introduced by #14 are removed — no longer needed now that metadata rides on its own structured fields.

Tests

  • Existing happy-path tests for ask_peer_claude / send_peer_message updated to decode the body from StructuredContent rather than the (now-removed) inline header.
  • New regression test TestAskPeerClaude_BodyReachesStructuredOnlyConsumer decodes via the structured channel only and asserts the body is non-empty — the actual rendering rule on Claude Code-class clients.
  • TestReadSession_ReturnsMarkdownAndMetadata extended to verify meta.Body carries the rendered markdown.
  • TestReadToolResult_NoStructuredContent is not affected — that tool already chose to drop StructuredContent rather than carry a body in it; whether to fold it into the same pattern is a separate decision (its primary use is large-sidecar reads, where the duplication might matter for size).

Version bump

0.3.1-dev → 0.3.2-dev. Same diagnostic motivation as last time: lets a calling Claude tell, via get_peer_info, whether the connected peer has the actual fix.

Test plan

Follow-up to #14, which turned out to be only half the fix.

PR #14 emptied AskPeerClaudeOutput / SendPeerMessageOutput on the theory
that consumers preferring StructuredContent would fall back to Content
when StructuredContent was just `{}`. Empirical test against Claude Code
showed they don't: the structured channel is treated as the source of
truth whenever it's *present*, even if empty, and Content is silently
dropped. Net effect: every successful `ask_peer_claude` returned a
literal "{}" to the calling model — body invisible.

This PR puts the body inside StructuredContent (where the consumer
actually looks) and keeps it in Content as a redundant fallback for any
consumer that does the opposite:

- AskPeerClaudeOutput.Body / .TurnCount / .ToolCallCount /
  .StopReason / .ElapsedMs / .ErrorSummary  — restored as separate
  structured fields, with `Body` carrying the markdown transcript.
- SendPeerMessageOutput aliased to AskPeerClaudeOutput (same surface).
- ReadSessionOutput grows a `body` field for format="full"; format=
  "json" continues to omit it.
- The transcriptHeaderLine / withTranscriptHeader helpers introduced by
  #14 are removed — no longer needed now that metadata rides on its own
  structured fields.
- New regression test `TestAskPeerClaude_BodyReachesStructuredOnlyConsumer`
  decodes via the structured channel only and asserts body is non-empty.
  Existing happy-path tests updated to read body from structured rather
  than from the (now-removed) inline header.
- Version bumped 0.3.1-dev → 0.3.2-dev so `get_peer_info` lets a
  calling Claude tell whether the connected peer has the actual fix.

The IsError signaling, errResult helper, and all other tools are
unchanged.
@WiktorStarczewski WiktorStarczewski merged commit 1c90dee into main Apr 27, 2026
2 checks passed
@WiktorStarczewski WiktorStarczewski deleted the wiktor/body-in-structured branch April 27, 2026 16:16
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