MCP auth retry leaves orphaned transport/session when 401 occurs during initialize

Open
#42,384 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
python

Research direction

Start in api/core/mcp/auth_client.py, tracing _execute_with_retry, enter, _initialize, and connect_server, then inspect the repro assertion for the partial-session case. Verify that a 401 during initialization closes the existing ExitStack and clears session state before retrying, while the fully initialized path still retries correctly.

Written by the indexing model from the issue text.

Description

Problem

MCPClientWithAuthRetry._execute_with_retry only tears down the MCP transport/ClientSession ExitStack when self._initialized is already True.

MCPClient.__enter__ sets _initialized = True after _initialize() returns. Inside _initializeconnect_server, the streamable-HTTP/SSE transport and ClientSession are already pushed onto _exit_stack before ClientSession.initialize() runs. A 401 from that handshake raises MCPAuthError while _initialized is still False.

On auth refresh + retry, the client therefore:

  1. Updates Authorization headers
  2. Calls _initialize() again without closing the first ExitStack entries
  3. Opens a second transport + session while the expired-token connection stays alive until the outer context exits

Why this matters

  • Core MCP OAuth / session-binding path used by tool list + invoke
  • Leaks concurrent MCP sessions (servers that bind one session per client can reject or confuse the second connect)
  • Stale connection with the old token remains open alongside the refreshed one

Evidence

# Current gate in api/core/mcp/auth_client.py
if self._initialized:
    self._exit_stack.close()
    self._session = None
    self._initialized = False
    self._initialize()
    self._initialized = True

Repro unit assertion (fails on main): after MCPAuthError with _initialized is False and a partial _session already attached, _exit_stack.close() is never called before the retry.

Proposed fix

Always close _exit_stack, clear _session, reset _initialized, and replace the ExitStack before retrying. Only call _initialize() again when the client was already fully initialized (list_tools / invoke path). The __enter__ retry path re-enters and initializes itself.

Related (not the same root cause)

  • #41499 / #41482 — orphaned JSON-RPC responses crashing the receive loop (base_session.py)
  • #42326 / #42327 — invoke skipping OAuth when custom headers are set (mcp_tool/tool.py)
Dominant language
TypeScript
Stars
157k
Forks
24.7k
Avg merge
22h 32m
Merged PRs (30d)
611

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from langgenius/dify

All issues in langgenius/dify

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.