modelcontextprotocol / modelcontextprotocol/python-sdk
Bug: anyio.Lock in async_auth_flow causes RuntimeError under concurrent OAuth MCP connections
まだ誰も着手していません。
- 主要言語
- Python
- スター
- 24.3k
- フォーク
- 4k
- 平均マージ
- 1日 1時間
- マージ済み PR(30日)
- 31
説明
Summary
Confirmed production occurrence of the bug described in #2644 (self-closed by the original reporter before they could follow through). Filing to provide a second validated repro and draw attention to the existing draft fix in #2660.
Environment
mcp==1.26.0anyio==4.13.0httpx==0.28.1- Python 3.11.14, macOS
What happens
When a gateway session starts and connects to multiple OAuth-authenticated MCP servers concurrently (Notion + TinyFish, both OAuth 2.1 PKCE), Notion fails intermittently:
ERROR asyncio: Task exception was never retrieved
RuntimeError: The current task is not holding this lock
File ".../mcp/client/auth/oauth2.py", line 503, in async_auth_flow
File ".../mcp/client/auth/oauth2.py", line 484, in async_auth_flow
raise RuntimeError("The current task is not holding this lock")
WARNING tools.mcp_tool: MCP server 'notion' connection lost (attempt 1/5), reconnecting in 1s
WARNING tools.mcp_tool: Failed to connect to MCP server 'notion': CancelledError
INFO tools.mcp_tool: MCP: registered 114 tool(s) from 4 server(s) (1 failed)
Notion is affected more often than TinyFish because its OAuth token refreshes frequently (~every 15–60 min), consistently triggering the refresh yield path in async_auth_flow. TinyFish tokens expire less often and typically take the happy path (add header, yield once).
Root cause
OAuthContext.lock is anyio.Lock, which records task identity at acquire() and enforces same-task release(). async_auth_flow is an async generator that holds this lock across yield points. When httpx drives the generator from a different task during concurrent connections, anyio.Lock.release() throws.
Existing draft fix
PR #2660 addresses this correctly by narrowing lock scope so no lock is held across yields — GET SSE long-polls and token refresh yields both run outside any lock. It has full test coverage (100% on oauth2.py, 1177 passed) and no breaking changes, but has been sitting as a draft without maintainer review since May 22.
Workaround applied locally
Replacing anyio.Lock with asyncio.Lock in OAuthContext stops the error since asyncio.Lock does not enforce task identity on release. This is a bandaid — it loses trio portability — but unblocks asyncio deployments. Note: this is a mechanical fix and has not been load-tested to exhaustion; the intermittent nature of the bug means full verification requires sustained concurrent load.
Request
Could a maintainer review and merge PR #2660? The fix is principled and well-tested.
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
mcp/client/auth/oauth2.py と併せて PR #2660 を確認し、OAuthContext.lock と async_auth_flow に重点を置く。既存の OAuth テストを実行し、GET SSE と token-refresh のパスが yield ポイントをまたぐ場合でも、並行 OAuth repro が失敗しなくなったことを確認する。Done は、maintainer による fix の検証と draft PR のレビューが完了した状態とする。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- python
- 領域
- api, authentication
- issue の種類
- バグ
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 活発さ
- 停滞
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 20/100