Connect an agent to kthx
Mint an agent token in the kthx console and connect an MCP client, Rowbutt or the owner's workstation to the kthx engine, which serves each console command as a tool.
Use this runbook to give an MCP client the commands of the kthx built-apps console. The kthx engine on offsite serves each command in apps/kthx-engine/src/commands/registry.ts as a Model Context Protocol (MCP) tool at https://kthx-engine.lolwtf.dev/mcp. The endpoint accepts only an agent token, which you mint in the console at https://kthx.lolwtf.ca. A token lasts 90 days. The console shows it once, because kthx stores only its SHA-256 hash.
Before you start#
- Get a passkey on the installation (Install kthx).
- The console resolves to a private address, so your browser must reach the offsite network. The Cloudflare tunnel forwards
/mcpfrom the internet. kthx lists both addresses, andhostnameandKTHX_ENGINE_PUBLIC_HOSTNAMEinclusters/offsite/apps/kthx-engine/helm-release.yamldeclare them. - Get an MCP client that speaks streamable HTTP, such as Claude Code.
Mint an agent token#
note
An agent token cannot mint another token. The browser session cookie does not work as a bearer token.
Open
https://kthx.lolwtf.ca.Sign in with your passkey.
Go to Settings, then Identity.
In the "Agent tokens" card, select "Mint an agent token".
Result: The card shows the token and "Copy this now — it is not shown again."
Copy the token into your password manager.
Connect a client#
warning
An agent token can run every command except the ones Ownership and security lists, including the commands that delete Apps and Datastores. No token is read-only. Use a client that asks you to approve each call.
Store the token in the
TOKENvariable.read -rs TOKENPaste the token, then press Enter. The shell does not show the token.
If the client is Claude Code, add the server.
claude mcp add --transport http kthx https://kthx-engine.lolwtf.dev/mcp --header "Authorization: Bearer $TOKEN"Result: The command prints
Added HTTP MCP server kthx with URL: https://kthx-engine.lolwtf.dev/mcp to local config.If the client reads a JSON file, add the server to it.
{ "mcpServers": { "kthx": { "type": "http", "url": "https://kthx-engine.lolwtf.dev/mcp", "headers": { "Authorization": "Bearer <token>" } } } }Make sure the endpoint lists its tools.
curl -s https://kthx-engine.lolwtf.dev/mcp \ -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools | length'Result: A number larger than zero.
Ask the client to call
getDeveloperSurfaces. The tool is read-only.Result: The endpoint's private and public URLs, and the quick-site origin and zone.
Give Rowbutt a token#
Rowbutt runs every command without approval, so the warning above applies in full. mate reaches the engine in the cluster, and the ExternalSecret mate-kthx-agent in namespace mate gives mate the token as KTHX_AGENT_TOKEN. mate bridges the engine's tools into the agent as kthx_* tools. mate reads the token when it starts. Reloader restarts mate when the Secret changes, but not when the first sync creates the Secret.
Mint an agent token as above.
In the
homelabvault in 1Password, create an API Credential item titledmate kthx agent token.Put the token in the item's
credentialfield.Make sure the ExternalSecret has synced. It refreshes on its interval.
kubectl --context offsite -n mate get externalsecret mate-kthx-agentResult:
STATUSisSecretSynced.Read how long ago mate started.
kubectl --context offsite -n mate get pods -l app.kubernetes.io/name=mateResult: The pod is
Running, and itsAGEis the time since it started.If mate started before the sync, restart mate.
kubectl --context offsite -n mate rollout restart deploy/mateResult:
deployment.apps/mate restarted.In a Rowbutt thread, ask which
kthxtools the agent has.Result: Rowbutt names tools whose names start with
kthx_.
A revoked or expired token makes the engine refuse mate. After the next restart the agent has no kthx_* tools, and until then their calls fail. Mint a new token, and update the item.
Give the workstation a token#
The mate pi package connects pi on the owner's workstation to the engine. pi/mate/extensions/mcp.ts in the dotfiles repository reads the token with op read when pi starts, so the token never enters pi's environment. pi names the tools mcp__kthx__*.
warning
pi asks for no approval before each call, so the warning in Connect a client applies in full. The owner accepts this for the workstation token.
Mint an agent token as above. Do not use Rowbutt's token.
In the
homelabvault in 1Password, create an API Credential item titledworkstation kthx agent token.Put the token in the item's
credentialfield.Put the date 90 days after the mint in the item's
expiresfield.Make sure that the item resolves. The command does not print the token.
op read 'op://homelab/workstation kthx agent token/credential' >/dev/null && echo resolvesResult: The command prints
resolves.Start pi from a shell where
opis signed in.Ask pi to call the kthx tool
getDeveloperSurfaces. The tool is read-only.Result: The endpoint's private and public URLs, and the quick-site origin and zone.
The extension sends this Authorization header, a command that pi runs in a shell:
!t=$(op read 'op://homelab/workstation kthx agent token/credential') && printf 'Bearer %s' "$t"If op fails, pi reports that kthx failed and starts without it. pi reads the token once when it starts, so restart pi after you replace the token.
Revoke a token#
Go to Settings, then Identity, in the console.
In the "Agent tokens" card, find the token by its mint date and last use.
Select "Revoke".
Result: The card does not list the token. Your browser session stays signed in.
If something goes wrong#
| Symptom | Cause | Action |
|---|---|---|
| HTTP 401 with "this surface needs an agent token". | The token is missing, expired, revoked, or a browser cookie. | Mint a new agent token. |
| HTTP 405 with "POST JSON-RPC here". | The request is not a POST. | Use POST. The endpoint does not stream Server-Sent Events (SSE). |
HTTP 404 on /mcp. | The host is not a kthx engine host. | Use https://kthx-engine.lolwtf.dev/mcp. |
A tool result has isError set to true. | kthx refused the command. The text has a code, such as NOT_DEPLOYABLE or INVALID_INPUT, and the sentence the console shows. | Correct the input as the sentence tells you. Call the tool again. |
A tool result has the code FORBIDDEN. | An agent token called mintAgentToken. | Mint tokens in the console. |
Related#
Linked from 7
- kthxThe lab's hosting product on the offsite cluster, which the owner and agents use to serve a directory at <name>.kthx.dev or to build a repository and deploy it to a cluster or a cloud.
- RowbuttA chat bot, code name mate, that answers the owner in Discord and Slack threads with a coding agent whose tools run in a sandbox on the offsite cluster.
- HomeThe owner's homelab at two sites, folly and offsite, managed as code: its apps, platform, hosts and runbooks.
- GlossaryThe lab-specific terms that the wiki pages use, what each means, and the page that defines it.
- Connect an agent to the wikiAdd the wiki's public, read-only MCP endpoint to Claude or another MCP client, and check that it answers.
- RunbooksStep-by-step procedures for the hosts, clusters, network, secrets and apps, grouped by area.
- Install kthxInstall the kthx engine on the offsite cluster, from the OpenTofu roots to the first passkey.