Skip to main content

One Token, Seven Tools, Total Access: What a Czech Community's Coolify MCP Bridge Teaches About Agent-Operated Self-Hosting

11 min readDora NodaDora Noda
Share
On this page

The most honest picture of agent-operated self-hosting in 2026 is not a vendor launch post. It is a 200-line Python file, written in Czech, with TLS verification switched off.

In early July 2026, a small Czech tech collective operating as xlop.cz published xlop-dev/coolify-anythingllm-mcp: a Model Context Protocol server that plugs the open-source chat front end AnythingLLM straight into the Coolify API. An agent can list, inspect, and operate apps on a self-hosted VPS or homelab without ever opening the Coolify dashboard. The README's pitch line is exactly right: "giving them the context of your infrastructure is a game-changer."

Coolify itself is one of the most-starred self-hosted PaaS projects on GitHub — north of 40,000 stars by 2026 counts — so a community bridge that lets agents drive it matters far beyond its own audience. The bridge itself has essentially none yet: an MIT license, zero stars, code last touched in July 2026.

I read the whole thing — both server files, the Dockerfile, the README — and the verdict up front is this: the bridge works, the pattern is real, and the trust model is one bearer token away from de-facto root. A single unscoped API token plus a direct Docker-socket reader plus an agent-callable SSH shell means the agent is not a dashboard assistant. It is the operator, with no permission model standing between a misread prompt and production.

Below is the full tool inventory, what the project gets right, the five findings in the code, and the guardrail checklist that separates a homelab experiment from a platform you would hand to tenants.

The bridge, inventoried: seven tools, one token, stdio

The architecture is deliberately boring, which is a compliment. AnythingLLM spawns the bridge as a child process over MCP's stdio transport — the same { command, args, env } shape AnythingLLM documents for all its custom MCP servers — and the Python server speaks hand-rolled JSON-RPC back, standard library only, no MCP SDK. Configuration is two environment variables: a Coolify base URL and a bearer token minted in the Coolify dashboard. (Coolify's API is disabled by default and has to be switched on in Settings; tokens live under Security or Keys and Tokens, and Coolify checks the token's user/team permissions per action.)

Here is every tool the server actually exposes, from coolify_mcp.py:

ToolWhat it doesPrivilege it needs
coolify_list_serversLists servers via GET /api/v1/serversRead, via bearer token
coolify_list_applicationsLists apps (uuid, name, status, FQDN)Read, via bearer token
coolify_application_logsFetches one app's logs by uuidRead, via bearer token
host_docker_psLists all containers on the host, Coolify-managed or notDirect /var/run/docker.sock access — bypasses the API entirely
host_docker_logsLast 50 lines of any container's logs by namedocker.sock, same bypass
host_docker_statsPer-container CPU/RAM against limitsdocker.sock, same bypass
ssh_execRuns an arbitrary bash command on an arbitrary host over SSHA baked-in private key at /app/server/storage/sysadmin_key, StrictHostKeyChecking=no

Note what is not there: no deploy tool, no restart tool, no rollback tool — despite the README advertising "Deployment Automation: trigger new builds, restarts, or rollbacks." More on that gap shortly, because it turns out to be the most instructive part. Also note the language: every tool description is in Czech ("Seznam serverů v Coolify", "Spustí příkaz na vzdáleném serveru"). Agent-ops is already happening outside the Anglophone core, in the language of whoever runs the box. Any platform serious about agents as first-class operators should take that as a requirements hint, not a curiosity.

What it gets right

Credit where it is due — four decisions in this little repo are genuinely good engineering for its context:

  • Read-mostly by default. Six of seven tools only observe. An agent that can read logs, list apps, and check container stats resolves most "why is it down" sessions without mutating anything. Least privilege by accident is still least privilege.
  • Boring dependencies. The stdio server imports sys, json, os, urllib, ssl, and socket. Nothing to supply-chain-attack, nothing to version-pin, nothing that breaks when a framework ships a major. For a bridge that holds infrastructure credentials, a tiny dependency surface is a security feature.
  • Secrets in environment, not in code. The token and URL come from env vars with placeholder defaults, which is the correct shape — and matches how AnythingLLM passes env to stdio children.
  • The disclaimer is load-bearing. The README recommends human-in-the-loop confirmations and disclaims responsibility for downtime or data loss. For a zero-star community project, an honest "this is experimental" beats a false "secure by design" — though, as we will see, the README claims both.

What the code actually grants: five findings

1. TLS verification is off — in both servers. coolify_mcp.py builds its SSL context with check_hostname = False and verify_mode = ssl.CERT_NONE; the FastAPI sibling main.py calls requests.get(..., verify=False). The default URL is even plaintext HTTP to a LAN address (http://192.168.1.4:8000). On a home LAN this is understandable; the moment the Coolify instance moves behind a public hostname, the bearer token — the only credential in the system — travels over a connection the code refuses to authenticate. Finding: the single credential's transport security is opt-in and currently opted out.

2. Half the tools bypass the platform's permission model. The three host_docker_* tools talk straight to /var/run/docker.sock, which is root-equivalent on the host. Coolify's per-action permission checks never see these calls. Anything the agent can name — including containers Coolify does not manage, like the AnythingLLM instance itself — it can read. Side-channel admin access next to a permission-checked API means the audit story has two books, one of which nobody writes.

3. ssh_exec is an agent-callable remote shell. The seventh tool takes a host and a command and runs it over SSH with a hardcoded key path and host-key checking disabled — no allowlist of hosts, no allowlist of commands, no confirmation step in code. The Czech description even steers toward privileged use: reach for it for root access to Ubuntu servers. Whatever the other six tools cannot do, this one can, including restarting, deleting, or exfiltrating anything the xlop user can reach.

4. One token rules everything, forever. A single Coolify bearer token authenticates every API call with no scoping (read-only vs. mutate is not expressible), no expiry handling, and no rotation story. There is no audit log anywhere in the code — the main loop prints JSON-RPC responses to stdout and remembers nothing. If the token leaks through a log line the agent helpfully pastes into chat, there is no scope to narrow the blast radius and no trail to reconstruct what happened.

5. The README describes a different project. The setup docs tell you to run node /path/to/dist/index.js with COOLIFY_API_URL / COOLIFY_TOKEN; the code is Python reading COOLIFY_URL / COOLIFY_TOKEN. The README promises deploy automation; the code ships no deploy verb. In a normal library this is tech-debt trivia. In infrastructure tooling operated by an agent that reads docs to decide what it can do, doc-code drift is a safety issue: the agent plans against capabilities that do not exist and misses ones that do.

None of this is an accusation — it is a July-2026 homelab experiment that says "experimental" on the tin. The reason to take it seriously is that findings 1–4 are the default shape of every hand-rolled agent bridge, and this one is small enough to hold entirely in your head.

The missing deploy button is the lesson

Here is the uncomfortable question: if there is no deploy tool, where is the "agent as capacity planner" half of the story? Answer: the deploy path exists — in the least-governed form possible. When this agent needs to redeploy, its only road is ssh_exec plus the bearer token: raw shell against the host, or a raw API call with a credential that can do everything. No plan step, no validation, no diff to review, no rollback verb.

Compare that with what the managed platforms converged on this summer: Render's trigger_deploy as a named, scoped, logged verb; validate-before-apply flows where the agent reviews a resource plan and pricing before anything mutates. The spectrum is not "can the agent deploy" — the homelab bridge can, via SSH — it is how many governed steps stand between intent and mutation. Raw shell is zero. A scoped deploy verb with a plan step is the whole ballgame, and it is the single biggest item on the checklist below.

The spectrum: first-party OAuth vs. community stdio

Place the bridge next to the summer-2026 vendor launches and a pattern snaps into focus:

First-party vendor MCP (Render, Fly, Railway)Community stdio bridge (this repo)
TransportStreamable HTTP, networkedstdio child process, same machine
AuthOAuth with per-client flowsOne static bearer token in env
ScopingEvolving (OAuth scopes, per-assistant plugins)None — token, socket, and SSH key are each all-powerful
AuditVendor dashboard (their retention, their rules)No log at all
CeilingWhatever the vendor exposesWhatever the OS allows (i.e., everything)

The protocol's answer to the left column's problems already exists: MCP's authorization spec layers OAuth 2.1 with mandatory PKCE, RFC 8707 resource indicators that bind a token's audience to one server so it cannot be replayed elsewhere, and dynamic client registration — with identity vendors like WorkOS AuthKit now supporting resource indicators natively so small teams do not have to run the authorization server themselves. The bridge uses none of it, because stdio + a static token was shippable in an afternoon and OAuth was not. That tradeoff is rational for a homelab and disqualifying for a tenant-facing platform — and "shippable in an afternoon" is exactly why the pattern will keep spreading faster than its guardrails.

The guardrail checklist, mapped to the findings

A first-class platform MCP server — the kind a self-hosted PaaS should offer tenants — needs every row below. Each maps to a finding above:

  • Scoped authority, not one token. Separate read, mutate, and secrets grants (the bex.read / bex.write / bex.sensitive shape), so a log-reading agent never holds redeploy power. Fixes findings 3–4.
  • TLS on, verified, no exceptions. No CERT_NONE, no verify=False, no plaintext defaults. Fixes finding 1.
  • No side-channel admin. Every tool call — including host-level inspection — goes through the same policy check as the platform API. There must not be a docker.sock-shaped back door around the permission model. Fixes finding 2.
  • An audit trail you can hold. Who authorized what, which agent call mutated which resource. Self-hosted means the trail lives on your machines under your retention policy — strictly better than the vendor dashboard, but only if you actually write it. Fixes finding 4.
  • TTLs and quotas on agent credentials. Short-lived tokens, per-workspace rate limits, automatic expiry for credentials minted for a single job. Fixes finding 4.
  • Enforced confirmation for mutations, not recommended. Human-in-the-loop as a policy gate on the server side — protected operations demanding an explicit approval — rather than a README suggestion the agent's harness may or may not implement. Fixes findings 3–5.
  • Deploy as a scoped verb with a plan step. Validate-then-apply against a checked-in manifest, with rollback as a first-class tool. Replaces the raw-shell deploy road with something reviewable. Fixes the missing button.

The pattern arrives bottom-up

Step back and the geography is the story. The managed platforms spent summer 2026 announcing agent surfaces top-down — OAuth servers, chat plugins, disposable computers. Meanwhile, on a Czech homelab, an agent was already restarting containers via a 200-line stdio bridge with Czech tool names. Agent-operated self-hosting is not waiting for permission, standards completion, or a vendor roadmap: it is arriving bottom-up, one community bridge at a time, on machines their owners already have.

That is both exciting and exactly the risk. Every such bridge concentrates total infrastructure power behind a single static credential with no audit trail. The platforms that win the agent-ops era will not be the ones that merely expose tools — this repo proves tools are the easy part — but the ones that wrap those tools in scoped authority, verified transport, real audit trails, and deploy verbs with plan steps. The homelab showed us the shape. Now build it like tenants live there.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Sources

Related articles

Give your agents a chain backend

Autonomous agents hit RPC endpoints very differently than people do. See what bex router handles on their behalf.

Read the agents guide