Nextbase Docs
Nextbase Docs
HomeBlogWelcome to Nextbase v5!Choose your starterGetting startedLocal Supabase per agent
Next.js + SupabaseInstallationEnvironment variablesAuthenticationAgent connectionsProject structureDeployment
AuthenticationWorkspaces and the admin area
Building with agentsEmails with Resend
TroubleshootingNextbase v5
The 8 starter kitsNext.js + Supabase

Agent connections

The User MCP and Admin MCP of the Next.js + Supabase starter: endpoints on the web and admin origins, tools, consent flow and where the code lives.

This kit exposes two remote MCP servers, one per origin: the User MCP on the web app and the Admin MCP on the admin app. They use the Supabase Auth session you already have; nothing else is installed on the user's side. For the cross-kit overview read user and admin MCPs, and for the client walkthrough read connect an agent.

Where users manage connections

ConnectionApp and settings pageLive demo server
UserWeb app, /mcp/connections/userhttps://nextjs-supabase.usenextbase.com/mcp/user
AdminAdmin app, /mcp/connections/adminhttps://nextjs-supabase-admin.usenextbase.com/mcp/admin

The web app's sidebar links to Agent connections (sidebar-platform-nav.tsx); the admin app's sidebar links to Admin agent connections (sidebar-admin-panel-nav.tsx). Each page shows the server address to copy, the account's active connections for that server and a Disconnect button. The admin page requires an application administrator session on the admin origin.

Each app serves only its own audience. apps/web/src/mcp/adapter.ts passes audiences: ["user"] and apps/admin/src/mcp/adapter.ts passes audiences: ["admin"], so /mcp/admin on the web origin and /mcp/user on the admin origin return 404. For your own deployment the addresses are the web app's NEXT_PUBLIC_SITE_URL followed by /mcp/user and the admin app's NEXT_PUBLIC_SITE_URL followed by /mcp/admin. The settings pages print the exact values.

Tools and permissions

Both servers are read-only in v5. Every tool carries readOnlyHint: true and destructiveHint: false.

ServerToolScopeWhat it returns
Useraccount_contextuser:readThe signed-in user's id and email, and the workspace approved for this connection
Userprojects_listuser:readUp to limit projects (1 to 50, default 20) in the approved workspace, newest first
Adminadmin_users_listadmin:readUp to limit user profiles (id, full_name, created_at)
Adminadmin_workspaces_listadmin:readUp to limit workspaces (id, name, slug, created_at)

A user connection is bound to one workspace chosen during consent. Only workspaces where the account is owner or admin, or has the view_projects permission, are offered. projects_list runs the SQL function mcp_projects, which rechecks the live session and the membership in the same statement as the read.

Admin tools call mcp_live_actor on every request and fail with "Administrator access required" if the user_roles row is gone. A user token is never accepted at /mcp/admin, even for an administrator.

OAuth and consent

Each origin is its own authorization server. The issuer is {NEXT_PUBLIC_SITE_URL}/oauth of the app that serves the resource. Discovery documents on each origin:

  • /.well-known/oauth-protected-resource/mcp/user (web) or /.well-known/oauth-protected-resource/mcp/admin (admin)
  • /.well-known/oauth-authorization-server/oauth

Endpoints are /oauth/register, /oauth/authorize, /oauth/token and /oauth/revoke. Public clients only, with dynamic registration, authorization code and mandatory PKCE S256. Callback URLs must be HTTPS, except loopback HTTP on localhost, 127.0.0.1 or ::1.

The consent page is /mcp/authorize. If you are not signed in, the login page receives next=/mcp/authorize?... and the app sends you back through /auth/callback to the same pending request. The page shows the client name (marked unverified), callback address, server and scope, and for user connections a workspace selector. If the account has no eligible workspace, approval is disabled until you create or join one.

Lifetimes from src/mcp/core.ts: pending consent 10 minutes, authorization codes 5 minutes, access tokens 15 minutes, refresh for at most 30 days and never past the original Supabase session. Refresh tokens rotate; reuse revokes the whole connection. The UI limits each account to 20 active connections.

Browser clients are accepted from the configured app origin, https://claude.ai and https://chatgpt.com. Desktop and server clients without an Origin header are accepted too.

Where the code lives

The MCP module is duplicated in both apps; keep the copies in sync when you change the protocol core.

FileRole
apps/web/src/mcp/core.ts and apps/admin/src/mcp/core.tsProtocol, OAuth endpoints, consent and connection pages, tool registration. Shared with the other Postgres kits
apps/web/src/mcp/supabase-adapter.ts and apps/admin/src/mcp/supabase-adapter.tsSupabase-specific actor checks, workspace lookup and the tool implementations
apps/web/src/mcp/supabase-store.ts and apps/admin/src/mcp/supabase-store.tsStorage of registrations, pending consents and credentials in public.mcp_records
apps/web/src/mcp/adapter.ts (audiences: ["user"]) and apps/admin/src/mcp/adapter.ts (audiences: ["admin"])Build the handler from each app's NEXT_PUBLIC_SITE_URL, NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SECRET_KEY
src/app/mcp/[...path]/route.ts, src/app/oauth/[...path]/route.ts, src/app/.well-known/... in each appThin Next.js route handlers that forward to handleMcp
apps/database/supabase/migrations/20261014114351_user_admin_mcp.sqlmcp_records table and the mcp_live_actor and mcp_projects functions, shared by both apps
apps/web/src/mcp/core.test.tsVitest coverage for protocol, PKCE, CSRF, replay, expiry, demotion and revocation. The admin copy has no tests of its own
apps/web/src/mcp/supabase-store.integration.test.tsStorage tests against a local database, enabled by the MCP_TEST_SUPABASE_* variables

The migration grants mcp_records and both functions to service_role only. anon and authenticated cannot read credentials or call the bridge functions. The repository's own write-up is docs/mcp-connections.md.

Adding a tool safely

  1. Add the tool in the audience === "user" branch of createMcpHandler in the web app's core.ts, or in the admin branch of the admin app's core.ts, with the same annotations object and a bounded limit input.
  2. Implement the data access in that app's supabase-adapter.ts. Call liveActor(actor) first, and for workspace data pass the actor's user id and session id into a SQL function that checks membership inside the query, like mcp_projects does.
  3. Never trust userId or workspaceId arguments from the client. The grant's workspace and the live session are the only source of permission.
  4. If the tool writes data, add a new scope. requiredScope currently only knows user:read and admin:read, and the consent page labels everything "Read access only". Changing that needs new consent copy and tests.
  5. Extend apps/web/src/mcp/core.test.ts, run pnpm test, and run the integration test against a local database. Mirror protocol changes into the admin copy.

See tools and permissions for the shared rules, and troubleshooting for 401 and 403 cases.

Authentication

How Supabase Auth, email verification, social providers, the admin role, the separate admin app and RLS work in the Next.js + Supabase starter.

Project structure

The monorepo layout of the Next.js + Supabase starter, including the separate admin app, and where to make each kind of change.

On this page

Where users manage connectionsTools and permissionsOAuth and consentWhere the code livesAdding a tool safely