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

Agent connections

The User MCP and Admin MCP of the Next.js + Convex starter: endpoints, tools, OAuth consent and how to add a tool.

This kit serves two MCP servers from the Convex backend, not from either Next.js app. Each is its own convex-mcp-gateway component instance (mcpUser and mcpAdmin in apps/database/convex/convex.config.ts) with its own tool registry and OAuth resource. Read user and admin MCPs for the shared concepts and connect an agent for the client-side steps.

Where users manage connections

ConnectionSettings pageLive demo settingsLive demo server
User/user/settings/connections in the web apphttps://nextjs-convex.usenextbase.com/user/settings/connectionshttps://dynamic-grasshopper-80.convex.site/mcp/user
Admin/app-admin/connections in the admin apphttps://nextjs-convex-admin.usenextbase.com/app-admin/connectionshttps://dynamic-grasshopper-80.convex.site/mcp/admin

Both pages render McpConnections (one copy in each app's src/components/) and read api.mcp.connections.settings, which returns the exact endpoint for the current deployment, the account's connections and recent activity. Revoke a connection from the same page. The admin page requires a current platformRoles admin role and lives on the admin origin, not in apps/web.

Your own deployment has its own address: CONVEX_SITE_URL followed by /mcp/user or /mcp/admin. Neither app origin is the MCP address. Copy the URL shown in connection settings rather than guessing it.

Endpoints on the backend

All of these live on the .convex.site origin and are mounted by mountMcp in apps/database/convex/mcp/http.ts:

EndpointPurpose
/mcp/user, /mcp/adminStreamable HTTP MCP servers (POST, GET, DELETE, OPTIONS)
/.well-known/oauth-protected-resource/mcp/user and .../mcp/adminResource metadata, pointing at the issuer
/.well-known/oauth-authorization-server/oauthIssuer metadata (the issuer is CONVEX_SITE_URL/oauth)
/oauth/registerDynamic public-client registration
/oauth/authorizeStarts a consent request and redirects to the consent page of the web app (user) or the admin app (admin)
/oauth/tokenAuthorization code and refresh token exchange
/oauth/revokeToken revocation

Opening an MCP URL without a token returns an authentication challenge. That is expected.

Tools and permissions

From apps/database/convex/mcp/tools.ts:

ServerToolScopeAccessWhat it does
Useraccount_contextuser:readReadSigned-in account, the one authorized workspace and the granted scopes
Userprojects_listuser:readReadActive projects in the authorized workspace, paginated with cursor and limit (1 to 50, default 20)
Userprojects_createuser:writeWriteCreates a project through createProjectForActor, the same helper the website uses. Needs at least the member role
Adminadmin_users_listadmin:readReadUser profiles (id, email, name, disabled and deleted flags). Never credentials or sessions
Adminadmin_workspaces_listadmin:readReadWorkspaces (id, name, slug)

The gateway only lets a tool run when the token's scopes include the tool's metadata.scope. Each handler then calls verifiedCaller, which rechecks on every call that the connection is unrevoked and unexpired, that the approving Convex Auth session still exists, that the account is active, and that the user still has the required workspace role or admin role. A token issued for /mcp/user is rejected by /mcp/admin and the other way round, even for an administrator.

The caller argument is injected by the gateway and hidden from tool schemas; clients cannot supply it. Successful write calls are recorded in mcpActivity.

Consent behaviour in this kit

  1. The agent registers itself at /oauth/register (public client, PKCE S256, authorization_code and refresh_token grants only) and requests /oauth/authorize with the exact resource URL.
  2. The backend stores a pending request (valid 10 minutes) and redirects to /mcp/authorize?request_id=... on the consent origin. For the user server that origin is MCP_APP_URL (falling back to APP_URL, then SITE_URL), which is the web app. For the admin server it is ADMIN_SITE_URL first, so administrator consent happens in the admin app. If you are not signed in, the consent page sends you to that app's /login?next=/mcp/authorize?... and the middleware brings you back.
  3. The consent page (src/components/mcp-consent.tsx in each app) shows the client name as unverified, the callback URL and the requested scopes. For the user server you pick one workspace; user:write (create projects) is an optional checkbox and is reset when you change workspaces. The admin server offers only admin:read and shows an error if you are not an administrator. If an admin request ever reaches the web app's consent page, it shows a link that continues the request in the admin app at NEXT_PUBLIC_ADMIN_APP_URL.
  4. Approval creates an mcpConnections row bound to your user and session, valid for 30 days, and redirects to the client with a one-time code (5 minutes). Each account can hold 20 active connections.
  5. Access tokens (nb_mcp_...) last 15 minutes. Refresh tokens rotate once; reusing one revokes the connection. Raw codes and tokens are stored only as hashes.

Browser clients are accepted from MCP_APP_URL (or its APP_URL and SITE_URL fallbacks), https://claude.ai and https://chatgpt.com. Desktop and server clients without an Origin header also work. Confidential clients and client ID metadata documents are not supported. An hourly cron (convex/crons.ts) prunes expired credentials and 90-day-old activity.

For the end-to-end walkthrough with an agent client, follow connect an agent. For the tool catalogue across kits, see tools and permissions.

Where the code lives

FileRole
apps/database/convex/mcp/tools.tsTool handlers and the userTools and adminTools registries
apps/database/convex/mcp/http.tsMounts both gateways, OAuth routes and discovery documents
apps/database/convex/mcp/oauth.tsRegistration, authorize, exchange and revoke mutations
apps/database/convex/mcp/connections.tsverifiedCaller, record, consent and the settings query
apps/database/convex/mcp/actor.tsShared read helpers (listProjects, listUsers, listWorkspaces)
apps/database/convex/mcp/protocol.tsScopes, resource and issuer URLs, PKCE and hashing
apps/database/convex/mcp/schema.tsmcpClients, mcpRequests, mcpConnections, mcpCodes, mcpTokens, mcpActivity
apps/database/convex/mcp/cleanup.tsThe hourly prune
apps/database/convex/mcp.test.tsScope, consent, replay, audience and role tests
apps/web/src/app/mcp/authorize/ and apps/web/src/components/mcp-consent.tsxUser consent page
apps/admin/src/app/mcp/authorize/ and apps/admin/src/components/mcp-consent.tsxAdmin consent page
apps/web/src/components/mcp-connections.tsx, apps/admin/src/components/mcp-connections.tsxSettings pages
docs/mcp-connections.mdThe repository's own reference

Adding a tool safely

The repository ships an mcp-tool skill in .agents/skills/mcp-tool/SKILL.md that walks through this. In short:

  1. Write the handler in mcp/tools.ts as an internalQuery or internalMutation, never a public function. The first line is verifiedCaller(ctx, caller, "user", "user:read") for reads, or verifiedCaller(ctx, caller, "user", "user:write", true) for writes (the true requires at least member). Admin tools use "admin", "admin:read".
  2. Scope data to grant.workspaceId. Reuse pageArgs and bounded(limit) for lists, validate free-text sizes, and call the same exported helper the website uses for writes, then await record(ctx, grant, "<tool_name>").
  3. Register it in userTools or adminTools with defineMcpQuery or defineMcpMutation, identityArg: "caller" and a metadata.scope that matches the handler's check.
  4. Add tests in mcp.test.ts for the happy path, the missing scope, the downgraded role, cross-workspace access and a forged caller. Run pnpm --filter database test and pnpm --filter database typecheck.
  5. Update docs/mcp-connections.md. A new scope also needs scopesFor in protocol.ts, scopes_supported in http.ts and the labels in both copies of mcp-consent.tsx.

Never publish MCP test fixtures or synthetic identity helpers as public Convex functions.

Authentication

How Convex Auth works in the Next.js + Convex starter: providers, sessions, the admin app and role, and how functions are protected.

Project structure

The Next.js + Convex monorepo layout and where to make changes to routes, data, schema, auth, the admin app, emails, jobs, MCP and tests.

On this page

Where users manage connectionsEndpoints on the backendTools and permissionsConsent behaviour in this kitWhere the code livesAdding a tool safely