@hoststack.dev/mcp 0.9.1 → 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.
@@ -1154,20 +1154,23 @@ defineTool({
1154
1154
  "",
1155
1155
  "Inputs:",
1156
1156
  ' - type: "A" | "AAAA" | "CNAME" | "MX" | "TXT" | "NS" | "SRV" | "CAA" | "ALIAS".',
1157
- ' - name: "@" for the zone apex, or a subdomain label ("www", "api", "_acme-challenge").',
1158
- " - content: the literal record value. For TXT pass the unquoted string \u2014 the PowerDNS layer handles quoting.",
1157
+ ' - 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.',
1158
+ " - 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.",
1159
1159
  " - ttl (optional): 60\u201386400 seconds, default 3600.",
1160
1160
  " - priority (optional, required for MX and SRV): 0\u201365535.",
1161
1161
  "",
1162
1162
  "Returns: { record: Record } \u2014 see get_dns_record for the shape.",
1163
1163
  "",
1164
+ "Notes:",
1165
+ ' - CNAME at the apex is rejected (RFC 1034 forbids CNAME coexisting with NS/SOA). Use type="ALIAS" for apex aliasing.',
1166
+ "",
1164
1167
  'Example: create_dns_record({ domain: "micci.dk", type: "TXT", name: "@", content: "google-site-verification=C0V0scK48g2\u2026", ttl: 300 })'
1165
1168
  ].join("\n"),
1166
1169
  input: {
1167
1170
  zone_id: z7.string().optional().describe("Zone publicId."),
1168
1171
  domain: z7.string().optional().describe("Apex or subdomain to resolve a hosted zone."),
1169
1172
  type: z7.enum(DNS_RECORD_TYPES).describe("DNS record type."),
1170
- name: z7.string().min(1).max(253).describe('"@" for apex, or label (no trailing dot).'),
1173
+ name: z7.string().min(1).max(253).describe('"@" for apex, or a bare label. Do NOT include the zone suffix.'),
1171
1174
  content: z7.string().min(1).max(2e3).describe("Record value; TXT values are unquoted."),
1172
1175
  ttl: z7.number().int().min(60).max(86400).optional().describe("TTL in seconds (default 3600)."),
1173
1176
  priority: z7.number().int().min(0).max(65535).optional().describe("Required for MX / SRV.")
@@ -1221,7 +1224,7 @@ defineTool({
1221
1224
  input: {
1222
1225
  record_id: z7.string().describe("Record publicId."),
1223
1226
  type: z7.enum(DNS_RECORD_TYPES).describe("DNS record type."),
1224
- name: z7.string().min(1).max(253).describe('"@" for apex, or label.'),
1227
+ name: z7.string().min(1).max(253).describe('"@" for apex, or a bare label. Do NOT include the zone suffix.'),
1225
1228
  content: z7.string().min(1).max(2e3).describe("Record value; TXT unquoted."),
1226
1229
  ttl: z7.number().int().min(60).max(86400).optional().describe("TTL in seconds (default 3600)."),
1227
1230
  priority: z7.number().int().min(0).max(65535).optional().describe("Required for MX / SRV.")
@@ -2058,6 +2061,24 @@ defineTool({
2058
2061
 
2059
2062
  // src/tools/services.ts
2060
2063
  import { z as z13 } from "zod";
2064
+ var DEV_ENV_IMAGE = "hoststack/dev-env:claude";
2065
+ var DEV_ENV_VOLUME = { name: "workspace", mountPath: "/workspace", sizeGb: 10 };
2066
+ var SERVICE_TYPES = [
2067
+ "web_service",
2068
+ "private_service",
2069
+ "worker",
2070
+ "cron_job",
2071
+ "static_site"
2072
+ ];
2073
+ var SERVICE_PLANS = [
2074
+ "pico",
2075
+ "nano",
2076
+ "micro",
2077
+ "starter",
2078
+ "standard",
2079
+ "pro_standard",
2080
+ "pro_large"
2081
+ ];
2061
2082
  defineTool({
2062
2083
  name: "list_services",
2063
2084
  category: "services",
@@ -2109,10 +2130,171 @@ defineTool({
2109
2130
  args2.status ? `status=${args2.status}` : null,
2110
2131
  args2.type ? `type=${args2.type}` : null
2111
2132
  ].filter(Boolean).join(", ");
2112
- 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}` : ""}.`;
2133
+ 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}` : ""}.`;
2113
2134
  return respond({ summary, data });
2114
2135
  }
2115
2136
  });
2137
+ defineTool({
2138
+ name: "create_service",
2139
+ category: "services",
2140
+ description: [
2141
+ "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.",
2142
+ "",
2143
+ "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).",
2144
+ "",
2145
+ "Inputs:",
2146
+ ' - project_id: numeric id or publicId ("prj_\u2026") of the target project.',
2147
+ " - name: service name (1\u2013100 chars).",
2148
+ ' - type: "web_service" | "private_service" | "worker" | "cron_job" | "static_site".',
2149
+ " - docker_image (optional): pre-built image ref to deploy instead of building from source.",
2150
+ " - github_repo_id (optional): connect a previously-linked GitHub repo by numeric id.",
2151
+ ' - branch (optional): git branch (default "main").',
2152
+ " - install_command / build_command / start_command (optional): build/run shell commands. start_command is required for web/private services without a docker_image.",
2153
+ " - cron_schedule (optional): cron expression \u2014 required for cron_job.",
2154
+ ' - publish_path (optional): static-site output dir (e.g. "dist").',
2155
+ ' - runtime (optional): "node" | "bun" | "python" | \u2026 (auto-detected from a repo when omitted).',
2156
+ ' - plan (optional): service size (default "micro").',
2157
+ " - environment_id (optional): bind to a specific environment; defaults to the project Production env.",
2158
+ " - auto_deploy (optional, default true): trigger the first deploy immediately when a source is present.",
2159
+ "",
2160
+ "Returns: { service: Service, deployId: number | null }.",
2161
+ "",
2162
+ 'Example: create_service({ project_id: "prj_abc", name: "api", type: "web_service", github_repo_id: 42 }) \u2192 { service: { publicId: "svc_\u2026" }, deployId: 1234 }'
2163
+ ].join("\n"),
2164
+ input: {
2165
+ project_id: z13.union([z13.number().int().positive(), z13.string()]).describe("Target project \u2014 numeric id or publicId."),
2166
+ name: z13.string().min(1).max(100).describe("Service name (1\u2013100 chars)."),
2167
+ type: z13.enum(SERVICE_TYPES).describe("Service type."),
2168
+ docker_image: z13.string().max(500).optional().describe("Pre-built image ref. Mutually exclusive with github_repo_id."),
2169
+ github_repo_id: z13.number().int().positive().optional().describe("Linked GitHub repo numeric id. Mutually exclusive with docker_image."),
2170
+ branch: z13.string().max(200).optional().describe('Git branch (default "main").'),
2171
+ install_command: z13.string().max(1e3).optional().describe("Install shell command."),
2172
+ build_command: z13.string().max(1e3).optional().describe("Build shell command."),
2173
+ start_command: z13.string().max(1e3).optional().describe("Start shell command (required for web/private services without an image)."),
2174
+ cron_schedule: z13.string().max(100).optional().describe("Cron expression \u2014 required for cron_job."),
2175
+ publish_path: z13.string().max(500).optional().describe("Static-site output dir."),
2176
+ runtime: z13.string().max(50).optional().describe("Runtime hint (node/bun/python/\u2026)."),
2177
+ plan: z13.enum(SERVICE_PLANS).optional().describe('Service size (default "micro").'),
2178
+ environment_id: z13.union([z13.number().int().positive(), z13.string()]).optional().describe("Bind to a specific environment; defaults to Production."),
2179
+ auto_deploy: z13.boolean().optional().describe("Trigger the first deploy immediately (default true).")
2180
+ },
2181
+ handler: async (args2, ctx) => {
2182
+ const teamId = await ctx.resolveTeamId();
2183
+ const projectId = await ctx.hoststack.resolveId(args2.project_id, {
2184
+ kind: "project",
2185
+ teamId
2186
+ });
2187
+ const input = {
2188
+ name: args2.name,
2189
+ type: args2.type,
2190
+ projectId
2191
+ };
2192
+ if (args2.docker_image !== void 0) input.dockerImage = args2.docker_image;
2193
+ if (args2.github_repo_id !== void 0) input.githubRepoId = args2.github_repo_id;
2194
+ if (args2.branch !== void 0) input.branch = args2.branch;
2195
+ if (args2.install_command !== void 0) input.installCommand = args2.install_command;
2196
+ if (args2.build_command !== void 0) input.buildCommand = args2.build_command;
2197
+ if (args2.start_command !== void 0) input.startCommand = args2.start_command;
2198
+ if (args2.cron_schedule !== void 0) input.cronSchedule = args2.cron_schedule;
2199
+ if (args2.publish_path !== void 0) input.publishPath = args2.publish_path;
2200
+ if (args2.runtime !== void 0) input.runtime = args2.runtime;
2201
+ if (args2.plan !== void 0) input.plan = args2.plan;
2202
+ if (args2.auto_deploy !== void 0) input.autoDeploy = args2.auto_deploy;
2203
+ if (args2.environment_id !== void 0) {
2204
+ input.environmentId = await ctx.hoststack.resolveId(args2.environment_id, {
2205
+ kind: "environment",
2206
+ teamId
2207
+ });
2208
+ }
2209
+ const response = await ctx.hoststack.services.create(teamId, input);
2210
+ const data = {
2211
+ service: shapeService(response.service),
2212
+ deployId: response.deployId ?? null
2213
+ };
2214
+ const publicId = data.service && "publicId" in data.service ? data.service.publicId : "unknown";
2215
+ return respond({ summary: `Created service "${args2.name}" (${publicId}).`, data });
2216
+ }
2217
+ });
2218
+ defineTool({
2219
+ name: "create_dev_environment",
2220
+ category: "services",
2221
+ description: [
2222
+ "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.",
2223
+ "",
2224
+ `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.`,
2225
+ "",
2226
+ "Inputs:",
2227
+ ' - project_id: numeric id or publicId ("prj_\u2026") of the target project.',
2228
+ ' - name (optional): service name (default "dev-environment").',
2229
+ ' - plan (optional): service size (default "micro").',
2230
+ " - disk_gb (optional): /workspace volume size in GB (default 10, 1\u2013100).",
2231
+ " - hoststack_api_key (optional): sets HOSTSTACK_API_KEY so the hoststack MCP works inside the container.",
2232
+ " - poststack_api_key (optional): sets POSTSTACK_API_KEY so the poststack MCP works inside the container.",
2233
+ "",
2234
+ "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.",
2235
+ "",
2236
+ 'Example: create_dev_environment({ project_id: "prj_abc", name: "scratch", hoststack_api_key: "hs_live_\u2026" })'
2237
+ ].join("\n"),
2238
+ input: {
2239
+ project_id: z13.union([z13.number().int().positive(), z13.string()]).describe("Target project \u2014 numeric id or publicId."),
2240
+ name: z13.string().min(1).max(100).optional().describe('Service name (default "dev-environment").'),
2241
+ plan: z13.enum(SERVICE_PLANS).optional().describe('Service size (default "micro").'),
2242
+ disk_gb: z13.number().int().min(1).max(100).optional().describe("/workspace volume size in GB (default 10)."),
2243
+ hoststack_api_key: z13.string().optional().describe("Value for HOSTSTACK_API_KEY (enables the hoststack MCP in-container)."),
2244
+ poststack_api_key: z13.string().optional().describe("Value for POSTSTACK_API_KEY (enables the poststack MCP in-container).")
2245
+ },
2246
+ handler: async (args2, ctx) => {
2247
+ const teamId = await ctx.resolveTeamId();
2248
+ const projectId = await ctx.hoststack.resolveId(args2.project_id, {
2249
+ kind: "project",
2250
+ teamId
2251
+ });
2252
+ const name = args2.name ?? "dev-environment";
2253
+ const sizeGb = args2.disk_gb ?? DEV_ENV_VOLUME.sizeGb;
2254
+ const createInput = {
2255
+ name,
2256
+ type: "private_service",
2257
+ projectId,
2258
+ dockerImage: DEV_ENV_IMAGE,
2259
+ autoDeploy: false
2260
+ };
2261
+ if (args2.plan !== void 0) createInput.plan = args2.plan;
2262
+ const created = await ctx.hoststack.services.create(teamId, createInput);
2263
+ const service = created.service;
2264
+ const envVars = [];
2265
+ if (args2.hoststack_api_key)
2266
+ envVars.push({
2267
+ key: "HOSTSTACK_API_KEY",
2268
+ value: args2.hoststack_api_key,
2269
+ isSecret: true
2270
+ });
2271
+ if (args2.poststack_api_key)
2272
+ envVars.push({
2273
+ key: "POSTSTACK_API_KEY",
2274
+ value: args2.poststack_api_key,
2275
+ isSecret: true
2276
+ });
2277
+ if (envVars.length > 0) {
2278
+ await ctx.hoststack.envVars.bulkSet(teamId, service.id, { vars: envVars });
2279
+ }
2280
+ let volumeAttached = false;
2281
+ try {
2282
+ await ctx.hoststack.volumes.create(teamId, service.id, {
2283
+ name: DEV_ENV_VOLUME.name,
2284
+ mountPath: DEV_ENV_VOLUME.mountPath,
2285
+ sizeGb
2286
+ });
2287
+ volumeAttached = true;
2288
+ } catch {
2289
+ }
2290
+ const deploy = await ctx.hoststack.deploys.trigger(teamId, service.id);
2291
+ const deployId = deploy.deploy?.id ?? null;
2292
+ return respond({
2293
+ summary: `Created AI dev environment "${name}" (${service.publicId})${volumeAttached ? " with a /workspace volume" : ""} \u2014 deploying. Open its Terminal tab once running.`,
2294
+ data: { service: shapeService(service), volumeAttached, deployId }
2295
+ });
2296
+ }
2297
+ });
2116
2298
  defineTool({
2117
2299
  name: "get_service",
2118
2300
  category: "services",