Skip to content

MCP tools

The MCP server of PaaSbox Clusters gives a coding agent, such as Claude Code or any other MCP client, the portal’s cluster operations as tools. This page lists the endpoint, how a call is authenticated, and every tool with its scope, its input, its output and what it refuses.

ItemValue
URL/mcp/ on the portal’s host (/mcp works too). The API keys & agents page shows the full URL.
TransportStreamable HTTP, stateless, JSON answers. POST one JSON-RPC 2.0 message or a batch: a request gets 200 with its response, a notification 202. GET and DELETE answer 405: there is no event stream and no session.
Protocol versions2025-11-25 (the latest), 2025-06-18, 2025-03-26, 2024-11-05. initialize answers with the client’s version if it is one of these, else the latest; an MCP-Protocol-Version header must name one of them (400 otherwise).
Methodsinitialize, ping, tools/list, tools/call
Servername paasbox-clusters, title “PaaSbox Clusters”. Its instructions tell the agent its team, its person and role, the key’s scopes, and the rules below.
AuthenticationAuthorization: Bearer <key> on every request, a team API key (Give an agent access). No key, an unknown, revoked or expired one, or one whose owner is no longer an active member of the team: 401 with WWW-Authenticate: Bearer. A key with no k3s scope, or a team without PaaSbox Clusters: 403.
BrowsersAn Origin header must be the portal’s own host or one the portal allows (403 otherwise).
Tool listtools/list shows only the tools the key’s scopes allow.

“Admin” means the key’s owner must be a team admin. Every tool that takes cluster takes the cluster’s slug (its DNS label) or its id. A required input is marked with *.

ToolScopeAdminInputOutput
list_clustersk3s:read—none{items}: up to 100 clusters
get_clusterk3s:read—cluster*the cluster in full: state and health, nodes, the operation in flight, the last snapshot, the backup target, the maintenance window, the upgrade on offer (upgrade.available)
list_snapshotsk3s:read—cluster*, limit (1–100, default 20){items, truncated}, newest first, one row per copy (local, s3)
create_snapshotk3s:writeyescluster*the queued operation
list_operationsk3s:read—cluster*, limit, state, type{items, truncated}, newest first
get_operationk3s:read—cluster*, operation_id*the operation with its events
schedule_upgradek3s:writeyescluster*, when* (window or now), releasethe queued operation
list_addonsk3s:read—cluster*the release’s add-ons: on or off, options, optionsSchema, requires, conflicts, memory, what the cluster reported
set_addonk3s:writeyescluster*, addon*, enabled, options, also{addon, switched, generation, note}
request_kubeconfigk3s:accessfor admin, if the team does not allow memberscluster*, public_key*, role (view default, or admin), lifetime_seconds (600–86400, default 3600)the queued operation and its operation_id
get_kubeconfig_resultk3s:access—cluster*, operation_id*{operationId, state, ready: false, retryAfterSeconds}, then once {operationId, state, ready: true, sealed, aad, expiresAt, decrypt}; decrypt spells out how to open it
revoke_accessk3s:accessfor anyone but the key’s ownercluster*, user (me default, all, or a member’s id or e-mail)the queued operation
list_projectsk3s:read—none{items}: the team’s Hetzner projects, {id, name, ref}
list_server_typesk3s:read—project*, location (default fsn1)the project’s locations and the server types offered in one, with memory and Hetzner’s monthly net price in EUR
create_clusterk3s:destructiveyesname*, confirm*, project*, and the optional fields below{cluster, next}; provisioning runs in the background
delete_clusterk3s:destructiveyescluster*, confirm*, final_snapshot (default true){cluster, next}; the cluster stops counting for billing at once
restore_snapshotk3s:destructiveyescluster*, snapshot*, confirm*the queued operation
detach_clusterk3s:destructiveyescluster*, confirm*{cluster, next}

A queued operation is returned as {operation, operation_id, next}. pbx-agent picks it up at its next sync, about every 30 seconds: poll get_operation until its state is succeeded or failed.

The read tools carry the MCP annotation readOnlyHint; create_cluster, delete_cluster, restore_snapshot and detach_cluster carry destructiveHint.

InputValues
slugthe DNS label, lowercase letters, digits and hyphens, up to 32; made from the name
location, server_typefrom list_server_types
adopt_servera server you already have: id*, confirm_name* (its name, typed: it is rebuilt), keep_volumes, detach_firewalls
channelstable (default) or early
topologysingle
maintenancedays (at least one weekday), start and end as HH:MM, timezone
allowed_api_rangesCIDRs that may reach the Kubernetes API; default everyone (0.0.0.0/0, ::/0)
backupholder (portal default, or customer), s3 (endpoint, bucket, folder, region, access_key, secret_key), schedule_cron (five cron fields, UTC), keep_cycles (1–500)
hcloud_tokenmode (separate default, or copy), token
platformprofile*, observability. Since paasbox-platform 1.0.2 (release 2026.10.2) the platform needs an ACME e-mail (acmeEmail), which create_cluster cannot pass, so a create with platform is refused with validation_failed. Create without platform and switch the platform on with set_addon: addon paasbox-platform, enabled true, options with profile and acmeEmail, also ["flux"]

The create page’s checks apply, in its order: a connected project, a release on offer on the stable channel, the team’s cluster limit of 10 (quota_exceeded), the accepted terms, and billing. A team’s first cluster starts its subscription and is created in the portal, not through a tool (payment_method_required); so is the first cluster after the team’s subscription has ended. The input is checked against the release on the stable channel, also for channel early, and platform is refused while the add-on needs an e-mail the tool cannot pass (see above).

The four destructive tools need k3s:destructive, a team admin as the key’s owner, and confirm:

  • delete_cluster, restore_snapshot, detach_cluster: the cluster’s name (not its slug), exactly;
  • create_cluster: the new cluster’s name, typed again.

The server’s instructions tell the agent to ask you before it sets confirm. The portal cannot check that it did; it checks the name. Whether you are asked depends on your MCP client and how you set it up.

A key with k3s:destructive reaches every cluster of its team. To keep an agent that creates throwaway clusters away from production, keep production in a team the key does not belong to (Guardrails for agents).

CaseWhat the client receives
SuccessA tool result with the answer as JSON text and as structuredContent, isError: false. Text over 64 KiB is cut off with a note to narrow the call.
RefusalA tool result with isError: true: the text <code>: <detail> and the problem document (type, title, status, detail, code, field and extra members) as structuredContent. The codes are the REST API’s (REST API › Errors).
Input that does not match the schemavalidation_failed, with errors listing every field. The schemas allow no extra properties.
Too many callsrate_limited, with retryAfterSeconds.
Unknown toolJSON-RPC error -32602.
Unknown methodJSON-RPC error -32601.
Not a JSON-RPC 2.0 messageJSON-RPC error -32600; a body that is not JSON: -32700 with HTTP 400.

A kubeconfig never travels in clear, and the portal cannot read it:

  1. The agent makes an ephemeral ECDH P-256 key pair and keeps the private half. The portal’s helper does it: python3 paasbox_kubeconfig.py keygen --out key.pem prints the public key. The helper is served at /mcp/paasbox_kubeconfig.py and needs Python 3.10 or later and the cryptography package.
  2. request_kubeconfig with that public key. pbx-agent on the cluster issues a kubeconfig with a token that expires after lifetime_seconds and encrypts it to the key: ECDH, HKDF-SHA256 (32 zero bytes of salt, info pbx-access-v1), AES-256-GCM with the operation id as associated data.
  3. get_kubeconfig_result until ready is true. It hands the sealed kubeconfig out once, to the same key, within 5 minutes; after that it answers gone.
  4. The agent saves the result’s JSON and decrypts it locally: python3 paasbox_kubeconfig.py decrypt --key key.pem < result.json > kubeconfig.

The instructions tell the agent never to put the private key or the decrypted kubeconfig into the conversation.

A key may make 120 calls a minute, 20 of them calls of tools that are not read-only, among them request_kubeconfig, get_kubeconfig_result and revoke_access; the counters are the same as the REST API’s. Every tool call is recorded, refusals and unknown tools included, with the key, the person, the tool, the cluster, the arguments without secrets and the outcome. The team’s admins read it on API keys & agents (Give an agent access).

Guardrails for agents says what these rules stop and what they do not.