modelcontextprotocol / modelcontextprotocol/python-sdk
Bug: anyio.Lock in async_auth_flow causes RuntimeError under concurrent OAuth MCP connections
還沒有人認領這個 Issue。
- 主要語言
- Python
- 星號
- 24.3k
- 分支
- 4k
- 平均合併
- 1 天 1 小時
- 30 天內合併 PR
- 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 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
研究方向
結合 mcp/client/auth/oauth2.py 審查 PR #2660,重點關注 OAuthContext.lock 和 async_auth_flow。執行現有的 OAuth 測試,並驗證當 GET SSE 和 token-refresh 路徑跨越 yield 點時,並行 OAuth repro 不再失敗。完成的標準是 maintainer 驗證 fix 並審查 draft PR。
由索引模型根據 Issue 內容生成。
評估
- 技術堆疊
- python
- 領域
- api, authentication
- Issue 類型
- 缺陷
- 難度
- 4/5
- 預估耗時
- 3-5 天
- 活躍度
- 停滯
- 描述清晰度
- 基本清楚
- 新手友好度
- 20/100