modelcontextprotocol / modelcontextprotocol/python-sdk
Bug: anyio.Lock in async_auth_flow causes RuntimeError under concurrent OAuth MCP connections
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Python
- Star
- 24.3k
- Fork
- 4k
- Merge trung bình
- 1 ngày 1 giờ
- Pull request đã merge (30 ngày)
- 31
Mô tả
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.
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Xem xét PR #2660 cùng với mcp/client/auth/oauth2.py, tập trung vào OAuthContext.lock và async_auth_flow. Chạy các bài kiểm thử OAuth hiện có và xác minh rằng repro OAuth đồng thời không còn thất bại khi các đường dẫn GET SSE và làm mới token đi qua các điểm yield. Hoàn tất nghĩa là maintainer đã xác thực fix và review draft PR.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- python
- Lĩnh vực
- api, authentication
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Đình trệ
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 20/100