Skip to content

Python: Make MCP connection-loss recovery ownership-aware #9426

Closed

Description

Problem

Agent Framework has three MCP connection ownership modes:

  • framework-created high-level mcp.Client;
  • caller-supplied high-level mcp.Client;
  • caller-supplied low-level ClientSession.

The public contract is ownership-preserving. A supplied Client must already be entered, remains caller-owned, and is not entered, closed, replaced, or recreated by Agent Framework. A supplied ClientSession is likewise caller-owned and is not replaced or reconnected by the wrapper. Closing or resetting the wrapper preserves either supplied object.

Connection-loss handling does not consistently honor that contract. Discovery, liveness, tool, and prompt paths call connect(reset=True) or _reconnect_without_loading() and log that they are reconnecting. For a caller-owned connection, reset correctly preserves the supplied object. The operation can therefore retry against the same ended connection while reporting a reconnection that did not occur.

This mismatch predates caller-supplied high-level Client support. The original reconnect implementation did not distinguish ownership and could silently discard a supplied session and open the tool's configured transport. #8126 intentionally corrected that ownership violation, but the older recovery call sites were not made ownership-aware.

Public API Boundary

The existing API and documentation establish this division of responsibility:

  • Agent Framework may construct, enter, close, and recreate connections that it owns.
  • The caller constructs, configures, enters, closes, and replaces connections that it supplies.
  • The MCP SDK owns protocol negotiation, standard operations, MRTR, caching, and event delivery.
  • Agent Framework owns wrapper coordination, catalog publication, tracing, parsing, policy, and recovery only where the framework owns the connection lifecycle.

A caller-provided replacement factory is not part of this contract. Adding one would create a new lifecycle/ownership mode and is not required to fix the current behavior.

Desired Behavior

Centralize connection-loss handling around ownership:

  • Framework-owned connections may be recreated when the failed operation is safe to retry.
  • Caller-owned Client and ClientSession connections must not claim to reconnect or retry the same object after a definitive connection-loss condition.
  • Instead, fail immediately with an actionable error explaining that the caller must create and supply a replacement connection or reconstruct the MCP tool wrapper.
  • Preserve the supplied connection object so Agent Framework does not close or replace caller resources.
  • Keep diagnostics truthful about whether a connection was actually recreated.

The shared helper should decide whether Agent Framework can recover the connection, while individual operations retain their own retry-safety policy.

Separate Correctness Concern

Ordinary tools/call currently retries once after an ambiguous lost response. The server may already have executed a side-effecting tool, so replay can duplicate the operation. The repository already avoids this for task-backed calls before a task ID is known.

That replay-safety problem affects framework-owned connections as well as caller-owned connections and should be tracked separately. This issue must not assume that recreating a connection automatically makes tools/call safe to repeat.

Acceptance Criteria

  • Connection-loss recovery uses one ownership-aware internal decision point across discovery, liveness, tool, and prompt paths.
  • Framework-owned connection recovery continues to recreate the Client where the operation's policy permits retry.
  • Caller-owned connection loss produces an actionable ownership error before a second RPC is attempted against the same ended object.
  • Logs and exceptions do not claim that a caller-owned connection was reconnected or replaced.
  • Reset and close continue to preserve caller-owned Clients and ClientSessions.
  • Tests cover caller-owned modern and legacy Clients plus caller-owned ClientSession behavior for discovery, liveness, tools, and prompts.
  • ADR/API documentation remains explicit that replacement of supplied connections is the caller's responsibility.

Non-Goals

Related

Activity

  1. KNIHAL commented on Oct 18, 2026

    @KNIHAL

    Hi, I'd like to contribute to this issue.

    I'm interested in implementing the ownership-aware MCP connection recovery behavior, including focused regression tests for caller-owned Client and ClientSession connections.

    Before starting, could a maintainer confirm the intended recovery policy and whether this issue is available for a community contribution? If the direction aligns, could you please assign it to me?

    I'll wait for confirmation before implementation. Thanks!

  2. changed the title [-]Python: Define recovery semantics for caller-owned MCP connections[/-] [+]Python: Make MCP connection-loss recovery ownership-aware[/+] on Oct 21, 2026
  3. jpalvarezl commented on Oct 21, 2026

    @jpalvarezl
    MemberAuthor

    The scope has been clarified after reviewing the implementation history, ADR, API docs, and public Microsoft Learn guidance. This is now tracked as an ownership-aware recovery bug: framework-owned connections may be recreated where retry is safe, while definitive loss of a caller-owned connection must fail clearly instead of retrying the same ended object. A replacement factory/provider is explicitly out of scope, and ambiguous replay of ordinary side-effecting tools/call will be handled as a separate correctness concern.

  4. jpalvarezl commented on Oct 21, 2026

    @jpalvarezl
    MemberAuthor

    Maintainer direction confirmed: proceed with the ownership-aware fail-fast behavior and shared recovery decision described above. Keep caller-owned connection replacement and ordinary tools/call replay safety outside this issue's implementation scope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

mcpUsage: [Issues, PRs], Target: MCPpythonUsage: [Issues, PRs], Target: Python

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions