Skip to content

Fix: Render MCP tool output identically to built-in tools - #2

Open
Meldrey wants to merge 1 commit into
IIIIQIIII:mainfrom
Meldrey:fix/mcp-tool-output-rendering
Open

Fix: Render MCP tool output identically to built-in tools#2
Meldrey wants to merge 1 commit into
IIIIQIIII:mainfrom
Meldrey:fix/mcp-tool-output-rendering

Conversation

@Meldrey

@Meldrey Meldrey commented May 24, 2026

Copy link
Copy Markdown

Problem

MCP tool results are invisible to users. When Claude calls an MCP tool, the model receives the response, but users see no output block. Built-in tools (Bash, Edit, Read) display output normally.

This affects all MCP servers — plugins, project .mcp.json, everything.

Root Cause

Two gates in the rendering pipeline prevent MCP tool output from reaching users:

Gate 1: toolExecution.ts — separate code paths

// TOOD(hackyon): refactor so we don't have different experiences for MCP tools
if (!isMcpTool(tool)) {
  await addToolResult(toolOutput, mappedToolResultBlock)
}

addToolResult() stores tool results for UI rendering. It was only called for built-in tools. MCP tools were routed through a separate path that called addToolResult() later, without the pre-mapped block. The upstream dev left a typo'd TODO acknowledging this.

Gate 2: MCPTool.ts — outputSchema type mismatch

export const outputSchema = lazySchema(() =>
  z.string().describe("MCP tool execution result"),
)

MCP tool results arrive as content arrays (objects), not strings. UserToolSuccessMessage validates results against outputSchema before rendering — z.string().safeParse() on an array fails silently, returning null. The renderer never fires.

Fix

toolExecution.ts: Remove the isMcpTool guard. All tools call addToolResult() through the same path. Remove the duplicate MCP-only addToolResult() call downstream.

MCPTool.ts: Change outputSchema from z.string() to z.any() so validation accepts whatever the MCP server returns.

Result

MCP tool output renders in the same result block as built-in tools. Same component (OutputLine), same pipeline, same user experience.

Net change: +3 lines, -9 lines.

Two gates prevented MCP tool results from displaying to users:

1. toolExecution.ts: isMcpTool() guard routed MCP tools through a separate
   code path that skipped addToolResult(), the function that stores results
   for UI rendering. Built-in tools called it; MCP tools didn't. Removed
   the guard so all tools use the same rendering path. Also removed the
   duplicate MCP-only addToolResult() call that would have double-rendered.

2. MCPTool.ts: outputSchema was z.string(), but MCP tool results arrive as
   content arrays (objects), not strings. UserToolSuccessMessage validates
   results against outputSchema before rendering — string validation on an
   array silently returns null, killing the render. Changed to z.any().

The Anthropic dev left a guilty TODO on gate IIIIQIIII#1:
  'TOOD(hackyon): refactor so we don't have different experiences for MCP tools'

Now they don't.

Co-Authored-By: J5 <mod+ai_dev@arktechnwa.com>
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