mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Feature — a new UI Library block. Stacked on #49573 (already in main) Fixes AI-1064 ## What is the new behavior? Adds `headless-app-tanstack`: customers sign in, authorize an MCP client, and use the product through agent tool calls. It composes the existing Password-Based Auth, OAuth Consent, and MCP Server blocks. - `/agents` provides a copyable connection prompt, lists OAuth authorizations, and lets customers revoke access. - The shared MCP runtime exposes `whoami` plus example task CRUD tools. Tools use the caller's Supabase client, with database grants and RLS enforcing ownership. - A root-level `supabase/` directory supplies local Auth/OAuth configuration, a declarative tasks schema, and Edge Function files, including `.env.example`. - Docs cover local setup, signing keys, migrations, environment configuration, deployment, and extending the tools. `/example/headless-app` previews the sign-in, consent, connect, and connected states. Shared block fixes make a fresh install work: - Explicit public URL resolution fixes OAuth discovery in local Edge Runtime when middleware runtime detection fails. Both external OAuth access tokens and ordinary authenticated app session tokens remain supported; embedded agents do not need an additional consent flow. - Registry targets keep backend files outside `src/`, and generated consumer routes omit source-only TypeScript suppressions. - Signup respects `auth.email.enable_confirmations`; sign-in/signup preserve the return destination. Missing consent IDs retain the existing error state without serializing `null` into the URL. ## How to test Use the UI Library on **staging** and follow the block pages' instructions. 1. Open the **Headless App** block page for TanStack Start. Install it into a fresh app and follow the setup instructions through connecting an MCP client. 2. Sign up, open `/agents`, and use the connection prompt to authorize a client. Call `whoami`, then create, list, update, and delete a task. 3. Confirm the client appears on `/agents`. Revoke access and verify it disappears and token refresh fails. An existing access token can continue working until it expires. 4. Follow the **MCP Server** block page's embedded-agent instructions using an authenticated app session. Confirm tools work without another OAuth consent flow and `whoami` returns `client_id: null`. 5. With a second user, confirm each user can only access their own tasks. Check that signup behaves correctly for the configured email-confirmation setting. 6. Check the Headless App preview states and run the installed app's typecheck and production build. ## Validation performed Fresh local installation and browser/SDK verification passed: 26 live MCP/Data API checks, 10 Deno tests, and 7 connection-page component tests. Also passed UI Library typecheck, targeted lint, registry/Markdown builds, and fresh consumer typecheck/production build. Both OAuth and ordinary app session authentication were exercised. Hosted deployment and consuming the confirmation-email link were not tested. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a TanStack Headless App example with sign-in, OAuth consent, MCP connection, and connected-agent screens. - Added task management tools for listing, creating, updating, and deleting tasks through MCP. - Added connected-agent management, including server URL and prompt copying, refresh, and access revocation. - Added a new Headless App registry block and documentation. - **Bug Fixes** - Preserved intended destinations through sign-up, email confirmation, and protected-route login redirects. - Improved OAuth discovery URL handling across forwarded-host deployments. - **Documentation** - Updated setup, environment, deployment, and Supabase CLI guidance for headless apps and MCP servers. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Saxon Fletcher <SaxonF@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: repro <repro@local> Co-authored-by: Raúl Barroso <code@raulb.dev>
228 lines
8.1 KiB
Plaintext
228 lines
8.1 KiB
Plaintext
---
|
|
title: MCP Server
|
|
description: Add a user-scoped MCP server to your product
|
|
---
|
|
|
|
Give embedded product agents and external clients such as Codex, Claude Code,
|
|
and ChatGPT secure, user-scoped access to your product through MCP tools. This
|
|
block runs as a Supabase Edge Function, verifies Supabase user access tokens,
|
|
and gives every tool an RLS-scoped client.
|
|
|
|
## Installation
|
|
|
|
<BlockItem name="mcp-server" showOpenInV0={false} />
|
|
|
|
Installs Deno Edge Function files into a Supabase project or empty directory. No
|
|
`components.json` is required. Backend files stay in `supabase/` at the project
|
|
root even when the frontend uses `src/`.
|
|
|
|
In a frontend app, add `"supabase/functions/**"` to the app's `tsconfig.json` `exclude`
|
|
list, preserving existing entries. Check the function separately with Deno.
|
|
|
|
## Folder structure
|
|
|
|
<RegistryBlock itemName="mcp-server" />
|
|
|
|
## Configure the project
|
|
|
|
The function verifies access tokens itself, so disable the gateway JWT check:
|
|
|
|
```toml
|
|
[functions.mcp-server]
|
|
verify_jwt = false
|
|
```
|
|
|
|
The project must sign JWTs with an asymmetric key. `withSupabase` verifies user
|
|
tokens against the project JWKS and rejects legacy HS256 tokens, so a project
|
|
that still uses the legacy secret cannot authenticate embedded product sessions
|
|
or external MCP clients. Switch to an ES256 or RS256 key in
|
|
[JWT Keys](https://supabase.com/dashboard/project/_/settings/jwt).
|
|
|
|
Use Supabase CLI 2.117.0 or later. It supplies asymmetric signing keys for local
|
|
development and injects the function slug into the Edge Function, so the URL
|
|
advertised in the OAuth discovery metadata is canonical whatever path a request
|
|
arrives on.
|
|
|
|
## Choose how agents authenticate
|
|
|
|
### Embedded product agents
|
|
|
|
A trusted product backend can forward its signed-in user's Supabase access
|
|
token as `Authorization: Bearer <token>`. This reuses the product session, so
|
|
the user does not need to authorize their own product again.
|
|
|
|
Keep the token inside your backend or agent orchestrator. Never place it in a
|
|
prompt or expose it directly to a model provider.
|
|
|
|
### External MCP clients
|
|
|
|
External clients authenticate with OAuth, so users approve and revoke each
|
|
client separately. Install the [OAuth Consent block](../nextjs/oauth-consent),
|
|
then enable OAuth in `supabase/config.toml`:
|
|
|
|
```toml
|
|
[auth.oauth_server]
|
|
enabled = true
|
|
authorization_url_path = "/oauth/consent"
|
|
allow_dynamic_registration = true
|
|
```
|
|
|
|
Set the Auth **Site URL** to the origin that serves `/oauth/consent`. Use HTTPS
|
|
in production. Run `supabase config push` or restart the local stack to apply the
|
|
change.
|
|
|
|
`allow_dynamic_registration` lets any compatible client register itself. Set it
|
|
to `false` if you register clients yourself.
|
|
|
|
## Authentication
|
|
|
|
The function is a `pipeline` from `@supabase/middleware` with two entries from
|
|
`@supabase/server`, in this order:
|
|
|
|
```ts
|
|
Deno.serve(
|
|
pipeline(
|
|
[withOAuthProtectedResource(), withSupabase({ auth: 'user', cors: { headers: CORS_HEADERS } })],
|
|
handleMcp
|
|
)
|
|
)
|
|
```
|
|
|
|
`withOAuthProtectedResource()` runs before the auth gate. It serves RFC 9728
|
|
metadata at `/functions/v1/mcp-server/oauth-protected-resource` and adds a
|
|
`WWW-Authenticate` challenge to `401` responses so MCP clients can discover the
|
|
authorization server. On Edge Functions it derives the public URLs itself, locally
|
|
and hosted; off Edge Functions pass `resourceServer` and `authorizationServer`.
|
|
|
|
`withSupabase({ auth: 'user' })` is the gate. It verifies the JWT and hands
|
|
`handleMcp` an RLS-scoped client. It accepts both product session tokens and
|
|
OAuth access tokens. OAuth tokens include `client_id`; ordinary product sessions
|
|
do not. The included `whoami` tool exposes that difference.
|
|
|
|
Composing `withSupabase` as a `pipeline` entry is alpha in `@supabase/server`
|
|
and tracks `@supabase/middleware` 0.x. The nested form,
|
|
`withOAuthProtectedResource(withSupabase(config, handleMcp))`, is stable and
|
|
behaves the same.
|
|
|
|
Any holder of a valid user token can call this function directly. Treat its
|
|
tools as an authenticated product API: keep RLS enabled, check authorization for
|
|
business operations, and do not add admin clients to the shared tool context.
|
|
|
|
OAuth scopes control identity, not database or tool access. Use `client_id` for
|
|
client-specific policies when it is present, and define the intended behavior
|
|
for product sessions where it is null. Never use user-editable metadata for
|
|
authorization decisions.
|
|
|
|
## Add tools
|
|
|
|
Each tool module exports one registration function:
|
|
|
|
```ts
|
|
// supabase/functions/mcp-server/tools/tasks.ts
|
|
import type { McpServer } from 'npm:@modelcontextprotocol/server@2.0.0'
|
|
import { z } from 'npm:zod@4.4.3'
|
|
|
|
import { jsonResult, runtimeErrorResult } from './result.ts'
|
|
import type { ToolContext } from './types.ts'
|
|
|
|
export function registerTasksTools(server: McpServer, { supabase }: ToolContext): void {
|
|
server.registerTool(
|
|
'close_task',
|
|
{
|
|
description: 'Mark a task as closed.',
|
|
inputSchema: z.object({ id: z.string().uuid() }),
|
|
annotations: { readOnlyHint: false, idempotentHint: true },
|
|
},
|
|
async ({ id }) => {
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('tasks')
|
|
.update({ closed: true })
|
|
.eq('id', id)
|
|
.select()
|
|
if (error) throw error
|
|
return jsonResult(data)
|
|
} catch (error) {
|
|
return runtimeErrorResult(error)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
```
|
|
|
|
Then add one call in `tools/index.ts`, the server's composition point:
|
|
|
|
```ts
|
|
import type { McpServer } from 'npm:@modelcontextprotocol/server@2.0.0'
|
|
|
|
import { registerTasksTools } from './tasks.ts'
|
|
import type { ToolContext } from './types.ts'
|
|
import { registerWhoamiTool } from './whoami.ts'
|
|
|
|
export function registerTools(server: McpServer, context: ToolContext): void {
|
|
registerWhoamiTool(server, context)
|
|
registerTasksTools(server, context)
|
|
}
|
|
```
|
|
|
|
Each registration function receives:
|
|
|
|
- `supabase`, a user-scoped client for Database, Auth, Storage, and Functions
|
|
- `userClaims`, the normalized signed-in user identity
|
|
- `jwtClaims`, including `client_id` when the caller used OAuth
|
|
|
|
The context deliberately excludes `supabaseAdmin`. The MCP SDK rejects duplicate
|
|
tool names, and `jsonResult` returns both structured data and a text fallback for
|
|
older clients.
|
|
|
|
For typed table and column autocomplete, generate `database.types.ts` and make
|
|
the `SupabaseClient` in `tools/types.ts` a `SupabaseClient<Database>`.
|
|
|
|
## Environment
|
|
|
|
| Variable | Default | Purpose |
|
|
| ------------------------ | ---------------- | -------------------------------- |
|
|
| `MCP_SERVER_NAME` | `supabase-mcp` | Server name shown to MCP clients |
|
|
| `MCP_SERVER_DESCRIPTION` | Generic sentence | Instructions shown to clients |
|
|
|
|
The block includes `supabase/functions/mcp-server/.env.example`. Copy it before
|
|
serving locally, then customize the name and description:
|
|
|
|
```bash
|
|
cp supabase/functions/mcp-server/.env.example supabase/functions/.env
|
|
```
|
|
|
|
Add `supabase/functions/.env` to `.gitignore`. Supabase supplies the project URL,
|
|
API keys, and function slug to Edge Functions automatically; OAuth discovery
|
|
combines the slug with the public origin the gateway forwards to advertise the
|
|
function's public URL.
|
|
|
|
## Deploy
|
|
|
|
Check the function before serving or deploying it:
|
|
|
|
```bash
|
|
cd supabase/functions/mcp-server
|
|
deno task check
|
|
cd ../../..
|
|
supabase functions serve mcp-server --env-file supabase/functions/.env
|
|
```
|
|
|
|
Then deploy:
|
|
|
|
```bash
|
|
supabase config push
|
|
supabase secrets set --env-file supabase/functions/.env
|
|
supabase functions deploy mcp-server
|
|
```
|
|
|
|
## Further reading
|
|
|
|
- [OAuth Consent block](../nextjs/oauth-consent)
|
|
- [`withOAuthProtectedResource` and `pipeline` composition](https://github.com/supabase/server/blob/main/docs/api-reference.md)
|
|
- [MCP authentication](https://supabase.com/docs/guides/auth/oauth-server/mcp-authentication)
|
|
- [OAuth 2.1 server](https://supabase.com/docs/guides/auth/oauth-server/getting-started)
|
|
- [Token security and RLS](https://supabase.com/docs/guides/auth/oauth-server/token-security)
|
|
- [OAuth grant management](https://supabase.com/docs/guides/auth/oauth-server/oauth-flows#managing-user-grants)
|
|
- [Edge Functions](https://supabase.com/docs/guides/functions)
|