@hoststack.dev/mcp 0.9.1 → 0.10.1
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 +3 -3
- package/dist/hoststack-mcp.js +191 -8
- package/dist/hoststack-mcp.js.map +1 -1
- package/dist/index.js +191 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1158,20 +1158,23 @@ defineTool({
|
|
|
1158
1158
|
"",
|
|
1159
1159
|
"Inputs:",
|
|
1160
1160
|
' - type: "A" | "AAAA" | "CNAME" | "MX" | "TXT" | "NS" | "SRV" | "CAA" | "ALIAS".',
|
|
1161
|
-
' - name: "@" for the zone apex, or a subdomain label ("www", "api", "_acme-challenge").',
|
|
1162
|
-
" - 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.",
|
|
1163
1163
|
" - ttl (optional): 60\u201386400 seconds, default 3600.",
|
|
1164
1164
|
" - priority (optional, required for MX and SRV): 0\u201365535.",
|
|
1165
1165
|
"",
|
|
1166
1166
|
"Returns: { record: Record } \u2014 see get_dns_record for the shape.",
|
|
1167
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
|
+
"",
|
|
1168
1171
|
'Example: create_dns_record({ domain: "micci.dk", type: "TXT", name: "@", content: "google-site-verification=C0V0scK48g2\u2026", ttl: 300 })'
|
|
1169
1172
|
].join("\n"),
|
|
1170
1173
|
input: {
|
|
1171
1174
|
zone_id: z7.string().optional().describe("Zone publicId."),
|
|
1172
1175
|
domain: z7.string().optional().describe("Apex or subdomain to resolve a hosted zone."),
|
|
1173
1176
|
type: z7.enum(DNS_RECORD_TYPES).describe("DNS record type."),
|
|
1174
|
-
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.'),
|
|
1175
1178
|
content: z7.string().min(1).max(2e3).describe("Record value; TXT values are unquoted."),
|
|
1176
1179
|
ttl: z7.number().int().min(60).max(86400).optional().describe("TTL in seconds (default 3600)."),
|
|
1177
1180
|
priority: z7.number().int().min(0).max(65535).optional().describe("Required for MX / SRV.")
|
|
@@ -1225,7 +1228,7 @@ defineTool({
|
|
|
1225
1228
|
input: {
|
|
1226
1229
|
record_id: z7.string().describe("Record publicId."),
|
|
1227
1230
|
type: z7.enum(DNS_RECORD_TYPES).describe("DNS record type."),
|
|
1228
|
-
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.'),
|
|
1229
1232
|
content: z7.string().min(1).max(2e3).describe("Record value; TXT unquoted."),
|
|
1230
1233
|
ttl: z7.number().int().min(60).max(86400).optional().describe("TTL in seconds (default 3600)."),
|
|
1231
1234
|
priority: z7.number().int().min(0).max(65535).optional().describe("Required for MX / SRV.")
|
|
@@ -1941,6 +1944,7 @@ defineTool({
|
|
|
1941
1944
|
|
|
1942
1945
|
// src/tools/projects.ts
|
|
1943
1946
|
import { z as z12 } from "zod";
|
|
1947
|
+
var REGION_IDS = ["eu-central-1", "eu-central-2", "eu-west-1", "us-east-1"];
|
|
1944
1948
|
defineTool({
|
|
1945
1949
|
name: "list_projects",
|
|
1946
1950
|
category: "projects",
|
|
@@ -1973,16 +1977,16 @@ defineTool({
|
|
|
1973
1977
|
"Inputs:",
|
|
1974
1978
|
" - name: human-readable project name (1\u201360 chars).",
|
|
1975
1979
|
" - description (optional): short blurb shown in the dashboard.",
|
|
1976
|
-
' - region (optional): "
|
|
1980
|
+
' - region (optional): "eu-central-1" (Falkenstein) | "eu-central-2" (Nuremberg) | "eu-west-1" (Helsinki) | "us-east-1" (Ashburn). Defaults to eu-central-2.',
|
|
1977
1981
|
"",
|
|
1978
1982
|
"Returns: { project: Project } \u2014 includes the new id and publicId.",
|
|
1979
1983
|
"",
|
|
1980
|
-
'Example: create_project({ name: "billing-api", description: "Stripe webhooks", region: "
|
|
1984
|
+
'Example: create_project({ name: "billing-api", description: "Stripe webhooks", region: "eu-central-1" }) \u2192 { project: { id: 12, publicId: "prj_\u2026", \u2026 } }'
|
|
1981
1985
|
].join("\n"),
|
|
1982
1986
|
input: {
|
|
1983
1987
|
name: z12.string().min(1).max(60).describe("Project name (1\u201360 chars)."),
|
|
1984
1988
|
description: z12.string().max(500).optional().describe("Short description (\u2264500 chars)."),
|
|
1985
|
-
region: z12.enum(
|
|
1989
|
+
region: z12.enum(REGION_IDS).optional().describe("Region: eu-central-1 | eu-central-2 | eu-west-1 | us-east-1.")
|
|
1986
1990
|
},
|
|
1987
1991
|
handler: async (args, ctx) => {
|
|
1988
1992
|
const teamId = await ctx.resolveTeamId();
|
|
@@ -2062,6 +2066,24 @@ defineTool({
|
|
|
2062
2066
|
|
|
2063
2067
|
// src/tools/services.ts
|
|
2064
2068
|
import { z as z13 } from "zod";
|
|
2069
|
+
var DEV_ENV_IMAGE = "hoststack/dev-env:claude";
|
|
2070
|
+
var DEV_ENV_VOLUME = { name: "workspace", mountPath: "/workspace", sizeGb: 10 };
|
|
2071
|
+
var SERVICE_TYPES = [
|
|
2072
|
+
"web_service",
|
|
2073
|
+
"private_service",
|
|
2074
|
+
"worker",
|
|
2075
|
+
"cron_job",
|
|
2076
|
+
"static_site"
|
|
2077
|
+
];
|
|
2078
|
+
var SERVICE_PLANS = [
|
|
2079
|
+
"pico",
|
|
2080
|
+
"nano",
|
|
2081
|
+
"micro",
|
|
2082
|
+
"starter",
|
|
2083
|
+
"standard",
|
|
2084
|
+
"pro_standard",
|
|
2085
|
+
"pro_large"
|
|
2086
|
+
];
|
|
2065
2087
|
defineTool({
|
|
2066
2088
|
name: "list_services",
|
|
2067
2089
|
category: "services",
|
|
@@ -2113,10 +2135,171 @@ defineTool({
|
|
|
2113
2135
|
args.status ? `status=${args.status}` : null,
|
|
2114
2136
|
args.type ? `type=${args.type}` : null
|
|
2115
2137
|
].filter(Boolean).join(", ");
|
|
2116
|
-
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}` : ""}.`;
|
|
2138
|
+
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}` : ""}.`;
|
|
2117
2139
|
return respond({ summary, data });
|
|
2118
2140
|
}
|
|
2119
2141
|
});
|
|
2142
|
+
defineTool({
|
|
2143
|
+
name: "create_service",
|
|
2144
|
+
category: "services",
|
|
2145
|
+
description: [
|
|
2146
|
+
"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.",
|
|
2147
|
+
"",
|
|
2148
|
+
"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).",
|
|
2149
|
+
"",
|
|
2150
|
+
"Inputs:",
|
|
2151
|
+
' - project_id: numeric id or publicId ("prj_\u2026") of the target project.',
|
|
2152
|
+
" - name: service name (1\u2013100 chars).",
|
|
2153
|
+
' - type: "web_service" | "private_service" | "worker" | "cron_job" | "static_site".',
|
|
2154
|
+
" - docker_image (optional): pre-built image ref to deploy instead of building from source.",
|
|
2155
|
+
" - github_repo_id (optional): connect a previously-linked GitHub repo by numeric id.",
|
|
2156
|
+
' - branch (optional): git branch (default "main").',
|
|
2157
|
+
" - install_command / build_command / start_command (optional): build/run shell commands. start_command is required for web/private services without a docker_image.",
|
|
2158
|
+
" - cron_schedule (optional): cron expression \u2014 required for cron_job.",
|
|
2159
|
+
' - publish_path (optional): static-site output dir (e.g. "dist").',
|
|
2160
|
+
' - runtime (optional): "node" | "bun" | "python" | \u2026 (auto-detected from a repo when omitted).',
|
|
2161
|
+
' - plan (optional): service size (default "micro").',
|
|
2162
|
+
" - environment_id (optional): bind to a specific environment; defaults to the project Production env.",
|
|
2163
|
+
" - auto_deploy (optional, default true): trigger the first deploy immediately when a source is present.",
|
|
2164
|
+
"",
|
|
2165
|
+
"Returns: { service: Service, deployId: number | null }.",
|
|
2166
|
+
"",
|
|
2167
|
+
'Example: create_service({ project_id: "prj_abc", name: "api", type: "web_service", github_repo_id: 42 }) \u2192 { service: { publicId: "svc_\u2026" }, deployId: 1234 }'
|
|
2168
|
+
].join("\n"),
|
|
2169
|
+
input: {
|
|
2170
|
+
project_id: z13.union([z13.number().int().positive(), z13.string()]).describe("Target project \u2014 numeric id or publicId."),
|
|
2171
|
+
name: z13.string().min(1).max(100).describe("Service name (1\u2013100 chars)."),
|
|
2172
|
+
type: z13.enum(SERVICE_TYPES).describe("Service type."),
|
|
2173
|
+
docker_image: z13.string().max(500).optional().describe("Pre-built image ref. Mutually exclusive with github_repo_id."),
|
|
2174
|
+
github_repo_id: z13.number().int().positive().optional().describe("Linked GitHub repo numeric id. Mutually exclusive with docker_image."),
|
|
2175
|
+
branch: z13.string().max(200).optional().describe('Git branch (default "main").'),
|
|
2176
|
+
install_command: z13.string().max(1e3).optional().describe("Install shell command."),
|
|
2177
|
+
build_command: z13.string().max(1e3).optional().describe("Build shell command."),
|
|
2178
|
+
start_command: z13.string().max(1e3).optional().describe("Start shell command (required for web/private services without an image)."),
|
|
2179
|
+
cron_schedule: z13.string().max(100).optional().describe("Cron expression \u2014 required for cron_job."),
|
|
2180
|
+
publish_path: z13.string().max(500).optional().describe("Static-site output dir."),
|
|
2181
|
+
runtime: z13.string().max(50).optional().describe("Runtime hint (node/bun/python/\u2026)."),
|
|
2182
|
+
plan: z13.enum(SERVICE_PLANS).optional().describe('Service size (default "micro").'),
|
|
2183
|
+
environment_id: z13.union([z13.number().int().positive(), z13.string()]).optional().describe("Bind to a specific environment; defaults to Production."),
|
|
2184
|
+
auto_deploy: z13.boolean().optional().describe("Trigger the first deploy immediately (default true).")
|
|
2185
|
+
},
|
|
2186
|
+
handler: async (args, ctx) => {
|
|
2187
|
+
const teamId = await ctx.resolveTeamId();
|
|
2188
|
+
const projectId = await ctx.hoststack.resolveId(args.project_id, {
|
|
2189
|
+
kind: "project",
|
|
2190
|
+
teamId
|
|
2191
|
+
});
|
|
2192
|
+
const input = {
|
|
2193
|
+
name: args.name,
|
|
2194
|
+
type: args.type,
|
|
2195
|
+
projectId
|
|
2196
|
+
};
|
|
2197
|
+
if (args.docker_image !== void 0) input.dockerImage = args.docker_image;
|
|
2198
|
+
if (args.github_repo_id !== void 0) input.githubRepoId = args.github_repo_id;
|
|
2199
|
+
if (args.branch !== void 0) input.branch = args.branch;
|
|
2200
|
+
if (args.install_command !== void 0) input.installCommand = args.install_command;
|
|
2201
|
+
if (args.build_command !== void 0) input.buildCommand = args.build_command;
|
|
2202
|
+
if (args.start_command !== void 0) input.startCommand = args.start_command;
|
|
2203
|
+
if (args.cron_schedule !== void 0) input.cronSchedule = args.cron_schedule;
|
|
2204
|
+
if (args.publish_path !== void 0) input.publishPath = args.publish_path;
|
|
2205
|
+
if (args.runtime !== void 0) input.runtime = args.runtime;
|
|
2206
|
+
if (args.plan !== void 0) input.plan = args.plan;
|
|
2207
|
+
if (args.auto_deploy !== void 0) input.autoDeploy = args.auto_deploy;
|
|
2208
|
+
if (args.environment_id !== void 0) {
|
|
2209
|
+
input.environmentId = await ctx.hoststack.resolveId(args.environment_id, {
|
|
2210
|
+
kind: "environment",
|
|
2211
|
+
teamId
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
const response = await ctx.hoststack.services.create(teamId, input);
|
|
2215
|
+
const data = {
|
|
2216
|
+
service: shapeService(response.service),
|
|
2217
|
+
deployId: response.deployId ?? null
|
|
2218
|
+
};
|
|
2219
|
+
const publicId = data.service && "publicId" in data.service ? data.service.publicId : "unknown";
|
|
2220
|
+
return respond({ summary: `Created service "${args.name}" (${publicId}).`, data });
|
|
2221
|
+
}
|
|
2222
|
+
});
|
|
2223
|
+
defineTool({
|
|
2224
|
+
name: "create_dev_environment",
|
|
2225
|
+
category: "services",
|
|
2226
|
+
description: [
|
|
2227
|
+
"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.",
|
|
2228
|
+
"",
|
|
2229
|
+
`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.`,
|
|
2230
|
+
"",
|
|
2231
|
+
"Inputs:",
|
|
2232
|
+
' - project_id: numeric id or publicId ("prj_\u2026") of the target project.',
|
|
2233
|
+
' - name (optional): service name (default "dev-environment").',
|
|
2234
|
+
' - plan (optional): service size (default "micro").',
|
|
2235
|
+
" - disk_gb (optional): /workspace volume size in GB (default 10, 1\u2013100).",
|
|
2236
|
+
" - hoststack_api_key (optional): sets HOSTSTACK_API_KEY so the hoststack MCP works inside the container.",
|
|
2237
|
+
" - poststack_api_key (optional): sets POSTSTACK_API_KEY so the poststack MCP works inside the container.",
|
|
2238
|
+
"",
|
|
2239
|
+
"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.",
|
|
2240
|
+
"",
|
|
2241
|
+
'Example: create_dev_environment({ project_id: "prj_abc", name: "scratch", hoststack_api_key: "hs_live_\u2026" })'
|
|
2242
|
+
].join("\n"),
|
|
2243
|
+
input: {
|
|
2244
|
+
project_id: z13.union([z13.number().int().positive(), z13.string()]).describe("Target project \u2014 numeric id or publicId."),
|
|
2245
|
+
name: z13.string().min(1).max(100).optional().describe('Service name (default "dev-environment").'),
|
|
2246
|
+
plan: z13.enum(SERVICE_PLANS).optional().describe('Service size (default "micro").'),
|
|
2247
|
+
disk_gb: z13.number().int().min(1).max(100).optional().describe("/workspace volume size in GB (default 10)."),
|
|
2248
|
+
hoststack_api_key: z13.string().optional().describe("Value for HOSTSTACK_API_KEY (enables the hoststack MCP in-container)."),
|
|
2249
|
+
poststack_api_key: z13.string().optional().describe("Value for POSTSTACK_API_KEY (enables the poststack MCP in-container).")
|
|
2250
|
+
},
|
|
2251
|
+
handler: async (args, ctx) => {
|
|
2252
|
+
const teamId = await ctx.resolveTeamId();
|
|
2253
|
+
const projectId = await ctx.hoststack.resolveId(args.project_id, {
|
|
2254
|
+
kind: "project",
|
|
2255
|
+
teamId
|
|
2256
|
+
});
|
|
2257
|
+
const name = args.name ?? "dev-environment";
|
|
2258
|
+
const sizeGb = args.disk_gb ?? DEV_ENV_VOLUME.sizeGb;
|
|
2259
|
+
const createInput = {
|
|
2260
|
+
name,
|
|
2261
|
+
type: "private_service",
|
|
2262
|
+
projectId,
|
|
2263
|
+
dockerImage: DEV_ENV_IMAGE,
|
|
2264
|
+
autoDeploy: false
|
|
2265
|
+
};
|
|
2266
|
+
if (args.plan !== void 0) createInput.plan = args.plan;
|
|
2267
|
+
const created = await ctx.hoststack.services.create(teamId, createInput);
|
|
2268
|
+
const service = created.service;
|
|
2269
|
+
const envVars = [];
|
|
2270
|
+
if (args.hoststack_api_key)
|
|
2271
|
+
envVars.push({
|
|
2272
|
+
key: "HOSTSTACK_API_KEY",
|
|
2273
|
+
value: args.hoststack_api_key,
|
|
2274
|
+
isSecret: true
|
|
2275
|
+
});
|
|
2276
|
+
if (args.poststack_api_key)
|
|
2277
|
+
envVars.push({
|
|
2278
|
+
key: "POSTSTACK_API_KEY",
|
|
2279
|
+
value: args.poststack_api_key,
|
|
2280
|
+
isSecret: true
|
|
2281
|
+
});
|
|
2282
|
+
if (envVars.length > 0) {
|
|
2283
|
+
await ctx.hoststack.envVars.bulkSet(teamId, service.id, { vars: envVars });
|
|
2284
|
+
}
|
|
2285
|
+
let volumeAttached = false;
|
|
2286
|
+
try {
|
|
2287
|
+
await ctx.hoststack.volumes.create(teamId, service.id, {
|
|
2288
|
+
name: DEV_ENV_VOLUME.name,
|
|
2289
|
+
mountPath: DEV_ENV_VOLUME.mountPath,
|
|
2290
|
+
sizeGb
|
|
2291
|
+
});
|
|
2292
|
+
volumeAttached = true;
|
|
2293
|
+
} catch {
|
|
2294
|
+
}
|
|
2295
|
+
const deploy = await ctx.hoststack.deploys.trigger(teamId, service.id);
|
|
2296
|
+
const deployId = deploy.deploy?.id ?? null;
|
|
2297
|
+
return respond({
|
|
2298
|
+
summary: `Created AI dev environment "${name}" (${service.publicId})${volumeAttached ? " with a /workspace volume" : ""} \u2014 deploying. Open its Terminal tab once running.`,
|
|
2299
|
+
data: { service: shapeService(service), volumeAttached, deployId }
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
});
|
|
2120
2303
|
defineTool({
|
|
2121
2304
|
name: "get_service",
|
|
2122
2305
|
category: "services",
|