Clerk Authentication
Setting up OAuth with Clerk. DCR mode only — MCP clients register themselves directly with Clerk via Dynamic Client Registration; the MCP server only verifies the resulting JWTs against Clerk's JWKS.
Learn more: Clerk OAuth Docs · Runnable example
Quick Start
import { MCPServer, oauthClerkProvider, object } from "mcp-use/server";
const server = new MCPServer({
name: "my-server",
version: "1.0.0",
oauth: oauthClerkProvider(),
});
server.tool(
{ name: "whoami", description: "Get authenticated user info" },
async (_args, ctx) =>
object({
userId: ctx.auth.user.userId,
email: ctx.auth.user.email,
name: ctx.auth.user.name,
})
);
server.listen();With a .env file:
# Development: https://[verb-noun-##].clerk.accounts.dev
# Production: https://clerk.[YOUR_APP_DOMAIN].com
MCP_USE_OAUTH_CLERK_FRONTEND_API_URL=https://verb-noun-42.clerk.accounts.devThat's it. JWT verification, OAuth discovery, and .well-known passthrough are handled automatically.
Setup
- Sign up at clerk.com and create an application.
- Clerk Dashboard → Configure → OAuth Applications — enable Dynamic Client Registration. This is required for MCP clients to self-register.
- Clerk Dashboard → Configure → API Keys — copy the Frontend API URL (shown in the
.env.localquickstart).
- Development example:
https://verb-noun-42.clerk.accounts.dev - Production example:
https://clerk.yourdomain.com
Environment Variables
| Variable | Required | Description |
|---|---|---|
MCP_USE_OAUTH_CLERK_FRONTEND_API_URL | Yes | Clerk Frontend API URL (e.g. https://verb-noun-42.clerk.accounts.dev) |
Configuration Options
Zero-config (reads from env vars):
oauth: oauthClerkProvider()Explicit config (overrides env vars):
oauth: oauthClerkProvider({
frontendApiUrl: "https://verb-noun-42.clerk.accounts.dev",
verifyJwt: process.env.NODE_ENV === "production", // default: true
})| Option | Type | Default | Description |
|---|---|---|---|
frontendApiUrl | string | env var | Clerk Frontend API URL |
verifyJwt | boolean? | true | Set false to skip JWT verification (development only) |
User Context
Clerk populates these fields on ctx.auth.user:
| Field | Type | Source |
|---|---|---|
userId | string | sub claim |
email | string? | email claim |
name | string? | name claim |
roles | string[]? | roles claim |
permissions | string[]? | permissions claim |
Organization Context
Clerk includes the active organization on the JWT payload. The fields are non-standard, so cast off ctx.auth.user:
server.tool(
{ name: "get-organization-info", description: "Get the active organization" },
async (_args, ctx) => {
const { org_id, org_role, org_slug } = ctx.auth.user as {
org_id?: string;
org_role?: string;
org_slug?: string;
};
if (!org_id) {
return error("No active organization. Select one in Clerk first.");
}
return object({ org_id, org_role, org_slug });
}
);Common Mistakes
- DCR not enabled — If MCP Inspector stalls at the registration step, confirm Dynamic Client Registration is enabled in Clerk Dashboard → Configure → OAuth Applications. Without DCR, there is no
client_idfor MCP clients to obtain. - Wrong Frontend API URL — Must be the full URL (with
https://), not just the subdomain. - Skipping JWT verification in production —
verifyJwt: falseis development only.
Next Steps
- Auth overview → overview.md
- Auth0 setup → auth0.md
- WorkOS setup → workos.md
- Build tools → ../server/tools.md