@hoststack.dev/mcp 0.20.0 → 0.22.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 CHANGED
@@ -83,6 +83,18 @@ npx @hoststack.dev/mcp --print-config claude-code
83
83
 
84
84
  If `HOSTSTACK_API_KEY` is set in your shell, it gets baked into the snippet; otherwise you'll see a `hs_live_your_api_key_here` placeholder you can search-and-replace.
85
85
 
86
+ `--help` and `--version` work the same way — no key needed, because the people
87
+ most likely to reach for them are the ones who haven't set one up yet:
88
+
89
+ ```bash
90
+ npx @hoststack.dev/mcp --help
91
+ npx @hoststack.dev/mcp --version
92
+ ```
93
+
94
+ Running it with no arguments starts the server on stdio, and that is the one
95
+ path that does need `HOSTSTACK_API_KEY`. It speaks JSON-RPC, so it isn't much
96
+ use by hand — an MCP client is what should be launching it.
97
+
86
98
  ## Tool inventory
87
99
 
88
100
  107 tools. The headings are the registry's own categories rather than a friendlier regrouping, so the build can diff this table against the registry and fail when the two disagree — which is how an earlier version of it came to advertise a total from three releases back and send agents to the dashboard for a `create_database` that had already shipped.
@@ -8,7 +8,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
8
  import { HostStack } from "@hoststack.dev/sdk";
9
9
 
10
10
  // src/version.ts
11
- var MCP_VERSION = true ? "0.20.0" : "0.0.0-dev";
11
+ var MCP_VERSION = true ? "0.22.0" : "0.0.0-dev";
12
12
  var USER_AGENT = `hoststack-mcp/${MCP_VERSION}`;
13
13
 
14
14
  // src/api-client.ts
@@ -3458,6 +3458,8 @@ defineTool({
3458
3458
  ' - task_id: "task_\u2026" publicId or numeric id.',
3459
3459
  ' - status: "done" or "idea". Only these two; `queued`/`running`/`failed`/`cancelled` belong to the runner and are rejected.',
3460
3460
  " - title / body / service_id: optional edits.",
3461
+ ' - provider: which agent runs it \u2014 "claude", "codex" or "opencode". null clears the pin and lets the box decide.',
3462
+ ' - permission_mode: how that agent is allowed to act \u2014 "default" (ask before acting), "acceptEdits" (edit files without asking) or "bypassPermissions" (no prompts at all). Only set bypassPermissions when the person asked for it: it is the box-wide yolo setting scoped to one task.',
3461
3463
  "",
3462
3464
  "Only mark a task done when it is actually resolved - the backlog is what someone reads to decide what still needs doing.",
3463
3465
  "",
@@ -3470,7 +3472,11 @@ defineTool({
3470
3472
  status: z17.enum(["idea", "done"]).optional().describe("The only two a person may set. The runner owns the rest of the lifecycle."),
3471
3473
  title: z17.string().min(1).max(200).optional().describe("New title."),
3472
3474
  body: z17.string().max(2e4).optional().describe("New prompt body."),
3473
- service_id: z17.union([z17.number().int().positive(), z17.string()]).nullable().optional().describe("Pin to a dev box, or null to unpin it back to a loose idea.")
3475
+ service_id: z17.union([z17.number().int().positive(), z17.string()]).nullable().optional().describe("Pin to a dev box, or null to unpin it back to a loose idea."),
3476
+ provider: z17.enum(["claude", "codex", "opencode"]).nullable().optional().describe("Which agent runs this task, or null to leave it to the box."),
3477
+ permission_mode: z17.enum(["acceptEdits", "bypassPermissions", "default"]).optional().describe(
3478
+ "How the agent may act. bypassPermissions asks for nothing \u2014 set it only on request."
3479
+ )
3474
3480
  },
3475
3481
  handler: async (args2, ctx) => {
3476
3482
  const teamId = await ctx.resolveTeamId();
@@ -3479,7 +3485,10 @@ defineTool({
3479
3485
  ...args2.status ? { status: args2.status } : {},
3480
3486
  ...args2.title ? { title: args2.title } : {},
3481
3487
  ...args2.body !== void 0 ? { body: args2.body } : {},
3482
- ...serviceId !== void 0 ? { serviceId } : {}
3488
+ ...serviceId !== void 0 ? { serviceId } : {},
3489
+ // null is meaningful on provider (unpin), so compare to undefined.
3490
+ ...args2.provider !== void 0 ? { provider: args2.provider } : {},
3491
+ ...args2.permission_mode !== void 0 ? { permissionMode: args2.permission_mode } : {}
3483
3492
  });
3484
3493
  const data = shape(response.task);
3485
3494
  return respond({
@@ -4674,6 +4683,8 @@ defineTool({
4674
4683
  " - instance_count (optional): integer 1\u201350 \u2014 pin both min and max instances to this value.",
4675
4684
  " - min_instances, max_instances (optional): integers \u2014 autoscale bounds. Use instead of instance_count when you want a range.",
4676
4685
  " - scale_cpu_threshold, scale_memory_threshold (optional): integer 10\u2013100 \u2014 autoscale trigger percentage.",
4686
+ " - scale_requests_per_sec_threshold (optional): integer 1\u201310000 \u2014 autoscale on request rate instead of (or alongside) CPU and memory. Pass null to stop scaling on request rate.",
4687
+ " - build_timeout_seconds (optional): integer 60\u20131800 \u2014 how long a build may run before it is killed. Raise it for a service whose build legitimately takes longer than the default; pass null to go back to the default.",
4677
4688
  ` - log_filter_rules (optional): list of { pattern, action } rules applied at read time \u2014 to get_service_logs (tail AND count) and to the dashboard's live stream alike. Pattern matches the message by case-insensitive substring, literally (a % or _ is not a wildcard); action is "drop" (filter out) or "downgrade" (flip stderr \u2192 stdout so it stops looking like an error). Stored lines are never altered, so removing a rule re-exposes everything it was hiding. Pass [] to clear all rules. Capped at 50 rules.`,
4678
4689
  "",
4679
4690
  "Returns: { service?: Service, config?: ServiceConfig } \u2014 whichever rows were touched.",
@@ -4714,6 +4725,8 @@ defineTool({
4714
4725
  max_instances: z19.number().int().min(1).max(50).optional().describe("Autoscale upper bound. Use with min_instances for a range."),
4715
4726
  scale_cpu_threshold: z19.number().int().min(10).max(100).optional().describe("Autoscale CPU trigger percentage (10\u2013100)."),
4716
4727
  scale_memory_threshold: z19.number().int().min(10).max(100).optional().describe("Autoscale memory trigger percentage (10\u2013100)."),
4728
+ scale_requests_per_sec_threshold: z19.number().int().min(1).max(1e4).nullable().optional().describe("Autoscale request-rate trigger (1\u201310000 req/s). null stops using it."),
4729
+ build_timeout_seconds: z19.number().int().min(60).max(1800).nullable().optional().describe("Kill a build that runs longer than this (60\u20131800s). null = default."),
4717
4730
  log_filter_rules: z19.array(
4718
4731
  z19.object({
4719
4732
  pattern: z19.string().min(1).max(200),
@@ -4768,6 +4781,10 @@ defineTool({
4768
4781
  configUpdate["scaleCpuThreshold"] = args2.scale_cpu_threshold;
4769
4782
  if (args2.scale_memory_threshold !== void 0)
4770
4783
  configUpdate["scaleMemoryThreshold"] = args2.scale_memory_threshold;
4784
+ if (args2.scale_requests_per_sec_threshold !== void 0)
4785
+ configUpdate["scaleRequestsPerSecThreshold"] = args2.scale_requests_per_sec_threshold;
4786
+ if (args2.build_timeout_seconds !== void 0)
4787
+ configUpdate["buildTimeoutSeconds"] = args2.build_timeout_seconds;
4771
4788
  if (args2.log_filter_rules !== void 0) {
4772
4789
  configUpdate["logFilterRules"] = args2.log_filter_rules;
4773
4790
  }
@@ -5218,6 +5235,7 @@ defineTool({
5218
5235
  " - volume_id: publicId of the volume to update.",
5219
5236
  " - mount_path (optional): new in-container mount path.",
5220
5237
  " - size_gb (optional): new size in GB (must be \u2265 current).",
5238
+ " - backup_enabled (optional): whether this volume is backed up. Turning it off stops future backups; it does not delete the ones already taken.",
5221
5239
  "",
5222
5240
  "Returns: { volume: Volume } \u2014 the updated record.",
5223
5241
  "",
@@ -5227,13 +5245,15 @@ defineTool({
5227
5245
  service_id: z21.string().describe("Service publicId."),
5228
5246
  volume_id: z21.string().describe("Volume publicId (e.g. vol_\u2026)."),
5229
5247
  mount_path: z21.string().startsWith("/").max(500).optional().describe("New mount path."),
5230
- size_gb: z21.number().int().min(MIN_VOLUME_SIZE_GB).max(100).optional().describe(`New size in GB (minimum ${MIN_VOLUME_SIZE_GB}, grow-only).`)
5248
+ size_gb: z21.number().int().min(MIN_VOLUME_SIZE_GB).max(100).optional().describe(`New size in GB (minimum ${MIN_VOLUME_SIZE_GB}, grow-only).`),
5249
+ backup_enabled: z21.boolean().optional().describe("Whether to keep backing this volume up. Past backups are kept either way.")
5231
5250
  },
5232
5251
  handler: async (args2, ctx) => {
5233
5252
  const teamId = await ctx.resolveTeamId();
5234
5253
  const input = {};
5235
5254
  if (args2.mount_path !== void 0) input.mountPath = args2.mount_path;
5236
5255
  if (args2.size_gb !== void 0) input.sizeGb = args2.size_gb;
5256
+ if (args2.backup_enabled !== void 0) input.backupEnabled = args2.backup_enabled;
5237
5257
  if (Object.keys(input).length === 0) {
5238
5258
  return respond({ summary: "No fields to update.", data: {} });
5239
5259
  }
@@ -5315,10 +5335,38 @@ function createMcpServer(options) {
5315
5335
 
5316
5336
  // src/cli.ts
5317
5337
  var args = process.argv.slice(2);
5338
+ var PRINT_CONFIG_TARGETS = ["claude-desktop", "cursor", "claude-code"];
5339
+ var USAGE = `hoststack-mcp ${MCP_VERSION} \u2014 the HostStack MCP server (stdio transport).
5340
+
5341
+ Usage:
5342
+ hoststack-mcp Serve on stdio. This is what an MCP client runs;
5343
+ it speaks JSON-RPC, so it is not useful by hand.
5344
+ hoststack-mcp --print-config <client> Emit a ready-to-paste config snippet.
5345
+ <client>: ${PRINT_CONFIG_TARGETS.join(" | ")}
5346
+ hoststack-mcp --help This text.
5347
+ hoststack-mcp --version Print the version and exit.
5348
+
5349
+ Environment:
5350
+ HOSTSTACK_API_KEY Required to SERVE. An hs_live_\u2026 or hs_test_\u2026 key from
5351
+ hoststack.dev \u2192 Settings \u2192 API keys. Not needed for
5352
+ --help, --version or --print-config.
5353
+ HOSTSTACK_BASE_URL API host. Default https://hoststack.dev.
5354
+ HOSTSTACK_MCP_TRACE Set to 1 to write one NDJSON line per tool call to
5355
+ stderr, for a local trace (2> mcp-trace.log).
5356
+ `;
5357
+ if (args.includes("--help") || args.includes("-h")) {
5358
+ process.stdout.write(USAGE);
5359
+ process.exit(0);
5360
+ }
5361
+ if (args.includes("--version") || args.includes("-v")) {
5362
+ process.stdout.write(`${MCP_VERSION}
5363
+ `);
5364
+ process.exit(0);
5365
+ }
5318
5366
  var printIdx = args.indexOf("--print-config");
5319
5367
  if (printIdx !== -1) {
5320
5368
  const target = args[printIdx + 1];
5321
- const validTargets = /* @__PURE__ */ new Set(["claude-desktop", "cursor", "claude-code"]);
5369
+ const validTargets = new Set(PRINT_CONFIG_TARGETS);
5322
5370
  if (!target || !validTargets.has(target)) {
5323
5371
  console.error(
5324
5372
  `Usage: hoststack-mcp --print-config <${[...validTargets].join("|")}>
@@ -5351,9 +5399,18 @@ if (printIdx !== -1) {
5351
5399
  process.exit(0);
5352
5400
  }
5353
5401
  }
5402
+ var unknownFlag = args.find((arg) => arg.startsWith("-"));
5403
+ if (unknownFlag) {
5404
+ console.error(`hoststack-mcp: unknown option ${unknownFlag}
5405
+
5406
+ ${USAGE}`);
5407
+ process.exit(1);
5408
+ }
5354
5409
  var apiKey = process.env["HOSTSTACK_API_KEY"];
5355
5410
  if (!apiKey) {
5356
- console.error("HOSTSTACK_API_KEY environment variable is required");
5411
+ console.error(
5412
+ "hoststack-mcp: HOSTSTACK_API_KEY is required to serve.\nCreate one at hoststack.dev \u2192 Settings \u2192 API keys, then either export it\nor put it in your client config: hoststack-mcp --print-config claude-code\n(--help, --version and --print-config need no key.)"
5413
+ );
5357
5414
  process.exit(1);
5358
5415
  }
5359
5416
  var baseUrl = process.env["HOSTSTACK_BASE_URL"] ?? "https://hoststack.dev";