One of the seven tools in this MCP server is called ssh_exec. Its description, written in Czech, translates roughly to: "Runs a command on a remote server over SSH. Use for root access to Ubuntu servers." It takes two arguments — a host and an arbitrary bash command — and executes them with a baked-in private key, host-key checking disabled. That is what "deploy authority" looks like when a community builds MCP for infrastructure before the platform does: not a scoped deploy token, but a shell.
The project is xlop-dev/coolify-anythingllm-mcp, a Model Context Protocol server that wires the AnythingLLM chat interface to a self-hosted Coolify instance so an AI agent can inspect, troubleshoot, and operate production servers in natural language. I read every line of it. What follows is a tool-by-tool teardown of what the agent actually gets, where the authorization boundary breaks, and the five things a platform-owned MCP server has to do differently.
The 30-second version
- What: An MCP server bridging Coolify's API to AnythingLLM (and through it, to Claude or any other model) for what the README calls "autonomous AI infrastructure management."
- Who: The team behind xlop.cz, self-described as "the Czech tech hub for AI and Self-Hosting innovations." The Czech provenance is visible in the code itself: every tool description is written in Czech.
- How big: The MCP server (
coolify_mcp.py) is 133 lines of dependency-free Python — hand-rolled JSON-RPC over stdio. MIT licensed, created July 4, 2026, last pushed July 24, 2026. - How Claude enters the picture: AnythingLLM is the MCP client and agent UI — it has supported MCP servers since version 1.6.0 via its Agent Custom Tools settings — and Claude is one of the models that can drive the agent. The bridge itself is model-agnostic: any LLM behind AnythingLLM inherits the same tools.
- The headline finding: The README promises natural-language deploys, restarts, and rollbacks, but the shipped code contains no deploy or restart tool. All mutating power flows through raw infrastructure escape hatches: a Docker socket reader and an unconstrained SSH command runner.
That last point is the post in one sentence. Now the evidence.
What the agent actually gets: all 7 tools
The agent-to-infrastructure chain has three links, and each one matters for the security analysis:
- Transport:
coolify_mcp.pyspeaks MCP over stdio — plain JSON-RPC lines on stdin/stdout, no HTTP, no authentication layer of its own. The server inherits its launcher's environment, including the Coolify API token. - Client: AnythingLLM acts as the MCP client, the agent runtime, and the chat UI. Its agent invokes tools during
@agentchats or no-code Agent Flows. - Brain: Claude (or any configured model) decides which tools to call. The model sees only tool names and Czech-language descriptions — there is no policy layer between its decision and execution.
Here is the complete tool surface, all seven tools:
| Tool | Transport | What it grants | Privilege |
|---|---|---|---|
coolify_list_servers | Coolify API | Server UUIDs, names, IPs, reachability | Read-only |
coolify_list_applications | Coolify API | App UUIDs, names, statuses, domains | Read-only |
coolify_application_logs | Coolify API | Full logs for any app UUID | Read (often sensitive) |
host_docker_ps | Raw docker.sock | Every container on the host, all states | Host-wide read |
host_docker_logs | Raw docker.sock | Last 50 lines of any container's logs | Host-wide read |
host_docker_stats | Raw docker.sock | Live CPU/RAM for any container | Host-wide read |
ssh_exec | SSH as xlop@host | Arbitrary bash on any reachable host | Unconstrained root-intended shell |
Two things stand out. First, the "shell access" in this post's title is precisely ssh_exec plus the docker.sock trio — there is no first-class deploy, restart, or rollback tool anywhere in the code, despite the README's Deployment Automation section promising exactly that. If the agent restarts your Postgres container, it does so by shelling out over SSH, not by calling a reviewed, auditable platform API.
Second, the repository actually contains two half-merged implementations. Alongside the stdio MCP server sits main.py, a FastAPI HTTP wrapper exposing five strictly read-only tools (list servers, list containers, server resources, list applications, application logs). The README, meanwhile, documents a third thing that does not exist: a Node.js server at dist/index.js configured with COOLIFY_API_URL and COOLIFY_API_TOKEN, while the Python code reads COOLIFY_URL and COOLIFY_TOKEN.
This is a scrappy community project mid-evolution, and the documentation describes its aspirations more reliably than its behavior. For a security review, only the code counts — so the code is what this post audits.
The authorization boundary, line by line
Strip away the MCP framing and the bridge grants its agent five compounding privileges, each visible in a line or two of source:
1. One bearer token for everything. Both implementations read a single COOLIFY_TOKEN from the environment and attach it to every API call. Coolify v4 does offer team-scoped tokens with permission levels (read, read:sensitive, write, deploy, root) — but the bridge never touches that machinery. Whatever permission the operator's one token carries, the agent carries too, across every read tool, with no per-tool least privilege.
2. TLS verification is off. The stdio server builds an SSL context with CERT_NONE and hostname checking disabled; the FastAPI variant passes verify=False on every request. On a LAN this is a shrug; the moment the Coolify URL points at anything beyond localhost, the bearer token rides a connection the code refuses to authenticate.
3. Raw Docker socket access. The three host_* tools speak to /var/run/docker.sock directly over a Unix socket. Docker socket access is root-equivalent on the host — anyone who can write to it can start a privileged container and own the machine. Here it is used read-only, but the channel itself has no read-only mode: the code chose the most powerful interface available and relies on its own restraint.
4. Unconstrained SSH with a baked-in key. ssh_exec shells out to ssh -i /app/server/storage/sysadmin_key -o StrictHostKeyChecking=no xlop@[host] [command]. Any host string, any command, no allowlist, no confirmation hook, no logging beyond stdout capture. The README recommends human-in-the-loop confirmations and disclaims responsibility for "server downtime, data loss, or accidental resource deletion" — an honest admission that the tool's blast radius is whatever the model types.
5. A plaintext LAN default. Both files default COOLIFY_URL to http://192.168.1.4:8000 — unencrypted HTTP to a hardcoded private IP. Sensible for the author's homelab, and exactly the kind of default that follows a project into production; combined with finding 2, encryption is opt-in at every layer and default at none.
Now the contrast that makes this a platform-design lesson rather than a bug report:
| Kubernetes-native agent | This bridge | |
|---|---|---|
| Identity | Namespaced ServiceAccount per agent | One shared bearer token |
| Scope | Role: verbs on resources in one namespace | Whole team API, whole Docker host, any SSH host |
| Enforcement | API server checks every call | None — ambient authority |
| Audit | API audit log | stdout capture only |
On a Kubernetes-native platform, the equivalent agent would hold get pods/log in namespace tenant-a and nothing else, with the platform API server enforcing that boundary on every call. Here there is no identity per agent, no scope per tool, and no enforcement point at all — the agent inherits whatever the single token and the Unix socket allow, which on a single-box Docker host is effectively everything. That gap — ambient authority versus scoped identity — is the thing a platform MCP server must close, and it is the thread the rest of this post pulls on.
Why this still matters: the demand is real
The obvious dismissal writes itself: zero stars, zero forks, last touched in July, README that documents a different program. Why should anyone designing platform infrastructure care about a 0-star repo from a Czech homelab?
Because it is not alone. At least three independent "MCP for Coolify" efforts appeared in 2026, each built by a different author, each converging on the same shape — an MCP server fronting the Coolify API for an AI agent:
| Project | First commit | Approach | Stars |
|---|---|---|---|
| Softtor/coolify-mcp-server | January 2026 | TypeScript, multi-team token support | 2 |
| xlop-dev/coolify-anythingllm-mcp | July 2026 | Python, AnythingLLM/Claude bridge plus SSH | 0 |
| hecateq/mcp-coolify | August 2026 | TypeScript, "policy-controlled," least-privilege token selection | 1 |
Three independent implementations in eight months, from zero shared code, is what real demand looks like at the community-prototype stage. Nobody coordinated this; three separate operators each felt the same itch — "let my agent see and touch my PaaS" — and scratched it the same way. The August entrant is already evolving past the pattern: hecateq/mcp-coolify describes itself as "production-grade" and "policy-controlled," automatically selecting among five scoped tokens (read, sensitive-read, write, deploy, fallback) so each operation uses the least privilege available. The ecosystem is visibly climbing the authorization ladder the xlop bridge skips — read-only first, scoping second, policy third.
The demand side is strengthening from the client direction too. AnythingLLM's full MCP compatibility — MCP servers as agent tools since 1.6.0, @agent invocation in chat, a no-code Agent Flows builder — means every self-hosted AnythingLLM instance is now a would-be infrastructure operator waiting for a server to connect to. The bridge's README even lists the prompts its author runs: list my apps, explain this failed deploy from the logs, restart Postgres now. Those are not demo prompts. They are the Tuesday-afternoon reality of operating a small fleet, and an agent that can genuinely perform them is worth real money to a solo operator.
There is one more honest note, and it cuts against the bleakest reading of Section 3: Coolify itself has been building the scoping primitives a careful bridge would use. v4 API tokens are team-scoped with distinct read, read:sensitive, write, deploy, and root permissions — an operator can already mint a deploy-only token that cannot read environment variables. What Coolify does not offer is per-app or per-server granularity within a team: a deploy token can trigger deploys of anything the team owns. So the fair statement of the gap is narrower and more actionable than "no RBAC": team-level scoping exists and the xlop bridge ignores it, while app-level scoping does not exist and no bridge can invent it. A platform absorbing this lesson should ship both — and the bridge layer, as hecateq demonstrates, can already deliver the first half today.
What a platform-owned MCP server must do instead
If community bridges are the prototype, the platform-owned MCP server is the product — and the teardown above writes its requirements list. Five non-negotiables, in dependency order:
- Per-agent identity, not one ambient token. Every agent session authenticates as its own principal — the equivalent of the namespaced ServiceAccount from the Section 3 contrast — so that revoking one agent's access never touches another's, and every action is attributable. A single
COOLIFY_TOKENshared by all tools is the exact pattern to invert. - Least-privilege scoped tokens from day one. Each tool declares the minimum permission it needs, and the server mints or selects a token carrying only that — deploy tools get deploy scope, log tools get read scope, and nothing ever holds the fallback full-access token except the human operator. Ship this at launch; the xlop story proves auth bolted on after the bridge already works never gets bolted on.
- No socket or SSH escape hatches. If a capability is not expressible as a scoped platform API call, the agent does not get it — full stop. The moment
docker.sockorssh_execenters the tool list, every scope above it becomes theater, because the agent can route around the API entirely. Read-only-via-root is still root. - Human-in-the-loop for every mutating tool. Reads can flow freely; anything that changes state — deploy, restart, rollback, scale, delete — pauses for explicit operator confirmation, with the exact action and blast radius shown before approval. The xlop README recommends this; a platform server enforces it in code rather than documentation.
- An audit log the agent cannot edit. Every tool call — principal, tool, arguments, result — lands in append-only history the agent's own credentials cannot modify or delete. Without this, the first incident involving an agent action is undebuggable: no record of what the model did, in what order, or why.
Items 1 and 2 are the direct fix for the Section 3 contrast row: they rebuild, at the MCP layer, the scoped-identity boundary Kubernetes operators take for granted and single-box Docker hosts never had.
Conclusion: bridges are the signal, platforms are the answer
Step back and the xlop bridge is a familiar artifact wearing new clothes. Every platform shift produces a wave of community bridges that prove demand by routing around the platform's missing interface: the pre-API screen scrapers, the pre-webhook polling scripts, the pre-Terraform-provider shell wrappers. They are always under-scoped, always a little terrifying, and always correct about what users want. A Czech self-hosting hub giving Claude SSH access to its Coolify fleet is not a curiosity — it is the market telling every PaaS vendor, in Czech, that agent-operated infrastructure is a feature request with working code attached.
The question for platform builders is not whether to meet that request but where the authorization boundary lands when they do. The bridge authors answered "trust the model, disclaim the rest," and were honest enough to write the disclaimer down. A platform cannot ship a disclaimer as its security model. Per-agent identity, scoped tokens from day one, no escape hatches, human approval on mutations, and an agent-proof audit trail — that is the whole list, and none of it is exotic. It is just RBAC, applied to a principal that happens to be software.
The bridges will keep coming while platforms deliberate. The operators running them have already voted with their homelabs.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with AI agents as first-class operators rather than bolted-on bridges. Star the repo on GitHub or deploy your first app today.



