Skip to content

MCP server

Kaio speaks the Model Context Protocol at /mcp, on the same port as the API and the web UI. An agent that connects there gets Kaio’s state as tools it can call, so “why is the media stack partial” becomes something it answers by itself.

The endpoint is off until you turn it on.

Terminal window
KAIO_MCP=read
KAIO_MCP_ALLOWED_HOSTS=kaio.example.net

KAIO_MCP is a ladder, and each rung adds tools to the one below it:

Value What the agent can do
off Nothing. /mcp is not routed at all (the default)
read Read the state of the host: no tool changes anything
write Act on stacks and containers: deploy, update, restart, apply
admin The destructive ones too: delete a stack, prune, remove an image

Only the tools the mode allows appear in tools/list, so an agent never proposes what it cannot run, and a rung you did not grant is not merely refused: it is invisible. Start at read and move up when an agent has earned it.

The server logs the mode it settled on at startup:

INFO kaio_server: Serving MCP over streamable HTTP at /mcp in read mode
Terminal window
claude mcp add --transport http kaio https://kaio.example.net/mcp

Any MCP client that speaks streamable HTTP works the same way. To try it by hand, point the inspector at the endpoint:

Terminal window
npx @modelcontextprotocol/inspector

The transport runs with sessions off and JSON responses on. An agent posts a request and gets an answer: there is no session to establish first, nothing breaks when the server restarts, and nothing needs sticky routing if you put several Kaio behind one address. The cost is that a tool cannot stream, which is why log tools take a bounded tail rather than following.

Every tool is prefixed kaio_, takes JSON and answers JSON, and carries the annotations a client uses to decide what needs a human: the read rung is marked read-only, the write rung is marked non-destructive, the admin rung is marked destructive.

Tool What it answers
kaio_overview The state of the host in one call: container counts, every stack with its aggregated status, the version it runs, whether an update or an env change is pending, CVE totals, and the containers of a stack that are not running
kaio_stack One stack in full: status, deployed version, every container with its image and resource use, the variables it carries, which images are outdated, what the last update check said
kaio_stack_compose The Compose file of a stack: the deployed one, or the one a past version holds
kaio_stack_versions The deploy history: every version kept, newest first, with the variables it was deployed with
kaio_logs The last lines of a stack or of one container, bounded by tail
kaio_images Every image with its size and the CVE summary of its last scan
kaio_image_vulnerabilities The findings of an image’s last scan, worst first, cut to a severity floor (high by default) and a count
kaio_volumes Every volume with its driver and mountpoint
kaio_networks Every network with its driver and scope

kaio_overview leaves out what is not worth an agent’s context: a clean CVE summary is omitted rather than sent as zeroes, and a healthy stack carries no container list. Start there, then reach for a narrower tool.

A secret variable is never sent back. kaio_stack and kaio_stack_versions name the key and mark it secret, with no value, exactly as the API does.

Tool What it does
kaio_stack_deploy Write a Compose file and bring the stack up, creating it if it is new. Keeps a numbered version
kaio_stack_update Pull the images of a stack and recreate the containers that changed
kaio_stack_apply Redeploy with the stored variables, closing an env_pending. Keeps a numbered version
kaio_stack_env Store the variables of a stack. Nothing runs until kaio_stack_apply
kaio_stack_action start, restart, pause or unpause every container of a stack
kaio_stack_rollback Redeploy a stack from a stored version, variables included
kaio_stack_check_updates Ask the registry whether the images of a stack moved, and record the answer
kaio_container_action start, stop, restart, pause or unpause one container
kaio_scan Scan one image, the images of a stack, or every image on the host

kaio_stack_env replaces the stored set, so send the whole set. A variable sent without a value keeps the value and the secret flag already stored, which is how an agent edits one variable of a stack whose secrets it cannot read, and why writing a secret back cannot unmask it.

No tool in this rung is a delete call, and a deploy that fails restores the Compose file it replaced, so a bad edit is one kaio_stack_rollback away. That is where the guarantee stops. kaio_stack_deploy takes a Compose file and hands it to docker compose up -d --remove-orphans unexamined: a file that drops a service removes its containers, and a file that asks for privileged: true, a host namespace or a bind mount of the Docker socket is granted exactly that, which is root on the host.

So write is worth what an API call is worth, no less: it is the rung for an agent you would already trust with curl against /api. read is the only rung that cannot change the machine.

Tool What it removes
kaio_stack_delete A whole stack: containers, Compose file and version history. Named volumes survive
kaio_stack_version_delete One stored version. The deployed one cannot be deleted
kaio_container_delete One container, with its anonymous volumes
kaio_image_delete One image and the scan Kaio holds for it
kaio_volume_delete One volume and the data it holds
kaio_network_delete One network
kaio_prune Unused images, stopped containers, unused volumes or unused networks

These are the calls with no undo. admin is the rung to hand out last, and a client that asks before running a destructive tool is the point of the annotation.

A tool that acts on one container takes the name or an id prefix, as kaio_overview and kaio_stack report them: an agent does not have to carry full ids around. A prefix that matches two containers is refused rather than guessed. Images answer to a tag or to an id prefix the same way.

There is deliberately no shell tool. The container terminal is an interactive WebSocket that does not fit a request/response tool, and handing an agent arbitrary execution inside every container is not something a mode flag makes safe.

Kaio has no authentication, and /mcp changes nothing about that: it sits on the same port as an API that already controls the Docker daemon. Your reverse proxy remains the layer that decides who gets through. Two guards do apply, and both are about browsers rather than agents:

KAIO_MCP_ALLOWED_HOSTS is the list of Host values /mcp answers, on top of the loopback names it always accepts. Left unset, only loopback is accepted, so a remote agent is refused: that is the variable to set first when the handshake fails with 403. Set it to * to accept any Host, which turns the guard off.

KAIO_MCP_ALLOWED_ORIGINS is the list of browser origins allowed to call /mcp. Left unset, every request carrying an Origin header is refused, while requests without one (every agent, every CLI) pass. This is what stops a web page open in your browser from reaching a Kaio on localhost and driving your Docker daemon through it. Set it only if a browser app of your own needs the endpoint.

Note that these are separate from KAIO_ALLOWED_ORIGINS, which governs the REST API’s CORS policy and does not apply to /mcp.

Every Kaio serves its own /mcp, and each one speaks for its own Docker daemon. So the setup is one MCP entry per host in your agent’s configuration, named after the host, which also means the mode is per host: the lab box can sit at admin while production stays at read.

The node gateway is not in the picture here: a tool call lands on the daemon of the Kaio that answered it, never on a node it proxies.

Kaio, built by Régis Gaidot