mirror of
https://github.com/supabase/supabase.git
synced 2026-09-07 02:20:52 +08: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. Bottom of a two-PR stack; #49579 builds on it. ## What is the new behavior? Adds an `mcp-server` block: a Supabase Edge Function that exposes MCP tools scoped to the signed-in user. It is backend-only, so every file has an explicit target and no `components.json` is needed. - `withSupabase({ auth: 'user' })` verifies the access token and gives each tool an RLS-scoped client. Both product session tokens and OAuth tokens work; only the latter carry `client_id`. - `withOAuthProtectedResource` serves RFC 9728 metadata and adds a `WWW-Authenticate` challenge to `401`s, so external MCP clients can discover the authorization server. - Tools are composed in `tools/index.ts`. One is included, `whoami`, which shows the caller's identity and OAuth client. Docs at `/library/docs/headless/mcp-server`, under a new MCP group in the sidebar. `BlockItem` gained a `showOpenInV0` flag (v0 cannot take Deno functions), and the file-tree viewer now picks a language per file instead of always TypeScript. ## To test 1. `npx shadcn@latest add http://localhost:3004/library/r/mcp-server.json` into a Supabase project or empty directory. 2. Add `[functions.mcp-server] verify_jwt = false` to `supabase/config.toml`, then: ```bash supabase start supabase functions serve mcp-server --env-file supabase/functions/.env ``` 3. **Unauthenticated:** `curl -i localhost:54321/functions/v1/mcp-server` returns `401` with a `WWW-Authenticate` header, and `/functions/v1/mcp-server/oauth-protected-resource` returns the metadata document. 4. **Product session:** sign up a user, then call the endpoint with `Authorization: Bearer <their access token>`. `tools/list` shows `whoami`; calling it returns that user's id and `client_id: null`. 5. **External client:** enable `[auth.oauth_server]` with `allow_dynamic_registration = true`, install the OAuth Consent block, point an MCP client (Claude Code, Codex) at the function URL, approve the consent screen, and call `whoami` again. `client_id` is now populated. 6. Confirm RLS holds: add a table with a user-scoped policy and a tool that reads it, then check a second user cannot see the first user's rows. 7. Docs page renders at `/library/docs/headless/mcp-server`, and `deno.json` / `.env.example` in the folder tree highlight as JSON and bash rather than TypeScript. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added an installable Supabase MCP Server block with user-scoped authentication and a read-only identity tool. - Added MCP Blocks to documentation navigation and setup guidance. - Code blocks now automatically detect syntax highlighting from file names. - Added an option to hide the “Open in v0” button. - **Documentation** - Expanded MCP Server guidance covering installation, configuration, validation, deployment, OAuth, and security. <!-- 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>
188 lines
6.3 KiB
Plaintext
188 lines
6.3 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.
|
|
|
|
## 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. Projects that still use the
|
|
legacy HS256 secret do not expose signing keys from the JWKS endpoint, so the
|
|
function 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).
|
|
|
|
## 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
|
|
|
|
`withOAuthProtectedResource` 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.
|
|
|
|
`withSupabase({ auth: 'user' })` verifies the JWT and provides 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.
|
|
|
|
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 |
|
|
|
|
## 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 functions deploy mcp-server
|
|
```
|
|
|
|
## Further reading
|
|
|
|
- [OAuth Consent block](../nextjs/oauth-consent)
|
|
- [OAuth protected resource middleware](https://supabase.com/docs/reference/server/middleware-withoauthprotectedresource)
|
|
- [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)
|