@hoststack.dev/mcp 0.13.1 → 0.15.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 +256 -99
- package/dist/hoststack-mcp.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +253 -99
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { HostStack } from "@hoststack.dev/sdk";
|
|
4
4
|
|
|
5
|
+
// src/version.ts
|
|
6
|
+
var MCP_VERSION = true ? "0.15.0" : "0.0.0-dev";
|
|
7
|
+
var USER_AGENT = `hoststack-mcp/${MCP_VERSION}`;
|
|
8
|
+
|
|
5
9
|
// src/api-client.ts
|
|
6
10
|
var ApiClient = class {
|
|
7
11
|
constructor(apiKey, baseUrl) {
|
|
@@ -11,7 +15,8 @@ var ApiClient = class {
|
|
|
11
15
|
get headers() {
|
|
12
16
|
return {
|
|
13
17
|
Authorization: `Bearer ${this.apiKey}`,
|
|
14
|
-
"Content-Type": "application/json"
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
"User-Agent": USER_AGENT
|
|
15
20
|
};
|
|
16
21
|
}
|
|
17
22
|
async get(path, params) {
|
|
@@ -1054,10 +1059,20 @@ defineTool({
|
|
|
1054
1059
|
|
|
1055
1060
|
// src/tools/dns-records.ts
|
|
1056
1061
|
import { z as z7 } from "zod";
|
|
1057
|
-
var DNS_RECORD_TYPES = [
|
|
1058
|
-
|
|
1062
|
+
var DNS_RECORD_TYPES = [
|
|
1063
|
+
"A",
|
|
1064
|
+
"AAAA",
|
|
1065
|
+
"CNAME",
|
|
1066
|
+
"MX",
|
|
1067
|
+
"TXT",
|
|
1068
|
+
"NS",
|
|
1069
|
+
"SRV",
|
|
1070
|
+
"CAA",
|
|
1071
|
+
"ALIAS"
|
|
1072
|
+
];
|
|
1073
|
+
async function resolveZonePublicId(hoststack, teamId, input) {
|
|
1059
1074
|
if (input.zone_id) {
|
|
1060
|
-
const { zones: zones2 } = await
|
|
1075
|
+
const { zones: zones2 } = await hoststack.dns.listZones(teamId);
|
|
1061
1076
|
const match = zones2.find((z15) => z15.publicId === input.zone_id);
|
|
1062
1077
|
if (!match) {
|
|
1063
1078
|
throw new Error(`Zone ${input.zone_id} not found on this team.`);
|
|
@@ -1067,7 +1082,7 @@ async function resolveZonePublicId(api, teamId, input) {
|
|
|
1067
1082
|
if (!input.domain) {
|
|
1068
1083
|
throw new Error("Provide either zone_id or domain.");
|
|
1069
1084
|
}
|
|
1070
|
-
const { zones } = await
|
|
1085
|
+
const { zones } = await hoststack.dns.listZones(teamId);
|
|
1071
1086
|
const fqdn = input.domain.toLowerCase().replace(/\.$/, "");
|
|
1072
1087
|
const labels = fqdn.split(".");
|
|
1073
1088
|
for (let i = 0; i < labels.length - 1; i++) {
|
|
@@ -1096,7 +1111,7 @@ defineTool({
|
|
|
1096
1111
|
input: {},
|
|
1097
1112
|
handler: async (_args, ctx) => {
|
|
1098
1113
|
const teamId = await ctx.resolveTeamId();
|
|
1099
|
-
const response = await ctx.
|
|
1114
|
+
const response = await ctx.hoststack.dns.listZones(teamId);
|
|
1100
1115
|
const items = Array.isArray(response.zones) ? response.zones.map(shape) : [];
|
|
1101
1116
|
const summary = items.length === 0 ? "No DNS zones hosted on this team. Create one in the dashboard (Domains \u2192 DNS)." : `Found ${items.length} hosted DNS zone${items.length === 1 ? "" : "s"}.`;
|
|
1102
1117
|
return respond({ summary, data: { items } });
|
|
@@ -1125,10 +1140,8 @@ defineTool({
|
|
|
1125
1140
|
const zoneInput = {};
|
|
1126
1141
|
if (args.zone_id !== void 0) zoneInput.zone_id = args.zone_id;
|
|
1127
1142
|
if (args.domain !== void 0) zoneInput.domain = args.domain;
|
|
1128
|
-
const zone = await resolveZonePublicId(ctx.
|
|
1129
|
-
const response = await ctx.
|
|
1130
|
-
`/api/dns-zones/${teamId}/${zone.publicId}/records`
|
|
1131
|
-
);
|
|
1143
|
+
const zone = await resolveZonePublicId(ctx.hoststack, teamId, zoneInput);
|
|
1144
|
+
const response = await ctx.hoststack.dns.listRecords(teamId, zone.publicId);
|
|
1132
1145
|
const items = Array.isArray(response.records) ? response.records.map(shape) : [];
|
|
1133
1146
|
const summary = items.length === 0 ? `Zone ${zone.domainName} has no records yet.` : `Returned ${items.length} record${items.length === 1 ? "" : "s"} for ${zone.domainName}.`;
|
|
1134
1147
|
return respond({
|
|
@@ -1157,11 +1170,9 @@ defineTool({
|
|
|
1157
1170
|
},
|
|
1158
1171
|
handler: async (args, ctx) => {
|
|
1159
1172
|
const teamId = await ctx.resolveTeamId();
|
|
1160
|
-
const { zones } = await ctx.
|
|
1173
|
+
const { zones } = await ctx.hoststack.dns.listZones(teamId);
|
|
1161
1174
|
for (const zone of zones) {
|
|
1162
|
-
const { records } = await ctx.
|
|
1163
|
-
`/api/dns-zones/${teamId}/${zone.publicId}/records`
|
|
1164
|
-
);
|
|
1175
|
+
const { records } = await ctx.hoststack.dns.listRecords(teamId, zone.publicId);
|
|
1165
1176
|
const match = records.find((r) => r.publicId === args.record_id);
|
|
1166
1177
|
if (match) {
|
|
1167
1178
|
return respond({
|
|
@@ -1217,7 +1228,7 @@ defineTool({
|
|
|
1217
1228
|
const zoneInput = {};
|
|
1218
1229
|
if (args.zone_id !== void 0) zoneInput.zone_id = args.zone_id;
|
|
1219
1230
|
if (args.domain !== void 0) zoneInput.domain = args.domain;
|
|
1220
|
-
const zone = await resolveZonePublicId(ctx.
|
|
1231
|
+
const zone = await resolveZonePublicId(ctx.hoststack, teamId, zoneInput);
|
|
1221
1232
|
const body = {
|
|
1222
1233
|
type: args.type,
|
|
1223
1234
|
name: args.name,
|
|
@@ -1225,10 +1236,7 @@ defineTool({
|
|
|
1225
1236
|
};
|
|
1226
1237
|
if (args.ttl !== void 0) body.ttl = args.ttl;
|
|
1227
1238
|
if (args.priority !== void 0) body.priority = args.priority;
|
|
1228
|
-
const response = await ctx.
|
|
1229
|
-
`/api/dns-zones/${teamId}/${zone.publicId}/records`,
|
|
1230
|
-
body
|
|
1231
|
-
);
|
|
1239
|
+
const response = await ctx.hoststack.dns.createRecord(teamId, zone.publicId, body);
|
|
1232
1240
|
return respond({
|
|
1233
1241
|
summary: `Created ${args.type} ${args.name} on ${zone.domainName}.`,
|
|
1234
1242
|
data: {
|
|
@@ -1268,7 +1276,7 @@ defineTool({
|
|
|
1268
1276
|
return respondError(`Priority is required for ${args.type} records (0\u201365535).`);
|
|
1269
1277
|
}
|
|
1270
1278
|
const teamId = await ctx.resolveTeamId();
|
|
1271
|
-
const target = await findRecordZone(ctx.
|
|
1279
|
+
const target = await findRecordZone(ctx.hoststack, teamId, args.record_id);
|
|
1272
1280
|
if (!target) {
|
|
1273
1281
|
return respondError(
|
|
1274
1282
|
`Record ${args.record_id} not found on any zone owned by this team.`
|
|
@@ -1281,8 +1289,10 @@ defineTool({
|
|
|
1281
1289
|
};
|
|
1282
1290
|
if (args.ttl !== void 0) body.ttl = args.ttl;
|
|
1283
1291
|
if (args.priority !== void 0) body.priority = args.priority;
|
|
1284
|
-
const response = await ctx.
|
|
1285
|
-
|
|
1292
|
+
const response = await ctx.hoststack.dns.updateRecord(
|
|
1293
|
+
teamId,
|
|
1294
|
+
target.zone.publicId,
|
|
1295
|
+
args.record_id,
|
|
1286
1296
|
body
|
|
1287
1297
|
);
|
|
1288
1298
|
return respond({
|
|
@@ -1317,27 +1327,23 @@ defineTool({
|
|
|
1317
1327
|
},
|
|
1318
1328
|
handler: async (args, ctx) => {
|
|
1319
1329
|
const teamId = await ctx.resolveTeamId();
|
|
1320
|
-
const target = await findRecordZone(ctx.
|
|
1330
|
+
const target = await findRecordZone(ctx.hoststack, teamId, args.record_id);
|
|
1321
1331
|
if (!target) {
|
|
1322
1332
|
return respondError(
|
|
1323
1333
|
`Record ${args.record_id} not found on any zone owned by this team.`
|
|
1324
1334
|
);
|
|
1325
1335
|
}
|
|
1326
|
-
await ctx.
|
|
1327
|
-
`/api/dns-zones/${teamId}/${target.zone.publicId}/records/${args.record_id}`
|
|
1328
|
-
);
|
|
1336
|
+
await ctx.hoststack.dns.deleteRecord(teamId, target.zone.publicId, args.record_id);
|
|
1329
1337
|
return respond({
|
|
1330
1338
|
summary: `Deleted record ${args.record_id} from ${target.zone.domainName}.`,
|
|
1331
1339
|
data: { ok: true }
|
|
1332
1340
|
});
|
|
1333
1341
|
}
|
|
1334
1342
|
});
|
|
1335
|
-
async function findRecordZone(
|
|
1336
|
-
const { zones } = await
|
|
1343
|
+
async function findRecordZone(hoststack, teamId, recordPublicId) {
|
|
1344
|
+
const { zones } = await hoststack.dns.listZones(teamId);
|
|
1337
1345
|
for (const zone of zones) {
|
|
1338
|
-
const { records } = await
|
|
1339
|
-
`/api/dns-zones/${teamId}/${zone.publicId}/records`
|
|
1340
|
-
);
|
|
1346
|
+
const { records } = await hoststack.dns.listRecords(teamId, zone.publicId);
|
|
1341
1347
|
const match = records.find((r) => r.publicId === recordPublicId);
|
|
1342
1348
|
if (match) return { zone, record: match };
|
|
1343
1349
|
}
|
|
@@ -1497,6 +1503,7 @@ defineTool({
|
|
|
1497
1503
|
' - key: env-var name (e.g. "DATABASE_URL").',
|
|
1498
1504
|
" - value: new value (will be encrypted at rest if is_secret=true).",
|
|
1499
1505
|
" - is_secret (optional): true marks the value as secret (masked on read). On create, defaults to true for safety. On update, omitting it leaves the existing flag untouched \u2014 pass it explicitly only when you want to change classification.",
|
|
1506
|
+
' - target (optional): where the var is injected \u2014 "build", "runtime", or "both". On create, defaults to "both". On update, omitting it leaves the existing target untouched.',
|
|
1500
1507
|
"",
|
|
1501
1508
|
'Returns: { envVar: EnvVar, action: "created" | "updated" }.',
|
|
1502
1509
|
"",
|
|
@@ -1506,7 +1513,8 @@ defineTool({
|
|
|
1506
1513
|
service_id: z9.string().describe("Service publicId."),
|
|
1507
1514
|
key: z9.string().min(1).max(128).describe("Env-var key."),
|
|
1508
1515
|
value: z9.string().describe("New value."),
|
|
1509
|
-
is_secret: z9.boolean().optional().describe("Mark as secret (encrypted, masked on read). Default true.")
|
|
1516
|
+
is_secret: z9.boolean().optional().describe("Mark as secret (encrypted, masked on read). Default true."),
|
|
1517
|
+
target: z9.enum(["build", "runtime", "both"]).optional().describe("Injection target: build, runtime, or both. Default both.")
|
|
1510
1518
|
},
|
|
1511
1519
|
handler: async (args, ctx) => {
|
|
1512
1520
|
const teamId = await ctx.resolveTeamId();
|
|
@@ -1515,6 +1523,7 @@ defineTool({
|
|
|
1515
1523
|
if (match) {
|
|
1516
1524
|
const updatePayload = { value: args.value };
|
|
1517
1525
|
if (args.is_secret !== void 0) updatePayload.isSecret = args.is_secret;
|
|
1526
|
+
if (args.target !== void 0) updatePayload.target = args.target;
|
|
1518
1527
|
const response2 = await ctx.hoststack.envVars.update(
|
|
1519
1528
|
teamId,
|
|
1520
1529
|
args.service_id,
|
|
@@ -1530,7 +1539,8 @@ defineTool({
|
|
|
1530
1539
|
const response = await ctx.hoststack.envVars.create(teamId, args.service_id, {
|
|
1531
1540
|
key: args.key,
|
|
1532
1541
|
value: args.value,
|
|
1533
|
-
isSecret: args.is_secret ?? true
|
|
1542
|
+
isSecret: args.is_secret ?? true,
|
|
1543
|
+
...args.target !== void 0 ? { target: args.target } : {}
|
|
1534
1544
|
});
|
|
1535
1545
|
const data = {
|
|
1536
1546
|
envVar: shapeEnvVar(response.envVar),
|
|
@@ -1586,7 +1596,7 @@ defineTool({
|
|
|
1586
1596
|
"",
|
|
1587
1597
|
"Inputs:",
|
|
1588
1598
|
" - service_id: publicId of the service.",
|
|
1589
|
-
|
|
1599
|
+
' - env_vars: array of { key, value, is_secret?, target? }. is_secret defaults to FALSE per row (a row is stored in the clear unless you set is_secret:true); target defaults to "both". Note this differs from set_env_var, which defaults a newly-created var to secret.',
|
|
1590
1600
|
"",
|
|
1591
1601
|
"Returns: { ok: true }. Re-list with list_env_vars to confirm the new state.",
|
|
1592
1602
|
"",
|
|
@@ -1598,7 +1608,8 @@ defineTool({
|
|
|
1598
1608
|
z9.object({
|
|
1599
1609
|
key: z9.string().min(1).max(128),
|
|
1600
1610
|
value: z9.string(),
|
|
1601
|
-
is_secret: z9.boolean().optional()
|
|
1611
|
+
is_secret: z9.boolean().optional(),
|
|
1612
|
+
target: z9.enum(["build", "runtime", "both"]).optional()
|
|
1602
1613
|
})
|
|
1603
1614
|
).max(500).describe("Array of env-var rows. Hard cap 500.")
|
|
1604
1615
|
},
|
|
@@ -1611,6 +1622,7 @@ defineTool({
|
|
|
1611
1622
|
value: v.value
|
|
1612
1623
|
};
|
|
1613
1624
|
if (v.is_secret !== void 0) row.isSecret = v.is_secret;
|
|
1625
|
+
if (v.target !== void 0) row.target = v.target;
|
|
1614
1626
|
return row;
|
|
1615
1627
|
})
|
|
1616
1628
|
};
|
|
@@ -1632,6 +1644,10 @@ defineTool({
|
|
|
1632
1644
|
"",
|
|
1633
1645
|
"When to use: the user wants to see what envs exist before creating a service in one or promoting a deploy. Every project has at least Production.",
|
|
1634
1646
|
"",
|
|
1647
|
+
// §2.2: these are project deployment environments, not cloud Dev Boxes — point agents at the
|
|
1648
|
+
// right surface so the two "dev" concepts stay distinct (bidirectional cross-reference).
|
|
1649
|
+
"NOTE \u2014 these are project deployment environments (Production / Staging / Preview), NOT agentic cloud Dev Boxes. To list Dev Boxes use list_dev_environments; to create one use create_dev_environment / create_standalone_dev_environment / spin_up_dev_environment.",
|
|
1650
|
+
"",
|
|
1635
1651
|
"Inputs:",
|
|
1636
1652
|
' - project_id: project publicId (e.g. "prj_abc123").',
|
|
1637
1653
|
"",
|
|
@@ -1658,10 +1674,15 @@ defineTool({
|
|
|
1658
1674
|
"",
|
|
1659
1675
|
"When to use: the user wants to add an env so they can run a sibling service alongside production for testing or staging before release.",
|
|
1660
1676
|
"",
|
|
1677
|
+
// §2.1/§2.2: steer callers away from the agentic cloud Dev Box tools (which live in
|
|
1678
|
+
// services.ts) — an environment is a deployment target, NOT a Dev Box. Bidirectional with
|
|
1679
|
+
// the cross-references those dev-box tools point back here.
|
|
1680
|
+
'NOTE \u2014 this is NOT a cloud Dev Box. An environment is a project deployment target (Production / Staging / Preview); type="development" just gives services a -dev hostname suffix. For an agentic cloud Dev Box (a terminal you drive coding agents in) use create_dev_environment / create_standalone_dev_environment / spin_up_dev_environment instead.',
|
|
1681
|
+
"",
|
|
1661
1682
|
"Inputs:",
|
|
1662
1683
|
" - project_id: project publicId.",
|
|
1663
1684
|
" - name: human-readable name (1\u201364 chars). Shown in the env switcher.",
|
|
1664
|
-
' - type: "production" | "staging" | "development" | "preview". Determines the hostname suffix on services in this env (production stays clean; others get -staging / -dev / -preview).',
|
|
1685
|
+
' - type: "production" | "staging" | "development" | "preview". Determines the hostname suffix on services in this env (production stays clean; others get -staging / -dev / -preview). type="development" creates a PROJECT deployment environment (a -dev hostname suffix), NOT an agentic Dev Box \u2014 for a cloud dev box use create_dev_environment / create_standalone_dev_environment / spin_up_dev_environment.',
|
|
1665
1686
|
" - is_protected (optional): require admin role for destructive actions in this env. Default false.",
|
|
1666
1687
|
"",
|
|
1667
1688
|
"Returns: { environment: Environment }.",
|
|
@@ -1671,7 +1692,10 @@ defineTool({
|
|
|
1671
1692
|
input: {
|
|
1672
1693
|
project_id: z10.string().describe("Project publicId."),
|
|
1673
1694
|
name: z10.string().min(1).max(64).describe("Environment name (1\u201364 chars)."),
|
|
1674
|
-
type: z10.enum(["production", "staging", "development", "preview"]).describe(
|
|
1695
|
+
type: z10.enum(["production", "staging", "development", "preview"]).describe(
|
|
1696
|
+
// §2.1: "development" here is a project deployment env, NOT a cloud Dev Box.
|
|
1697
|
+
'Environment type \u2014 drives the hostname suffix. type="development" creates a PROJECT deployment environment (a -dev hostname suffix), NOT an agentic Dev Box \u2014 for a cloud dev box use create_dev_environment / create_standalone_dev_environment / spin_up_dev_environment.'
|
|
1698
|
+
),
|
|
1675
1699
|
is_protected: z10.boolean().optional().describe("Require admin role for destructive actions. Default false.")
|
|
1676
1700
|
},
|
|
1677
1701
|
handler: async (args, ctx) => {
|
|
@@ -1763,6 +1787,14 @@ defineTool({
|
|
|
1763
1787
|
});
|
|
1764
1788
|
|
|
1765
1789
|
// src/tools/meta.ts
|
|
1790
|
+
var DEV_ENV_TOOL_NAMES = [
|
|
1791
|
+
"create_dev_environment",
|
|
1792
|
+
"create_standalone_dev_environment",
|
|
1793
|
+
"spin_up_dev_environment",
|
|
1794
|
+
"list_dev_environments",
|
|
1795
|
+
"resize_dev_environment",
|
|
1796
|
+
"delete_dev_environment"
|
|
1797
|
+
];
|
|
1766
1798
|
defineTool({
|
|
1767
1799
|
name: "get_me",
|
|
1768
1800
|
category: "meta",
|
|
@@ -1787,6 +1819,36 @@ defineTool({
|
|
|
1787
1819
|
return respond({ summary: `Authenticated as ${userEmail}${teamLabel}.`, data });
|
|
1788
1820
|
}
|
|
1789
1821
|
});
|
|
1822
|
+
defineTool({
|
|
1823
|
+
name: "describe_mcp",
|
|
1824
|
+
category: "meta",
|
|
1825
|
+
description: [
|
|
1826
|
+
"Report this MCP build: its package version, total tool count, and the list of Dev Box tools it exposes. Read-only \u2014 touches no account data.",
|
|
1827
|
+
"",
|
|
1828
|
+
"When to use: at the start of a session to confirm the live MCP build, OR when a Dev Box tool you expect (create_dev_environment, create_standalone_dev_environment, spin_up_dev_environment, list_dev_environments, resize_dev_environment, delete_dev_environment) seems to be missing. A STALE npx/npm cache can silently present an old tool set with no other signal \u2014 call this and check `devEnvToolsPresent`; if it is false, the cached build predates the Dev Box tools and the user should refresh it (e.g. `npx --yes @hoststack.dev/mcp@latest` / clear the npx cache).",
|
|
1829
|
+
"",
|
|
1830
|
+
'Note on terminology: a "Dev Box" (a.k.a. Cloud Dev Box) is an agentic cloud box you code inside (create_dev_environment & friends). It is NOT a project "environment" (create_environment), which is a deployment target like Production / Staging / Preview. This tool only reports the Dev Box tool set.',
|
|
1831
|
+
"",
|
|
1832
|
+
"Inputs: none.",
|
|
1833
|
+
"",
|
|
1834
|
+
"Returns: { version, toolCount, devEnvTools: string[], devEnvToolsPresent: boolean } \u2014 version is the @hoststack.dev/mcp package version, toolCount is every registered tool, devEnvTools is the expected Dev Box tool names, and devEnvToolsPresent is true only if all of them are actually registered in this build.",
|
|
1835
|
+
"",
|
|
1836
|
+
'Example: describe_mcp() \u2192 { version: "0.14.0", toolCount: 30, devEnvTools: ["create_dev_environment", \u2026], devEnvToolsPresent: true }'
|
|
1837
|
+
].join("\n"),
|
|
1838
|
+
input: {},
|
|
1839
|
+
handler: async (_args, _ctx) => {
|
|
1840
|
+
const registered = new Set(listToolDefinitions().map((t) => t.name));
|
|
1841
|
+
const devEnvToolsPresent = DEV_ENV_TOOL_NAMES.every((name) => registered.has(name));
|
|
1842
|
+
const data = {
|
|
1843
|
+
version: MCP_VERSION,
|
|
1844
|
+
toolCount: registered.size,
|
|
1845
|
+
devEnvTools: [...DEV_ENV_TOOL_NAMES],
|
|
1846
|
+
devEnvToolsPresent
|
|
1847
|
+
};
|
|
1848
|
+
const summary = devEnvToolsPresent ? `hoststack MCP v${MCP_VERSION} \u2014 ${data.toolCount} tools, all ${DEV_ENV_TOOL_NAMES.length} Dev Box tools present.` : `hoststack MCP v${MCP_VERSION} \u2014 ${data.toolCount} tools, but Dev Box tools are MISSING (stale build \u2014 refresh the npx/npm cache).`;
|
|
1849
|
+
return respond({ summary, data });
|
|
1850
|
+
}
|
|
1851
|
+
});
|
|
1790
1852
|
|
|
1791
1853
|
// src/tools/notifications.ts
|
|
1792
1854
|
import { z as z11 } from "zod";
|
|
@@ -2111,11 +2173,19 @@ var SERVICE_PLANS = [
|
|
|
2111
2173
|
"pico",
|
|
2112
2174
|
"nano",
|
|
2113
2175
|
"micro",
|
|
2114
|
-
"
|
|
2176
|
+
"small",
|
|
2115
2177
|
"standard",
|
|
2178
|
+
"large",
|
|
2179
|
+
"xlarge",
|
|
2116
2180
|
"pro_standard",
|
|
2117
2181
|
"pro_large"
|
|
2118
2182
|
];
|
|
2183
|
+
var DEV_ENV_MIN_SIZE = "standard";
|
|
2184
|
+
function coerceDevEnvSize(value) {
|
|
2185
|
+
const minIdx = SERVICE_PLANS.indexOf(DEV_ENV_MIN_SIZE);
|
|
2186
|
+
const idx = value ? SERVICE_PLANS.indexOf(value) : -1;
|
|
2187
|
+
return idx >= minIdx ? value : DEV_ENV_MIN_SIZE;
|
|
2188
|
+
}
|
|
2119
2189
|
defineTool({
|
|
2120
2190
|
name: "list_services",
|
|
2121
2191
|
category: "services",
|
|
@@ -2124,11 +2194,14 @@ defineTool({
|
|
|
2124
2194
|
"",
|
|
2125
2195
|
"When to use: the agent needs to find a service by name, check what is deployed, or pick a target for a follow-up tool (logs, deploys, env vars). This is the canonical way to resolve a publicId from a human-friendly name.",
|
|
2126
2196
|
"",
|
|
2197
|
+
"Note: agentic Dev Boxes (cloud dev environments) are EXCLUDED by default. Pass dev_environment:true to include them, or use list_dev_environments for the dedicated, annotated dev-box listing.",
|
|
2198
|
+
"",
|
|
2127
2199
|
"Inputs (all optional):",
|
|
2128
2200
|
' - project_id: narrow to one project (numeric id or publicId "prj_\u2026").',
|
|
2129
2201
|
' - environment_id: narrow to one environment (numeric id or publicId "env_\u2026").',
|
|
2130
2202
|
' - status: "active" | "deploying" | "suspended" | "failed" | "not_deployed".',
|
|
2131
2203
|
' - type: "web_service" | "private_service" | "worker" | "cron_job" | "static_site".',
|
|
2204
|
+
" - dev_environment: include Dev Boxes in the results (excluded by default).",
|
|
2132
2205
|
"",
|
|
2133
2206
|
"Returns: { items: Service[] } \u2014 each service includes id, publicId, name, type, status, projectId, repoUrl, branch, runtime, createdAt.",
|
|
2134
2207
|
"",
|
|
@@ -2138,11 +2211,15 @@ defineTool({
|
|
|
2138
2211
|
project_id: z13.union([z13.number().int().positive(), z13.string()]).optional().describe("Project filter \u2014 numeric id or publicId."),
|
|
2139
2212
|
environment_id: z13.union([z13.number().int().positive(), z13.string()]).optional().describe("Environment filter \u2014 numeric id or publicId."),
|
|
2140
2213
|
status: z13.enum(["active", "deploying", "suspended", "failed", "not_deployed"]).optional().describe("Filter by current runtime status."),
|
|
2141
|
-
type: z13.enum(["web_service", "private_service", "worker", "cron_job", "static_site"]).optional().describe("Filter by service type.")
|
|
2214
|
+
type: z13.enum(["web_service", "private_service", "worker", "cron_job", "static_site"]).optional().describe("Filter by service type."),
|
|
2215
|
+
dev_environment: z13.boolean().optional().describe(
|
|
2216
|
+
"Include agentic Dev Boxes in the results (excluded by default; see list_dev_environments)."
|
|
2217
|
+
)
|
|
2142
2218
|
},
|
|
2143
2219
|
handler: async (args, ctx) => {
|
|
2144
2220
|
const teamId = await ctx.resolveTeamId();
|
|
2145
2221
|
const filters = {};
|
|
2222
|
+
if (args.dev_environment) filters.devEnvironment = true;
|
|
2146
2223
|
if (args.project_id !== void 0) {
|
|
2147
2224
|
const resolved = await ctx.hoststack.resolveId(args.project_id, {
|
|
2148
2225
|
kind: "project",
|
|
@@ -2165,7 +2242,8 @@ defineTool({
|
|
|
2165
2242
|
args.project_id !== void 0 ? `project=${args.project_id}` : null,
|
|
2166
2243
|
args.environment_id !== void 0 ? `env=${args.environment_id}` : null,
|
|
2167
2244
|
args.status ? `status=${args.status}` : null,
|
|
2168
|
-
args.type ? `type=${args.type}` : null
|
|
2245
|
+
args.type ? `type=${args.type}` : null,
|
|
2246
|
+
args.dev_environment ? "devBoxes=included" : null
|
|
2169
2247
|
].filter(Boolean).join(", ");
|
|
2170
2248
|
const summary = data.items.length === 0 ? filterDesc ? `No services match the given filters (${filterDesc}).` : "No services yet \u2014 create one with create_service (or create_dev_environment for an AI dev box), or via the dashboard." : `Found ${data.items.length} service${data.items.length === 1 ? "" : "s"}${filterDesc ? ` matching ${filterDesc}` : ""}.`;
|
|
2171
2249
|
return respond({ summary, data });
|
|
@@ -2256,14 +2334,16 @@ defineTool({
|
|
|
2256
2334
|
name: "create_dev_environment",
|
|
2257
2335
|
category: "services",
|
|
2258
2336
|
description: [
|
|
2259
|
-
|
|
2337
|
+
'Spin up an AI Dev Box in one call: a cloud "Dev Box" (an agentic container from the dev-env image \u2014 Claude Code + Codex + OpenCode + MCPs preinstalled) inside a project, with a persistent /workspace volume and the MCP API keys set, then fire the first deploy.',
|
|
2338
|
+
"",
|
|
2339
|
+
'This makes a cloud Dev Box, NOT a project deploy environment. A project "environment" (create_environment \u2192 Production / Staging / Preview) is a deployment target; this is a workspace you code in. If you actually want a -dev deploy target, use create_environment instead.',
|
|
2260
2340
|
"",
|
|
2261
|
-
`When to use: the user wants a cloud terminal /
|
|
2341
|
+
`When to use: the user wants a cloud terminal / Dev Box they can drive coding agents in (from a desk or a phone). This mirrors the dashboard's "AI Dev Environment" wizard preset \u2014 create (deploy deferred) \u2192 set keys \u2192 attach /workspace \u2192 deploy, so the first container boots with its volume and keys already in place.`,
|
|
2262
2342
|
"",
|
|
2263
2343
|
"Inputs:",
|
|
2264
2344
|
' - project_id: numeric id or publicId ("prj_\u2026") of the target project.',
|
|
2265
2345
|
' - name (optional): service name (default "dev-environment").',
|
|
2266
|
-
' - plan (optional):
|
|
2346
|
+
' - plan (optional): box size. Defaults to "standard" (2 GB) \u2014 the OOM-safe floor (DEV_ENV_MIN_SIZE); a coding agent + build OOMs below it. A smaller plan is clamped up to "standard".',
|
|
2267
2347
|
" - disk_gb (optional): /workspace volume size in GB (default 10, 1\u2013100).",
|
|
2268
2348
|
" - hoststack_api_key (optional): sets HOSTSTACK_API_KEY so the hoststack MCP works inside the container.",
|
|
2269
2349
|
" - poststack_api_key (optional): sets POSTSTACK_API_KEY so the poststack MCP works inside the container.",
|
|
@@ -2277,7 +2357,7 @@ defineTool({
|
|
|
2277
2357
|
project_id: z13.union([z13.number().int().positive(), z13.string()]).describe("Target project \u2014 numeric id or publicId."),
|
|
2278
2358
|
name: z13.string().min(1).max(100).optional().describe('Service name (default "dev-environment").'),
|
|
2279
2359
|
plan: z13.enum(SERVICE_PLANS).optional().describe(
|
|
2280
|
-
'
|
|
2360
|
+
'Box size (default "standard" \u2014 2 GB, the OOM-safe floor; a smaller plan is clamped up to "standard").'
|
|
2281
2361
|
),
|
|
2282
2362
|
disk_gb: z13.number().int().min(1).max(100).optional().describe("/workspace volume size in GB (default 10)."),
|
|
2283
2363
|
hoststack_api_key: z13.string().optional().describe("Value for HOSTSTACK_API_KEY (enables the hoststack MCP in-container)."),
|
|
@@ -2295,68 +2375,94 @@ defineTool({
|
|
|
2295
2375
|
});
|
|
2296
2376
|
const name = args.name ?? "dev-environment";
|
|
2297
2377
|
const sizeGb = args.disk_gb ?? DEV_ENV_VOLUME.sizeGb;
|
|
2378
|
+
const plan = coerceDevEnvSize(args.plan);
|
|
2298
2379
|
const createInput = {
|
|
2299
2380
|
name,
|
|
2300
2381
|
type: "private_service",
|
|
2301
2382
|
projectId,
|
|
2302
2383
|
dockerImage: DEV_ENV_IMAGE,
|
|
2303
|
-
autoDeploy: false
|
|
2384
|
+
autoDeploy: false,
|
|
2385
|
+
plan
|
|
2304
2386
|
};
|
|
2305
|
-
if (args.plan !== void 0) createInput.plan = args.plan;
|
|
2306
2387
|
const created = await ctx.hoststack.services.create(teamId, createInput);
|
|
2307
2388
|
const service = created.service;
|
|
2308
|
-
const
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
if (args.repo_url) {
|
|
2322
|
-
envVars.push({
|
|
2323
|
-
key: "HOSTSTACK_DEVENV_REPO_URL",
|
|
2324
|
-
value: args.repo_url,
|
|
2325
|
-
isSecret: false
|
|
2326
|
-
});
|
|
2327
|
-
if (args.branch)
|
|
2389
|
+
const rollback = async () => {
|
|
2390
|
+
try {
|
|
2391
|
+
await ctx.hoststack.services.tearDownDevEnvironment(teamId, service.id);
|
|
2392
|
+
} catch {
|
|
2393
|
+
try {
|
|
2394
|
+
await ctx.hoststack.services.delete(teamId, service.id);
|
|
2395
|
+
} catch {
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
};
|
|
2399
|
+
try {
|
|
2400
|
+
const envVars = [];
|
|
2401
|
+
if (args.hoststack_api_key)
|
|
2328
2402
|
envVars.push({
|
|
2329
|
-
key: "
|
|
2330
|
-
value: args.
|
|
2403
|
+
key: "HOSTSTACK_API_KEY",
|
|
2404
|
+
value: args.hoststack_api_key,
|
|
2405
|
+
isSecret: true
|
|
2406
|
+
});
|
|
2407
|
+
if (args.poststack_api_key)
|
|
2408
|
+
envVars.push({
|
|
2409
|
+
key: "POSTSTACK_API_KEY",
|
|
2410
|
+
value: args.poststack_api_key,
|
|
2411
|
+
isSecret: true
|
|
2412
|
+
});
|
|
2413
|
+
if (args.repo_url) {
|
|
2414
|
+
envVars.push({
|
|
2415
|
+
key: "HOSTSTACK_DEVENV_REPO_URL",
|
|
2416
|
+
value: args.repo_url,
|
|
2331
2417
|
isSecret: false
|
|
2332
2418
|
});
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2419
|
+
if (args.branch)
|
|
2420
|
+
envVars.push({
|
|
2421
|
+
key: "HOSTSTACK_DEVENV_BRANCH",
|
|
2422
|
+
value: args.branch,
|
|
2423
|
+
isSecret: false
|
|
2424
|
+
});
|
|
2425
|
+
}
|
|
2426
|
+
if (envVars.length > 0) {
|
|
2427
|
+
await ctx.hoststack.envVars.bulkSet(teamId, service.id, { vars: envVars });
|
|
2428
|
+
}
|
|
2429
|
+
try {
|
|
2430
|
+
await ctx.hoststack.volumes.create(teamId, service.id, {
|
|
2431
|
+
name: DEV_ENV_VOLUME.name,
|
|
2432
|
+
mountPath: DEV_ENV_VOLUME.mountPath,
|
|
2433
|
+
sizeGb
|
|
2434
|
+
});
|
|
2435
|
+
} catch (error) {
|
|
2436
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2437
|
+
await rollback();
|
|
2438
|
+
return respondError(
|
|
2439
|
+
`Failed to attach the /workspace volume for Dev Box "${name}" \u2014 aborted and rolled back rather than deploy a box that would lose all work on recreate. Volume error: ${reason}`,
|
|
2440
|
+
{ volumeError: reason, rolledBack: true }
|
|
2441
|
+
);
|
|
2442
|
+
}
|
|
2443
|
+
const deploy = await ctx.hoststack.deploys.trigger(teamId, service.id);
|
|
2444
|
+
const deployId = deploy.deploy?.id ?? null;
|
|
2445
|
+
return respond({
|
|
2446
|
+
summary: `Created AI Dev Box "${name}" (${service.publicId}) with a /workspace volume \u2014 deploying. Open it in the dashboard's Dev Boxes section (or the service's Terminal tab) once running.`,
|
|
2447
|
+
data: { service: shapeService(service), volumeAttached: true, deployId }
|
|
2343
2448
|
});
|
|
2344
|
-
|
|
2345
|
-
|
|
2449
|
+
} catch (error) {
|
|
2450
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2451
|
+
await rollback();
|
|
2452
|
+
return respondError(
|
|
2453
|
+
`Failed to provision Dev Box "${name}" \u2014 rolled back the partially-created box. Error: ${reason}`,
|
|
2454
|
+
{ rolledBack: true }
|
|
2455
|
+
);
|
|
2346
2456
|
}
|
|
2347
|
-
const deploy = await ctx.hoststack.deploys.trigger(teamId, service.id);
|
|
2348
|
-
const deployId = deploy.deploy?.id ?? null;
|
|
2349
|
-
return respond({
|
|
2350
|
-
summary: `Created AI dev environment "${name}" (${service.publicId})${volumeAttached ? " with a /workspace volume" : ""} \u2014 deploying. Open it in the dashboard's Development section (or the service's Terminal tab) once running.`,
|
|
2351
|
-
data: { service: shapeService(service), volumeAttached, deployId }
|
|
2352
|
-
});
|
|
2353
2457
|
}
|
|
2354
2458
|
});
|
|
2355
2459
|
defineTool({
|
|
2356
2460
|
name: "spin_up_dev_environment",
|
|
2357
2461
|
category: "services",
|
|
2358
2462
|
description: [
|
|
2359
|
-
"Spin up an agentic
|
|
2463
|
+
"Spin up an agentic Dev Box FROM an existing service so you can reproduce a bug, fix it, view it, and ship it. Creates a cloud Dev Box (Claude/Codex/OpenCode + MCPs) that RUNS a clone of the app: the repo is auto-cloned into /workspace, the service env-vars are copied, and its linked database is cloned (so the box never touches the prod DB). The box gets an unguessable public dev URL and seamless `git push`.",
|
|
2464
|
+
"",
|
|
2465
|
+
"This makes a cloud Dev Box, NOT a project deploy environment \u2014 for a -dev/-staging deploy target use create_environment instead.",
|
|
2360
2466
|
"",
|
|
2361
2467
|
'When to use: "spin up a dev environment for <service>", "I found a bug on <service>, give me a box to fix it". Distinct from create_dev_environment, which makes a BARE box not tied to any app.',
|
|
2362
2468
|
"",
|
|
@@ -2399,7 +2505,9 @@ defineTool({
|
|
|
2399
2505
|
name: "delete_dev_environment",
|
|
2400
2506
|
category: "services",
|
|
2401
2507
|
description: [
|
|
2402
|
-
"Tear down an agentic
|
|
2508
|
+
"Tear down an agentic Dev Box in one call: removes the cloud Dev Box AND cascade-deletes its cloned database, its /workspace volume, and the auto-created `development` environment if it is now empty.",
|
|
2509
|
+
"",
|
|
2510
|
+
"This deletes a cloud Dev Box, NOT a project deploy environment \u2014 to remove a -dev/-staging deploy target use delete_environment instead.",
|
|
2403
2511
|
"",
|
|
2404
2512
|
'When to use: "delete / tear down the dev environment", once you have shipped the fix and no longer need the box.',
|
|
2405
2513
|
"",
|
|
@@ -2430,15 +2538,17 @@ defineTool({
|
|
|
2430
2538
|
name: "resize_dev_environment",
|
|
2431
2539
|
category: "services",
|
|
2432
2540
|
description: [
|
|
2433
|
-
"Resize a
|
|
2541
|
+
"Resize a cloud Dev Box (or any service) to a different size tier \u2014 the supported way to give it more memory/CPU/disk headroom (e.g. when ESLint/tsc OOMs).",
|
|
2434
2542
|
"",
|
|
2435
|
-
"
|
|
2543
|
+
"This resizes a cloud Dev Box, NOT a project deploy environment.",
|
|
2544
|
+
"",
|
|
2545
|
+
"When to use: a Dev Box OOM-killed (see exitReason/recommendedSize from list_dev_environments), or you just want more headroom. This changes the SIZE TIER \u2014 unlike per-config memory/CPU overrides, which are clamped to the current tier and so cannot grow a box past it.",
|
|
2436
2546
|
"",
|
|
2437
2547
|
"How it applies: the new tier's memory + CPU take effect LIVE on the running container (no recreate, no dropped shell sessions); a larger disk takes effect on the next recreate (suspend\u2192resume). Dev boxes are floored to the OOM-safe minimum size server-side.",
|
|
2438
2548
|
"",
|
|
2439
2549
|
"Inputs:",
|
|
2440
2550
|
" - service_id: the box to resize \u2014 numeric id or publicId.",
|
|
2441
|
-
' - size: target tier (e.g. "standard", "large", "xlarge").',
|
|
2551
|
+
' - size: target tier \u2014 one of the service catalog sizes (e.g. "standard", "large", "xlarge").',
|
|
2442
2552
|
"",
|
|
2443
2553
|
"Returns: { service } with the new plan.",
|
|
2444
2554
|
"",
|
|
@@ -2446,7 +2556,9 @@ defineTool({
|
|
|
2446
2556
|
].join("\n"),
|
|
2447
2557
|
input: {
|
|
2448
2558
|
service_id: z13.union([z13.number().int().positive(), z13.string()]).describe("The box to resize \u2014 numeric id or publicId."),
|
|
2449
|
-
size: z13.
|
|
2559
|
+
size: z13.enum(SERVICE_PLANS).describe(
|
|
2560
|
+
'Target size tier (service catalog size, e.g. "standard", "large", "xlarge").'
|
|
2561
|
+
)
|
|
2450
2562
|
},
|
|
2451
2563
|
handler: async (args, ctx) => {
|
|
2452
2564
|
const teamId = await ctx.resolveTeamId();
|
|
@@ -2465,7 +2577,9 @@ defineTool({
|
|
|
2465
2577
|
name: "list_dev_environments",
|
|
2466
2578
|
category: "services",
|
|
2467
2579
|
description: [
|
|
2468
|
-
`List the team's
|
|
2580
|
+
`List the team's cloud Dev Boxes (the dashboard "Dev Boxes" / Development section), each annotated with the companion services attached to it.`,
|
|
2581
|
+
"",
|
|
2582
|
+
"These are cloud Dev Boxes, NOT project deploy environments \u2014 to list a project's deploy targets (Production / Staging / Preview) use list_environments instead.",
|
|
2469
2583
|
"",
|
|
2470
2584
|
`When to use: "show my dev environments", before opening/tearing one down, to find a box's id.`,
|
|
2471
2585
|
"",
|
|
@@ -2492,22 +2606,60 @@ defineTool({
|
|
|
2492
2606
|
});
|
|
2493
2607
|
}
|
|
2494
2608
|
});
|
|
2609
|
+
defineTool({
|
|
2610
|
+
name: "list_templates",
|
|
2611
|
+
category: "services",
|
|
2612
|
+
description: [
|
|
2613
|
+
"List the canonical Dev Box (AI dev environment) preset so an agent can see, without guessing, what a `create_standalone_dev_environment` / `create_dev_environment` actually provisions: the container image, the persistent /workspace volume (mount + default size), the OOM-safe size floor, and the companion database options.",
|
|
2614
|
+
"",
|
|
2615
|
+
"When to use: before creating a Dev Box, to confirm the image, the default /workspace size, the minimum plan, or which companion engines can be attached \u2014 instead of hand-duplicating those constants.",
|
|
2616
|
+
"",
|
|
2617
|
+
'Returns: { templates: [{ id, name, image, volume: { name, mountPath, sizeGb }, minPlan, companions }] }. Today there is one preset ("dev-environment").',
|
|
2618
|
+
"",
|
|
2619
|
+
'Example: list_templates() \u2192 { templates: [{ id: "dev-environment", image: "hoststack/dev-env:latest", volume: { mountPath: "/workspace", sizeGb: 10 }, minPlan: "standard", companions: ["postgres","redis","meilisearch"] }] }'
|
|
2620
|
+
].join("\n"),
|
|
2621
|
+
input: {},
|
|
2622
|
+
handler: async () => {
|
|
2623
|
+
const templates = [
|
|
2624
|
+
{
|
|
2625
|
+
id: "dev-environment",
|
|
2626
|
+
name: "AI Dev Environment",
|
|
2627
|
+
image: DEV_ENV_IMAGE,
|
|
2628
|
+
volume: {
|
|
2629
|
+
name: DEV_ENV_VOLUME.name,
|
|
2630
|
+
mountPath: DEV_ENV_VOLUME.mountPath,
|
|
2631
|
+
sizeGb: DEV_ENV_VOLUME.sizeGb
|
|
2632
|
+
},
|
|
2633
|
+
/** OOM-safe plan floor — a coding agent + build needs at least this. */
|
|
2634
|
+
minPlan: DEV_ENV_MIN_SIZE,
|
|
2635
|
+
/** Companion engines you can attach (fresh + empty), like local `make db-up`. */
|
|
2636
|
+
companions: ["postgres", "redis", "meilisearch"]
|
|
2637
|
+
}
|
|
2638
|
+
];
|
|
2639
|
+
return respond({
|
|
2640
|
+
summary: `${templates.length} Dev Box template${templates.length === 1 ? "" : "s"} available (image ${DEV_ENV_IMAGE}, /workspace ${DEV_ENV_VOLUME.sizeGb} GB, floor "${DEV_ENV_MIN_SIZE}").`,
|
|
2641
|
+
data: { templates }
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
});
|
|
2495
2645
|
defineTool({
|
|
2496
2646
|
name: "create_standalone_dev_environment",
|
|
2497
2647
|
category: "services",
|
|
2498
2648
|
description: [
|
|
2499
|
-
"Create a STANDALONE
|
|
2649
|
+
"Create a STANDALONE Dev Box in one call: a cloud box (Claude Code + Codex + OpenCode + MCPs) on a persistent /workspace, from a connected GitHub repo, an arbitrary clone URL, or blank \u2014 with optional companion Postgres / Redis / Meilisearch wired into its env (mirrors a local `make db-up`). The box lives in the team's hidden Development home, NOT under a project.",
|
|
2650
|
+
"",
|
|
2651
|
+
"This makes a cloud Dev Box, NOT a project deploy environment (create_environment \u2192 Production / Staging / Preview). It is a workspace you code in, not a deployment target.",
|
|
2500
2652
|
"",
|
|
2501
2653
|
'When to use: "create a dev environment for <repo>", "spin me up a cloud dev box with a Postgres". Distinct from spin_up_dev_environment (which clones an EXISTING service) and create_dev_environment (a bare box in a chosen project).',
|
|
2502
2654
|
"",
|
|
2503
2655
|
"Inputs:",
|
|
2504
|
-
" - name: the
|
|
2656
|
+
" - name: the Dev Box name.",
|
|
2505
2657
|
' - source_kind: "github_repo" (clone a connected repo \u2014 needs github_repo_id), "url" (clone any http(s) git URL \u2014 needs clone_url), or "blank" (empty box).',
|
|
2506
2658
|
" - github_repo_id (for github_repo): numeric id of a connected GitHub repo.",
|
|
2507
2659
|
" - clone_url (for url): an http(s) git clone URL.",
|
|
2508
2660
|
" - branch (optional): branch to clone.",
|
|
2509
2661
|
' - databases (optional): companion services to attach \u2014 any of "postgres", "redis", "meilisearch".',
|
|
2510
|
-
' - plan (optional): box size (
|
|
2662
|
+
' - plan (optional): box size. Defaults to "standard" (2 GB) \u2014 the OOM-safe floor (DEV_ENV_MIN_SIZE); a smaller plan is floored up to "standard" server-side.',
|
|
2511
2663
|
" - agent_accounts (optional): bind specific saved agent logins by account id per provider. OMIT to auto-inherit the box owner's default logins (claude/codex/opencode), so `claude` is already authenticated on first boot \u2014 no manual login.",
|
|
2512
2664
|
"",
|
|
2513
2665
|
"Returns: { service, devUrl, deployId } \u2014 deploying. Once live: open the Terminal tab, run `claude`, start the dev server on $PORT, view at https://<devUrl>. Tear down with delete_dev_environment.",
|
|
@@ -2515,13 +2667,15 @@ defineTool({
|
|
|
2515
2667
|
'Example: create_standalone_dev_environment({ name: "app-dev", source_kind: "github_repo", github_repo_id: 42, databases: ["postgres","redis"] })'
|
|
2516
2668
|
].join("\n"),
|
|
2517
2669
|
input: {
|
|
2518
|
-
name: z13.string().min(1).max(100).describe("Dev
|
|
2670
|
+
name: z13.string().min(1).max(100).describe("Dev Box name."),
|
|
2519
2671
|
source_kind: z13.enum(["github_repo", "url", "blank"]).describe("Where the code comes from."),
|
|
2520
2672
|
github_repo_id: z13.number().int().positive().optional().describe('Connected GitHub repo id (required when source_kind="github_repo").'),
|
|
2521
2673
|
clone_url: z13.string().url().optional().describe('http(s) git clone URL (required when source_kind="url").'),
|
|
2522
2674
|
branch: z13.string().min(1).max(255).optional().describe("Branch to clone."),
|
|
2523
2675
|
databases: z13.array(z13.enum(["postgres", "redis", "meilisearch"])).optional().describe("Companion services to attach (fresh + empty)."),
|
|
2524
|
-
plan: z13.enum(SERVICE_PLANS).optional().describe(
|
|
2676
|
+
plan: z13.enum(SERVICE_PLANS).optional().describe(
|
|
2677
|
+
'Box size (default "standard" \u2014 2 GB; a smaller plan is floored to "standard").'
|
|
2678
|
+
),
|
|
2525
2679
|
agent_accounts: z13.array(
|
|
2526
2680
|
z13.object({
|
|
2527
2681
|
provider: z13.enum(["claude", "codex", "opencode"]),
|
|
@@ -3191,7 +3345,7 @@ defineTool({
|
|
|
3191
3345
|
|
|
3192
3346
|
// src/server-factory.ts
|
|
3193
3347
|
var PACKAGE_NAME = "hoststack";
|
|
3194
|
-
var PACKAGE_VERSION =
|
|
3348
|
+
var PACKAGE_VERSION = MCP_VERSION;
|
|
3195
3349
|
function createMcpServer(options) {
|
|
3196
3350
|
const baseUrl = (options.baseUrl ?? "https://hoststack.dev").replace(/\/$/, "");
|
|
3197
3351
|
const hoststack = new HostStack({ apiKey: options.apiKey, baseUrl });
|