@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.
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 (no trailing dot).'),
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.")
@@ -2062,6 +2065,24 @@ defineTool({
2062
2065
 
2063
2066
  // src/tools/services.ts
2064
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
+ ];
2065
2086
  defineTool({
2066
2087
  name: "list_services",
2067
2088
  category: "services",
@@ -2113,10 +2134,171 @@ defineTool({
2113
2134
  args.status ? `status=${args.status}` : null,
2114
2135
  args.type ? `type=${args.type}` : null
2115
2136
  ].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}` : ""}.`;
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}` : ""}.`;
2117
2138
  return respond({ summary, data });
2118
2139
  }
2119
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
+ });
2120
2302
  defineTool({
2121
2303
  name: "get_service",
2122
2304
  category: "services",