@mcpcloud/cli 0.23.0 → 0.24.0-next-20260917193810

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.
Files changed (3) hide show
  1. package/README.md +4 -1
  2. package/dist/index.js +202 -68
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -353,7 +353,7 @@ _Generated from the live command tree by `bun run docs:readme` — do not edit b
353
353
  | `mcp api-keys create` | Create a new API key (the secret is shown once) |
354
354
  | `mcp api-keys describe <apiKey>` | Show one API key (accepts id or name; status, expiry, last used) — never the secret |
355
355
  | `mcp api-keys list` | List active API keys for the current user |
356
- | `mcp api-keys revoke <apiKey>` | Permanently revoke an API key (accepts id or name) |
356
+ | `mcp api-keys revoke <apiKey>` | Permanently revoke an API key (accepts id or name). Needs --confirm "<key name>". |
357
357
  | `mcp api-keys rotate <apiKey>` | Mint a replacement key and grace-expire the old one (24h window; accepts id or name). The new secret is shown once. |
358
358
 
359
359
  ### `mcp audit` — Workspace audit log (admin/owner only)
@@ -505,6 +505,9 @@ _Generated from the live command tree by `bun run docs:readme` — do not edit b
505
505
  | `mcp servers deps add <server> <child>` | Compose <child>'s tools into <server>. Both accept an id, name or slug. |
506
506
  | `mcp servers deps list [server]` | List the servers this one composes. |
507
507
  | `mcp servers deps rm <server> <dependencyId>` | Remove a composed dependency (id from `deps list`). |
508
+ | `mcp servers end-user` | Read and set a server's end-user plane: the access switch and the tool allowlist customers may call. |
509
+ | `mcp servers end-user get <server>` | Show the end-user access configuration: switch, connect page, allowed tools, branding and sign-in federation. |
510
+ | `mcp servers end-user set <server>` | Enable or disable end-user access, and/or replace the tool allowlist. |
508
511
  | `mcp servers env` | Manage server runtime environment bindings (encrypted secrets injected by the proxy). |
509
512
  | `mcp servers env get <server> <bindingName>` | Show metadata for one env binding. Plaintext is never returned — values are write-only. |
510
513
  | `mcp servers env list <server>` | List a server's env bindings (metadata only; values are never returned). |
package/dist/index.js CHANGED
@@ -6133,8 +6133,120 @@ function registerServerShapingCommands(servers) {
6133
6133
  }));
6134
6134
  }
6135
6135
 
6136
+ // src/commands/servers-end-user.ts
6137
+ import { readFileSync as readFileSync5 } from "node:fs";
6138
+ var END_USER_PATH = "/api/v1/server/end-user-access";
6139
+ async function readEndUserAccess(serverRef, opts) {
6140
+ const orgId = await resolveOrgId(opts.org);
6141
+ const serverId = await resolveServerId(serverRef, orgId, undefined);
6142
+ return await api.get(END_USER_PATH, {
6143
+ organizationId: orgId,
6144
+ serverId
6145
+ });
6146
+ }
6147
+ function printEndUserAccess(data) {
6148
+ printKeyValue({
6149
+ server: `${data.server.name} (${data.server.id})`,
6150
+ access: data.endUserAccess,
6151
+ "connect page": data.connectPageUrl ?? "—",
6152
+ "allowed tools": data.allowedTools.length > 0 ? `${data.allowedTools.length}: ${data.allowedTools.join(", ")}` : "none — end users can call nothing",
6153
+ "display name": data.branding?.displayName ?? "—",
6154
+ logo: data.branding?.logoUrl ?? "—",
6155
+ accent: data.branding?.accentColor ?? "—",
6156
+ support: data.branding?.supportUrl ?? "—",
6157
+ "sign-in federation": data.signIn.configured ? `${data.signIn.status} (${data.signIn.issuer}, keys via ${data.signIn.keySource})` : "not configured",
6158
+ "sign-in url": data.signIn.signInUrl ?? "—"
6159
+ });
6160
+ }
6161
+ function parseToolList(raw) {
6162
+ const text = raw.startsWith("@") ? readFileSync5(raw.slice(1), "utf8") : raw;
6163
+ const trimmed = text.trim();
6164
+ if (trimmed.startsWith("[")) {
6165
+ const parsed = JSON.parse(trimmed);
6166
+ if (!Array.isArray(parsed) || parsed.some((entry) => typeof entry !== "string")) {
6167
+ throw new Error("--allow-tools JSON must be an array of tool names.");
6168
+ }
6169
+ return parsed;
6170
+ }
6171
+ return trimmed.split(",").map((entry) => entry.trim()).filter(Boolean);
6172
+ }
6173
+ async function listServerToolNames(orgId, projectId, serverId) {
6174
+ const { rows } = await fetchAllPagesWithEnvelope("/api/v1/project/tools", { organizationId: orgId, projectId }, "tools");
6175
+ return rows.filter((row) => row.serverId === serverId).map((row) => row.name).sort((left, right) => left.localeCompare(right));
6176
+ }
6177
+ function registerServerEndUserCommands(servers) {
6178
+ const endUser = servers.command("end-user").alias("end-users").description("Read and set a server's end-user plane: the access switch and the tool allowlist customers may call.");
6179
+ endUser.command("get <server>").description("Show the end-user access configuration: switch, connect page, allowed tools, branding and sign-in federation.").option("--org <organizationId>", "Organization ID").addHelpText("after", [
6180
+ "",
6181
+ "End users are deny-by-default, so `allowed tools` is exhaustive — a",
6182
+ "tool missing from it cannot be called by a customer, whatever the",
6183
+ "server's own access mode says.",
6184
+ "",
6185
+ "Examples:",
6186
+ " $ mcp servers end-user get srv_123",
6187
+ " $ mcp --json servers end-user get srv_123 | jq '.allowedTools'"
6188
+ ].join(`
6189
+ `)).action(runAction(async (serverRef, opts) => {
6190
+ const data = await readEndUserAccess(serverRef, opts);
6191
+ if (isJsonMode()) {
6192
+ printJson(data);
6193
+ return;
6194
+ }
6195
+ printEndUserAccess(data);
6196
+ }));
6197
+ endUser.command("set <server>").description("Enable or disable end-user access, and/or replace the tool allowlist.").option("--org <organizationId>", "Organization ID").option("--enable", "Turn end-user access on").option("--disable", "Turn end-user access off").option("--allow-tools <list>", "Replace the allowlist with these tool names (comma-separated, or @path to a JSON array)").option("--allow-all-tools", "Replace the allowlist with every tool currently on the server — re-run after adding tools, since the list is a replacement, not a merge").addHelpText("after", [
6198
+ "",
6199
+ "The allowlist is REPLACED, never merged: what you pass is what end",
6200
+ "users may call afterwards. That is what makes it safe to run on every",
6201
+ "CI pass — the allowlist ends up matching the contract rather than",
6202
+ "accumulating whatever was granted along the way.",
6203
+ "",
6204
+ "Leave --enable / --disable off to change only the allowlist; the",
6205
+ "switch is read first and left where it is.",
6206
+ "",
6207
+ "Examples:",
6208
+ " $ mcp servers end-user set srv_123 --enable --allow-all-tools",
6209
+ " $ mcp servers end-user set srv_123 --allow-tools listbooks,getbook",
6210
+ " $ mcp --ci servers end-user set srv_123 --allow-tools @allowed.json"
6211
+ ].join(`
6212
+ `)).action(runAction(async (serverRef, opts) => {
6213
+ if (opts.enable && opts.disable) {
6214
+ throw new Error("--enable and --disable are mutually exclusive.");
6215
+ }
6216
+ if (opts.allowTools && opts.allowAllTools) {
6217
+ throw new Error("--allow-tools and --allow-all-tools are mutually exclusive.");
6218
+ }
6219
+ const current = await readEndUserAccess(serverRef, opts);
6220
+ const orgId = await resolveOrgId(opts.org);
6221
+ const enabled = opts.enable ? true : opts.disable ? false : current.endUserAccess === "enabled";
6222
+ let allowedTools = null;
6223
+ if (opts.allowTools) {
6224
+ allowedTools = parseToolList(opts.allowTools);
6225
+ } else if (opts.allowAllTools) {
6226
+ allowedTools = await listServerToolNames(orgId, current.server.projectId, current.server.id);
6227
+ }
6228
+ await api.put(END_USER_PATH, {
6229
+ organizationId: orgId,
6230
+ projectId: current.server.projectId,
6231
+ serverId: current.server.id,
6232
+ enabled,
6233
+ ...allowedTools === null ? {} : { allowedTools }
6234
+ });
6235
+ const updated = await readEndUserAccess(serverRef, opts);
6236
+ if (isJsonMode()) {
6237
+ printJson(updated);
6238
+ return;
6239
+ }
6240
+ printSuccess(`End-user access ${updated.endUserAccess} for ${updated.server.name}.`);
6241
+ printEndUserAccess(updated);
6242
+ if (updated.endUserAccess === "enabled" && updated.connectPageUrl) {
6243
+ printInfo(` Customers connect at ${updated.connectPageUrl}`);
6244
+ }
6245
+ }));
6246
+ }
6247
+
6136
6248
  // src/commands/servers-env.ts
6137
- import { existsSync as existsSync3, readFileSync as readFileSync5 } from "node:fs";
6249
+ import { existsSync as existsSync3, readFileSync as readFileSync6 } from "node:fs";
6138
6250
  import { resolve } from "node:path";
6139
6251
  function registerServerEnvCommands(servers) {
6140
6252
  const env = servers.command("env").description("Manage server runtime environment bindings (encrypted secrets injected by the proxy).");
@@ -6283,7 +6395,7 @@ async function resolveSetValue(args) {
6283
6395
  printError(`--from-file path not found: ${path}`);
6284
6396
  throw new CliExitError(1);
6285
6397
  }
6286
- return readFileSync5(path, "utf-8");
6398
+ return readFileSync6(path, "utf-8");
6287
6399
  }
6288
6400
  return await readStdin();
6289
6401
  }
@@ -6762,7 +6874,7 @@ function registerServerSandboxVariableCommands(servers) {
6762
6874
  }
6763
6875
 
6764
6876
  // src/lib/mcp-invoke.ts
6765
- import { existsSync as existsSync4, readFileSync as readFileSync6 } from "node:fs";
6877
+ import { existsSync as existsSync4, readFileSync as readFileSync7 } from "node:fs";
6766
6878
  import { resolve as resolve2 } from "node:path";
6767
6879
  var DEFAULT_TIMEOUT_MS3 = 60000;
6768
6880
  var invokeRequestId = 1;
@@ -6984,7 +7096,7 @@ async function parseToolArguments(opts) {
6984
7096
  if (!existsSync4(filePath)) {
6985
7097
  return { ok: false, reason: "file-not-found", path: filePath };
6986
7098
  }
6987
- source = readFileSync6(filePath, "utf-8");
7099
+ source = readFileSync7(filePath, "utf-8");
6988
7100
  } else {
6989
7101
  source = raw;
6990
7102
  }
@@ -7410,6 +7522,7 @@ function registerServerGetCommand(servers) {
7410
7522
  url: s.deploymentUrl ?? "—",
7411
7523
  "tool surface": describeToolSurface(s),
7412
7524
  "upstream keys": s.upstreamApiKeyMode ?? "shared",
7525
+ upstream: "mcp deployments get latest --server <server>",
7413
7526
  "end-user access": s.endUserAccess ?? "disabled",
7414
7527
  ...s.endUserAccess === "enabled" && s.connectPageUrl ? { "connect page": s.connectPageUrl } : {},
7415
7528
  git: gitLink ? `${gitLink.owner}/${gitLink.name}@${gitLink.branch} (mcp servers git status)` : "—",
@@ -7538,7 +7651,7 @@ function formatDeploymentExposure(deployment) {
7538
7651
  import { createHash as createHash2 } from "node:crypto";
7539
7652
  import {
7540
7653
  existsSync as existsSync5,
7541
- readFileSync as readFileSync7,
7654
+ readFileSync as readFileSync8,
7542
7655
  watch as fsWatch
7543
7656
  } from "node:fs";
7544
7657
  import { dirname as dirname3 } from "node:path";
@@ -7592,7 +7705,7 @@ function summarizeDiff(diff) {
7592
7705
  `) };
7593
7706
  }
7594
7707
  function readSpecFile(specPath) {
7595
- const content = readFileSync7(specPath, "utf-8");
7708
+ const content = readFileSync8(specPath, "utf-8");
7596
7709
  return { content, hash: hashSpecContent(content) };
7597
7710
  }
7598
7711
  function watchSpecFile(options) {
@@ -7612,7 +7725,7 @@ function watchSpecFile(options) {
7612
7725
  return;
7613
7726
  let content;
7614
7727
  try {
7615
- content = readFileSync7(options.specPath, "utf-8");
7728
+ content = readFileSync8(options.specPath, "utf-8");
7616
7729
  } catch {
7617
7730
  return;
7618
7731
  }
@@ -8441,7 +8554,7 @@ function registerServerMutationCommands(servers) {
8441
8554
  }
8442
8555
 
8443
8556
  // src/lib/spec-pipeline.ts
8444
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "node:fs";
8557
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "node:fs";
8445
8558
  import { dirname as dirname4, join as join5, resolve as resolve5 } from "node:path";
8446
8559
  var SOURCE_TYPES = ["openapi", "graphql", "url"];
8447
8560
  function isHttpUrl(value) {
@@ -8474,7 +8587,7 @@ async function readSpecSource(spec, override, options = {}) {
8474
8587
  if (!existsSync7(filePath)) {
8475
8588
  throw new Error(`Spec file not found: ${filePath}`);
8476
8589
  }
8477
- content = readFileSync8(filePath, "utf-8");
8590
+ content = readFileSync9(filePath, "utf-8");
8478
8591
  pathHint = filePath;
8479
8592
  }
8480
8593
  if (!content.trim()) {
@@ -8654,7 +8767,7 @@ function registerServerSpecCommands(servers) {
8654
8767
  }
8655
8768
 
8656
8769
  // src/commands/servers-test-authoring.ts
8657
- import { readFileSync as readFileSync9 } from "node:fs";
8770
+ import { readFileSync as readFileSync10 } from "node:fs";
8658
8771
  var EXECUTION_PROFILES = [
8659
8772
  "mockBindings",
8660
8773
  "previewBindings",
@@ -8672,9 +8785,9 @@ function parseTimeoutMs(value, fallbackMs) {
8672
8785
  }
8673
8786
  function readValueOption(raw) {
8674
8787
  if (raw === "@-")
8675
- return readFileSync9(0, "utf8");
8788
+ return readFileSync10(0, "utf8");
8676
8789
  if (raw.startsWith("@"))
8677
- return readFileSync9(raw.slice(1), "utf8");
8790
+ return readFileSync10(raw.slice(1), "utf8");
8678
8791
  return raw;
8679
8792
  }
8680
8793
  async function resolveServerAndProject(serverRef, org) {
@@ -9035,6 +9148,7 @@ function registerServerTestCommands(servers) {
9035
9148
  function registerServerCommands(program) {
9036
9149
  const servers = program.command("servers").description("Manage MCP servers");
9037
9150
  registerServerDepsCommands(servers);
9151
+ registerServerEndUserCommands(servers);
9038
9152
  registerServerGitCommands(servers);
9039
9153
  registerServerCommitCommand(servers);
9040
9154
  registerServerPromptCommands(servers);
@@ -9214,14 +9328,14 @@ function registerServerCommands(program) {
9214
9328
  }
9215
9329
 
9216
9330
  // src/commands/tools-diff.ts
9217
- import { existsSync as existsSync11, readFileSync as readFileSync13 } from "node:fs";
9331
+ import { existsSync as existsSync11, readFileSync as readFileSync14 } from "node:fs";
9218
9332
  import { relative } from "node:path";
9219
9333
 
9220
9334
  // src/lib/dev/handlers-sync.ts
9221
9335
  import {
9222
9336
  existsSync as existsSync10,
9223
9337
  mkdirSync as mkdirSync7,
9224
- readFileSync as readFileSync12,
9338
+ readFileSync as readFileSync13,
9225
9339
  statSync,
9226
9340
  writeFileSync as writeFileSync6
9227
9341
  } from "node:fs";
@@ -9229,7 +9343,7 @@ import { createHash as createHash3 } from "node:crypto";
9229
9343
  import { dirname as dirname5, join as join8 } from "node:path";
9230
9344
 
9231
9345
  // src/lib/dev/state.ts
9232
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
9346
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "node:fs";
9233
9347
  import { join as join6, resolve as resolve6 } from "node:path";
9234
9348
  function cachedStateMatchesTarget(cached, target) {
9235
9349
  return cached !== null && cached.serverId === target.serverId && cached.organizationId === target.organizationId;
@@ -9258,7 +9372,7 @@ function readState(cwd) {
9258
9372
  if (!existsSync8(path))
9259
9373
  return null;
9260
9374
  try {
9261
- const parsed = JSON.parse(readFileSync10(path, "utf-8"));
9375
+ const parsed = JSON.parse(readFileSync11(path, "utf-8"));
9262
9376
  if (!parsed || typeof parsed !== "object")
9263
9377
  return null;
9264
9378
  const s = parsed;
@@ -9299,7 +9413,7 @@ function ensureGitignore(cwd) {
9299
9413
  import {
9300
9414
  existsSync as existsSync9,
9301
9415
  mkdirSync as mkdirSync6,
9302
- readFileSync as readFileSync11,
9416
+ readFileSync as readFileSync12,
9303
9417
  readdirSync,
9304
9418
  rmSync,
9305
9419
  writeFileSync as writeFileSync5
@@ -9513,7 +9627,7 @@ function readToolsState(devRootDir, serverId) {
9513
9627
  if (!existsSync9(file))
9514
9628
  return null;
9515
9629
  try {
9516
- const parsed = JSON.parse(readFileSync11(file, "utf-8"));
9630
+ const parsed = JSON.parse(readFileSync12(file, "utf-8"));
9517
9631
  if (!parsed || typeof parsed !== "object")
9518
9632
  return null;
9519
9633
  const s = parsed;
@@ -9593,7 +9707,7 @@ function readLocalTool(devRootDir, serverId, toolName) {
9593
9707
  const stateEntry = state?.tools[toolName];
9594
9708
  if (!stateEntry)
9595
9709
  return null;
9596
- const view = parseToolMarkdown(readFileSync11(filePath, "utf-8"));
9710
+ const view = parseToolMarkdown(readFileSync12(filePath, "utf-8"));
9597
9711
  return {
9598
9712
  view,
9599
9713
  cloudId: stateEntry.id,
@@ -9742,7 +9856,7 @@ async function pushLocalHandlerEdit(args) {
9742
9856
  if (!resolved) {
9743
9857
  return { kind: "unknown-tool", slug: args.slug };
9744
9858
  }
9745
- const source = readFileSync12(filePath, "utf-8");
9859
+ const source = readFileSync13(filePath, "utf-8");
9746
9860
  const hash = hashSource(source);
9747
9861
  if (lastPushedHash.get(filePath) === hash) {
9748
9862
  return { kind: "noop" };
@@ -9901,7 +10015,7 @@ function inspectLocalHandler(args) {
9901
10015
  if (!existsSync10(filePath))
9902
10016
  return { kind: "no-local-file" };
9903
10017
  const cached = lastPushedHash.get(filePath);
9904
- const onDisk = hashSource(readFileSync12(filePath, "utf-8"));
10018
+ const onDisk = hashSource(readFileSync13(filePath, "utf-8"));
9905
10019
  if (cached !== undefined) {
9906
10020
  return cached === onDisk ? { kind: "matches-cache" } : { kind: "differs-from-cache" };
9907
10021
  }
@@ -10046,7 +10160,7 @@ function buildMetadataDiff(args) {
10046
10160
  let local = null;
10047
10161
  let parseError = null;
10048
10162
  try {
10049
- local = parseToolMarkdown(readFileSync13(filePath, "utf-8"));
10163
+ local = parseToolMarkdown(readFileSync14(filePath, "utf-8"));
10050
10164
  } catch (err) {
10051
10165
  parseError = err instanceof Error ? err.message : String(err);
10052
10166
  }
@@ -10089,7 +10203,7 @@ async function buildHandlerDiff(args) {
10089
10203
  const slug = handlerSlugFromToolName(args.toolName);
10090
10204
  const filePath = `${serverBundleHandlersDir(args.devRootDir)}/${slug}.ts`;
10091
10205
  const hasLocal = existsSync11(filePath);
10092
- const localContent = hasLocal ? readFileSync13(filePath, "utf-8") : null;
10206
+ const localContent = hasLocal ? readFileSync14(filePath, "utf-8") : null;
10093
10207
  const localPath = hasLocal ? relative(args.cwd, filePath) || filePath : null;
10094
10208
  const changed = hasLocal ? localContent !== cloudResult.snapshot.content : true;
10095
10209
  return {
@@ -10204,7 +10318,7 @@ import { join as join10, relative as relative2 } from "node:path";
10204
10318
 
10205
10319
  // src/lib/editor-shell.ts
10206
10320
  import { spawn as spawn2 } from "node:child_process";
10207
- import { existsSync as existsSync12, readFileSync as readFileSync14 } from "node:fs";
10321
+ import { existsSync as existsSync12, readFileSync as readFileSync15 } from "node:fs";
10208
10322
  import { platform as platform2 } from "node:os";
10209
10323
  import { delimiter, join as join9 } from "node:path";
10210
10324
  var DEFAULT_FALLBACKS = ["vi"];
@@ -10290,7 +10404,7 @@ async function runEditor(args) {
10290
10404
  message: `${command} exited with code ${exitCode}. The file may be unsaved; re-run after fixing.`
10291
10405
  };
10292
10406
  }
10293
- const contents = readFileSync14(args.filePath, "utf-8");
10407
+ const contents = readFileSync15(args.filePath, "utf-8");
10294
10408
  return { ok: true, contents, command };
10295
10409
  }
10296
10410
 
@@ -11533,13 +11647,13 @@ function isAgentKey(value) {
11533
11647
  }
11534
11648
 
11535
11649
  // src/commands/skills-lifecycle.ts
11536
- import { readFileSync as readFileSync15 } from "node:fs";
11650
+ import { readFileSync as readFileSync16 } from "node:fs";
11537
11651
  function readValueOption2(raw) {
11538
11652
  if (raw === "@-") {
11539
- return readFileSync15(0, "utf8");
11653
+ return readFileSync16(0, "utf8");
11540
11654
  }
11541
11655
  if (raw.startsWith("@")) {
11542
- return readFileSync15(raw.slice(1), "utf8");
11656
+ return readFileSync16(raw.slice(1), "utf8");
11543
11657
  }
11544
11658
  return raw;
11545
11659
  }
@@ -11889,7 +12003,7 @@ function registerSkillMutationCommands(skills) {
11889
12003
  }
11890
12004
 
11891
12005
  // src/commands/skills-test-authoring.ts
11892
- import { readFileSync as readFileSync16 } from "node:fs";
12006
+ import { readFileSync as readFileSync17 } from "node:fs";
11893
12007
  var FIXTURE_MODES = [
11894
12008
  "baseline",
11895
12009
  "validationFailure",
@@ -11898,9 +12012,9 @@ var FIXTURE_MODES = [
11898
12012
  ];
11899
12013
  function readValueOption3(raw) {
11900
12014
  if (raw === "@-")
11901
- return readFileSync16(0, "utf8");
12015
+ return readFileSync17(0, "utf8");
11902
12016
  if (raw.startsWith("@"))
11903
- return readFileSync16(raw.slice(1), "utf8");
12017
+ return readFileSync17(raw.slice(1), "utf8");
11904
12018
  return raw;
11905
12019
  }
11906
12020
  function parseAssertionsOption(raw) {
@@ -12953,14 +13067,14 @@ function registerSkillCommands(program) {
12953
13067
  }
12954
13068
 
12955
13069
  // src/lib/dev/prepare.ts
12956
- import { existsSync as existsSync21, readFileSync as readFileSync20, writeFileSync as writeFileSync12 } from "node:fs";
13070
+ import { existsSync as existsSync21, readFileSync as readFileSync21, writeFileSync as writeFileSync12 } from "node:fs";
12957
13071
  import { join as join18, relative as relative6, resolve as resolve8 } from "node:path";
12958
13072
 
12959
13073
  // src/lib/dev/bundle-sync.ts
12960
13074
  import {
12961
13075
  existsSync as existsSync15,
12962
13076
  mkdirSync as mkdirSync9,
12963
- readFileSync as readFileSync17,
13077
+ readFileSync as readFileSync18,
12964
13078
  rmSync as rmSync2,
12965
13079
  writeFileSync as writeFileSync8
12966
13080
  } from "node:fs";
@@ -13097,7 +13211,7 @@ import { spawnSync } from "node:child_process";
13097
13211
  import {
13098
13212
  existsSync as existsSync16,
13099
13213
  mkdirSync as mkdirSync10,
13100
- readFileSync as readFileSync18,
13214
+ readFileSync as readFileSync19,
13101
13215
  writeFileSync as writeFileSync9
13102
13216
  } from "node:fs";
13103
13217
  import { dirname as dirname8, join as join13 } from "node:path";
@@ -13192,7 +13306,7 @@ function isGitAvailable() {
13192
13306
  }
13193
13307
  function applyLocalExcludes(repoDir) {
13194
13308
  const excludePath = join13(repoDir, ".git", "info", "exclude");
13195
- const existing = existsSync16(excludePath) ? readFileSync18(excludePath, "utf-8") : "";
13309
+ const existing = existsSync16(excludePath) ? readFileSync19(excludePath, "utf-8") : "";
13196
13310
  const next = mergeLocalExcludes(existing);
13197
13311
  if (next === null)
13198
13312
  return;
@@ -28578,7 +28692,7 @@ import {
28578
28692
  appendFileSync as appendFileSync2,
28579
28693
  existsSync as existsSync19,
28580
28694
  mkdirSync as mkdirSync13,
28581
- readFileSync as readFileSync19,
28695
+ readFileSync as readFileSync20,
28582
28696
  readdirSync as readdirSync2,
28583
28697
  renameSync,
28584
28698
  statSync as statSync2
@@ -28630,7 +28744,7 @@ function readAll(dir) {
28630
28744
  for (const path of [...listRotatedFiles(dir), activeFilePath(dir)]) {
28631
28745
  if (!existsSync19(path))
28632
28746
  continue;
28633
- out.push(...parseLines(readFileSync19(path, "utf-8")));
28747
+ out.push(...parseLines(readFileSync20(path, "utf-8")));
28634
28748
  }
28635
28749
  return out;
28636
28750
  }
@@ -28782,9 +28896,9 @@ function printEnvBindingHint(dest, envPath, cwd) {
28782
28896
  let example = "";
28783
28897
  let envJson = "{}";
28784
28898
  try {
28785
- example = readFileSync20(examplePath, "utf-8");
28899
+ example = readFileSync21(examplePath, "utf-8");
28786
28900
  if (existsSync21(envPath))
28787
- envJson = readFileSync20(envPath, "utf-8");
28901
+ envJson = readFileSync21(envPath, "utf-8");
28788
28902
  } catch {
28789
28903
  return;
28790
28904
  }
@@ -28957,7 +29071,7 @@ async function prepareDev(opts) {
28957
29071
 
28958
29072
  // src/lib/dev/tree-sync.ts
28959
29073
  import { createHash as createHash4 } from "node:crypto";
28960
- import { existsSync as existsSync22, mkdirSync as mkdirSync14, readdirSync as readdirSync3, readFileSync as readFileSync21, writeFileSync as writeFileSync13 } from "node:fs";
29074
+ import { existsSync as existsSync22, mkdirSync as mkdirSync14, readdirSync as readdirSync3, readFileSync as readFileSync22, writeFileSync as writeFileSync13 } from "node:fs";
28961
29075
  import { join as join19 } from "node:path";
28962
29076
  var AUTHORING_GUIDE_FILE = "AGENTS.md";
28963
29077
  var STRUCTURAL_FRONTMATTER_KEYS = [
@@ -28979,7 +29093,7 @@ function listToolFiles(devRootDir, serverId) {
28979
29093
  }
28980
29094
  function readToolFile(devRootDir, serverId, toolName) {
28981
29095
  const file = join19(serverToolsDir(devRootDir, serverId), `${toolName}.md`);
28982
- const raw = readFileSync21(file, "utf-8");
29096
+ const raw = readFileSync22(file, "utf-8");
28983
29097
  try {
28984
29098
  return { raw, view: parseToolMarkdown(raw) };
28985
29099
  } catch (err) {
@@ -29099,7 +29213,7 @@ function listHandlerFiles(devRootDir) {
29099
29213
  return [];
29100
29214
  return readdirSync3(dir).filter((entry) => entry.endsWith(".ts") && !entry.endsWith(".definition.ts")).sort().map((entry) => ({
29101
29215
  slug: entry.slice(0, -3),
29102
- source: readFileSync21(join19(dir, entry), "utf-8")
29216
+ source: readFileSync22(join19(dir, entry), "utf-8")
29103
29217
  }));
29104
29218
  }
29105
29219
  function writeHandlerManifest(args) {
@@ -29127,7 +29241,7 @@ function recordHandlerPushed(args) {
29127
29241
  serverId: args.serverId,
29128
29242
  version: 1
29129
29243
  };
29130
- manifest.handlers[args.slug] = hashSource2(readFileSync21(file, "utf-8"));
29244
+ manifest.handlers[args.slug] = hashSource2(readFileSync22(file, "utf-8"));
29131
29245
  writeFileSync13(handlerManifestPath(args.devRootDir, args.serverId), JSON.stringify(manifest, null, 2), "utf-8");
29132
29246
  }
29133
29247
  function readHandlerManifest(devRootDir, serverId) {
@@ -29135,7 +29249,7 @@ function readHandlerManifest(devRootDir, serverId) {
29135
29249
  if (!existsSync22(file))
29136
29250
  return null;
29137
29251
  try {
29138
- const parsed = JSON.parse(readFileSync21(file, "utf-8"));
29252
+ const parsed = JSON.parse(readFileSync22(file, "utf-8"));
29139
29253
  return parsed.version === 1 && parsed.handlers ? parsed : null;
29140
29254
  } catch {
29141
29255
  return null;
@@ -29642,8 +29756,24 @@ function registerApiKeyCommands(program) {
29642
29756
  "old key valid until": data.rotatedFrom ? formatDate(data.rotatedFrom.gracePeriodEndsAt) : "—"
29643
29757
  });
29644
29758
  }));
29645
- apiKeys.command("revoke <apiKey>").description("Permanently revoke an API key (accepts id or name)").action(runAction(async (apiKeyRef) => {
29759
+ apiKeys.command("revoke <apiKey>").description('Permanently revoke an API key (accepts id or name). Needs --confirm "<key name>".').option("--confirm <name>", "The key's exact name, to confirm. Required — revocation is immediate and cannot be undone.").addHelpText("after", [
29760
+ "",
29761
+ "Revocation takes effect at once and cannot be reversed: every caller",
29762
+ "holding this secret starts failing, including CI. To replace a key",
29763
+ "without a gap, use `mcp api-keys rotate` instead — it mints the new",
29764
+ "secret and grace-expires this one over 24h.",
29765
+ "",
29766
+ "Example:",
29767
+ ' $ mcp api-keys revoke ci-build --confirm "ci-build"'
29768
+ ].join(`
29769
+ `)).action(runAction(async (apiKeyRef, opts) => {
29646
29770
  const apiKeyId = await resolveApiKeyId(apiKeyRef);
29771
+ const current = await api.get("/api/v1/api-keys", { apiKeyId });
29772
+ assertConfirmMatches({
29773
+ entityKind: "API key",
29774
+ entityName: current.apiKey.name,
29775
+ confirm: opts.confirm
29776
+ });
29647
29777
  const data = await api.delete("/api/v1/api-keys", { apiKeyId });
29648
29778
  if (isJsonMode()) {
29649
29779
  printJson(data);
@@ -30133,7 +30263,7 @@ import { isAbsolute as isAbsolute4, relative as relative7, resolve as resolve10
30133
30263
  import {
30134
30264
  existsSync as existsSync23,
30135
30265
  mkdirSync as mkdirSync15,
30136
- readFileSync as readFileSync22,
30266
+ readFileSync as readFileSync23,
30137
30267
  readdirSync as readdirSync4,
30138
30268
  unlinkSync,
30139
30269
  writeFileSync as writeFileSync14
@@ -30177,7 +30307,7 @@ function listSessions() {
30177
30307
  continue;
30178
30308
  const path = join21(dir, entry);
30179
30309
  try {
30180
- const parsed = JSON.parse(readFileSync22(path, "utf-8"));
30310
+ const parsed = JSON.parse(readFileSync23(path, "utf-8"));
30181
30311
  if (typeof parsed.pid !== "number") {
30182
30312
  unlinkSync(path);
30183
30313
  continue;
@@ -31545,19 +31675,19 @@ import { homedir as homedir4 } from "node:os";
31545
31675
  import { join as join26 } from "node:path";
31546
31676
 
31547
31677
  // src/lib/dev/agent-connectors/json-config-utils.ts
31548
- import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "node:fs";
31678
+ import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync24, writeFileSync as writeFileSync15 } from "node:fs";
31549
31679
  import { dirname as dirname10, join as join25, basename } from "node:path";
31550
31680
  function readJsonFile(path) {
31551
31681
  if (!existsSync29(path))
31552
31682
  return { ok: true, value: {} };
31553
31683
  try {
31554
- const raw = readFileSync23(path, "utf-8");
31684
+ const raw = readFileSync24(path, "utf-8");
31555
31685
  if (!raw.trim())
31556
31686
  return { ok: true, value: {} };
31557
31687
  return { ok: true, value: JSON.parse(raw) };
31558
31688
  } catch {
31559
31689
  try {
31560
- return { ok: false, raw: readFileSync23(path, "utf-8") };
31690
+ return { ok: false, raw: readFileSync24(path, "utf-8") };
31561
31691
  } catch {
31562
31692
  return { ok: false };
31563
31693
  }
@@ -31579,7 +31709,7 @@ function backupConfig(args) {
31579
31709
  const filename = `${args.agentId}__${basename(args.configPath)}.bak`;
31580
31710
  const backupPath = join25(args.backupsDir, filename);
31581
31711
  if (!existsSync29(backupPath)) {
31582
- const raw = readFileSync23(args.configPath, "utf-8");
31712
+ const raw = readFileSync24(args.configPath, "utf-8");
31583
31713
  writeFileSync15(backupPath, raw, "utf-8");
31584
31714
  }
31585
31715
  return { backupPath, existed: true };
@@ -31590,7 +31720,7 @@ function restoreFromBackup(args) {
31590
31720
  if (!existsSync29(backupPath)) {
31591
31721
  return { restored: false };
31592
31722
  }
31593
- const raw = readFileSync23(backupPath, "utf-8");
31723
+ const raw = readFileSync24(backupPath, "utf-8");
31594
31724
  mkdirSync16(dirname10(args.configPath), { recursive: true });
31595
31725
  writeFileSync15(args.configPath, raw, "utf-8");
31596
31726
  return { restored: true };
@@ -31679,7 +31809,7 @@ var claudeCodeConnector = {
31679
31809
  import {
31680
31810
  existsSync as existsSync31,
31681
31811
  mkdirSync as mkdirSync17,
31682
- readFileSync as readFileSync24,
31812
+ readFileSync as readFileSync25,
31683
31813
  unlinkSync as unlinkSync3,
31684
31814
  writeFileSync as writeFileSync17
31685
31815
  } from "node:fs";
@@ -31738,7 +31868,7 @@ var codexConnector = {
31738
31868
  });
31739
31869
  let existing = "";
31740
31870
  if (existsSync31(path)) {
31741
- existing = readFileSync24(path, "utf-8");
31871
+ existing = readFileSync25(path, "utf-8");
31742
31872
  }
31743
31873
  const conflict = findSectionRange(existing, args.name) !== null;
31744
31874
  let next = existing;
@@ -31760,7 +31890,7 @@ var codexConnector = {
31760
31890
  async remove(args) {
31761
31891
  const path = configPath2();
31762
31892
  if (existsSync31(path)) {
31763
- const existing = readFileSync24(path, "utf-8");
31893
+ const existing = readFileSync25(path, "utf-8");
31764
31894
  const range = findSectionRange(existing, args.name);
31765
31895
  if (range) {
31766
31896
  const next = existing.slice(0, range.start) + existing.slice(range.end);
@@ -31773,7 +31903,7 @@ var codexConnector = {
31773
31903
  agentId: this.id
31774
31904
  });
31775
31905
  if (!restored.restored && existsSync31(path)) {
31776
- const after = readFileSync24(path, "utf-8");
31906
+ const after = readFileSync25(path, "utf-8");
31777
31907
  if (!after.trim()) {
31778
31908
  try {
31779
31909
  unlinkSync3(path);
@@ -33071,6 +33201,10 @@ function registerDeploymentCommands(program) {
33071
33201
  hostname: d.hostname ?? "—",
33072
33202
  exposure: formatDeploymentExposure(d),
33073
33203
  "access mode": d.accessMode,
33204
+ environment: d.environment ?? "production",
33205
+ upstream: d.upstream?.baseUrl ?? "(spec default)",
33206
+ "key header": d.upstream?.apiKeyHeader ?? "—",
33207
+ "key prefix": d.upstream?.apiKeyPrefix ?? "—",
33074
33208
  status: d.status,
33075
33209
  version: d.version,
33076
33210
  "deployed at": d.deployedAt ? formatDate(d.deployedAt) : "—",
@@ -33252,12 +33386,12 @@ async function confirmRollback(deploymentId) {
33252
33386
  }
33253
33387
 
33254
33388
  // src/commands/doctor.ts
33255
- import { existsSync as existsSync37, statSync as statSync6, readFileSync as readFileSync26 } from "node:fs";
33389
+ import { existsSync as existsSync37, statSync as statSync6, readFileSync as readFileSync27 } from "node:fs";
33256
33390
  import { homedir as homedir9, platform as platform6 } from "node:os";
33257
33391
  import { join as join32, delimiter as delimiter3 } from "node:path";
33258
33392
 
33259
33393
  // src/lib/version-check.ts
33260
- import { existsSync as existsSync36, mkdirSync as mkdirSync18, readFileSync as readFileSync25, writeFileSync as writeFileSync21 } from "node:fs";
33394
+ import { existsSync as existsSync36, mkdirSync as mkdirSync18, readFileSync as readFileSync26, writeFileSync as writeFileSync21 } from "node:fs";
33261
33395
  import { join as join31 } from "node:path";
33262
33396
  var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
33263
33397
  var FETCH_TIMEOUT_MS2 = 2000;
@@ -33272,7 +33406,7 @@ function readCache() {
33272
33406
  if (!existsSync36(cacheFile()))
33273
33407
  return null;
33274
33408
  try {
33275
- const raw = JSON.parse(readFileSync25(cacheFile(), "utf-8"));
33409
+ const raw = JSON.parse(readFileSync26(cacheFile(), "utf-8"));
33276
33410
  if (typeof raw.latest !== "string" || typeof raw.fetchedAt !== "number")
33277
33411
  return null;
33278
33412
  return { latest: raw.latest, fetchedAt: raw.fetchedAt };
@@ -33418,7 +33552,7 @@ function checkConfigFile() {
33418
33552
  } catch {}
33419
33553
  }
33420
33554
  try {
33421
- JSON.parse(readFileSync26(path, "utf-8"));
33555
+ JSON.parse(readFileSync27(path, "utf-8"));
33422
33556
  } catch (err) {
33423
33557
  return {
33424
33558
  name: "Config file",
@@ -34300,7 +34434,7 @@ function registerInstallationCommands(program) {
34300
34434
  }
34301
34435
 
34302
34436
  // src/commands/invoke.ts
34303
- import { existsSync as existsSync38, readFileSync as readFileSync27 } from "node:fs";
34437
+ import { existsSync as existsSync38, readFileSync as readFileSync28 } from "node:fs";
34304
34438
  import { join as join33 } from "node:path";
34305
34439
 
34306
34440
  // src/lib/dev/local-invoke.ts
@@ -34393,9 +34527,9 @@ function selectCodeModeInvocation(args) {
34393
34527
  }
34394
34528
  function readCodeOption(raw) {
34395
34529
  if (raw === "@-")
34396
- return readFileSync27(0, "utf-8");
34530
+ return readFileSync28(0, "utf-8");
34397
34531
  if (raw.startsWith("@"))
34398
- return readFileSync27(raw.slice(1), "utf-8");
34532
+ return readFileSync28(raw.slice(1), "utf-8");
34399
34533
  return raw;
34400
34534
  }
34401
34535
  function parseTimeoutMs4(value, fallbackMs) {
@@ -34677,7 +34811,7 @@ async function runLocalInvoke(args) {
34677
34811
  throw new CliExitError(1);
34678
34812
  }
34679
34813
  const env = {
34680
- ...args.envFile ? parseEnvFile(readFileSync27(args.envFile, "utf-8")) : {},
34814
+ ...args.envFile ? parseEnvFile(readFileSync28(args.envFile, "utf-8")) : {},
34681
34815
  ...parseEnvPairs(args.envPairs)
34682
34816
  };
34683
34817
  const port = await findAvailablePort({ range: 40, start: 43117 });
@@ -35669,7 +35803,7 @@ import {
35669
35803
  existsSync as existsSync39,
35670
35804
  mkdirSync as mkdirSync19,
35671
35805
  readdirSync as readdirSync5,
35672
- readFileSync as readFileSync28,
35806
+ readFileSync as readFileSync29,
35673
35807
  rmSync as rmSync3,
35674
35808
  statSync as statSync7
35675
35809
  } from "node:fs";
@@ -35689,7 +35823,7 @@ function readManifestFromPackageDir(packageDir) {
35689
35823
  return null;
35690
35824
  let pkg;
35691
35825
  try {
35692
- pkg = JSON.parse(readFileSync28(pkgPath, "utf-8"));
35826
+ pkg = JSON.parse(readFileSync29(pkgPath, "utf-8"));
35693
35827
  } catch {
35694
35828
  return null;
35695
35829
  }
@@ -37424,7 +37558,7 @@ function displayValueFor(field, value) {
37424
37558
  import {
37425
37559
  existsSync as existsSync40,
37426
37560
  mkdirSync as mkdirSync20,
37427
- readFileSync as readFileSync29,
37561
+ readFileSync as readFileSync30,
37428
37562
  writeFileSync as writeFileSync22
37429
37563
  } from "node:fs";
37430
37564
  import { join as join35 } from "node:path";
@@ -37506,7 +37640,7 @@ function readTuiState() {
37506
37640
  return {};
37507
37641
  let parsed;
37508
37642
  try {
37509
- parsed = JSON.parse(readFileSync29(path, "utf-8"));
37643
+ parsed = JSON.parse(readFileSync30(path, "utf-8"));
37510
37644
  } catch {
37511
37645
  return {};
37512
37646
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcpcloud/cli",
3
- "version": "0.23.0",
3
+ "version": "0.24.0-next-20260917193810",
4
4
  "description": "The official CLI for MCPCloud — manage projects, servers, skills, and API keys from the terminal",
5
5
  "type": "module",
6
6
  "bin": {