ux(mcp): tool errors are invisible to the human — Claude Code renders isError results empty; surface them via plugin PostToolUse hook + stderr #70

Closed
opened 2026-07-14 21:46:41 +00:00 by hexajon · 0 comments
hexajon commented 2026-07-14 21:46:41 +00:00 (Migrated from codeberg.org)

Observed (2026-07-14, live session)

madtea returns tool failures in the MCP-spec-correct shape — CallToolResult with IsError=true and the message as a text content block (internal/mcp/register.go, arg_suggest.go) — and the model receives the text fine (e.g. API error 429: rate limited by server — still throttled after 1 attempt(s) and API error 404: The target couldn't be found.). But when I expand the MCP tool call in the Claude Code UI, the result pane is empty: the harness does not render isError result content. I only learn of failures if the failure is narrated in the conversation.

The wire shape is correct and verified — this is a harness display gap. But errors going dark is bad enough to work around on our side.

Ask (work around the harness, without breaking the spec)

  1. Plugin PostToolUse hook (primary): the plugin already ships hooks; add a PostToolUse hook matched to madtea's tool names that inspects tool_response, and on an error result emits a user-visible message (hook systemMessage output) carrying the tool name + error text (bounded, e.g. first ~500 chars). The model's view is unchanged; I see every madtea tool failure inline. Cover both tool-name prefixes (direct registration mcp__madtea__*, and the plugin-era prefix if any caches still carry it).
  2. stderr logging of tool errors (secondary, durable): the serve-diagnosability work (issue #4) already wires a structured logger to stderr + ~/.cache/madtea/serve.log; ensure every IsError tool result is logged there too (tool name, action, error text) so failures are greppable after the fact.
  3. Do NOT reach for MCP notifications/message as the workaround: the current MCP draft spec deprecates the Logging feature entirely (SEP-2577; suggested migration is stderr or OpenTelemetry), so building on it now would be adopting a deprecated feature. Keep IsError in-band exactly as-is — it stays the spec-correct model-facing channel.
  4. Upstream: this deserves a harness bug report (Claude Code: expanded MCP tool calls render isError results empty) — tracked here so the workaround can be retired when fixed. Reproduction: any failing madtea call, e.g. madt_issues action=get number=99999.

Acceptance

  • A failing madtea MCP call produces a visible line in my transcript (via the hook) naming the tool and the error, while the model-facing result keeps IsError=true + text unchanged.
  • Tool errors appear in serve's stderr/log file with tool name and action.
  • Hook tests cover: error result → systemMessage emitted; success result → silent; oversized error text → bounded.
  • No use of MCP Logging (notifications/message) for this path.
## Observed (2026-07-14, live session) madtea returns tool failures in the MCP-spec-correct shape — `CallToolResult` with `IsError=true` and the message as a text content block (`internal/mcp/register.go`, `arg_suggest.go`) — and the model receives the text fine (e.g. `API error 429: rate limited by server — still throttled after 1 attempt(s)` and `API error 404: The target couldn't be found.`). But when I expand the MCP tool call in the Claude Code UI, the result pane is **empty**: the harness does not render `isError` result content. I only learn of failures if the failure is narrated in the conversation. The wire shape is correct and verified — this is a harness display gap. But errors going dark is bad enough to work around on our side. ## Ask (work around the harness, without breaking the spec) 1. **Plugin PostToolUse hook** (primary): the plugin already ships hooks; add a PostToolUse hook matched to madtea's tool names that inspects `tool_response`, and on an error result emits a user-visible message (hook `systemMessage` output) carrying the tool name + error text (bounded, e.g. first ~500 chars). The model's view is unchanged; I see every madtea tool failure inline. Cover both tool-name prefixes (direct registration `mcp__madtea__*`, and the plugin-era prefix if any caches still carry it). 2. **stderr logging of tool errors** (secondary, durable): the serve-diagnosability work (issue #4) already wires a structured logger to stderr + `~/.cache/madtea/serve.log`; ensure every IsError tool result is logged there too (tool name, action, error text) so failures are greppable after the fact. 3. Do **NOT** reach for MCP `notifications/message` as the workaround: the current MCP draft spec **deprecates the Logging feature entirely** (SEP-2577; suggested migration is stderr or OpenTelemetry), so building on it now would be adopting a deprecated feature. Keep `IsError` in-band exactly as-is — it stays the spec-correct model-facing channel. 4. Upstream: this deserves a harness bug report (Claude Code: expanded MCP tool calls render `isError` results empty) — tracked here so the workaround can be retired when fixed. Reproduction: any failing madtea call, e.g. `madt_issues action=get number=99999`. ## Acceptance - A failing madtea MCP call produces a visible line in my transcript (via the hook) naming the tool and the error, while the model-facing result keeps `IsError=true` + text unchanged. - Tool errors appear in serve's stderr/log file with tool name and action. - Hook tests cover: error result → systemMessage emitted; success result → silent; oversized error text → bounded. - No use of MCP Logging (`notifications/message`) for this path.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sixfold-space/madtea#70
No description provided.