@hoststack.dev/mcp 0.2.1 → 0.3.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/dist/hoststack-mcp.js +143 -0
- package/dist/hoststack-mcp.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +143 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/hoststack-mcp.js
CHANGED
|
@@ -320,6 +320,9 @@ function shapeUser(value) {
|
|
|
320
320
|
function shapeTeam(value) {
|
|
321
321
|
return isObject(value) ? dropNullsAndInternals(value) : {};
|
|
322
322
|
}
|
|
323
|
+
function shapeVolume(value) {
|
|
324
|
+
return isObject(value) ? dropNullsAndInternals(value) : {};
|
|
325
|
+
}
|
|
323
326
|
|
|
324
327
|
// src/resources/hoststack-resources.ts
|
|
325
328
|
defineResource({
|
|
@@ -1406,6 +1409,146 @@ defineTool({
|
|
|
1406
1409
|
}
|
|
1407
1410
|
});
|
|
1408
1411
|
|
|
1412
|
+
// src/tools/volumes.ts
|
|
1413
|
+
import { z as z10 } from "zod";
|
|
1414
|
+
defineTool({
|
|
1415
|
+
name: "list_volumes",
|
|
1416
|
+
category: "volumes",
|
|
1417
|
+
description: [
|
|
1418
|
+
"List persistent disks attached to a service. Volumes mount writable storage into the container at the path you choose, surviving redeploys and restarts.",
|
|
1419
|
+
"",
|
|
1420
|
+
"When to use: auditing what disks are attached, checking sizes before adding more, or confirming a volume took effect after creation. Note: storage is metered for billing \u2014 use this to spot oversized disks.",
|
|
1421
|
+
"",
|
|
1422
|
+
"Inputs:",
|
|
1423
|
+
" - service_id: publicId of the service.",
|
|
1424
|
+
"",
|
|
1425
|
+
"Returns: { items: Volume[] } \u2014 each entry has id, publicId, name, mountPath, sizeGb, status (pending|active|deleting), createdAt, updatedAt.",
|
|
1426
|
+
"",
|
|
1427
|
+
'Example: list_volumes({ service_id: "svc_abc" }) \u2192 { items: [{ name: "data", mountPath: "/var/data", sizeGb: 10, status: "active" }] }'
|
|
1428
|
+
].join("\n"),
|
|
1429
|
+
input: {
|
|
1430
|
+
service_id: z10.string().describe("Service publicId (e.g. svc_abc123).")
|
|
1431
|
+
},
|
|
1432
|
+
handler: async (args2, ctx) => {
|
|
1433
|
+
const teamId = await ctx.resolveTeamId();
|
|
1434
|
+
const response = await ctx.hoststack.volumes.list(teamId, args2.service_id);
|
|
1435
|
+
const data = shapeList(response, "volumes", shapeVolume);
|
|
1436
|
+
const summary = data.items.length === 0 ? `No volumes attached to service ${args2.service_id}.` : `Found ${data.items.length} volume${data.items.length === 1 ? "" : "s"} on service ${args2.service_id}.`;
|
|
1437
|
+
return respond({ summary, data });
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
defineTool({
|
|
1441
|
+
name: "create_volume",
|
|
1442
|
+
category: "volumes",
|
|
1443
|
+
description: [
|
|
1444
|
+
"Attach a new persistent disk to a service. The disk gets provisioned on the host and bind-mounted into the container at `mount_path` on the next deploy. Use this when an app needs writable persistent storage \u2014 uploads, caches, SQLite databases, anything that must survive container restarts.",
|
|
1445
|
+
"",
|
|
1446
|
+
"When to use: the user mentions storing files, uploads, or any data that needs to outlive a container restart. For purely ephemeral scratch space, point the app at /tmp instead (already tmpfs-mounted, free, no provisioning needed) \u2014 see HOSTSTACK_TMP_DIR.",
|
|
1447
|
+
"",
|
|
1448
|
+
"Inputs:",
|
|
1449
|
+
" - service_id: publicId of the service to attach to.",
|
|
1450
|
+
" - name: lowercase alphanumeric + hyphens, \u226464 chars (used as the docker volume identifier \u2014 change with care once data is written).",
|
|
1451
|
+
' - mount_path: in-container absolute path (e.g. "/var/data").',
|
|
1452
|
+
" - size_gb: optional, 1\u2013100, default 1. Counts against your plan storage quota and is metered for billing.",
|
|
1453
|
+
"",
|
|
1454
|
+
"Returns: { volume: Volume } \u2014 the created record.",
|
|
1455
|
+
"",
|
|
1456
|
+
'Example: create_volume({ service_id: "svc_abc", name: "data", mount_path: "/var/data", size_gb: 10 }) \u2192 { volume: { name: "data", mountPath: "/var/data", sizeGb: 10, status: "active" } }'
|
|
1457
|
+
].join("\n"),
|
|
1458
|
+
input: {
|
|
1459
|
+
service_id: z10.string().describe("Service publicId."),
|
|
1460
|
+
name: z10.string().min(1).max(64).regex(/^[a-z0-9-]+$/).describe("Volume name (lowercase alphanumeric + hyphens)."),
|
|
1461
|
+
mount_path: z10.string().startsWith("/").max(500).describe("In-container mount path (absolute)."),
|
|
1462
|
+
size_gb: z10.number().int().min(1).max(100).optional().describe("Disk size in GB (default 1, max 100).")
|
|
1463
|
+
},
|
|
1464
|
+
handler: async (args2, ctx) => {
|
|
1465
|
+
const teamId = await ctx.resolveTeamId();
|
|
1466
|
+
const input = {
|
|
1467
|
+
name: args2.name,
|
|
1468
|
+
mountPath: args2.mount_path
|
|
1469
|
+
};
|
|
1470
|
+
if (args2.size_gb !== void 0) input.sizeGb = args2.size_gb;
|
|
1471
|
+
const response = await ctx.hoststack.volumes.create(teamId, args2.service_id, input);
|
|
1472
|
+
const data = { volume: shape(response.volume) };
|
|
1473
|
+
return respond({
|
|
1474
|
+
summary: `Attached volume "${args2.name}" (${args2.size_gb ?? 1}GB) at ${args2.mount_path} on service ${args2.service_id}.`,
|
|
1475
|
+
data
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
defineTool({
|
|
1480
|
+
name: "update_volume",
|
|
1481
|
+
category: "volumes",
|
|
1482
|
+
description: [
|
|
1483
|
+
"Update a volume's mountPath or sizeGb. Resizes take effect on the next deploy; shrinking is rejected at the storage layer (filesystems can't safely shrink under live data).",
|
|
1484
|
+
"",
|
|
1485
|
+
"When to use: the user wants to grow the disk for an existing volume, or move where it mounts inside the container.",
|
|
1486
|
+
"",
|
|
1487
|
+
"Inputs:",
|
|
1488
|
+
" - service_id: publicId of the service.",
|
|
1489
|
+
" - volume_id: publicId of the volume to update.",
|
|
1490
|
+
" - mount_path (optional): new in-container mount path.",
|
|
1491
|
+
" - size_gb (optional): new size in GB (must be \u2265 current).",
|
|
1492
|
+
"",
|
|
1493
|
+
"Returns: { volume: Volume } \u2014 the updated record.",
|
|
1494
|
+
"",
|
|
1495
|
+
'Example: update_volume({ service_id: "svc_abc", volume_id: "vol_xyz", size_gb: 20 }) \u2192 { volume: { sizeGb: 20, \u2026 } }'
|
|
1496
|
+
].join("\n"),
|
|
1497
|
+
input: {
|
|
1498
|
+
service_id: z10.string().describe("Service publicId."),
|
|
1499
|
+
volume_id: z10.string().describe("Volume publicId (e.g. vol_\u2026)."),
|
|
1500
|
+
mount_path: z10.string().startsWith("/").max(500).optional().describe("New mount path."),
|
|
1501
|
+
size_gb: z10.number().int().min(1).max(100).optional().describe("New size in GB.")
|
|
1502
|
+
},
|
|
1503
|
+
handler: async (args2, ctx) => {
|
|
1504
|
+
const teamId = await ctx.resolveTeamId();
|
|
1505
|
+
const input = {};
|
|
1506
|
+
if (args2.mount_path !== void 0) input.mountPath = args2.mount_path;
|
|
1507
|
+
if (args2.size_gb !== void 0) input.sizeGb = args2.size_gb;
|
|
1508
|
+
if (Object.keys(input).length === 0) {
|
|
1509
|
+
return respond({ summary: "No fields to update.", data: {} });
|
|
1510
|
+
}
|
|
1511
|
+
const response = await ctx.hoststack.volumes.update(
|
|
1512
|
+
teamId,
|
|
1513
|
+
args2.service_id,
|
|
1514
|
+
args2.volume_id,
|
|
1515
|
+
input
|
|
1516
|
+
);
|
|
1517
|
+
const data = { volume: shape(response.volume) };
|
|
1518
|
+
const fields = Object.keys(input).join(", ");
|
|
1519
|
+
return respond({ summary: `Updated ${fields} on volume ${args2.volume_id}.`, data });
|
|
1520
|
+
}
|
|
1521
|
+
});
|
|
1522
|
+
defineTool({
|
|
1523
|
+
name: "delete_volume",
|
|
1524
|
+
category: "volumes",
|
|
1525
|
+
description: [
|
|
1526
|
+
"Detach and deprovision a volume. The underlying disk is destroyed \u2014 back up any data first. Async: marks the row deleting, the host agent finalises removal once it acks. A periodic reaper retries if the agent was offline at delete time.",
|
|
1527
|
+
"",
|
|
1528
|
+
"When to use: the user explicitly says to remove a volume, or you're cleaning up after retiring a service. Confirm before calling on production data.",
|
|
1529
|
+
"",
|
|
1530
|
+
"Inputs:",
|
|
1531
|
+
" - service_id: publicId of the service.",
|
|
1532
|
+
" - volume_id: publicId of the volume.",
|
|
1533
|
+
"",
|
|
1534
|
+
"Returns: { ok: true }.",
|
|
1535
|
+
"",
|
|
1536
|
+
'Example: delete_volume({ service_id: "svc_abc", volume_id: "vol_xyz" }) \u2192 { ok: true }'
|
|
1537
|
+
].join("\n"),
|
|
1538
|
+
input: {
|
|
1539
|
+
service_id: z10.string().describe("Service publicId."),
|
|
1540
|
+
volume_id: z10.string().describe("Volume publicId.")
|
|
1541
|
+
},
|
|
1542
|
+
handler: async (args2, ctx) => {
|
|
1543
|
+
const teamId = await ctx.resolveTeamId();
|
|
1544
|
+
await ctx.hoststack.volumes.delete(teamId, args2.service_id, args2.volume_id);
|
|
1545
|
+
return respond({
|
|
1546
|
+
summary: `Detached volume ${args2.volume_id} from service ${args2.service_id} (deprovisioning).`,
|
|
1547
|
+
data: { ok: true }
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1409
1552
|
// src/server-factory.ts
|
|
1410
1553
|
var PACKAGE_NAME = "hoststack";
|
|
1411
1554
|
var PACKAGE_VERSION = "0.1.2";
|