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.
Turning it on
Section titled “Turning it on”KAIO_MCP=readKAIO_MCP_ALLOWED_HOSTS=kaio.example.netKAIO_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 modeConnecting an agent
Section titled “Connecting an agent”claude mcp add --transport http kaio https://kaio.example.net/mcpAny MCP client that speaks streamable HTTP works the same way. To try it by hand, point the inspector at the endpoint:
npx @modelcontextprotocol/inspectorStateless by design
Section titled “Stateless by design”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.
The tools
Section titled “The tools”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.
Reading, in read
Section titled “Reading, in read”| 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.
Acting, in write
Section titled “Acting, in write”| 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.
Destroying, in admin
Section titled “Destroying, in admin”| 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.
Naming a container, an image or a network
Section titled “Naming a container, an image or a network”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.
What reaches the endpoint
Section titled “What reaches the endpoint”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.
Across a cluster
Section titled “Across a cluster”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