Skip to content

fix(mcp-oauth): avoid locking across resource requests - #97458

Merged
teknium1 merged 1 commit into
NousResearch:mainfrom
helix4u:fix/mcp-oauth-lock-scope
Aug 29, 2026
Merged

teknium1 merged 1 commit into
NousResearch:mainfrom
helix4u:fix/mcp-oauth-lock-scope

Conversation

@helix4u

@helix4u helix4u commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

MCP SDK 2.0.0 holds one task-owned OAuth context lock across the entire HTTPX auth-flow generator. A session-long Streamable HTTP GET can therefore hold the lock indefinitely and prevent concurrent schema or tool POST requests from authenticating.

This keeps the SDK's existing OAuth state machine intact while replacing its task-owned lock with a binary semaphore and releasing that semaphore only while the actual MCP resource request is in flight. OAuth initialization, refresh, discovery, registration, and token exchange remain serialized. If another request refreshes the token while a resource request is pending, a stale 401 or 403 now retries with the new token instead of starting another OAuth transition.

This is the narrow lock-scope complement to the auth-flow cleanup work in #90888 and the stale-connection retry work in #91460. It follows the concurrency model proposed upstream in modelcontextprotocol/python-sdk#3243 without copying the SDK's full 401/403 implementation into Hermes.

Related Issue

Related: modelcontextprotocol/python-sdk#1326

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • Replace the pinned SDK provider's task-owned OAuth lock with a task-agnostic binary semaphore.
  • Release the OAuth state lock only around the real MCP resource request, while preserving serialization for every token-changing OAuth transition.
  • Retry stale 401/403 responses with a token refreshed by another request.
  • Add a regression that leaves a GET open while a concurrent POST authenticates and proceeds.

How to Test

  1. Start an authenticated MCP GET auth flow and leave its response pending.
  2. Start a POST auth flow through the same provider and verify it receives its Bearer token without waiting for the GET.
  3. Refresh the shared token, return a stale 401 to the GET, and verify it retries with the new token rather than starting duplicate authorization.

Focused test command:

scripts/run_tests.sh tests/tools/test_mcp_oauth_bidirectional.py tests/tools/test_mcp_oauth_manager.py -q -j 4

Result: 17 passed.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass. Full-suite coverage is left to GitHub CI; the focused tests above pass.
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Windows 11

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — N/A; behavior is covered by code comments and regression tests
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Screenshots / Logs

Not applicable. The regression is covered by the focused async OAuth tests above.

@alt-glitch alt-glitch added type/bug Something isn't working comp/tools Tool registry, model_tools, toolsets tool/mcp MCP client and OAuth area/auth Authentication, OAuth, credential pools P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Aug 28, 2026
@helix4u
helix4u marked this pull request as ready for review August 28, 2026 21:13
@teknium1
teknium1 merged commit 9a1eef7 into NousResearch:main Aug 29, 2026
38 checks passed
andrexibiza added a commit to andrexibiza/hermes-agent that referenced this pull request Sep 11, 2026
Rematerialize the credited NousResearch#84963 lifecycle remainder on current main
(bf51fee):

- compose NousResearch#90888's owner-task teardown contract into current main's
  async_auth_flow: close-once inner-generator teardown under a shielded
  CancelScope, NousResearch#97458 semaphore rebalancing on every cancellation path,
  primary-error precedence (cleanup failures attach notes instead of
  masking the real OAuth error)
- carry tests/tools/test_mcp_oauth_lifecycle_owner.py (task-bound
  teardown, close-once, cancellation shielding, semaphore balance)
- credit Igor Verhun (igorhvr) in contributors/emails

Zero-file overlap with main's movement since base 4209d37.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/tools Tool registry, model_tools, toolsets P2 Medium — degraded but workaround exists sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/mcp MCP client and OAuth type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants