Keycloak Admin API

io.github.sferarc由 GitHub 帳號 sferarc 發布v3.8.0更新於 Nov 6, 2026

Unofficial: one tool per Keycloak admin operation on a realm, read-only unless writes are enabled

已驗證STDIO僅桌面開發者工具安全與監控

概覽

AI 產生的概覽

非官方 Keycloak Admin API MCP 伺服器,以單一 realm 為範圍提供管理操作工具,預設唯讀,讓助理查看使用者、群組與角色。

可以做什麼
  • 以每個 Keycloak 管理操作一個工具的方式,查詢使用者、群組、角色及其他 realm 資源
  • 讀取所設定 realm 的層級設定與物件
  • 啟用寫入後,可選擇列出並執行寫入操作,包含刪除
  • 把開放的工具限縮為指定資源,例如 users、groups、roles
  • 將 HTTP 錯誤以工具錯誤回傳,並附上狀態碼與 Keycloak 的錯誤訊息
適用情境
適合讓助理回答關於自架 Keycloak realm 的問題,例如某個角色有哪些使用者,或在對話中檢視身分資料。若需要對單一 realm 做範圍受限、以唯讀為主的存取,而非完整的腳本化管理,可考慮加入。
執行需求
透過 npx 在本機執行(需 Node.js)。需要 KEYCLOAK_URL 與 KEYCLOAK_REALM,並提供固定的 KEYCLOAK_TOKEN,或使用具 KEYCLOAK_CLIENT_ID 與 KEYCLOAK_CLIENT_SECRET 的機密用戶端(可選 KEYCLOAK_TOKEN_REALM)。服務帳號需具備 view-users 等 realm-management 角色。需要能連線到 Keycloak 伺服器的網路。
安裝前請注意
它持有管理憑證:KEYCLOAK_CLIENT_SECRET 或 KEYCLOAK_TOKEN 可存取該 realm,應將服務帳號權限降到最低。設定 KEYCLOAK_MCP_ALLOW_WRITES=1 會開放修改或刪除資料的工具,其中包含破壞性刪除。未設定 KEYCLOAK_MCP_RESOURCES 時會列出約 200 個唯讀工具,可能超過用戶端的工具數量上限。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Keycloak Admin API,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

keycloak-api

Unofficial. Not made or endorsed by Keycloak.

Auto-generated, fully typed client for the Keycloak Admin and Account REST APIs.

Generated from the admin OpenAPI spec at https://www.keycloak.org/docs-api/latest/rest-api/openapi.json and the account spec kept in this repository at https://raw.githubusercontent.com/sferarc/openapi/main/packages/keycloak-api/specs/account.yaml; before generating, kubb.config.ts renames operation IDs, camel-cases path parameters, sends raw string bodies unquoted and types the workflow resource path parameter as its enum.

Features

  • Fully typed - every endpoint, parameter and response is generated from the official OpenAPI specs
  • Admin and Account APIs - separate clients for the two Keycloak surfaces
  • Tree-shakable - only the operations you import end up in your bundle
  • Runtime agnostic - uses the global fetch, or bring your own implementation
  • Effect support - optional Effect bindings for the Admin API via keycloak-api/effect

Installation

bash
npm install keycloak-api

Quick Start

Keycloak is self-hosted, so both clients require the baseUrl of your server. Endpoints are addressed as "<METHOD> <path>".

src/admin/generated/OPERATIONS.md and src/account/generated/OPERATIONS.md list every admin and account operation with its "<METHOD> <path>" request and the spec's summary, one table per tag. They ship in the package, so grep in node_modules/keycloak-api finds an operation without reading the types.

Admin API

ts
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

The Account API is scoped to a single realm, passed at construction time.

ts
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/");

Refreshing the token

Keycloak access tokens expire after a few minutes. Pass a function as token, sync or async, and both clients call it before every request, so each call sends the token it returns:

ts
const admin = new KeycloakAdminApi({  baseUrl: "https://keycloak.example.com",  token: () => tokenStore.getAccessToken(),});

A call rejects with what the function threw, before any request is sent. A call's own config.token still wins.

Custom fetch

ts
const admin = new KeycloakAdminApi({  baseUrl: "https://keycloak.example.com",  token: process.env.KEYCLOAK_TOKEN ?? null,  fetch: myFetchImplementation,});

Response headers

A call resolves to the parsed body only. To read a response header, pass onResponse in config; it gets the status and headers of the response before the body is read, an error response included. Keycloak answers a create call, such as a new user or client, with 201, an empty body and the new resource's URL, which ends in its id, in Location:

ts
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()) },});

An error thrown inside onResponse rejects the call with an Error whose message starts with onResponse threw on and whose cause is what was thrown.

Errors

A call that gets an HTTP error response rejects with an ApiResponseError, exported from the package, which extends Error: status is the HTTP status code, payload is the parsed JSON error body, or the raw body text, up to 1000 characters, when it is not JSON, and method and path name the request, without its query string. Its message reads <METHOD> <path> returned <status>: <detail>, where the detail is the API's own error message or else the first 200 characters of the body. A request that gets no response at all, for example because the network failed, rejects with an Error whose message names the method, the path and the reason, and whose cause is the original exception. A request cancelled through signal rejects with what the signal was aborted with, as is: the AbortError, or the TimeoutError of AbortSignal.timeout().

Effect

The effect entrypoint exposes every Admin API operation as an Effect, by tag in AdminApiService and by "<METHOD> <path>" in adminEffectByPath, and every Account API operation in AccountApiService and accountEffectByPath. It requires effect 4 as a peer dependency. As with the client class, the layer needs the baseUrl of your server, and token can be a function that returns a fresh token before every call.

ts
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(),      })    )  ));

Errors are typed as ApiError, NetworkError and ValidationError. An HTTP error response fails with ApiError, which carries the HTTP status; a request that never gets a response, or a token function that throws, fails with NetworkError.

A call can pass its own config, which layers over the layer's: its headers join the layer's, and its other fields, such as onResponse, replace them. An onResponse that throws makes the effect die with the onResponse threw on error as a defect rather than fail with NetworkError.

Account API paths sit under a realm, so an account effect also needs makeAccountLayer(realm), which rebases the server layer's baseUrl onto /realms/<realm> and encodes the realm as KeycloakAccountApi does. One makeApiLayer then serves admin and account effects in the same program:

ts
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(),      })    )  ));

An account effect run without makeAccountLayer goes to <baseUrl>/account/, which is not a Keycloak endpoint.

MCP server

The package ships keycloak-api-mcp, a stdio MCP server with one tool per admin operation on a realm, so an agent can answer questions such as which users of a realm hold a role. It lists only the read (GET) tools; set KEYCLOAK_MCP_ALLOW_WRITES=1 to add the others, which carry destructiveHint on DELETE. Every tool acts on KEYCLOAK_REALM, which is not a tool argument: the two operations that list and import realms are left out, and a call whose URL resolves outside that realm, such as a group path with .. segments, is refused as a tool error. An HTTP error comes back as a tool error with the status and Keycloak's message.

Variable
KEYCLOAK_URLBase URL of the server, such as https://keycloak.example.com
KEYCLOAK_REALMRealm the tools act on
KEYCLOAK_TOKENA fixed access token, or else:
KEYCLOAK_CLIENT_ID, KEYCLOAK_CLIENT_SECRETA confidential client with a service account; the server gets a token with the client credentials grant and renews it before it expires
KEYCLOAK_TOKEN_REALMRealm of that client, if not KEYCLOAK_REALM (for example master)
KEYCLOAK_MCP_ALLOW_WRITES1 to list the tools that change data
KEYCLOAK_MCP_RESOURCESComma separated resources to list tools for, such as users,groups,roles: the first path segment after the realm, or realm for the realm itself. Unset lists every resource

The service account needs the realm-management roles for what the tools should read, such as view-users.

Without KEYCLOAK_MCP_RESOURCES the server lists about 200 read tools, over the 128 a VS Code chat request can enable, so pick the resources there. An unknown name stops the server with the list of valid ones.

Claude Code, in .mcp.json:

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, in .vscode/mcp.json:

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"      }    }  }}

Entrypoints

ImportContents
keycloak-apiboth client classes, plus admin/account operations, schemas and types
keycloak-api/admin/componentsadmin operations grouped by tag and by path
keycloak-api/admin/schemasZod schemas for admin operations
keycloak-api/admin/typesTypeScript types for admin operations
keycloak-api/account/componentsaccount operations grouped by tag and by path
keycloak-api/account/schemasZod schemas for account operations
keycloak-api/account/typesTypeScript types for account operations
keycloak-api/effectEffect bindings for admin operations

License

ISC

來源:packages/keycloak-api/README.md,提交 0152cfc

工具

0
SourceWeft 曾在隔離沙箱中以無憑證方式啟動該軟體包一次,以列出其工具,但未能完成:伺服器未能啟動。

版本歷史

1
  1. v3.8.0最新Nov 6, 2026

評分

已滿足適用檢查項的 50%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分

    請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。

  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分