Skip to content

feat: add --oauth login for the --http local entry - #404

Open
barryroodt wants to merge 8 commits into
feat/local-http-entryfrom
feat/local-http-oauth
Open

feat: add --oauth login for the --http local entry#404
barryroodt wants to merge 8 commits into
feat/local-http-entryfrom
feat/local-http-oauth

Conversation

@barryroodt

@barryroodt barryroodt commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Feature: --oauth sign-in for the local --http entry.

What is the current behavior?

Stacked on #401. The --http entry takes a PAT per request from the client's Authorization header, so every user still has to mint and store a PAT.

What is the new behavior?

  • --http --oauth signs the user in with Supabase OAuth in the browser; the entry acts as an OAuth client to the Supabase authorization server, the same flow MCP Inspector uses (metadata, dynamic client registration, PKCE, loopback callback on 127.0.0.1:3112); the protocol core is oauth4webapi. No platform change.
  • The token lives in memory by default; --oauth-store file opts into ~/.supabase/mcp-oauth.json (mode 0600) and --logout revokes the tokens and deletes it.
  • The printed .mcp.json snippet has no headers; the entry attaches the token to every Management API call itself.
  • Both modes now check Origin as well as Host; browser origins get 403.
  • CONTRIBUTING.md recommends --http --oauth for local development, with PAT mode as the alternative.

How to Review

  1. CLI flags

    • packages/mcp-server-supabase/src/cli.ts
    • --oauth, --oauth-store, --oauth-callback-port, --logout; --oauth without --http exits 1.
  2. OAuth client

    • packages/mcp-server-supabase/src/transports/oauth-client.ts
    • oauth4webapi handles metadata discovery, registration, PKCE, the code exchange and refresh; the token stores, the loopback callback server, the single-flight refresh, the loopback-only HTTP rule, the S256 check and revoke stay in this file. Check that allowInsecureRequests is passed only for a loopback issuer and assertSecureOAuthUrl still runs on every discovered endpoint.
  3. Entry integration

    • packages/mcp-server-supabase/src/transports/local-http-entry.ts
    • The accessToken option, the Origin check, the headerless banner, and the per-process principal.
  4. Tests

    • packages/mcp-server-supabase/src/transports/oauth-client.test.ts, packages/mcp-server-supabase/src/transports/local-http-entry.test.ts
    • Full flow against an msw fake authorization server, refresh sharing, store behaviour, Origin 403; fixtures require the 201 registration response and token_type that the library enforces.
  5. Docs

    • CONTRIBUTING.md
    • The --http --oauth steps, the residual risk note, and the storage tool limitation.

Review questions

  • Is the OAuth client framing (intermediary as OAuth client to upstream) the right posture, given the resource-server shape needs platform work?
  • Is Host + Origin without a request gate an acceptable residual for a loopback tool?
  • Are the file store defaults (path, mode 0600, one process per file, --logout revoke) what we want?
  • Does the single-flight refresh handle concurrent tool calls without losing the single-use refresh token?
  • Do the tests exercise the observable flow rather than the client internals?

Verification

Commands on 55e16f6 (the test job runs once this PR retargets main):

  • packages/mcp-utils tsup --clean: ok
  • pnpm --filter @supabase/mcp-server-supabase test src/transports: 38/38 pass
  • pnpm --filter @supabase/mcp-server-supabase typecheck: clean
  • pnpm --filter @supabase/mcp-server-supabase build: ok
  • node dist/cli.js --version: 0.11.0
  • pnpm test:packed-platform-consumer: 3/3 pass
  • pnpm exec biome ci .: clean
  • pnpm install --frozen-lockfile: ok
  • SUPABASE_MCP_NO_BROWSER=1 node packages/mcp-server-supabase/dist/cli.js --http --oauth --port 0 --api-url http://127.0.0.1:9: exits 1 without contacting a real host

How to test:

  1. npx https://pkg.pr.new/@supabase/mcp-server-supabase@55e16f6 --http --oauth (add --api-url https://api.supabase.green for staging). Local alternative: pnpm build, then node packages/mcp-server-supabase/dist/cli.js --http --oauth.
  2. Sign in and consent in the browser, then paste the headerless snippet into .mcp.json.
  3. list_projects works; get_storage_config returns the platform's 401 message.

Verified by Barry on 2026-09-03 on staging at d2ff583 (hand-rolled OAuth client): OAuth login, project list, $0 project created directly, branch cost dialog shown in Claude Code 2.1.258. The oauth4webapi head (55e16f6; 1661479 plus one test-only commit) has not had a staging run yet; that run is the only test against the real registration and token endpoints.

Additional context

  • OAuth client framing: the MCP authorization spec allows an intermediary to act as an OAuth client to its upstream. The resource-server shape would need a platform change and is the token passthrough anti-pattern. packages/mcp-server-supabase/src/transports/oauth-client.ts.
  • oauth4webapi (^3.5.3, resolves 3.8.7, no transitive dependencies) is the OAuth protocol client; platform already carries 3.5.3, so the range dedupes there. Its registration parser requires HTTP 201, which the platform's oauth-apps controller returns (POST register with ApiCreatedResponse, no HttpCode override). packages/mcp-server-supabase/src/transports/oauth-client.ts.
  • Stacked on feat: add --http local HTTP entry to mcp-server-supabase #401 at 5b86dd4 (toNodeHandler entry); the OAuth wiring sits inside that pipeline. 55e16f6 adds one test-only commit from the independent re-review: the OAuth entry test now proves the token source is read on every Management API call.
  • No request gate. Host + Origin checks stop browsers; any local process that can reach 127.0.0.1:<port> gets the signed-in user's scopes. Residual named in CONTRIBUTING.md.
  • Memory store by default; file store is opt-in. packages/mcp-server-supabase/src/transports/oauth-client.ts.
  • Authorize omits the resource parameter; the platform AS only accepts the hosted MCP URL there. packages/mcp-server-supabase/src/transports/oauth-client.ts.
  • Refresh happens on expiry only, through one shared in-flight promise, because the refresh token is single-use. packages/mcp-server-supabase/src/transports/oauth-client.ts.
  • In OAuth mode the cost-confirmation principal is a per-process random value, so a token refresh cannot invalidate a pending requestState. packages/mcp-server-supabase/src/transports/local-http-entry.ts.
  • Known limit: get_storage_config / update_storage_config return 401 under an OAuth token because the platform storage handlers lack @RequiredScope; the hosted server has the same gap.
  • The check job is red from upstream management-API types drift; it fails on main too.
  • Base is feat/local-http-entry (feat: add --http local HTTP entry to mcp-server-supabase #401); the CI test job runs after this retargets main.

Part of AI-1166

@barryroodt
barryroodt requested a review from a team as a code owner September 3, 2026 08:19
@barryroodt
barryroodt force-pushed the feat/local-http-oauth branch 2 times, most recently from 3ba6f78 to 41dbecd Compare September 3, 2026 09:01
@barryroodt barryroodt self-assigned this Sep 3, 2026
@barryroodt barryroodt added the publish-preview Runs `publish-preview` workflow to publish preview packages via https://pkg.pr.new/ label Sep 3, 2026
@pkg-pr-new

pkg-pr-new Bot commented Sep 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

pnpm add https://pkg.pr.new/@supabase/mcp-server-postgrest@9f140f0
pnpm add https://pkg.pr.new/@supabase/mcp-server-supabase@9f140f0
pnpm add https://pkg.pr.new/@supabase/mcp-utils@9f140f0

commit: 9f140f0

@coveralls

coveralls commented Sep 3, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 33917889096

Warning

No base build found for commit bbe20a1 on feat/local-http-entry.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 94.402%

Details

  • Patch coverage: 87 uncovered changes across 1 file (13 of 100 lines covered, 13.0%).

Uncovered Changes

File Changed Covered %
packages/mcp-server-supabase/src/transports/oauth-client.ts 87 0 0.0%
Total (2 files) 100 13 13.0%

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 3648
Covered Lines: 3451
Line Coverage: 94.6%
Relevant Branches: 532
Covered Branches: 495
Branch Coverage: 93.05%
Branches in Coverage %: Yes
Coverage Strength: 59.43 hits per line

💛 - Coveralls

@barryroodt
barryroodt force-pushed the feat/local-http-oauth branch from 6a4af2c to d2ff583 Compare September 3, 2026 14:16
oauth4webapi ^3.5.3 handles discovery, dynamic client registration, PKCE,
the authorization-code exchange and refresh. The token stores, the loopback
callback server, the single-flight refresh, the loopback-only HTTPS rule,
the S256 check and revocation stay in-repo. Test fixtures now answer
registration with 201 and carry token_type on every token response, as the
library requires.
@barryroodt
barryroodt force-pushed the feat/local-http-oauth branch from d2ff583 to 1661479 Compare September 4, 2026 11:52
barryroodt and others added 3 commits September 4, 2026 15:07
The previous test used a constant token, so hoisting await tokenSource() to
startup (breaking every request after the first refresh) would still pass.
# Conflicts:
#	CONTRIBUTING.md
#	packages/mcp-server-supabase/src/cli.ts
#	packages/mcp-server-supabase/src/transports/local-http-entry.test.ts
#	packages/mcp-server-supabase/src/transports/local-http-entry.ts
},
"dependencies": {
"@mjackson/multipart-parser": "^0.10.1",
"@modelcontextprotocol/client": "catalog:",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Note @modelcontextprotocol/client becomes a runtime dep with my changes so --oauth can use the SDK's auth() flow. Platform already has it at the same catalog version as a devDependency of mgmt-api, so the next bump will pull it (plus some small transitives) into the image as install weight. Platform doesn't reach the code that uses it though.

@mattrossman

Copy link
Copy Markdown
Collaborator

Pushed changes to trim this PR, though might be good to check the @modelcontextprotocol/client dep decision w/ platform team in case they object. We already get a nice DX boost from #401 alone so not the end of the world if we don't get OAuth in near term.

Tested w/ --oauth flag in latest Claude Code, staging.

npx https://pkg.pr.new/@supabase/mcp-server-supabase@9f140f0 --http --oauth --api-url https://api.supabase.green

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

publish-preview Runs `publish-preview` workflow to publish preview packages via https://pkg.pr.new/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants