Skip to content

feat(mcp): add @supabase/server/mcp - #146

Draft
raulb wants to merge 6 commits into
mainfrom
raulb/add-mcp-server
Draft

feat(mcp): add @supabase/server/mcp#146
raulb wants to merge 6 commits into
mainfrom
raulb/add-mcp-server

Conversation

@raulb

@raulb raulb commented Sep 4, 2026

Copy link
Copy Markdown
Member

Try it out

npm i @supabase/server@1.7.0-beta.0

What kind of change does this PR introduce?

Bug fix, feature, docs update, ...

What is the current behavior?

Please link any relevant issues here.

What is the new behavior?

Feel free to include screenshots if it includes visual changes.

Additional context

Fixes AI-1173

Testing here

@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@supabase/server@146

commit: 25e3be9

Comment thread package.json Outdated
@mandarini
mandarini force-pushed the raulb/add-mcp-server branch 3 times, most recently from 35b398c to 80d9f0d Compare September 9, 2026 12:01
@mandarini
mandarini force-pushed the raulb/add-mcp-server branch from 80d9f0d to 25e3be9 Compare September 9, 2026 14:23
@mandarini

Copy link
Copy Markdown
Collaborator

Tried generateTools from 1.7.0-beta.0 in a hosted Edge Function (pipeline([withOAuthProtectedResource(), withSupabase({ auth: 'user' }), withPostgresClient()]), Claude Code as the client). Two things:

Hosted platform gates the OpenAPI root to secret keys

  • GET /rest/v1/ with a publishable apikey returns 401 {"message":"Secret API key required","hint":"Only secret API keys can be used for this endpoint."}. Decided by the apikey alone; a user JWT in Authorization makes no difference.
  • So generateTools(ctx.supabase) throws SPEC_FETCH_FAILED on every request on hosted Supabase. The caller-scoped client can never read the description there.
  • Fix needed: let the description come from somewhere other than the client that executes the tools, e.g. generateTools(supabase, { describeWith: supabaseAdmin }) or generateTools(supabase, { spec }). Handlers keep running on the RLS-scoped client.
  • Workaround I'm running: a facade { from, rpc, getOpenApiSpec: () => admin.getOpenApiSpec() } passed to generateTools. Works, but relies on those three being the only members touched.
  • Consequence worth documenting: the admin description lists every verb of every table and every RPC the service role can execute, so callers will want to filter _meta.kind === 'function' and rely on RLS/grants at call time.

Name collisions with hand-written tools

  • A hand-written list_daily_metrics next to table daily_metrics throws Tool list_daily_metrics is already registered inside the createMcpHandler factory → 500 with nothing in the logs.
  • The docs already say to delete the generated entry first. A registerTools(server, tools, { skipExisting: true }) or a names/except option would make that the default path rather than a runtime crash.

Everything else worked as described: _meta filtering, COMMENT ON descriptions, fromJsonSchema argument validation, RLS at call time.

@mandarini

Copy link
Copy Markdown
Collaborator

Three more from the same dogfooding run, so this thread carries the complete list when the PR is picked up again:

  • Errors thrown inside the createMcpHandler factory are invisible. Both failures above surfaced as a 500 with only boot/shutdown lines in the function logs; finding them took the Management API logs endpoint plus a fixture that patches McpServer.prototype.registerTool. Worth a paragraph in docs/mcp.md on surfacing ToolGenerationError (a try/catch with console.error in the factory), and it is a second argument for registerTools not throwing on a duplicate name.
  • generateTools fetches the OpenAPI description on every call. In an Edge Function that is one GET /rest/v1/ per MCP message, on top of the secret-key requirement above. Accepting a pre-fetched spec or memoizing per isolate would pair naturally with the describeWith ask.
  • Generation exposes whatever the project scaffold left behind. public.rls_auto_enable() is a SECURITY DEFINER helper the scaffold creates, executable by anon and authenticated; with the admin-described spec it became a tool until _meta.kind === 'function' was dropped. supabase db advisors --linked --type security flags it. A line in docs/mcp.md saying "run the security advisor before exposing generated tools" would save the next person the surprise.

Full write-up: https://linear.app/supabase/document/findings-from-dogfooding-ce88c7499028 (items 9, 12, 14).

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