sky-cloak/skycloak
Skycloak is identity-as-a-service, delivered today as managed Keycloak. Run clusters, realms, applications, SSO, users, domains and audit events from the hosted Skycloak MCP server.
Changelog
All notable changes to this project are documented here. The format follows Keep a Changelog, and the project adheres to Semantic Versioning.
[Unreleased]
Added
skycloak_list_openfga_instances,skycloak_get_openfga_instance,skycloak_create_openfga_instanceandskycloak_delete_openfga_instance, coveringGET/POST /clusters/{cluster_id}/openfga-instancesandGET/DELETE /clusters/{cluster_id}/openfga-instances/{instance_id}. An instance is a hosted OpenFGA bound to one realm of a cluster, and it accepts only tokens from that realm. Creating one takescluster_idandrealm, is asynchronous (202, poll the get tool untilstatusisreadyorfailed), and is refused with403when the plan does not include OpenFGA or the workspace reached its instance limit,409when the realm already has an instance, and422when the realm ismaster, missing or disabled. The get tool returns theapi_url,issuerandaudienceto call it with. Listing and deleting work without OpenFGA access. Deleting removes the deployment, route and database for good, so the tool requiresconfirm=true; it answers202while the instance isdeletingand204when it is already gone. Theopenfga:readandopenfga:writescopes are part of the scopes requested at sign-in. The vendored OpenAPI spec carries the OpenFGA operations from the app's TypeSpec, and the generated client is regenerated from it.- A plugin bundle at
plugins/skycloak/that serves two clients from one folder. Antigravity reads.antigravity-plugin/plugin.jsonandmcp_config.json; Claude reads.claude-plugin/plugin.json, which carries its MCP server inline rather than in a.mcp.json, because that file name makes the Antigravity loader warn that it is never loaded. Both declare the hosted server athttps://mcp.skycloak.ioas a single remote MCP server, and both ship the README, the square logo and the four skills. The Antigravity manifest sits under.antigravity-plugin/rather than at the plugin root because that is the location its build guide reserves for a directory shared with other providers. Neither declares an OAuth client:mcp.skycloak.ioadvertises its authorization server through the protected resource metadata the MCP spec defines, so the client discovers the sign-in flow on the first401.agy plugin validate --strict ./plugins/skycloakandclaude plugin validate ./plugins/skycloakboth pass with nothing to report. - A
plugin.jsonandmcp.jsonat the repository root, in the Agent Plugins v1.0.0 format GitHub's awesome-copilot marketplace validates external submissions against. The bundled skills are byte-for-byte copies ofinternal/tools/skills/, because an installer receives only the plugin folder; a test fails if the two sides drift. us-west(US West) as a cluster location of its own.create_clusteraccepts it in any case, andlist_clusters,get_clusterandlist_cluster_locationsreport it instead of folding West Coast clusters intous, which now always means US East. It is open only to workspaces granted access; without access the create returns the API's400"location not available", like any other closed region. The vendored OpenAPI spec and the generated client carry the new enum value.skycloak_update_theme_content, which replaces a deployed theme's archive in place throughPUT /clusters/{cluster_id}/themes/{theme_id}/content. Editing a theme from MCP no longer means detaching it, deleting it and uploading a replacement under a new ID: the theme keeps its ID and name, every realm and application assignment keeps pointing at it, and the sign-in page is never left unbranded in between. The archive is passed base64-encoded incontent_base64(ZIP, or a Keycloakify JAR whenfilenameends in.jar), the same encodingskycloak_download_theme_contentreturns, and an optionalversionrecords a new label once the content is live. Replacing the archive discards the current one for good, so the tool requiresconfirm=truelike the other destructive tools. Oversized, non-base64 and non-archive payloads are refused before the upload rather than after it: the archive's central directory is read locally, so a truncated or empty ZIP is caught too.docs/theme-content-update.mdexplains the call; the existing theme tools are unchanged.skycloak_get_theme_settingsandskycloak_update_theme_settings, coveringGET/PUT /theme-settings: the workspace'sexact_theme_namespolicy. Turning it on serves every theme from a folder named exactly like the theme and moves existing themes there in the background; the update requires the API key to have been minted for a workspace owner or admin, so it 403s for any other role even withthemes:write; the tool requiresconfirm=truebecause turning it on moves every theme in the workspace.skycloak_get_theme,skycloak_list_themesandskycloak_update_theme_contentnow surface each theme'srestart_required, set when its content was replaced under its exact name and Keycloak has not restarted since.skycloak_restart_cluster_instances, coveringPOST /clusters/{cluster_id}/restart-instances, to roll a cluster's Keycloak instances so replaced theme content starts rendering. Applies immediately unless the workspace latches disruptive changes to the maintenance window, reported asdeferred: truewithnext_windowwhen known. A409names why the cluster cannot restart right now (cluster_busy,cluster_not_available, orenv_var_limit), which the tool now translates into a plain-language hint instead of a bare code. Requiresconfirm=truelike the other destructive tools.- Every request the server makes to the Skycloak API while serving a tool call
now carries
X-MCP-Tool, the tool's registered name, andX-MCP-Call-Id, a random UUID made once per call. The gateway logs requests by route, and a route does not say which tool asked: one call can reach the API once per cluster whencluster_idis omitted, and a request is retried after a429or5xx. All requests of one call, retries included, share its call ID, so usage can be counted per tool and per call. Requests made outside a tool call (signing in, minting API keys) carry neither header, andAPI-Key,API-VersionandUser-Agentare unchanged. The README lists what is sent.
Changed
-
internal/apiclientregenerated from the app's current spec (make generate) to add the two endpoints above. That pulled in a few unrelated upstream additions along with them:Extension.display_name, a machine-readablecodefield on plan-limit429errors, and reworded validation text on webhook/SIEM destinationheaders. No tool in this repo consumes any of the three yet. -
skycloak_list_extensionsnow prints each catalog extension'sversion(also added to the structured result), andskycloak_list_cluster_extensionsprintsavailable_versionnext to the installed version andupgrade_available, so a caller sees what an upgrade would move to without a second call. Versions are shown verbatim (1.5.0,v2.7,1.4.1-SNAPSHOT), and a missing one readsunknown. The values come from the app's public API, which reports the real resolved artifact once the app release that stores it is live; until then marketplace rows still read1.0.0. The em-dashes in both summary lines are gone.
Fixed
- The tool counts in the README, which had drifted from what the server registers.
skycloak_create_domain_routeand theset_up_custom_domainprompt now tell agents never to route a custom domain to the master realm. Doing so sets master's Frontend URL and cuts Skycloak off from managing the cluster, and the API now refuses it with a400, butskycloak_list_realmslists master like any other realm, so an agent could still pick it. The prompt excludes master when choosing a realm, and when master is the only realm it asks the user to create or name an app realm instead.
[0.9.0] - 2026-08-18
Added
- Four MCP skills served over the draft SEP-2640 Skills extension: the server
declares
io.modelcontextprotocol/skills, answersskills/listandskills/getwith per-file sha256 digests, and serves eachSKILL.mdas a resource atskill://<name>/SKILL.md, which is the shape OpenAI's plugin directory imports. The skills are operational playbooks rather than tool summaries: triaging an authentication incident (read-only), rolling out an enterprise SSO integration end to end, assessing Keycloak upgrade readiness with rollout sequencing, and preflighting or diagnosing Keycloak exports, imports and migrations, built from the failure modes support tickets actually show and centered on reading a failed job'serror_message, which the API records but the dashboard notice does not surface. They follow the same gating as the tools they name: the three write workflows are withheld from read-only sessions, and tests fail if a served skill names a tool its session does not have, if a listing digest stops matching the served bytes, or if an entry's frontmatter drifts from the file's. - Eight MCP prompts, one per workflow users actually start with: auditing self-registration, reviewing upgrades, triaging failed logins, reviewing identity providers, reviewing admin changes, provisioning an environment, setting up a custom domain, and rotating a client secret. Each takes arguments and names the exact tools to use in order, so a client that surfaces prompts offers a starting point instead of a bare 129-tool list. Prompts follow the same gating as the tools they name: the three mutating workflows are withheld from read-only sessions, and a test fails if a prompt names a tool its session cannot have, lacks a description, or does not require the scopes of every tool it references.
Changed
- MCP Go SDK upgraded from v1.6.1 to v1.7.0, which adds the custom JSON-RPC
method registration that
skills/listandskills/getare built on.
[0.8.0] - 2026-08-18
Added
- Every tool now declares
openWorldHint, and the thirteen write tools that had nodestructiveHintnow carry one. Both are required by OpenAI's plugin directory, which validates them during its tool scan, and the first is a correctness fix in its own right: the MCP spec defaultsopenWorldHintto true, so all 129 tools were telling clients they might act on the open internet while operating on a single tenant. Six are genuinely open-world: the SMTP, SIEM, webhook and identity-provider test tools, OIDC discovery, and domain verification. OPENAI_APPS_CHALLENGE_TOKENserves that directory's domain verification token at/.well-known/openai-apps-challenge. Unset, the route is not registered.- A test that fails if any registered tool reaches the wire without a title,
openWorldHint, or, for write tools,destructiveHint.
[0.7.2] - 2026-08-18
Fixed
- The registry description fits the registry. 0.7.1 shipped a 444-character one and
the publish step rejected it with
expected length <= 100, so the tag produced a GitHub release and a container image but never reached the registry. The longer copy belongs in directory listings that have their own description field, not here.
[0.7.1] - 2026-08-18
Changed
- The registry entry carries a description worth indexing. The previous one was 98 characters and said little beyond the product name, which is what every directory mirroring the registry was displaying.
Added
- The registry entry points at the source repository, so directories can show the licence and the code rather than only an endpoint. Uses GitHub's numeric repository id, which survives a rename and changes if a repository is deleted and recreated.
[0.7.0] - 2026-08-18
Changed
- This repository is now open source under Apache-2.0. The pre-release history was squashed into a single initial commit, so the commit log starts here; the changelog above remains the record of what changed in each earlier release.
- The README leads with a quick start and example prompts instead of authentication internals, and states plainly that the hosted server is write-capable with writes bounded by your credential's scopes. The previous "read-only by default" wording described the local binary only and read as if it covered the hosted server.
Added
SECURITY.mdwith a private disclosure address, and aNOTICEclarifying that the bundled OpenAPI description is (c) Skycloak rather than Apache-2.0.
Removed
- The internal distribution playbook, which was a go-to-market document rather than something a user of this server needs.
[0.6.6] - 2026-08-18
Changed
- The MCP Registry entry carries a 512x512 icon and a display title, so the server renders properly in registry and directory listings rather than falling back to a bare package name.
[0.6.5] - 2026-08-17
Fixed
resource_documentationin the protected-resource metadata points at the published documentation rather than a placeholder, so a client following the RFC 9728 pointer lands somewhere useful.
Added
- Releases publish to the MCP Registry automatically, authenticated by DNS against
skycloak.io, so a tagged release no longer needs a manual registry step.
[0.6.4] - 2026-08-15
Fixed
- Every tool parameter that maps to an API enum accepts any case, not just the two #31 reached. Thirty-one more were forwarding the model's spelling untouched, so
create_cluster(size="Large"),create_export(format="SQL"),upsert_smtp(encryption="STARTTLS"), the SIEM destination transports and the WAF, geo-blocking and bot-management modes all answered422 Validation Failed: Invalid parameterwith nothing in it pointing at the case. Each parameter is folded to the case its own enum uses, which cannot be a blanket lowercase becauseUserEventType,AdminOperationTypeandDnsRecordTypeare uppercase. A value the enum does not list is still passed through untouched, so a value the API grows after this release reaches it and the API's own error is what the caller sees. get_cluster_insightsrefuses atypeit does not recognise instead of answering with the overview document. The five insight kinds are five endpoints, so the client picks the path and the value never reaches the API: a typo, or the right word in the wrong case, used to return a different document than the one asked for, successfully. Correct values in any case still work; anything else now names the five that are valid.- The affected tool descriptions state the values they accept and that case does not matter, across all of them rather than the two that already did. Two of fifteen advertising case-insensitivity was worse than none: a model generalising from those two hit an opaque failure everywhere else.
create_identity_providerdescribed itsprovider_idas a free-form alias when the spec declares it theSkycloakProviderIdenum; the description now says so and points atskycloak_list_identity_provider_templatesfor the values. The provider alias in the other four identity-provider tools is a free-form string in the spec, so it stays case-sensitive and its description says so.
Added
- The enum inventory is checked against the committed
openapi.yaml, so it cannot drift back. A value spelled differently from the spec, an enum that gains or loses one, a normalised parameter whose description stops naming what it accepts, and a newly added tool parameter that carries an API enum with no normalisation each fail the build instead of a user's call. The checks drive the registered tools by name over an in-memory MCP session, so a tool wired to a handler that skips the normalisation fails too.
[0.6.3] - 2026-08-14
Fixed
get_logsaccepts a log level in any case, solevel="ERROR"stops failing with422 Invalid parameter: level. The API's enum is lowercase (info,warn,error,debug) but the tool's own description advertisedERROR, WARN, INFO, so a model that followed the documentation got a validation error with nothing in it to suggest the case was the problem. The value is now lowercased and trimmed before the request, and the description names the four values the API actually accepts.query_eventstakes itscategory(user,admin) the same way.
[0.6.2] - 2026-08-14
Fixed
- The hosted OAuth sign-in advertises the scopes it needs, so it stops failing at the token exchange with
stage=exchange status=403 dashboard_status=401. The protected-resource metadata carried noscopes_supportedand theWWW-Authenticatechallenge carried noscope, so a client following RFC 9728 had nothing to ask for and requested none. Keycloak then issued a token withoutopenid, which verifies and passes introspection but makes Keycloak's userinfo endpoint answer403when the dashboard validates it, so a sign-in that looked correct at every earlier step died at the last one. Both the document and the challenge now nameopenid profile email. - A token granted without
openidis refused at verification, asno_openid_scope, instead of being carried to an exchange that cannot succeed. Advertising the scopes only helps a client that has yet to sign in: one that connected before holds a refresh token from the old grant, and every access token minted from it inherits the gap. Those requests used to end in a403with nothing to act on, so the client retried the same doomed token until its realm session lapsed. They are now answered with a401and the challenge, which is what makes a client authorize again and come back with the right scopes.
[0.6.1] - 2026-08-14
Added
- The hosted HTTP transport logs every request it refuses on the OAuth path, so "credentials rejected" stops being a report with four possible causes and no evidence. Each rejection is one line naming the stage that failed (token verification, the dashboard exchange, or the empty-grant check), the status the caller got, and the underlying error, capped in length because part of that error is the key id from an unverified token header and an uncapped line would let one request write a log entry its own size. A verification failure names the check that rejected the token (expired, wrong issuer, bad signature, unknown key id, wrong token type, and the rest) rather than collapsing to "invalid token"; an exchange failure names the dashboard's own status and the host being called. The caller is identified by the token's subject, and only once it is verified. Nothing that is a credential is logged: not the access token or any part of it, not the
Authorizationheader, and not the minted API key. Accepted requests stay silent. - The HTTP transport logs the configuration it resolved at startup: whether OAuth is on, the issuer, the dashboard URL, the public URL and the API endpoint. Both of the last two production problems were a value nobody could read back from a running pod, a public URL advertising
httpfor anhttpsendpoint and a dev deployment exchanging tokens against the production dashboard. Values only, no secrets.
[0.6.0] - 2026-08-14
Added
- The hosted HTTP transport accepts OAuth, so a client can connect with no credential in its configuration:
claude mcp add --transport http skycloak https://mcp.skycloak.ionow opens a browser, the user approves in the Skycloak login realm, and the tools appear. The server publishes RFC 9728 protected-resource metadata at/.well-known/oauth-protected-resourceand names it in theWWW-Authenticatechallenge, which is what a first-time client follows to find the authorization server. Skycloak API keys keep working exactly as before, on bothAuthorization: Bearer sk_sc_...andAPI-Key:. - A verified realm access token is exchanged, at the dashboard, for a short-lived workspace-scoped API key, and the session runs on that. The exchange is cached per user and workspace and refreshed before the key lapses, and concurrent first requests share one mint rather than racing to rotate each other's key. Naming your only workspace explicitly resolves to the same session as not naming it, so the two spellings cannot mint over one another. Neither the token nor the key is ever logged.
- The realm's signing keys are cached, refreshed when a key id is unfamiliar and again once the set is ten minutes old, so a key the realm withdraws stops verifying tokens instead of living on until the process restarts. Refreshes are coalesced and rate limited, counting failed attempts, so neither a flood of junk tokens nor an unreachable realm turns into one outbound request per inbound one. Only tokens the realm labels as access tokens are accepted, and signing keys below 2048 bits are ignored.
- Tools are tailored to what the session may actually do. A read-only workspace member no longer sees dozens of write tools that would answer 403; a workspace owner sees the full surface.
get_cluster_credentialsstays hidden unless the credential carriesclusters:credentials:read, which no OAuth session is granted. Stdio and API-key callers are unchanged: their grant is not enumerable, so every tool is registered as before. ?workspace=<uuid>on the hosted URL picks which workspace an OAuth session acts on, alongside the existing?readonly=true. A user who belongs to several workspaces and names none gets a400that lists them and says how to choose.SKYCLOAK_PUBLIC_URLoverrides the resource identifier published in the OAuth metadata. It is only needed when the URL clients use differs from theHostthey send.
Fixed
- An OAuth session that the dashboard grants no scopes is no longer cached. Connecting before your workspace role was granted used to pin you to that empty answer for the key's whole life: the server refused with a
403, every retry hit the cached session, and an admin granting the role changed nothing until the key lapsed nearly an hour later. The next request now asks the dashboard again, so the grant takes effect immediately. update_cluster_securityreads the cluster's current configuration so the sections it does not manage, CAPTCHA among them, survive the update. A refused read used to fall through to an empty body, so the update silently wiped them. It now fails instead of writing.
Changed
- The realm issuer and dashboard URL (
SKYCLOAK_ISSUER,SKYCLOAK_DASHBOARD_URL) now configure the HTTP transport as well as the CLI sign-in. Blanking either turns the OAuth path off, and the server goes back to advertising nothing but the API-key challenge.
[0.5.1] - 2026-08-13
Fixed
- The hosted HTTP transport answered every unknown path with
401and the credential challenge, including the OAuth discovery paths. A client that got a401on connect then probed/.well-known/oauth-protected-resource, read the401there as "this server does OAuth", attempted Dynamic Client Registration, and told the user registration had been rejected, when all the server ever wanted was an API key. Only the MCP endpoint is credential-gated now; anything else is a plain404with no challenge. The endpoint still serves the bare origin,/mcpand/mcp/, and/healthzand/readyzstay unauthenticated.
[0.5.0] - 2026-08-13
Added
init --allow-credentialsrequestsclusters:credentials:read, the scopeget_cluster_credentialsneeds. It stays out of the default grant, because a key that carries it lets an assistant read a cluster's Keycloak admin credentials.
Changed
get_cluster_credentialsanswers a403by naming the scope it needs and how to get a key that carries it, instead of a bare "forbidden". It was previously the only tool a signed-in user could never call, with nothing to say why. The message covers both transports and notes thatSKYCLOAK_API_KEYoutranks the stored key.
[0.4.0] - 2026-08-13
Added
- Realm import/export tools:
create_realm_export,get_realm_export,create_realm_import,get_realm_import,create_realm_import_upload_url. Both jobs are asynchronous; the archive is always encrypted, and a realm can be imported from an existing export without re-uploading it. Import creates a realm and refuses a name collision rather than overwriting, and requiresconfirm=truebecause it brings users and credentials with it. download_theme_contentreturns a theme's archive as an MCP resource blob, with size and SHA-256 always reported and the bytes inlined only when small enough to be worth it.- This completes the API surface: all 134 generated operations are now used.
Fixed
- A
5xxresponse to a non-idempotent request is no longer retried. A gateway504on aPOSToften means the origin did accept it, so replaying it could start a second realm import or export. A429is still retried on any method: it means the request was refused, not performed. --allow-writesrequested seven scope names the API does not define (users:read,users:write,cluster-logs:read,cluster-insights:read,cluster-events:read,cluster-security:read,cluster-security:write), and omitted every scope for domains, themes, branding, extensions, SMTP, SIEM, webhooks, exports and imports. A minted key therefore 403'd on those tools. The list now matches the scopes declared in the OpenAPI spec, and a test checks it against the spec so a new tool area cannot silently miss one.- The container image declares its user numerically (
65532:65532) instead of by name. Under arunAsNonRootpolicy the kubelet has to verify the user is not root, cannot do that from a name, and refuses to start the container withCreateContainerConfigError.
[0.3.0] - 2026-08-13
Added
- Browser sign-in:
skycloak-mcp initruns the OAuth 2.0 device authorization flow (RFC 8628) against the Skycloak realm, mints a workspace-scoped API key, and stores it in the OS keychain.skycloak-mcp runloads it;skycloak-mcp logoutremoves it. No API key to copy or paste. --workspace,--allow-writes, and--ttl-daysflags oninit;SKYCLOAK_ISSUER,SKYCLOAK_CLIENT_ID, andSKYCLOAK_DASHBOARD_URLenv vars to target dev / self-hosted control planes.initopens your browser to the verification page automatically (code pre-filled), and still prints the URL/code as a fallback.--no-browserdisables the auto-open for SSH / headless terminals.- Hosted HTTP transport (
run --transport http): every request authenticates on its own viaAuthorization: Bearer <key>orAPI-Key: <key>, so one process serves many workspaces without holding a key of its own.?readonly=truenarrows a session to read-only tools. - Unauthenticated
GET /healthzandGET /readyzfor container probes, and a graceful drain onSIGTERM.
Changed
SKYCLOAK_API_KEYis now optional: it is the headless/CI path (and still used by the container image) and takes precedence over the keychain when set. Invoking the binary with no subcommand still serves stdio, so existing configurations keep working.skycloak-mcp runsigns you in automatically (device flow) when started interactively in a terminal with no stored key. When an MCP client launches it over a pipe it stays non-interactive and surfaces the actionablerun skycloak-mcp initmessage instead.- The HTTP transport runs stateless: it no longer caches an MCP session (and the server built for it) under the client-supplied
Mcp-Session-Id. This closes a session-handling issue and means every request is served strictly on its own credential. Replicas now need no session affinity. - An unauthenticated HTTP request is answered with
401and aWWW-Authenticatechallenge instead of400. - Request retries are bounded per attempt rather than by one overall client deadline, so a
Retry-Afterlonger than a single attempt's timeout is honored instead of failing the call. The caller's context still bounds the whole operation. - The client no longer installs its retry transport onto an
http.Clientsupplied viaWithHTTPClient; it copies the client first. - A server-supplied
Retry-Afteris capped at 60s, and the backoff a single call may accumulate is capped at 2 minutes, so a gateway incident cannot park a tool call indefinitely. - A query string containing
;is rejected rather than silently parsed as absent, so?readonly=true;x=1can no longer fall back to the write-capable default.
Removed
- list_cluster_builds / get_cluster_build tools: the cluster-builds endpoints were removed from the Skycloak public API.
[0.2.0] - 2026-05-26
Added
- Observability read tools:
get_logs,get_security_logs,query_events. - Custom-domain tools:
list_domains,get_domain(read);create_domain,verify_domain,delete_domain(write).create_domainreturns the DNS records to create;delete_domainrequiresconfirm=true. - Branding & theme tools:
list_themes,get_theme_assignment,get_login_branding,get_email_branding(read);set_theme_assignment(write — activates a custom theme per Keycloak theme type, empty string resets to the built-in default). - Extension tools:
list_extensions(catalog),list_cluster_extensions(read);install_extension,upgrade_extension,uninstall_extension(write). Install/upgrade are asynchronous;uninstall_extensionrequiresconfirm=true. - Database export tools:
list_exports,get_export(read);create_export(write, asynchronous — pollget_exportfor the download URL). Including credentials requires anencryption_password. - Realm RBAC tools:
list_realm_roles,list_realm_groups,list_realm_users,get_realm_user(read);create_realm_role,delete_realm_role,create_realm_group,delete_realm_group,create_realm_user,delete_realm_user,assign_realm_user_role,remove_realm_user_role,add_realm_user_to_group,remove_realm_user_from_group(write). Destructivedelete_*tools requireconfirm=true. - Application role & session tools:
list_application_roles,list_application_sessions(read);assign_application_role,remove_application_role(write). - Read-parity tools:
get_realm,get_application,get_identity_provider,list_cluster_locations,list_cluster_types,list_cluster_features,list_cluster_versions,list_cluster_upgrades,list_identity_provider_templates,list_domain_routes. - Write/read parity tools:
get_smtp,get_theme,get_domain_route,get_client_theme_assignment,list_user_roles,list_user_groups(read);rotate_application_secret,update_realm,delete_smtp,create_domain_route,delete_domain_route,set_client_theme_assignment,delete_theme,delete_extension,delete_export(write). Destructive deletes requireconfirm=true. - Action tools:
discover_oidc(read);test_smtp,test_identity_provider,cancel_cluster_upgrade(write).cancel_cluster_upgraderequiresconfirm=true. - Cluster security tools:
get_cluster_security(read);update_cluster_security(write) — IP allow-listing, rate limiting, WAF, geo-blocking, bot management (CAPTCHA preserved). - Read-parity tools:
get_cluster_credentials,list_cluster_builds,get_cluster_build,get_cluster_upgrade_path,get_cluster_insights,get_realm_role,get_realm_group,list_realm_group_members. - Update/upsert tools:
update_realm_role,update_realm_group,update_realm_user,update_application,update_identity_provider,update_cluster,update_extension,update_theme,update_domain_route,upsert_smtp,upsert_login_branding,upsert_email_branding,delete_login_branding,delete_email_branding,export_cluster_events.
[0.1.0] - 2026-05-25
Added
- MCP server with stdio and streamable-HTTP transports, built on the official Go MCP SDK.
- API-key authentication (
SKYCLOAK_API_KEY) scoped to the key's workspace. --allow-writesgate (default off) separating the read-only and mutating tool surfaces.- Read-only tools:
list_clusters,get_cluster,list_realms,list_applications,list_identity_providers. - Write tools (behind
--allow-writes):create_cluster,delete_cluster,create_realm,delete_realm,create_application,delete_application,create_identity_provider(OIDC),delete_identity_provider. Destructive tools (delete_*) requireconfirm=true. list_applicationsfollows pagination across all pages.server.json(MCP Registry listing metadata) andDISTRIBUTION.md(discovery/marketing channels).- Typed API client generated from the Skycloak OpenAPI specification (oapi-codegen) with a
make generateworkflow. - Unit tests, CI, a container image, and a release pipeline.
- Automatic
Retry-After-aware retries on429/5xxresponses. spec-syncworkflow +scripts/check-api-coverage.shthat detect upstream OpenAPI drift and report API operations not yet exposed as tools.