# 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 | [](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");
```