Skip to content

fix: reconnect with fresh transport after OAuth completion#104

Open
KentonYu wants to merge 2 commits intosteipete:mainfrom
KentonYu:fix/oauth-reconnect-stale-transport
Open

fix: reconnect with fresh transport after OAuth completion#104
KentonYu wants to merge 2 commits intosteipete:mainfrom
KentonYu:fix/oauth-reconnect-stale-transport

Conversation

@KentonYu
Copy link

After a successful OAuth browser flow, connectWithAuth looped back to client.connect(transport) reusing the same StreamableHTTPClientTransport instance. The MCP SDK does not allow calling start() twice on a transport, so it threw "StreamableHTTPClientTransport already started!".

Because this error was neither an UnauthorizedError nor an OAuthTimeoutError, the catch block in createClientContext fell through to the SSE fallback. Servers that only support Streamable HTTP (POST) then returned 404 on the SSE GET request, producing a confusing "SSE error: Non-200 status code (404)" — even though OAuth had already succeeded and tokens were persisted.

The fix introduces OAuthCompletedError: after finishAuth succeeds, connectWithAuth throws this sentinel instead of retrying on the stale transport. createClientContext catches it and continues the outer loop, which creates a brand-new Client + Transport with the now-cached tokens and connects cleanly.

After a successful OAuth browser flow, `connectWithAuth` looped back to
`client.connect(transport)` reusing the same StreamableHTTPClientTransport
instance.  The MCP SDK does not allow calling `start()` twice on a
transport, so it threw "StreamableHTTPClientTransport already started!".

Because this error was neither an `UnauthorizedError` nor an
`OAuthTimeoutError`, the catch block in `createClientContext` fell through
to the SSE fallback.  Servers that only support Streamable HTTP (POST)
then returned 404 on the SSE GET request, producing a confusing
"SSE error: Non-200 status code (404)" — even though OAuth had already
succeeded and tokens were persisted.

The fix introduces `OAuthCompletedError`: after `finishAuth` succeeds,
`connectWithAuth` throws this sentinel instead of retrying on the stale
transport.  `createClientContext` catches it and `continue`s the outer
loop, which creates a brand-new Client + Transport with the now-cached
tokens and connects cleanly.
@KentonYu
Copy link
Author

@steipete Please help me review this PR. When we use mcporter auth, we can't use MCP services like notion normally

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c217031232

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

The OAuthCompletedError handler was missing oauthSession.close(),
leaking the callback server. The next loop iteration creates a new
session; with a fixed oauthRedirectUrl port this causes EADDRINUSE.
@KentonYu
Copy link
Author

@codex reivew

@nabigraphics
Copy link

I reproduced the same issue locally with Notion MCP on mcporter 0.7.3.

In my case, the browser OAuth flow completed successfully and tokens were saved, but the auth flow broke immediately after:

  • Received OAuth authorization code for notion
  • Saved OAuth tokens for notion
  • Authorization code accepted. Retrying connection...

I first confirmed locally that recreating the transport after finishAuth() was enough to make the auth flow complete successfully.

That said, after comparing approaches, the OAuthCompletedError pattern in this PR looks cleaner to me. It keeps the reconnect decision in the outer transport loop instead of making connectWithAuth() manage transport lifecycle directly, which feels like a better separation of responsibility.

If you have a moment, I’d appreciate a review when you get the chance. 🙏

@nabigraphics
Copy link

@KentonYu one follow-up from my testing: catch (sseError) may also need the same OAuthCompletedError handling.

I reproduced this with Atlassian MCP when the initial HTTP path returned 502 and mcporter fell back to SSE. Since the SSE fallback path also goes through connectWithAuth(), it can raise the same OAuthCompletedError after OAuth completes successfully. Handling that sentinel in the SSE catch path made the Atlassian auth flow complete successfully on my side.

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