@jimrarras/coolify-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +410 -0
- package/THIRD-PARTY-NOTICES.txt +418 -0
- package/dist/cli/index.js +42562 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dimitrios Rarras
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# coolify-mcp
|
|
2
|
+
|
|
3
|
+
A TypeScript MCP server that drives a self-hosted [Coolify](https://coolify.io) instance.
|
|
4
|
+
Full REST CRUD, deploy/watch, and a flag-gated host-ops tier (SSH + Docker + psql) for live log streaming and ad-hoc access.
|
|
5
|
+
|
|
6
|
+
## Quick Start
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
# Install from npm
|
|
10
|
+
npm install -g @jimrarras/coolify-mcp
|
|
11
|
+
# …or run ad-hoc without installing: npx -y @jimrarras/coolify-mcp doctor
|
|
12
|
+
#
|
|
13
|
+
# (Or install the latest straight from GitHub: npm install -g github:jimrarras/coolify-mcp)
|
|
14
|
+
#
|
|
15
|
+
# The published build is a single self-contained bundle with ZERO native/runtime
|
|
16
|
+
# dependencies — it installs on any machine with no C/C++ toolchain and runs no
|
|
17
|
+
# install scripts. No flags needed.
|
|
18
|
+
|
|
19
|
+
# Copy and fill in env
|
|
20
|
+
cp .env.example .env
|
|
21
|
+
$EDITOR .env
|
|
22
|
+
|
|
23
|
+
# Run (API tier only)
|
|
24
|
+
coolify-mcp
|
|
25
|
+
|
|
26
|
+
# Run with host-ops (SSH access) enabled
|
|
27
|
+
coolify-mcp --enable-host-ops
|
|
28
|
+
|
|
29
|
+
# Run with destructive actions allowed (requires confirm:true per-call)
|
|
30
|
+
coolify-mcp --enable-host-ops --allow-destructive
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Guided setup (recommended)
|
|
34
|
+
|
|
35
|
+
Two commands take you from installed to working:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
coolify-mcp init # one-time interactive wizard — writes ~/.coolify-mcp/config.json
|
|
39
|
+
coolify-mcp doctor # verify the setup any time, with a specific fix for each failure
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> Prefer not to touch a config file? The [zero-file env-var mode](#1-zero-file-quick-start)
|
|
43
|
+
> (just `COOLIFY_BASE_URL` + `COOLIFY_TOKEN`) still works for the API tier — `init` is for a
|
|
44
|
+
> guided setup that also wires up host-ops.
|
|
45
|
+
|
|
46
|
+
#### What `init` asks
|
|
47
|
+
|
|
48
|
+
1. **Base URL + API token** — validated live against your instance before continuing. The token
|
|
49
|
+
must be `<id>|<secret>` with scope **write + read:sensitive**.
|
|
50
|
+
2. **Enable host-ops?** If yes, it resolves the SSH control host. On a standard single-server
|
|
51
|
+
install Coolify reports the host's IP as `host.docker.internal`, so auto-detect can't match it —
|
|
52
|
+
`init` then **lists your servers and asks you to pick** the control host (anti-hijack: it won't
|
|
53
|
+
silently guess). It substitutes your `baseUrl` host as the reachable SSH address.
|
|
54
|
+
3. It then **auto-discovers a working SSH key** — it scans `~/.ssh`, tries each OpenSSH key against
|
|
55
|
+
the host, and prompts (masked) for a passphrase if the key needs one. (A PuTTY `.ppk` is detected
|
|
56
|
+
and you're told to export an OpenSSH key first.)
|
|
57
|
+
4. It shows the host's **key fingerprint** and asks you to confirm before pinning it.
|
|
58
|
+
5. **Enable `query_coolify_db`?** If yes, it prints ready-to-run `CREATE ROLE … GRANT … REVOKE …`
|
|
59
|
+
SQL (with a generated password) for you to run on your Coolify Postgres.
|
|
60
|
+
|
|
61
|
+
It writes `~/.coolify-mcp/config.json` (backing up any existing one) with **secrets as `${ENV}`
|
|
62
|
+
references, never inline** — for example:
|
|
63
|
+
|
|
64
|
+
```jsonc
|
|
65
|
+
{
|
|
66
|
+
"defaultInstance": "default",
|
|
67
|
+
"instances": {
|
|
68
|
+
"default": {
|
|
69
|
+
"baseUrl": "https://coolify.example.com",
|
|
70
|
+
"token": "${COOLIFY_TOKEN}",
|
|
71
|
+
"enableHostOps": true,
|
|
72
|
+
"allowDestructive": false,
|
|
73
|
+
"ssh": {
|
|
74
|
+
"keyPath": "/home/you/.ssh/id_ed25519",
|
|
75
|
+
"hostServer": "<control-server-uuid>",
|
|
76
|
+
"fingerprint": "SHA256:…",
|
|
77
|
+
"passphrase": "${COOLIFY_SSH_KEY_PASSPHRASE}"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
…then prints the env vars to set (`COOLIFY_TOKEN`, `COOLIFY_SSH_KEY_PASSPHRASE`, …) and the
|
|
85
|
+
MCP-client snippet to paste. Set those env vars in your MCP client's `env` block (or shell), since
|
|
86
|
+
the `${ENV}` references are expanded at startup.
|
|
87
|
+
|
|
88
|
+
#### `doctor`
|
|
89
|
+
|
|
90
|
+
`doctor` runs read-only checks and prints a fix for anything that fails (add `--enable-host-ops`
|
|
91
|
+
to include the SSH/DB checks):
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
$ coolify-mcp doctor --enable-host-ops
|
|
95
|
+
── instance: default ──
|
|
96
|
+
PASS api — Coolify 4.1.2 reachable
|
|
97
|
+
PASS control_host — root@coolify.example.com:22 (using baseUrl host)
|
|
98
|
+
PASS ssh — SSH root@coolify.example.com:22 OK
|
|
99
|
+
SKIP db_role — query_coolify_db not configured
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
It exits non-zero if any check fails, so it's usable as a preflight in scripts.
|
|
103
|
+
|
|
104
|
+
Add to your MCP client config (e.g. `~/.claude/claude_desktop_config.json`):
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"mcpServers": {
|
|
109
|
+
"coolify": {
|
|
110
|
+
"command": "coolify-mcp",
|
|
111
|
+
"args": [],
|
|
112
|
+
"env": {
|
|
113
|
+
"COOLIFY_BASE_URL": "https://coolify.example.com",
|
|
114
|
+
"COOLIFY_TOKEN": "<id>|<secret>"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Configuration
|
|
122
|
+
|
|
123
|
+
### 1. Zero-file quick start
|
|
124
|
+
|
|
125
|
+
Set two environment variables and run — no config file needed:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
export COOLIFY_BASE_URL="https://coolify.example.com"
|
|
129
|
+
export COOLIFY_TOKEN="<id>|<secret>"
|
|
130
|
+
coolify-mcp
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The token format is `<id>|<secret>` — both parts required. The `id` is an integer; the `secret` is an alphanumeric string.
|
|
134
|
+
|
|
135
|
+
### 2. Config file
|
|
136
|
+
|
|
137
|
+
For multi-instance setups or richer per-instance settings, supply a JSON config file.
|
|
138
|
+
|
|
139
|
+
**File resolution order:**
|
|
140
|
+
1. `--config <path>` CLI flag
|
|
141
|
+
2. `COOLIFY_CONFIG` environment variable
|
|
142
|
+
3. `~/.coolify-mcp/config.json` (auto-discovered if present)
|
|
143
|
+
4. Falls back to env-var mode (step 1) if none of the above exists
|
|
144
|
+
|
|
145
|
+
**`${ENV}` expansion** is applied to every string value in the file, including nested ones.
|
|
146
|
+
`${VAR}` — substitutes the environment variable; throws if unset.
|
|
147
|
+
`${VAR:-default}` — uses `default` when `VAR` is unset or absent.
|
|
148
|
+
|
|
149
|
+
Only `baseUrl` and `token` are required per instance; everything else is optional and defaults to safe values.
|
|
150
|
+
|
|
151
|
+
See [`config.example.json`](./config.example.json) for a full multi-instance example.
|
|
152
|
+
|
|
153
|
+
### 3. Host-ops configuration
|
|
154
|
+
|
|
155
|
+
To enable SSH access, set `"enableHostOps": true` and provide `ssh.keyPath`.
|
|
156
|
+
The SSH **host**, **user**, and **port** are **auto-derived** from the Coolify API — no need to set them manually.
|
|
157
|
+
|
|
158
|
+
> **Single-server installs (`host.docker.internal`).** Coolify's built-in "localhost" server often
|
|
159
|
+
> reports its `ip` as `host.docker.internal` (a Docker-internal alias) that a remote workstation can't
|
|
160
|
+
> SSH to. Two things handle this:
|
|
161
|
+
> - **Select the control host explicitly** with `ssh.hostServer` (its UUID or name) — required because
|
|
162
|
+
> a non-matching server is not auto-selected (anti-hijack). When the selected server's `ip` is a
|
|
163
|
+
> non-routable alias, coolify-mcp automatically substitutes the `baseUrl` host (which is reachable and
|
|
164
|
+
> operator-trusted).
|
|
165
|
+
> - **Override the SSH address** with `ssh.host` when even the `baseUrl` host isn't SSH-reachable (e.g.
|
|
166
|
+
> it's behind a proxy/CDN) — set it to the server's real IP/hostname.
|
|
167
|
+
>
|
|
168
|
+
> Minimal host-ops config for a standard single-server install:
|
|
169
|
+
> ```json
|
|
170
|
+
> "ssh": { "keyPath": "~/.ssh/id_ed25519", "hostServer": "<server-uuid-or-name>" }
|
|
171
|
+
> ```
|
|
172
|
+
> Add `"host": "<reachable-ip>"` if `baseUrl` isn't directly SSH-reachable.
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
"ssh": {
|
|
176
|
+
"keyPath": "~/.ssh/id_ed25519"
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Tilde (`~`) is expanded to the home directory. Optional overrides:
|
|
181
|
+
|
|
182
|
+
| Field | Description |
|
|
183
|
+
|---|---|
|
|
184
|
+
| `ssh.keyPath` | Path to the SSH private key (required for host-ops). |
|
|
185
|
+
| `ssh.host` | Explicit SSH host/IP override. Use when the API-derived address isn't reachable (e.g. it reports `host.docker.internal`, or `baseUrl` is behind a proxy). Takes precedence over auto-derivation. |
|
|
186
|
+
| `ssh.knownHostsPath` | Path to a known_hosts file. Defaults to `~/.ssh/known_hosts`. |
|
|
187
|
+
| `ssh.fingerprint` | SHA-256 host fingerprint (alternative to known_hosts). |
|
|
188
|
+
| `ssh.hostServer` | UUID or name of the Coolify control server (override when auto-match fails). |
|
|
189
|
+
| `ssh.user` | SSH user override (else from API). |
|
|
190
|
+
| `ssh.port` | SSH port override (else from API). |
|
|
191
|
+
| `ssh.passphrase` | Private key passphrase. |
|
|
192
|
+
|
|
193
|
+
**SSH host-key verification is fail-closed.** The server will not connect unless the key presented by the remote host matches either `ssh.fingerprint` (SHA-256, from `ssh-keyscan <host> | ssh-keygen -lf -`) or the appropriate entry in `ssh.knownHostsPath` / `~/.ssh/known_hosts`. A missing or non-matching entry is an immediate connection refusal. Note: known_hosts matching is **literal** — wildcard (`*.example.com`) and hashed (`|1|...`) entries are not matched; use `ssh.fingerprint` or a literal host line for those hosts.
|
|
194
|
+
|
|
195
|
+
**Threat-model note (host-ops trusts the Coolify API).** The SSH host/user/port are derived from the Coolify API (`GET /servers`). A **compromised Coolify API** could therefore influence which host the MCP connects to — but this is bounded by the fail-closed host-key verification above (a redirect to an untrusted host is refused). For the strongest assurance set `ssh.fingerprint` to **pin** the control host's key regardless of `known_hosts`. Connections to remote managed servers run via `docker -H ssh://…` *on the Coolify host*, so that hop is governed by the Coolify host's own SSH trust store rather than this client's.
|
|
196
|
+
|
|
197
|
+
### 4. `query_coolify_db` — read-only DB role
|
|
198
|
+
|
|
199
|
+
Set `db.readonlyUser` per instance to enable the `query_coolify_db` tool. The in-code SQL blocklist and output redaction are best-effort defense-in-depth only — they cannot make arbitrary free-form SQL safe. **You MUST provision the role so PostgreSQL enforces the constraints:**
|
|
200
|
+
|
|
201
|
+
```sql
|
|
202
|
+
CREATE ROLE coolify_ro LOGIN PASSWORD '...' NOSUPERUSER NOCREATEDB NOCREATEROLE;
|
|
203
|
+
GRANT CONNECT ON DATABASE coolify TO coolify_ro;
|
|
204
|
+
GRANT USAGE ON SCHEMA public TO coolify_ro;
|
|
205
|
+
GRANT SELECT ON ALL TABLES IN SCHEMA public TO coolify_ro; -- omit sensitive tables/columns you don't want exposed
|
|
206
|
+
REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA public FROM coolify_ro; -- blocks adminpack/dblink/file fns
|
|
207
|
+
-- do NOT grant pg_read_server_files / pg_write_server_files / pg_execute_server_program / superuser
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Treat `query_coolify_db` output as "whatever this role may SELECT" — redaction reduces incidental leakage but is not guaranteed.
|
|
211
|
+
|
|
212
|
+
### 5. Multi-instance + per-call `instance` selector
|
|
213
|
+
|
|
214
|
+
A single `coolify-mcp` process can drive multiple Coolify instances simultaneously.
|
|
215
|
+
Every tool exposes an optional `instance` argument; omit it to use the default instance.
|
|
216
|
+
|
|
217
|
+
```jsonc
|
|
218
|
+
// config.json
|
|
219
|
+
{
|
|
220
|
+
"defaultInstance": "prod",
|
|
221
|
+
"instances": {
|
|
222
|
+
"prod": { "baseUrl": "https://coolify.prod.example.com", "token": "${PROD_TOKEN}" },
|
|
223
|
+
"staging": { "baseUrl": "https://coolify.staging.example.com", "token": "${STAGING_TOKEN}" }
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Example tool call routing to the non-default instance:
|
|
229
|
+
```json
|
|
230
|
+
{ "tool": "list_resources", "arguments": { "instance": "staging", "type": "applications" } }
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`enableHostOps`/`allowDestructive` are **per-instance** — you can allow destructive actions on staging while keeping them blocked on prod.
|
|
234
|
+
|
|
235
|
+
### 6. CLI flags + back-compat
|
|
236
|
+
|
|
237
|
+
When no config file is used, the legacy flags and environment variables still work and map onto the synthesized `default` instance:
|
|
238
|
+
|
|
239
|
+
| Flag / Variable | Maps to |
|
|
240
|
+
|---|---|
|
|
241
|
+
| `--enable-host-ops` | `instances.default.enableHostOps = true` |
|
|
242
|
+
| `--allow-destructive` | `instances.default.allowDestructive = true` |
|
|
243
|
+
| `COOLIFY_SSH_KEY_PATH` | `instances.default.ssh.keyPath` |
|
|
244
|
+
| `COOLIFY_SSH_HOST` | `instances.default.ssh.host` |
|
|
245
|
+
| `COOLIFY_SSH_KNOWN_HOST_FINGERPRINT` | `instances.default.ssh.fingerprint` |
|
|
246
|
+
| `COOLIFY_SSH_KNOWN_HOSTS_PATH` | `instances.default.ssh.knownHostsPath` |
|
|
247
|
+
| `COOLIFY_SSH_USER` | `instances.default.ssh.user` |
|
|
248
|
+
| `COOLIFY_SSH_PORT` | `instances.default.ssh.port` |
|
|
249
|
+
| `COOLIFY_SSH_KEY_PASSPHRASE` | `instances.default.ssh.passphrase` |
|
|
250
|
+
| `COOLIFY_SSH_HOST_SERVER` | `instances.default.ssh.hostServer` |
|
|
251
|
+
| `COOLIFY_DB_READONLY_USER` | `instances.default.db.readonlyUser` |
|
|
252
|
+
| `COOLIFY_DB_READONLY_PASSWORD` | `instances.default.db.readonlyPassword` |
|
|
253
|
+
| `COOLIFY_PINNED_VERSION` | `instances.default.pinnedCoolifyVersion` |
|
|
254
|
+
| `--header "K: V"` | `instances.default.extraHeaders` (repeatable) |
|
|
255
|
+
|
|
256
|
+
When a config file is loaded, `--enable-host-ops` and `--allow-destructive` are **ignored** (a warning is printed); per-instance gating comes from the file.
|
|
257
|
+
|
|
258
|
+
### 7. HTTP transport (optional)
|
|
259
|
+
|
|
260
|
+
By default the server speaks MCP over **stdio**. To serve it over **Streamable HTTP**
|
|
261
|
+
instead, pass `--http [port]` (default `3000`) or set `COOLIFY_MCP_HTTP_PORT`:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
export COOLIFY_MCP_HTTP_TOKEN="<a long random secret>" # required
|
|
265
|
+
coolify-mcp --http 3000
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
| Variable | Description |
|
|
269
|
+
|---|---|
|
|
270
|
+
| `COOLIFY_MCP_HTTP_PORT` | Enable HTTP on this port (or use `--http <port>`). |
|
|
271
|
+
| `COOLIFY_MCP_HTTP_TOKEN` | **Required (min 16 chars).** Bearer token clients must send as `Authorization: Bearer <token>`. The server refuses to start an unauthenticated endpoint, or one with a token shorter than 16 characters. |
|
|
272
|
+
| `COOLIFY_MCP_HTTP_HOST` | Bind address. Defaults to `127.0.0.1`; a non-localhost bind prints a warning. |
|
|
273
|
+
| `COOLIFY_MCP_HTTP_ALLOWED_HOSTS` | Comma-separated `host:port` allowlist for the `Host` header (DNS-rebinding defense), enforced on **non-loopback** binds. Defaults to the bind `host:port`; set this to your client-facing `host:port` when binding `0.0.0.0`. |
|
|
274
|
+
|
|
275
|
+
**The host-ops tier is never exposed over HTTP.** `ssh_exec`, `docker_op`,
|
|
276
|
+
`query_coolify_db`, `read_host_file`, and `stream_logs` are root-level operations
|
|
277
|
+
registered **only on the stdio transport** — over HTTP the server serves the API tier
|
|
278
|
+
(read/write/destructive) only, regardless of `enableHostOps`. Keep the bind on
|
|
279
|
+
`127.0.0.1`, keep the bearer token secret, and treat any non-localhost bind as
|
|
280
|
+
internet-facing.
|
|
281
|
+
|
|
282
|
+
### Token Scope Guidance
|
|
283
|
+
|
|
284
|
+
Coolify tokens carry full-account permissions. For read-only use (monitoring, querying), prefer creating a dedicated read-only token in Coolify's settings if the feature is available for your version. For write operations, use a token scoped to the team/project you intend to manage. Never share the same token across environments.
|
|
285
|
+
|
|
286
|
+
## Tools
|
|
287
|
+
|
|
288
|
+
Tools are grouped by tier. Tier determines what flags must be set for the tool to be registered and callable.
|
|
289
|
+
|
|
290
|
+
| Tier | Meaning | Required flags |
|
|
291
|
+
|---|---|---|
|
|
292
|
+
| **R** (read) | Non-mutating reads: list, get, inspect | none |
|
|
293
|
+
| **W** (write) | Creates and updates | none (but Coolify token must have write access) |
|
|
294
|
+
| **D** (destructive) | Deletes and stop/restart/kill operations | `--allow-destructive` **and** `confirm: true` in the call |
|
|
295
|
+
| **host** | SSH, Docker, psql, file reads | `--enable-host-ops` |
|
|
296
|
+
|
|
297
|
+
Destructive host actions (docker rm/rmi/stop/kill/prune/exec) additionally require `--allow-destructive` and `confirm: true`. The host tier is root-level read access by design: read-only `docker_op` actions (`inspect`, `logs`, …) and `read_host_file` can surface container configuration **including environment variables/secrets** — treat their output as sensitive. `docker_args` rejects shell metacharacters and `{}` template braces, but plain `docker inspect <container>` still returns that container's full config; only enable `--enable-host-ops` for trusted callers.
|
|
298
|
+
|
|
299
|
+
### Deploy
|
|
300
|
+
|
|
301
|
+
| Tool | Tier | Description |
|
|
302
|
+
|---|---|---|
|
|
303
|
+
| `deploy` | W | Trigger a deployment for a resource by UUID or tag. |
|
|
304
|
+
| `deploy_watch` | W | Trigger and poll until a terminal deploy status, emitting MCP progress. |
|
|
305
|
+
| `get_deployments` | R | List active deployments or fetch deployment history for an application. |
|
|
306
|
+
| `cancel_deployment` | D | Cancel a running deployment. |
|
|
307
|
+
|
|
308
|
+
### Resources (Applications, Databases, Services)
|
|
309
|
+
|
|
310
|
+
| Tool | Tier | Description |
|
|
311
|
+
|---|---|---|
|
|
312
|
+
| `list_resources` | R | List all resources of a given kind with summary fields. |
|
|
313
|
+
| `get_resource` | R | Fetch full details for a single resource by UUID. |
|
|
314
|
+
| `create_resource` | W | Create an application (public/private-github-app/private-deploy-key/dockerfile/dockerimage), database, or service. |
|
|
315
|
+
| `update_resource` | W | Update an existing resource's settings. |
|
|
316
|
+
| `control_resource` | W/D | Start/stop/restart a resource (stop/restart require `--allow-destructive`). |
|
|
317
|
+
| `delete_resource` | D | Permanently delete a resource. Requires `--allow-destructive` + `confirm: true`. |
|
|
318
|
+
| `manage_storage` | W/D | List, create, update, or delete persistent storage volumes for a resource. |
|
|
319
|
+
| `manage_backups` | W/D | List, create, update, or delete backup schedules for databases. |
|
|
320
|
+
| `manage_scheduled_tasks` | W/D | List, create, update, or delete scheduled tasks for apps/services. |
|
|
321
|
+
|
|
322
|
+
### Environment Variables
|
|
323
|
+
|
|
324
|
+
| Tool | Tier | Description |
|
|
325
|
+
|---|---|---|
|
|
326
|
+
| `manage_env` | W/D | List, upsert-bulk, or delete environment variables for a resource. |
|
|
327
|
+
|
|
328
|
+
### Projects
|
|
329
|
+
|
|
330
|
+
| Tool | Tier | Description |
|
|
331
|
+
|---|---|---|
|
|
332
|
+
| `manage_projects` | R/W/D | List, get, create, update, or delete projects and their environments. |
|
|
333
|
+
|
|
334
|
+
### Servers & Keys
|
|
335
|
+
|
|
336
|
+
| Tool | Tier | Description |
|
|
337
|
+
|---|---|---|
|
|
338
|
+
| `get_servers` | R | List servers or get a single server with validation/resource info. |
|
|
339
|
+
| `manage_server` | W/D | Create, update, or delete servers. |
|
|
340
|
+
| `provision_hetzner` | W | Provision a new Hetzner cloud server via Coolify. |
|
|
341
|
+
| `hetzner_inventory` | R | List Hetzner locations, server types, images, or SSH keys. |
|
|
342
|
+
| `manage_keys` | W/D | Manage Coolify private keys and cloud provider tokens. |
|
|
343
|
+
|
|
344
|
+
### Logs
|
|
345
|
+
|
|
346
|
+
| Tool | Tier | Description |
|
|
347
|
+
|---|---|---|
|
|
348
|
+
| `get_logs` | R / host | Snapshot logs for an application (REST API). For databases and services, falls back to `docker logs --tail` via host-ops. |
|
|
349
|
+
| `stream_logs` | host | Live-tail Docker logs via SSH/HostOps. Sends MCP progress every 25 lines. Hard cap: 1000 lines / 15 min. |
|
|
350
|
+
|
|
351
|
+
### Host Ops
|
|
352
|
+
|
|
353
|
+
| Tool | Tier | Description |
|
|
354
|
+
|---|---|---|
|
|
355
|
+
| `ssh_exec` | host | Run a shell command on a server over SSH. Returns stdout, stderr, exit code. |
|
|
356
|
+
| `docker_op` | host | Run a Docker CLI sub-command on a server. Mutating actions require `--allow-destructive` + `confirm: true`. |
|
|
357
|
+
| `query_coolify_db` | host | Execute a read-only SELECT query against the Coolify PostgreSQL database. |
|
|
358
|
+
| `read_host_file` | host | Read an allowed file on the Coolify host (restricted to `/data/coolify/**`). |
|
|
359
|
+
|
|
360
|
+
## Security Notes
|
|
361
|
+
|
|
362
|
+
### Never-Exposed Endpoints (Lockout Policy)
|
|
363
|
+
|
|
364
|
+
The following Coolify API endpoints are intentionally **never** exposed as tools, because calling them from an automated agent risks locking out all access to the Coolify UI:
|
|
365
|
+
|
|
366
|
+
- `GET /enable` — enables Coolify
|
|
367
|
+
- `GET /disable` — disables Coolify
|
|
368
|
+
- `POST /mcp/enable` — enables Coolify's own MCP endpoint
|
|
369
|
+
- `POST /mcp/disable` — disables Coolify's own MCP endpoint
|
|
370
|
+
- IP-allowlist mutation endpoints
|
|
371
|
+
|
|
372
|
+
### Destructive Operations
|
|
373
|
+
|
|
374
|
+
All destructive operations follow a deny-by-default, double-confirmation model:
|
|
375
|
+
|
|
376
|
+
1. The server must be started with `--allow-destructive`.
|
|
377
|
+
2. Each individual call must include `confirm: true` in its arguments.
|
|
378
|
+
3. You can pass `dry_run: true` to preview what would be executed without performing it.
|
|
379
|
+
|
|
380
|
+
### Host-Ops Tier
|
|
381
|
+
|
|
382
|
+
When `enableHostOps: true` is set for an instance (or `--enable-host-ops` in env mode), the server opens an SSH connection to the Coolify host on first use. Commands run as the configured SSH user (typically root). File access is restricted to `/data/coolify/**` prefixes. SQL access is restricted to read-only SELECT statements.
|
|
383
|
+
|
|
384
|
+
**SSH host-key verification is fail-closed.** The server refuses to connect unless the key presented by the remote host matches either `ssh.fingerprint` (SHA-256, from `ssh-keyscan <host> | ssh-keygen -lf -`) or the appropriate entry in `ssh.knownHostsPath` / `~/.ssh/known_hosts`. A missing or non-matching entry is an immediate connection refusal.
|
|
385
|
+
|
|
386
|
+
## Development
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
npm install
|
|
390
|
+
npm test # vitest
|
|
391
|
+
npm run build # esbuild -> single self-contained dist/cli/index.js bundle
|
|
392
|
+
npm run probe # scripts/probe.ts (requires COOLIFY_TEST_BASE_URL / COOLIFY_TEST_TOKEN)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
> **Note — `dist/` is committed.** `npm run build` bundles the CLI, the MCP server, and
|
|
396
|
+
> **all runtime deps** (ssh2, the MCP SDK, …) into a single `dist/cli/index.js` via esbuild,
|
|
397
|
+
> with ssh2's native bindings externalized (it falls back to pure-JS crypto). The result has
|
|
398
|
+
> **zero runtime dependencies**, so `npm install github:…` compiles nothing and runs no install
|
|
399
|
+
> scripts on the user's machine. Because no build runs at install time, the bundle is checked
|
|
400
|
+
> into git: **run `npm run build` and commit the updated `dist/cli/index.js` (and the
|
|
401
|
+
> regenerated `THIRD-PARTY-NOTICES.txt`) whenever you change anything under `src/`.**
|
|
402
|
+
> (Runtime libs live in `devDependencies` since they're bundled, not installed by
|
|
403
|
+
> consumers.) CI runs `git diff --exit-code dist/ THIRD-PARTY-NOTICES.txt` so a stale
|
|
404
|
+
> committed bundle fails the build.
|
|
405
|
+
|
|
406
|
+
## License
|
|
407
|
+
|
|
408
|
+
MIT. The published single-file bundle inlines third-party packages (ssh2, the MCP
|
|
409
|
+
SDK, ajv, …); their license and copyright notices are reproduced in
|
|
410
|
+
[`THIRD-PARTY-NOTICES.txt`](./THIRD-PARTY-NOTICES.txt), regenerated at build time.
|