[BUG] MCP tool output not rendered in terminal when using native TextContent (fastmcp 3.x)

Resolved 💬 5 comments Opened May 7, 2026 by bkatkam123 Closed May 7, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

MCP tool output is not displayed in the terminal ⎿ block when the server returns native TextContent via fastmcp 3.x. The text is consumed silently by the model — users cannot see what the tool returned.

When the same text is returned as -> str (which fastmcp wraps in structuredContent: {"result": "..."} automatically), it IS shown — but as a JSON blob with escaped \n characters, not as formatted multi-line text.

Built-in tools like Bash render multi-line output with real newlines. MCP tools have no equivalent path.

What Should Happen?

MCP tool text output should be rendered in the terminal identically to Bash stdout — with real newlines, passed directly to the uK display component. Specifically:

  1. When CallToolResult.content contains TextContent items, their .text should be displayed in the ⎿ block with newlines preserved.
  2. When structuredContent.result is a string, it should be displayed as plain text, not JSON-serialized.

Error Messages/Logs

No errors. The behavior is silent — tool output simply doesn't appear.

Steps to Reproduce

Minimal repro — two tools, same text, different visibility:

#server.py — requires: pip install fastmcp==3.2.4
from fastmcp import FastMCP
from mcp.types import TextContent

mcp = FastMCP("repro")

@mcp.tool()
async def visible() -> str:
"""Visible but JSON-escaped."""
return "Line 1\n Line 2\n Line 3"

@mcp.tool()
async def hidden() -> TextContent:
"""Hidden from terminal entirely."""
return TextContent(type="text", text="Line 1\n Line 2\n Line 3")

if __name__ == "__main__":
mcp.run(transport="stdio")

Add to .mcp.json:
{"mcpServers": {"repro": {"command": "python3", "args": ["server.py"]}}}

  1. Call visible — terminal shows ⎿ {"result":"Line 1\n Line 2\n Line 3"} (JSON blob, newlines escaped)
  2. Call hidden — terminal shows nothing (output consumed by model, invisible to user)

Expected for both: terminal shows:
⎿ Line 1
Line 2
Line 3

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.116

Platform

AWS Bedrock

Operating System

Other Linux

Terminal/Shell

Other

Additional Information

Root cause from source analysis (cli.js):

The MCP tool renderer in C2B() has two paths:

// Path 1: structuredContent present → JSON.stringify → visible but escaped
if ("toolResult" in W) {
return String(W.toolResult); // loses newlines when toString'd from object
}

// Path 2: content array → mapped but displayed differently
if ("content" in W && Array.isArray(W.content)) {
let F = W.content.map((V) => H2B(V, B)).flat();
return F; // array → renderer called but output not shown in terminal
}

The MCP renderer renderToolResultMessage correctly handles TextContent[] arrays and passes .text to uK. However, in practice the output is not displayed when structuredContent is absent from the wire response.

For comparison, Bash's renderer (mu component) passes stdout directly to uK as a plain string — newlines render correctly.

Suggested fix: When the MCP tool result contains only TextContent items, extract and join their .text values and pass directly to uK as a plain string (same as Bash stdout), instead of JSON-serializing the response object.

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗