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 is stacked on #2134 and adds structured tool-error transport on top of its shared null/bash lifecycle and replay-safety foundation.

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
message_hash now differs for error vs success tool messages, which can change graph dedup for existing traces; provider-facing tool content also gains a failure prefix on chat wire output.

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

MCP harness: call_mcp now returns MCP isError; tool messages can carry a private _vf_is_error marker, and chat requests use extra_body.messages so that marker survives the localhost interception path.

Wire / providers: Chat serialization prefixes model-visible content with Tool execution failed: when not using internal mode; harness handoff writes initial messages with include_internal=True. Anthropic tool_result.is_error maps into the same field. apply_overrides re-normalizes messages that still carry the internal marker before the upstream request.

Traces: message_hash for tool nodes includes error vs success, so identical content from a failed vs successful tool call stays distinct after persistence and resume.

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

Note

Preserve MCP tool error outcomes across harness, transcript, and retry logic

  • Adds is_error to ToolMessage in types.py and propagates it through Anthropic and chat dialect parsing/serialization, including a _vf_is_error internal key and a human-visible Tool execution failed: prefix on wire output.
  • Introduces MCPTransportError and MCPDeliveryUnknownError in errors.py; the harness raises these instead of a generic HarnessError when the program emits a VF_MCP_ERROR= JSON marker.
  • Consolidates the null and bash harness programs into a single minimal/program.py with a --bash flag; both harnesses now write initial messages with include_internal=True to preserve error markers.
  • mcp_session in the minimal program classifies MCP failures as transient/non-transient and replay-safe/unsafe, and with_retry narrows retries to replay-safe transport failures only.
  • Episodes containing MCPDeliveryUnknownError are blocked from retry in retries.py unless explicitly opted in via retry config.
  • Risk: message_hash in graph.py now differs between error and success tool outcomes, which may invalidate cached hashes for existing tool messages.
📊 Macroscope summarized daab68f. 13 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@macroscopeapp

macroscopeapp Bot commented Jul 26, 2026

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

This PR introduces behavioral changes to tool error handling with a new field on a core type. An unresolved review comment from the file's primary author questions the fundamental design approach, which should be resolved before merging.

No code changes detected at daab68f. Prior analysis still applies.

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
@xeophon
xeophon force-pushed the agent/preserve-mcp-tool-errors branch from 254c065 to e056a0a Compare July 29, 2026 09:18
@xeophon
xeophon changed the base branch from agent/fix-mcp-session-lifecycle to main July 29, 2026 09:23
@xeophon
xeophon force-pushed the agent/preserve-mcp-tool-errors branch from e056a0a to 6b5814a Compare July 29, 2026 14:35
@xeophon
xeophon changed the base branch from main to agent/fix-mcp-session-lifecycle July 29, 2026 14:36
@xeophon
xeophon requested a review from mikasenghaas July 29, 2026 14:44
# `reasoning` (vLLM / Together / OpenRouter), `reasoning_content` (DeepSeek / Qwen / SGLang /
# Fireworks / Kimi), `reasoning_details` (OpenRouter / MiniMax).
REASONING_FIELDS = ("reasoning", "reasoning_content", "reasoning_details")
TOOL_ERROR_KEY = "_vf_is_error"

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.

do we need this only to prefix with TOOL_ERROR_PREFIX? can we not just let the error type bubble up as is. i think its going to be clear enough that the tool call failed. this is also what we do in e.g. rlm harness. e.g. my terminal is also not prefixing every error with Command failed:\n

@xeophon
xeophon force-pushed the agent/preserve-mcp-tool-errors branch from 6b5814a to daab68f Compare July 31, 2026 21:17
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

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