@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/hoststack-mcp.js
CHANGED
|
@@ -7,6 +7,10 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
7
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
8
|
import { HostStack } from "@hoststack.dev/sdk";
|
|
9
9
|
|
|
10
|
+
// src/version.ts
|
|
11
|
+
var MCP_VERSION = true ? "0.15.0" : "0.0.0-dev";
|
|
12
|
+
var USER_AGENT = `hoststack-mcp/${MCP_VERSION}`;
|
|
13
|
+
|
|
10
14
|
// src/api-client.ts
|
|
11
15
|
var ApiClient = class {
|
|
12
16
|
constructor(apiKey2, baseUrl2) {
|
|
@@ -16,7 +20,8 @@ var ApiClient = class {
|
|
|
16
20
|
get headers() {
|
|
17
21
|
return {
|
|
18
22
|
Authorization: `Bearer ${this.apiKey}`,
|
|
19
|
-
"Content-Type": "application/json"
|
|
23
|
+
"Content-Type": "application/json",
|
|
24
|
+
"User-Agent": USER_AGENT
|
|
20
25
|
};
|
|
21
26
|
}
|
|
22
27
|
async get(path, params) {
|
|
@@ -134,6 +139,9 @@ function defineTool(def) {
|
|
|
134
139
|
handler: def.handler
|
|
135
140
|
});
|
|
136
141
|
}
|
|
142
|
+
function listToolDefinitions() {
|
|
143
|
+
return tools;
|
|
144
|
+
}
|
|
137
145
|
function attachTools(server2, ctx, sink) {
|
|
138
146
|
const register = server2.tool.bind(server2);
|
|
139
147
|
for (const def of tools) {
|
|
@@ -1050,10 +1058,20 @@ defineTool({
|
|
|
1050
1058
|
|
|
1051
1059
|
// src/tools/dns-records.ts
|
|
1052
1060
|
import { z as z7 } from "zod";
|
|
1053
|
-
var DNS_RECORD_TYPES = [
|
|
1054
|
-
|
|
1061
|
+
var DNS_RECORD_TYPES = [
|
|
1062
|
+
"A",
|
|
1063
|
+
"AAAA",
|
|
1064
|
+
"CNAME",
|
|
1065
|
+
"MX",
|
|
1066
|
+
"TXT",
|
|
1067
|
+
"NS",
|
|
1068
|
+
"SRV",
|
|
1069
|
+
"CAA",
|
|
1070
|
+
"ALIAS"
|
|
1071
|
+
];
|
|
1072
|
+
async function resolveZonePublicId(hoststack, teamId, input) {
|
|
1055
1073
|
if (input.zone_id) {
|
|
1056
|
-
const { zones: zones2 } = await
|
|
1074
|
+
const { zones: zones2 } = await hoststack.dns.listZones(teamId);
|
|
1057
1075
|
const match = zones2.find((z15) => z15.publicId === input.zone_id);
|
|
1058
1076
|
if (!match) {
|
|
1059
1077
|
throw new Error(`Zone ${input.zone_id} not found on this team.`);
|
|
@@ -1063,7 +1081,7 @@ async function resolveZonePublicId(api, teamId, input) {
|
|
|
1063
1081
|
if (!input.domain) {
|
|
1064
1082
|
throw new Error("Provide either zone_id or domain.");
|
|
1065
1083
|
}
|
|
1066
|
-
const { zones } = await
|
|
1084
|
+
const { zones } = await hoststack.dns.listZones(teamId);
|
|
1067
1085
|
const fqdn = input.domain.toLowerCase().replace(/\.$/, "");
|
|
1068
1086
|
const labels = fqdn.split(".");
|
|
1069
1087
|
for (let i = 0; i < labels.length - 1; i++) {
|
|
@@ -1092,7 +1110,7 @@ defineTool({
|
|
|
1092
1110
|
input: {},
|
|
1093
1111
|
handler: async (_args, ctx) => {
|
|
1094
1112
|
const teamId = await ctx.resolveTeamId();
|
|
1095
|
-
const response = await ctx.
|
|
1113
|
+
const response = await ctx.hoststack.dns.listZones(teamId);
|
|
1096
1114
|
const items = Array.isArray(response.zones) ? response.zones.map(shape) : [];
|
|
1097
1115
|
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"}.`;
|
|
1098
1116
|
return respond({ summary, data: { items } });
|
|
@@ -1121,10 +1139,8 @@ defineTool({
|
|
|
1121
1139
|
const zoneInput = {};
|
|
1122
1140
|
if (args2.zone_id !== void 0) zoneInput.zone_id = args2.zone_id;
|
|
1123
1141
|
if (args2.domain !== void 0) zoneInput.domain = args2.domain;
|
|
1124
|
-
const zone = await resolveZonePublicId(ctx.
|
|
1125
|
-
const response = await ctx.
|
|
1126
|
-
`/api/dns-zones/${teamId}/${zone.publicId}/records`
|
|
1127
|
-
);
|
|
1142
|
+
const zone = await resolveZonePublicId(ctx.hoststack, teamId, zoneInput);
|
|
1143
|
+
const response = await ctx.hoststack.dns.listRecords(teamId, zone.publicId);
|
|
1128
1144
|
const items = Array.isArray(response.records) ? response.records.map(shape) : [];
|
|
1129
1145
|
const summary = items.length === 0 ? `Zone ${zone.domainName} has no records yet.` : `Returned ${items.length} record${items.length === 1 ? "" : "s"} for ${zone.domainName}.`;
|
|
1130
1146
|
return respond({
|
|
@@ -1153,11 +1169,9 @@ defineTool({
|
|
|
1153
1169
|
},
|
|
1154
1170
|
handler: async (args2, ctx) => {
|
|
1155
1171
|
const teamId = await ctx.resolveTeamId();
|
|
1156
|
-
const { zones } = await ctx.
|
|
1172
|
+
const { zones } = await ctx.hoststack.dns.listZones(teamId);
|
|
1157
1173
|
for (const zone of zones) {
|
|
1158
|
-
const { records } = await ctx.
|
|
1159
|
-
`/api/dns-zones/${teamId}/${zone.publicId}/records`
|
|
1160
|
-
);
|
|
1174
|
+
const { records } = await ctx.hoststack.dns.listRecords(teamId, zone.publicId);
|
|
1161
1175
|
const match = records.find((r) => r.publicId === args2.record_id);
|
|
1162
1176
|
if (match) {
|
|
1163
1177
|
return respond({
|
|
@@ -1213,7 +1227,7 @@ defineTool({
|
|
|
1213
1227
|
const zoneInput = {};
|
|
1214
1228
|
if (args2.zone_id !== void 0) zoneInput.zone_id = args2.zone_id;
|
|
1215
1229
|
if (args2.domain !== void 0) zoneInput.domain = args2.domain;
|
|
1216
|
-
const zone = await resolveZonePublicId(ctx.
|
|
1230
|
+
const zone = await resolveZonePublicId(ctx.hoststack, teamId, zoneInput);
|
|
1217
1231
|
const body = {
|
|
1218
1232
|
type: args2.type,
|
|
1219
1233
|
name: args2.name,
|
|
@@ -1221,10 +1235,7 @@ defineTool({
|
|
|
1221
1235
|
};
|
|
1222
1236
|
if (args2.ttl !== void 0) body.ttl = args2.ttl;
|
|
1223
1237
|
if (args2.priority !== void 0) body.priority = args2.priority;
|
|
1224
|
-
const response = await ctx.
|
|
1225
|
-
`/api/dns-zones/${teamId}/${zone.publicId}/records`,
|
|
1226
|
-
body
|
|
1227
|
-
);
|
|
1238
|
+
const response = await ctx.hoststack.dns.createRecord(teamId, zone.publicId, body);
|
|
1228
1239
|
return respond({
|
|
1229
1240
|
summary: `Created ${args2.type} ${args2.name} on ${zone.domainName}.`,
|
|
1230
1241
|
data: {
|
|
@@ -1264,7 +1275,7 @@ defineTool({
|
|
|
1264
1275
|
return respondError(`Priority is required for ${args2.type} records (0\u201365535).`);
|
|
1265
1276
|
}
|
|
1266
1277
|
const teamId = await ctx.resolveTeamId();
|
|
1267
|
-
const target = await findRecordZone(ctx.
|
|
1278
|
+
const target = await findRecordZone(ctx.hoststack, teamId, args2.record_id);
|
|
1268
1279
|
if (!target) {
|
|
1269
1280
|
return respondError(
|
|
1270
1281
|
`Record ${args2.record_id} not found on any zone owned by this team.`
|
|
@@ -1277,8 +1288,10 @@ defineTool({
|
|
|
1277
1288
|
};
|
|
1278
1289
|
if (args2.ttl !== void 0) body.ttl = args2.ttl;
|
|
1279
1290
|
if (args2.priority !== void 0) body.priority = args2.priority;
|
|
1280
|
-
const response = await ctx.
|
|
1281
|
-
|
|
1291
|
+
const response = await ctx.hoststack.dns.updateRecord(
|
|
1292
|
+
teamId,
|
|
1293
|
+
target.zone.publicId,
|
|
1294
|
+
args2.record_id,
|
|
1282
1295
|
body
|
|
1283
1296
|
);
|
|
1284
1297
|
return respond({
|
|
@@ -1313,27 +1326,23 @@ defineTool({
|
|
|
1313
1326
|
},
|
|
1314
1327
|
handler: async (args2, ctx) => {
|
|
1315
1328
|
const teamId = await ctx.resolveTeamId();
|
|
1316
|
-
const target = await findRecordZone(ctx.
|
|
1329
|
+
const target = await findRecordZone(ctx.hoststack, teamId, args2.record_id);
|
|
1317
1330
|
if (!target) {
|
|
1318
1331
|
return respondError(
|
|
1319
1332
|
`Record ${args2.record_id} not found on any zone owned by this team.`
|
|
1320
1333
|
);
|
|
1321
1334
|
}
|
|
1322
|
-
await ctx.
|
|
1323
|
-
`/api/dns-zones/${teamId}/${target.zone.publicId}/records/${args2.record_id}`
|
|
1324
|
-
);
|
|
1335
|
+
await ctx.hoststack.dns.deleteRecord(teamId, target.zone.publicId, args2.record_id);
|
|
1325
1336
|
return respond({
|
|
1326
1337
|
summary: `Deleted record ${args2.record_id} from ${target.zone.domainName}.`,
|
|
1327
1338
|
data: { ok: true }
|
|
1328
1339
|
});
|
|
1329
1340
|
}
|
|
1330
1341
|
});
|
|
1331
|
-
async function findRecordZone(
|
|
1332
|
-
const { zones } = await
|
|
1342
|
+
async function findRecordZone(hoststack, teamId, recordPublicId) {
|
|
1343
|
+
const { zones } = await hoststack.dns.listZones(teamId);
|
|
1333
1344
|
for (const zone of zones) {
|
|
1334
|
-
const { records } = await
|
|
1335
|
-
`/api/dns-zones/${teamId}/${zone.publicId}/records`
|
|
1336
|
-
);
|
|
1345
|
+
const { records } = await hoststack.dns.listRecords(teamId, zone.publicId);
|
|
1337
1346
|
const match = records.find((r) => r.publicId === recordPublicId);
|
|
1338
1347
|
if (match) return { zone, record: match };
|
|
1339
1348
|
}
|
|
@@ -1493,6 +1502,7 @@ defineTool({
|
|
|
1493
1502
|
' - key: env-var name (e.g. "DATABASE_URL").',
|
|
1494
1503
|
" - value: new value (will be encrypted at rest if is_secret=true).",
|
|
1495
1504
|
" - 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.",
|
|
1505
|
+
' - 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.',
|
|
1496
1506
|
"",
|
|
1497
1507
|
'Returns: { envVar: EnvVar, action: "created" | "updated" }.',
|
|
1498
1508
|
"",
|
|
@@ -1502,7 +1512,8 @@ defineTool({
|
|
|
1502
1512
|
service_id: z9.string().describe("Service publicId."),
|
|
1503
1513
|
key: z9.string().min(1).max(128).describe("Env-var key."),
|
|
1504
1514
|
value: z9.string().describe("New value."),
|
|
1505
|
-
is_secret: z9.boolean().optional().describe("Mark as secret (encrypted, masked on read). Default true.")
|
|
1515
|
+
is_secret: z9.boolean().optional().describe("Mark as secret (encrypted, masked on read). Default true."),
|
|
1516
|
+
target: z9.enum(["build", "runtime", "both"]).optional().describe("Injection target: build, runtime, or both. Default both.")
|
|
1506
1517
|
},
|
|
1507
1518
|
handler: async (args2, ctx) => {
|
|
1508
1519
|
const teamId = await ctx.resolveTeamId();
|
|
@@ -1511,6 +1522,7 @@ defineTool({
|
|
|
1511
1522
|
if (match) {
|
|
1512
1523
|
const updatePayload = { value: args2.value };
|
|
1513
1524
|
if (args2.is_secret !== void 0) updatePayload.isSecret = args2.is_secret;
|
|
1525
|
+
if (args2.target !== void 0) updatePayload.target = args2.target;
|
|
1514
1526
|
const response2 = await ctx.hoststack.envVars.update(
|
|
1515
1527
|
teamId,
|
|
1516
1528
|
args2.service_id,
|
|
@@ -1526,7 +1538,8 @@ defineTool({
|
|
|
1526
1538
|
const response = await ctx.hoststack.envVars.create(teamId, args2.service_id, {
|
|
1527
1539
|
key: args2.key,
|
|
1528
1540
|
value: args2.value,
|
|
1529
|
-
isSecret: args2.is_secret ?? true
|
|
1541
|
+
isSecret: args2.is_secret ?? true,
|
|
1542
|
+
...args2.target !== void 0 ? { target: args2.target } : {}
|
|
1530
1543
|
});
|
|
1531
1544
|
const data = {
|
|
1532
1545
|
envVar: shapeEnvVar(response.envVar),
|
|
@@ -1582,7 +1595,7 @@ defineTool({
|
|
|
1582
1595
|
"",
|
|
1583
1596
|
"Inputs:",
|
|
1584
1597
|
" - service_id: publicId of the service.",
|
|
1585
|
-
|
|
1598
|
+
' - 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.',
|
|
1586
1599
|
"",
|
|
1587
1600
|
"Returns: { ok: true }. Re-list with list_env_vars to confirm the new state.",
|
|
1588
1601
|
"",
|
|
@@ -1594,7 +1607,8 @@ defineTool({
|
|
|
1594
1607
|
z9.object({
|
|
1595
1608
|
key: z9.string().min(1).max(128),
|
|
1596
1609
|
value: z9.string(),
|
|
1597
|
-
is_secret: z9.boolean().optional()
|
|
1610
|
+
is_secret: z9.boolean().optional(),
|
|
1611
|
+
target: z9.enum(["build", "runtime", "both"]).optional()
|
|
1598
1612
|
})
|
|
1599
1613
|
).max(500).describe("Array of env-var rows. Hard cap 500.")
|
|
1600
1614
|
},
|
|
@@ -1607,6 +1621,7 @@ defineTool({
|
|
|
1607
1621
|
value: v.value
|
|
1608
1622
|
};
|
|
1609
1623
|
if (v.is_secret !== void 0) row.isSecret = v.is_secret;
|
|
1624
|
+
if (v.target !== void 0) row.target = v.target;
|
|
1610
1625
|
return row;
|
|
1611
1626
|
})
|
|
1612
1627
|
};
|
|
@@ -1628,6 +1643,10 @@ defineTool({
|
|
|
1628
1643
|
"",
|
|
1629
1644
|
"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.",
|
|
1630
1645
|
"",
|
|
1646
|
+
// §2.2: these are project deployment environments, not cloud Dev Boxes — point agents at the
|
|
1647
|
+
// right surface so the two "dev" concepts stay distinct (bidirectional cross-reference).
|
|
1648
|
+
"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.",
|
|
1649
|
+
"",
|
|
1631
1650
|
"Inputs:",
|
|
1632
1651
|
' - project_id: project publicId (e.g. "prj_abc123").',
|
|
1633
1652
|
"",
|
|
@@ -1654,10 +1673,15 @@ defineTool({
|
|
|
1654
1673
|
"",
|
|
1655
1674
|
"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.",
|
|
1656
1675
|
"",
|
|
1676
|
+
// §2.1/§2.2: steer callers away from the agentic cloud Dev Box tools (which live in
|
|
1677
|
+
// services.ts) — an environment is a deployment target, NOT a Dev Box. Bidirectional with
|
|
1678
|
+
// the cross-references those dev-box tools point back here.
|
|
1679
|
+
'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.',
|
|
1680
|
+
"",
|
|
1657
1681
|
"Inputs:",
|
|
1658
1682
|
" - project_id: project publicId.",
|
|
1659
1683
|
" - name: human-readable name (1\u201364 chars). Shown in the env switcher.",
|
|
1660
|
-
' - type: "production" | "staging" | "development" | "preview". Determines the hostname suffix on services in this env (production stays clean; others get -staging / -dev / -preview).',
|
|
1684
|
+
' - 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.',
|
|
1661
1685
|
" - is_protected (optional): require admin role for destructive actions in this env. Default false.",
|
|
1662
1686
|
"",
|
|
1663
1687
|
"Returns: { environment: Environment }.",
|
|
@@ -1667,7 +1691,10 @@ defineTool({
|
|
|
1667
1691
|
input: {
|
|
1668
1692
|
project_id: z10.string().describe("Project publicId."),
|
|
1669
1693
|
name: z10.string().min(1).max(64).describe("Environment name (1\u201364 chars)."),
|
|
1670
|
-
type: z10.enum(["production", "staging", "development", "preview"]).describe(
|
|
1694
|
+
type: z10.enum(["production", "staging", "development", "preview"]).describe(
|
|
1695
|
+
// §2.1: "development" here is a project deployment env, NOT a cloud Dev Box.
|
|
1696
|
+
'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.'
|
|
1697
|
+
),
|
|
1671
1698
|
is_protected: z10.boolean().optional().describe("Require admin role for destructive actions. Default false.")
|
|
1672
1699
|
},
|
|
1673
1700
|
handler: async (args2, ctx) => {
|
|
@@ -1759,6 +1786,14 @@ defineTool({
|
|
|
1759
1786
|
});
|
|
1760
1787
|
|
|
1761
1788
|
// src/tools/meta.ts
|
|
1789
|
+
var DEV_ENV_TOOL_NAMES = [
|
|
1790
|
+
"create_dev_environment",
|
|
1791
|
+
"create_standalone_dev_environment",
|
|
1792
|
+
"spin_up_dev_environment",
|
|
1793
|
+
"list_dev_environments",
|
|
1794
|
+
"resize_dev_environment",
|
|
1795
|
+
"delete_dev_environment"
|
|
1796
|
+
];
|
|
1762
1797
|
defineTool({
|
|
1763
1798
|
name: "get_me",
|
|
1764
1799
|
category: "meta",
|
|
@@ -1783,6 +1818,36 @@ defineTool({
|
|
|
1783
1818
|
return respond({ summary: `Authenticated as ${userEmail}${teamLabel}.`, data });
|
|
1784
1819
|
}
|
|
1785
1820
|
});
|
|
1821
|
+
defineTool({
|
|
1822
|
+
name: "describe_mcp",
|
|
1823
|
+
category: "meta",
|
|
1824
|
+
description: [
|
|
1825
|
+
"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.",
|
|
1826
|
+
"",
|
|
1827
|
+
"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).",
|
|
1828
|
+
"",
|
|
1829
|
+
'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.',
|
|
1830
|
+
"",
|
|
1831
|
+
"Inputs: none.",
|
|
1832
|
+
"",
|
|
1833
|
+
"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.",
|
|
1834
|
+
"",
|
|
1835
|
+
'Example: describe_mcp() \u2192 { version: "0.14.0", toolCount: 30, devEnvTools: ["create_dev_environment", \u2026], devEnvToolsPresent: true }'
|
|
1836
|
+
].join("\n"),
|
|
1837
|
+
input: {},
|
|
1838
|
+
handler: async (_args, _ctx) => {
|
|
1839
|
+
const registered = new Set(listToolDefinitions().map((t) => t.name));
|
|
1840
|
+
const devEnvToolsPresent = DEV_ENV_TOOL_NAMES.every((name) => registered.has(name));
|
|
1841
|
+
const data = {
|
|
1842
|
+
version: MCP_VERSION,
|
|
1843
|
+
toolCount: registered.size,
|
|
1844
|
+
devEnvTools: [...DEV_ENV_TOOL_NAMES],
|
|
1845
|
+
devEnvToolsPresent
|
|
1846
|
+
};
|
|
1847
|
+
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).`;
|
|
1848
|
+
return respond({ summary, data });
|
|
1849
|
+
}
|
|
1850
|
+
});
|
|
1786
1851
|
|
|
1787
1852
|
// src/tools/notifications.ts
|
|
1788
1853
|
import { z as z11 } from "zod";
|
|
@@ -2107,11 +2172,19 @@ var SERVICE_PLANS = [
|
|
|
2107
2172
|
"pico",
|
|
2108
2173
|
"nano",
|
|
2109
2174
|
"micro",
|
|
2110
|
-
"
|
|
2175
|
+
"small",
|
|
2111
2176
|
"standard",
|
|
2177
|
+
"large",
|
|
2178
|
+
"xlarge",
|
|
2112
2179
|
"pro_standard",
|
|
2113
2180
|
"pro_large"
|
|
2114
2181
|
];
|
|
2182
|
+
var DEV_ENV_MIN_SIZE = "standard";
|
|
2183
|
+
function coerceDevEnvSize(value) {
|
|
2184
|
+
const minIdx = SERVICE_PLANS.indexOf(DEV_ENV_MIN_SIZE);
|
|
2185
|
+
const idx = value ? SERVICE_PLANS.indexOf(value) : -1;
|
|
2186
|
+
return idx >= minIdx ? value : DEV_ENV_MIN_SIZE;
|
|
2187
|
+
}
|
|
2115
2188
|
defineTool({
|
|
2116
2189
|
name: "list_services",
|
|
2117
2190
|
category: "services",
|
|
@@ -2120,11 +2193,14 @@ defineTool({
|
|
|
2120
2193
|
"",
|
|
2121
2194
|
"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.",
|
|
2122
2195
|
"",
|
|
2196
|
+
"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.",
|
|
2197
|
+
"",
|
|
2123
2198
|
"Inputs (all optional):",
|
|
2124
2199
|
' - project_id: narrow to one project (numeric id or publicId "prj_\u2026").',
|
|
2125
2200
|
' - environment_id: narrow to one environment (numeric id or publicId "env_\u2026").',
|
|
2126
2201
|
' - status: "active" | "deploying" | "suspended" | "failed" | "not_deployed".',
|
|
2127
2202
|
' - type: "web_service" | "private_service" | "worker" | "cron_job" | "static_site".',
|
|
2203
|
+
" - dev_environment: include Dev Boxes in the results (excluded by default).",
|
|
2128
2204
|
"",
|
|
2129
2205
|
"Returns: { items: Service[] } \u2014 each service includes id, publicId, name, type, status, projectId, repoUrl, branch, runtime, createdAt.",
|
|
2130
2206
|
"",
|
|
@@ -2134,11 +2210,15 @@ defineTool({
|
|
|
2134
2210
|
project_id: z13.union([z13.number().int().positive(), z13.string()]).optional().describe("Project filter \u2014 numeric id or publicId."),
|
|
2135
2211
|
environment_id: z13.union([z13.number().int().positive(), z13.string()]).optional().describe("Environment filter \u2014 numeric id or publicId."),
|
|
2136
2212
|
status: z13.enum(["active", "deploying", "suspended", "failed", "not_deployed"]).optional().describe("Filter by current runtime status."),
|
|
2137
|
-
type: z13.enum(["web_service", "private_service", "worker", "cron_job", "static_site"]).optional().describe("Filter by service type.")
|
|
2213
|
+
type: z13.enum(["web_service", "private_service", "worker", "cron_job", "static_site"]).optional().describe("Filter by service type."),
|
|
2214
|
+
dev_environment: z13.boolean().optional().describe(
|
|
2215
|
+
"Include agentic Dev Boxes in the results (excluded by default; see list_dev_environments)."
|
|
2216
|
+
)
|
|
2138
2217
|
},
|
|
2139
2218
|
handler: async (args2, ctx) => {
|
|
2140
2219
|
const teamId = await ctx.resolveTeamId();
|
|
2141
2220
|
const filters = {};
|
|
2221
|
+
if (args2.dev_environment) filters.devEnvironment = true;
|
|
2142
2222
|
if (args2.project_id !== void 0) {
|
|
2143
2223
|
const resolved = await ctx.hoststack.resolveId(args2.project_id, {
|
|
2144
2224
|
kind: "project",
|
|
@@ -2161,7 +2241,8 @@ defineTool({
|
|
|
2161
2241
|
args2.project_id !== void 0 ? `project=${args2.project_id}` : null,
|
|
2162
2242
|
args2.environment_id !== void 0 ? `env=${args2.environment_id}` : null,
|
|
2163
2243
|
args2.status ? `status=${args2.status}` : null,
|
|
2164
|
-
args2.type ? `type=${args2.type}` : null
|
|
2244
|
+
args2.type ? `type=${args2.type}` : null,
|
|
2245
|
+
args2.dev_environment ? "devBoxes=included" : null
|
|
2165
2246
|
].filter(Boolean).join(", ");
|
|
2166
2247
|
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}` : ""}.`;
|
|
2167
2248
|
return respond({ summary, data });
|
|
@@ -2252,14 +2333,16 @@ defineTool({
|
|
|
2252
2333
|
name: "create_dev_environment",
|
|
2253
2334
|
category: "services",
|
|
2254
2335
|
description: [
|
|
2255
|
-
|
|
2336
|
+
'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.',
|
|
2337
|
+
"",
|
|
2338
|
+
'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.',
|
|
2256
2339
|
"",
|
|
2257
|
-
`When to use: the user wants a cloud terminal /
|
|
2340
|
+
`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.`,
|
|
2258
2341
|
"",
|
|
2259
2342
|
"Inputs:",
|
|
2260
2343
|
' - project_id: numeric id or publicId ("prj_\u2026") of the target project.',
|
|
2261
2344
|
' - name (optional): service name (default "dev-environment").',
|
|
2262
|
-
' - plan (optional):
|
|
2345
|
+
' - 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".',
|
|
2263
2346
|
" - disk_gb (optional): /workspace volume size in GB (default 10, 1\u2013100).",
|
|
2264
2347
|
" - hoststack_api_key (optional): sets HOSTSTACK_API_KEY so the hoststack MCP works inside the container.",
|
|
2265
2348
|
" - poststack_api_key (optional): sets POSTSTACK_API_KEY so the poststack MCP works inside the container.",
|
|
@@ -2273,7 +2356,7 @@ defineTool({
|
|
|
2273
2356
|
project_id: z13.union([z13.number().int().positive(), z13.string()]).describe("Target project \u2014 numeric id or publicId."),
|
|
2274
2357
|
name: z13.string().min(1).max(100).optional().describe('Service name (default "dev-environment").'),
|
|
2275
2358
|
plan: z13.enum(SERVICE_PLANS).optional().describe(
|
|
2276
|
-
'
|
|
2359
|
+
'Box size (default "standard" \u2014 2 GB, the OOM-safe floor; a smaller plan is clamped up to "standard").'
|
|
2277
2360
|
),
|
|
2278
2361
|
disk_gb: z13.number().int().min(1).max(100).optional().describe("/workspace volume size in GB (default 10)."),
|
|
2279
2362
|
hoststack_api_key: z13.string().optional().describe("Value for HOSTSTACK_API_KEY (enables the hoststack MCP in-container)."),
|
|
@@ -2291,68 +2374,94 @@ defineTool({
|
|
|
2291
2374
|
});
|
|
2292
2375
|
const name = args2.name ?? "dev-environment";
|
|
2293
2376
|
const sizeGb = args2.disk_gb ?? DEV_ENV_VOLUME.sizeGb;
|
|
2377
|
+
const plan = coerceDevEnvSize(args2.plan);
|
|
2294
2378
|
const createInput = {
|
|
2295
2379
|
name,
|
|
2296
2380
|
type: "private_service",
|
|
2297
2381
|
projectId,
|
|
2298
2382
|
dockerImage: DEV_ENV_IMAGE,
|
|
2299
|
-
autoDeploy: false
|
|
2383
|
+
autoDeploy: false,
|
|
2384
|
+
plan
|
|
2300
2385
|
};
|
|
2301
|
-
if (args2.plan !== void 0) createInput.plan = args2.plan;
|
|
2302
2386
|
const created = await ctx.hoststack.services.create(teamId, createInput);
|
|
2303
2387
|
const service = created.service;
|
|
2304
|
-
const
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
if (args2.repo_url) {
|
|
2318
|
-
envVars.push({
|
|
2319
|
-
key: "HOSTSTACK_DEVENV_REPO_URL",
|
|
2320
|
-
value: args2.repo_url,
|
|
2321
|
-
isSecret: false
|
|
2322
|
-
});
|
|
2323
|
-
if (args2.branch)
|
|
2388
|
+
const rollback = async () => {
|
|
2389
|
+
try {
|
|
2390
|
+
await ctx.hoststack.services.tearDownDevEnvironment(teamId, service.id);
|
|
2391
|
+
} catch {
|
|
2392
|
+
try {
|
|
2393
|
+
await ctx.hoststack.services.delete(teamId, service.id);
|
|
2394
|
+
} catch {
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
};
|
|
2398
|
+
try {
|
|
2399
|
+
const envVars = [];
|
|
2400
|
+
if (args2.hoststack_api_key)
|
|
2324
2401
|
envVars.push({
|
|
2325
|
-
key: "
|
|
2326
|
-
value: args2.
|
|
2402
|
+
key: "HOSTSTACK_API_KEY",
|
|
2403
|
+
value: args2.hoststack_api_key,
|
|
2404
|
+
isSecret: true
|
|
2405
|
+
});
|
|
2406
|
+
if (args2.poststack_api_key)
|
|
2407
|
+
envVars.push({
|
|
2408
|
+
key: "POSTSTACK_API_KEY",
|
|
2409
|
+
value: args2.poststack_api_key,
|
|
2410
|
+
isSecret: true
|
|
2411
|
+
});
|
|
2412
|
+
if (args2.repo_url) {
|
|
2413
|
+
envVars.push({
|
|
2414
|
+
key: "HOSTSTACK_DEVENV_REPO_URL",
|
|
2415
|
+
value: args2.repo_url,
|
|
2327
2416
|
isSecret: false
|
|
2328
2417
|
});
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2418
|
+
if (args2.branch)
|
|
2419
|
+
envVars.push({
|
|
2420
|
+
key: "HOSTSTACK_DEVENV_BRANCH",
|
|
2421
|
+
value: args2.branch,
|
|
2422
|
+
isSecret: false
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
if (envVars.length > 0) {
|
|
2426
|
+
await ctx.hoststack.envVars.bulkSet(teamId, service.id, { vars: envVars });
|
|
2427
|
+
}
|
|
2428
|
+
try {
|
|
2429
|
+
await ctx.hoststack.volumes.create(teamId, service.id, {
|
|
2430
|
+
name: DEV_ENV_VOLUME.name,
|
|
2431
|
+
mountPath: DEV_ENV_VOLUME.mountPath,
|
|
2432
|
+
sizeGb
|
|
2433
|
+
});
|
|
2434
|
+
} catch (error) {
|
|
2435
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2436
|
+
await rollback();
|
|
2437
|
+
return respondError(
|
|
2438
|
+
`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}`,
|
|
2439
|
+
{ volumeError: reason, rolledBack: true }
|
|
2440
|
+
);
|
|
2441
|
+
}
|
|
2442
|
+
const deploy = await ctx.hoststack.deploys.trigger(teamId, service.id);
|
|
2443
|
+
const deployId = deploy.deploy?.id ?? null;
|
|
2444
|
+
return respond({
|
|
2445
|
+
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.`,
|
|
2446
|
+
data: { service: shapeService(service), volumeAttached: true, deployId }
|
|
2339
2447
|
});
|
|
2340
|
-
|
|
2341
|
-
|
|
2448
|
+
} catch (error) {
|
|
2449
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2450
|
+
await rollback();
|
|
2451
|
+
return respondError(
|
|
2452
|
+
`Failed to provision Dev Box "${name}" \u2014 rolled back the partially-created box. Error: ${reason}`,
|
|
2453
|
+
{ rolledBack: true }
|
|
2454
|
+
);
|
|
2342
2455
|
}
|
|
2343
|
-
const deploy = await ctx.hoststack.deploys.trigger(teamId, service.id);
|
|
2344
|
-
const deployId = deploy.deploy?.id ?? null;
|
|
2345
|
-
return respond({
|
|
2346
|
-
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.`,
|
|
2347
|
-
data: { service: shapeService(service), volumeAttached, deployId }
|
|
2348
|
-
});
|
|
2349
2456
|
}
|
|
2350
2457
|
});
|
|
2351
2458
|
defineTool({
|
|
2352
2459
|
name: "spin_up_dev_environment",
|
|
2353
2460
|
category: "services",
|
|
2354
2461
|
description: [
|
|
2355
|
-
"Spin up an agentic
|
|
2462
|
+
"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`.",
|
|
2463
|
+
"",
|
|
2464
|
+
"This makes a cloud Dev Box, NOT a project deploy environment \u2014 for a -dev/-staging deploy target use create_environment instead.",
|
|
2356
2465
|
"",
|
|
2357
2466
|
'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.',
|
|
2358
2467
|
"",
|
|
@@ -2395,7 +2504,9 @@ defineTool({
|
|
|
2395
2504
|
name: "delete_dev_environment",
|
|
2396
2505
|
category: "services",
|
|
2397
2506
|
description: [
|
|
2398
|
-
"Tear down an agentic
|
|
2507
|
+
"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.",
|
|
2508
|
+
"",
|
|
2509
|
+
"This deletes a cloud Dev Box, NOT a project deploy environment \u2014 to remove a -dev/-staging deploy target use delete_environment instead.",
|
|
2399
2510
|
"",
|
|
2400
2511
|
'When to use: "delete / tear down the dev environment", once you have shipped the fix and no longer need the box.',
|
|
2401
2512
|
"",
|
|
@@ -2426,15 +2537,17 @@ defineTool({
|
|
|
2426
2537
|
name: "resize_dev_environment",
|
|
2427
2538
|
category: "services",
|
|
2428
2539
|
description: [
|
|
2429
|
-
"Resize a
|
|
2540
|
+
"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).",
|
|
2430
2541
|
"",
|
|
2431
|
-
"
|
|
2542
|
+
"This resizes a cloud Dev Box, NOT a project deploy environment.",
|
|
2543
|
+
"",
|
|
2544
|
+
"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.",
|
|
2432
2545
|
"",
|
|
2433
2546
|
"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.",
|
|
2434
2547
|
"",
|
|
2435
2548
|
"Inputs:",
|
|
2436
2549
|
" - service_id: the box to resize \u2014 numeric id or publicId.",
|
|
2437
|
-
' - size: target tier (e.g. "standard", "large", "xlarge").',
|
|
2550
|
+
' - size: target tier \u2014 one of the service catalog sizes (e.g. "standard", "large", "xlarge").',
|
|
2438
2551
|
"",
|
|
2439
2552
|
"Returns: { service } with the new plan.",
|
|
2440
2553
|
"",
|
|
@@ -2442,7 +2555,9 @@ defineTool({
|
|
|
2442
2555
|
].join("\n"),
|
|
2443
2556
|
input: {
|
|
2444
2557
|
service_id: z13.union([z13.number().int().positive(), z13.string()]).describe("The box to resize \u2014 numeric id or publicId."),
|
|
2445
|
-
size: z13.
|
|
2558
|
+
size: z13.enum(SERVICE_PLANS).describe(
|
|
2559
|
+
'Target size tier (service catalog size, e.g. "standard", "large", "xlarge").'
|
|
2560
|
+
)
|
|
2446
2561
|
},
|
|
2447
2562
|
handler: async (args2, ctx) => {
|
|
2448
2563
|
const teamId = await ctx.resolveTeamId();
|
|
@@ -2461,7 +2576,9 @@ defineTool({
|
|
|
2461
2576
|
name: "list_dev_environments",
|
|
2462
2577
|
category: "services",
|
|
2463
2578
|
description: [
|
|
2464
|
-
`List the team's
|
|
2579
|
+
`List the team's cloud Dev Boxes (the dashboard "Dev Boxes" / Development section), each annotated with the companion services attached to it.`,
|
|
2580
|
+
"",
|
|
2581
|
+
"These are cloud Dev Boxes, NOT project deploy environments \u2014 to list a project's deploy targets (Production / Staging / Preview) use list_environments instead.",
|
|
2465
2582
|
"",
|
|
2466
2583
|
`When to use: "show my dev environments", before opening/tearing one down, to find a box's id.`,
|
|
2467
2584
|
"",
|
|
@@ -2488,22 +2605,60 @@ defineTool({
|
|
|
2488
2605
|
});
|
|
2489
2606
|
}
|
|
2490
2607
|
});
|
|
2608
|
+
defineTool({
|
|
2609
|
+
name: "list_templates",
|
|
2610
|
+
category: "services",
|
|
2611
|
+
description: [
|
|
2612
|
+
"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.",
|
|
2613
|
+
"",
|
|
2614
|
+
"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.",
|
|
2615
|
+
"",
|
|
2616
|
+
'Returns: { templates: [{ id, name, image, volume: { name, mountPath, sizeGb }, minPlan, companions }] }. Today there is one preset ("dev-environment").',
|
|
2617
|
+
"",
|
|
2618
|
+
'Example: list_templates() \u2192 { templates: [{ id: "dev-environment", image: "hoststack/dev-env:latest", volume: { mountPath: "/workspace", sizeGb: 10 }, minPlan: "standard", companions: ["postgres","redis","meilisearch"] }] }'
|
|
2619
|
+
].join("\n"),
|
|
2620
|
+
input: {},
|
|
2621
|
+
handler: async () => {
|
|
2622
|
+
const templates = [
|
|
2623
|
+
{
|
|
2624
|
+
id: "dev-environment",
|
|
2625
|
+
name: "AI Dev Environment",
|
|
2626
|
+
image: DEV_ENV_IMAGE,
|
|
2627
|
+
volume: {
|
|
2628
|
+
name: DEV_ENV_VOLUME.name,
|
|
2629
|
+
mountPath: DEV_ENV_VOLUME.mountPath,
|
|
2630
|
+
sizeGb: DEV_ENV_VOLUME.sizeGb
|
|
2631
|
+
},
|
|
2632
|
+
/** OOM-safe plan floor — a coding agent + build needs at least this. */
|
|
2633
|
+
minPlan: DEV_ENV_MIN_SIZE,
|
|
2634
|
+
/** Companion engines you can attach (fresh + empty), like local `make db-up`. */
|
|
2635
|
+
companions: ["postgres", "redis", "meilisearch"]
|
|
2636
|
+
}
|
|
2637
|
+
];
|
|
2638
|
+
return respond({
|
|
2639
|
+
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}").`,
|
|
2640
|
+
data: { templates }
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2643
|
+
});
|
|
2491
2644
|
defineTool({
|
|
2492
2645
|
name: "create_standalone_dev_environment",
|
|
2493
2646
|
category: "services",
|
|
2494
2647
|
description: [
|
|
2495
|
-
"Create a STANDALONE
|
|
2648
|
+
"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.",
|
|
2649
|
+
"",
|
|
2650
|
+
"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.",
|
|
2496
2651
|
"",
|
|
2497
2652
|
'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).',
|
|
2498
2653
|
"",
|
|
2499
2654
|
"Inputs:",
|
|
2500
|
-
" - name: the
|
|
2655
|
+
" - name: the Dev Box name.",
|
|
2501
2656
|
' - 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).',
|
|
2502
2657
|
" - github_repo_id (for github_repo): numeric id of a connected GitHub repo.",
|
|
2503
2658
|
" - clone_url (for url): an http(s) git clone URL.",
|
|
2504
2659
|
" - branch (optional): branch to clone.",
|
|
2505
2660
|
' - databases (optional): companion services to attach \u2014 any of "postgres", "redis", "meilisearch".',
|
|
2506
|
-
' - plan (optional): box size (
|
|
2661
|
+
' - 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.',
|
|
2507
2662
|
" - 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.",
|
|
2508
2663
|
"",
|
|
2509
2664
|
"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.",
|
|
@@ -2511,13 +2666,15 @@ defineTool({
|
|
|
2511
2666
|
'Example: create_standalone_dev_environment({ name: "app-dev", source_kind: "github_repo", github_repo_id: 42, databases: ["postgres","redis"] })'
|
|
2512
2667
|
].join("\n"),
|
|
2513
2668
|
input: {
|
|
2514
|
-
name: z13.string().min(1).max(100).describe("Dev
|
|
2669
|
+
name: z13.string().min(1).max(100).describe("Dev Box name."),
|
|
2515
2670
|
source_kind: z13.enum(["github_repo", "url", "blank"]).describe("Where the code comes from."),
|
|
2516
2671
|
github_repo_id: z13.number().int().positive().optional().describe('Connected GitHub repo id (required when source_kind="github_repo").'),
|
|
2517
2672
|
clone_url: z13.string().url().optional().describe('http(s) git clone URL (required when source_kind="url").'),
|
|
2518
2673
|
branch: z13.string().min(1).max(255).optional().describe("Branch to clone."),
|
|
2519
2674
|
databases: z13.array(z13.enum(["postgres", "redis", "meilisearch"])).optional().describe("Companion services to attach (fresh + empty)."),
|
|
2520
|
-
plan: z13.enum(SERVICE_PLANS).optional().describe(
|
|
2675
|
+
plan: z13.enum(SERVICE_PLANS).optional().describe(
|
|
2676
|
+
'Box size (default "standard" \u2014 2 GB; a smaller plan is floored to "standard").'
|
|
2677
|
+
),
|
|
2521
2678
|
agent_accounts: z13.array(
|
|
2522
2679
|
z13.object({
|
|
2523
2680
|
provider: z13.enum(["claude", "codex", "opencode"]),
|
|
@@ -3187,7 +3344,7 @@ defineTool({
|
|
|
3187
3344
|
|
|
3188
3345
|
// src/server-factory.ts
|
|
3189
3346
|
var PACKAGE_NAME = "hoststack";
|
|
3190
|
-
var PACKAGE_VERSION =
|
|
3347
|
+
var PACKAGE_VERSION = MCP_VERSION;
|
|
3191
3348
|
function createMcpServer(options) {
|
|
3192
3349
|
const baseUrl2 = (options.baseUrl ?? "https://hoststack.dev").replace(/\/$/, "");
|
|
3193
3350
|
const hoststack = new HostStack({ apiKey: options.apiKey, baseUrl: baseUrl2 });
|