Documentation Index
Fetch the complete documentation index at: https://docs.mcp-use.com/llms.txt
Use this file to discover all available pages before exploring further.
Clerk uses Dynamic Client Registration (DCR). MCP clients discover Clerk’s OAuth endpoints via .well-known passthrough and register themselves directly with Clerk. Your server only verifies the resulting tokens — it never handles authorization or token exchange directly.
Setup
1. Create a Clerk application
Sign up at the Clerk Dashboard and create a new application.
2. Enable Dynamic Client Registration
In the Clerk Dashboard, go to Configure → OAuth Applications and enable Dynamic Client Registration.
3. Find your Frontend API URL
In the Clerk Dashboard, go to API Keys. Your Frontend API URL is shown there:
- Development:
https://[verb-noun-##].clerk.accounts.dev
- Production:
https://clerk.[YOUR_APP_DOMAIN].com
4. Environment variables
MCP_USE_OAUTH_CLERK_FRONTEND_API_URL=https://verb-noun-42.clerk.accounts.dev
Configuration
import { MCPServer, oauthClerkProvider } from 'mcp-use/server'
// Zero-config: reads MCP_USE_OAUTH_CLERK_FRONTEND_API_URL
const server = new MCPServer({
name: 'my-server',
version: '1.0.0',
oauth: oauthClerkProvider()
})
await server.listen(3000)
Or pass config explicitly:
oauth: oauthClerkProvider({
frontendApiUrl: 'https://verb-noun-42.clerk.accounts.dev',
// Optional: restrict token acceptance to a specific audience
audience: 'https://my-mcp-api.example.com',
// Disable JWT verification during development only
verifyJwt: process.env.NODE_ENV === 'production',
// Override advertised scopes
// Default: ['profile', 'email', 'offline_access']
scopesSupported: ['profile', 'email', 'offline_access'],
})
Never disable verifyJwt in production. It skips signature verification and accepts any token.
Clerk provides standard OIDC claims plus organization context when using Clerk Organizations:
server.tool(
{
name: 'get-profile',
description: 'Get authenticated user profile',
},
async (_args, ctx) => ({
userId: ctx.auth.user.userId,
email: ctx.auth.user.email,
name: ctx.auth.user.name,
username: ctx.auth.user.username,
picture: ctx.auth.user.picture,
// Organization context (when using Clerk Organizations)
orgId: ctx.auth.user.org_id,
orgRole: ctx.auth.user.org_role,
orgSlug: ctx.auth.user.org_slug,
roles: ctx.auth.user.roles,
permissions: ctx.auth.permissions,
scopes: ctx.auth.scopes,
})
)
Organization-based access control
When using Clerk Organizations, the JWT includes org_id, org_role, org_slug, and org_permissions. Use these to implement multi-tenant access control:
server.tool(
{
name: 'get-documents',
description: 'Get documents for the authenticated organization',
},
async (_args, ctx) => {
const orgId = ctx.auth.user.org_id as string | undefined
if (!orgId) {
return {
content: [{ type: 'text', text: 'Organization context required' }],
isError: true,
}
}
const documents = await db.documents.findMany({
where: { organizationId: orgId },
})
return { content: [{ type: 'text', text: JSON.stringify(documents) }] }
}
)
Check organization permissions (set in the Clerk Dashboard under Organizations → Roles & Permissions):
server.tool(
{
name: 'delete-document',
description: 'Delete a document (requires org:documents:delete)',
},
async ({ documentId }, ctx) => {
if (!ctx.auth.permissions?.includes('org:documents:delete')) {
return {
content: [{ type: 'text', text: 'Forbidden: org:documents:delete permission required' }],
isError: true,
}
}
await db.documents.delete({ id: documentId })
return { content: [{ type: 'text', text: 'Document deleted' }] }
}
)
Resources
Next Steps