keycloak-model-context-protocol 0.0.2 → 1.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -101
  3. package/dist/config.js +178 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/guards.js +47 -0
  6. package/dist/guards.js.map +1 -0
  7. package/dist/index.js +36 -174
  8. package/dist/index.js.map +1 -0
  9. package/dist/keycloak/errors.js +86 -0
  10. package/dist/keycloak/errors.js.map +1 -0
  11. package/dist/keycloak/gateway.js +131 -0
  12. package/dist/keycloak/gateway.js.map +1 -0
  13. package/dist/logging.js +49 -0
  14. package/dist/logging.js.map +1 -0
  15. package/dist/prompts/audit-realm-security.js +26 -0
  16. package/dist/prompts/audit-realm-security.js.map +1 -0
  17. package/dist/prompts/index.js +12 -0
  18. package/dist/prompts/index.js.map +1 -0
  19. package/dist/prompts/investigate-failed-logins.js +34 -0
  20. package/dist/prompts/investigate-failed-logins.js.map +1 -0
  21. package/dist/prompts/offboard-employee.js +31 -0
  22. package/dist/prompts/offboard-employee.js.map +1 -0
  23. package/dist/prompts/onboard-employee.js +50 -0
  24. package/dist/prompts/onboard-employee.js.map +1 -0
  25. package/dist/prompts/shared.js +46 -0
  26. package/dist/prompts/shared.js.map +1 -0
  27. package/dist/resources/index.js +8 -0
  28. package/dist/resources/index.js.map +1 -0
  29. package/dist/resources/realms.js +96 -0
  30. package/dist/resources/realms.js.map +1 -0
  31. package/dist/resources/shared.js +44 -0
  32. package/dist/resources/shared.js.map +1 -0
  33. package/dist/resources/users.js +26 -0
  34. package/dist/resources/users.js.map +1 -0
  35. package/dist/server.js +16 -0
  36. package/dist/server.js.map +1 -0
  37. package/dist/tools/clients.js +137 -0
  38. package/dist/tools/clients.js.map +1 -0
  39. package/dist/tools/events.js +161 -0
  40. package/dist/tools/events.js.map +1 -0
  41. package/dist/tools/groups.js +135 -0
  42. package/dist/tools/groups.js.map +1 -0
  43. package/dist/tools/index.js +31 -0
  44. package/dist/tools/index.js.map +1 -0
  45. package/dist/tools/realms.js +99 -0
  46. package/dist/tools/realms.js.map +1 -0
  47. package/dist/tools/roles.js +270 -0
  48. package/dist/tools/roles.js.map +1 -0
  49. package/dist/tools/security.js +58 -0
  50. package/dist/tools/security.js.map +1 -0
  51. package/dist/tools/sessions.js +68 -0
  52. package/dist/tools/sessions.js.map +1 -0
  53. package/dist/tools/shared.js +144 -0
  54. package/dist/tools/shared.js.map +1 -0
  55. package/dist/tools/users.js +272 -0
  56. package/dist/tools/users.js.map +1 -0
  57. package/dist/version.js +5 -0
  58. package/dist/version.js.map +1 -0
  59. package/package.json +47 -12
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Christoph Englisch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,42 +1,102 @@
1
1
  # Keycloak MCP Server
2
2
 
3
- A Model Context Protocol server for Keycloak administration, providing tools to manage users and realms.
3
+ [![npm version](https://img.shields.io/npm/v/keycloak-model-context-protocol.svg)](https://www.npmjs.com/package/keycloak-model-context-protocol)
4
+ [![CI](https://github.com/ChristophEnglisch/keycloak-model-context-protocol/actions/workflows/ci.yml/badge.svg)](https://github.com/ChristophEnglisch/keycloak-model-context-protocol/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+
7
+ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for **Keycloak administration**. It lets an AI assistant such as Claude manage users, groups, roles and sessions, and inspect clients, events and the security posture of your realms, through the Keycloak Admin REST API, with least-privilege authentication and guardrails around every write.
8
+
9
+ > [!WARNING]
10
+ > This server acts with the permissions of the Keycloak account you give it. Use a **dedicated service-account client with only the roles you need**, never the bootstrap admin. Destructive tools ask for confirmation by default, and a read-only mode is available. See [Security](#security).
11
+
12
+ ## Highlights
13
+
14
+ - **24 tools in 7 toolsets**, enabled per toolset. The default set (`core`, `groups`, `roles`) is small; everything else is opt-in.
15
+ - **Least-privilege auth.** `client_credentials` with a service account is the recommended grant; the password grant stays available. No credential defaults, so a missing setting fails fast.
16
+ - **Guardrails.** Read-only mode, realm allowlist, write protection for the `master` realm, and a preview-then-`confirm: true` flow for destructive operations.
17
+ - **4 guided prompts** (onboarding, offboarding, security audit, failed-login investigation) and **MCP resources** for realms and users, with realm-name completion.
18
+ - **Structured, paginated output** with output schemas; secrets and credentials are never returned or logged.
19
+ - Works with Claude Desktop, Claude Code, VS Code, Cursor and any stdio MCP client. Requires Node.js 22 or newer.
20
+
21
+ ## How it works
22
+
23
+ Your MCP client starts the server as a local child process and talks to it over stdio. Every tool call passes the guards, then goes through a single gateway that owns the Keycloak login and caches the access token.
24
+
25
+ ```mermaid
26
+ flowchart LR
27
+ subgraph Host["Your machine"]
28
+ Client["MCP client<br/>(Claude Desktop, Claude Code,<br/>VS Code, Cursor, ...)"]
29
+ subgraph Server["keycloak-model-context-protocol (Node.js)"]
30
+ Guards["Guards<br/>read-only, realm allowlist,<br/>master protection, confirmation"]
31
+ Toolsets["Toolsets, prompts, resources<br/>core, groups, roles, clients,<br/>sessions, events, security"]
32
+ Gateway["Gateway<br/>login and token cache"]
33
+ Guards --> Toolsets --> Gateway
34
+ end
35
+ Client <-->|"JSON-RPC over stdio"| Guards
36
+ end
37
+
38
+ subgraph KC["Keycloak"]
39
+ Token["Token endpoint"]
40
+ Admin["Admin REST API<br/>/admin/realms/..."]
41
+ end
42
+
43
+ Gateway -->|"client_credentials or password grant"| Token
44
+ Gateway -->|"Bearer token"| Admin
45
+ ```
4
46
 
5
- ## Features
47
+ ### What a destructive call looks like
48
+
49
+ A destructive tool never acts on the first call. It returns a preview of what would happen and changes nothing. Only after you approve does the assistant repeat the call with `confirm: true`.
50
+
51
+ ```mermaid
52
+ sequenceDiagram
53
+ autonumber
54
+ actor User
55
+ participant LLM as MCP client / LLM
56
+ participant MCP as Keycloak MCP server
57
+ participant KC as Keycloak
58
+
59
+ User->>LLM: "Delete user bob from realm demo"
60
+ LLM->>MCP: user_delete (realm, userId)
61
+ MCP->>KC: GET user (for the preview)
62
+ KC-->>MCP: user data
63
+ MCP-->>LLM: isError, "Confirmation required. This would: permanently delete user bob ... Nothing was changed."
64
+ LLM->>User: "This will permanently delete bob. Proceed?"
65
+ User->>LLM: "Yes"
66
+ LLM->>MCP: user_delete (realm, userId, confirm: true)
67
+ MCP->>KC: DELETE user
68
+ KC-->>MCP: 204 No Content
69
+ MCP-->>LLM: deleted: true
70
+ LLM-->>User: "User bob was deleted."
71
+ ```
6
72
 
7
- - Create new users in specific realms
8
- - Delete users from realms
9
- - List available realms
10
- - List users in specific realms
73
+ ## Quick start
11
74
 
12
- ## Installation
75
+ **Requirements:** Node.js 22 or newer, a Keycloak server your machine can reach (tested with Keycloak 24.0 to 26.8), and an MCP client.
13
76
 
14
- ### Via NPM (Recommended)
77
+ ### 1. Prepare Keycloak (3 steps)
15
78
 
16
- The server is available as an NPM package:
17
- ```bash
18
- # Direct usage with npx
19
- npx -y keycloak-model-context-protocol
79
+ In the realm you want to administer (here `demo`):
80
+ 1. **Create a client** `mcp-admin` with _Client authentication_ **on** and only _Service accounts roles_ enabled (turn off _Standard flow_ and _Direct access grants_).
81
+ 2. **Assign roles.** On the client's _Service accounts roles_ tab, assign client roles of `realm-management`. For a first read-only trial: `view-users`, `query-users`, `query-groups`, `view-realm`.
82
+ 3. **Copy the secret** from the client's _Credentials_ tab.
20
83
 
21
- # Or global installation
22
- npm install -g keycloak-model-context-protocol
23
- ```
84
+ Step-by-step instructions with Admin Console click paths, `kcadm.sh` commands and a local Docker Keycloak: [docs/getting-started.md](docs/getting-started.md).
24
85
 
25
- ### Local Development Setup
86
+ ### 2. Configure your MCP client
26
87
 
27
- If you want to develop or modify the server:
88
+ Example for Claude Code (other clients: [docs/clients.md](docs/clients.md)):
28
89
 
29
90
  ```bash
30
- git clone <repository-url>
31
- cd keycloak-model-context-protocol
32
- npm install
33
- npm run build
91
+ claude mcp add --transport stdio \
92
+ --env KEYCLOAK_URL=https://keycloak.example.com \
93
+ --env KEYCLOAK_AUTH_REALM=demo \
94
+ --env KEYCLOAK_CLIENT_ID=mcp-admin \
95
+ --env KEYCLOAK_CLIENT_SECRET="$KEYCLOAK_CLIENT_SECRET" \
96
+ keycloak -- npx -y keycloak-model-context-protocol
34
97
  ```
35
98
 
36
- ## Configuration
37
-
38
- ### Using NPM Package (Recommended)
39
- Configure the server in your Claude Desktop configuration file:
99
+ Or as JSON (Claude Desktop, `claude_desktop_config.json`):
40
100
 
41
101
  ```json
42
102
  {
@@ -45,89 +105,116 @@ Configure the server in your Claude Desktop configuration file:
45
105
  "command": "npx",
46
106
  "args": ["-y", "keycloak-model-context-protocol"],
47
107
  "env": {
48
- "KEYCLOAK_URL": "http://localhost:8080",
49
- "KEYCLOAK_ADMIN": "admin",
50
- "KEYCLOAK_ADMIN_PASSWORD": "admin"
108
+ "KEYCLOAK_URL": "https://keycloak.example.com",
109
+ "KEYCLOAK_AUTH_REALM": "demo",
110
+ "KEYCLOAK_CLIENT_ID": "mcp-admin",
111
+ "KEYCLOAK_CLIENT_SECRET": "<client-secret>"
51
112
  }
52
113
  }
53
114
  }
54
115
  }
55
116
  ```
56
117
 
57
- ### For Local Development
58
- ```json
59
- {
60
- "mcpServers": {
61
- "keycloak": {
62
- "command": "node",
63
- "args": ["path/to/dist/index.js"],
64
- "env": {
65
- "KEYCLOAK_URL": "http://localhost:8080",
66
- "KEYCLOAK_ADMIN": "admin",
67
- "KEYCLOAK_ADMIN_PASSWORD": "admin"
68
- }
69
- }
70
- }
71
- }
72
- ```
73
-
74
- ## Available Tools
75
-
76
- ### create-user
77
- Creates a new user in a specified realm.
78
-
79
- **Inputs**:
80
- - `realm`: The realm name
81
- - `username`: Username for the new user
82
- - `email`: Email address for the user
83
- - `firstName`: User's first name
84
- - `lastName`: User's last name
118
+ ### 3. Ask your first question
85
119
 
86
- ### delete-user
87
- Deletes a user from a specified realm.
120
+ > _List the realms you can access, then show me the first 10 users of realm demo._
88
121
 
89
- **Inputs**:
90
- - `realm`: The realm name
91
- - `userId`: The ID of the user to delete
122
+ If you get an answer, you are done. If not, see [docs/troubleshooting.md](docs/troubleshooting.md).
92
123
 
93
- ### list-realms
94
- Lists all available realms.
95
-
96
- ### list-users
97
- Lists all users in a specified realm.
98
-
99
- **Inputs**:
100
- - `realm`: The realm name
101
-
102
- ## Development
103
-
104
- ```bash
105
- npm run watch
106
- ```
107
-
108
- ## Testing
109
-
110
- To test the server using MCP Inspector:
111
-
112
- ```bash
113
- npx -y @modelcontextprotocol/inspector npx -y keycloak-model-context-protocol
114
- ```
115
-
116
- ## Deployment
117
-
118
- ### NPM Package
119
-
120
- 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.
121
-
122
- #### Setup Requirements for Deployment
123
-
124
- 1. Create NPM account and get access token
125
- 2. Add NPM_TOKEN secret to GitHub repository
126
- - Go to repository Settings > Secrets
127
- - Add new secret named `NPM_TOKEN`
128
- - Paste your NPM access token as the value
129
-
130
- ## Prerequisites
124
+ ## Configuration
131
125
 
132
- - Node.js 18 or higher
133
- - Running Keycloak instance
126
+ All settings are environment variables. Only `KEYCLOAK_URL` and one set of credentials are required.
127
+
128
+ | Variable | Default | Purpose |
129
+ | ----------------------------------------------- | ------------------- | -------------------------------------------------------------- |
130
+ | `KEYCLOAK_URL` | (required) | Base URL of the Keycloak server |
131
+ | `KEYCLOAK_AUTH_REALM` | `master` | Realm where the login account or client lives |
132
+ | `KEYCLOAK_CLIENT_ID` / `KEYCLOAK_CLIENT_SECRET` | none | `client_credentials` login (recommended) |
133
+ | `KEYCLOAK_USERNAME` / `KEYCLOAK_PASSWORD` | none | Password grant login (client defaults to `admin-cli`) |
134
+ | `KEYCLOAK_GRANT_TYPE` | auto | `client_credentials` or `password`; detected from what you set |
135
+ | `KEYCLOAK_MCP_TOOLSETS` | `core,groups,roles` | Enabled toolsets, or `all` |
136
+ | `KEYCLOAK_MCP_READ_ONLY` | `false` | Register read-only tools only |
137
+ | `KEYCLOAK_MCP_REALMS` | all | Comma-separated allowlist of target realms |
138
+ | `KEYCLOAK_MCP_ALLOW_MASTER_WRITE` | `false` | Allow write tools in the `master` realm |
139
+ | `KEYCLOAK_MCP_REQUIRE_CONFIRMATION` | `true` | Destructive tools need `confirm: true` |
140
+ | `KEYCLOAK_MCP_TIMEOUT_MS` | `30000` | HTTP timeout for Keycloak requests |
141
+ | `KEYCLOAK_MCP_LOG_LEVEL` | `info` | `debug`, `info`, `warn` or `error` (stderr) |
142
+
143
+ Full reference with examples, validation rules and the tool-to-role matrix: [docs/configuration.md](docs/configuration.md).
144
+
145
+ ## Toolsets and tools
146
+
147
+ Tools are named `<domain>_<verb>`. Only the **default** toolsets are enabled unless you set `KEYCLOAK_MCP_TOOLSETS`. In read-only mode only tools of kind _read_ are registered.
148
+
149
+ | Toolset | Default | Tools |
150
+ | ---------- | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------ |
151
+ | `core` | yes | `realm_list`, `realm_get`, `user_search`, `user_get`, `user_create`, `user_update`, `user_delete` (destructive), `user_send_actions_email` |
152
+ | `groups` | yes | `group_list`, `group_members_list`, `user_group_add`, `user_group_remove` (destructive) |
153
+ | `roles` | yes | `role_list`, `user_roles_list`, `user_role_assign` (confirmation for admin roles), `user_role_revoke` (destructive) |
154
+ | `clients` | no | `client_list`, `client_get` |
155
+ | `sessions` | no | `user_sessions_list`, `user_logout` (destructive) |
156
+ | `events` | no | `event_list`, `admin_event_list` |
157
+ | `security` | no | `user_brute_force_status`, `user_brute_force_unlock` |
158
+
159
+ Details for every tool (parameters, outputs, required roles): [docs/tools-reference.md](docs/tools-reference.md).
160
+
161
+ ## Prompts and resources
162
+
163
+ **Prompts** are guided workflows built on the tools. They appear in your client's prompt picker (in Claude Code as slash commands). The `realm` argument autocompletes.
164
+
165
+ | Prompt | What it does |
166
+ | --------------------------- | --------------------------------------------------------------------------------- |
167
+ | `onboard_employee` | Create a user, add groups and roles, send the welcome e-mail |
168
+ | `offboard_employee` | Review, end sessions and disable a departing employee's account (does not delete) |
169
+ | `audit_realm_security` | Read-only security review with prioritized findings |
170
+ | `investigate_failed_logins` | Analyze failed logins and lockout status without changing anything |
171
+
172
+ **Resources** are read-only JSON documents, filtered by the same realm allowlist:
173
+
174
+ | URI | Content |
175
+ | -------------------------------------------- | ---------------------------------------------------------------------------------------- |
176
+ | `keycloak://realms` | Names of accessible realms |
177
+ | `keycloak://realms/{realm}` | Safe subset of the realm settings |
178
+ | `keycloak://realms/{realm}/security-summary` | Brute-force, password policy, SSL, OTP, event flags, client exposure counts (no secrets) |
179
+ | `keycloak://realms/{realm}/users/{userId}` | One user's basic fields (no attributes, no credentials) |
180
+
181
+ ## Security
182
+
183
+ - **Least privilege first.** Use a service-account client scoped to one realm with only the `realm-management` roles you need. A role matrix is in [docs/configuration.md](docs/configuration.md#minimal-roles-per-tool).
184
+ - **Destructive operations need confirmation** (`KEYCLOAK_MCP_REQUIRE_CONFIRMATION=true`), and granting administrative roles does too.
185
+ - **Blast-radius limits:** `KEYCLOAK_MCP_READ_ONLY`, `KEYCLOAK_MCP_REALMS`, and write protection for `master` (off by default).
186
+ - **No secrets in output or logs.** Credentials are masked on stderr; tools never return client secrets, user credentials or attributes.
187
+ - **Prompt injection:** treat names and descriptions coming from Keycloak as data. The shipped prompts say so explicitly; keep write tools disabled when you do not need them.
188
+
189
+ Threat model, hardening checklist and how to report a vulnerability: [docs/security.md](docs/security.md) and [SECURITY.md](SECURITY.md).
190
+
191
+ ## Example prompts
192
+
193
+ - "Which realms can you see? Show me the settings of realm `demo`."
194
+ - "Find all users in `demo` whose e-mail ends with `@example.com` and who are disabled."
195
+ - "Create user `jdoe` (Jane Doe, jane@example.com) in `demo`, add her to group `engineering` and send her an e-mail to set a password."
196
+ - "Who is in the group `ops`, and which roles does `alice` have, including composite roles?"
197
+ - "Run the `audit_realm_security` prompt for `demo`." _(needs `clients` and `events` toolsets for the full audit)_
198
+ - "There were many failed logins last night. Investigate realm `demo`."
199
+ - "Offboard `bob` in `demo`: end his sessions and disable the account."
200
+
201
+ ## Documentation
202
+
203
+ | Document | Content |
204
+ | -------------------------------------------------- | --------------------------------------------------------------- |
205
+ | [docs/getting-started.md](docs/getting-started.md) | Local Keycloak, service-account setup, first run, MCP Inspector |
206
+ | [docs/configuration.md](docs/configuration.md) | Environment reference, role matrix, role profiles, TLS |
207
+ | [docs/clients.md](docs/clients.md) | Claude Desktop, Claude Code, VS Code, Cursor, generic clients |
208
+ | [docs/tools-reference.md](docs/tools-reference.md) | Every tool in detail |
209
+ | [docs/security.md](docs/security.md) | Threat model and hardening |
210
+ | [docs/troubleshooting.md](docs/troubleshooting.md) | Symptoms, causes, fixes |
211
+ | [docs/architecture.md](docs/architecture.md) | Components, flows, design decisions |
212
+ | [CHANGELOG.md](CHANGELOG.md) | Release notes |
213
+
214
+ ## Upgrade from 0.x
215
+
216
+ Version 1.0 changes the defaults: credentials are required (no `admin`/`admin` fallback), destructive tools ask for confirmation, and the tools were renamed to `<domain>_<verb>` (for example `create-user` became `user_create`, `delete-user` became `user_delete`). The old `KEYCLOAK_ADMIN` and `KEYCLOAK_ADMIN_PASSWORD` variables still work but are deprecated. See the [migration guide](docs/migration-1.0.md).
217
+
218
+ ## Contributing and license
219
+
220
+ Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) and the [Code of Conduct](CODE_OF_CONDUCT.md). Released under the [MIT License](LICENSE).
package/dist/config.js ADDED
@@ -0,0 +1,178 @@
1
+ import { z } from "zod";
2
+ import { LOG_LEVELS } from "./logging.js";
3
+ export const TOOLSET_NAMES = [
4
+ "core",
5
+ "groups",
6
+ "roles",
7
+ "clients",
8
+ "sessions",
9
+ "events",
10
+ "security",
11
+ ];
12
+ export const DEFAULT_TOOLSETS = ["core", "groups", "roles"];
13
+ export class ConfigError extends Error {
14
+ issues;
15
+ constructor(issues) {
16
+ super(`Invalid configuration:\n${issues.map((issue) => ` - ${issue}`).join("\n")}`);
17
+ this.name = "ConfigError";
18
+ this.issues = issues;
19
+ }
20
+ }
21
+ const blankToUndefined = (value) => {
22
+ if (typeof value !== "string")
23
+ return value;
24
+ const trimmed = value.trim();
25
+ return trimmed === "" ? undefined : trimmed;
26
+ };
27
+ const optionalString = z.preprocess(blankToUndefined, z.string().optional());
28
+ const flag = (fallback) => z.preprocess(blankToUndefined, z.stringbool().default(fallback));
29
+ const commaList = z.preprocess(blankToUndefined, z
30
+ .string()
31
+ .optional()
32
+ .transform((value) => (value ?? "")
33
+ .split(",")
34
+ .map((item) => item.trim())
35
+ .filter((item) => item.length > 0)));
36
+ const EnvSchema = z.object({
37
+ KEYCLOAK_URL: z.preprocess(blankToUndefined, z
38
+ .string({ error: "is required (base URL of the Keycloak server)" })
39
+ .pipe(z.url({ protocol: /^https?$/, error: "must be a valid http(s) URL" }))),
40
+ KEYCLOAK_AUTH_REALM: z.preprocess(blankToUndefined, z.string().default("master")),
41
+ KEYCLOAK_CLIENT_ID: optionalString,
42
+ KEYCLOAK_CLIENT_SECRET: optionalString,
43
+ KEYCLOAK_USERNAME: optionalString,
44
+ KEYCLOAK_PASSWORD: optionalString,
45
+ KEYCLOAK_ADMIN: optionalString,
46
+ KEYCLOAK_ADMIN_PASSWORD: optionalString,
47
+ KEYCLOAK_GRANT_TYPE: z.preprocess(blankToUndefined, z
48
+ .enum(["client_credentials", "password"], {
49
+ error: "must be 'client_credentials' or 'password'",
50
+ })
51
+ .optional()),
52
+ KEYCLOAK_MCP_READ_ONLY: flag(false),
53
+ KEYCLOAK_MCP_REALMS: commaList,
54
+ KEYCLOAK_MCP_ALLOW_MASTER_WRITE: flag(false),
55
+ KEYCLOAK_MCP_TOOLSETS: commaList,
56
+ KEYCLOAK_MCP_REQUIRE_CONFIRMATION: flag(true),
57
+ KEYCLOAK_MCP_TIMEOUT_MS: z.preprocess(blankToUndefined, z.coerce
58
+ .number({ error: "must be a number of milliseconds" })
59
+ .int({ error: "must be an integer" })
60
+ .min(1, { error: "must be at least 1" })
61
+ .default(30_000)),
62
+ KEYCLOAK_MCP_LOG_LEVEL: z.preprocess(blankToUndefined, z.enum(LOG_LEVELS, { error: `must be one of: ${LOG_LEVELS.join(", ")}` }).default("info")),
63
+ });
64
+ function resolveToolsets(names, issues) {
65
+ if (names.length === 0)
66
+ return new Set(DEFAULT_TOOLSETS);
67
+ if (names.includes("all"))
68
+ return new Set(TOOLSET_NAMES);
69
+ const result = new Set();
70
+ for (const name of names) {
71
+ if (TOOLSET_NAMES.includes(name)) {
72
+ result.add(name);
73
+ }
74
+ else {
75
+ issues.push(`KEYCLOAK_MCP_TOOLSETS: unknown toolset '${name}' (valid: ${TOOLSET_NAMES.join(", ")}, all)`);
76
+ }
77
+ }
78
+ return result;
79
+ }
80
+ /**
81
+ * Parses and validates the environment. There are deliberately no credential
82
+ * defaults: missing or contradictory settings fail fast with a ConfigError.
83
+ */
84
+ export function loadConfig(env = process.env) {
85
+ const parsed = EnvSchema.safeParse(env);
86
+ if (!parsed.success) {
87
+ throw new ConfigError(parsed.error.issues.map((issue) => `${issue.path.join(".") || "env"}: ${issue.message}`));
88
+ }
89
+ const e = parsed.data;
90
+ const issues = [];
91
+ const warnings = [];
92
+ const username = e.KEYCLOAK_USERNAME ?? e.KEYCLOAK_ADMIN;
93
+ const password = e.KEYCLOAK_PASSWORD ?? e.KEYCLOAK_ADMIN_PASSWORD;
94
+ if (e.KEYCLOAK_ADMIN !== undefined && e.KEYCLOAK_USERNAME === undefined) {
95
+ warnings.push("KEYCLOAK_ADMIN is deprecated; use KEYCLOAK_USERNAME instead.");
96
+ }
97
+ if (e.KEYCLOAK_ADMIN_PASSWORD !== undefined && e.KEYCLOAK_PASSWORD === undefined) {
98
+ warnings.push("KEYCLOAK_ADMIN_PASSWORD is deprecated; use KEYCLOAK_PASSWORD instead.");
99
+ }
100
+ const grantType = e.KEYCLOAK_GRANT_TYPE ??
101
+ (e.KEYCLOAK_CLIENT_SECRET !== undefined
102
+ ? "client_credentials"
103
+ : username !== undefined
104
+ ? "password"
105
+ : undefined);
106
+ let credentials;
107
+ if (grantType === undefined) {
108
+ issues.push("No credentials configured: set KEYCLOAK_CLIENT_ID + KEYCLOAK_CLIENT_SECRET (client_credentials) or KEYCLOAK_USERNAME + KEYCLOAK_PASSWORD (password).");
109
+ }
110
+ else if (grantType === "client_credentials") {
111
+ if (e.KEYCLOAK_CLIENT_ID === undefined) {
112
+ issues.push("KEYCLOAK_CLIENT_ID: is required for the client_credentials grant");
113
+ }
114
+ if (e.KEYCLOAK_CLIENT_SECRET === undefined) {
115
+ issues.push("KEYCLOAK_CLIENT_SECRET: is required for the client_credentials grant");
116
+ }
117
+ if (e.KEYCLOAK_CLIENT_ID !== undefined && e.KEYCLOAK_CLIENT_SECRET !== undefined) {
118
+ credentials = {
119
+ grantType,
120
+ clientId: e.KEYCLOAK_CLIENT_ID,
121
+ clientSecret: e.KEYCLOAK_CLIENT_SECRET,
122
+ };
123
+ }
124
+ }
125
+ else {
126
+ if (username === undefined)
127
+ issues.push("KEYCLOAK_USERNAME: is required for the password grant");
128
+ if (password === undefined)
129
+ issues.push("KEYCLOAK_PASSWORD: is required for the password grant");
130
+ if (username !== undefined && password !== undefined) {
131
+ credentials = {
132
+ grantType,
133
+ clientId: e.KEYCLOAK_CLIENT_ID ?? "admin-cli",
134
+ ...(e.KEYCLOAK_CLIENT_SECRET !== undefined && { clientSecret: e.KEYCLOAK_CLIENT_SECRET }),
135
+ username,
136
+ password,
137
+ };
138
+ }
139
+ }
140
+ const toolsets = resolveToolsets(e.KEYCLOAK_MCP_TOOLSETS, issues);
141
+ if (issues.length > 0 || credentials === undefined) {
142
+ throw new ConfigError(issues);
143
+ }
144
+ const { protocol, hostname } = new URL(e.KEYCLOAK_URL);
145
+ if (protocol === "http:" && !isLoopbackHost(hostname)) {
146
+ warnings.push(`KEYCLOAK_URL uses plain http:// for the remote host '${hostname}'; tokens and credentials travel unencrypted. Use https://.`);
147
+ }
148
+ return {
149
+ url: e.KEYCLOAK_URL.replace(/\/+$/, ""),
150
+ authRealm: e.KEYCLOAK_AUTH_REALM,
151
+ credentials,
152
+ readOnly: e.KEYCLOAK_MCP_READ_ONLY,
153
+ allowedRealms: e.KEYCLOAK_MCP_REALMS,
154
+ allowMasterWrite: e.KEYCLOAK_MCP_ALLOW_MASTER_WRITE,
155
+ toolsets,
156
+ requireConfirmation: e.KEYCLOAK_MCP_REQUIRE_CONFIRMATION,
157
+ timeoutMs: e.KEYCLOAK_MCP_TIMEOUT_MS,
158
+ logLevel: e.KEYCLOAK_MCP_LOG_LEVEL,
159
+ warnings,
160
+ };
161
+ }
162
+ function isLoopbackHost(hostname) {
163
+ return (hostname === "localhost" ||
164
+ hostname.endsWith(".localhost") ||
165
+ hostname === "[::1]" ||
166
+ hostname.startsWith("127."));
167
+ }
168
+ /** Values that must be masked in logs. */
169
+ export function collectSecrets(config) {
170
+ const { credentials } = config;
171
+ const secrets = [];
172
+ if (credentials.clientSecret)
173
+ secrets.push(credentials.clientSecret);
174
+ if (credentials.grantType === "password")
175
+ secrets.push(credentials.password);
176
+ return secrets;
177
+ }
178
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAiB,MAAM,cAAc,CAAC;AAEzD,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,MAAM;IACN,QAAQ;IACR,OAAO;IACP,SAAS;IACT,UAAU;IACV,QAAQ;IACR,UAAU;CACF,CAAC;AAGX,MAAM,CAAC,MAAM,gBAAgB,GAA2B,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AA4BpF,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,MAAM,CAAoB;IAEnC,YAAY,MAAyB;QACnC,KAAK,CAAC,2BAA2B,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrF,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAW,EAAE;IACnD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;AAC9C,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;AAC7E,MAAM,IAAI,GAAG,CAAC,QAAiB,EAAE,EAAE,CACjC,CAAC,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;AACnE,MAAM,SAAS,GAAG,CAAC,CAAC,UAAU,CAC5B,gBAAgB,EAChB,CAAC;KACE,MAAM,EAAE;KACR,QAAQ,EAAE;KACV,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CACnB,CAAC,KAAK,IAAI,EAAE,CAAC;KACV,KAAK,CAAC,GAAG,CAAC;KACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;KAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CACrC,CACJ,CAAC;AAEF,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC;IACzB,YAAY,EAAE,CAAC,CAAC,UAAU,CACxB,gBAAgB,EAChB,CAAC;SACE,MAAM,CAAC,EAAE,KAAK,EAAE,+CAA+C,EAAE,CAAC;SAClE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,6BAA6B,EAAE,CAAC,CAAC,CAC/E;IACD,mBAAmB,EAAE,CAAC,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjF,kBAAkB,EAAE,cAAc;IAClC,sBAAsB,EAAE,cAAc;IACtC,iBAAiB,EAAE,cAAc;IACjC,iBAAiB,EAAE,cAAc;IACjC,cAAc,EAAE,cAAc;IAC9B,uBAAuB,EAAE,cAAc;IACvC,mBAAmB,EAAE,CAAC,CAAC,UAAU,CAC/B,gBAAgB,EAChB,CAAC;SACE,IAAI,CAAC,CAAC,oBAAoB,EAAE,UAAU,CAAC,EAAE;QACxC,KAAK,EAAE,4CAA4C;KACpD,CAAC;SACD,QAAQ,EAAE,CACd;IACD,sBAAsB,EAAE,IAAI,CAAC,KAAK,CAAC;IACnC,mBAAmB,EAAE,SAAS;IAC9B,+BAA+B,EAAE,IAAI,CAAC,KAAK,CAAC;IAC5C,qBAAqB,EAAE,SAAS;IAChC,iCAAiC,EAAE,IAAI,CAAC,IAAI,CAAC;IAC7C,uBAAuB,EAAE,CAAC,CAAC,UAAU,CACnC,gBAAgB,EAChB,CAAC,CAAC,MAAM;SACL,MAAM,CAAC,EAAE,KAAK,EAAE,kCAAkC,EAAE,CAAC;SACrD,GAAG,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;SACpC,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;SACvC,OAAO,CAAC,MAAM,CAAC,CACnB;IACD,sBAAsB,EAAE,CAAC,CAAC,UAAU,CAClC,gBAAgB,EAChB,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,mBAAmB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAC1F;CACF,CAAC,CAAC;AAEH,SAAS,eAAe,CAAC,KAAwB,EAAE,MAAgB;IACjE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACzD,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,GAAG,CAAC,aAAa,CAAC,CAAC;IACzD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAe,CAAC;IACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAK,aAAmC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACxD,MAAM,CAAC,GAAG,CAAC,IAAmB,CAAC,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CACT,2CAA2C,IAAI,aAAa,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAC7F,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG;IAC7D,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,WAAW,CACnB,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CACzF,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC;IACtB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,MAAM,QAAQ,GAAG,CAAC,CAAC,iBAAiB,IAAI,CAAC,CAAC,cAAc,CAAC;IACzD,MAAM,QAAQ,GAAG,CAAC,CAAC,iBAAiB,IAAI,CAAC,CAAC,uBAAuB,CAAC;IAClE,IAAI,CAAC,CAAC,cAAc,KAAK,SAAS,IAAI,CAAC,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;QACxE,QAAQ,CAAC,IAAI,CAAC,8DAA8D,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,CAAC,CAAC,uBAAuB,KAAK,SAAS,IAAI,CAAC,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;QACjF,QAAQ,CAAC,IAAI,CAAC,uEAAuE,CAAC,CAAC;IACzF,CAAC;IAED,MAAM,SAAS,GACb,CAAC,CAAC,mBAAmB;QACrB,CAAC,CAAC,CAAC,sBAAsB,KAAK,SAAS;YACrC,CAAC,CAAC,oBAAoB;YACtB,CAAC,CAAC,QAAQ,KAAK,SAAS;gBACtB,CAAC,CAAC,UAAU;gBACZ,CAAC,CAAC,SAAS,CAAC,CAAC;IAEnB,IAAI,WAAoC,CAAC;IACzC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,CACT,sJAAsJ,CACvJ,CAAC;IACJ,CAAC;SAAM,IAAI,SAAS,KAAK,oBAAoB,EAAE,CAAC;QAC9C,IAAI,CAAC,CAAC,kBAAkB,KAAK,SAAS,EAAE,CAAC;YACvC,MAAM,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;QAClF,CAAC;QACD,IAAI,CAAC,CAAC,sBAAsB,KAAK,SAAS,EAAE,CAAC;YAC3C,MAAM,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;QACtF,CAAC;QACD,IAAI,CAAC,CAAC,kBAAkB,KAAK,SAAS,IAAI,CAAC,CAAC,sBAAsB,KAAK,SAAS,EAAE,CAAC;YACjF,WAAW,GAAG;gBACZ,SAAS;gBACT,QAAQ,EAAE,CAAC,CAAC,kBAAkB;gBAC9B,YAAY,EAAE,CAAC,CAAC,sBAAsB;aACvC,CAAC;QACJ,CAAC;IACH,CAAC;SAAM,CAAC;QACN,IAAI,QAAQ,KAAK,SAAS;YACxB,MAAM,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;QACvE,IAAI,QAAQ,KAAK,SAAS;YACxB,MAAM,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;QACvE,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YACrD,WAAW,GAAG;gBACZ,SAAS;gBACT,QAAQ,EAAE,CAAC,CAAC,kBAAkB,IAAI,WAAW;gBAC7C,GAAG,CAAC,CAAC,CAAC,sBAAsB,KAAK,SAAS,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,sBAAsB,EAAE,CAAC;gBACzF,QAAQ;gBACR,QAAQ;aACT,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,eAAe,CAAC,CAAC,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;IAElE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QACnD,MAAM,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAED,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IACvD,IAAI,QAAQ,KAAK,OAAO,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtD,QAAQ,CAAC,IAAI,CACX,wDAAwD,QAAQ,6DAA6D,CAC9H,CAAC;IACJ,CAAC;IAED,OAAO;QACL,GAAG,EAAE,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QACvC,SAAS,EAAE,CAAC,CAAC,mBAAmB;QAChC,WAAW;QACX,QAAQ,EAAE,CAAC,CAAC,sBAAsB;QAClC,aAAa,EAAE,CAAC,CAAC,mBAAmB;QACpC,gBAAgB,EAAE,CAAC,CAAC,+BAA+B;QACnD,QAAQ;QACR,mBAAmB,EAAE,CAAC,CAAC,iCAAiC;QACxD,SAAS,EAAE,CAAC,CAAC,uBAAuB;QACpC,QAAQ,EAAE,CAAC,CAAC,sBAAsB;QAClC,QAAQ;KACT,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB;IACtC,OAAO,CACL,QAAQ,KAAK,WAAW;QACxB,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC/B,QAAQ,KAAK,OAAO;QACpB,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAC5B,CAAC;AACJ,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,cAAc,CAAC,MAAmC;IAChE,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAC/B,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,WAAW,CAAC,YAAY;QAAE,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IACrE,IAAI,WAAW,CAAC,SAAS,KAAK,UAAU;QAAE,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC7E,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/dist/guards.js ADDED
@@ -0,0 +1,47 @@
1
+ import { ToolError } from "./keycloak/errors.js";
2
+ function hasControlChars(value) {
3
+ for (const char of value) {
4
+ const code = char.codePointAt(0) ?? 0;
5
+ if (code < 0x20 || code === 0x7f)
6
+ return true;
7
+ }
8
+ return false;
9
+ }
10
+ /**
11
+ * Validates a value that ends up as a URL path segment (realm name, user id, ...).
12
+ * Rejects separators, dot segments and control characters.
13
+ */
14
+ export function assertPathSegment(value, label = "value") {
15
+ if (value.length === 0 ||
16
+ value.length > 255 ||
17
+ value === "." ||
18
+ value === ".." ||
19
+ value.includes("/") ||
20
+ value.includes("\\") ||
21
+ hasControlChars(value)) {
22
+ throw new ToolError("guard", `Invalid ${label}: must be non-empty and must not contain '/', '\\', '..' or control characters.`);
23
+ }
24
+ }
25
+ /** Checks the realm against the allowlist and the master write protection. */
26
+ export function assertRealmAllowed(policy, realm, options) {
27
+ assertPathSegment(realm, "realm");
28
+ if (policy.allowedRealms.length > 0 && !policy.allowedRealms.includes(realm)) {
29
+ throw new ToolError("guard", `Realm '${realm}' is not permitted by KEYCLOAK_MCP_REALMS (allowed: ${policy.allowedRealms.join(", ")}).`);
30
+ }
31
+ if (options.write && realm.toLowerCase() === "master" && !policy.allowMasterWrite) {
32
+ throw new ToolError("guard", "Write operations in the 'master' realm are disabled. Set KEYCLOAK_MCP_ALLOW_MASTER_WRITE=true to allow them.");
33
+ }
34
+ }
35
+ export function isRealmAllowed(policy, realm) {
36
+ return policy.allowedRealms.length === 0 || policy.allowedRealms.includes(realm);
37
+ }
38
+ /**
39
+ * Destructive tools must be called with confirm=true when confirmation is required.
40
+ * Throws a ToolError carrying the preview so the model can ask the user and retry.
41
+ */
42
+ export function requireConfirmation(policy, confirm, preview) {
43
+ if (!policy.requireConfirmation || confirm === true)
44
+ return;
45
+ throw new ToolError("confirmation_required", `Confirmation required. This would: ${preview} Nothing was changed. Ask the user for approval, then call this tool again with confirm: true.`);
46
+ }
47
+ //# sourceMappingURL=guards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guards.js","sourceRoot":"","sources":["../src/guards.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAIjD,SAAS,eAAe,CAAC,KAAa;IACpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,IAAI,GAAG,IAAI,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;IAChD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,KAAK,GAAG,OAAO;IAC9D,IACE,KAAK,CAAC,MAAM,KAAK,CAAC;QAClB,KAAK,CAAC,MAAM,GAAG,GAAG;QAClB,KAAK,KAAK,GAAG;QACb,KAAK,KAAK,IAAI;QACd,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;QACnB,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QACpB,eAAe,CAAC,KAAK,CAAC,EACtB,CAAC;QACD,MAAM,IAAI,SAAS,CACjB,OAAO,EACP,WAAW,KAAK,iFAAiF,CAClG,CAAC;IACJ,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,kBAAkB,CAChC,MAAmB,EACnB,KAAa,EACb,OAA2B;IAE3B,iBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAClC,IAAI,MAAM,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7E,MAAM,IAAI,SAAS,CACjB,OAAO,EACP,UAAU,KAAK,uDAAuD,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAC1G,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC;QAClF,MAAM,IAAI,SAAS,CACjB,OAAO,EACP,8GAA8G,CAC/G,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAmB,EAAE,KAAa;IAC/D,OAAO,MAAM,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACnF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAA2C,EAC3C,OAA4B,EAC5B,OAAe;IAEf,IAAI,CAAC,MAAM,CAAC,mBAAmB,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO;IAC5D,MAAM,IAAI,SAAS,CACjB,uBAAuB,EACvB,sCAAsC,OAAO,gGAAgG,CAC9I,CAAC;AACJ,CAAC"}