# MCP connectors

> Authorize a user with an MCP server through OAuth with dynamic client registration in Vault, connect with the stored session and give the model the server's tools.

Source: https://bettersupabase.com/docs/ai-sdk/mcp

`better-supabase/ai-sdk/mcp` connects the [connectors](/docs/blocks/connectors)
block to MCP servers through `@ai-sdk/mcp`. It runs the OAuth flow for a
user, keeps the tokens in Vault, reuses the MCP session of a chat and only
returns tools from a tool list an admin approved.

```bash
pnpm add ai @ai-sdk/mcp
```

## Authorize a user [#authorize-a-user]

```ts title="app/connectors/[id]/connect/route.ts"
import { authorizeConnector } from "better-supabase/ai-sdk/mcp";

const result = await authorizeConnector({
  connectors,
  vault,
  server,
  userId,
  redirectUrl: `${origin}/connectors/callback`,
}).orThrow();
if (result.url) return Response.redirect(result.url);
```

```ts title="app/connectors/callback/route.ts"
import { completeConnector } from "better-supabase/ai-sdk/mcp";

await completeConnector({
  connectors,
  vault,
  server,
  userId,
  redirectUrl: `${origin}/connectors/callback`,
  callback: request.url,
}).orThrow();
```

`authorizeConnector` registers the app with the server's authorization
server when it has no client yet (dynamic client registration) and returns
the URL to send the user to, or no URL when the stored tokens still work.
`completeConnector` checks the state, exchanges the code, then records the
grant. `vaultOAuthProvider` is the `OAuthClientProvider`
behind both: the client information goes in an app secret
`mcp-client:<serverId>` and the user's tokens and PKCE verifier in a user
secret under `mcpOAuthRef(serverId)`.

## Connect and get tools [#connect-and-get-tools]

```ts
import { connectAll } from "better-supabase/ai-sdk/mcp";

const servers = await connectors.servers.list(organizationId).orThrow();
const { tools, skipped, close } = await connectAll(servers, {
  connectors,
  userId,
  vault,
  credentials,
  chatKey: chatId,
});
try {
  return streamText({ model, messages, tools });
} finally {
  await close();
}
```

`connectTools` connects to one server and `connectAll` to many, naming each
tool after its server (`github_create_issue`). A server's auth type sets
the credential:

| Auth     | Sends                                                              |
| -------- | ------------------------------------------------------------------ |
| `none`   | Nothing                                                            |
| `header` | The headers of the server's `credential_ref`, resolved for the app |
| `oauth`  | The user's Vault tokens, or their grant through another provider   |

With `chatKey`, the MCP session id and initialize result are stored per
chat and reused on the next request. A server that is disabled, has no
grant for the user or can't be reached fails with the hint
`CONNECTOR_DISABLED`, `CONNECTOR_NOT_AUTHORIZED` or `CONNECTOR_UNREACHABLE`;
`connectAll` lists those in `skipped` and keeps the rest. A tool list that
changed returns `CONNECTOR_TOOLS_CHANGED` until an admin approves it.

Pass `apps: true` to split out MCP Apps tools into `appTools`, and `elicit`
to answer a server's elicitation requests; without it they are declined.

## Supabase's MCP server [#supabases-mcp-server]

Supabase's [MCP server](https://supabase.com/docs/guides/getting-started/mcp)
gives an agent project tools: list tables, run SQL, read the logs and search
the Supabase docs. `supabaseMcp` returns the URL to register as a connector
server and the tool schemas from `@supabase/mcp-server-supabase`, so
`connectTools` types each tool's input and output:

```bash
pnpm add @supabase/mcp-server-supabase
```

```ts
import { connectTools, supabaseMcp } from "better-supabase/ai-sdk/mcp";

const supabase = await supabaseMcp({
  projectRef: "abcdefghijklmnopqrst",
  features: ["database", "docs"],
});
// supabase.url is
// https://mcp.supabase.com/mcp?project_ref=abcdefghijklmnopqrst&read_only=true&features=database%2Cdocs

const { tools, close } = await connectTools({
  connectors,
  server, // a connector server registered with supabase.url
  userId,
  vault,
  schemas: supabase.schemas,
}).orThrow();
```

`supabaseMcp` is read-only unless you pass `readOnly: false`: the URL sets
`read_only=true`, so SQL runs read-only, and the schemas leave out the tools
that write, such as `apply_migration`.
`connectTools` returns only the tools the schemas name, and the fingerprint
an admin approves still covers every tool the server lists. Pass
`url: "http://localhost:54321/mcp"` for the server of the local Supabase
stack.

The hosted server signs the user in with their Supabase account through
OAuth, so register it with the `oauth` auth type and send the user through
`authorizeConnector` first. To serve it from your own app with a Management
API token you hold, see
[Supabase's MCP server](/docs/frameworks/mcp#supabases-mcp-server) on the MCP
page.

Supabase's server and better-supabase's MCP tools answer different needs.
Supabase's tools administer a project (SQL, migrations, logs, branches) for
the people who run it. [`createMcp`](/docs/frameworks/mcp) tools are typed,
RLS-scoped tools over your tables for your app's own users.