# Clerk MCP Server
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for [Clerk](https://clerk.com)
Query and manage your Clerk organizations, members, users, roles, and metadata directly from AI assistants like Claude, Cursor, VS Code Copilot, Windsurf, and more.
---
## Getting Your Clerk API Key
You need a **Clerk Secret Key** to use this server.
1. Go to [dashboard.clerk.com](https://dashboard.clerk.com) and sign in (or create an account)
2. Select your application (or create one)
3. Navigate to **Configure** → **API Keys**
4. Copy the **Secret Key** — it starts with `sk_test_` (development) or `sk_live_` (production)
> **Never commit your secret key to git.** Use environment variables or pass it via headers.
---
## Two Operating Modes
The server supports two modes, auto-detected at startup:
### Hosted Mode (Private)
The server owns the Clerk secret key. Set `CLERK_SECRET_KEY` in your `.env` file and all requests use that single key. No headers needed from clients.
**Best for:** Personal use, internal teams, self-hosted deployments.
### Public Mode (Per-Request Key)
No secret key on the server. Each client passes their own key via the `X-Clerk-Secret-Key` HTTP header on every request. The server creates a fresh Clerk client per request.
**Best for:** Shared deployments, multi-tenant setups, or when you don't want the key stored on the server.
| | Hosted Mode | Public Mode |
| -------------------- | -------------------- | ----------------------- |
| Key stored on server | Yes (in `.env`) | No |
| Key sent per request | No | Yes (via header) |
| Setup complexity | Simpler | Slightly more config |
| Multi-user support | Single Clerk account | Multiple Clerk accounts |
---
## Quick Start
### 1. Clone & install
```bash
git clone https://github.com/BalajiSriraman/Clerk-MCP.git
cd Clerk-MCP
npm install
```
### 2. Configure & run
**Hosted mode** — set the key once on the server:
```bash
cp .env.example .env
# Edit .env and paste your secret key:
# CLERK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
npm run dev
```
**Public mode** — no `.env` needed, clients provide the key:
```bash
npm run dev
```
Your MCP endpoint is now live at **`https://clerk-mcp.vercel.app/mcp`**.
### 3. Verify
```bash
curl https://clerk-mcp.vercel.app/api/health
```
- **Hosted mode**: `{"status":"ok","mode":"hosted","clerkConnected":true}`
- **Public mode**: `{"status":"ok","mode":"public","clerkConnected":null}`
---
## Connect to AI Assistants
### Claude Code (CLI)
**Hosted mode:**
```bash
claude mcp add --transport http clerk https://clerk-mcp.vercel.app/mcp
```
**Public mode:**
```bash
claude mcp add --transport http \
--header "X-Clerk-Secret-Key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
clerk https://clerk-mcp.vercel.app/mcp
```
### Claude Desktop
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
**Hosted mode:**
```json
{
"mcpServers": {
"clerk": {
"command": "npx",
"args": ["mcp-remote", "https://clerk-mcp.vercel.app/mcp"]
}
}
}
```
**Public mode:**
```json
{
"mcpServers": {
"clerk": {
"command": "npx",
"args": [
"mcp-remote",
"https://clerk-mcp.vercel.app/mcp",
"--header",
"X-Clerk-Secret-Key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
]
}
}
}
```
### Cursor
Go to **Settings → MCP → Add Server**:
**Hosted mode:**
```json
{
"mcpServers": {
"clerk": {
"url": "https://clerk-mcp.vercel.app/mcp"
}
}
}
```
**Public mode:**
```json
{
"mcpServers": {
"clerk": {
"url": "https://clerk-mcp.vercel.app/mcp",
"headers": {
"X-Clerk-Secret-Key": "sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}
```
### VS Code (GitHub Copilot)
Add to your workspace `.vscode/mcp.json`:
**Hosted mode:**
```json
{
"mcp": {
"servers": {
"clerk": {
"url": "https://clerk-mcp.vercel.app/mcp"
}
}
}
}
```
**Public mode:**
```json
{
"mcp": {
"servers": {
"clerk": {
"url": "https://clerk-mcp.vercel.app/mcp",
"headers": {
"X-Clerk-Secret-Key": "sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}
}
```
### Windsurf
Add to MCP settings:
**Hosted mode:**
```json
{
"context_servers": {
"clerk": {
"source": "custom",
"command": "npx",
"args": ["mcp-remote", "https://clerk-mcp.vercel.app/mcp"],
"env": {}
}
}
}
```
**Public mode:**
```json
{
"context_servers": {
"clerk": {
"source": "custom",
"command": "npx",
"args": [
"mcp-remote",
"https://clerk-mcp.vercel.app/mcp",
"--header",
"X-Clerk-Secret-Key: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
],
"env": {}
}
}
}
```
---
## Docker
### Build the image
```bash
docker build -t clerk-mcp .
```
### Run in hosted mode
```bash
docker run -d -p 3000:3000 -e CLERK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx clerk-mcp
```
### Run in public mode
```bash
docker run -d -p 3000:3000 clerk-mcp
```
### Docker Compose
Both modes are defined in `docker-compose.yml`:
```bash
# Hosted mode on port 3000 — set CLERK_SECRET_KEY in .env first
docker compose up clerk-mcp-hosted
# Public mode on port 3001 — no key needed on the server
docker compose up clerk-mcp-public
```
---
## Available Tools
### Organizations
| Tool | Description |
| ------------------------------------ | -------------------------------------------------------------------- |
| `clerk_list_organizations` | List orgs with filtering by name/slug, pagination, and member counts |
| `clerk_get_organization` | Get org details by ID or slug (includes metadata, timestamps) |
| `clerk_create_organization` | Create a new organization with name, slug, and metadata |
| `clerk_update_organization_metadata` | Update org public/private metadata |
| `clerk_delete_organization` | Delete an organization permanently (irreversible) |
### Members
| Tool | Description |
| --------------------------------- | ------------------------------------------------------- |
| `clerk_list_organization_members` | List org members with roles, user data, and metadata |
| `clerk_update_member_role` | Change a member's role (e.g. `org:admin`, `org:member`) |
| `clerk_update_member_metadata` | Update membership public/private metadata |
| `clerk_remove_member` | Remove a member from an organization |
### Invitations
| Tool | Description |
| ------------------------------------- | ----------------------------------------------------- |
| `clerk_list_organization_invitations` | List invitations by status (pending/accepted/revoked) |
| `clerk_create_invitation` | Invite a user to an org by email |
### Users
| Tool | Description |
| ---------------------------- | ------------------------------------------------------------------ |
| `clerk_list_users` | List all instance users with search by name/email/phone |
| `clerk_get_user` | Get full user profile: emails, phones, external accounts, metadata |
| `clerk_update_user_metadata` | Update user public/private/unsafe metadata |
---
## Example Prompts
Once connected, try asking your AI assistant:
```
List all organizations in my Clerk instance.
How many members does the "engineering" organization have?
Create a new organization called "Design Team" with slug "design-team".
Show me all pending invitations for organization org_2abc123.
Find all users with email addresses containing "@example.com".
Update the public metadata for user user_2xyz789 to set plan: "pro".
```
---
## Deploying to Production
Build and deploy as any Nuxt/Node.js app:
```bash
npm run build
node .output/server/index.mjs
```
Or use Docker:
```bash
docker build -t clerk-mcp .
docker run -p 3000:3000 -e CLERK_SECRET_KEY=sk_live_xxx clerk-mcp
```
If you self-host, use your own deployment URL instead of `https://clerk-mcp.vercel.app` in the client configs above.
Compatible with: **Vercel**, **Netlify**, **Cloudflare Workers** (with nitro preset), **Railway**, **Fly.io**, or any Node.js host.
---
## Adding New Tools
Create a new file in `server/mcp/tools/` — it's automatically discovered by the MCP toolkit:
```typescript
// server/mcp/tools/clerk-my-new-tool.ts
import { z } from "zod";
export default defineMcpTool({
name: "clerk_my_new_tool",
description: "Description of what this tool does",
inputSchema: {
param: z.string().describe("Parameter description"),
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
openWorldHint: true,
},
async handler({ param }) {
const clerk = useClerkClient();
const result = await clerkCall(() => clerk.someApi.someMethod({ param }));
return jsonResult(result);
},
});
```
## Tech Stack
- [Nuxt 4](https://nuxt.com) — Full-stack Vue framework
- [@nuxtjs/mcp-toolkit](https://mcp-toolkit.nuxt.dev) — MCP server module for Nuxt
- [@clerk/backend](https://clerk.com/docs/reference/backend/overview) — Clerk Backend SDK
- [Zod](https://zod.dev) — Schema validation
## License
MIT