"Restart the PostgreSQL database container immediately."
That is not a runbook step. It is a chat message — typed into AnythingLLM, interpreted by an agent, translated into tool calls, and executed against self-hosted infrastructure, where the container actually restarts. The wiring that makes it possible is xlop-dev/coolify-anythingllm-mcp, an MIT-licensed MCP server from the team behind xlop.cz that hands AnythingLLM's agent tools the keys to a Coolify box. The whole setup is one stdio subprocess, one API URL, and one API token in the environment. Chat-to-container in an afternoon.
This post is a teardown of that bridge: what it concretely puts in an agent's hands, why its authority model caps out at the homelab, what scoped agent-ops looks like in Render's official MCP server, and the five gaps a Render-compatible MCP server on infrastructure you own still has to close. The throughline is simple — the demo was never the hard part. The credential was.
What the bridge puts in the agent's hands
The architecture has three parts, exactly as the README describes them: AnythingLLM acts as the agent and UI, MCP provides the tool-calling standard, and the Coolify API executes infrastructure tasks on a VPS or homelab. AnythingLLM boots the server as a subprocess from its anythingllm_mcp_servers.json plugin config and offers its tools to the agent loop.
But read the code, not just the README, and the picture sharpens considerably. The README documents a Node server — command node, path dist/index.js, variables COOLIFY_API_URL and COOLIFY_API_TOKEN — that does not exist in the repo. What ships is a Python stdio server (coolify_mcp.py, answering JSON-RPC at MCP protocol 2024-11-05) configured with COOLIFY_URL and COOLIFY_TOKEN, defaulting to plain HTTP on a LAN IP with TLS certificate verification switched off in code. A sibling main.py offers a second, REST-shaped variant with five read-only tools. And the README's headline feature — "deployment automation" with builds, restarts, and rollbacks — has no corresponding tool in the code. The restart path is cruder and more powerful than a deploy API, as the actual seven-tool list shows. Grouped by what gates them, the agent's real powers look like this:
| Power | Tools | What gates it |
|---|---|---|
| Coolify reads | coolify_list_servers, coolify_list_applications, coolify_application_logs | The one Bearer token, at whatever permission level you minted |
| Host container introspection | host_docker_ps, host_docker_logs, host_docker_stats | Nothing in the API — raw HTTP to /var/run/docker.sock, bypassing Coolify RBAC entirely (and only working when the server runs on the host; the composed container never mounts the socket) |
| Arbitrary root shell | ssh_exec | An SSH key at a hardcoded path, a hardcoded username, host-key checking disabled — any host, any bash, 30-second timeout |
That third row is the table's real payload, and the second row is the runner-up. The README's advertised deploy automation is the one power the code doesn't implement; what it implements instead needs no Coolify permission at all. Reads ride one shared token, container inspection rides a socket mount, and restarts ride a root shell. The README calls this "secure by design" because nothing is hardcoded and everything comes from environment variables. The SSH key path and username are, in fact, hardcoded; the token is ambient; the socket needs no token at all. Hygiene is not an authority model.
The authority model is one ambient token
Name the mechanism precisely, because everything in the rest of this post hangs on it. The bridge runs over stdio, which in MCP-land means a local subprocess with ambient credentials: whatever the process environment holds, the agent wields. There is no per-agent identity, no per-tool scope negotiation, no step in the loop where a tool call carries less authority than the operator who pasted the token. The agent is the operator, cryptographically indistinguishable from them.
Coolify v4's own RBAC sharpens the point rather than softening it. API tokens are team-scoped with four permission levels — read-only, read:sensitive, * (full CRUD), and deploy (trigger deploys only) — but scoping stops at the team boundary. There are no per-app or per-server grants inside a team. So even a careful operator who mints the narrowest token that still covers the tool list gets a token with team-wide reach. And two of the three power groups never touch the permission system at all: the docker-socket tools inherit whatever the socket mount allows, and ssh_exec inherits whatever the key allows — root on any host you name. No token level, however narrow, constrains a tool that doesn't present the token. The community has felt this ceiling directly: at least one sibling Coolify MCP server exists specifically to juggle one token per team for multi-team setups, because a single token cannot express "these apps but not those."
Then there is the disclaimer, which deserves to be quoted rather than paraphrased because it is the most honest part of the repository:
Running AI with infrastructure access is powerful but experimental. We strongly recommend running your AI agent with human-in-the-loop confirmations enabled. The authors are not responsible for any server downtime, data loss, or accidental resource deletion caused by your AI model.
Read that as documentation, not legalese. "Experimental" plus "human-in-the-loop" plus "accidental resource deletion" is exactly what one ambient god-token predicts. The bridge proves the transport works — chat to container, genuinely, today. It also proves that transport was never the missing piece. Identity, scope, and accountability were.
This is also why counting tools misses the point. Another community project, StuMason's coolify-mcp, ships 42 token-optimized tools for debugging, management, and deployment against the same API. Forty-two tools behind one credential is not forty-two times the capability — it is the same authority with a larger vocabulary. The gap a serious agent-ops surface has to close is not "more verbs." It is "fewer ambient powers per verb."
What scoped agent-ops looks like: Render's official MCP server
The reference implementation for doing this properly is render-oss/render-mcp-server, Render's official MCP server, documented at render.com/docs/mcp-server. It speaks both Streamable HTTP and stdio, negotiates MCP protocol versions from 2024-11-05 through 2025-11-25 (plus a sessionless 2026-07-28 on stdio), and exposes workspaces, services, deploys, logs, metrics, Postgres, and Key Value stores as tools. Three of its design decisions are worth studying individually, because each one is a gap in the bridge made concrete.
Explicit per-call workspace scope. Every resource tool accepts a workspaceId, and the server keeps the target workspace explicit across reconnects and transport-session changes. The older implicit behavior — "whatever workspace this session selected" — is deprecated and scheduled for removal. Compare that with the bridge, where the token is the scope and there is nothing to pass per call. Render's direction of travel is stated plainly: ambient session scope is a bug being fixed, not a feature.
Read-only SQL by construction. The query_render_postgres tool runs a SQL query against a Render-hosted database — read-only, enforced by the tool, not by asking the model nicely. This is the shape scoped agent-ops takes: the dangerous verb simply does not exist in the tool list. The model cannot be prompt-injected into dropping a table through a tool that only reads. The bridge, by contrast, routes mutation through a root shell over SSH that answers to no credential the API can scope, with human-in-the-loop as the only guardrail.
Refused mutations that say where to go instead. The update_web_service, update_static_site, and update_cron_job tools exist — and deliberately refuse, returning a link to the service's settings in the Render dashboard where the change can be made. This is the honest tension at the heart of the reference bar: even Render, with workspaces and scoped API keys behind its MCP server, punts structural mutations back to the dashboard. The bridge lets the agent do everything with no scope; Render lets it do a scoped subset and redirects the rest. Neither has reached "the deploy API collapses to one tool call an agent can invoke without a dashboard." That sentence is the target, and the target is still open.
But notice what Render does allow without a dashboard: trigger_deploy fires a deploy for services whose auto-deploy won't cover the change, and update_environment_variables rewrites a service's env set — scoped to an explicit workspace, through tools whose blast radius is bounded by design. One-call, dashboard-free mutation exists in the reference implementation. It just exists only where the platform was confident enough in the scope to allow it. That confidence, extended further, is precisely the work list.
The 5 gaps a Render-compatible self-hosted MCP server closes
Stack the three columns — the community bridge today, Render's official MCP server today, and the Render-compatible self-hosted target — and the work list writes itself. Each row is one gap, stated as a mechanism with its credible end state:
| # | Gap | Bridge today | Render MCP today | Render-compatible self-hosted target |
|---|---|---|---|---|
| 1 | Per-agent identity | One shared API token, plus a socket tap and SSH key no token level constrains; agent is indistinguishable from operator | API-key-backed tools with explicit workspaceId per call | OAuth 2.1 + PKCE per the MCP Authorization spec: each agent holds its own token, discoverable via /.well-known/oauth-protected-resource, refreshable and revocable without touching any human's credential |
| 2 | Per-tool scopes | Every tool inherits full team authority; reads and restarts share one credential | Scoped surface by construction: read-only SQL, refused structural updates | Least-privilege tool grants per agent: the log-reading agent cannot restart, the deploy agent cannot read secrets — enforced server-side, not by prompt |
| 3 | Quotas and rate limits | None — a looping agent can restart the fleet until something melts | Platform-side limits inherited from Render's API | First-class agent quotas: tool-call budgets per agent per window, deploy-frequency caps, spend ceilings on provisioned capacity |
| 4 | Audit trails | API calls come from "the token"; shell and socket actions from nothing attributable at all | Actions attributable to the API key and workspace that made them | Every tool call logged against agent identity, granted scope, and resulting diff — "which agent changed what, under which grant" is answerable after the fact |
| 5 | Dashboard-free deploy plus machine lifecycle | No deploy tool in code — restarts ride a root shell, and Coolify orchestrates servers you attach, it does not provision machines | trigger_deploy proves scoped one-call deploy; structural changes still punt to dashboard links | One scoped, quota-checked, audited deploy tool call from prompt to running service — and when capacity runs out, the platform behind it (Cluster API on machines you own) provisions the next box instead of failing the deploy |
Gap 5 needs one paragraph of unpacking, because it is actually two gaps fused at the deploy boundary. The first half is the title's payoff made literal: a deploy tool whose shape is deploy(app, revision) — one call, no dashboard round-trip — made safe by gaps 1 through 4 rather than by refusing to exist. The second half is what no API bridge can ever supply: machine lifecycle. The bridge can restart containers on boxes you wired into Coolify, but when the deploy needs capacity that does not exist yet, the agent's vocabulary ends. A Render-compatible server on a Cluster API fleet does not have that edge — the same declarative reconciliation that owns the machines can summon one, and the deploy tool's contract extends from "run this on what we have" to "have somewhere to run this." That is the seam where "LLM drives my PaaS dashboard" becomes "LLM drives my platform."
What to do Monday morning
Two playbooks, depending on which side of the gap table you live on.
If you run the homelab stack today, the bridge is genuinely useful within a fenced perimeter. Mint the narrowest Coolify token that covers what you actually ask for — read-only if the agent only diagnoses — and keep the full * token out of the agent's environment entirely. Then remember the token is only one of three gates: the docker-socket tools and ssh_exec don't present it at all, so fence the host too — no socket mount and no root-capable key anywhere near the agent — or strip those tools before you connect it. Leave human-in-the-loop confirmations on for every mutating verb; the README recommends it because the authors know what the token can do. Treat "restart production state" prompts as the red line: reads and log diagnosis are the safe daily use, shell-backed actions are the supervised exception.
And if you outgrow one team, accept the multi-token juggling as the signal it is — you have hit the ceiling that per-agent identity exists to raise.
If you build the platform, build the gaps in order: identity first (OAuth per agent, since every other control needs a subject to attach to), then scopes, then audit, then quotas — each one compounds the last, and audit without identity is just a log of what "the token" did. Keep Render's refusal pattern in your pocket: a tool that declines a mutation and links the dashboard is not a failure, it is a scoped surface being honest about its confidence. Extend the confident subset one verb at a time until the one-call deploy in gap 5 is the boring default. And own the machine lifecycle underneath, because the deploy tool is only as complete as the capacity story behind it.
The xlop.cz bridge deserves its credit straight: a small team proved that chat-to-container on self-hosted infrastructure is an afternoon's work, not a research program. But afternoons produce demos, and demos run on god-tokens, socket mounts, and root shells with disclaimers. Turning "restart the database" from a party trick into a permission — scoped, budgeted, attributable, and backed by machines the platform itself can summon — is the actual project. That is what a Render-compatible MCP server is for.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with a Render-compatible API that treats AI agents as first-class operators. Star the repo on GitHub or deploy your first app today.



