mcp tool call with error handling - #5894
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughMCP execution and protocol failures now set ChangesTool error propagation
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant MCPExecution
participant ChatToolMessage
participant ChatToResponses
participant ResponsesToChat
MCPExecution->>ChatToolMessage: Set IsError for failed execution
ChatToolMessage->>ChatToResponses: Convert IsError=true
ChatToResponses->>ResponsesToChat: Preserve incomplete status
ResponsesToChat->>ChatToolMessage: Restore IsError=true
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@core/mcp/codemode/starlark/executecode.go`:
- Around line 199-201: Update the result.Errors != nil branch in the sandbox
execution flow to set executionSuccess = false before createToolResponseMessage
is called, ensuring failed executions are marked as tool failures. Add a
regression test covering an execution result with non-nil Errors and verify the
response is flagged as unsuccessful.
- Around line 556-560: The extractResultFromChatMessage path must propagate
protocol-level tool failures by checking ChatToolMessage.IsError and returning
an error when it is set, including for responses without an “Error: ” text
prefix. Preserve the existing successful result handling when the flag is false.
In `@core/schemas/mux.go`:
- Around line 851-858: Update the Responses-to-Chat allocation guard in
core/schemas/mux.go#L851-L858 to create ChatToolMessage when CallID is present
or when ResponsesToolMessage.Error is non-empty or Status is "incomplete",
preserving IsError for ID-less failures. Add corresponding error-string and
incomplete-status fixtures without CallID in
core/schemas/toolerrormux_test.go#L51-L114, asserting IsError is true for both.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 996f9d94-1a15-459e-bcfb-59e193eafdf7
📒 Files selected for processing (15)
core/changelog.mdcore/mcp/agent.gocore/mcp/codemode/starlark/executecode.gocore/mcp/codemode/starlark/getdocs.gocore/mcp/codemode/starlark/listfiles.gocore/mcp/codemode/starlark/readfile.gocore/mcp/codemode/starlark/toolerrormarker_test.gocore/mcp/codemode/starlark/utils.gocore/mcp/toolerrormarker_test.gocore/mcp/toolmanager.gocore/mcp/utils.gocore/schemas/chatcompletions.gocore/schemas/mux.gocore/schemas/toolerrormux_test.gocore/schemas/toolmessageiserror_test.go
e68d6ff to
413be75
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
core/mcp/toolerrormarker_test.go (1)
10-18: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse
schemas.Ptrfor fixture pointers.Replace
&idand&namewithschemas.Ptr(id)andschemas.Ptr(name). This follows the repository pointer-helper convention for simple values.Based on learnings, “prefer using bifrost.Ptr() to create pointers instead of the address operator (&) … including test utilities.”
Proposed change
- ID: &id, + ID: schemas.Ptr(id), Function: schemas.ChatAssistantMessageToolCallFunction{ - Name: &name, + Name: schemas.Ptr(name),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/mcp/toolerrormarker_test.go` around lines 10 - 18, Update the testToolCall fixture to use schemas.Ptr(id) for ID and schemas.Ptr(name) for Function.Name instead of address operators, preserving the existing ChatAssistantMessageToolCall values and repository pointer-helper convention.Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@core/providers/bedrock/toolerroringress_test.go`:
- Around line 110-115: Update the success-conversion assertions in the test to
first locate a non-nil ChatToolMessage within chatReq.Input and fail if none
exists, then assert that the located message’s IsError field is nil. Ensure the
test distinguishes a missing successful tool message from one that is present
but incorrectly marked as an error.
---
Nitpick comments:
In `@core/mcp/toolerrormarker_test.go`:
- Around line 10-18: Update the testToolCall fixture to use schemas.Ptr(id) for
ID and schemas.Ptr(name) for Function.Name instead of address operators,
preserving the existing ChatAssistantMessageToolCall values and repository
pointer-helper convention.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c480247c-da45-454b-8388-b79b8f553fee
📒 Files selected for processing (16)
core/changelog.mdcore/mcp/agent.gocore/mcp/codemode/starlark/executecode.gocore/mcp/codemode/starlark/getdocs.gocore/mcp/codemode/starlark/listfiles.gocore/mcp/codemode/starlark/readfile.gocore/mcp/codemode/starlark/toolerrormarker_test.gocore/mcp/codemode/starlark/utils.gocore/mcp/toolerrormarker_test.gocore/mcp/toolmanager.gocore/mcp/utils.gocore/providers/bedrock/toolerroringress_test.gocore/schemas/chatcompletions.gocore/schemas/mux.gocore/schemas/toolerrormux_test.gocore/schemas/toolmessageiserror_test.go
🚧 Files skipped from review as they are similar to previous changes (14)
- core/mcp/toolmanager.go
- core/mcp/codemode/starlark/listfiles.go
- core/mcp/codemode/starlark/toolerrormarker_test.go
- core/mcp/codemode/starlark/getdocs.go
- core/schemas/mux.go
- core/changelog.md
- core/mcp/agent.go
- core/mcp/codemode/starlark/executecode.go
- core/schemas/toolmessageiserror_test.go
- core/schemas/chatcompletions.go
- core/mcp/codemode/starlark/readfile.go
- core/schemas/toolerrormux_test.go
- core/mcp/codemode/starlark/utils.go
- core/mcp/utils.go
413be75 to
266a3c4
Compare
cee6003 to
26d84d0
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
26d84d0 to
e615503
Compare
266a3c4 to
a8ee2d2
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
a8ee2d2 to
c8bbad0
Compare
e615503 to
e8604f9
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
e8604f9 to
e8b5de0
Compare
c8bbad0 to
0e91527
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
Merge activity
|
The base branch was changed.
## Summary Failed MCP tool executions were being replayed to the model as successful results. The `is_error` / `IsError` marker was either never set, silently dropped during JSON unmarshalling, or lost when converting between the chat completions and Responses surfaces. This PR closes all three gaps so that Anthropic `tool_result.is_error`, Bedrock `toolResult.status`, and the Responses surface `status: "incomplete"` all reflect the actual outcome of a tool call. ## Changes - **Agent loop (`mcp/agent.go`):** `createToolResultMessage` now sets `IsError: true` on the returned `ChatToolMessage` when the execution error is non-nil, so a tool call that bifrost itself watched fail is no longer reported to the model as a success. - **MCP tool manager (`mcp/toolmanager.go`, `mcp/utils.go`):** `createToolResponseMessage` gains an `isError bool` parameter. `executeToolInternal` passes `toolResponse.IsError` through, carrying the MCP protocol's own failure signal (`mcp.CallToolResult.IsError`) which was previously discarded. - **CodeMode (`mcp/codemode/starlark/`):** All call sites of `createToolResponseMessage` are updated to pass an explicit `isError` flag. Lookup failures (unknown server, unknown tool, ambiguous filename), sandbox execution failures, and MCP protocol-level errors are all marked `true`; successful responses and informational listings are marked `false`. - **Unmarshal gate (`schemas/chatcompletions.go`):** The `ChatMessage.UnmarshalJSON` reattach gate previously keyed solely on `ToolCallID`, which silently dropped `ChatToolMessage` — and with it `IsError` — for any tool message that carried the error marker without a `tool_call_id`. The gate now also triggers on `IsError != nil`. - **Chat ↔ Responses mux (`schemas/mux.go`):** `ToResponsesMessages` maps `IsError: true` to `Status: "incomplete"` (the Responses surface equivalent already read back as `is_error` by the Anthropic Responses converter). `ToChatMessages` maps both `ResponsesToolMessage.Error` (non-empty string) and `Status: "incomplete"` back to `IsError: true`, so the marker survives a full chat → Responses → chat round trip. - **Tests:** New test files cover the agent-loop marker (`mcp/toolerrormarker_test.go`), the CodeMode marker (`mcp/codemode/starlark/toolerrormarker_test.go`), the unmarshal gate edge case (`schemas/toolmessageiserror_test.go`), and both directions of the mux conversion including a round-trip regression (`schemas/toolerrormux_test.go`). ## Type of change - [x] Bug fix ## Affected areas - [x] Core (Go) - [x] Providers/Integrations ## How to test ```sh go test ./core/... ``` Key test targets: - `core/mcp` — `TestCreateToolResultMessageMarksError`, `TestCreateToolResponseMessageMarksError` - `core/mcp/codemode/starlark` — `TestCreateToolResponseMessageMarksError` - `core/schemas` — `TestChatMessageIsErrorWithoutToolCallID`, `TestChatToResponsesCarriesToolError`, `TestResponsesToChatCarriesToolError`, `TestToolErrorSurvivesSurfaceRoundTrip` ## Breaking changes - [ ] Yes - [x] No ## Related issues Closes #5890 ## Security considerations None. Changes are confined to how tool result metadata is propagated between internal surfaces and providers. ## Checklist - [x] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [x] I verified the CI pipeline passes locally if applicable
## Summary Failed MCP tool executions were being replayed to the model as successful results. The `is_error` / `IsError` marker was either never set, silently dropped during JSON unmarshalling, or lost when converting between the chat completions and Responses surfaces. This PR closes all three gaps so that Anthropic `tool_result.is_error`, Bedrock `toolResult.status`, and the Responses surface `status: "incomplete"` all reflect the actual outcome of a tool call. ## Changes - **Agent loop (`mcp/agent.go`):** `createToolResultMessage` now sets `IsError: true` on the returned `ChatToolMessage` when the execution error is non-nil, so a tool call that bifrost itself watched fail is no longer reported to the model as a success. - **MCP tool manager (`mcp/toolmanager.go`, `mcp/utils.go`):** `createToolResponseMessage` gains an `isError bool` parameter. `executeToolInternal` passes `toolResponse.IsError` through, carrying the MCP protocol's own failure signal (`mcp.CallToolResult.IsError`) which was previously discarded. - **CodeMode (`mcp/codemode/starlark/`):** All call sites of `createToolResponseMessage` are updated to pass an explicit `isError` flag. Lookup failures (unknown server, unknown tool, ambiguous filename), sandbox execution failures, and MCP protocol-level errors are all marked `true`; successful responses and informational listings are marked `false`. - **Unmarshal gate (`schemas/chatcompletions.go`):** The `ChatMessage.UnmarshalJSON` reattach gate previously keyed solely on `ToolCallID`, which silently dropped `ChatToolMessage` — and with it `IsError` — for any tool message that carried the error marker without a `tool_call_id`. The gate now also triggers on `IsError != nil`. - **Chat ↔ Responses mux (`schemas/mux.go`):** `ToResponsesMessages` maps `IsError: true` to `Status: "incomplete"` (the Responses surface equivalent already read back as `is_error` by the Anthropic Responses converter). `ToChatMessages` maps both `ResponsesToolMessage.Error` (non-empty string) and `Status: "incomplete"` back to `IsError: true`, so the marker survives a full chat → Responses → chat round trip. - **Tests:** New test files cover the agent-loop marker (`mcp/toolerrormarker_test.go`), the CodeMode marker (`mcp/codemode/starlark/toolerrormarker_test.go`), the unmarshal gate edge case (`schemas/toolmessageiserror_test.go`), and both directions of the mux conversion including a round-trip regression (`schemas/toolerrormux_test.go`). ## Type of change - [x] Bug fix ## Affected areas - [x] Core (Go) - [x] Providers/Integrations ## How to test ```sh go test ./core/... ``` Key test targets: - `core/mcp` — `TestCreateToolResultMessageMarksError`, `TestCreateToolResponseMessageMarksError` - `core/mcp/codemode/starlark` — `TestCreateToolResponseMessageMarksError` - `core/schemas` — `TestChatMessageIsErrorWithoutToolCallID`, `TestChatToResponsesCarriesToolError`, `TestResponsesToChatCarriesToolError`, `TestToolErrorSurvivesSurfaceRoundTrip` ## Breaking changes - [ ] Yes - [x] No ## Related issues Closes maximhq#5890 ## Security considerations None. Changes are confined to how tool result metadata is propagated between internal surfaces and providers. ## Checklist - [x] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [x] I verified the CI pipeline passes locally if applicable
## Summary Failed MCP tool executions were being replayed to the model as successful results. The `is_error` / `IsError` marker was either never set, silently dropped during JSON unmarshalling, or lost when converting between the chat completions and Responses surfaces. This PR closes all three gaps so that Anthropic `tool_result.is_error`, Bedrock `toolResult.status`, and the Responses surface `status: "incomplete"` all reflect the actual outcome of a tool call. ## Changes - **Agent loop (`mcp/agent.go`):** `createToolResultMessage` now sets `IsError: true` on the returned `ChatToolMessage` when the execution error is non-nil, so a tool call that bifrost itself watched fail is no longer reported to the model as a success. - **MCP tool manager (`mcp/toolmanager.go`, `mcp/utils.go`):** `createToolResponseMessage` gains an `isError bool` parameter. `executeToolInternal` passes `toolResponse.IsError` through, carrying the MCP protocol's own failure signal (`mcp.CallToolResult.IsError`) which was previously discarded. - **CodeMode (`mcp/codemode/starlark/`):** All call sites of `createToolResponseMessage` are updated to pass an explicit `isError` flag. Lookup failures (unknown server, unknown tool, ambiguous filename), sandbox execution failures, and MCP protocol-level errors are all marked `true`; successful responses and informational listings are marked `false`. - **Unmarshal gate (`schemas/chatcompletions.go`):** The `ChatMessage.UnmarshalJSON` reattach gate previously keyed solely on `ToolCallID`, which silently dropped `ChatToolMessage` — and with it `IsError` — for any tool message that carried the error marker without a `tool_call_id`. The gate now also triggers on `IsError != nil`. - **Chat ↔ Responses mux (`schemas/mux.go`):** `ToResponsesMessages` maps `IsError: true` to `Status: "incomplete"` (the Responses surface equivalent already read back as `is_error` by the Anthropic Responses converter). `ToChatMessages` maps both `ResponsesToolMessage.Error` (non-empty string) and `Status: "incomplete"` back to `IsError: true`, so the marker survives a full chat → Responses → chat round trip. - **Tests:** New test files cover the agent-loop marker (`mcp/toolerrormarker_test.go`), the CodeMode marker (`mcp/codemode/starlark/toolerrormarker_test.go`), the unmarshal gate edge case (`schemas/toolmessageiserror_test.go`), and both directions of the mux conversion including a round-trip regression (`schemas/toolerrormux_test.go`). ## Type of change - [x] Bug fix ## Affected areas - [x] Core (Go) - [x] Providers/Integrations ## How to test ```sh go test ./core/... ``` Key test targets: - `core/mcp` — `TestCreateToolResultMessageMarksError`, `TestCreateToolResponseMessageMarksError` - `core/mcp/codemode/starlark` — `TestCreateToolResponseMessageMarksError` - `core/schemas` — `TestChatMessageIsErrorWithoutToolCallID`, `TestChatToResponsesCarriesToolError`, `TestResponsesToChatCarriesToolError`, `TestToolErrorSurvivesSurfaceRoundTrip` ## Breaking changes - [ ] Yes - [x] No ## Related issues Closes maximhq#5890 ## Security considerations None. Changes are confined to how tool result metadata is propagated between internal surfaces and providers. ## Checklist - [x] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [x] I verified the CI pipeline passes locally if applicable
## Summary Failed MCP tool executions were being replayed to the model as successful results. The `is_error` / `IsError` marker was either never set, silently dropped during JSON unmarshalling, or lost when converting between the chat completions and Responses surfaces. This PR closes all three gaps so that Anthropic `tool_result.is_error`, Bedrock `toolResult.status`, and the Responses surface `status: "incomplete"` all reflect the actual outcome of a tool call. ## Changes - **Agent loop (`mcp/agent.go`):** `createToolResultMessage` now sets `IsError: true` on the returned `ChatToolMessage` when the execution error is non-nil, so a tool call that bifrost itself watched fail is no longer reported to the model as a success. - **MCP tool manager (`mcp/toolmanager.go`, `mcp/utils.go`):** `createToolResponseMessage` gains an `isError bool` parameter. `executeToolInternal` passes `toolResponse.IsError` through, carrying the MCP protocol's own failure signal (`mcp.CallToolResult.IsError`) which was previously discarded. - **CodeMode (`mcp/codemode/starlark/`):** All call sites of `createToolResponseMessage` are updated to pass an explicit `isError` flag. Lookup failures (unknown server, unknown tool, ambiguous filename), sandbox execution failures, and MCP protocol-level errors are all marked `true`; successful responses and informational listings are marked `false`. - **Unmarshal gate (`schemas/chatcompletions.go`):** The `ChatMessage.UnmarshalJSON` reattach gate previously keyed solely on `ToolCallID`, which silently dropped `ChatToolMessage` — and with it `IsError` — for any tool message that carried the error marker without a `tool_call_id`. The gate now also triggers on `IsError != nil`. - **Chat ↔ Responses mux (`schemas/mux.go`):** `ToResponsesMessages` maps `IsError: true` to `Status: "incomplete"` (the Responses surface equivalent already read back as `is_error` by the Anthropic Responses converter). `ToChatMessages` maps both `ResponsesToolMessage.Error` (non-empty string) and `Status: "incomplete"` back to `IsError: true`, so the marker survives a full chat → Responses → chat round trip. - **Tests:** New test files cover the agent-loop marker (`mcp/toolerrormarker_test.go`), the CodeMode marker (`mcp/codemode/starlark/toolerrormarker_test.go`), the unmarshal gate edge case (`schemas/toolmessageiserror_test.go`), and both directions of the mux conversion including a round-trip regression (`schemas/toolerrormux_test.go`). ## Type of change - [x] Bug fix ## Affected areas - [x] Core (Go) - [x] Providers/Integrations ## How to test ```sh go test ./core/... ``` Key test targets: - `core/mcp` — `TestCreateToolResultMessageMarksError`, `TestCreateToolResponseMessageMarksError` - `core/mcp/codemode/starlark` — `TestCreateToolResponseMessageMarksError` - `core/schemas` — `TestChatMessageIsErrorWithoutToolCallID`, `TestChatToResponsesCarriesToolError`, `TestResponsesToChatCarriesToolError`, `TestToolErrorSurvivesSurfaceRoundTrip` ## Breaking changes - [ ] Yes - [x] No ## Related issues Closes maximhq#5890 ## Security considerations None. Changes are confined to how tool result metadata is propagated between internal surfaces and providers. ## Checklist - [x] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [x] I verified the CI pipeline passes locally if applicable

Summary
Failed MCP tool executions were being replayed to the model as successful results. The
is_error/IsErrormarker was either never set, silently dropped during JSON unmarshalling, or lost when converting between the chat completions and Responses surfaces. This PR closes all three gaps so that Anthropictool_result.is_error, BedrocktoolResult.status, and the Responses surfacestatus: "incomplete"all reflect the actual outcome of a tool call.Changes
mcp/agent.go):createToolResultMessagenow setsIsError: trueon the returnedChatToolMessagewhen the execution error is non-nil, so a tool call that bifrost itself watched fail is no longer reported to the model as a success.mcp/toolmanager.go,mcp/utils.go):createToolResponseMessagegains anisError boolparameter.executeToolInternalpassestoolResponse.IsErrorthrough, carrying the MCP protocol's own failure signal (mcp.CallToolResult.IsError) which was previously discarded.mcp/codemode/starlark/): All call sites ofcreateToolResponseMessageare updated to pass an explicitisErrorflag. Lookup failures (unknown server, unknown tool, ambiguous filename), sandbox execution failures, and MCP protocol-level errors are all markedtrue; successful responses and informational listings are markedfalse.schemas/chatcompletions.go): TheChatMessage.UnmarshalJSONreattach gate previously keyed solely onToolCallID, which silently droppedChatToolMessage— and with itIsError— for any tool message that carried the error marker without atool_call_id. The gate now also triggers onIsError != nil.schemas/mux.go):ToResponsesMessagesmapsIsError: truetoStatus: "incomplete"(the Responses surface equivalent already read back asis_errorby the Anthropic Responses converter).ToChatMessagesmaps bothResponsesToolMessage.Error(non-empty string) andStatus: "incomplete"back toIsError: true, so the marker survives a full chat → Responses → chat round trip.mcp/toolerrormarker_test.go), the CodeMode marker (mcp/codemode/starlark/toolerrormarker_test.go), the unmarshal gate edge case (schemas/toolmessageiserror_test.go), and both directions of the mux conversion including a round-trip regression (schemas/toolerrormux_test.go).Type of change
Affected areas
How to test
go test ./core/...Key test targets:
core/mcp—TestCreateToolResultMessageMarksError,TestCreateToolResponseMessageMarksErrorcore/mcp/codemode/starlark—TestCreateToolResponseMessageMarksErrorcore/schemas—TestChatMessageIsErrorWithoutToolCallID,TestChatToResponsesCarriesToolError,TestResponsesToChatCarriesToolError,TestToolErrorSurvivesSurfaceRoundTripBreaking changes
Related issues
Closes #5890
Security considerations
None. Changes are confined to how tool result metadata is propagated between internal surfaces and providers.
Checklist
docs/contributing/README.mdand followed the guidelines