Skip to content

Fix tests timing out during interactive OAuth flow (#2510) - #2544

Open
GhagSagar23 wants to merge 6 commits into
modelcontextprotocol:mainfrom
GhagSagar23:fix-auth-redirect-retry
Open

Fix tests timing out during interactive OAuth flow (#2510)#2544
GhagSagar23 wants to merge 6 commits into
modelcontextprotocol:mainfrom
GhagSagar23:fix-auth-redirect-retry

Conversation

@GhagSagar23

Copy link
Copy Markdown

Summary

This PR fixes the test timeouts and potential hanging requests that occur when an interactive OAuth flow (returning REDIRECT) is triggered by the StreamableHTTPClientTransport. It addresses issue #2510.

Changes

  • Changed StreamableHTTPClientTransport._send and StreamableHTTPClientTransport._startOrAuthSse to explicitly await the _pendingAuthPromise when the auth() function returns REDIRECT.
  • Added a _handleAuthResult method to serialize requests waiting on the OAuth redirect flow and to throw UnauthorizedError when the result is not AUTHORIZED.
  • Ensured that finishAuth properly resolves or rejects the pending auth promise.
  • Ensured that if the transport is closed while an auth flow is pending, the promise is explicitly rejected.
  • Modified the streamableHttp.test.ts to mock redirectToAuthorization by throwing an UnauthorizedError, which properly simulates the rejection path for the tests without timing out.

@GhagSagar23
GhagSagar23 requested a review from a team as a code owner July 24, 2026 11:48
@changeset-bot

changeset-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 4dbb8a0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 24, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@2544

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/@modelcontextprotocol/codemod@2544

@modelcontextprotocol/core

npm i https://pkg.pr.new/@modelcontextprotocol/core@2544

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@2544

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/@modelcontextprotocol/server-legacy@2544

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@2544

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@2544

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@2544

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@2544

commit: 4dbb8a0

Fixes modelcontextprotocol#2510 by serializing auth requests in StreamableHTTPClientTransport and properly handling 401s without crashing the test runner.
@GhagSagar23
GhagSagar23 force-pushed the fix-auth-redirect-retry branch from 615c4b9 to a740d28 Compare July 24, 2026 18:43

@gnanirahulnutakki gnanirahulnutakki left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head a740d28ca71f6bc4221f63ad01fd9faac0ea15d3 against #2510's redirect-completion requirement. The client package passes typecheck, lint, and all 771 tests locally, but the current tests still encode the pre-fix rejection path. I found one lifecycle issue that leaves the reported mid-session request dead-ended.

stepUpRetries = 0
): Promise<void> {
if (this._pendingAuthPromise) {
await this._pendingAuthPromise;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Keep the triggering request pending across REDIRECT

This await only observes a promise that finishAuth() creates later. In #2510's mid-session path, _send() is already inside onUnauthorized; handleOAuthUnauthorized() receives REDIRECT and throws UnauthorizedError, so the original send() rejects before the browser callback can call finishAuth() or create this promise. On this exact head, the isolated existing test uses custom fetch during auth flow on 401 - no global fetch fallback still passes specifically by expecting that rejection, and this PR changes no tests. Thus the code may exchange a callback code later, but it still cannot resume and retry the triggering request. Please establish the deferred when REDIRECT is produced, leave the triggering request pending, resolve/reject it from finishAuth()/close(), retry the request, and cover that full sequence.

GhagSagar23 and others added 2 commits August 4, 2026 16:38
… completes

- Updates `StreamableHTTPClientTransport` to intercept `UnauthorizedError` during requests.
- Retains resolve/reject handlers in a `_pendingAuthPromise` to prevent premature request failure and manual resends.
- Retries the pending request upon successful `finishAuth()` completion.
- Updates tests in `streamableHttp.test.ts` to handle the new pending promise behavior instead of immediate rejection.
- Fixes Vitest mock leaking between test suites in `streamableHttp.test.ts` by using `mockReset` instead of `mockRestore`.
- Updates `probeAuthSeam.test.ts` to align with the new `finishAuth` contract where probe requests remain pending until resolved.
@GhagSagar23

Copy link
Copy Markdown
Author

I've updated the implementation to address this feedback!

Triggering requests now correctly remain pending in the background until finishAuth() completes instead of aggressively rejecting with UnauthorizedError.

Changes made:

  • StreamableHTTPClientTransport._send and _startOrAuthSse now intercept UnauthorizedError and set up a deferred _pendingAuthPromise.
  • The resolve/reject handlers are saved, and the request awaits this promise.
  • finishAuth() (and close()) handle resolving or rejecting this pending promise. Once finishAuth() succeeds, the transport automatically retries the initial triggering request without requiring the user to do so manually.
  • Updated streamableHttp.test.ts tests to verify the new hanging behavior in negative test scenarios by properly capturing the promise and calling transport.close() to cleanly reject it.
  • Fixed a mock leak in streamableHttp.test.ts where fetchMock.mockRestore() was unbinding the vitest spy for subsequent tests.
  • Updated the assertions in probeAuthSeam.test.ts which previously expected UnauthorizedError to propagate during a redirect. The test now correctly validates the new finishAuth contract by ensuring the probe stays pending and then successfully completes when finishAuth() is called.

All 798 tests across the client packages are green. Let me know if there's anything else you'd like me to address!

* StreamableHTTPClientTransport._send and _startOrAuthSse now wait for finishAuth() rather than rejecting immediately with UnauthorizedError when a REDIRECT occurs.
* Refactored OAuth test suite to support asynchronous redirect flows. Tests now hang gracefully during REDIRECT, waiting for finishAuth() rather than asserting immediate rejections.
* Updated simpleOAuthClient example to accurately reflect the new asynchronous flow.
* Fixed sporadic vitest timeouts and FakeTimers leakage by properly managing unhandled transport promises during tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants