@hoststack.dev/mcp 0.26.0 → 0.27.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 +2 -2
- package/dist/hoststack-mcp.js +46 -6
- package/dist/hoststack-mcp.js.map +1 -1
- package/dist/index.js +46 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -97,7 +97,7 @@ use by hand — an MCP client is what should be launching it.
|
|
|
97
97
|
|
|
98
98
|
## Tool inventory
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
113 tools. The headings are the registry's own categories rather than a friendlier regrouping, so the build can diff this table against the registry and fail when the two disagree — which is how an earlier version of it came to advertise a total from three releases back and send agents to the dashboard for a `create_database` that had already shipped.
|
|
101
101
|
|
|
102
102
|
| Category | Read | Write |
|
|
103
103
|
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -106,7 +106,7 @@ use by hand — an MCP client is what should be launching it.
|
|
|
106
106
|
| **logs** | `get_service_logs`, `get_service_logs_bulk` | — |
|
|
107
107
|
| **deploys** | `list_deploys`, `get_deploy`, `get_deploy_logs`, `diagnose_deploy` | `trigger_deploy`, `cancel_deploy` |
|
|
108
108
|
| **environments** | `list_environments` | `create_environment`, `update_environment`, `delete_environment`, `promote_deploy` |
|
|
109
|
-
| **databases** | `list_databases`, `get_database`, `get_database_cluster`, `query_database`
|
|
109
|
+
| **databases** | `list_databases`, `get_database`, `get_database_cluster`, `list_database_backups`, `query_database` | `create_database`, `update_database`, `delete_database`, `suspend_database`, `resume_database`, `restart_database`, `upgrade_database_to_ha`, `upgrade_database_version` |
|
|
110
110
|
| **volumes** | `list_volumes`, `list_volume_backups` | `create_volume`, `update_volume`, `delete_volume`, `restore_volume` |
|
|
111
111
|
| **resource-links** | `list_managed_resources`, `list_service_resources` | `link_resource_to_service`, `unlink_resource_from_service` |
|
|
112
112
|
| **machines** | `list_machines`, `get_machine` | — |
|
package/dist/hoststack-mcp.js
CHANGED
|
@@ -8,7 +8,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
8
8
|
import { HostStack } from "@hoststack.dev/sdk";
|
|
9
9
|
|
|
10
10
|
// src/version.ts
|
|
11
|
-
var MCP_VERSION = true ? "0.
|
|
11
|
+
var MCP_VERSION = true ? "0.27.0" : "0.0.0-dev";
|
|
12
12
|
var USER_AGENT = `hoststack-mcp/${MCP_VERSION}`;
|
|
13
13
|
|
|
14
14
|
// src/api-client.ts
|
|
@@ -523,7 +523,7 @@ defineTool({
|
|
|
523
523
|
"Deploy.failed_consecutive entries (v89) carry the offending `commitHash` in lastMetadata, and the streak now dedupes per (service, commitHash) \u2014 one critical per bad commit, not one every 6 retries.",
|
|
524
524
|
"",
|
|
525
525
|
"Inputs (all optional):",
|
|
526
|
-
' - since: ISO-8601 timestamp OR relative offset like "-1h" / "-2d". Default: -24h.',
|
|
526
|
+
' - since: ISO-8601 timestamp OR relative offset like "-1h" / "-2d". Default: -24h, clamped to 30 days back. It bounds the CLEARED history only: an alert that is still open is always returned, however long ago it fired \u2014 so the default view answers "what is on fire" and not "what caught fire today".',
|
|
527
527
|
" - until: ISO-8601 upper bound (ignored when aggregating \u2014 aggregated view always extends to now).",
|
|
528
528
|
" - limit: max rows (default 100, hard cap 500).",
|
|
529
529
|
" - aggregate: true (default) collapses by (action, resourceId); false returns raw rows.",
|
|
@@ -534,7 +534,9 @@ defineTool({
|
|
|
534
534
|
"Example: list_alerts({ since: '-1h' }) \u2192 { alerts: [{ action: 'deploy.failed_consecutive', resourceId: 31, severity: 'critical', active: true, count: 3, lastFiredAt: '\u2026', lastResolvedAt: null, lastMetadata: { commitHash: 'abc1234' }, \u2026 }] }."
|
|
535
535
|
].join("\n"),
|
|
536
536
|
input: {
|
|
537
|
-
since: z3.string().optional().describe(
|
|
537
|
+
since: z3.string().optional().describe(
|
|
538
|
+
'ISO-8601 timestamp or relative offset (e.g. "-1h", "-2d"). Default: -24h. Bounds the cleared history only \u2014 still-open alerts are returned regardless of age.'
|
|
539
|
+
),
|
|
538
540
|
until: z3.string().optional().describe("ISO-8601 upper bound. Only honored when aggregate=false."),
|
|
539
541
|
limit: z3.number().int().positive().max(500).optional().describe("Max rows (default 100, hard cap 500)."),
|
|
540
542
|
aggregate: z3.boolean().optional().describe("Collapse by (action, resourceId). Default true."),
|
|
@@ -759,7 +761,7 @@ defineTool({
|
|
|
759
761
|
" - environment_id (optional): bind to a specific environment; defaults to the project Production env.",
|
|
760
762
|
" - postgis (optional): provision the PostGIS image variant so `CREATE EXTENSION postgis` works. Postgres only.",
|
|
761
763
|
" - pgvector (optional): provision the pgvector image variant so `CREATE EXTENSION vector` works. Postgres only, mutually exclusive with postgis.",
|
|
762
|
-
" - machine (optional): put it on one of the team's OWN enrolled machines (name or id, see list_machines) instead of HostStack compute. It costs nothing there and is still backed up off-site, but it is reachable ONLY from that same machine \u2014 the service that queries it has to run there too, and linking it to a service anywhere else is refused. No HA and no external access on a machine you own.",
|
|
764
|
+
" - machine (optional): put it on one of the team's OWN enrolled machines (name or id, see list_machines) instead of HostStack compute. It costs nothing there and is still backed up off-site, but it is reachable ONLY from that same machine \u2014 the service that queries it has to run there too, and linking it to a service anywhere else is refused. No HA and no external access on a machine you own. An infrastructure machine takes a database of the project it is bound to, and nothing else.",
|
|
763
765
|
"",
|
|
764
766
|
'Returns: { database: Database } \u2014 note BOTH `id` (numeric \u2014 this is what link_resource_to_service wants) and `publicId` ("db_\u2026" \u2014 what the other database tools want).',
|
|
765
767
|
"",
|
|
@@ -1136,6 +1138,37 @@ defineTool({
|
|
|
1136
1138
|
return respond({ summary, data: result });
|
|
1137
1139
|
}
|
|
1138
1140
|
});
|
|
1141
|
+
defineTool({
|
|
1142
|
+
name: "list_database_backups",
|
|
1143
|
+
category: "databases",
|
|
1144
|
+
description: [
|
|
1145
|
+
"List the off-site archives a managed database can be restored from, newest first.",
|
|
1146
|
+
"",
|
|
1147
|
+
'WHY THIS MATTERS: HostStack dumps every managed database nightly to off-site object storage. That is a SCHEDULE \u2014 a promise about what should happen. It does not say any dump exists. A database whose dump has failed every night since it was created (no upload grant, a full disk, a rotated credential, an engine version the dumper does not know) looks identical from the outside to one backed up every night, and the first person to find out is whoever already needed the restore. This tool is how you tell "protected" from "believed to be protected". Check it for anything whose loss is not survivable.',
|
|
1148
|
+
"",
|
|
1149
|
+
"A dump written onto the machine's OWN disk is not listed, on purpose. On a machine of the customer's own with no upload grant that is where the dump lands \u2014 the same disk holding the database \u2014 so counting it would be counting the thing that can be lost.",
|
|
1150
|
+
"",
|
|
1151
|
+
"When to use: before a risky migration, when someone asks what the recovery position is, or when auditing a project's backup exposure. Read it together with `lastOffsiteBackupAt` on get_database: that field says WHEN, this says WHAT.",
|
|
1152
|
+
"",
|
|
1153
|
+
"Inputs:",
|
|
1154
|
+
" - database_id: publicId of the database (db_\u2026).",
|
|
1155
|
+
"",
|
|
1156
|
+
"Returns: { items: RestorePoint[] } \u2014 id, archiveName, sizeBytes (may be null on older agents), createdAt, s3Url. Only the most recent few are kept, matching the retention on the machine: seven rows means seven restore points, not seven backups ever taken.",
|
|
1157
|
+
"",
|
|
1158
|
+
'Example: list_database_backups({ database_id: "db_abc" }) \u2192 { items: [{ id: 12, archiveName: "backup-2026-09-16T02-00-11-004Z.sql.gz", sizeBytes: 314572800, createdAt: "2026-09-16T02:01:42Z" }] }'
|
|
1159
|
+
].join("\n"),
|
|
1160
|
+
input: {
|
|
1161
|
+
database_id: z6.string().describe("Database publicId (e.g. db_\u2026).")
|
|
1162
|
+
},
|
|
1163
|
+
handler: async (args2, ctx) => {
|
|
1164
|
+
const teamId = await ctx.resolveTeamId();
|
|
1165
|
+
const response = await ctx.hoststack.databases.listRestorePoints(teamId, args2.database_id);
|
|
1166
|
+
const data = shapeList(response, "restorePoints", shape);
|
|
1167
|
+
const newest = response.restorePoints[0];
|
|
1168
|
+
const summary = data.items.length === 0 ? `Database ${args2.database_id} has NO off-site restore points. Either no dump has completed yet, or every dump is landing on the machine's own disk \u2014 either way there is currently nothing to restore from. Check lastBackupStatus on get_database for which: offsite_failed means a dump ran and its upload was attempted and failed, and lastBackupError carries the provider's reason.` : `Database ${args2.database_id} has ${data.items.length} restore point${data.items.length === 1 ? "" : "s"}; newest ${newest?.archiveName} from ${newest?.createdAt}.`;
|
|
1169
|
+
return respond({ summary, data });
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1139
1172
|
|
|
1140
1173
|
// src/tools/deploys.ts
|
|
1141
1174
|
import { z as z7 } from "zod";
|
|
@@ -3240,6 +3273,7 @@ var NOTIFICATION_EVENTS = [
|
|
|
3240
3273
|
"dns.registry_record_changed",
|
|
3241
3274
|
"domain.registrant_verification_lapsed",
|
|
3242
3275
|
"service.auto_restarted",
|
|
3276
|
+
"project.release_awaiting_start",
|
|
3243
3277
|
"machine.offline",
|
|
3244
3278
|
"machine.online",
|
|
3245
3279
|
"billing.invoice",
|
|
@@ -5396,10 +5430,14 @@ defineTool({
|
|
|
5396
5430
|
"Inputs:",
|
|
5397
5431
|
" - service_id: publicId of the service.",
|
|
5398
5432
|
"",
|
|
5399
|
-
"Returns: { items: Volume[] } \u2014 each entry has id, publicId, name, mountPath, sizeGb, status (pending|active|deleting), backupEnabled, createdAt, updatedAt.",
|
|
5433
|
+
"Returns: { items: Volume[] } \u2014 each entry has id, publicId, name, mountPath, sizeGb, status (pending|active|deleting), backupEnabled, blockBacked, adopted, createdAt, updatedAt.",
|
|
5400
5434
|
"",
|
|
5401
5435
|
"`backupEnabled` says backups are being TAKEN, not that any exist. Call list_volume_backups to see which archives are actually restorable \u2014 a volume can be enabled and have nothing behind it (nothing has completed yet, or the host has no upload grant and is writing tars onto the very disk it is backing up, which is not a copy of anything). Turn it on with update_volume({ backup_enabled: true }).",
|
|
5402
5436
|
"",
|
|
5437
|
+
'READ `backupEnabled: false` AGAINST `blockBacked` BEFORE REPORTING IT. On a block-backed volume it is not a to-do: the disk is already triple-replicated by Hetzner and update_volume refuses to turn the toggle on, so "backups are off" is the wrong finding to hand a user. On every other volume \u2014 `blockBacked: false`, `adopted` either way \u2014 it IS a to-do, and it means the only copy of that data is one disk.',
|
|
5438
|
+
"",
|
|
5439
|
+
'`adopted: true` is an existing Docker volume on an infrastructure machine, mounted in place rather than created by us. `sizeGb` is 0 on one and means "not ours to say", not "empty". Backups can be turned on \u2014 they only read it \u2014 but resize, restore, import and delete are refused.',
|
|
5440
|
+
"",
|
|
5403
5441
|
'Example: list_volumes({ service_id: "svc_abc" }) \u2192 { items: [{ name: "data", mountPath: "/var/data", sizeGb: 10, status: "active", backupEnabled: false }] }'
|
|
5404
5442
|
].join("\n"),
|
|
5405
5443
|
input: {
|
|
@@ -5472,7 +5510,9 @@ defineTool({
|
|
|
5472
5510
|
" - volume_id: publicId of the volume to update.",
|
|
5473
5511
|
" - mount_path (optional): new in-container mount path.",
|
|
5474
5512
|
" - size_gb (optional): new size in GB (must be \u2265 current).",
|
|
5475
|
-
" - backup_enabled (optional): whether this volume is backed up nightly. Turning it off stops future backups; it does not delete the ones already taken. Verify with list_volume_backups \u2014 enabling is not the same as having a backup.",
|
|
5513
|
+
" - backup_enabled (optional): whether this volume is backed up nightly. Turning it off stops future backups; it does not delete the ones already taken. Verify with list_volume_backups \u2014 enabling is not the same as having a backup. Refused on a block-backed volume (Hetzner already replicates it three ways).",
|
|
5514
|
+
"",
|
|
5515
|
+
"On an ADOPTED volume (`adopted: true` from list_volumes \u2014 an infrastructure machine's own Docker volume) this is the ONLY field that can be changed: a backup reads the volume and writes the archive elsewhere, while mount_path and size_gb describe what the machine's operator set up.",
|
|
5476
5516
|
"",
|
|
5477
5517
|
"IMPORTANT about what a volume backup is: a block-level tar of a LIVE filesystem, i.e. CRASH CONSISTENT, not application consistent. Nothing is quiesced. For a container running its own database (WordPress + MariaDB in one box, Postgres on a disk, SQLite under write), the archive captures whatever was on disk mid-write \u2014 the same state the database would face after a power cut. Usually recoverable, occasionally not. Recommend this as the disaster fallback and a scheduled dump as the actual backup; do not present it as a substitute for one.",
|
|
5478
5518
|
"",
|