# MCP Server

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

Install this block:

```bash
npx shadcn@latest add @supabase/mcp-server
```

Installs Deno Edge Function files into a Supabase project or empty directory. No
`components.json` is required.

## Folder structure

- `supabase/`
  - `functions/`
    - `mcp-server/`
      - `.env.example`
      - `deno.json`
      - `index.ts`
      - `tools/`
        - `index.ts`
        - `result.ts`
        - `types.ts`
        - `whoami.ts`

Full source: https://supabase.com/library/r/mcp-server.json

## 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)
