#
tokens: 3853/50000 6/6 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .github
│   └── workflows
│       └── npm-publish.yml
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | .idea/
2 | node_modules/
3 | dist/
4 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Keycloak MCP Server
  2 | 
  3 | [![smithery badge](https://smithery.ai/badge/keycloak-model-context-protocol)](https://smithery.ai/server/keycloak-model-context-protocol)
  4 | 
  5 | A Model Context Protocol server for Keycloak administration, providing tools to manage users and realms.
  6 | 
  7 | ## Features
  8 | 
  9 | - Create new users in specific realms
 10 | - Delete users from realms
 11 | - List available realms
 12 | - List users in specific realms
 13 | 
 14 | ## Installation
 15 | 
 16 | ### Installing via Smithery
 17 | 
 18 | To install Keycloak for Claude Desktop automatically via [Smithery](https://smithery.ai/server/keycloak-model-context-protocol):
 19 | 
 20 | ```bash
 21 | npx -y @smithery/cli install keycloak-model-context-protocol --client claude
 22 | ```
 23 | 
 24 | ### Via NPM (Recommended)
 25 | 
 26 | The server is available as an NPM package:
 27 | ```bash
 28 | # Direct usage with npx
 29 | npx -y keycloak-model-context-protocol
 30 | 
 31 | # Or global installation
 32 | npm install -g keycloak-model-context-protocol
 33 | ```
 34 | 
 35 | ### Local Development Setup
 36 | 
 37 | If you want to develop or modify the server:
 38 | 
 39 | ```bash
 40 | git clone <repository-url>
 41 | cd keycloak-model-context-protocol
 42 | npm install
 43 | npm run build
 44 | ```
 45 | 
 46 | ## Configuration
 47 | 
 48 | ### Using NPM Package (Recommended)
 49 | Configure the server in your Claude Desktop configuration file:
 50 | 
 51 | ```json
 52 | {
 53 |   "mcpServers": {
 54 |     "keycloak": {
 55 |       "command": "npx",
 56 |       "args": ["-y", "keycloak-model-context-protocol"],
 57 |       "env": {
 58 |         "KEYCLOAK_URL": "http://localhost:8080",
 59 |         "KEYCLOAK_ADMIN": "admin",
 60 |         "KEYCLOAK_ADMIN_PASSWORD": "admin"
 61 |       }
 62 |     }
 63 |   }
 64 | }
 65 | ```
 66 | 
 67 | ### For Local Development
 68 | ```json
 69 | {
 70 |   "mcpServers": {
 71 |     "keycloak": {
 72 |       "command": "node",
 73 |       "args": ["path/to/dist/index.js"],
 74 |       "env": {
 75 |         "KEYCLOAK_URL": "http://localhost:8080",
 76 |         "KEYCLOAK_ADMIN": "admin",
 77 |         "KEYCLOAK_ADMIN_PASSWORD": "admin"
 78 |       }
 79 |     }
 80 |   }
 81 | }
 82 | ```
 83 | 
 84 | ## Available Tools
 85 | 
 86 | ### create-user
 87 | Creates a new user in a specified realm.
 88 | 
 89 | **Inputs**:
 90 | - `realm`: The realm name
 91 | - `username`: Username for the new user
 92 | - `email`: Email address for the user
 93 | - `firstName`: User's first name
 94 | - `lastName`: User's last name
 95 | 
 96 | ### delete-user
 97 | Deletes a user from a specified realm.
 98 | 
 99 | **Inputs**:
100 | - `realm`: The realm name
101 | - `userId`: The ID of the user to delete
102 | 
103 | ### list-realms
104 | Lists all available realms.
105 | 
106 | ### list-users
107 | Lists all users in a specified realm.
108 | 
109 | **Inputs**:
110 | - `realm`: The realm name
111 | 
112 | ## Development
113 | 
114 | ```bash
115 | npm run watch
116 | ```
117 | 
118 | ## Testing
119 | 
120 | To test the server using MCP Inspector:
121 | 
122 | ```bash
123 | npx -y @modelcontextprotocol/inspector npx -y keycloak-model-context-protocol
124 | ```
125 | 
126 | ## Deployment
127 | 
128 | ### NPM Package
129 | 
130 | This project is automatically published to [NPM](https://www.npmjs.com/package/keycloak-model-context-protocol) via GitHub Actions when a new release is published on GitHub.
131 | 
132 | #### Setup Requirements for Deployment
133 | 
134 | 1. Create NPM account and get access token
135 | 2. Add NPM_TOKEN secret to GitHub repository
136 |    - Go to repository Settings > Secrets
137 |    - Add new secret named `NPM_TOKEN`
138 |    - Paste your NPM access token as the value
139 | 
140 | ## Prerequisites
141 | 
142 | - Node.js 18 or higher
143 | - Running Keycloak instance
144 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "strict": true,
 7 |     "esModuleInterop": true,
 8 |     "skipLibCheck": true,
 9 |     "forceConsistentCasingInFileNames": true,
10 |     "resolveJsonModule": true,
11 |     "outDir": "./dist",
12 |     "rootDir": "./src"
13 |   },
14 |   "include": [
15 |     "src/**/*"
16 |   ],
17 |   "exclude": [
18 |     "node_modules"
19 |   ]
20 | }
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "keycloak-model-context-protocol",
 3 |   "version": "0.0.2",
 4 |   "description": "MCP server for Keycloak administration",
 5 |   "license": "MIT",
 6 |   "author": "Christoph Englisch",
 7 |   "type": "module",
 8 |   "bin": {
 9 |     "keycloak-model-context-protocol": "./dist/index.js"
10 |   },
11 |   "files": [
12 |     "dist"
13 |   ],
14 |   "scripts": {
15 |     "build": "tsc && shx chmod +x dist/*.js",
16 |     "prepare": "npm run build",
17 |     "watch": "tsc --watch"
18 |   },
19 |   "dependencies": {
20 |     "@modelcontextprotocol/sdk": "0.5.0",
21 |     "@keycloak/keycloak-admin-client": "^22.0.5",
22 |     "zod": "^3.22.4"
23 |   },
24 |   "devDependencies": {
25 |     "@types/node": "^22",
26 |     "shx": "^0.3.4",
27 |     "typescript": "^5.3.3"
28 |   }
29 | }
```

--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: NPM Publish
 2 | on:
 3 |   push:
 4 |     branches:
 5 |       - main
 6 |   pull_request:
 7 |   release:
 8 |     types: [published]
 9 | jobs:
10 |   build:
11 |     runs-on: ubuntu-latest
12 |     steps:
13 |       - uses: actions/checkout@v4
14 |       - uses: actions/setup-node@v4
15 |         with:
16 |           node-version: 22
17 |           cache: npm
18 |       - name: Install dependencies
19 |         run: npm ci
20 |       - name: Build package
21 |         run: npm run build
22 |   publish:
23 |     runs-on: ubuntu-latest
24 |     needs: [build]
25 |     if: github.event_name == 'release'
26 |     environment: release
27 |     permissions:
28 |       contents: read
29 |       id-token: write
30 |     steps:
31 |       - uses: actions/checkout@v4
32 |       - uses: actions/setup-node@v4
33 |         with:
34 |           node-version: 22
35 |           cache: npm
36 |           registry-url: "https://registry.npmjs.org"
37 |       - name: Install dependencies
38 |         run: npm ci
39 |       - name: Publish package
40 |         run: npm publish --access public
41 |         env:
42 |           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  3 | import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
  4 | import KcAdminClient from '@keycloak/keycloak-admin-client';
  5 | import { z } from 'zod';
  6 | 
  7 | const server = new Server(
  8 |   {
  9 |     name: "keycloak-admin",
 10 |     version: "0.0.1",
 11 |   },
 12 |   {
 13 |     capabilities: {
 14 |       tools: {},
 15 |     },
 16 |   }
 17 | );
 18 | 
 19 | // Initialize Keycloak client
 20 | const kcAdminClient = new KcAdminClient({
 21 |   baseUrl: process.env.KEYCLOAK_URL || 'http://localhost:8080',
 22 |   realmName: 'master'
 23 | });
 24 | 
 25 | // Tool schemas
 26 | const CreateUserSchema = z.object({
 27 |   realm: z.string(),
 28 |   username: z.string(),
 29 |   email: z.string().email(),
 30 |   firstName: z.string(),
 31 |   lastName: z.string()
 32 | });
 33 | 
 34 | const DeleteUserSchema = z.object({
 35 |   realm: z.string(),
 36 |   userId: z.string()
 37 | });
 38 | 
 39 | const ListUsersSchema = z.object({
 40 |   realm: z.string()
 41 | });
 42 | 
 43 | // List available tools
 44 | server.setRequestHandler(ListToolsRequestSchema, async () => {
 45 |   return {
 46 |     tools: [
 47 |       {
 48 |         name: "create-user",
 49 |         description: "Create a new user in a specific realm",
 50 |         inputSchema: {
 51 |           type: "object",
 52 |           properties: {
 53 |             realm: { type: "string" },
 54 |             username: { type: "string" },
 55 |             email: { type: "string", format: "email" },
 56 |             firstName: { type: "string" },
 57 |             lastName: { type: "string" }
 58 |           },
 59 |           required: ["realm", "username", "email", "firstName", "lastName"]
 60 |         }
 61 |       },
 62 |       {
 63 |         name: "delete-user",
 64 |         description: "Delete a user from a specific realm",
 65 |         inputSchema: {
 66 |           type: "object",
 67 |           properties: {
 68 |             realm: { type: "string" },
 69 |             userId: { type: "string" }
 70 |           },
 71 |           required: ["realm", "userId"]
 72 |         }
 73 |       },
 74 |       {
 75 |         name: "list-realms",
 76 |         description: "List all available realms",
 77 |         inputSchema: {
 78 |           type: "object",
 79 |           properties: {},
 80 |           required: []
 81 |         }
 82 |       },
 83 |       {
 84 |         name: "list-users",
 85 |         description: "List users in a specific realm",
 86 |         inputSchema: {
 87 |           type: "object",
 88 |           properties: {
 89 |             realm: { type: "string" }
 90 |           },
 91 |           required: ["realm"]
 92 |         }
 93 |       }
 94 |     ]
 95 |   };
 96 | });
 97 | 
 98 | // Handle tool calls
 99 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
100 |   // Authenticate before each request
101 |   await kcAdminClient.auth({
102 |     username: process.env.KEYCLOAK_ADMIN || 'admin',
103 |     password: process.env.KEYCLOAK_ADMIN_PASSWORD || 'admin',
104 |     grantType: 'password',
105 |     clientId: 'admin-cli',
106 |   });
107 | 
108 |   const { name, arguments: args } = request.params;
109 | 
110 |   try {
111 |     switch (name) {
112 |       case "create-user": {
113 |         const { realm, username, email, firstName, lastName } = CreateUserSchema.parse(args);
114 |         
115 |         kcAdminClient.setConfig({
116 |           realmName: realm
117 |         });
118 | 
119 |         const user = await kcAdminClient.users.create({
120 |           realm,
121 |           username,
122 |           email,
123 |           firstName,
124 |           lastName,
125 |           enabled: true
126 |         });
127 | 
128 |         return {
129 |           content: [{
130 |             type: "text",
131 |             text: `User created successfully. User ID: ${user.id}`
132 |           }]
133 |         };
134 |       }
135 | 
136 |       case "delete-user": {
137 |         const { realm, userId } = DeleteUserSchema.parse(args);
138 |         
139 |         kcAdminClient.setConfig({
140 |           realmName: realm
141 |         });
142 | 
143 |         await kcAdminClient.users.del({
144 |           id: userId,
145 |           realm
146 |         });
147 | 
148 |         return {
149 |           content: [{
150 |             type: "text",
151 |             text: `User ${userId} deleted successfully from realm ${realm}`
152 |           }]
153 |         };
154 |       }
155 | 
156 |       case "list-realms": {
157 |         const realms = await kcAdminClient.realms.find();
158 |         
159 |         return {
160 |           content: [{
161 |             type: "text",
162 |             text: `Available realms:\n${realms.map(r => `- ${r.realm}`).join('\n')}`
163 |           }]
164 |         };
165 |       }
166 | 
167 |       case "list-users": {
168 |         const { realm } = ListUsersSchema.parse(args);
169 |         
170 |         kcAdminClient.setConfig({
171 |           realmName: realm
172 |         });
173 | 
174 |         const users = await kcAdminClient.users.find();
175 |         
176 |         return {
177 |           content: [{
178 |             type: "text",
179 |             text: `Users in realm ${realm}:\n${users.map(u => `- ${u.username} (${u.id})`).join('\n')}`
180 |           }]
181 |         };
182 |       }
183 | 
184 |       default:
185 |         throw new Error(`Unknown tool: ${name}`);
186 |     }
187 |   } catch (error) {
188 |     if (error instanceof z.ZodError) {
189 |       return {
190 |         isError: true,
191 |         content: [{
192 |           type: "text",
193 |           text: `Invalid arguments: ${error.errors.map(e => `${e.path.join(".")}: ${e.message}`).join(", ")}`
194 |         }]
195 |       };
196 |     }
197 |     throw error;
198 |   }
199 | });
200 | 
201 | // Start the server
202 | const transport = new StdioServerTransport();
203 | await server.connect(transport);
204 | console.error("Keycloak MCP Server running on stdio");
```