microsoft / microsoft/vscode

MCP connection failures cancel the agent turn as if the user pressed Stop

Open
#336,192 0 comments 0 reactions 1 assignee Claimed by @TylerLeonhardt View on GitHub
Dominant language
TypeScript
Stars
193k
Forks
42.4k
PR merge metrics
PR metrics pending

Description

## Summary

An in-flight MCP tool call can terminate an entire Copilot agent turn when the MCP connection fails, even though the caller's cancellation token was not canceled. The UI shows `Canceled: Canceled`, and Copilot classifies the failed invocation as `The user cancelled the tool call.`

Observed with VS Code 1.136.1 and the bundled Copilot extension 0.64.1. The same tool-boundary behavior is reproducible in tests on current main at `b376c21d3e45b4d4fac2e084efc93696f1a733cd`.

## Observed sequence

I matched the failed tool invocation in the persisted chat to its MCP connection log. There were two failure shapes:

* An HTTP MCP transport returned 502 and entered `Error` while `tools/call` was outstanding.
* A direct HTTP MCP server could no longer obtain authentication silently and entered `Stopped`. Its failed tool result was likewise classified as user cancellation.

In both cases the task ended without the model getting another opportunity to respond to the failure. Sending a new user message resumed work.

Server names, internal queries, credentials, tenant IDs, and private logs are omitted. The underlying transport/authentication failure is not the subject of this issue.

## Reproduction

A deterministic reproduction does not require a real authenticated server:

1. Start an MCP server advertising a tool.
2. Invoke that tool with an uncanceled caller token, leaving the request outstanding.
3. Transition the transport to either `Error` or `Stopped`.
4. Observe that the public MCP tool-call promise rejects with `CancellationError`, indistinguishable from user cancellation.

The test fixtures `TestMcpRegistry` and `TestMcpMessageTransport` can drive this through the real `McpService`, `McpServer`, `McpTool`, request handler, and JSON-RPC implementation.

## Code path

* `src/vs/workbench/contrib/mcp/common/mcpServerRequestHandler.ts` deliberately calls `cancelAllRequests()` when the transport enters `Error` or `Stopped`.
* `JsonRpcProtocol.cancelAllRequests()` cancels the pending promises.
* `McpTool._callWithProgress()` in `mcpServer.ts` currently rethrows that cancellation after its existing retry checks.
* `toolCallErrorToResult()` in `extensions/copilot/src/extension/prompts/node/panel/toolCalling.tsx` interprets any cancellation exception as user cancellation.
* `ToolCallingLoop.buildPrompt2()` detects a canceled tool result and throws cancellation; the outer loop exits instead of sending the tool failure back to the model.

Canceling pending RPCs on disconnect is an existing, explicitly tested low-level contract. The missing distinction is at the public MCP tool-call boundary.

## Expected

If the caller actually canceled, preserve cancellation immediately.

If only the connection failed or stopped, expose an ordinary connection/tool error instead. For a stopped connection with reason `needs-user-interaction`, retain an actionable indication that interaction is required.

This should not bypass authentication, automatically retry arbitrary tools, or convert a server's intentional cancellation while its connection is still running.

Related: #280203 and the closed retry proposal #280215. This report is narrower: preserve the distinction between connection failure and user cancellation, without adding a new retry policy.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.