keycloak-model-context-protocol 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -0
- package/dist/index.js +178 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Keycloak MCP Server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol server for Keycloak administration, providing tools to manage users and realms.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Create new users in specific realms
|
|
8
|
+
- Delete users from realms
|
|
9
|
+
- List available realms
|
|
10
|
+
- List users in specific realms
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
### Prerequisites
|
|
15
|
+
|
|
16
|
+
- Node.js 18 or higher
|
|
17
|
+
- Running Keycloak instance
|
|
18
|
+
|
|
19
|
+
### Setup
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install
|
|
23
|
+
npm run build
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Configuration
|
|
27
|
+
|
|
28
|
+
Configure the server in your Claude Desktop configuration file:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"keycloak": {
|
|
34
|
+
"command": "node",
|
|
35
|
+
"args": ["path/to/dist/index.js"],
|
|
36
|
+
"env": {
|
|
37
|
+
"KEYCLOAK_URL": "http://localhost:8080",
|
|
38
|
+
"KEYCLOAK_ADMIN": "admin",
|
|
39
|
+
"KEYCLOAK_ADMIN_PASSWORD": "admin"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Available Tools
|
|
47
|
+
|
|
48
|
+
### create-user
|
|
49
|
+
Creates a new user in a specified realm.
|
|
50
|
+
|
|
51
|
+
**Inputs**:
|
|
52
|
+
- `realm`: The realm name
|
|
53
|
+
- `username`: Username for the new user
|
|
54
|
+
- `email`: Email address for the user
|
|
55
|
+
- `firstName`: User's first name
|
|
56
|
+
- `lastName`: User's last name
|
|
57
|
+
|
|
58
|
+
### delete-user
|
|
59
|
+
Deletes a user from a specified realm.
|
|
60
|
+
|
|
61
|
+
**Inputs**:
|
|
62
|
+
- `realm`: The realm name
|
|
63
|
+
- `userId`: The ID of the user to delete
|
|
64
|
+
|
|
65
|
+
### list-realms
|
|
66
|
+
Lists all available realms.
|
|
67
|
+
|
|
68
|
+
### list-users
|
|
69
|
+
Lists all users in a specified realm.
|
|
70
|
+
|
|
71
|
+
**Inputs**:
|
|
72
|
+
- `realm`: The realm name
|
|
73
|
+
|
|
74
|
+
## Development
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm run watch
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Deployment
|
|
81
|
+
|
|
82
|
+
### NPM Package
|
|
83
|
+
|
|
84
|
+
This project is set up for automated deployment to NPM via GitHub Actions. When a new release is published on GitHub:
|
|
85
|
+
|
|
86
|
+
1. The workflow automatically builds the package
|
|
87
|
+
2. Tests are run
|
|
88
|
+
3. The package is published to NPM if all checks pass
|
|
89
|
+
|
|
90
|
+
#### Setup Requirements
|
|
91
|
+
|
|
92
|
+
1. Create NPM account and get access token
|
|
93
|
+
2. Add NPM_TOKEN secret to GitHub repository
|
|
94
|
+
- Go to repository Settings > Secrets
|
|
95
|
+
- Add new secret named `NPM_TOKEN`
|
|
96
|
+
- Paste your NPM access token as the value
|
|
97
|
+
|
|
98
|
+
### Using the Package
|
|
99
|
+
|
|
100
|
+
After deployment, install globally:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm install -g keycloak-model-context-protocol
|
|
104
|
+
```
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
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
|
+
const server = new Server({
|
|
7
|
+
name: "keycloak-admin",
|
|
8
|
+
version: "0.0.1",
|
|
9
|
+
}, {
|
|
10
|
+
capabilities: {
|
|
11
|
+
tools: {},
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
// Initialize Keycloak client
|
|
15
|
+
const kcAdminClient = new KcAdminClient({
|
|
16
|
+
baseUrl: process.env.KEYCLOAK_URL || 'http://localhost:8080',
|
|
17
|
+
realmName: 'master'
|
|
18
|
+
});
|
|
19
|
+
// Tool schemas
|
|
20
|
+
const CreateUserSchema = z.object({
|
|
21
|
+
realm: z.string(),
|
|
22
|
+
username: z.string(),
|
|
23
|
+
email: z.string().email(),
|
|
24
|
+
firstName: z.string(),
|
|
25
|
+
lastName: z.string()
|
|
26
|
+
});
|
|
27
|
+
const DeleteUserSchema = z.object({
|
|
28
|
+
realm: z.string(),
|
|
29
|
+
userId: z.string()
|
|
30
|
+
});
|
|
31
|
+
const ListUsersSchema = z.object({
|
|
32
|
+
realm: z.string()
|
|
33
|
+
});
|
|
34
|
+
// List available tools
|
|
35
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
36
|
+
return {
|
|
37
|
+
tools: [
|
|
38
|
+
{
|
|
39
|
+
name: "create-user",
|
|
40
|
+
description: "Create a new user in a specific realm",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
realm: { type: "string" },
|
|
45
|
+
username: { type: "string" },
|
|
46
|
+
email: { type: "string", format: "email" },
|
|
47
|
+
firstName: { type: "string" },
|
|
48
|
+
lastName: { type: "string" }
|
|
49
|
+
},
|
|
50
|
+
required: ["realm", "username", "email", "firstName", "lastName"]
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "delete-user",
|
|
55
|
+
description: "Delete a user from a specific realm",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
realm: { type: "string" },
|
|
60
|
+
userId: { type: "string" }
|
|
61
|
+
},
|
|
62
|
+
required: ["realm", "userId"]
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "list-realms",
|
|
67
|
+
description: "List all available realms",
|
|
68
|
+
inputSchema: {
|
|
69
|
+
type: "object",
|
|
70
|
+
properties: {},
|
|
71
|
+
required: []
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "list-users",
|
|
76
|
+
description: "List users in a specific realm",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {
|
|
80
|
+
realm: { type: "string" }
|
|
81
|
+
},
|
|
82
|
+
required: ["realm"]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
// Handle tool calls
|
|
89
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
90
|
+
// Authenticate before each request
|
|
91
|
+
await kcAdminClient.auth({
|
|
92
|
+
username: process.env.KEYCLOAK_ADMIN || 'admin',
|
|
93
|
+
password: process.env.KEYCLOAK_ADMIN_PASSWORD || 'admin',
|
|
94
|
+
grantType: 'password',
|
|
95
|
+
clientId: 'admin-cli',
|
|
96
|
+
});
|
|
97
|
+
const { name, arguments: args } = request.params;
|
|
98
|
+
try {
|
|
99
|
+
switch (name) {
|
|
100
|
+
case "create-user": {
|
|
101
|
+
const { realm, username, email, firstName, lastName } = CreateUserSchema.parse(args);
|
|
102
|
+
kcAdminClient.setConfig({
|
|
103
|
+
realmName: realm
|
|
104
|
+
});
|
|
105
|
+
const user = await kcAdminClient.users.create({
|
|
106
|
+
realm,
|
|
107
|
+
username,
|
|
108
|
+
email,
|
|
109
|
+
firstName,
|
|
110
|
+
lastName,
|
|
111
|
+
enabled: true
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
content: [{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: `User created successfully. User ID: ${user.id}`
|
|
117
|
+
}]
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
case "delete-user": {
|
|
121
|
+
const { realm, userId } = DeleteUserSchema.parse(args);
|
|
122
|
+
kcAdminClient.setConfig({
|
|
123
|
+
realmName: realm
|
|
124
|
+
});
|
|
125
|
+
await kcAdminClient.users.del({
|
|
126
|
+
id: userId,
|
|
127
|
+
realm
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
content: [{
|
|
131
|
+
type: "text",
|
|
132
|
+
text: `User ${userId} deleted successfully from realm ${realm}`
|
|
133
|
+
}]
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
case "list-realms": {
|
|
137
|
+
const realms = await kcAdminClient.realms.find();
|
|
138
|
+
return {
|
|
139
|
+
content: [{
|
|
140
|
+
type: "text",
|
|
141
|
+
text: `Available realms:\n${realms.map(r => `- ${r.realm}`).join('\n')}`
|
|
142
|
+
}]
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
case "list-users": {
|
|
146
|
+
const { realm } = ListUsersSchema.parse(args);
|
|
147
|
+
kcAdminClient.setConfig({
|
|
148
|
+
realmName: realm
|
|
149
|
+
});
|
|
150
|
+
const users = await kcAdminClient.users.find();
|
|
151
|
+
return {
|
|
152
|
+
content: [{
|
|
153
|
+
type: "text",
|
|
154
|
+
text: `Users in realm ${realm}:\n${users.map(u => `- ${u.username} (${u.id})`).join('\n')}`
|
|
155
|
+
}]
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
default:
|
|
159
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
if (error instanceof z.ZodError) {
|
|
164
|
+
return {
|
|
165
|
+
isError: true,
|
|
166
|
+
content: [{
|
|
167
|
+
type: "text",
|
|
168
|
+
text: `Invalid arguments: ${error.errors.map(e => `${e.path.join(".")}: ${e.message}`).join(", ")}`
|
|
169
|
+
}]
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
// Start the server
|
|
176
|
+
const transport = new StdioServerTransport();
|
|
177
|
+
await server.connect(transport);
|
|
178
|
+
console.error("Keycloak MCP Server running on stdio");
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "keycloak-model-context-protocol",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "MCP server for Keycloak administration",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Christoph Englisch",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"mcp-server-keycloak": "./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
|
+
}
|