Skip to content

Clerk MCP Server

Authentication Auth Required

The Clerk MCP server provides comprehensive user authentication and organization management capabilities, enabling you to manage users, organizations, and access control directly through Claude.

This server enables complete authentication management including:

  • User creation, updates, and profile management
  • Organization creation and membership management
  • Invitation system for users and organizations
  • Role-based access control
  • Metadata management (public, private, unsafe)
  • Session and authentication state management
Required Environment Variables
# Add to your environment file
CLERK_SECRET_KEY=sk_test_your_secret_key_here
CLERK_PUBLISHABLE_KEY=pk_test_your_publishable_key_here

Get your keys from the Clerk Dashboard:

  1. Select your application
  2. Go to Developers > API Keys
  3. Copy the Secret Key and Publishable Key
Basic User Operations
// Get current authenticated user ID
const userId = await getUserId()
// Get detailed user information
const user = await getUser({ userId: "user_123" })
// Get total user count
const count = await getUserCount()
// Update user profile
await updateUser({
userId: "user_123",
firstName: "John",
lastName: "Doe",
username: "johndoe",
profileImageUrl: "https://example.com/avatar.jpg"
})
Organization CRUD
// Get organization by ID or slug
const org = await getOrganization({
organizationId: "org_123",
includeMembersCount: true
})
// Or get by slug
const orgBySlug = await getOrganization({
slug: "my-company"
})
// Create new organization
const newOrg = await createOrganization({
name: "Acme Corporation",
slug: "acme-corp",
createdBy: "user_123",
maxAllowedMemberships: 50,
publicMetadata: {
industry: "Technology",
website: "https://acme.com"
}
})
// Update organization
await updateOrganization({
organizationId: "org_123",
name: "Acme Corp (Updated)",
maxAllowedMemberships: 100
})
// Delete organization (permanent)
await deleteOrganization({
organizationId: "org_123"
})
Invitation Management
// Create organization invitation
await createOrganizationInvitation({
organizationId: "org_123",
emailAddress: "[email protected]",
role: "member",
inviterUserId: "user_123",
redirectUrl: "https://yourapp.com/accept-invitation",
publicMetadata: {
department: "Marketing",
welcomeMessage: "Welcome to the team!"
}
})
// Revoke organization invitation
await revokeOrganizationInvitation({
organizationId: "org_123",
invitationId: "inv_123"
})
// Create general application invitation
await createInvitation({
emailAddress: "[email protected]",
redirectUrl: "https://yourapp.com/signup",
publicMetadata: {
source: "referral",
referredBy: "user_123"
},
notify: true,
ignoreExisting: false
})
// Revoke application invitation
await revokeInvitation({ invitationId: "inv_456" })

Complete user onboarding workflow:

User Onboarding Flow
// 1. Get current user
const currentUserId = await getUserId()
if (!currentUserId) {
console.log("User not authenticated")
return
}
// 2. Get user details to check onboarding status
const user = await getUser({ userId: currentUserId })
// 3. Check if onboarding is complete
const onboardingComplete = user.unsafeMetadata?.onboardingCompleted
if (!onboardingComplete) {
// 4. Update user profile with onboarding data
await updateUser({
userId: currentUserId,
firstName: "John",
lastName: "Doe"
})
// 5. Set user preferences
await updateUserPublicMetadata({
userId: currentUserId,
metadata: {
preferences: {
theme: "dark",
emailNotifications: true,
weeklyDigest: true
},
onboardingStep: "profile-complete"
}
})
// 6. Mark onboarding as complete
await updateUserUnsafeMetadata({
userId: currentUserId,
metadata: {
onboardingCompleted: true,
onboardingCompletedAt: new Date().toISOString()
}
})
console.log("User onboarding completed successfully")
}
astro-basics Integration Patterns
// Common patterns for astro-basics project
// 1. User profile management for dashboard
async function updateUserProfile(profileData) {
const userId = await getUserId()
if (!userId) {
throw new Error('User not authenticated')
}
// Update basic profile
await updateUser({
userId,
firstName: profileData.firstName,
lastName: profileData.lastName,
username: profileData.username
})
// Update preferences in public metadata
await updateUserPublicMetadata({
userId,
metadata: {
preferences: {
theme: profileData.theme || 'light',
emailNotifications: profileData.emailNotifications || false,
language: profileData.language || 'en'
},
profile: {
bio: profileData.bio,
website: profileData.website,
location: profileData.location
}
}
})
}
// 2. Comment system integration
async function getUserForComment() {
const userId = await getUserId()
if (!userId) {
return null
}
const user = await getUser({ userId })
return {
id: user.id,
name: `${user.firstName} ${user.lastName}`.trim(),
username: user.username,
avatar: user.profileImageUrl,
role: user.publicMetadata?.role || 'user'
}
}
// 3. Organization-based content access
async function checkContentAccess(organizationId, contentType) {
const userId = await getUserId()
if (!userId) {
return false
}
try {
const org = await getOrganization({ organizationId })
// Check if user is member of organization
// In real implementation, you'd check membership
return true // Simplified for example
} catch (error) {
return false
}
}
Metadata Security Examples
// ✅ Good: Safe public metadata
await updateUserPublicMetadata({
userId,
metadata: {
preferences: { theme: "dark" },
displayName: "John D.",
publicProfile: { bio: "Developer" }
}
})
// ❌ Bad: Sensitive data in public metadata
await updateUserPublicMetadata({
userId,
metadata: {
creditCard: "4111-1111-1111-1111", // Never do this!
socialSecurity: "123-45-6789", // Never do this!
password: "secret123" // Never do this!
}
})
// ✅ Good: Internal data in unsafe/private metadata
await updateUserUnsafeMetadata({
userId,
metadata: {
lastLoginIP: "192.168.1.1",
loginCount: 42,
internalNotes: "VIP customer"
}
})
  • Rate Limits: API calls are subject to Clerk’s rate limiting
  • Metadata Size: Limited metadata storage per user/organization
  • Role Flexibility: Built-in roles, custom RBAC requires additional logic
  • Bulk Operations: No native bulk user operations
Error Handling Patterns
// Handle common Clerk errors
try {
await updateUser({
userId: "invalid-user-id",
firstName: "John"
})
} catch (error) {
if (error.message.includes('User not found')) {
console.log('User does not exist')
} else if (error.message.includes('Invalid user ID')) {
console.log('Malformed user ID provided')
} else if (error.message.includes('Rate limit exceeded')) {
console.log('Too many requests, please retry later')
} else {
console.log('Unexpected error:', error.message)
}
}

Authentication failures:

  • Verify your secret key is correct and not expired
  • Check if the user ID exists in your Clerk instance
  • Ensure proper environment variable configuration

Permission errors:

  • Verify the API key has necessary permissions
  • Check organization membership and roles
  • Ensure proper role hierarchy implementation

Metadata updates not working:

  • Check for metadata size limits (typically 10KB per user)
  • Verify JSON structure is valid
  • Use null values to remove metadata keys