Skip to content

Pass through full CallToolResult to preserve binary content#21

Merged
rockfordlhotka merged 1 commit into
mainfrom
feature/pass-through-binary-content
Mar 11, 2026
Merged

Pass through full CallToolResult to preserve binary content#21
rockfordlhotka merged 1 commit into
mainfrom
feature/pass-through-binary-content

Conversation

@rockfordlhotka

Copy link
Copy Markdown
Member

Summary

  • ToolProxyHandler.InvokeAsync now returns Task<CallToolResult> instead of Task<string>, passing the downstream result through unchanged
  • ConsumerTools.InvokeTool returns Task<CallToolResult> — the MCP SDK natively supports this return type and passes it straight to the caller
  • REST endpoint (ServicesController) returns the full structured CallToolResult JSON

Problem

The previous implementation collapsed all tool results to a single string, silently dropping any non-text content blocks:

// Before — image/audio/embedded-resource blocks became useless placeholders
parts.Add($"[{block.Type ?? "unknown"} content block]");

This meant any downstream MCP server that generates or returns images, audio, or embedded resources would have that content lost in transit.

What flows through now

Content type Before After
TextContentBlock ✓ extracted as text ✓ preserved in content[]
ImageContentBlock [image content block] ✓ base64 + mimeType preserved
AudioContentBlock [audio content block] ✓ base64 + mimeType preserved
EmbeddedResourceContentBlock ✗ dropped ✓ preserved
IsError = true thrown as ToolExecutionException (HTTP 400) returned with isError flag intact

Breaking change

The REST POST /api/services/{name}/tools/{tool}/invoke response shape changes from { "result": "text" } to the full CallToolResult JSON ({ "content": [...], "isError": false }). Callers should check isError in the body rather than relying on HTTP 400 for tool errors.

Test plan

  • Build passes (dotnet build)
  • Existing tests pass (dotnet test)
  • Verify a text-only tool still works end-to-end via MCP client
  • Verify an image-returning tool passes the ImageContentBlock through to the caller

🤖 Generated with Claude Code

Previously invoke_tool collapsed all content to a string, silently
dropping image, audio, and embedded-resource blocks with placeholder
text. Now ToolProxyHandler returns CallToolResult directly and
ConsumerTools.InvokeTool returns Task<CallToolResult>, which the MCP
SDK passes through unchanged. The REST endpoint also returns the full
structured result so binary content survives both paths.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@rockfordlhotka
rockfordlhotka force-pushed the feature/pass-through-binary-content branch from fd59aa8 to ea49d52 Compare March 11, 2026 00:41
@rockfordlhotka
rockfordlhotka merged commit 0ef0da7 into main Mar 11, 2026
1 check passed
@rockfordlhotka
rockfordlhotka deleted the feature/pass-through-binary-content branch March 11, 2026 00:41
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