Nextbase Docs
Nextbase Docs
HomeBlogWelcome to Nextbase v5!Choose your starterGetting startedLocal Supabase per agent
TanStack + NeonInstallationEnvironment variablesAuthenticationAgent connectionsProject structureDeployment
AuthenticationWorkspaces and the admin area
Building with agentsEmails with Resend
TroubleshootingNextbase v5
The 8 starter kitsTanStack + Neon

Agent connections

User MCP and Admin MCP in the TanStack + Neon starter: settings pages, endpoints on the web and admin origins, tools, OAuth behaviour and where the code lives.

The kit ships two remote MCP servers. The User MCP runs on the web origin and the Admin MCP runs on the admin origin, because the admin application deploys separately. Both use OAuth with your existing Better Auth account, and both are read-only in v5. The shared pages cover the general flow: User and admin MCPs, connect an agent and tools and permissions.

Where users manage connections

ConnectionSettings pageWho can open it
User/mcp/connections/user on the web appAny signed-in, verified account
Admin/mcp/connections/admin on the admin appAccounts with the global admin role

The web app shell lists Agent connections first in its navigation and the admin app shell lists Admin agent connections. These pages are rendered by Express, not React Router, so links to them use reloadDocument. Each page shows the server address to copy, lists the account's connections for that server with the client name, workspace (or "Application administration"), scope, connection date and expiry, and offers Disconnect. The UI caps active connections at 20 per account.

Each server answers only its own audience. Asking the web app for /mcp/admin or /mcp/connections/admin returns 404, and the admin app does the same for the user paths. The small cross-link at the bottom of each connections page points at the other audience's path on the same origin, so on a split deployment follow the table above instead.

Endpoints

ServerLive demoYour deployment
User MCPhttps://tanstack-neon.usenextbase.com/mcp/user{web APP_BASE_URL}/mcp/user
Admin MCPhttps://tanstack-neon-admin.usenextbase.com/mcp/admin{admin APP_BASE_URL}/mcp/admin

Each app uses its own APP_BASE_URL as the MCP origin. Opening either URL in a browser without a token returns 401 with a WWW-Authenticate challenge pointing at the resource metadata; that is expected.

OAuth discovery and endpoints, each on the origin that serves the MCP server:

  • Protected resource metadata: /.well-known/oauth-protected-resource/mcp/user (web) and /.well-known/oauth-protected-resource/mcp/admin (admin)
  • Authorization server metadata: /.well-known/oauth-authorization-server/oauth (issuer {APP_BASE_URL}/oauth). Each app advertises only its own scope in scopes_supported
  • /oauth/register, /oauth/authorize, /oauth/token, /oauth/revoke
  • Consent page: /mcp/authorize

Tools

ServerToolScopeWhat it returns
Useraccount_contextuser:readThe signed-in user's id and email, and the workspace approved for this connection
Userprojects_listuser:readProjects in the approved workspace, newest first. limit 1 to 50, default 20
Adminadmin_users_listadmin:readId, name, email and role of application users, newest first. limit 1 to 50, default 20
Adminadmin_workspaces_listadmin:readId, name and slug of all workspaces, newest first. limit 1 to 50, default 20

All four are annotated readOnlyHint: true. There is no projects_create in this kit; the write tool exists only in the Convex kits. A user token never advertises admin tools and doesn't work at the admin endpoint, and an admin connecting to the User MCP gets only that one workspace's tools.

Each tool call reloads the grant and re-checks the native session in apps/web/src/mcp/adapter.ts: the session row must still exist and not be expired, the user must be verified and not banned, impersonated sessions are rejected, and admin tools re-check role = 'admin' on every request. Losing workspace membership or the admin role ends access even if the token hasn't expired.

OAuth and consent behaviour

The protocol is Streamable HTTP with stateless JSON responses (official TypeScript MCP SDK 1.32.1). The kit supports public clients with dynamic client registration, authorization code with mandatory PKCE S256, exact callback matching and exact resource binding. Confidential clients and client ID metadata documents are not supported. Callback URLs must be HTTPS, except HTTP loopback on localhost, 127.0.0.1 or ::1. Request bodies are capped at 32 KB.

When an agent starts a connection, the kit redirects to /mcp/authorize?request_id=.... If you aren't signed in, you go to /login?returnTo=/mcp/authorize?... and come back to the same request after a full document load. The consent page shows the client name (marked unverified, because the client supplies it), the callback address, the server, the scope with "Read access only" and the account you are signed in as. For a user connection you pick one workspace from the ones you belong to; if you have none, the page tells you to create or join a workspace first. The only choices are Allow read access and Cancel.

Lifetimes: pending consent 10 minutes, authorization codes 5 minutes, access tokens 15 minutes, refresh up to 30 days and never longer than the native session that approved it. Refresh tokens rotate, and reusing an old one revokes the whole connection. Secrets are stored as SHA-256 hashes only.

Browser origins allowed for MCP requests are the app's own APP_BASE_URL, https://claude.ai and https://chatgpt.com. Desktop and server clients without an Origin header are accepted.

Where the code lives

PathRole
apps/web/src/mcp/core.tsProvider-independent protocol: discovery, registration, consent HTML, tokens, revocation, tool registry, rate limits
apps/web/src/mcp/adapter.tsThis kit's adapter: handleMcp(request, audience) reads the live session from the Better Auth tables, lists workspaces and projects through withActorTransaction, checks role = 'admin' for admin tools
apps/web/src/mcp/pg-store.tsStorage in public.mcp_records using compare-and-set on a revision column
apps/web/src/mcp/express.tsBridges Express to the fetch-style handler for /mcp/*, /oauth/* and /.well-known/oauth-*
apps/web/server.tsMounts expressMcp(handleMcp, env.APP_BASE_URL) with the default user audience
apps/admin/server.tsMounts the same bridge with handleMcp(request, "admin"), so only the admin audience exists on that origin
apps/database/migrations/0003_user_admin_mcp.sqlCreates mcp_records with RLS enabled, revokes it from PUBLIC, app_runtime and worker_runtime, grants it to auth_runtime only
apps/web/src/mcp/core.test.tsUnit tests for audience isolation, PKCE, callbacks, CSRF, replay, expiry, demotion, revocation and the split user/admin surfaces
apps/web/src/mcp/pg-store.integration.test.tsStorage test. Runs only when MCP_TEST_PG_URL points at a loopback database

MCP credential storage runs on the auth_runtime connection, so application roles can't read tokens. Tool data for users still goes through withActorTransaction and RLS, the same as the website.

Adding a tool safely

The repository has an mcp-tool skill in .agents/skills/ that walks an AI coding tool through these steps.

  1. Add the data access to apps/web/src/mcp/adapter.ts, inside withActorTransaction for anything tenant-scoped. Call the existing services or the same SQL shape they use rather than new unchecked queries.
  2. Register the tool in apps/web/src/mcp/core.ts under the right audience (user or admin) with a bounded input schema and honest annotations. Keep readOnlyHint: true unless you are deliberately adding a write and a new scope.
  3. Resolve the actor with currentActor() inside the handler. Never trust a workspaceId or userId argument as proof of permission; the grant already carries the approved workspace.
  4. Add both the allowed and the denied case to core.test.ts, and an integration test if the query touches RLS.
  5. Run pnpm verify, pnpm test:integration and pnpm db:check before deploying. Admin tools ship with the admin app, user tools with the web app; deploy whichever you changed.

If you add a write tool, also add a distinct scope, show it on the consent page, and keep the current read-only scopes unable to perform it. See the shared tools and permissions page for the cross-kit rules.

Authentication

How Better Auth, global and workspace roles, the separate admin app and PostgreSQL row-level security work in the TanStack + Neon starter.

Project structure

The TanStack + Neon monorepo layout and where to change routes, services, schema, auth, the admin app, emails, jobs, MCP and tests.

On this page

Where users manage connectionsEndpointsToolsOAuth and consent behaviourWhere the code livesAdding a tool safely