@hoststack.dev/mcp 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/dist/hoststack-mcp.js +215 -27
- package/dist/hoststack-mcp.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +214 -26
- package/dist/index.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ interface CreateServerOptions {
|
|
|
83
83
|
onToolCall?: ToolCallSink;
|
|
84
84
|
}
|
|
85
85
|
declare const PACKAGE_NAME = "hoststack";
|
|
86
|
-
declare const PACKAGE_VERSION = "0.
|
|
86
|
+
declare const PACKAGE_VERSION = "0.9.1";
|
|
87
87
|
/**
|
|
88
88
|
* Build a fully-wired MCP server. Used by both the stdio CLI entry-point and
|
|
89
89
|
* the HTTP transport mounted by the API server. A new server (and its
|
package/dist/index.js
CHANGED
|
@@ -838,13 +838,14 @@ defineTool({
|
|
|
838
838
|
name: "trigger_deploy",
|
|
839
839
|
category: "deploys",
|
|
840
840
|
description: [
|
|
841
|
-
"Kick off a new deploy
|
|
841
|
+
"Kick off a new deploy. Defaults to the latest commit on the service branch; pass commit_hash or branch to deploy something else.",
|
|
842
842
|
"",
|
|
843
|
-
'When to use: the user explicitly asks to deploy ("ship it", "redeploy", "kick off a new build"). For services with auto-deploy enabled, a fresh git push already triggers a deploy automatically \u2014 only call this for manual triggers
|
|
843
|
+
'When to use: the user explicitly asks to deploy ("ship it", "redeploy", "kick off a new build"). For services with auto-deploy enabled, a fresh git push already triggers a deploy automatically \u2014 only call this for manual triggers or after a config change.',
|
|
844
844
|
"",
|
|
845
845
|
"Inputs:",
|
|
846
846
|
" - service_id: publicId of the service.",
|
|
847
|
-
" -
|
|
847
|
+
" - commit_hash (optional): build a specific commit instead of HEAD on the configured branch.",
|
|
848
|
+
" - branch (optional): build the tip of a non-default branch.",
|
|
848
849
|
"",
|
|
849
850
|
'Returns: { deploy: Deploy } \u2014 the new deploy record. Status will start as "pending" then transition through "building" \u2192 "deploying" \u2192 "live" or "failed".',
|
|
850
851
|
"",
|
|
@@ -852,12 +853,14 @@ defineTool({
|
|
|
852
853
|
].join("\n"),
|
|
853
854
|
input: {
|
|
854
855
|
service_id: z6.string().describe("Service publicId."),
|
|
855
|
-
|
|
856
|
+
commit_hash: z6.string().max(40).optional().describe("Specific commit to build (default: branch HEAD)."),
|
|
857
|
+
branch: z6.string().max(200).optional().describe("Branch to build (default: service configured branch).")
|
|
856
858
|
},
|
|
857
859
|
handler: async (args, ctx) => {
|
|
858
860
|
const teamId = await ctx.resolveTeamId();
|
|
859
861
|
const input = {};
|
|
860
|
-
if (args.
|
|
862
|
+
if (args.commit_hash !== void 0) input.commitHash = args.commit_hash;
|
|
863
|
+
if (args.branch !== void 0) input.branch = args.branch;
|
|
861
864
|
const response = await ctx.hoststack.deploys.trigger(teamId, args.service_id, input);
|
|
862
865
|
const data = { deploy: shapeDeploy(response.deploy) };
|
|
863
866
|
const publicId = data.deploy && "publicId" in data.deploy ? data.deploy.publicId : "unknown";
|
|
@@ -900,7 +903,7 @@ defineTool({
|
|
|
900
903
|
name: "get_deploy_logs",
|
|
901
904
|
category: "deploys",
|
|
902
905
|
description: [
|
|
903
|
-
"Fetch the build/deploy logs for a single deploy. Returns the entire log buffer the agent can
|
|
906
|
+
"Fetch the build/deploy logs for a single deploy. Returns the entire log buffer the agent can then search for errors.",
|
|
904
907
|
"",
|
|
905
908
|
"When to use: a deploy failed and you need to read the build output to diagnose. Pair with get_deploy to find a failed deploy_id, then call this. For runtime (post-deploy) logs of the running container, use get_service_logs instead.",
|
|
906
909
|
"",
|
|
@@ -1155,20 +1158,23 @@ defineTool({
|
|
|
1155
1158
|
"",
|
|
1156
1159
|
"Inputs:",
|
|
1157
1160
|
' - type: "A" | "AAAA" | "CNAME" | "MX" | "TXT" | "NS" | "SRV" | "CAA" | "ALIAS".',
|
|
1158
|
-
' - name: "@" for the zone apex, or a subdomain label ("www", "api", "_acme-challenge").',
|
|
1159
|
-
" - content: the literal record value. For TXT pass the unquoted string \u2014 the PowerDNS layer handles quoting.",
|
|
1161
|
+
' - name: "@" for the zone apex, or a subdomain label ("www", "api", "_acme-challenge"). Do NOT include the zone suffix \u2014 the backend prepends it. "bounce" on zone "micci.dk" publishes "bounce.micci.dk"; passing "bounce.micci.dk" works (the backend strips the suffix) but is non-idiomatic.',
|
|
1162
|
+
" - content: the literal record value. For TXT pass the unquoted string \u2014 the PowerDNS layer handles quoting. For CNAME / MX / NS / SRV / ALIAS the target may be passed with or without a trailing dot; the adapter FQDN-izes it.",
|
|
1160
1163
|
" - ttl (optional): 60\u201386400 seconds, default 3600.",
|
|
1161
1164
|
" - priority (optional, required for MX and SRV): 0\u201365535.",
|
|
1162
1165
|
"",
|
|
1163
1166
|
"Returns: { record: Record } \u2014 see get_dns_record for the shape.",
|
|
1164
1167
|
"",
|
|
1168
|
+
"Notes:",
|
|
1169
|
+
' - CNAME at the apex is rejected (RFC 1034 forbids CNAME coexisting with NS/SOA). Use type="ALIAS" for apex aliasing.',
|
|
1170
|
+
"",
|
|
1165
1171
|
'Example: create_dns_record({ domain: "micci.dk", type: "TXT", name: "@", content: "google-site-verification=C0V0scK48g2\u2026", ttl: 300 })'
|
|
1166
1172
|
].join("\n"),
|
|
1167
1173
|
input: {
|
|
1168
1174
|
zone_id: z7.string().optional().describe("Zone publicId."),
|
|
1169
1175
|
domain: z7.string().optional().describe("Apex or subdomain to resolve a hosted zone."),
|
|
1170
1176
|
type: z7.enum(DNS_RECORD_TYPES).describe("DNS record type."),
|
|
1171
|
-
name: z7.string().min(1).max(253).describe('"@" for apex, or label
|
|
1177
|
+
name: z7.string().min(1).max(253).describe('"@" for apex, or a bare label. Do NOT include the zone suffix.'),
|
|
1172
1178
|
content: z7.string().min(1).max(2e3).describe("Record value; TXT values are unquoted."),
|
|
1173
1179
|
ttl: z7.number().int().min(60).max(86400).optional().describe("TTL in seconds (default 3600)."),
|
|
1174
1180
|
priority: z7.number().int().min(0).max(65535).optional().describe("Required for MX / SRV.")
|
|
@@ -1222,7 +1228,7 @@ defineTool({
|
|
|
1222
1228
|
input: {
|
|
1223
1229
|
record_id: z7.string().describe("Record publicId."),
|
|
1224
1230
|
type: z7.enum(DNS_RECORD_TYPES).describe("DNS record type."),
|
|
1225
|
-
name: z7.string().min(1).max(253).describe('"@" for apex, or label.'),
|
|
1231
|
+
name: z7.string().min(1).max(253).describe('"@" for apex, or a bare label. Do NOT include the zone suffix.'),
|
|
1226
1232
|
content: z7.string().min(1).max(2e3).describe("Record value; TXT unquoted."),
|
|
1227
1233
|
ttl: z7.number().int().min(60).max(86400).optional().describe("TTL in seconds (default 3600)."),
|
|
1228
1234
|
priority: z7.number().int().min(0).max(65535).optional().describe("Required for MX / SRV.")
|
|
@@ -1341,7 +1347,8 @@ defineTool({
|
|
|
1341
1347
|
"",
|
|
1342
1348
|
"Inputs:",
|
|
1343
1349
|
' - hostname: the domain to add (e.g. "api.example.com").',
|
|
1344
|
-
" - service_id
|
|
1350
|
+
" - service_id: publicId of the service to bind the domain to.",
|
|
1351
|
+
" - path_prefix (optional): when set, only requests under this URL prefix route to this service. Use for path-based fan-out (e.g. `/api` to an api service, `/` to a web service on the same hostname).",
|
|
1345
1352
|
"",
|
|
1346
1353
|
"Returns: { domain: Domain } \u2014 includes dnsTargets you must configure (CNAME / A records).",
|
|
1347
1354
|
"",
|
|
@@ -1349,12 +1356,16 @@ defineTool({
|
|
|
1349
1356
|
].join("\n"),
|
|
1350
1357
|
input: {
|
|
1351
1358
|
hostname: z8.string().min(3).max(253).describe("Fully-qualified hostname (e.g. api.example.com)."),
|
|
1352
|
-
service_id: z8.string().
|
|
1359
|
+
service_id: z8.string().describe("Service publicId to bind to."),
|
|
1360
|
+
path_prefix: z8.string().optional().describe('Optional URL prefix for path-based routing (e.g. "/api").')
|
|
1353
1361
|
},
|
|
1354
1362
|
handler: async (args, ctx) => {
|
|
1355
1363
|
const teamId = await ctx.resolveTeamId();
|
|
1356
|
-
const input = {
|
|
1357
|
-
|
|
1364
|
+
const input = {
|
|
1365
|
+
domain: args.hostname,
|
|
1366
|
+
serviceId: args.service_id
|
|
1367
|
+
};
|
|
1368
|
+
if (args.path_prefix !== void 0) input.pathPrefix = args.path_prefix;
|
|
1358
1369
|
const response = await ctx.hoststack.domains.add(teamId, input);
|
|
1359
1370
|
const data = { domain: shapeDomain(response.domain) };
|
|
1360
1371
|
return respond({
|
|
@@ -1455,7 +1466,7 @@ defineTool({
|
|
|
1455
1466
|
" - service_id: publicId of the service.",
|
|
1456
1467
|
' - key: env-var name (e.g. "DATABASE_URL").',
|
|
1457
1468
|
" - value: new value (will be encrypted at rest if is_secret=true).",
|
|
1458
|
-
" - is_secret (optional): true marks the value as secret (masked on read).
|
|
1469
|
+
" - 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.",
|
|
1459
1470
|
"",
|
|
1460
1471
|
'Returns: { envVar: EnvVar, action: "created" | "updated" }.',
|
|
1461
1472
|
"",
|
|
@@ -1469,18 +1480,16 @@ defineTool({
|
|
|
1469
1480
|
},
|
|
1470
1481
|
handler: async (args, ctx) => {
|
|
1471
1482
|
const teamId = await ctx.resolveTeamId();
|
|
1472
|
-
const isSecret = args.is_secret ?? true;
|
|
1473
1483
|
const existing = await ctx.hoststack.envVars.list(teamId, args.service_id);
|
|
1474
1484
|
const match = existing.envVars.find((v) => v.key === args.key);
|
|
1475
1485
|
if (match) {
|
|
1486
|
+
const updatePayload = { value: args.value };
|
|
1487
|
+
if (args.is_secret !== void 0) updatePayload.isSecret = args.is_secret;
|
|
1476
1488
|
const response2 = await ctx.hoststack.envVars.update(
|
|
1477
1489
|
teamId,
|
|
1478
1490
|
args.service_id,
|
|
1479
1491
|
String(match.id),
|
|
1480
|
-
|
|
1481
|
-
value: args.value,
|
|
1482
|
-
isSecret
|
|
1483
|
-
}
|
|
1492
|
+
updatePayload
|
|
1484
1493
|
);
|
|
1485
1494
|
const data2 = {
|
|
1486
1495
|
envVar: shapeEnvVar(response2.envVar),
|
|
@@ -1491,7 +1500,7 @@ defineTool({
|
|
|
1491
1500
|
const response = await ctx.hoststack.envVars.create(teamId, args.service_id, {
|
|
1492
1501
|
key: args.key,
|
|
1493
1502
|
value: args.value,
|
|
1494
|
-
isSecret
|
|
1503
|
+
isSecret: args.is_secret ?? true
|
|
1495
1504
|
});
|
|
1496
1505
|
const data = {
|
|
1497
1506
|
envVar: shapeEnvVar(response.envVar),
|
|
@@ -1627,7 +1636,7 @@ defineTool({
|
|
|
1627
1636
|
"",
|
|
1628
1637
|
"Returns: { environment: Environment }.",
|
|
1629
1638
|
"",
|
|
1630
|
-
'Example: create_environment({ project_id: "prj_abc", name: "Staging", type: "staging" }) \u2192 { environment: { id: 7, publicId: "
|
|
1639
|
+
'Example: create_environment({ project_id: "prj_abc", name: "Staging", type: "staging" }) \u2192 { environment: { id: 7, publicId: "env_\u2026", name: "Staging", type: "staging" } }'
|
|
1631
1640
|
].join("\n"),
|
|
1632
1641
|
input: {
|
|
1633
1642
|
project_id: z10.string().describe("Project publicId."),
|
|
@@ -1664,7 +1673,7 @@ defineTool({
|
|
|
1664
1673
|
"",
|
|
1665
1674
|
"Returns: { success: true } on success.",
|
|
1666
1675
|
"",
|
|
1667
|
-
'Example: delete_environment({ project_id: "prj_abc", environment_id: "
|
|
1676
|
+
'Example: delete_environment({ project_id: "prj_abc", environment_id: "env_xyz" }) \u2192 { success: true }'
|
|
1668
1677
|
].join("\n"),
|
|
1669
1678
|
input: {
|
|
1670
1679
|
project_id: z10.string().describe("Project publicId."),
|
|
@@ -1696,7 +1705,7 @@ defineTool({
|
|
|
1696
1705
|
"",
|
|
1697
1706
|
"Returns: { deploy: Deploy } \u2014 the new deploy on the target service.",
|
|
1698
1707
|
"",
|
|
1699
|
-
'Example: promote_deploy({ service_id: "svc_staging", deploy_id: "dpl_built", target_environment_id: "
|
|
1708
|
+
'Example: promote_deploy({ service_id: "svc_staging", deploy_id: "dpl_built", target_environment_id: "env_prod" }) \u2192 { deploy: { id: 99, status: "pending", trigger: "rollback", dockerImageId: "sha256:\u2026" } }'
|
|
1700
1709
|
].join("\n"),
|
|
1701
1710
|
input: {
|
|
1702
1711
|
service_id: z10.string().describe("Source service publicId."),
|
|
@@ -2056,6 +2065,24 @@ defineTool({
|
|
|
2056
2065
|
|
|
2057
2066
|
// src/tools/services.ts
|
|
2058
2067
|
import { z as z13 } from "zod";
|
|
2068
|
+
var DEV_ENV_IMAGE = "hoststack/dev-env:claude";
|
|
2069
|
+
var DEV_ENV_VOLUME = { name: "workspace", mountPath: "/workspace", sizeGb: 10 };
|
|
2070
|
+
var SERVICE_TYPES = [
|
|
2071
|
+
"web_service",
|
|
2072
|
+
"private_service",
|
|
2073
|
+
"worker",
|
|
2074
|
+
"cron_job",
|
|
2075
|
+
"static_site"
|
|
2076
|
+
];
|
|
2077
|
+
var SERVICE_PLANS = [
|
|
2078
|
+
"pico",
|
|
2079
|
+
"nano",
|
|
2080
|
+
"micro",
|
|
2081
|
+
"starter",
|
|
2082
|
+
"standard",
|
|
2083
|
+
"pro_standard",
|
|
2084
|
+
"pro_large"
|
|
2085
|
+
];
|
|
2059
2086
|
defineTool({
|
|
2060
2087
|
name: "list_services",
|
|
2061
2088
|
category: "services",
|
|
@@ -2107,10 +2134,171 @@ defineTool({
|
|
|
2107
2134
|
args.status ? `status=${args.status}` : null,
|
|
2108
2135
|
args.type ? `type=${args.type}` : null
|
|
2109
2136
|
].filter(Boolean).join(", ");
|
|
2110
|
-
const summary = data.items.length === 0 ? filterDesc ? `No services match the given filters (${filterDesc}).` : "No services yet \u2014 create one with create_service or via the dashboard." : `Found ${data.items.length} service${data.items.length === 1 ? "" : "s"}${filterDesc ? ` matching ${filterDesc}` : ""}.`;
|
|
2137
|
+
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}` : ""}.`;
|
|
2111
2138
|
return respond({ summary, data });
|
|
2112
2139
|
}
|
|
2113
2140
|
});
|
|
2141
|
+
defineTool({
|
|
2142
|
+
name: "create_service",
|
|
2143
|
+
category: "services",
|
|
2144
|
+
description: [
|
|
2145
|
+
"Create a new service in a project. Source is either a connected git repo (github_repo_id) OR a pre-built docker_image \u2014 never both.",
|
|
2146
|
+
"",
|
|
2147
|
+
"When to use: the user wants to deploy something new. For a one-command AI dev environment specifically, prefer create_dev_environment (it also attaches the /workspace volume and sets the MCP keys).",
|
|
2148
|
+
"",
|
|
2149
|
+
"Inputs:",
|
|
2150
|
+
' - project_id: numeric id or publicId ("prj_\u2026") of the target project.',
|
|
2151
|
+
" - name: service name (1\u2013100 chars).",
|
|
2152
|
+
' - type: "web_service" | "private_service" | "worker" | "cron_job" | "static_site".',
|
|
2153
|
+
" - docker_image (optional): pre-built image ref to deploy instead of building from source.",
|
|
2154
|
+
" - github_repo_id (optional): connect a previously-linked GitHub repo by numeric id.",
|
|
2155
|
+
' - branch (optional): git branch (default "main").',
|
|
2156
|
+
" - install_command / build_command / start_command (optional): build/run shell commands. start_command is required for web/private services without a docker_image.",
|
|
2157
|
+
" - cron_schedule (optional): cron expression \u2014 required for cron_job.",
|
|
2158
|
+
' - publish_path (optional): static-site output dir (e.g. "dist").',
|
|
2159
|
+
' - runtime (optional): "node" | "bun" | "python" | \u2026 (auto-detected from a repo when omitted).',
|
|
2160
|
+
' - plan (optional): service size (default "micro").',
|
|
2161
|
+
" - environment_id (optional): bind to a specific environment; defaults to the project Production env.",
|
|
2162
|
+
" - auto_deploy (optional, default true): trigger the first deploy immediately when a source is present.",
|
|
2163
|
+
"",
|
|
2164
|
+
"Returns: { service: Service, deployId: number | null }.",
|
|
2165
|
+
"",
|
|
2166
|
+
'Example: create_service({ project_id: "prj_abc", name: "api", type: "web_service", github_repo_id: 42 }) \u2192 { service: { publicId: "svc_\u2026" }, deployId: 1234 }'
|
|
2167
|
+
].join("\n"),
|
|
2168
|
+
input: {
|
|
2169
|
+
project_id: z13.union([z13.number().int().positive(), z13.string()]).describe("Target project \u2014 numeric id or publicId."),
|
|
2170
|
+
name: z13.string().min(1).max(100).describe("Service name (1\u2013100 chars)."),
|
|
2171
|
+
type: z13.enum(SERVICE_TYPES).describe("Service type."),
|
|
2172
|
+
docker_image: z13.string().max(500).optional().describe("Pre-built image ref. Mutually exclusive with github_repo_id."),
|
|
2173
|
+
github_repo_id: z13.number().int().positive().optional().describe("Linked GitHub repo numeric id. Mutually exclusive with docker_image."),
|
|
2174
|
+
branch: z13.string().max(200).optional().describe('Git branch (default "main").'),
|
|
2175
|
+
install_command: z13.string().max(1e3).optional().describe("Install shell command."),
|
|
2176
|
+
build_command: z13.string().max(1e3).optional().describe("Build shell command."),
|
|
2177
|
+
start_command: z13.string().max(1e3).optional().describe("Start shell command (required for web/private services without an image)."),
|
|
2178
|
+
cron_schedule: z13.string().max(100).optional().describe("Cron expression \u2014 required for cron_job."),
|
|
2179
|
+
publish_path: z13.string().max(500).optional().describe("Static-site output dir."),
|
|
2180
|
+
runtime: z13.string().max(50).optional().describe("Runtime hint (node/bun/python/\u2026)."),
|
|
2181
|
+
plan: z13.enum(SERVICE_PLANS).optional().describe('Service size (default "micro").'),
|
|
2182
|
+
environment_id: z13.union([z13.number().int().positive(), z13.string()]).optional().describe("Bind to a specific environment; defaults to Production."),
|
|
2183
|
+
auto_deploy: z13.boolean().optional().describe("Trigger the first deploy immediately (default true).")
|
|
2184
|
+
},
|
|
2185
|
+
handler: async (args, ctx) => {
|
|
2186
|
+
const teamId = await ctx.resolveTeamId();
|
|
2187
|
+
const projectId = await ctx.hoststack.resolveId(args.project_id, {
|
|
2188
|
+
kind: "project",
|
|
2189
|
+
teamId
|
|
2190
|
+
});
|
|
2191
|
+
const input = {
|
|
2192
|
+
name: args.name,
|
|
2193
|
+
type: args.type,
|
|
2194
|
+
projectId
|
|
2195
|
+
};
|
|
2196
|
+
if (args.docker_image !== void 0) input.dockerImage = args.docker_image;
|
|
2197
|
+
if (args.github_repo_id !== void 0) input.githubRepoId = args.github_repo_id;
|
|
2198
|
+
if (args.branch !== void 0) input.branch = args.branch;
|
|
2199
|
+
if (args.install_command !== void 0) input.installCommand = args.install_command;
|
|
2200
|
+
if (args.build_command !== void 0) input.buildCommand = args.build_command;
|
|
2201
|
+
if (args.start_command !== void 0) input.startCommand = args.start_command;
|
|
2202
|
+
if (args.cron_schedule !== void 0) input.cronSchedule = args.cron_schedule;
|
|
2203
|
+
if (args.publish_path !== void 0) input.publishPath = args.publish_path;
|
|
2204
|
+
if (args.runtime !== void 0) input.runtime = args.runtime;
|
|
2205
|
+
if (args.plan !== void 0) input.plan = args.plan;
|
|
2206
|
+
if (args.auto_deploy !== void 0) input.autoDeploy = args.auto_deploy;
|
|
2207
|
+
if (args.environment_id !== void 0) {
|
|
2208
|
+
input.environmentId = await ctx.hoststack.resolveId(args.environment_id, {
|
|
2209
|
+
kind: "environment",
|
|
2210
|
+
teamId
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
2213
|
+
const response = await ctx.hoststack.services.create(teamId, input);
|
|
2214
|
+
const data = {
|
|
2215
|
+
service: shapeService(response.service),
|
|
2216
|
+
deployId: response.deployId ?? null
|
|
2217
|
+
};
|
|
2218
|
+
const publicId = data.service && "publicId" in data.service ? data.service.publicId : "unknown";
|
|
2219
|
+
return respond({ summary: `Created service "${args.name}" (${publicId}).`, data });
|
|
2220
|
+
}
|
|
2221
|
+
});
|
|
2222
|
+
defineTool({
|
|
2223
|
+
name: "create_dev_environment",
|
|
2224
|
+
category: "services",
|
|
2225
|
+
description: [
|
|
2226
|
+
"Spin up an AI dev environment in one call: a private service from the agentic dev-env image (Claude Code + Codex + OpenCode + MCPs preinstalled), with a persistent /workspace volume and the MCP API keys set, then fire the first deploy.",
|
|
2227
|
+
"",
|
|
2228
|
+
`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.`,
|
|
2229
|
+
"",
|
|
2230
|
+
"Inputs:",
|
|
2231
|
+
' - project_id: numeric id or publicId ("prj_\u2026") of the target project.',
|
|
2232
|
+
' - name (optional): service name (default "dev-environment").',
|
|
2233
|
+
' - plan (optional): service size (default "micro").',
|
|
2234
|
+
" - disk_gb (optional): /workspace volume size in GB (default 10, 1\u2013100).",
|
|
2235
|
+
" - hoststack_api_key (optional): sets HOSTSTACK_API_KEY so the hoststack MCP works inside the container.",
|
|
2236
|
+
" - poststack_api_key (optional): sets POSTSTACK_API_KEY so the poststack MCP works inside the container.",
|
|
2237
|
+
"",
|
|
2238
|
+
"Returns: { service: Service, volumeAttached: boolean, deployId: number | null }. Open the Terminal tab on the service (dashboard or phone) once it is running; log in once with `claude /login` inside the container.",
|
|
2239
|
+
"",
|
|
2240
|
+
'Example: create_dev_environment({ project_id: "prj_abc", name: "scratch", hoststack_api_key: "hs_live_\u2026" })'
|
|
2241
|
+
].join("\n"),
|
|
2242
|
+
input: {
|
|
2243
|
+
project_id: z13.union([z13.number().int().positive(), z13.string()]).describe("Target project \u2014 numeric id or publicId."),
|
|
2244
|
+
name: z13.string().min(1).max(100).optional().describe('Service name (default "dev-environment").'),
|
|
2245
|
+
plan: z13.enum(SERVICE_PLANS).optional().describe('Service size (default "micro").'),
|
|
2246
|
+
disk_gb: z13.number().int().min(1).max(100).optional().describe("/workspace volume size in GB (default 10)."),
|
|
2247
|
+
hoststack_api_key: z13.string().optional().describe("Value for HOSTSTACK_API_KEY (enables the hoststack MCP in-container)."),
|
|
2248
|
+
poststack_api_key: z13.string().optional().describe("Value for POSTSTACK_API_KEY (enables the poststack MCP in-container).")
|
|
2249
|
+
},
|
|
2250
|
+
handler: async (args, ctx) => {
|
|
2251
|
+
const teamId = await ctx.resolveTeamId();
|
|
2252
|
+
const projectId = await ctx.hoststack.resolveId(args.project_id, {
|
|
2253
|
+
kind: "project",
|
|
2254
|
+
teamId
|
|
2255
|
+
});
|
|
2256
|
+
const name = args.name ?? "dev-environment";
|
|
2257
|
+
const sizeGb = args.disk_gb ?? DEV_ENV_VOLUME.sizeGb;
|
|
2258
|
+
const createInput = {
|
|
2259
|
+
name,
|
|
2260
|
+
type: "private_service",
|
|
2261
|
+
projectId,
|
|
2262
|
+
dockerImage: DEV_ENV_IMAGE,
|
|
2263
|
+
autoDeploy: false
|
|
2264
|
+
};
|
|
2265
|
+
if (args.plan !== void 0) createInput.plan = args.plan;
|
|
2266
|
+
const created = await ctx.hoststack.services.create(teamId, createInput);
|
|
2267
|
+
const service = created.service;
|
|
2268
|
+
const envVars = [];
|
|
2269
|
+
if (args.hoststack_api_key)
|
|
2270
|
+
envVars.push({
|
|
2271
|
+
key: "HOSTSTACK_API_KEY",
|
|
2272
|
+
value: args.hoststack_api_key,
|
|
2273
|
+
isSecret: true
|
|
2274
|
+
});
|
|
2275
|
+
if (args.poststack_api_key)
|
|
2276
|
+
envVars.push({
|
|
2277
|
+
key: "POSTSTACK_API_KEY",
|
|
2278
|
+
value: args.poststack_api_key,
|
|
2279
|
+
isSecret: true
|
|
2280
|
+
});
|
|
2281
|
+
if (envVars.length > 0) {
|
|
2282
|
+
await ctx.hoststack.envVars.bulkSet(teamId, service.id, { vars: envVars });
|
|
2283
|
+
}
|
|
2284
|
+
let volumeAttached = false;
|
|
2285
|
+
try {
|
|
2286
|
+
await ctx.hoststack.volumes.create(teamId, service.id, {
|
|
2287
|
+
name: DEV_ENV_VOLUME.name,
|
|
2288
|
+
mountPath: DEV_ENV_VOLUME.mountPath,
|
|
2289
|
+
sizeGb
|
|
2290
|
+
});
|
|
2291
|
+
volumeAttached = true;
|
|
2292
|
+
} catch {
|
|
2293
|
+
}
|
|
2294
|
+
const deploy = await ctx.hoststack.deploys.trigger(teamId, service.id);
|
|
2295
|
+
const deployId = deploy.deploy?.id ?? null;
|
|
2296
|
+
return respond({
|
|
2297
|
+
summary: `Created AI dev environment "${name}" (${service.publicId})${volumeAttached ? " with a /workspace volume" : ""} \u2014 deploying. Open its Terminal tab once running.`,
|
|
2298
|
+
data: { service: shapeService(service), volumeAttached, deployId }
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2114
2302
|
defineTool({
|
|
2115
2303
|
name: "get_service",
|
|
2116
2304
|
category: "services",
|
|
@@ -2718,7 +2906,7 @@ defineTool({
|
|
|
2718
2906
|
|
|
2719
2907
|
// src/server-factory.ts
|
|
2720
2908
|
var PACKAGE_NAME = "hoststack";
|
|
2721
|
-
var PACKAGE_VERSION = "0.
|
|
2909
|
+
var PACKAGE_VERSION = "0.9.1";
|
|
2722
2910
|
function createMcpServer(options) {
|
|
2723
2911
|
const baseUrl = (options.baseUrl ?? "https://hoststack.dev").replace(/\/$/, "");
|
|
2724
2912
|
const hoststack = new HostStack({ apiKey: options.apiKey, baseUrl });
|