Skip to content

Let an agent test in its own cluster

In this step a coding agent runs the loop of the Quick start for you: it creates a throwaway cluster, deploys upcheck there from your upcheck.yaml, runs your tests against it, reports, and deletes the cluster. It works through the portal’s MCP tools with an API key that carries only the scopes it needs, and every call it makes is recorded.

A report from your agent on one version of upcheck, tested in a cluster that existed only for the test, and a log of every call the agent made, from creating that cluster to deleting it. upcheck-prod is not touched.

  • Steps 1 and 2 done. Your team’s first cluster started its subscription; the API cannot start one (it answers payment_method_required), so a team’s first cluster is always created in the portal. Keep your upcheck.yaml.
  • A release with the PaaSbox Platform, 2026.10.2 or newer, on the stable channel. A cluster the agent creates takes the release of its channel, stable unless the call names another.
  • A team admin’s account. Only team admins create keys, and a key acts with the rights of the admin who made it: creating and deleting a cluster need a team admin.
  • A Hetzner project for test clusters, connected like the one in step 1. A throwaway cluster gets a copy of its project’s token, so the agent never handles a Hetzner token; in a project that holds only test clusters, that copy reaches nothing else.
  • A coding agent that speaks MCP over HTTP, such as Claude Code, on a machine with kubectl and Python 3.10 or later.
  • The image to test in a registry the cluster can pull from, and your tests: a command that checks the app over HTTP, given its address. What the tests check is yours to decide.
  • Room for one more cluster in your team’s limit of 10.
  1. Open API keys & agents. Under Create a key, enter the Name claude-code, tick the Scopes k3s:read, k3s:write, k3s:access and k3s:destructive, and set Expires to in 30 days. Choose Create key.

  2. Copy the key. The page shows it once and keeps only a hash of it.

Each scope opens a set of tools, and the agent gets exactly those. k3s:read lists projects, server types, clusters, add-ons and operations. k3s:write switches add-ons, here the PaaSbox Platform on the test cluster; it would also take snapshots and schedule upgrades. k3s:access gets kubeconfigs. k3s:destructive creates and deletes clusters.

A scope is not limited to one cluster: this key could also switch add-ons on upcheck-prod, upgrade it or delete it. Give k3s:destructive only to an agent you watch, as here. A key for an agent that works on your stages alone should not carry it. To keep production out of a key’s reach entirely, keep it in a team the key does not belong to (Guardrails for agents).

  1. Connect your agent on the same page shows the MCP endpoint, the portal’s address followed by /mcp/. Add it to Claude Code:

    Terminal window
    export PAASBOX_API_KEY=<the key>
    claude mcp add --transport http paasbox https://<the portal>/mcp/ \
    --header "Authorization: Bearer $PAASBOX_API_KEY"
  2. Download the kubeconfig helper the page links, paasbox_kubeconfig.py, into the agent’s working directory, and run pip install cryptography.

  3. Let the agent ask you before it calls create_cluster, delete_cluster, restore_snapshot or detach_cluster: in Claude Code, do not add them to the allowed tools.

When the agent connects, the server tells it which team it acts in, as whom, and with which scopes. With all four scopes it sees all 18 tools.

Start the agent in the directory with upcheck.yaml and your tests, and give it the task:

Test the upcheck image registry.example.com/upcheck:<tag> in a throwaway PaaSbox cluster.
1. Create the cluster upcheck-test-1 in the Hetzner project for tests, location fsn1, server type cpx22,
with a copy of the project's token. Open its API only to this machine's address.
2. Wait until it is ready. Switch on the paasbox-platform add-on, profile saas-http01, ACME e-mail
you@example.com, with Flux, and wait until it reports applied.
3. Get an admin kubeconfig for one hour with paasbox_kubeconfig.py. Never show it or the private key.
4. Apply upcheck.yaml in namespace upcheck with spec.image set to the image above and without spec.exposure.
Wait until the SaaSApplication is Ready.
5. Port-forward svc/upcheck-web to localhost:8000 and run ./smoke-test.sh http://localhost:8000.
6. Report what passed and what failed.
7. Delete the cluster without a final snapshot.

What the agent does with it:

  1. It calls list_projects and list_server_types for the project’s ID and the server types with their memory and Hetzner’s monthly net price.

  2. It calls create_cluster with name and confirm both upcheck-test-1, hcloud_token {"mode": "copy"} and your address in allowed_api_ranges. Your agent asks you first; say yes. The portal refuses the call unless confirm is the new cluster’s name, and runs the create page’s checks: your team’s limit, the accepted terms, billing.

  3. It polls get_cluster until state is ready. In the lab a cluster was ready 2 minutes 46 seconds after the request; 69 of them copied the node image into the project, which only the project’s first cluster waits for.

  4. It calls set_addon with addon paasbox-platform, enabled true, options {"profile": "saas-http01", "acmeEmail": "you@example.com"} and also ["flux"], which switches Flux on in the same save, and polls list_addons until the platform reports applied. The create call has a platform field too, but the API has no field for the ACME e-mail that every profile needs, so a create with platform is refused; the portal’s create page asks for the e-mail, the API does not.

  5. It makes a key pair with python3 paasbox_kubeconfig.py keygen --out key.pem, passes the public half to request_kubeconfig with role admin, and polls get_kubeconfig_result. The result is encrypted to its key and handed out once; paasbox_kubeconfig.py decrypt opens it on the agent’s machine. The portal cannot read it.

  6. It applies the edited copy of upcheck.yaml, which creates the namespace upcheck too. Without spec.exposure the platform publishes nothing: no Ingress, no certificate, no DNS name to wait for, and the Django profile accepts any host name over plain HTTP. kubectl -n upcheck get saasapp shows the phase Ready once the migration has run and the components are up.

  7. It runs your tests through kubectl -n upcheck port-forward svc/upcheck-web 8000:80 and reports.

  8. It calls delete_cluster with confirm upcheck-test-1 and final_snapshot false: the cluster has no bucket, and a final snapshot would stay on the disk that is deleted. Your agent asks you again. Billing stops at once; in the lab a delete took 14 seconds, and no resource with the cluster’s label was left.

On API keys & agents, Recent activity lists the last calls; All activity has all of them, filtered by Key. Each MCP call is one row, reads and refusals included: when it ran, the key, the call, the cluster and the outcome, with the code of a refusal such as confirmation_required. The arguments are kept with every secret replaced by [redacted]. What the agent ran with kubectl is not in this log: those calls went to the cluster’s own API.

In progress Paddle charges a further cluster when it is first ready, prorated to the end of the billing period, and credits the rest when it is deleted; upcheck-prod keeps the team’s subscription running meanwhile. Hetzner billed the test cluster’s server by the hour, at its own price.

The MCP tools reference lists every tool with its scope and arguments; Guardrails for agents says what each limit stops and what it does not.

An agent that tests a change in a cluster built for the test and removes the cluster afterwards, with a key that carries the scopes you chose, and a record of each call. The next step gives upcheck a second stage.