You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hardens the explicit MCP OAuth login path used on headless hosts.
A non-TTY paste channel could turn EOF into reconnect-driven authorization retries, while the loopback callback listener and blocking TTY reader could outlive a completed flow. Concurrent HTTP/paste callbacks could also overwrite the first result, and callback/state errors exposed OAuth query values in logs.
This change keeps one login attempt single-flight, preserves the original loopback callback in non-TTY mode, deterministically shuts down and joins the listener, atomically latches the first terminal result, makes the TTY reader cancellable, and redacts callback/state values in both OAuth provider paths.
Related: #57836, #73997. I also reviewed the partial overlap in #74027 and the much broader #84963; neither covers this complete minimal callback/TTY/result/redaction contract.
Type of Change
🐛 Bug fix (non-breaking change that fixes an issue)
Result on Linux 6.8: 134 passed. TDD check against current main production files: the new regression file fails 8 cases for the expected EOF/listener/latch/TTY/redaction reasons; restored branch passes all 10 cases.
ruff check on all changed Python files and git diff --check origin/main...HEAD also pass.
Checklist
I've read the Contributing Guide
My commit message follows Conventional Commits
I searched open and closed PRs/issues for overlap
This PR contains only this OAuth callback lifecycle fix
I've added regression tests
Tested on Linux 6.8
Documentation/config/schema changes are N/A
Provenance: this is a byte-identical replay of the locally reviewed cumulative patch from aa3491815a... through 5bafded1e7... onto upstream f293e7206b... (diff SHA-256 1709c6d1e7d188a46d1d8d7d7805bc7727668c07a8a96febec51aa7f179ccd1a).
AI code review — automated review for reference, author can ignore or act on any point.
Thorough lifecycle hardening with genuinely good tests (real sockets, pty-based TTY cases, credential-redaction assertions, stalled-client timeout). The threaded callback server + bounded request timeouts + first-result latching close real races. Suggestions:
tools/mcp_oauth.py:857-861 — log_message now drops the request line entirely. That fixes the code/state leak but also discards useful diagnostics (path, status). Logging the path with the query stripped (self.path.split('?', 1)[0]) keeps signal without exposing credentials.
Duplicated redaction override: _perform_authorization is copy-pasted into both provider classes (tools/mcp_oauth.py:1365-1375, tools/mcp_oauth_manager.py:159-169), matching on the SDK's English message prefix "State parameter mismatch:". Two copies plus a brittle string match means an SDK rewording silently disables redaction in both places at once. Consider one shared helper/mixin and matching more loosely (e.g., "state" in str(exc).lower() and "mismatch" in str(exc).lower()).
tools/mcp_oauth.py:501-524 — _try_set_callback_result's lock-free fallback duplicates decision logic for legacy dicts; since every current caller now includes _lock, consider making the lock mandatory so there's exactly one path to reason about.
Windows paste reader (_read_windows_paste_line) raising KeyboardInterrupt from a non-main thread is caught by _paste_callback_reader's except clause — fine — but \x1a (Ctrl+Z) returning "" conflates EOF with empty input; harmless today given both paths bail, just noting the semantic overlap.
The port-release assertions after every flow are exactly the regression guard this area needed.
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
area/authAuthentication, OAuth, credential poolsP2Medium — degraded but workaround existssweeper:risk-security-boundarySweeper risk: may affect sandboxing, auth, credentials, or sensitive datatool/mcpMCP client and OAuthtype/bugSomething isn't working
3 participants
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.
What does this PR do?
Hardens the explicit MCP OAuth login path used on headless hosts.
A non-TTY paste channel could turn EOF into reconnect-driven authorization retries, while the loopback callback listener and blocking TTY reader could outlive a completed flow. Concurrent HTTP/paste callbacks could also overwrite the first result, and callback/state errors exposed OAuth query values in logs.
This change keeps one login attempt single-flight, preserves the original loopback callback in non-TTY mode, deterministically shuts down and joins the listener, atomically latches the first terminal result, makes the TTY reader cancellable, and redacts callback/state values in both OAuth provider paths.
Related: #57836, #73997. I also reviewed the partial overlap in #74027 and the much broader #84963; neither covers this complete minimal callback/TTY/result/redaction contract.
Type of Change
Changes Made
tools/mcp_oauth.py: non-TTY gating, bounded listener teardown, first-result latch, cancellable paste reader, and callback/state log redaction.tools/mcp_oauth_manager.py: the same state-mismatch redaction on the cached-provider path.tests/tools/test_mcp_oauth_single_attempt.py: end-to-end regressions for the headless, loopback, TTY, teardown, latch, and redaction cases.How to Test
Result on Linux 6.8: 134 passed. TDD check against current
mainproduction files: the new regression file fails 8 cases for the expected EOF/listener/latch/TTY/redaction reasons; restored branch passes all 10 cases.ruff checkon all changed Python files andgit diff --check origin/main...HEADalso pass.Checklist
Provenance: this is a byte-identical replay of the locally reviewed cumulative patch from
aa3491815a...through5bafded1e7...onto upstreamf293e7206b...(diff SHA-2561709c6d1e7d188a46d1d8d7d7805bc7727668c07a8a96febec51aa7f179ccd1a).