Skip to content

feat: add --http local HTTP entry to mcp-server-supabase - #401

Open
barryroodt wants to merge 12 commits into
mainfrom
feat/local-http-entry
Open

feat: add --http local HTTP entry to mcp-server-supabase#401
barryroodt wants to merge 12 commits into
mainfrom
feat/local-http-entry

Conversation

@barryroodt

@barryroodt barryroodt commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Feature: a local Streamable HTTP entry (--http) for the MCP server.

What is the current behavior?

The local server is stdio only. It needs a PAT in the client config, cannot show elicitation dialogs, and the hosted handler only runs inside platform. Matt asked for a local HTTP entry here.

What is the new behavior?

  • --http on the primary bin serves the tools over Streamable HTTP on http://127.0.0.1:3111/mcp.
  • The client sends a bearer PAT per request in Authorization; the server stores nothing and has no token flag or env var.
  • Runs with legacy: 'stateless', so 2025-era clients keep working; one stderr line per request names the protocol era.
  • The startup banner prints a ready-to-paste .mcp.json snippet using ${SUPABASE_ACCESS_TOKEN}.
  • Modern form-capable clients get cost-confirmation dialogs for create_project and create_branch; legacy clients keep get_cost / confirm_cost.
  • CONTRIBUTING.md recommends --http for local development; stdio stays documented as the alternative.

How to Review

  1. CLI entry

    • packages/mcp-server-supabase/src/cli.ts
    • The --http branch: flag parsing, the --access-token rejection, and the hand-off to the entry.
  2. Per-request pipeline

    • packages/mcp-server-supabase/src/transports/local-http-entry.ts
    • Bearer extraction, the 401 shape, the era log line, the banner, and the costConfirmation wiring.
  3. Body cap and node adapter

    • packages/mcp-server-supabase/src/transports/local-http-entry.ts
    • readJsonBody owns the 4 MiB cap (413) and the -32700 parse-error envelope for malformed JSON; toNodeHandler from @modelcontextprotocol/node adapts the fetch handler to node:http.
  4. Shared handler option

    • packages/mcp-server-supabase/src/transports/http.ts
    • The new legacy option; the hosted handler must still default to reject.
  5. Tests

    • packages/mcp-server-supabase/src/transports/local-http-entry.test.ts
    • Bearer handling, 401 without a challenge, Host guard, path routing, 4 MiB cap, malformed JSON, dropped-client resilience.
  6. Docs

    • CONTRIBUTING.md
    • The --http steps and the .mcp.json snippet match what the banner prints.

Review questions

  • Is the --http flag surface (flags, port, path) the minimal one we want to support long term?
  • Does legacy: 'stateless' here leave the hosted handler's reject posture untouched?
  • Is the 401 without WWW-Authenticate the right call given the Claude Code discovery behaviour?
  • Are loopback binding plus the Host guard enough for a per-request PAT that is never stored?
  • Do the tests cover the observable contract (status codes, headers, era line) rather than internals?

Verification

Commands on 5b86dd4 (CI on 5b86dd4 pending):

  • packages/mcp-utils tsup --clean: ok
  • pnpm --filter @supabase/mcp-server-supabase test src/transports: 26/26 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
  • node dist/cli.js --http --port 0: banner printed
  • pnpm test:packed-platform-consumer: 3/3 pass
  • pnpm exec biome ci .: clean

How to test:

  1. npx https://pkg.pr.new/@supabase/mcp-server-supabase@5b86dd4 --http (or pnpm build, then node packages/mcp-server-supabase/dist/cli.js --http). --project-ref is optional; without it create_project is available too.
  2. export SUPABASE_ACCESS_TOKEN=<pat> and paste the printed snippet into .mcp.json.
  3. Start Claude Code with MCP_SDK_GENERATION=v2 MCP_PROTOCOL_NEGOTIATION=auto and ask for a preview branch: expect the cost dialog and a modern era line on stderr.
  4. Repeat with stable Claude Code: no dialog, get_cost / confirm_cost guidance, legacy era line.

Verified by Barry on 2026-09-03 on staging through the #404 stack at d397277 (hand-rolled bridge): OAuth login, project list, $0 project created directly, branch cost dialog shown in Claude Code 2.1.258 with a modern 2026-07-28 era line. The toNodeHandler head (5b86dd4) has not had a staging run yet.

Additional context

  • The entry uses toNodeHandler from @modelcontextprotocol/node. The 4 MiB body cap is entry-owned because the SDK collects request bodies unbounded. hono arrives as a transitive of @hono/node-server and is never loaded on this path. packages/mcp-server-supabase/src/transports/local-http-entry.ts.
  • 401 without WWW-Authenticate: a challenge sends Claude Code into OAuth discovery against localhost. packages/mcp-server-supabase/src/transports/local-http-entry.ts.
  • Loopback only (127.0.0.1), SDK Host guard, /mcp only; other paths get 404. packages/mcp-server-supabase/src/transports/local-http-entry.ts.
  • Scoping through the same flags as stdio (--project-ref, --read-only, --features, --api-url), not query params; --access-token with --http exits 1. packages/mcp-server-supabase/src/cli.ts.
  • Cost-confirmation requestState principal is the SHA-256 of the bearer. packages/mcp-server-supabase/src/transports/local-http-entry.ts.
  • OAuth sign-in is out of scope here; it arrives in feat: add --oauth login for the --http local entry #404, stacked on this PR.
  • This PR absorbs the --http entrypoint shape from feat(mcp): add --http flag to serve the package over HTTP #402.
  • The check job is red from upstream management-API types drift; it fails on main too.

Closes AI-1166

@barryroodt
barryroodt requested a review from a team as a code owner September 2, 2026 17:38
@coveralls

coveralls commented Sep 2, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 33911539146

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage decreased (-0.1%) to 96.401%

Details

  • Coverage decreased (-0.1%) from the base build.
  • Patch coverage: 8 uncovered changes across 1 file (124 of 132 lines covered, 93.94%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
packages/mcp-server-supabase/src/transports/local-http-entry.ts 132 124 93.94%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 3558
Covered Lines: 3448
Line Coverage: 96.91%
Relevant Branches: 526
Covered Branches: 489
Branch Coverage: 92.97%
Branches in Coverage %: Yes
Coverage Strength: 60.53 hits per line

💛 - Coveralls

@barryroodt
barryroodt force-pushed the feat/local-http-entry branch 2 times, most recently from 3a37c5d to ab4d6b7 Compare September 2, 2026 17:46
@barryroodt
barryroodt force-pushed the feat/local-http-entry branch from ab4d6b7 to 841a505 Compare September 2, 2026 20:27
@barryroodt barryroodt changed the title feat: add mcp-server-supabase-http local HTTP entry feat: add --http local HTTP entry to mcp-server-supabase Sep 2, 2026
@barryroodt barryroodt added the publish-preview Runs `publish-preview` workflow to publish preview packages via https://pkg.pr.new/ label Sep 2, 2026
@pkg-pr-new

pkg-pr-new Bot commented Sep 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: bbe20a1

@barryroodt
barryroodt force-pushed the feat/local-http-entry branch 3 times, most recently from 7294022 to b5d5fef Compare September 3, 2026 13:29
@barryroodt
barryroodt force-pushed the feat/local-http-entry branch from b5d5fef to d397277 Compare September 3, 2026 13:47
barryroodt and others added 8 commits September 4, 2026 10:17
toNodeHandler replaces the hand-rolled node:http bridge. The 4 MiB body
cap and the -32700 parse-error envelope stay entry-owned in readJsonBody,
since the SDK collects bodies unbounded when no parsedBody is passed.
The server callback wraps that read in try/catch so a client dropping
mid-upload cannot kill the process on an unhandled rejection.

Bridge tests: the 4 MiB cap test moved into local-http-entry.test.ts,
the malformed-JSON test was rewritten as an entry test (400/-32700 plus
415 for a non-JSON content-type), a mid-upload drop test was added, and
the round-trip and abort tests were dropped as SDK-owned.
@mattrossman

mattrossman commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Sample of updated console output showing tool name and aligned protocol and client name/version columns

CleanShot 2026-09-04 at 15 29 57@2x

Confirming also that stdio still works (and funny enough, proves the point of why auto mode without elicitations risky since it auto approved branch creation instead of actually asking me)

CleanShot 2026-09-04 at 15 15 24@2x

@mattrossman

Copy link
Copy Markdown
Collaborator

@barryroodt I've pushed some changes to shrink the PR, mainly removing tests for stuff that @modelcontextprotocol/node already covers, using existing SDK-provided types / validators instead of hand rolling. I changed the way client options are populated from CLI flags to query params, which also shed some tests.

One other QoL improvement I added is a top level pnpm dev:http script which runs the HTTP dev server with live reloading, so you can save changes and they take effect immediately in your connected client. I also tweaked the console output (see screenshot in earlier comment) to focus on the dynamic bits and show which JSON-RPC method was called.

Tested locally and with the preview build in latest Claude Code against staging:

npx https://pkg.pr.new/@supabase/mcp-server-supabase@3e6807f --http --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