fix(mcp-oauth): per-server callback ports + reuse registered redirect URI - #5345
Closed
caseyg wants to merge 1 commit into
Closed
fix(mcp-oauth): per-server callback ports + reuse registered redirect URI#5345caseyg wants to merge 1 commit into
caseyg wants to merge 1 commit into
Conversation
… URI Two bugs in the OAuth flow caused reliable failures with multiple MCP servers: 1. All OAuth flows shared a single global `_oauth_port`, so concurrent flows (common on startup) all tried to bind the same port — every flow after the first got "Address already in use". 2. Each restart picked a new random port, but the OAuth client was already registered with the provider using the old port's redirect URI, causing "invalid_grant: Invalid redirect URI" on token exchange. Fix: replace the shared global with per-server ports via closure (`_make_wait_for_callback(port)`), and reuse the port from an existing client registration before falling back to a new random port. Fixes NousResearch#5344 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
7 tasks
jvg123
added a commit
to jvg123/hermes-agent
that referenced
this pull request
Apr 6, 2026
Two fixes for MCP OAuth in tools/mcp_oauth.py: 1. AnyUrl serialization: model_dump(mode='json') in set_tokens() and set_client_info() to handle Pydantic AnyUrl objects that aren't JSON-serializable by default. Without this, token storage fails on first OAuth connection with TypeError. 2. Cached token refresh (critical): get_tokens() now checks if the access token has expired by comparing file mtime + expires_in against current time. If expired, clears access_token but keeps refresh_token, so the MCP SDK uses the refresh flow instead of sending an expired token, getting 401, and falling through to full browser auth. Without this fix, any non-interactive deployment (gateway, Docker, cron) loses MCP connectivity after ~1 hour and cannot recover, even with a valid refresh token. Note: redirect URI port reuse is addressed separately in NousResearch#5345. Tests added for both fixes.
1 task
9 tasks
Contributor
|
Thanks for identifying the shared-port and stale-registration failure modes. The premise is still present on current main, but this patch needs adaptation to the MCP OAuth manager path. Problems
Suggested changes
This is an automated hermes-sweeper review. |
3 tasks
Contributor
|
Resolved on main via PR #65622. Your PR was the EARLIEST submission of the per-provider closure approach for the |
Author
|
That's so cool. Thanks for letting me know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
_oauth_portglobal with per-server ports via closure, fixing "Address already in use" when multiple MCP servers do OAuth concurrentlyFixes #5344
Context
PR #2552 fixed the original port mismatch (build vs callback using different ports) by introducing a module-level
_oauth_portglobal. However, when multiple OAuth-backed MCP servers initialize concurrently (common on startup), eachbuild_oauth_auth()call overwrites the global — all flows compete for the same port.Additionally, each restart picked a fresh random port, but the OAuth provider (e.g. Notion) still had the old port in the registered client's redirect URI, causing token exchange failures.
Test plan
rm ~/.hermes/mcp-tokens/*.json🤖 Generated with Claude Code