@selfhost.dev/mcp-server 0.9.0 → 0.10.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.
Files changed (45) hide show
  1. package/README.md +205 -43
  2. package/dist/client.d.ts +7 -0
  3. package/dist/client.js +2 -1
  4. package/dist/client.js.map +1 -1
  5. package/dist/index.js +21 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/tools/alerts.js +12 -2
  8. package/dist/tools/alerts.js.map +1 -1
  9. package/dist/tools/backups.js +29 -5
  10. package/dist/tools/backups.js.map +1 -1
  11. package/dist/tools/billing.js +233 -39
  12. package/dist/tools/billing.js.map +1 -1
  13. package/dist/tools/database-users.js +53 -6
  14. package/dist/tools/database-users.js.map +1 -1
  15. package/dist/tools/deployments.js +47 -3
  16. package/dist/tools/deployments.js.map +1 -1
  17. package/dist/tools/github.js +21 -4
  18. package/dist/tools/github.js.map +1 -1
  19. package/dist/tools/instance-logs.js +2 -2
  20. package/dist/tools/instance-logs.js.map +1 -1
  21. package/dist/tools/instances.js +423 -88
  22. package/dist/tools/instances.js.map +1 -1
  23. package/dist/tools/organizations.js +5 -1
  24. package/dist/tools/organizations.js.map +1 -1
  25. package/dist/tools/pitr.d.ts +22 -0
  26. package/dist/tools/pitr.js +75 -10
  27. package/dist/tools/pitr.js.map +1 -1
  28. package/dist/tools/postgres-extensions.js +176 -57
  29. package/dist/tools/postgres-extensions.js.map +1 -1
  30. package/dist/tools/project-backups.js +130 -0
  31. package/dist/tools/project-backups.js.map +1 -1
  32. package/dist/tools/project-databases.js +16 -23
  33. package/dist/tools/project-databases.js.map +1 -1
  34. package/dist/tools/project-services.js +544 -53
  35. package/dist/tools/project-services.js.map +1 -1
  36. package/dist/tools/project-ssh.d.ts +2 -0
  37. package/dist/tools/project-ssh.js +338 -0
  38. package/dist/tools/project-ssh.js.map +1 -0
  39. package/dist/tools/projects.js +21 -3
  40. package/dist/tools/projects.js.map +1 -1
  41. package/dist/tools/scaling.js +1 -1
  42. package/dist/tools/scaling.js.map +1 -1
  43. package/dist/types/tiers.js +27 -4
  44. package/dist/types/tiers.js.map +1 -1
  45. package/package.json +2 -1
@@ -1,30 +1,34 @@
1
1
  import { z } from "zod";
2
2
  import { apiRequest } from "../client.js";
3
3
  /**
4
- * PostgreSQL extensions - TimescaleDB and pgvector.
4
+ * PostgreSQL extensions - a server-side catalog, not a pair of booleans.
5
5
  *
6
- * Neither is a separate engine: both are `postgres` plus a boolean, set either at
7
- * create (`create_instance` with `timescaledb_enabled` / `vector_enabled`) or
8
- * afterwards through these tools, which PATCH `/aws/v1/instances/:id`.
6
+ * Endpoints:
7
+ * - GET /aws/v1/postgres_extensions[?db_version&architecture] - the catalog, no instance
8
+ * - GET /aws/v1/instances/:pid/extensions - enabled / available / unavailable
9
+ * - POST /aws/v1/instances/:pid/extensions - enable one or more
9
10
  *
10
- * Both are ENABLE-ONLY. The backend rejects `false` with a 422 ("Disabling
11
- * TimescaleDB support is not supported once enabled") because dropping the
12
- * extension can silently break queries that depend on it - hence the `confirm`
13
- * guard here. Both need a registered agent to run the enablement task, and both
14
- * put the instance into `updating` while it runs.
11
+ * BREAKING CHANGE the MCP shipped through: `timescaledb_enabled` and `vector_enabled` were
12
+ * REMOVED backend-side (selfhost#2516) - from create params, from PATCH, and from instance
13
+ * responses. A request still sending them is not an error, it is IGNORED, which is the worst
14
+ * possible failure mode: until v0.9.1 this server offered `enable_timescaledb` /
15
+ * `enable_pgvector` tools and `*_enabled` create flags that reported success and did nothing.
16
+ * They are gone. `extensions: ["pgvector"]` is the replacement everywhere, and `vector` is
17
+ * accepted as an alias for `pgvector` (it is the SQL name).
15
18
  *
16
- * They differ in blast radius:
17
- * - TimescaleDB installs an APT package, adds `timescaledb` to
18
- * `shared_preload_libraries`, and RESTARTS PostgreSQL once. Task:
19
- * `postgres_enable_timescaledb`.
20
- * - pgvector creates the extension and applies its config overrides with
21
- * `restart_required: false` - no restart. Tasks: `postgres_enable_pgvector`
22
- * then `postgres_config_update`.
19
+ * DO NOT HARD-CODE THE CATALOG. The backend integration guide forbids it, and the list has
20
+ * grown twice already. Every name here is a free-form string validated server-side; the
21
+ * orientation list in the tool descriptions is a hint, and `list_postgres_extension_catalog`
22
+ * is the authority. An extension the platform added after this release must still work.
23
23
  *
24
- * On AWS the two can coexist on one instance. (Project/Coolify databases are
25
- * different - there one image has to win; see `create_project_database`.)
24
+ * Compatibility is per entry, not global: `pg_textsearch` needs PostgreSQL 17 or 18, and
25
+ * entries can be gated by CPU architecture too. That is why `available` / `unavailable` come
26
+ * pre-computed per instance rather than being something a client works out.
27
+ *
28
+ * FORWARD-ONLY. There is no disable path on any endpoint - dropping an extension can break
29
+ * dependent objects. Every enable is permanent, hence the `confirm` guard.
26
30
  */
27
- /** Extension-specific metrics folded into the `postgresql.database` alert set once installed. */
31
+ /** Extension-specific metrics folded into the `postgresql.database` alert set once TimescaleDB is enabled. */
28
32
  const TIMESCALE_METRICS = [
29
33
  "timescaledb_hypertable_count",
30
34
  "timescaledb_chunk_count",
@@ -32,71 +36,186 @@ const TIMESCALE_METRICS = [
32
36
  "timescaledb_compression_ratio",
33
37
  "timescaledb_total_size_bytes",
34
38
  ];
39
+ /**
40
+ * Orientation only - the catalog endpoint is the authority and this list WILL fall behind it.
41
+ * Named here so a model can answer "can I have full-text search?" without a round trip, and
42
+ * so the enable tool can describe what it is refusing.
43
+ */
44
+ const KNOWN_EXTENSIONS = "pgvector (vectors), timescaledb (time_series), pgcrypto (security), uuid-ossp (data_types), " +
45
+ "postgres_fdw (integration), pg_partman (partitioning), pg_stat_statements (observability), " +
46
+ "pg_trgm (search), pg_cron (scheduling), postgis (geospatial), pg_textsearch (search, PG 17-18 only)";
35
47
  export function registerPostgresExtensionTools(server) {
36
- server.tool("enable_timescaledb", `Enable the TimescaleDB extension on an existing PostgreSQL instance - hypertables, continuous aggregates, compression, and retention policies for time-series data.
48
+ server.tool("list_postgres_extension_catalog", `List the PostgreSQL extensions the platform can install, with no instance in play. Use this for the two cases where there is nothing to ask about yet: choosing extensions for an instance you are ABOUT to create (pass them to \`create_instance\` as \`extensions\`), and choosing them for a project database (\`create_project_database\`, where they are creation-only).
37
49
 
38
- What happens: the APT package is installed, \`timescaledb\` is added to shared_preload_libraries, **PostgreSQL is restarted once**, and \`CREATE EXTENSION timescaledb\` runs. The instance enters \`updating\` while the \`postgres_enable_timescaledb\` task runs - poll get_instance to follow it.
50
+ Pass \`db_version\` and/or \`architecture\` to have the server apply the compatibility gate and fill in a \`reason\` on each rejected entry. With neither, everything comes back under \`available\` and you must gate yourself on each row's \`supported_postgres_versions\` / \`supported_architectures\`. An unsupported value is a 422, not a silent ignore.
39
51
 
40
- IRREVERSIBLE: TimescaleDB cannot be disabled afterwards (the backend 422s on any attempt). PostgreSQL only, and the instance needs a registered agent.
52
+ Each row carries \`name\` (the API id - send this exactly, \`uuid-ossp\` keeps its hyphen), \`display_name\`, \`category\`, \`description\`, \`install_source\`, \`requires_restart\`, \`dependencies\`, \`conflicts\`, and \`coolify_supported\`.
41
53
 
42
- To start a new instance with it already installed, pass timescaledb_enabled to create_instance instead - no restart needed that way. Once installed, these metrics join the instance's \`postgresql.database\` alert set: ${TIMESCALE_METRICS.join(", ")}.`, {
43
- instance_id: z.string().min(1).describe("Instance PID (e.g. awsinst_xxx)"),
44
- confirm: z.boolean().describe("Must be true to proceed. Ask the user first: this restarts PostgreSQL once and can never be undone."),
45
- }, async ({ instance_id, confirm }) => {
46
- if (!confirm) {
54
+ FILTER ON \`coolify_supported\` BEFORE OFFERING ANYTHING FOR A PROJECT DATABASE. A managed instance installs binaries on the host so it can run the whole catalog; a project database is a container and can only run what its image ships. Offering a name the create will refuse is the specific mistake this field exists to prevent - today \`pg_partman\` is the one entry no image provides.
55
+
56
+ If an instance already exists, \`list_instance_extensions\` is the better call - it knows that instance's version, architecture and current rows.
57
+
58
+ Catalog today (orientation - trust the response, not this line): ${KNOWN_EXTENSIONS}.`, {
59
+ db_version: z.string().optional()
60
+ .describe("PostgreSQL major, e.g. \"17\". Filters the catalog and explains each rejection."),
61
+ architecture: z.enum(["amd64", "arm64"]).optional()
62
+ .describe("CPU architecture of the instance you intend to create."),
63
+ }, async ({ db_version, architecture }) => {
64
+ const result = await apiRequest("/aws/v1/postgres_extensions", {
65
+ query: { db_version, architecture },
66
+ toolName: "list_postgres_extension_catalog",
67
+ skipOrgInjection: true,
68
+ });
69
+ if (!result.success) {
47
70
  return {
48
71
  content: [{
49
72
  type: "text",
50
- text: `About to enable TimescaleDB on ${instance_id}.\n\n- PostgreSQL restarts once, so expect a brief interruption.\n- The instance enters \`updating\` until the task finishes.\n- It CANNOT be disabled later.\n\nCall again with confirm=true to proceed.`,
73
+ text: `Failed to list the extension catalog (${result.statusCode}): ${result.message}`,
51
74
  }],
52
75
  };
53
76
  }
54
- const result = await apiRequest(`/aws/v1/instances/${instance_id}`, {
55
- method: "PATCH",
56
- body: { timescaledb_enabled: true },
57
- toolName: "enable_timescaledb",
58
- skipOrgInjection: true,
59
- });
60
- if (!result.success) {
61
- return { content: [{ type: "text", text: `Failed to enable TimescaleDB (${result.statusCode}): ${result.message}` }] };
62
- }
63
- return {
64
- content: [
65
- { type: "text", text: "TimescaleDB enablement started. Poll get_instance for the `postgres_enable_timescaledb` task; the instance returns to `running` when it completes. Verify with: SELECT extversion FROM pg_extension WHERE extname = 'timescaledb';" },
66
- { type: "text", text: JSON.stringify(result.data, null, 2) },
67
- ],
68
- };
77
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
69
78
  });
70
- server.tool("enable_pgvector", `Enable the pgvector extension on an existing PostgreSQL instance - the \`vector\` column type plus similarity search for embeddings.
79
+ server.tool("list_instance_extensions", `List PostgreSQL extension state for one managed instance, split three ways:
71
80
 
72
- What happens: \`CREATE EXTENSION vector\` runs and pgvector's parameter overrides are applied without a restart (tasks \`postgres_enable_pgvector\` then \`postgres_config_update\`). The instance enters \`updating\` while they run - poll get_instance.
81
+ - \`enabled\` - every row ON this instance, whatever its status. READ THE PER-ROW \`status\`: \`enabled\` (done, with the \`version\` reported by \`pg_extension\`), \`pending\` (recorded, no task yet), \`installing\` (a task is running - \`replica_tasks\` shows per-node progress on a Multi-AZ group), or \`failed\` (with \`error_message\`). A \`failed\` row is the retry affordance - call \`enable_instance_extensions\` with the same name again. Nothing is ever marked \`enabled\` optimistically; only a verified on-host outcome promotes a row, so this is a real answer and not a hopeful one.
82
+ - \`available\` - catalog entries this instance could enable and has no row for.
83
+ - \`unavailable\` - entries this instance CANNOT use, each with a \`reason\` (its PostgreSQL version or CPU architecture). Do not offer these; the enable would 422.
73
84
 
74
- IRREVERSIBLE: pgvector cannot be disabled afterwards (the backend 422s on any attempt). PostgreSQL only, and the instance needs a registered agent.
85
+ Also returns the instance's \`db_version\`, \`architecture\` and \`status\`, which is what determines the split.
75
86
 
76
- To start a new instance with it already installed, pass vector_enabled to create_instance instead.`, {
87
+ 404 for any non-PostgreSQL instance - extensions are a PostgreSQL feature and \`extensions\` is \`[]\` on every other engine.`, {
77
88
  instance_id: z.string().min(1).describe("Instance PID (e.g. awsinst_xxx)"),
78
- confirm: z.boolean().describe("Must be true to proceed. Ask the user first: this changes Postgres config and can never be undone."),
79
- }, async ({ instance_id, confirm }) => {
80
- if (!confirm) {
89
+ }, async ({ instance_id }) => {
90
+ const result = await apiRequest(`/aws/v1/instances/${instance_id}/extensions`, { toolName: "list_instance_extensions", skipOrgInjection: true });
91
+ if (!result.success) {
81
92
  return {
82
93
  content: [{
83
94
  type: "text",
84
- text: `About to enable pgvector on ${instance_id}.\n\n- No restart, but Postgres config overrides are applied.\n- The instance enters \`updating\` until the tasks finish.\n- It CANNOT be disabled later.\n\nCall again with confirm=true to proceed.`,
95
+ text: `Failed to list extensions (${result.statusCode}): ${result.message}`,
85
96
  }],
86
97
  };
87
98
  }
88
- const result = await apiRequest(`/aws/v1/instances/${instance_id}`, {
89
- method: "PATCH",
90
- body: { vector_enabled: true },
91
- toolName: "enable_pgvector",
99
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
100
+ });
101
+ server.tool("enable_instance_extensions", `Enable one or more PostgreSQL extensions on an existing managed instance. Batch them in ONE call - everything is validated together before any state changes, so you get every problem back at once instead of discovering them one failed request at a time.
102
+
103
+ PERMANENT. There is no disable endpoint on any path, because dropping an extension can break objects that depend on it. Confirm with the user before calling.
104
+
105
+ SOME EXTENSIONS RESTART POSTGRESQL. A restart happens once, only when a required library is not already loaded, and it drops live connections for a few seconds. Called without \`confirm\`, this tool reads the instance's own extension state first and tells you exactly which of your names need a restart, which are already enabled, and which this instance cannot run - so the confirmation you show the user is accurate rather than generic.
106
+
107
+ What happens on accept (202): each requested row goes to \`installing\`, the instance and each replica move to \`updating\`, and one \`postgres_enable_extensions\` task runs per node. Replicas are prepared BEFORE the primary on purpose - a standby replaying \`CREATE EXTENSION\` for a library it does not have would stall replication - so a replica failure cancels the primary and the error names the node. Poll \`list_instance_extensions\` until the rows settle to \`enabled\` or \`failed\`.
108
+
109
+ Re-sending names that are already \`enabled\` or \`installing\` is safe: the response is a 200 with them under \`skipped\`, no task, no status change.
110
+
111
+ Requirements: instance must be \`running\` or \`provisioned\` (replicas and instances mid-scale are refused), a registered agent is required (400 without one), and enables are rate-limited to 10/min.
112
+
113
+ Catalog today (orientation - \`list_postgres_extension_catalog\` is the authority and a newer platform may offer more): ${KNOWN_EXTENSIONS}. Send \`name\` exactly as the catalog spells it; \`vector\` is accepted as an alias for \`pgvector\`.
114
+
115
+ Once TimescaleDB is enabled these metrics join the instance's \`postgresql.database\` alert set - a rule on them silently never fires before that: ${TIMESCALE_METRICS.join(", ")}.`, {
116
+ instance_id: z.string().min(1).describe("Instance PID (e.g. awsinst_xxx)"),
117
+ extensions: z.array(z.string().min(1)).min(1)
118
+ .describe("Catalog names, e.g. [\"pgvector\", \"pg_stat_statements\"]. Free-form on purpose: the server validates against the live catalog, so an extension newer than this release still works."),
119
+ confirm: z.boolean().optional()
120
+ .describe("Must be true to proceed. Omit it first to get the per-extension restart / already-enabled / unavailable breakdown for this specific instance."),
121
+ }, async ({ instance_id, extensions, confirm }) => {
122
+ const requested = [...new Set(extensions.map((n) => n.trim()).filter((n) => n.length > 0))];
123
+ if (requested.length === 0) {
124
+ return { content: [{ type: "text", text: "No extension names given." }] };
125
+ }
126
+ if (confirm !== true) {
127
+ // Pre-flight against this instance's real state so the confirmation names
128
+ // consequences instead of guessing at them. A lookup failure is not fatal -
129
+ // fall back to a generic prompt rather than blocking the enable.
130
+ const state = await apiRequest(`/aws/v1/instances/${instance_id}/extensions`, { toolName: "list_instance_extensions", skipOrgInjection: true });
131
+ const lines = [`About to enable on ${instance_id}: ${requested.join(", ")}.`];
132
+ if (state.success && state.data) {
133
+ const byName = (rows) => new Map((rows ?? []).filter((r) => r.name).map((r) => [r.name, r]));
134
+ // `vector` is the SQL alias for the `pgvector` catalog entry; match either spelling
135
+ // so the pre-flight does not report a real row as unknown.
136
+ const canon = (n) => (n === "vector" ? "pgvector" : n);
137
+ const enabled = byName(state.data.enabled);
138
+ const available = byName(state.data.available);
139
+ const unavailable = byName(state.data.unavailable);
140
+ const alreadyDone = [];
141
+ const inFlight = [];
142
+ const failedBefore = [];
143
+ const needsRestart = [];
144
+ const cannotRun = [];
145
+ const unknown = [];
146
+ for (const raw of requested) {
147
+ const name = canon(raw);
148
+ const row = enabled.get(name);
149
+ if (row) {
150
+ if (row.status === "enabled")
151
+ alreadyDone.push(raw);
152
+ else if (row.status === "failed")
153
+ failedBefore.push(`${raw} (${row.error_message ?? "no reason given"})`);
154
+ else
155
+ inFlight.push(`${raw} (${row.status ?? "in progress"})`);
156
+ continue;
157
+ }
158
+ const bad = unavailable.get(name);
159
+ if (bad) {
160
+ cannotRun.push(`${raw} - ${bad.reason ?? "not available on this instance"}`);
161
+ continue;
162
+ }
163
+ const ok = available.get(name);
164
+ if (ok) {
165
+ if (ok.requires_restart)
166
+ needsRestart.push(raw);
167
+ continue;
168
+ }
169
+ unknown.push(raw);
170
+ }
171
+ lines.push(`\nInstance runs PostgreSQL ${state.data.db_version ?? "?"} on ${state.data.architecture ?? "?"}, status \`${state.data.status ?? "?"}\`.`);
172
+ if (cannotRun.length) {
173
+ lines.push(`\nWILL BE REJECTED - this instance cannot run these:\n- ${cannotRun.join("\n- ")}`);
174
+ }
175
+ if (unknown.length) {
176
+ lines.push(`\nNot in the catalog as spelled (check \`list_postgres_extension_catalog\`): ${unknown.join(", ")}`);
177
+ }
178
+ if (alreadyDone.length) {
179
+ lines.push(`\nAlready enabled, will be skipped: ${alreadyDone.join(", ")}`);
180
+ }
181
+ if (inFlight.length) {
182
+ lines.push(`\nAlready in progress, will be skipped: ${inFlight.join(", ")}`);
183
+ }
184
+ if (failedBefore.length) {
185
+ lines.push(`\nFailed previously - re-sending is the retry:\n- ${failedBefore.join("\n- ")}`);
186
+ }
187
+ if (needsRestart.length) {
188
+ lines.push(`\nRESTARTS POSTGRESQL ONCE (live connections dropped for a few seconds): ${needsRestart.join(", ")}`);
189
+ }
190
+ else {
191
+ lines.push("\nNo restart required for these.");
192
+ }
193
+ }
194
+ lines.push("\nEnabling an extension CANNOT be undone - there is no disable path.\n\nCall again with confirm=true to proceed.");
195
+ return { content: [{ type: "text", text: lines.join("\n") }] };
196
+ }
197
+ const result = await apiRequest(`/aws/v1/instances/${instance_id}/extensions`, {
198
+ method: "POST",
199
+ body: { extensions: requested },
200
+ toolName: "enable_instance_extensions",
92
201
  skipOrgInjection: true,
93
202
  });
94
203
  if (!result.success) {
95
- return { content: [{ type: "text", text: `Failed to enable pgvector (${result.statusCode}): ${result.message}` }] };
204
+ return {
205
+ content: [{
206
+ type: "text",
207
+ text: `Failed to enable extensions (${result.statusCode}): ${result.message}`,
208
+ }],
209
+ };
96
210
  }
211
+ const started = result.data?.started ?? [];
212
+ const skipped = result.data?.skipped ?? [];
213
+ const note = started.length === 0
214
+ ? `Nothing to do - ${skipped.length ? skipped.join(", ") + " " : "everything requested "}is already enabled or installing. No task was created and the instance was not touched.`
215
+ : `Enabling ${started.join(", ")}${skipped.length ? ` (skipped, already present: ${skipped.join(", ")})` : ""}. Rows are now \`installing\` and the nodes are \`updating\`. Poll \`list_instance_extensions\` until each row settles to \`enabled\` (with a version) or \`failed\` (with an error_message).`;
97
216
  return {
98
217
  content: [
99
- { type: "text", text: "pgvector enablement started. Poll get_instance for the `postgres_enable_pgvector` and `postgres_config_update` tasks. Verify with: SELECT extversion FROM pg_extension WHERE extname = 'vector';" },
218
+ { type: "text", text: note },
100
219
  { type: "text", text: JSON.stringify(result.data, null, 2) },
101
220
  ],
102
221
  };
@@ -1 +1 @@
1
- {"version":3,"file":"postgres-extensions.js","sourceRoot":"","sources":["../../src/tools/postgres-extensions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"postgres-extensions.js","sourceRoot":"","sources":["../../src/tools/postgres-extensions.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,18 @@ import { session } from "../session.js";
10
10
  * - POST /api/v1/platform/projects/:pid/backups/disable (202 - DELETES existing backups)
11
11
  * - POST /api/v1/platform/projects/:pid/backups/restore (202 - rebuilds the VM's boot disk)
12
12
  *
13
+ * AND on-demand SNAPSHOTS, which are a separate surface from the automatic dailies above:
14
+ * - GET /api/v1/platform/projects/:pid/snapshots
15
+ * - POST /api/v1/platform/projects/:pid/snapshots (202 - "back up now")
16
+ * - POST /api/v1/platform/projects/:pid/snapshots/:image_id/restore (202 - rebuilds the VM)
17
+ * - DELETE /api/v1/platform/projects/:pid/snapshots/:image_id
18
+ *
19
+ * TWO KINDS OF WHOLE-VM IMAGE, and conflating them is the mistake to avoid. The `backups`
20
+ * endpoints are a TOGGLE over Hetzner's automatic daily schedule, which Hetzner rotates and
21
+ * deletes with the server; you cannot pick when one is taken. The `snapshots` endpoints are
22
+ * MANUAL point-in-time images the user asks for and keeps until they delete them - which is
23
+ * what you want before a risky change. Both restore by rebuilding the whole VM.
24
+ *
13
25
  * Hetzner owns the schedule and retention; there is no local backup record, so
14
26
  * `list_project_backups` reads through to Hetzner and the toggles only flip project
15
27
  * state. Hetzner rotates and auto-deletes backups with the server.
@@ -29,6 +41,8 @@ const NO_ORG = "No active organization. Call `list_organizations` then `select_o
29
41
  function hasOrg() {
30
42
  return session.getActiveOrgId() !== null;
31
43
  }
44
+ /** Snapshot cap per project (`ServerSnapshotService::MAX_SNAPSHOTS`, env-tunable). */
45
+ const MAX_SNAPSHOTS = 10;
32
46
  /** Shared with every tool here: what a failure actually means. */
33
47
  const FAILURE_NOTE = "A 409 means the project is not `active` yet (or its server is not up) - wait, do not retry in a loop. A 502 means Hetzner refused the operation.";
34
48
  export function registerProjectBackupTools(server) {
@@ -155,5 +169,121 @@ Before calling: show the user the backup list with dates, get them to pick, and
155
169
  ],
156
170
  };
157
171
  });
172
+ server.tool("list_project_snapshots", `List the project's on-demand snapshots - manual point-in-time images of the whole server, distinct from the automatic daily backups \`list_project_backups\` shows.
173
+
174
+ Returns each snapshot's \`image_id\`, description, size and creation time, plus \`max_snapshots\` (the cap, currently ${MAX_SNAPSHOTS}). Use \`image_id\` for restore and delete.
175
+
176
+ Unlike the automatic backups, these persist until someone deletes them - they are not rotated. ${FAILURE_NOTE}`, {
177
+ project_pid: z.string().min(1),
178
+ }, async ({ project_pid }) => {
179
+ if (!hasOrg())
180
+ return { content: [{ type: "text", text: NO_ORG }] };
181
+ const result = await apiRequest(`${PROJECTS_BASE}/${project_pid}/snapshots`, {
182
+ toolName: "list_project_snapshots",
183
+ });
184
+ if (!result.success) {
185
+ return { content: [{ type: "text", text: `Failed to list snapshots (${result.statusCode}): ${result.message}` }] };
186
+ }
187
+ return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
188
+ });
189
+ server.tool("create_project_snapshot", `Take a snapshot of the project's server right now - "back up before I break something". This is the tool to reach for BEFORE a risky change: upgrading a service, editing env vars that boot an app, or restoring a database.
190
+
191
+ It captures the WHOLE VM, so it covers every service, database and anything installed on the box - which is also why it is the only real undo a project has.
192
+
193
+ Returns 202: Hetzner creates the image in the background, so it is not usable the instant this returns. Poll \`list_project_snapshots\` until it appears complete before relying on it.
194
+
195
+ Capped at ${MAX_SNAPSHOTS} per project - a 422 means the cap is reached and an old one has to go first (\`delete_project_snapshot\`). ${FAILURE_NOTE}
196
+
197
+ Not charged to the customer: there is no snapshot SKU, so the platform absorbs Hetzner's image cost, which is why the cap exists at all. Do not quote a price for this.`, {
198
+ project_pid: z.string().min(1),
199
+ description: z.string().max(255).optional()
200
+ .describe("A label so the user can tell snapshots apart later, e.g. \"before upgrading n8n\". Strongly recommended - the list is unreadable without one."),
201
+ }, async ({ project_pid, description }) => {
202
+ if (!hasOrg())
203
+ return { content: [{ type: "text", text: NO_ORG }] };
204
+ const body = {};
205
+ if (description)
206
+ body.description = description;
207
+ const result = await apiRequest(`${PROJECTS_BASE}/${project_pid}/snapshots`, {
208
+ method: "POST",
209
+ body,
210
+ toolName: "create_project_snapshot",
211
+ });
212
+ if (!result.success) {
213
+ const capHint = result.statusCode === 422 ? ` The cap is ${MAX_SNAPSHOTS} per project - delete an old snapshot first.` : "";
214
+ return { content: [{ type: "text", text: `Failed to create snapshot (${result.statusCode}): ${result.message}.${capHint}` }] };
215
+ }
216
+ return {
217
+ content: [
218
+ { type: "text", text: "Snapshot started. Hetzner creates the image in the background, so it is NOT yet a usable restore point - poll `list_project_snapshots` until it shows up complete before making the change you were protecting against." },
219
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
220
+ ],
221
+ };
222
+ });
223
+ server.tool("restore_project_snapshot", `Rebuild the project's server from one of its snapshots. THE MOST DESTRUCTIVE OPERATION ON A PROJECT.
224
+
225
+ WHAT THIS DOES: the whole VM is rebuilt from the image. Every change made since the snapshot was taken is GONE - not just the thing the user wanted to undo, but every service, database write, config edit and uploaded file across the entire project. There is no partial or per-service restore.
226
+
227
+ BEFORE CALLING: take a snapshot of the CURRENT state first (\`create_project_snapshot\`), so the restore itself is reversible. Say out loud what the user will lose - name the age of the snapshot they are restoring, since "restore from the 3rd" means discarding everything since the 3rd.
228
+
229
+ The project's exact \`name\` must be supplied and match, as a backend safety check. Returns 202; the rebuild runs in the background and the project is unusable while it does.
230
+
231
+ ${FAILURE_NOTE}`, {
232
+ project_pid: z.string().min(1),
233
+ image_id: z.string().min(1).describe("Snapshot image_id from `list_project_snapshots`"),
234
+ name: z.string().min(1).describe("The project's exact current name - a safety check, must match or the backend refuses"),
235
+ confirm: z.boolean().describe("Must be true. Rebuilds the entire server; everything written since the snapshot is destroyed."),
236
+ }, async ({ project_pid, image_id, name, confirm }) => {
237
+ if (!hasOrg())
238
+ return { content: [{ type: "text", text: NO_ORG }] };
239
+ if (!confirm) {
240
+ return {
241
+ content: [{
242
+ type: "text",
243
+ text: `About to REBUILD project \`${name}\` (${project_pid}) from snapshot ${image_id}.\n\nThis destroys everything on the server that postdates that snapshot - every database write, every service change, every uploaded file, across the whole project. It cannot be partially applied and there is no undo unless you snapshot the current state first.\n\nTake a snapshot of the CURRENT state with \`create_project_snapshot\` before proceeding, confirm with the user which snapshot they mean and how old it is, then call again with confirm=true.`,
244
+ }],
245
+ };
246
+ }
247
+ const result = await apiRequest(`${PROJECTS_BASE}/${project_pid}/snapshots/${image_id}/restore`, {
248
+ method: "POST",
249
+ body: { name },
250
+ toolName: "restore_project_snapshot",
251
+ });
252
+ if (!result.success) {
253
+ return { content: [{ type: "text", text: `Failed to restore snapshot (${result.statusCode}): ${result.message}` }] };
254
+ }
255
+ return {
256
+ content: [
257
+ { type: "text", text: "Restore started. The server is being rebuilt from the snapshot and the project is unusable until it finishes - poll `get_project` for `status` and `ready`. Everything written after the snapshot is gone." },
258
+ { type: "text", text: JSON.stringify(result.data, null, 2) },
259
+ ],
260
+ };
261
+ });
262
+ server.tool("delete_project_snapshot", `Delete one on-demand snapshot. Irreversible - the restore point is gone.
263
+
264
+ Do this to free a slot when the ${MAX_SNAPSHOTS}-snapshot cap blocks a new one. Check with \`list_project_snapshots\` which is genuinely the oldest or least useful rather than guessing, and do not delete the only snapshot a project has without saying so.`, {
265
+ project_pid: z.string().min(1),
266
+ image_id: z.string().min(1).describe("Snapshot image_id from `list_project_snapshots`"),
267
+ confirm: z.boolean().describe("Must be true. The snapshot is permanently deleted and can no longer be restored from."),
268
+ }, async ({ project_pid, image_id, confirm }) => {
269
+ if (!hasOrg())
270
+ return { content: [{ type: "text", text: NO_ORG }] };
271
+ if (!confirm) {
272
+ return {
273
+ content: [{
274
+ type: "text",
275
+ text: `About to permanently delete snapshot ${image_id} from project ${project_pid}. That restore point is gone for good.\n\nCall again with confirm=true.`,
276
+ }],
277
+ };
278
+ }
279
+ const result = await apiRequest(`${PROJECTS_BASE}/${project_pid}/snapshots/${image_id}`, {
280
+ method: "DELETE",
281
+ toolName: "delete_project_snapshot",
282
+ });
283
+ if (!result.success) {
284
+ return { content: [{ type: "text", text: `Failed to delete snapshot (${result.statusCode}): ${result.message}` }] };
285
+ }
286
+ return { content: [{ type: "text", text: "Snapshot deleted." }] };
287
+ });
158
288
  }
159
289
  //# sourceMappingURL=project-backups.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-backups.js","sourceRoot":"","sources":["../../src/tools/project-backups.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"project-backups.js","sourceRoot":"","sources":["../../src/tools/project-backups.ts"],"names":[],"mappings":"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"}
@@ -38,7 +38,7 @@ function checkPassword(label, v) {
38
38
  return null;
39
39
  }
40
40
  export function registerProjectDatabaseTools(server) {
41
- server.tool("list_project_databases", `List the databases attached to a project (Postgres/Redis/MySQL/MongoDB provisioned via Coolify, NOT AWS managed instances). Returns pid, name, db_type, status (\`active\` | \`inactive\` | \`creating\` | \`failed\` | \`unknown\`), is_public, public_port, \`vector_enabled\` / \`timescaledb_enabled\` on Postgres, and a \`provisioning\` block with per-stage progress.
41
+ server.tool("list_project_databases", `List the databases attached to a project (Postgres/Redis/MySQL/MongoDB provisioned via Coolify, NOT AWS managed instances). Returns pid, name, db_type, status (\`active\` | \`inactive\` | \`creating\` | \`failed\` | \`unknown\`), is_public, public_port, an \`extensions\` array on Postgres (per-extension \`status\` / \`version\` / \`error_message\`), and a \`provisioning\` block with per-stage progress.
42
42
 
43
43
  Passwords are never returned here. Statuses on this list are the last known values; \`get_project_database\` re-reads one live from Coolify.`, {
44
44
  project_pid: z.string().min(1),
@@ -55,7 +55,7 @@ Passwords are never returned here. Statuses on this list are the last known valu
55
55
  });
56
56
  server.tool("get_project_database", `Get one project database, with its status re-read LIVE from Coolify rather than served from the last known value. Use this when \`list_project_databases\` shows \`creating\` and you need to know whether it has actually finished, or when a status looks stale.
57
57
 
58
- Also returns \`vector_enabled\` / \`timescaledb_enabled\` (Postgres) and the \`provisioning\` stage block, so it is the tool to reach for when a user asks why provisioning is taking so long.
58
+ Also returns the \`extensions\` array (Postgres - each with \`status\`, \`version\` and \`error_message\`, so this is where a failed CREATE EXTENSION is visible) and the \`provisioning\` stage block, so it is the tool to reach for when a user asks why provisioning is taking so long.
59
59
 
60
60
  Unlike create, this works on a stopped or billing-suspended project. On one of those the live refresh is skipped (the server is powered off and there is nothing to ask) and you get the stored record - so a \`creating\` status on a stopped project means "we never found out", not "still working".
61
61
 
@@ -75,12 +75,12 @@ Passwords are not returned. Connection details for a project database come from
75
75
  });
76
76
  server.tool("create_project_database", `Create a database inside a project (provisioned via Coolify; returns 202, status starts \`creating\` - poll \`list_project_databases\`). The project must be \`active\`. Provide the type-specific credentials (see fields). Passwords must be 8+ chars with no spaces or shell-dangerous chars. \`is_public\` is REQUIRED and has no default: ASK THE USER whether the database should be publicly reachable on a port (convenient for external clients but exposes it to the internet) or kept private to the project (safer). Do not assume.
77
77
 
78
- POSTGRES EXTENSIONS (create-time only here - unlike AWS instances, a project database cannot gain an extension later; you would have to recreate it):
79
- - timescaledb_enabled → runs the TimescaleDB image, then enables the extension once the container is healthy.
80
- - vector_enabled → runs the pgvector image, then enables the extension.
81
- - Only ONE image can win: setting both gives you TimescaleDB, and pgvector is NOT installed. Use two databases if you need both.
82
- - Either flag pins the container to PostgreSQL 17.
83
- - The extension step needs a reachable database: with is_public=false there is no external URL, so provisioning succeeds but the CREATE EXTENSION is skipped and the user must run it themselves.`, {
78
+ POSTGRES EXTENSIONS - pass \`extensions: [...]\` (catalog names). The old \`timescaledb_enabled\` / \`vector_enabled\` booleans were REMOVED backend-side and are now IGNORED, so sending them yields a database with no extensions.
79
+ - CREATE-TIME ONLY HERE. Unlike an AWS instance, a project database can never gain an extension later - a forgotten one means recreating the database. Ask the user what they need BEFORE creating.
80
+ - CHECK \`coolify_supported\` FIRST via \`list_postgres_extension_catalog\`. A project database is a container and can only run what its image ships, so the catalog is narrower here than on a managed instance. Today \`pg_partman\` is the one entry no image provides, and requesting it is a 422 rather than a silent drop.
81
+ - The platform picks the cheapest image that covers your WHOLE request, so combinations that used to be impossible now work: pgvector and timescaledb together resolve to the bundled image. You no longer have to choose one.
82
+ - PostgreSQL 17 only - that is what the images ship.
83
+ - \`is_public: false\` BREAKS THE EXTENSION STEP. With no external URL the platform cannot connect to run CREATE EXTENSION, so every requested extension settles as \`failed\` with "database is not public" in its \`error_message\`. The database itself still works. If the user wants extensions on a private database, they must create them by hand from inside the project.`, {
84
84
  project_pid: z.string().min(1),
85
85
  db_type: z.enum(DB_TYPES).describe("postgres | redis | mysql | mongodb"),
86
86
  name: z.string().min(1).max(255),
@@ -90,8 +90,7 @@ POSTGRES EXTENSIONS (create-time only here - unlike AWS instances, a project dat
90
90
  postgres_user: z.string().max(255).optional(),
91
91
  postgres_password: z.string().optional(),
92
92
  postgres_db: z.string().max(255).optional(),
93
- timescaledb_enabled: z.boolean().optional().describe("postgres only: use the TimescaleDB image (pins PostgreSQL 17) and enable the extension for time-series data. Takes precedence over vector_enabled - only one image can run."),
94
- vector_enabled: z.boolean().optional().describe("postgres only: use the pgvector image (pins PostgreSQL 17) and enable the extension for embeddings. Ignored if timescaledb_enabled is also set."),
93
+ extensions: z.array(z.string().min(1)).optional().describe("postgres only, create-time only: PostgreSQL extensions by catalog name, e.g. [\"pgvector\"]. Only entries whose `coolify_supported` is true can run here - check `list_postgres_extension_catalog`. Pins PostgreSQL 17."),
95
94
  // redis
96
95
  redis_password: z.string().optional(),
97
96
  // mysql
@@ -113,11 +112,11 @@ POSTGRES EXTENSIONS (create-time only here - unlike AWS instances, a project dat
113
112
  body.public_port = args.public_port ?? DEFAULT_PORTS[dbType];
114
113
  // Extensions ride on the postgres image, so they are meaningless elsewhere - the API
115
114
  // would accept and ignore them, which reads like success.
116
- if (dbType !== "postgres" && (args.timescaledb_enabled || args.vector_enabled)) {
115
+ if (dbType !== "postgres" && args.extensions?.length) {
117
116
  return {
118
117
  content: [{
119
118
  type: "text",
120
- text: `timescaledb_enabled / vector_enabled are PostgreSQL-only and would be ignored on ${dbType}. Use db_type="postgres" to get them.`,
119
+ text: `extensions (${args.extensions.join(", ")}) are PostgreSQL-only and would be IGNORED on ${dbType}. Use db_type="postgres" to get them.`,
121
120
  }],
122
121
  };
123
122
  }
@@ -134,10 +133,8 @@ POSTGRES EXTENSIONS (create-time only here - unlike AWS instances, a project dat
134
133
  body.postgres_password = args.postgres_password;
135
134
  body.postgres_db = args.postgres_db;
136
135
  body.instant_deploy = true;
137
- if (args.timescaledb_enabled)
138
- body.timescaledb_enabled = true;
139
- if (args.vector_enabled)
140
- body.vector_enabled = true;
136
+ if (args.extensions?.length)
137
+ body.extensions = args.extensions;
141
138
  }
142
139
  break;
143
140
  case "redis":
@@ -185,14 +182,10 @@ POSTGRES EXTENSIONS (create-time only here - unlike AWS instances, a project dat
185
182
  return { content: [{ type: "text", text: `Failed to create database (${result.statusCode}): ${result.message}` }] };
186
183
  }
187
184
  const notes = [`Database \`${args.name}\` (${dbType}) provisioning started. Poll \`list_project_databases\` until status is \`active\`.`];
188
- if (args.timescaledb_enabled || args.vector_enabled) {
189
- const winner = args.timescaledb_enabled ? "TimescaleDB" : "pgvector";
190
- notes.push(`Running the ${winner} image on PostgreSQL 17; the extension is created once the container is healthy.`);
191
- if (args.timescaledb_enabled && args.vector_enabled) {
192
- notes.push("⚠️ Both extensions were requested but only one image can run: pgvector was NOT installed. Create a second database if you need it.");
193
- }
185
+ if (args.extensions?.length) {
186
+ notes.push(`Requested extensions (${args.extensions.join(", ")}) are created on PostgreSQL 17 once the container is healthy, then smoke-tested. Read them back from \`get_project_database\`: each carries its own \`status\`, \`version\` and \`error_message\`, and \`enabled\` means verified, not attempted.`);
194
187
  if (!isPublic) {
195
- notes.push("⚠️ This database is private, so there is no external URL for the CREATE EXTENSION step - it will be skipped. Run `CREATE EXTENSION IF NOT EXISTS " + (args.timescaledb_enabled ? "timescaledb" : "vector") + ";` yourself from inside the project.");
188
+ notes.push("⚠️ This database is PRIVATE, so the platform has no external URL to connect through and every requested extension will settle as `failed` (\"database is not public\"). The database works; the extensions are not there. Run `CREATE EXTENSION IF NOT EXISTS <name>;` yourself from inside the project, or recreate the database with is_public=true.");
196
189
  }
197
190
  }
198
191
  return {