Repository navigation
Conversation
Follow-up to CopilotKit#52. Adding an MCP server without a token now detects when it requires an account and offers Sign in. The official MCP SDK runs the spec flow (discovery, dynamic client registration, PKCE, code exchange, refresh); OpenDots persists its state per connection and handles the browser leg. - POST /api/connections/:id/sign-in returns the authorization URL (http(s) only). The tab opens during the click, with a visible fallback link if a popup blocker stops it; settings poll until signed in. - GET /oauth/mcp/callback sits outside /api because a redirect cannot carry the owner token. A single-use state that expires in 10 minutes ties it to an owner-started sign-in; background refreshes never replace it. The result page escapes all text. - Tokens refresh automatically; if the service stops accepting them, the connection asks to sign in again and its tools are hidden from the Dot. Sign out forgets tokens and stops the Dot's active turn. - Tokens and client registrations stay server-side. - New optional PUBLIC_URL (validated at startup) sets the callback base behind a proxy or on a hosted domain; otherwise the browser's origin. Tests run a real OAuth-protected MCP server (SDK auth router and demo provider with refresh tokens): full sign-in, tool call, silent refresh, replay/forged/denied callbacks, escaping, sign-out, and PUBLIC_URL validation. express and @types/express are dev dependencies for that fixture; the lockfile changes only by those two root entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Follow-up to #52.
Workflow
Many MCP servers, such as Google, GitHub or Notion style services, ask you to sign in rather than paste a bearer token. With this change, the owner adds the server without a token. If it requires an account, the connection shows needs sign-in:
Access tokens refresh automatically. If the service stops accepting them, the connection asks the owner to sign in again, and its tools are hidden from the Dot until they do. Sign out forgets the tokens and stops the Dot's active turn, like other access changes.
How it works
StoredOAuthProvider(src/server/connection-oauth.ts) persists the SDK's state per connection inmcp_oauth, and the transport gets it asauthProvider.POST /api/connections/:id/sign-in(owner-only) returns the authorization URL, and onlyhttp(s)URLs are accepted. The client opens the tab during the click so popup blockers allow it. If a blocker stops it anyway, a visible "Open sign-in" link appears. The settings poll until the connection is signed in.GET /oauth/mcp/callbacksits outside/api, because a browser redirect can't carry the owner token. A single-usestatethat expires after 10 minutes ties it to an owner-started sign-in. Background refreshes during tool calls never create or replace that state. The result page escapes all text.authModeandsignedIn.PUBLIC_URL, validated at startup: http(s), no credentials. It sets the callback base when OpenDots runs behind a proxy or on a hosted domain. Without it, the callback uses the origin the owner's browser is on. The dev proxy forwards/oauthto the API server.Verification
npm run check-format,lint,typecheck,test(306 passing) andbuildall pass.tests/connection-oauth.test.tsruns a real OAuth-protected MCP server: the SDK'smcpAuthRouterand bearer middleware, plus a demo provider extended with refresh tokens and a 401invalid_tokenfor expired tokens. It covers:PUBLIC_URLvalidationPUBLIC_URLset. Earlier testing in my fork also covered the popup-blocked fallback link.expressand@types/expressare dev dependencies for the test fixture (expresswas already installed via the MCP SDK).package-lock.jsondiffers frommainonly by those two root entries.🤖 Generated with Claude Code