@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.
- package/README.md +205 -43
- package/dist/client.d.ts +7 -0
- package/dist/client.js +2 -1
- package/dist/client.js.map +1 -1
- package/dist/index.js +21 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/alerts.js +12 -2
- package/dist/tools/alerts.js.map +1 -1
- package/dist/tools/backups.js +29 -5
- package/dist/tools/backups.js.map +1 -1
- package/dist/tools/billing.js +233 -39
- package/dist/tools/billing.js.map +1 -1
- package/dist/tools/database-users.js +53 -6
- package/dist/tools/database-users.js.map +1 -1
- package/dist/tools/deployments.js +47 -3
- package/dist/tools/deployments.js.map +1 -1
- package/dist/tools/github.js +21 -4
- package/dist/tools/github.js.map +1 -1
- package/dist/tools/instance-logs.js +2 -2
- package/dist/tools/instance-logs.js.map +1 -1
- package/dist/tools/instances.js +423 -88
- package/dist/tools/instances.js.map +1 -1
- package/dist/tools/organizations.js +5 -1
- package/dist/tools/organizations.js.map +1 -1
- package/dist/tools/pitr.d.ts +22 -0
- package/dist/tools/pitr.js +75 -10
- package/dist/tools/pitr.js.map +1 -1
- package/dist/tools/postgres-extensions.js +176 -57
- package/dist/tools/postgres-extensions.js.map +1 -1
- package/dist/tools/project-backups.js +130 -0
- package/dist/tools/project-backups.js.map +1 -1
- package/dist/tools/project-databases.js +16 -23
- package/dist/tools/project-databases.js.map +1 -1
- package/dist/tools/project-services.js +544 -53
- package/dist/tools/project-services.js.map +1 -1
- package/dist/tools/project-ssh.d.ts +2 -0
- package/dist/tools/project-ssh.js +338 -0
- package/dist/tools/project-ssh.js.map +1 -0
- package/dist/tools/projects.js +21 -3
- package/dist/tools/projects.js.map +1 -1
- package/dist/tools/scaling.js +1 -1
- package/dist/tools/scaling.js.map +1 -1
- package/dist/types/tiers.js +27 -4
- package/dist/types/tiers.js.map +1 -1
- 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 -
|
|
4
|
+
* PostgreSQL extensions - a server-side catalog, not a pair of booleans.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
-
*
|
|
25
|
-
*
|
|
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
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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: `
|
|
73
|
+
text: `Failed to list the extension catalog (${result.statusCode}): ${result.message}`,
|
|
51
74
|
}],
|
|
52
75
|
};
|
|
53
76
|
}
|
|
54
|
-
|
|
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("
|
|
79
|
+
server.tool("list_instance_extensions", `List PostgreSQL extension state for one managed instance, split three ways:
|
|
71
80
|
|
|
72
|
-
|
|
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
|
-
|
|
85
|
+
Also returns the instance's \`db_version\`, \`architecture\` and \`status\`, which is what determines the split.
|
|
75
86
|
|
|
76
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
if (!
|
|
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: `
|
|
95
|
+
text: `Failed to list extensions (${result.statusCode}): ${result.message}`,
|
|
85
96
|
}],
|
|
86
97
|
};
|
|
87
98
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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 {
|
|
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:
|
|
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":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C
|
|
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":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC
|
|
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, \`
|
|
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 \`
|
|
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
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
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
|
-
|
|
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" &&
|
|
115
|
+
if (dbType !== "postgres" && args.extensions?.length) {
|
|
117
116
|
return {
|
|
118
117
|
content: [{
|
|
119
118
|
type: "text",
|
|
120
|
-
text: `
|
|
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.
|
|
138
|
-
body.
|
|
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.
|
|
189
|
-
|
|
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
|
|
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 {
|