关于 Keycloak Admin API
非官方的 Keycloak Admin 和 Account API 软件包,同时包含 stdio MCP 服务。适用于需要类型化 API 访问或通过 Agent 管理 realm 的开发者和运维人员。
主要功能
- 01生成类型化 Admin 和 Account API 客户端
- 02为每项 realm 管理操作提供 MCP 工具
- 03默认仅提供只读工具
- 04通过环境变量启用写入工具
- 05将工具限制为选定资源
适用场景
- 01检查 realm 中的用户、群组和角色
- 02让 Agent 查询 Keycloak 管理数据
- 03将可用工具限制为指定资源
- 04启用选定的数据变更操作
MCP 相关
附带 stdio MCP 服务,将 realm 范围内的管理操作作为工具提供。默认列出 GET 工具;设置 KEYCLOAK_MCP_ALLOW_WRITES=1 后会加入写入操作。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y keycloak-apiKEYCLOAK_URL密钥Base URL of the server, such as https://keycloak.example.comKEYCLOAK_REALM密钥Realm the tools act onKEYCLOAK_TOKEN密钥A fixed access token; otherwise set KEYCLOAK_CLIENT_ID and KEYCLOAK_CLIENT_SECRETKEYCLOAK_CLIENT_ID密钥A confidential client with a service account, for the client credentials grantKEYCLOAK_CLIENT_SECRET密钥Secret of that clientKEYCLOAK_TOKEN_REALM密钥Realm of that client, if not KEYCLOAK_REALM (for example master)KEYCLOAK_MCP_ALLOW_WRITES密钥1 to list the tools that change dataKEYCLOAK_MCP_RESOURCES密钥Comma separated resources to list tools for, such as users,groups,roles本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
keycloak-api
非官方项目,未由 Keycloak 制作或认可。
为 Keycloak Admin 和 Account REST API 自动生成的完全类型化客户端。
根据 Admin OpenAPI 规范 https://www.keycloak.org/docs-api/latest/rest-api/openapi.json 和本仓库中的 Account 规范 https://raw.githubusercontent.com/sferarc/openapi/main/packages/keycloak-api/specs/account.yaml 生成;生成前,kubb.config.ts 会重命名操作 ID、将路径参数转换为驼峰命名、发送不带引号的原始字符串请求体,并将 workflow 资源路径参数类型设为其枚举。
功能
- 完全类型化 - 每个端点、参数和响应都根据官方 OpenAPI 规范生成
- Admin 和 Account API - 为 Keycloak 的这两个 API 分别提供客户端
- 支持 Tree-shaking - 只有导入的操作会打包到你的包中
- 与运行时无关 - 使用全局
fetch,也可以使用你自己的实现 - 支持 Effect - 通过
keycloak-api/effect为 Admin API 提供可选的 Effect 绑定
安装
npm install keycloak-api
快速开始
Keycloak 需要自行托管,因此两个客户端都需要服务器的 baseUrl。端点使用 "<METHOD> <path>" 格式寻址。
src/admin/generated/OPERATIONS.md 和 src/account/generated/OPERATIONS.md 列出了所有 Admin 和 Account 操作,包括 "<METHOD> <path>" 请求和规范中的摘要,每个标签对应一个表格。这些文件随包一同发布,因此无需阅读类型定义,就能在 node_modules/keycloak-api 中通过 grep 查找操作。
Admin API
import { KeycloakAdminApi } from "keycloak-api";
const admin = new KeycloakAdminApi({
baseUrl: "https://keycloak.example.com",
token: process.env.KEYCLOAK_TOKEN ?? null,
});
const realms = await admin.request("GET /admin/realms");
const realm = await admin.request("GET /admin/realms/{realm}", {
pathParams: { realm: "master" },
});
Account API
Account API 的作用范围仅限于单个 realm,该 realm 在创建客户端时传入。
import { KeycloakAccountApi } from "keycloak-api";
const account = new KeycloakAccountApi({
baseUrl: "https://keycloak.example.com",
realm: "master",
token: process.env.KEYCLOAK_TOKEN ?? null,
});
const me = await account.request("GET /account/");
刷新令牌
Keycloak 访问令牌会在几分钟后过期。将函数作为 token 传入,该函数可以是同步或异步的。两个客户端都会在每次请求前调用它,因此每次调用都会发送该函数返回的令牌:
const admin = new KeycloakAdminApi({
baseUrl: "https://keycloak.example.com",
token: () => tokenStore.getAccessToken(),
});
如果该函数抛出异常,调用会直接以该异常拒绝,不会发送任何请求。调用自身的 config.token 仍具有优先权。
自定义 fetch
const admin = new KeycloakAdminApi({
baseUrl: "https://keycloak.example.com",
token: process.env.KEYCLOAK_TOKEN ?? null,
fetch: myFetchImplementation,
});
响应头
调用只会解析为响应正文。要读取响应头,请在 config 中传入 onResponse;在读取正文之前,它会收到响应的状态码和响应头,包括错误响应。Keycloak 在创建资源时(例如新用户或客户端)会返回 201、空正文以及新资源的 URL,该 URL 以资源 ID 结尾,并通过 Location 返回:
let userId: string | undefined;
await admin.request("POST /admin/realms/{realm}/users", {
pathParams: { realm: "master" },
body: { username: "ada", enabled: true },
config: { onResponse: ({ headers }) => (userId = headers.get("Location")?.split("/").pop()) },
});
如果 onResponse 内部抛出异常,调用会以 Error 拒绝,其消息以 onResponse threw on 开头,且其 cause 为抛出的异常。
错误
如果调用收到 HTTP 错误响应,则会以从包中导出的 ApiResponseError 拒绝,该类型继承自 Error:status 是 HTTP 状态码;payload 是解析后的 JSON 错误正文,若正文不是 JSON,则为最多 1000 个字符的原始正文文本;method 和 path 标明请求,不含查询字符串。其消息格式为 <METHOD> <path> returned <status>: <detail>,其中 detail 是 API 自身的错误消息;若没有,则为正文的前 200 个字符。如果请求完全未收到响应(例如网络故障),则会以 Error 拒绝,其消息会说明方法、路径和原因,且其 cause 为原始异常。如果通过 signal 取消请求,则会原样以取消信号所携带的异常拒绝:即 AbortError,或 AbortSignal.timeout() 的 TimeoutError。
Effect
effect 入口点会将每个 Admin API 操作都以 Effect 形式暴露:按标签归入 AdminApiService,并按 "<METHOD> <path>" 归入 adminEffectByPath;每个 Account API 操作则归入 AccountApiService 和 accountEffectByPath。它要求将 effect 4 作为对等依赖。与客户端类一样,这一层需要服务器的 baseUrl,而 token 可以是一个在每次调用前返回新令牌的函数。
import { Effect } from "effect";
import { adminEffectByPath, makeApiLayer } from "keycloak-api/effect";
const program = adminEffectByPath["GET /admin/realms/{realm}"]({ pathParams: { realm: "master" } });
await Effect.runPromise(
program.pipe(
Effect.provide(
makeApiLayer({
baseUrl: "https://keycloak.example.com",
token: () => tokenStore.getAccessToken(),
})
)
)
);
错误采用 ApiError、NetworkError 和 ValidationError 类型。HTTP 错误响应会以 ApiError 失败,其中包含 HTTP status;请求未能收到响应,或令牌函数抛出异常时,则会以 NetworkError 失败。
调用可以传入自己的 config,它会叠加到该层的配置上:其中的 headers 会与该层的请求头合并,其他字段(例如 onResponse)则会替换该层的对应字段。如果 onResponse 抛出异常,effect 会以 onResponse threw on 错误作为 defect 终止,而不是以 NetworkError 失败。
Account API 路径位于某个 realm 之下,因此 account effect 还需要 makeAccountLayer(realm);它会将服务器层的 baseUrl 重新定位到 /realms/<realm>,并像 KeycloakAccountApi 一样对 realm 进行编码。只需一个 makeApiLayer,就能在同一程序中运行 admin 和 account effects:
import { Effect } from "effect";
import { AccountApiService, AdminApiService, makeAccountLayer, makeApiLayer } from "keycloak-api/effect";
const program = Effect.gen(function* () {
const realms = yield* AdminApiService.realmsAdmin.gETAdminRealms();
const account = yield* AccountApiService.account
.getAccount()
.pipe(Effect.provide(makeAccountLayer("my realm")));
return { realms, account };
});
await Effect.runPromise(
program.pipe(
Effect.provide(
makeApiLayer({
baseUrl: "https://keycloak.example.com",
token: () => tokenStore.getAccessToken(),
})
)
)
);
如果运行 account effect 时未使用 makeAccountLayer,请求会发送到 <baseUrl>/account/,这并不是 Keycloak 端点。
MCP 服务
此软件包提供 keycloak-api-mcp,这是一个基于 stdio 的 MCP 服务,针对某个 realm 的每项 admin 操作都提供一个工具,因此 Agent 可以回答诸如某个 realm 中哪些用户拥有某个角色的问题。默认只列出读取(GET)工具;设置 KEYCLOAK_MCP_ALLOW_WRITES=1 可添加其他工具,其中 DELETE 操作带有 destructiveHint。每个工具都针对 KEYCLOAK_REALM 执行操作,该变量不是工具参数:用于列出和导入 realm 的两项操作不会提供;如果某次调用解析出的 URL 位于该 realm 之外(例如包含 .. 片段的群组路径),则会作为工具错误被拒绝。HTTP 错误会作为工具错误返回,并附带状态码和 Keycloak 的消息。
| 变量 | |
|---|---|
KEYCLOAK_URL | 服务器的基础 URL,例如 https://keycloak.example.com |
KEYCLOAK_REALM | 工具所操作的 realm |
KEYCLOAK_TOKEN | 固定的访问令牌;否则使用: |
KEYCLOAK_CLIENT_ID、KEYCLOAK_CLIENT_SECRET | 配有服务账号的机密客户端;服务器会使用客户端凭据授权获取令牌,并在令牌过期前续期 |
KEYCLOAK_TOKEN_REALM | 该客户端所属的 realm;如果与 KEYCLOAK_REALM 不同则需设置(例如 master) |
KEYCLOAK_MCP_ALLOW_WRITES | 设为 1 可列出会更改数据的工具 |
KEYCLOAK_MCP_RESOURCES | 用于列出工具的资源,以逗号分隔,例如 users,groups,roles:即 realm 后面的第一个路径片段;如果是 realm 本身,则填 realm。未设置时会列出所有资源 |
服务账号需要具备工具要读取内容所需的 realm-management 角色,例如 view-users。
如果不设置 KEYCLOAK_MCP_RESOURCES,服务会列出约 200 个读取工具,超过 VS Code 聊天请求可启用的 128 个,因此请在此处选择资源。未知名称会导致服务停止,并列出有效名称。
Claude Code,在 .mcp.json 中:
{
"mcpServers": {
"keycloak": {
"command": "npx",
"args": ["-y", "-p", "keycloak-api", "keycloak-api-mcp"],
"env": {
"KEYCLOAK_URL": "https://keycloak.example.com",
"KEYCLOAK_REALM": "acme",
"KEYCLOAK_CLIENT_ID": "mcp",
"KEYCLOAK_CLIENT_SECRET": "${KEYCLOAK_CLIENT_SECRET}"
}
}
}
}
VS Code,在 .vscode/mcp.json 中:
{
"inputs": [
{ "type": "promptString", "id": "keycloak-secret", "description": "Keycloak client secret", "password": true }
],
"servers": {
"keycloak": {
"type": "stdio",
"command": "npx",
"args": ["-y", "-p", "keycloak-api", "keycloak-api-mcp"],
"env": {
"KEYCLOAK_URL": "https://keycloak.example.com",
"KEYCLOAK_REALM": "acme",
"KEYCLOAK_CLIENT_ID": "mcp",
"KEYCLOAK_CLIENT_SECRET": "${input:keycloak-secret}",
"KEYCLOAK_MCP_RESOURCES": "users,groups,roles"
}
}
}
}
入口点
| 导入路径 | 内容 |
|---|---|
keycloak-api | 两个客户端类,以及 admin/account 操作、schemas 和类型 |
keycloak-api/admin/components | 按标签和路径分组的 admin 操作 |
keycloak-api/admin/schemas | admin 操作的 Zod schemas |
keycloak-api/admin/types | admin 操作的 TypeScript 类型 |
keycloak-api/account/components | 按标签和路径分组的 account 操作 |
keycloak-api/account/schemas | account 操作的 Zod schemas |
keycloak-api/account/types | account 操作的 TypeScript 类型 |
keycloak-api/effect | admin 操作的 Effect 绑定 |
许可证
ISC
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。