Skip to content

Preserve MCP tool error outcomes - #2138

Open
xeophon wants to merge 3 commits into
agent/fix-mcp-session-lifecyclefrom
agent/preserve-mcp-tool-errors
Open

Preserve MCP tool error outcomes#2138
xeophon wants to merge 3 commits into
agent/fix-mcp-session-lifecyclefrom
agent/preserve-mcp-tool-errors

Conversation

@xeophon

@xeophon xeophon commented Jul 26, 2026

Copy link
Copy Markdown
Member

Overview

Preserves MCP tool-declared outcome semantics across harness execution, typed messages, traces, replay, and provider conversion.

This PR follows #2134 in a native GitHub pull request stack. #2134 owns the shared null/bash MCP client and its session lifecycle and replay-safety behavior.

Details

MCP CallToolResult contains both model-visible content and an isError outcome. The shared MCP client now returns both values, and the harness program carries the outcome into ToolMessage.is_error.

The typed outcome participates in graph identity and trace serialization, so success and failure results with identical content remain distinguishable after persistence and resume.

For chat-compatible providers, the localhost harness handoff uses a private marker that is removed before the provider request while adding a readable failure label to the model-visible content. Anthropic-native tool_result.is_error maps directly into the same typed representation.


Note

Medium Risk
Changes what chat-compatible models see for failed tools (failure prefix) and eval override message rewriting; trace hashing changes can fork dedup for identical-content success vs error results.

Overview
Adds ToolMessage.is_error so MCP and harness tool failures are typed end-to-end instead of living only in message text.

The shared minimal harness program propagates CallToolResult.isError into tool messages via a private _vf_is_error wire flag (and extra_body on chat completions so interception keeps that metadata). Bash/null harnesses serialize initial prompts with include_internal=True so resume handoff does not strip the flag.

Chat dialect maps the private key into is_error on parse; on export it either keeps _vf_is_error internally or prefixes model-visible content with Tool execution failed: before upstream LLM calls. apply_overrides re-wires flagged messages so providers never see the private field. Anthropic maps native tool_result.is_error into the same field.

Trace graph message_hash treats error vs success tool results as distinct even when content matches, so persistence and replay stay correct.

Reviewed by Cursor Bugbot for commit 05ea449. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Preserve MCP tool error outcomes through the verifier pipeline

  • Adds is_error: bool to ToolMessage in types.py and propagates it through parsing, hashing, and wire serialization.
  • program.py captures result.isError from MCP tool calls and tags the tool message with a private _vf_is_error key.
  • message_to_wire prefixes error tool content with "Tool execution failed:\n" when serializing for public wire format; the private flag is preserved instead when include_internal=True.
  • ChatDialect.apply_overrides strips internal error keys before forwarding requests to the upstream chat/completions endpoint.
  • Bash and Null harnesses now write initial messages with include_internal=True so private error flags survive into the child program.

Macroscope summarized 254c065.

@macroscopeapp

macroscopeapp Bot commented Jul 26, 2026

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

This PR adds tool error tracking across multiple components with an unresolved design question from another reviewer questioning whether this departure from standard API is necessary. The architectural concern should be resolved before merging.

You can customize Macroscope's approvability policy. Learn more.

Comment thread verifiers/v1/harnesses/minimal/program.py
@xeophon
xeophon force-pushed the agent/preserve-mcp-tool-errors branch from 47ea576 to 1433270 Compare July 27, 2026 13:30
Comment thread verifiers/v1/types.py
content: MessageContent
name: str | None = None
"""Needed by templates such as Harmony when bridge tails omit the issuing call."""
is_error: bool = False

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this departure from standard api? can we not just put the error in the content and call it a day?

@xeophon
xeophon force-pushed the agent/preserve-mcp-tool-errors branch 2 times, most recently from df1ae39 to 05ea449 Compare July 28, 2026 18:54

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 05ea449. Configure here.

Comment thread verifiers/v1/dialects/chat.py
@xeophon
xeophon force-pushed the agent/preserve-mcp-tool-errors branch from 05ea449 to 254c065 Compare July 28, 2026 20:55
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.

2 participants