@mcpcloud/cli 0.24.1 → 0.25.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.
Files changed (3) hide show
  1. package/README.md +6 -2
  2. package/dist/index.js +1297 -733
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4155,6 +4155,45 @@ ${suggestions.map((s) => ` • ${describeCandidate(s)}`).join(`
4155
4155
  Run \`${kind.listHint}\` to see available ${kind.label}s.`);
4156
4156
  }
4157
4157
 
4158
+ // src/lib/resolve-cross-org.ts
4159
+ async function otherOrganizationIds(currentOrgId) {
4160
+ try {
4161
+ const data = await api.get("/api/v1/organizations");
4162
+ return data.organizations.filter((org) => org.id && org.id !== currentOrgId).map((org) => org.id).slice(0, 10);
4163
+ } catch {
4164
+ return [];
4165
+ }
4166
+ }
4167
+ async function findInAnotherOrganization(ref, currentOrgId, loadIn) {
4168
+ const lower = ref.toLowerCase();
4169
+ for (const organizationId of await otherOrganizationIds(currentOrgId)) {
4170
+ let candidates;
4171
+ try {
4172
+ candidates = await loadIn(organizationId);
4173
+ } catch {
4174
+ continue;
4175
+ }
4176
+ const found = candidates.find((candidate) => candidate.id === ref) ?? candidates.find((candidate) => candidate.name.toLowerCase() === lower) ?? candidates.find((candidate) => candidate.slug === lower);
4177
+ if (found) {
4178
+ return { organizationId, id: found.id, name: found.name };
4179
+ }
4180
+ }
4181
+ return null;
4182
+ }
4183
+ function isOrganizationAccessError(err) {
4184
+ return err instanceof McpCloudApiError && (err.error.code === "organization_access_denied" || err.error.code === "organization_access_required");
4185
+ }
4186
+ async function organizationAccessError(args) {
4187
+ if (args.crossOrganizationHint) {
4188
+ const elsewhere = await findInAnotherOrganization(args.ref, args.organizationId, args.loadIn);
4189
+ if (elsewhere) {
4190
+ return foundInAnotherOrganizationError(args.kind, args.ref, elsewhere);
4191
+ }
4192
+ }
4193
+ return new CliExitError(1, `You are not a member of organization ${args.organizationId} (from --org or your saved default).
4194
+ ` + `If the ${args.kind.label} belongs to another organization, pass --org <id> — \`mcp orgs list\` shows yours.`);
4195
+ }
4196
+
4158
4197
  // src/lib/resolve.ts
4159
4198
  var KINDS = {
4160
4199
  server: {
@@ -4244,30 +4283,6 @@ async function resolveQualified(kind, spec, ref, scope) {
4244
4283
  const settled = await settleMatches(spec, ref, matches);
4245
4284
  return { id: settled.id, matchedBy: "qualified", name: settled.name };
4246
4285
  }
4247
- async function otherOrganizationIds(currentOrgId) {
4248
- try {
4249
- const data = await api.get("/api/v1/organizations");
4250
- return data.organizations.filter((org) => org.id && org.id !== currentOrgId).map((org) => org.id).slice(0, 10);
4251
- } catch {
4252
- return [];
4253
- }
4254
- }
4255
- async function findInAnotherOrganization(kind, spec, ref, scope) {
4256
- const lower = ref.toLowerCase();
4257
- for (const organizationId of await otherOrganizationIds(scope.organizationId)) {
4258
- let candidates;
4259
- try {
4260
- candidates = await loadCandidates(kind, spec, { organizationId });
4261
- } catch {
4262
- continue;
4263
- }
4264
- const found = candidates.find((candidate) => candidate.id === ref) ?? candidates.find((candidate) => candidate.name.toLowerCase() === lower) ?? candidates.find((candidate) => candidate.slug === lower);
4265
- if (found) {
4266
- return { organizationId, id: found.id, name: found.name };
4267
- }
4268
- }
4269
- return null;
4270
- }
4271
4286
  async function resolveRef(kind, reference, scope, options = {}) {
4272
4287
  const spec = KINDS[kind];
4273
4288
  const ref = reference.trim();
@@ -4286,6 +4301,15 @@ async function resolveRef(kind, reference, scope, options = {}) {
4286
4301
  await probeById(spec, scope, ref);
4287
4302
  return { id: ref, matchedBy: "id" };
4288
4303
  } catch (err) {
4304
+ if (isOrganizationAccessError(err)) {
4305
+ throw await organizationAccessError({
4306
+ kind: spec,
4307
+ ref,
4308
+ organizationId: scope.organizationId,
4309
+ crossOrganizationHint: options.crossOrganizationHint !== false,
4310
+ loadIn: (organizationId) => loadCandidates(kind, spec, { organizationId })
4311
+ });
4312
+ }
4289
4313
  if (!isNotFoundError(err)) {
4290
4314
  if (err instanceof McpCloudApiError)
4291
4315
  throw err;
@@ -4301,7 +4325,16 @@ async function resolveRef(kind, reference, scope, options = {}) {
4301
4325
  let candidates;
4302
4326
  try {
4303
4327
  candidates = await loadCandidates(kind, spec, scope);
4304
- } catch {
4328
+ } catch (err) {
4329
+ if (isOrganizationAccessError(err)) {
4330
+ throw await organizationAccessError({
4331
+ kind: spec,
4332
+ ref,
4333
+ organizationId: scope.organizationId,
4334
+ crossOrganizationHint: options.crossOrganizationHint !== false,
4335
+ loadIn: (organizationId) => loadCandidates(kind, spec, { organizationId })
4336
+ });
4337
+ }
4305
4338
  return { id: ref, matchedBy: "verbatim" };
4306
4339
  }
4307
4340
  const byId = candidates.find((c) => c.id === ref);
@@ -4323,7 +4356,7 @@ async function resolveRef(kind, reference, scope, options = {}) {
4323
4356
  return { id: ref, matchedBy: "verbatim" };
4324
4357
  } catch {
4325
4358
  if (options.crossOrganizationHint !== false) {
4326
- const elsewhere = await findInAnotherOrganization(kind, spec, ref, scope);
4359
+ const elsewhere = await findInAnotherOrganization(ref, scope.organizationId, (organizationId) => loadCandidates(kind, spec, { organizationId }));
4327
4360
  if (elsewhere) {
4328
4361
  throw foundInAnotherOrganizationError(spec, ref, elsewhere);
4329
4362
  }
@@ -7668,10 +7701,6 @@ function registerServerExportCommands(servers) {
7668
7701
  }));
7669
7702
  }
7670
7703
 
7671
- // src/commands/servers-lifecycle.ts
7672
- import { existsSync as existsSync6 } from "node:fs";
7673
- import { isAbsolute as isAbsolute2, resolve as resolve4 } from "node:path";
7674
-
7675
7704
  // src/lib/deployment-display.ts
7676
7705
  function formatDeploymentExposure(deployment) {
7677
7706
  const exposure = deployment.exposureTarget ?? null;
@@ -7689,6 +7718,118 @@ function formatDeploymentExposure(deployment) {
7689
7718
  return target ?? "unknown";
7690
7719
  }
7691
7720
 
7721
+ // src/commands/servers-deploy-shared.ts
7722
+ var DEPLOY_TARGETS = [
7723
+ "workersDev",
7724
+ "managedDomain"
7725
+ ];
7726
+ var ACCESS_MODES = [
7727
+ "public",
7728
+ "privateOrg",
7729
+ "unlisted"
7730
+ ];
7731
+ function emptyRuntimeConfig() {
7732
+ return {
7733
+ upstreamBaseUrl: null,
7734
+ upstreamApiKey: null,
7735
+ upstreamApiKeyHeader: null,
7736
+ upstreamApiKeyPrefix: null,
7737
+ upstreamCustomHeaders: null,
7738
+ additionalPlainTextBindings: null,
7739
+ additionalSecretBindings: null
7740
+ };
7741
+ }
7742
+ function reportDeployFailure(terminalEvent) {
7743
+ if (!terminalEvent || !isFailureEvent(terminalEvent))
7744
+ return false;
7745
+ const reason = formatEventDetails(terminalEvent.details);
7746
+ printError(`Deploy failed: ${reason ?? terminalEvent.message}`);
7747
+ return true;
7748
+ }
7749
+ async function isOnlyDeployment(args) {
7750
+ try {
7751
+ const listing = await api.get(`/api/v1/server/deployments?organizationId=${encodeURIComponent(args.orgId)}&serverId=${encodeURIComponent(args.serverId)}&limit=2`);
7752
+ const rows = listing.deployments;
7753
+ if (!Array.isArray(rows) || rows.length === 0)
7754
+ return false;
7755
+ return rows.every((row) => row.id === args.deploymentId);
7756
+ } catch {
7757
+ return false;
7758
+ }
7759
+ }
7760
+ async function refreshDeploymentRow(args) {
7761
+ try {
7762
+ const refreshed = await api.get(`/api/v1/server/deployments?organizationId=${encodeURIComponent(args.orgId)}&serverId=${encodeURIComponent(args.serverId)}&limit=5`);
7763
+ return (refreshed.deployments ?? []).find((entry) => entry.id === args.deploymentId) ?? null;
7764
+ } catch {
7765
+ return null;
7766
+ }
7767
+ }
7768
+ async function waitForDeploy(args) {
7769
+ const dep = args.deployment;
7770
+ let terminalEvent = null;
7771
+ let waitDone = !args.wait;
7772
+ if (args.wait) {
7773
+ if (isTerminalDeploymentStatus(dep.status)) {
7774
+ waitDone = true;
7775
+ } else {
7776
+ const totalTimeoutMs = Math.max(10, Number.parseInt(args.waitTimeout, 10) || 300) * 1000;
7777
+ if (!isJsonMode()) {
7778
+ printStep(`Following events for ${c.bold(dep.deploymentId)}${args.announceTimeout ? ` (timeout ${Math.round(totalTimeoutMs / 1000)}s)` : ""}…`);
7779
+ }
7780
+ const result = await followDeploymentEvents({
7781
+ organizationId: args.orgId,
7782
+ deploymentId: dep.deploymentId,
7783
+ totalTimeoutMs,
7784
+ onEvent: (event) => {
7785
+ if (!isJsonMode())
7786
+ printInfo(formatEventLine(event));
7787
+ }
7788
+ });
7789
+ terminalEvent = result.terminalEvent;
7790
+ waitDone = result.done;
7791
+ if (!waitDone && !isJsonMode()) {
7792
+ printError(`Deploy did not reach a terminal state within ${Math.round(totalTimeoutMs / 1000)}s. Check \`mcp deployments get ${dep.deploymentId}\` and \`mcp deployments health ${dep.deploymentId}\`, or tail \`mcp deployments logs ${dep.deploymentId} --follow\`.`);
7793
+ }
7794
+ }
7795
+ }
7796
+ const deployFailed = dep.status === "failed" || Boolean(terminalEvent && isFailureEvent(terminalEvent));
7797
+ return { terminalEvent, waitDone, deployFailed };
7798
+ }
7799
+ function terminalEventFields(terminalEvent) {
7800
+ if (!terminalEvent)
7801
+ return {};
7802
+ return {
7803
+ "terminal event": `${terminalEvent.stage} (${terminalEvent.level}) — ${terminalEvent.message}`,
7804
+ "terminal at": formatDate(terminalEvent.timestamp)
7805
+ };
7806
+ }
7807
+ async function finishDeployReport(args) {
7808
+ const dep = args.deployment;
7809
+ if (!args.accessModeRequested && dep.accessMode === "public" && await isOnlyDeployment({
7810
+ deploymentId: dep.deploymentId,
7811
+ orgId: args.orgId,
7812
+ serverId: args.serverId
7813
+ })) {
7814
+ printWarn("This first deployment is public — anyone with the URL can call it. Redeploy with --access-mode privateOrg or unlisted to require sign-in (recommended when the upstream API needs credentials).");
7815
+ }
7816
+ if (!args.wait && dep.status === "queued") {
7817
+ printInfo(`Deploy is running in the background — follow with \`mcp deployments get ${dep.deploymentId}\` or rerun with --wait.`);
7818
+ }
7819
+ if (args.wait && !args.outcome.waitDone)
7820
+ throw new CliExitError(1);
7821
+ if (args.outcome.deployFailed) {
7822
+ if (!reportDeployFailure(args.outcome.terminalEvent)) {
7823
+ printError("Deploy failed.");
7824
+ }
7825
+ throw new CliExitError(1);
7826
+ }
7827
+ }
7828
+
7829
+ // src/commands/servers-push-spec.ts
7830
+ import { existsSync as existsSync6 } from "node:fs";
7831
+ import { isAbsolute as isAbsolute2, resolve as resolve4 } from "node:path";
7832
+
7692
7833
  // src/lib/dev/spec-watcher.ts
7693
7834
  import { createHash as createHash2 } from "node:crypto";
7694
7835
  import {
@@ -7706,7 +7847,8 @@ async function regenerateDevBundle(args) {
7706
7847
  sourceType: args.sourceType ?? "openapi",
7707
7848
  preserveEnrichment: args.preserveEnrichment ?? true,
7708
7849
  ...args.applySpec ? { applySpec: true } : {},
7709
- ...args.dryRunApply ? { dryRunApply: true } : {}
7850
+ ...args.dryRunApply ? { dryRunApply: true } : {},
7851
+ ...args.renameToOperationIds ? { renameToOperationIds: true } : {}
7710
7852
  });
7711
7853
  return {
7712
7854
  ok: true,
@@ -7812,39 +7954,374 @@ function watchSpecFile(options) {
7812
7954
  };
7813
7955
  }
7814
7956
 
7815
- // src/commands/servers-lifecycle.ts
7816
- var DEPLOY_TARGETS = ["workersDev", "managedDomain"];
7817
- var ACCESS_MODES = [
7818
- "public",
7819
- "privateOrg",
7820
- "unlisted"
7957
+ // src/commands/servers-push-spec-deploy.ts
7958
+ async function deployPushedSpec(args) {
7959
+ const { result, orgId, serverId, target, accessMode, opts } = args;
7960
+ const serverData = await api.get("/api/v1/server", {
7961
+ organizationId: orgId,
7962
+ serverId
7963
+ });
7964
+ const body = {
7965
+ organizationId: orgId,
7966
+ projectId: serverData.server.projectId,
7967
+ serverId,
7968
+ target,
7969
+ ...accessMode ? { accessMode } : {},
7970
+ runtimeConfig: emptyRuntimeConfig()
7971
+ };
7972
+ if (!isJsonMode()) {
7973
+ printStep(`Deploying ${c.bold(serverId)} to ${c.cyan(target)}…`);
7974
+ }
7975
+ const data = await api.post("/api/v1/server/deploy", body);
7976
+ const dep = data.deployment;
7977
+ const outcome = await waitForDeploy({
7978
+ deployment: dep,
7979
+ orgId,
7980
+ wait: opts.wait,
7981
+ waitTimeout: opts.waitTimeout
7982
+ });
7983
+ const { terminalEvent, waitDone, deployFailed } = outcome;
7984
+ let final = dep;
7985
+ if (opts.wait && waitDone) {
7986
+ const row = await refreshDeploymentRow({
7987
+ deploymentId: dep.deploymentId,
7988
+ orgId,
7989
+ serverId
7990
+ });
7991
+ if (row)
7992
+ final = { ...dep, ...row };
7993
+ }
7994
+ if (isJsonMode()) {
7995
+ printJson({
7996
+ pushed: true,
7997
+ diff: result.diff,
7998
+ enrichmentApplied: result.enrichmentApplied,
7999
+ applied: result.applied,
8000
+ deployment: final,
8001
+ waitDone,
8002
+ terminalEvent
8003
+ });
8004
+ if (opts.wait && (!waitDone || deployFailed))
8005
+ throw new CliExitError(1);
8006
+ return;
8007
+ }
8008
+ printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
8009
+ printKeyValue({
8010
+ id: dep.deploymentId,
8011
+ ...final.deploymentUrl ? { url: final.deploymentUrl } : {},
8012
+ target: final.target,
8013
+ "access mode": final.accessMode,
8014
+ status: final.status,
8015
+ health: final.healthStatus,
8016
+ ...terminalEventFields(terminalEvent)
8017
+ });
8018
+ await finishDeployReport({
8019
+ deployment: dep,
8020
+ accessModeRequested: accessMode,
8021
+ orgId,
8022
+ serverId,
8023
+ wait: opts.wait,
8024
+ outcome
8025
+ });
8026
+ }
8027
+
8028
+ // src/commands/tool-name-drift.ts
8029
+ function nameDriftWarning(args) {
8030
+ const drift = args.drift ?? [];
8031
+ const example = drift[0];
8032
+ if (!example)
8033
+ return null;
8034
+ const subject = drift.length === 1 ? "1 tool keeps its current name" : `${drift.length} tools keep their current names`;
8035
+ const alternative = args.offerPushSpecFlag ? " or re-run with --rename-to-operation-ids" : "";
8036
+ return `${subject} (the naming rule changed: codegen now keeps the operationId's case, ` + `e.g. ${example.name} → ${example.codegenName}). ` + `Run \`mcp tools rename --from-operation-ids --server ${args.serverId}\`${alternative} to adopt it.`;
8037
+ }
8038
+ function formatNameList(names, max = 8) {
8039
+ if (names.length <= max)
8040
+ return names.join(", ");
8041
+ return `${names.slice(0, max).join(", ")}, +${names.length - max} more`;
8042
+ }
8043
+
8044
+ // src/commands/servers-push-spec-report.ts
8045
+ function printSpecReconcile(args) {
8046
+ const { result, serverId } = args;
8047
+ const applied = result.applied;
8048
+ if (!applied) {
8049
+ printNameDiff(result);
8050
+ if (args.renameToOperationIds)
8051
+ printRenameUnsupported(serverId);
8052
+ } else {
8053
+ printAppliedSummary(applied);
8054
+ printRenames(applied, serverId, args.renameToOperationIds);
8055
+ if (applied.removedKeys.length > 0) {
8056
+ printWarn(` ! ${applied.removedKeys.length} tool(s) no longer in the spec, left in place: ${applied.removedKeys.join(", ")}`);
8057
+ }
8058
+ for (const warning of applied.warnings)
8059
+ printWarn(` ! ${warning}`);
8060
+ if (!args.renameToOperationIds) {
8061
+ const drift = nameDriftWarning({
8062
+ drift: applied.nameDrift,
8063
+ serverId,
8064
+ offerPushSpecFlag: true
8065
+ });
8066
+ if (drift)
8067
+ printWarn(` ! ${drift}`);
8068
+ }
8069
+ if (!applied.dryRun)
8070
+ printStoredBundle(applied, serverId);
8071
+ if (applied.dryRun) {
8072
+ printInfo(c.dim(" → re-run with --apply to write this to the server’s tools."));
8073
+ }
8074
+ }
8075
+ if (result.enrichmentApplied) {
8076
+ printInfo(c.dim(" → preserved per-tool enrichment."));
8077
+ }
8078
+ printSuccess(applied && !applied.dryRun ? "Spec applied." : "Bundle regenerated (preview).");
8079
+ }
8080
+ function printNameDiff(result) {
8081
+ const summary = summarizeDiff(result.diff);
8082
+ if (!summary.hasChanges) {
8083
+ printInfo(c.dim(" → no tool surface changes; bundle refreshed."));
8084
+ return;
8085
+ }
8086
+ printInfo(` → ${summary.total} tool change${summary.total === 1 ? "" : "s"} by name (nothing written):`);
8087
+ printInfo(summary.text);
8088
+ }
8089
+ function withNames(names, label) {
8090
+ const head = `${names.length} ${label}`;
8091
+ return names.length > 0 ? `${head} (${formatNameList(names)})` : head;
8092
+ }
8093
+ function printAppliedSummary(applied) {
8094
+ const lead = applied.dryRun ? "would apply (nothing written)" : "applied";
8095
+ printInfo(` → ${lead}: ${withNames(applied.inserted, "inserted")}, ` + `${withNames(applied.updated, "updated")}, ${applied.unchanged} unchanged`);
8096
+ }
8097
+ function printRenames(applied, serverId, requested) {
8098
+ if (!requested)
8099
+ return;
8100
+ const renamed = applied.renamed;
8101
+ if (renamed === undefined) {
8102
+ printRenameUnsupported(serverId);
8103
+ return;
8104
+ }
8105
+ if (applied.renameError) {
8106
+ printWarn(` ! the spec was ${applied.dryRun ? "previewed" : "applied"}, but no tools were renamed: ${applied.renameError} ` + `See \`mcp tools rename --from-operation-ids --server ${serverId} --dry-run\`.`);
8107
+ return;
8108
+ }
8109
+ if (renamed.length === 0) {
8110
+ printInfo(c.dim(" → no tools to rename; names already follow the operationId."));
8111
+ return;
8112
+ }
8113
+ printInfo(` → ${applied.dryRun ? "would rename" : "renamed"} ${renamed.length} tool${renamed.length === 1 ? "" : "s"} to the operationId's case:`);
8114
+ for (const entry of renamed)
8115
+ printInfo(` ${entry.from} → ${entry.to}`);
8116
+ if (!applied.dryRun) {
8117
+ printInfo(c.dim(" → the live server keeps the old names until the next deploy; after it, agents calling an old name get unknown-tool."));
8118
+ }
8119
+ }
8120
+ function printRenameUnsupported(serverId) {
8121
+ printWarn(` ! this backend did not report any renames, so it may not support --rename-to-operation-ids. Run \`mcp tools rename --from-operation-ids --server ${serverId}\` instead.`);
8122
+ }
8123
+ function printStoredBundle(applied, serverId) {
8124
+ if (applied.storedBundle) {
8125
+ printInfo(c.dim(" → stored bundle regenerated; a deploy now ships this spec."));
8126
+ return;
8127
+ }
8128
+ printWarn(` ! stored bundle NOT regenerated${applied.storeError ? `: ${applied.storeError}` : ""}. ` + `Deploy ships the stored bundle — run \`mcp servers generate ${serverId} --store\` before deploying.`);
8129
+ }
8130
+
8131
+ // src/commands/servers-push-spec.ts
8132
+ var HELP_AFTER = [
8133
+ "",
8134
+ "Examples:",
8135
+ " # Preview what the spec would change (writes nothing):",
8136
+ " $ mcp servers push-spec srv_123 --spec ./openapi.yaml",
8137
+ "",
8138
+ " # Actually reconcile the tools against the spec, then deploy:",
8139
+ " $ mcp servers push-spec srv_123 --spec ./openapi.yaml --apply --deploy --wait",
8140
+ "",
8141
+ " # Push and deploy in one step:",
8142
+ " $ mcp servers push-spec srv_123 --spec ./openapi.yaml --deploy --wait",
8143
+ "",
8144
+ " # Adopt the current naming rule while applying (getrecording → getRecording):",
8145
+ " $ mcp servers push-spec srv_123 --spec ./openapi.yaml --apply --rename-to-operation-ids",
8146
+ "",
8147
+ " # Idempotent retry-safe push in CI:",
8148
+ " $ mcp --ci --idempotency-key $GITHUB_RUN_ID servers push-spec srv_123 --deploy --wait",
8149
+ "",
8150
+ "Notes (--apply):",
8151
+ " Without --apply this regenerates the BUNDLE and previews the reconcile",
8152
+ " --apply would run (inserted / updated / unchanged) — it does not touch",
8153
+ " the stored tools, so a deploy afterwards still ships the old ones.",
8154
+ " --apply writes the tools AND regenerates the stored bundle (what",
8155
+ " deploy ships), so the next deploy advertises the new spec. If the",
8156
+ " bundle step fails you are told, and --deploy refuses to ship the",
8157
+ " stale one; `mcp servers generate <server> --store` repairs it.",
8158
+ " --apply reconciles: tools match on their stable operation key so",
8159
+ " renames survive, spec-derived shape is refreshed, and curated fields",
8160
+ " (description, tags, riskClass, projections, enrichment) are kept.",
8161
+ " Operations missing from the spec are REPORTED, never deleted.",
8162
+ " It is lossy for hand-edited input schemas — the spec becomes the",
8163
+ " truth — which is why it is opt-in.",
8164
+ "",
8165
+ "Notes (tool names):",
8166
+ " The reconcile never renames a tool. Codegen now keeps the operationId’s",
8167
+ " case (getRecording); servers created before that change keep their",
8168
+ " lowercased names (getrecording), and the preview says how many.",
8169
+ " --rename-to-operation-ids adopts the current rule for those tools,",
8170
+ " keeping tool ids, access grants and saved scenarios — the same rename",
8171
+ " `mcp tools rename --from-operation-ids` performs. The live server keeps",
8172
+ " the old names until the next deploy."
7821
8173
  ];
7822
- function reportDeployFailure(terminalEvent) {
7823
- if (!terminalEvent || !isFailureEvent(terminalEvent))
7824
- return false;
7825
- const reason = formatEventDetails(terminalEvent.details);
7826
- printError(`Deploy failed: ${reason ?? terminalEvent.message}`);
7827
- return true;
8174
+ function registerServerPushSpecCommand(servers) {
8175
+ servers.command("push-spec <server>").description("Regenerate a server’s bundle from a local OpenAPI spec. Previews the tool changes; --apply writes them.").option("--org <organizationId>", "Organization ID").option("--apply", "Write the spec onto the server’s tools, refreshing their schemas. Without this the command only previews.").option("--spec <path>", "Path to the OpenAPI/Swagger spec file", "./openapi.yaml").option("--enrich-on-change", "Preserve per-tool enrichment when the spec changes").option("--rename-to-operation-ids", "Also rename tools whose names differ from codegen’s only by case (getrecording → getRecording). Previews without --apply.").option("--deploy", "Immediately deploy the regenerated bundle to Cloudflare Workers").option("--target <target>", `Deploy target (with --deploy): ${DEPLOY_TARGETS.join(" | ")}`, "workersDev").option("--access-mode <mode>", `Access mode (with --deploy): ${ACCESS_MODES.join(" | ")} (default: keep the server's current mode). Per-member upstream keys need privateOrg or unlisted.`).option("--wait", "With --deploy: tail deployment events until terminal state").option("--wait-timeout <seconds>", "Maximum seconds to wait for terminal state (default 300)", "300").addHelpText("after", HELP_AFTER.join(`
8176
+ `)).action(runAction(async (serverRef, opts) => {
8177
+ await runPushSpec(serverRef, opts);
8178
+ }));
7828
8179
  }
7829
- async function isOnlyDeployment(args) {
7830
- try {
7831
- const listing = await api.get(`/api/v1/server/deployments?organizationId=${encodeURIComponent(args.orgId)}&serverId=${encodeURIComponent(args.serverId)}&limit=2`);
7832
- const rows = listing.deployments;
7833
- if (!Array.isArray(rows) || rows.length === 0)
7834
- return false;
7835
- return rows.every((row) => row.id === args.deploymentId);
7836
- } catch {
7837
- return false;
8180
+ async function runPushSpec(serverRef, opts) {
8181
+ const cwd = process.cwd();
8182
+ const specPath = isAbsolute2(opts.spec) ? opts.spec : resolve4(cwd, opts.spec);
8183
+ if (!existsSync6(specPath)) {
8184
+ throw new Error(`Spec file not found: ${specPath}`);
8185
+ }
8186
+ const orgId = await resolveOrgId(opts.org);
8187
+ const serverId = await resolveServerId(serverRef, orgId);
8188
+ const target = validateChoice("target", opts.target, DEPLOY_TARGETS);
8189
+ const accessMode = opts.accessMode ? validateChoice("access-mode", opts.accessMode, ACCESS_MODES) : undefined;
8190
+ if (!isJsonMode()) {
8191
+ printStep(`Pushing spec ${c.bold(specPath)} to ${c.bold(serverId)}…`);
8192
+ }
8193
+ const { content } = readSpecFile(specPath);
8194
+ const result = await regenerateDevBundle({
8195
+ applySpec: true,
8196
+ dryRunApply: !opts.apply,
8197
+ organizationId: orgId,
8198
+ serverId,
8199
+ rawSpec: content,
8200
+ sourceType: "openapi",
8201
+ preserveEnrichment: Boolean(opts.enrichOnChange),
8202
+ renameToOperationIds: Boolean(opts.renameToOperationIds)
8203
+ });
8204
+ if (!result.ok) {
8205
+ const hint = result.code === "insufficient_scope" ? " push-spec --apply regenerates the server’s code, which needs the servers:build scope; mint the key with --preset ci-build or add --scope servers:build." : "";
8206
+ throw new Error(`Spec regen failed: ${result.message} (code: ${result.code})${hint}`);
8207
+ }
8208
+ if (!isJsonMode()) {
8209
+ printSpecReconcile({
8210
+ result,
8211
+ serverId,
8212
+ renameToOperationIds: Boolean(opts.renameToOperationIds)
8213
+ });
8214
+ }
8215
+ const appliedWithoutStore = result.applied !== null && !result.applied.dryRun && result.applied.storedBundle === false;
8216
+ if (!opts.deploy) {
8217
+ if (isJsonMode()) {
8218
+ printJson({
8219
+ applied: result.applied,
8220
+ diff: result.diff,
8221
+ enrichmentApplied: result.enrichmentApplied,
8222
+ pushed: Boolean(result.applied && !result.applied.dryRun)
8223
+ });
8224
+ } else if (appliedWithoutStore) {
8225
+ printInfo(c.dim(` → run \`mcp servers generate ${serverId} --store\`, then \`mcp servers deploy ${serverId} --wait\` to ship.`));
8226
+ } else {
8227
+ printInfo(c.dim(` → run \`mcp servers deploy ${serverId} --wait\` (or pass --deploy) to ship.`));
8228
+ }
8229
+ return;
7838
8230
  }
8231
+ if (appliedWithoutStore) {
8232
+ throw new Error(`The spec was applied but the stored bundle was not regenerated${result.applied?.storeError ? ` (${result.applied.storeError})` : ""}. ` + `Run \`mcp servers generate ${serverId} --store\`, then deploy.`);
8233
+ }
8234
+ await deployPushedSpec({ result, orgId, serverId, target, accessMode, opts });
7839
8235
  }
7840
- async function refreshDeploymentRow(args) {
7841
- try {
7842
- const refreshed = await api.get(`/api/v1/server/deployments?organizationId=${encodeURIComponent(args.orgId)}&serverId=${encodeURIComponent(args.serverId)}&limit=5`);
7843
- return (refreshed.deployments ?? []).find((entry) => entry.id === args.deploymentId) ?? null;
7844
- } catch {
7845
- return null;
8236
+
8237
+ // src/commands/servers-regenerate.ts
8238
+ function registerServerRegenerateCommand(servers) {
8239
+ servers.command("regenerate <server>").description("Re-fetch a server's source spec and rebuild its tools with the current codegen (preserves customizations). Works for spec-URL, OpenAPI, and GraphQL sources; a pasted or manual server has nothing to re-fetch. Wraps POST /api/v1/server/regenerate.").option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").option("--source-url <url>", "Override the stored spec source URL (needed for GraphQL servers whose introspection endpoint wasn't persisted)").option("--dry-run", "Report what would change (per tool) without writing anything").addHelpText("after", [
8240
+ "",
8241
+ "Regenerate re-introspects/re-parses the source and rebuilds each tool with",
8242
+ "the current codegen, so codegen fixes reach existing servers. It does not",
8243
+ "deploy — run `mcp servers deploy` afterwards to ship the rebuilt bundle.",
8244
+ "It never renames a tool: names that predate the current naming rule",
8245
+ "(getrecording vs getRecording) are reported, and",
8246
+ "`mcp tools rename --from-operation-ids` adopts the rule.",
8247
+ "",
8248
+ "Examples:",
8249
+ " # See which tools the current codegen would change, writing nothing:",
8250
+ " $ mcp servers regenerate srv_123 --dry-run",
8251
+ "",
8252
+ " # Propagate codegen fixes into an existing server, then deploy:",
8253
+ " $ mcp servers regenerate srv_123 && mcp servers deploy srv_123 --wait",
8254
+ "",
8255
+ " # GraphQL server whose introspection endpoint must be supplied:",
8256
+ " $ mcp servers regenerate srv_123 --source-url https://api.example.com/graphql"
8257
+ ].join(`
8258
+ `)).action(runAction(async (serverRef, opts) => {
8259
+ await runRegenerate(serverRef, opts);
8260
+ }));
8261
+ }
8262
+ async function runRegenerate(serverRef, opts) {
8263
+ const orgId = await resolveOrgId(opts.org);
8264
+ let projectId = await resolveOptionalProjectId(opts.project, orgId);
8265
+ const serverId = await resolveServerId(serverRef, orgId, projectId);
8266
+ if (!projectId) {
8267
+ const serverData = await api.get("/api/v1/server", { organizationId: orgId, serverId });
8268
+ projectId = serverData.server.projectId;
8269
+ }
8270
+ if (!isJsonMode()) {
8271
+ printStep(opts.dryRun ? `Previewing a regenerate of ${c.bold(serverId)} from its source spec…` : `Regenerating ${c.bold(serverId)} from its source spec…`);
8272
+ }
8273
+ const data = await api.post("/api/v1/server/regenerate", {
8274
+ organizationId: orgId,
8275
+ projectId,
8276
+ serverId,
8277
+ ...opts.sourceUrl ? { sourceUrl: opts.sourceUrl } : {},
8278
+ ...opts.dryRun ? { dryRun: true } : {}
8279
+ });
8280
+ const r = data.regeneration;
8281
+ const previewIgnored = Boolean(opts.dryRun) && r.dryRun !== true;
8282
+ if (isJsonMode()) {
8283
+ printJson({ regeneration: r });
8284
+ if (previewIgnored)
8285
+ throw new CliExitError(1);
8286
+ return;
7846
8287
  }
8288
+ if (previewIgnored) {
8289
+ printError("This backend does not support --dry-run and REGENERATED the server. The report below is what was written.");
8290
+ }
8291
+ printRegeneration(r, serverId);
8292
+ if (previewIgnored)
8293
+ throw new CliExitError(1);
7847
8294
  }
8295
+ function printRegeneration(r, serverId) {
8296
+ const dryRun = r.dryRun === true;
8297
+ printSuccess(dryRun ? `Regenerate preview for ${c.bold(serverId)} (${r.sourceType}) — nothing written.` : `Regenerated ${c.bold(serverId)} (${r.sourceType}).`);
8298
+ const verb = dryRun ? "that would change" : "updated";
8299
+ printKeyValue({
8300
+ [`tools ${verb}`]: String(r.updated),
8301
+ [`tools ${dryRun ? "that would be added" : "added"}`]: String(r.inserted),
8302
+ ...r.unchanged !== undefined ? { "tools unchanged": String(r.unchanged) } : {},
8303
+ "tools dropped from spec": String(r.removed),
8304
+ generation: r.generationStatus
8305
+ });
8306
+ for (const change of r.changes ?? []) {
8307
+ printInfo(` ~ ${change.name}${change.fields.length > 0 ? ` (${change.fields.join(", ")})` : ""}`);
8308
+ }
8309
+ for (const name of r.insertedNames ?? [])
8310
+ printInfo(` + ${name}`);
8311
+ if (r.removed > 0) {
8312
+ printInfo(c.dim(` → ${r.removed} operation(s) no longer in the spec ${dryRun ? "would be" : "were"} kept, not deleted (removal stays a manual action): ${formatNameList(r.removedKeys, 5)}`));
8313
+ }
8314
+ const drift = nameDriftWarning({
8315
+ drift: r.nameDrift,
8316
+ serverId,
8317
+ offerPushSpecFlag: false
8318
+ });
8319
+ if (drift)
8320
+ printWarn(` ! ${drift}`);
8321
+ printInfo(c.dim(dryRun ? ` → run \`mcp servers regenerate ${serverId}\` (without --dry-run) to apply, then \`mcp servers deploy ${serverId} --wait\`.` : ` → run \`mcp servers deploy ${serverId} --wait\` to ship the regenerated bundle.`));
8322
+ }
8323
+
8324
+ // src/commands/servers-lifecycle.ts
7848
8325
  function registerServerLifecycleCommands(servers) {
7849
8326
  servers.command("deploy <server>").description("Deploy a server's generated bundle to Cloudflare Workers (closes the spec → deploy loop)").option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").option("--target <target>", `Deploy target: ${DEPLOY_TARGETS.join(" | ")}`, "workersDev").option("--access-mode <mode>", `Access mode: ${ACCESS_MODES.join(" | ")} (default: keep the server's current mode). A server with per-member upstream keys needs privateOrg or unlisted — public has no caller identity.`).option("--env <environment>", "Deploy into a named environment (lane) — its own worker, hostname and credentials. Omit, or pass production, for the default lane.").option("--channel <channel>", "Deprecated alias for --env: beta means --env beta, stable means production. Mutually exclusive with --env.").option("--upstream-base-url <url>", "Upstream API base URL bound into the worker").option("--upstream-api-key <key>", "Upstream API key (encrypted at rest server-side)").option("--upstream-api-key-header <header>", "Header name to inject the upstream API key on (default: Authorization)").option("--upstream-api-key-prefix <prefix>", 'Prefix for the upstream API key value (e.g. "Bearer ")').option("--wait", "Tail deployment events until the deploy reaches a terminal state").option("--wait-timeout <seconds>", "Maximum seconds to wait for terminal state (default 300)", "300").addHelpText("after", [
7850
8327
  "",
@@ -7863,389 +8340,91 @@ function registerServerLifecycleCommands(servers) {
7863
8340
  " $ mcp servers deploy srv_123 --env staging --wait"
7864
8341
  ].join(`
7865
8342
  `)).action(runAction(async (serverRef, opts) => {
7866
- const target = validateChoice("target", opts.target, DEPLOY_TARGETS);
7867
- const environment = resolveDeployEnvironment(opts);
7868
- const accessMode = opts.accessMode ? validateChoice("access-mode", opts.accessMode, ACCESS_MODES) : undefined;
7869
- const orgId = await resolveOrgId(opts.org);
7870
- let projectId = await resolveOptionalProjectId(opts.project, orgId);
7871
- const serverId = await resolveServerId(serverRef, orgId, projectId);
7872
- if (!projectId) {
7873
- const serverData = await api.get("/api/v1/server", {
7874
- organizationId: orgId,
7875
- serverId
7876
- });
7877
- projectId = serverData.server.projectId;
7878
- }
7879
- const runtimeConfig = {
7880
- upstreamBaseUrl: opts.upstreamBaseUrl ?? null,
7881
- upstreamApiKey: opts.upstreamApiKey ?? null,
7882
- upstreamApiKeyHeader: opts.upstreamApiKeyHeader ?? null,
7883
- upstreamApiKeyPrefix: opts.upstreamApiKeyPrefix ?? null,
7884
- upstreamCustomHeaders: null,
7885
- additionalPlainTextBindings: null,
7886
- additionalSecretBindings: null
7887
- };
7888
- const body = {
7889
- organizationId: orgId,
7890
- projectId,
7891
- serverId,
7892
- target,
7893
- ...accessMode ? { accessMode } : {},
7894
- ...environment ? { environment } : {},
7895
- runtimeConfig
7896
- };
7897
- if (!isJsonMode()) {
7898
- printStep(`Deploying ${c.bold(serverId)} to ${c.cyan(target)}${environment ? ` in ${c.cyan(environment)}` : ""}…`);
7899
- }
7900
- const data = await api.post("/api/v1/server/deploy", body);
7901
- const dep = data.deployment;
7902
- let terminalEvent = null;
7903
- let waitDone = !opts.wait;
7904
- if (opts.wait) {
7905
- if (isTerminalDeploymentStatus(dep.status)) {
7906
- waitDone = true;
7907
- } else {
7908
- const totalTimeoutMs = Math.max(10, Number.parseInt(opts.waitTimeout, 10) || 300) * 1000;
7909
- if (!isJsonMode()) {
7910
- printStep(`Following events for ${c.bold(dep.deploymentId)} (timeout ${Math.round(totalTimeoutMs / 1000)}s)…`);
7911
- }
7912
- const result = await followDeploymentEvents({
7913
- organizationId: orgId,
7914
- deploymentId: dep.deploymentId,
7915
- totalTimeoutMs,
7916
- onEvent: (event) => {
7917
- if (!isJsonMode()) {
7918
- printInfo(formatEventLine(event));
7919
- }
7920
- }
7921
- });
7922
- terminalEvent = result.terminalEvent;
7923
- waitDone = result.done;
7924
- if (!waitDone && !isJsonMode()) {
7925
- printError(`Deploy did not reach a terminal state within ${Math.round(totalTimeoutMs / 1000)}s. Check \`mcp deployments get ${dep.deploymentId}\` and \`mcp deployments health ${dep.deploymentId}\`, or tail \`mcp deployments logs ${dep.deploymentId} --follow\`.`);
7926
- }
7927
- }
7928
- }
7929
- const deployFailed = dep.status === "failed" || Boolean(terminalEvent && isFailureEvent(terminalEvent));
7930
- let final = dep;
7931
- if (opts.wait && waitDone) {
7932
- const row = await refreshDeploymentRow({
7933
- deploymentId: dep.deploymentId,
7934
- orgId,
7935
- serverId
7936
- });
7937
- if (row)
7938
- final = { ...dep, ...row };
7939
- }
7940
- if (isJsonMode()) {
7941
- printJson({ deployment: final, waitDone, terminalEvent });
7942
- if (opts.wait && (!waitDone || deployFailed))
7943
- throw new CliExitError(1);
7944
- return;
7945
- }
7946
- printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
7947
- printKeyValue({
7948
- id: dep.deploymentId,
7949
- ...environment ? { environment } : {},
7950
- ...final.deploymentUrl ? { url: final.deploymentUrl } : {},
7951
- exposure: formatDeploymentExposure(final),
7952
- "access mode": dep.accessMode,
7953
- status: final.status,
7954
- health: final.healthStatus,
7955
- ...terminalEvent ? {
7956
- "terminal event": `${terminalEvent.stage} (${terminalEvent.level}) — ${terminalEvent.message}`,
7957
- "terminal at": formatDate(terminalEvent.timestamp)
7958
- } : {}
7959
- });
7960
- if (!accessMode && dep.accessMode === "public" && await isOnlyDeployment({
7961
- deploymentId: dep.deploymentId,
7962
- orgId,
7963
- serverId
7964
- })) {
7965
- printWarn("This first deployment is public — anyone with the URL can call it. Redeploy with --access-mode privateOrg or unlisted to require sign-in (recommended when the upstream API needs credentials).");
7966
- }
7967
- if (!opts.wait && dep.status === "queued") {
7968
- printInfo(`Deploy is running in the background — follow with \`mcp deployments get ${dep.deploymentId}\` or rerun with --wait.`);
7969
- }
7970
- if (opts.wait && !waitDone)
7971
- throw new CliExitError(1);
7972
- if (deployFailed) {
7973
- if (!reportDeployFailure(terminalEvent)) {
7974
- printError("Deploy failed.");
7975
- }
7976
- throw new CliExitError(1);
7977
- }
8343
+ await runDeploy(serverRef, opts);
7978
8344
  }));
7979
- servers.command("push-spec <server>").description("Regenerate a server’s bundle from a local OpenAPI spec. Previews the tool changes; --apply writes them.").option("--org <organizationId>", "Organization ID").option("--apply", "Write the spec onto the server’s tools, refreshing their schemas. Without this the command only previews.").option("--spec <path>", "Path to the OpenAPI/Swagger spec file", "./openapi.yaml").option("--enrich-on-change", "Preserve per-tool enrichment when the spec changes").option("--deploy", "Immediately deploy the regenerated bundle to Cloudflare Workers").option("--target <target>", `Deploy target (with --deploy): ${DEPLOY_TARGETS.join(" | ")}`, "workersDev").option("--access-mode <mode>", `Access mode (with --deploy): ${ACCESS_MODES.join(" | ")} (default: keep the server's current mode). Per-member upstream keys need privateOrg or unlisted.`).option("--wait", "With --deploy: tail deployment events until terminal state").option("--wait-timeout <seconds>", "Maximum seconds to wait for terminal state (default 300)", "300").addHelpText("after", [
7980
- "",
7981
- "Examples:",
7982
- " # Preview what the spec would change (writes nothing):",
7983
- " $ mcp servers push-spec srv_123 --spec ./openapi.yaml",
7984
- "",
7985
- " # Actually reconcile the tools against the spec, then deploy:",
7986
- " $ mcp servers push-spec srv_123 --spec ./openapi.yaml --apply --deploy --wait",
7987
- "",
7988
- " # Push and deploy in one step:",
7989
- " $ mcp servers push-spec srv_123 --spec ./openapi.yaml --deploy --wait",
7990
- "",
7991
- " # Idempotent retry-safe push in CI:",
7992
- " $ mcp --ci --idempotency-key $GITHUB_RUN_ID servers push-spec srv_123 --deploy --wait",
7993
- "",
7994
- "Notes (--apply):",
7995
- " Without --apply this regenerates the BUNDLE and reports the tool",
7996
- " changes the spec implies — it does not touch the stored tools, so a",
7997
- " deploy afterwards still ships the old ones.",
7998
- " --apply writes the tools AND regenerates the stored bundle (what",
7999
- " deploy ships), so the next deploy advertises the new spec. If the",
8000
- " bundle step fails you are told, and --deploy refuses to ship the",
8001
- " stale one; `mcp servers generate <server> --store` repairs it.",
8002
- " --apply reconciles: tools match on their stable operation key so",
8003
- " renames survive, spec-derived shape is refreshed, and curated fields",
8004
- " (description, tags, riskClass, projections, enrichment) are kept.",
8005
- " Operations missing from the spec are REPORTED, never deleted.",
8006
- " It is lossy for hand-edited input schemas — the spec becomes the",
8007
- " truth — which is why it is opt-in."
8008
- ].join(`
8009
- `)).action(runAction(async (serverRef, opts) => {
8010
- const cwd = process.cwd();
8011
- const specPath = isAbsolute2(opts.spec) ? opts.spec : resolve4(cwd, opts.spec);
8012
- if (!existsSync6(specPath)) {
8013
- throw new Error(`Spec file not found: ${specPath}`);
8014
- }
8015
- const orgId = await resolveOrgId(opts.org);
8016
- const serverId = await resolveServerId(serverRef, orgId);
8017
- const target = validateChoice("target", opts.target, DEPLOY_TARGETS);
8018
- const accessMode = opts.accessMode ? validateChoice("access-mode", opts.accessMode, ACCESS_MODES) : undefined;
8019
- if (!isJsonMode()) {
8020
- printStep(`Pushing spec ${c.bold(specPath)} to ${c.bold(serverId)}…`);
8021
- }
8022
- const { content } = readSpecFile(specPath);
8023
- const result = await regenerateDevBundle({
8024
- applySpec: true,
8025
- dryRunApply: !opts.apply,
8026
- organizationId: orgId,
8027
- serverId,
8028
- rawSpec: content,
8029
- sourceType: "openapi",
8030
- preserveEnrichment: Boolean(opts.enrichOnChange)
8031
- });
8032
- if (!result.ok) {
8033
- const hint = result.code === "insufficient_scope" ? " push-spec --apply regenerates the server’s code, which needs the servers:build scope; mint the key with --preset ci-build or add --scope servers:build." : "";
8034
- throw new Error(`Spec regen failed: ${result.message} (code: ${result.code})${hint}`);
8035
- }
8036
- const summary = summarizeDiff(result.diff);
8037
- if (!isJsonMode()) {
8038
- if (!summary.hasChanges) {
8039
- printInfo(c.dim(" → no tool surface changes; bundle refreshed."));
8040
- } else {
8041
- printInfo(` → ${summary.total} tool change${summary.total === 1 ? "" : "s"} by name${result.applied?.dryRun === false ? "" : " (nothing written)"}:`);
8042
- printInfo(summary.text);
8043
- }
8044
- if (result.applied) {
8045
- const {
8046
- dryRun,
8047
- inserted,
8048
- removedKeys,
8049
- unchanged,
8050
- updated,
8051
- warnings
8052
- } = result.applied;
8053
- printInfo(` → ${dryRun ? "would apply" : "applied"}: ${updated.length} updated, ${inserted.length} added, ${unchanged} unchanged`);
8054
- if (removedKeys.length > 0) {
8055
- printWarn(` ! ${removedKeys.length} tool(s) no longer in the spec, left in place: ${removedKeys.join(", ")}`);
8056
- }
8057
- for (const warning of warnings)
8058
- printWarn(` ! ${warning}`);
8059
- if (!dryRun) {
8060
- if (result.applied.storedBundle) {
8061
- printInfo(c.dim(" → stored bundle regenerated; a deploy now ships this spec."));
8062
- } else {
8063
- printWarn(` ! stored bundle NOT regenerated${result.applied.storeError ? `: ${result.applied.storeError}` : ""}. ` + `Deploy ships the stored bundle — run \`mcp servers generate ${serverId} --store\` before deploying.`);
8064
- }
8065
- }
8066
- }
8067
- if (result.applied?.dryRun) {
8068
- printInfo(c.dim(" → re-run with --apply to write this to the server’s tools."));
8069
- }
8070
- if (result.enrichmentApplied) {
8071
- printInfo(c.dim(" → preserved per-tool enrichment."));
8072
- }
8073
- printSuccess(result.applied && !result.applied.dryRun ? "Spec applied." : "Bundle regenerated (preview).");
8074
- }
8075
- const appliedWithoutStore = result.applied !== null && !result.applied.dryRun && result.applied.storedBundle === false;
8076
- if (!opts.deploy) {
8077
- if (isJsonMode()) {
8078
- printJson({
8079
- applied: result.applied,
8080
- diff: result.diff,
8081
- enrichmentApplied: result.enrichmentApplied,
8082
- pushed: Boolean(result.applied && !result.applied.dryRun)
8083
- });
8084
- } else if (appliedWithoutStore) {
8085
- printInfo(c.dim(" → run `mcp servers generate " + serverId + " --store`, then `mcp servers deploy " + serverId + " --wait` to ship."));
8086
- } else {
8087
- printInfo(c.dim(" → run `mcp servers deploy " + serverId + " --wait` (or pass --deploy) to ship."));
8088
- }
8089
- return;
8090
- }
8091
- if (appliedWithoutStore) {
8092
- throw new Error(`The spec was applied but the stored bundle was not regenerated${result.applied?.storeError ? ` (${result.applied.storeError})` : ""}. ` + `Run \`mcp servers generate ${serverId} --store\`, then deploy.`);
8093
- }
8345
+ registerServerPushSpecCommand(servers);
8346
+ registerServerRegenerateCommand(servers);
8347
+ }
8348
+ async function runDeploy(serverRef, opts) {
8349
+ const target = validateChoice("target", opts.target, DEPLOY_TARGETS);
8350
+ const environment = resolveDeployEnvironment(opts);
8351
+ const accessMode = opts.accessMode ? validateChoice("access-mode", opts.accessMode, ACCESS_MODES) : undefined;
8352
+ const orgId = await resolveOrgId(opts.org);
8353
+ let projectId = await resolveOptionalProjectId(opts.project, orgId);
8354
+ const serverId = await resolveServerId(serverRef, orgId, projectId);
8355
+ if (!projectId) {
8094
8356
  const serverData = await api.get("/api/v1/server", {
8095
8357
  organizationId: orgId,
8096
8358
  serverId
8097
8359
  });
8098
- const body = {
8099
- organizationId: orgId,
8100
- projectId: serverData.server.projectId,
8101
- serverId,
8102
- target,
8103
- ...accessMode ? { accessMode } : {},
8104
- runtimeConfig: {
8105
- upstreamBaseUrl: null,
8106
- upstreamApiKey: null,
8107
- upstreamApiKeyHeader: null,
8108
- upstreamApiKeyPrefix: null,
8109
- upstreamCustomHeaders: null,
8110
- additionalPlainTextBindings: null,
8111
- additionalSecretBindings: null
8112
- }
8113
- };
8114
- if (!isJsonMode()) {
8115
- printStep(`Deploying ${c.bold(serverId)} to ${c.cyan(target)}…`);
8116
- }
8117
- const data = await api.post("/api/v1/server/deploy", body);
8118
- const dep = data.deployment;
8119
- let terminalEvent = null;
8120
- let waitDone = !opts.wait;
8121
- if (opts.wait) {
8122
- if (isTerminalDeploymentStatus(dep.status)) {
8123
- waitDone = true;
8124
- } else {
8125
- const totalTimeoutMs = Math.max(10, Number.parseInt(opts.waitTimeout, 10) || 300) * 1000;
8126
- if (!isJsonMode()) {
8127
- printStep(`Following events for ${c.bold(dep.deploymentId)}…`);
8128
- }
8129
- const r = await followDeploymentEvents({
8130
- organizationId: orgId,
8131
- deploymentId: dep.deploymentId,
8132
- totalTimeoutMs,
8133
- onEvent: (event) => {
8134
- if (!isJsonMode())
8135
- printInfo(formatEventLine(event));
8136
- }
8137
- });
8138
- terminalEvent = r.terminalEvent;
8139
- waitDone = r.done;
8140
- if (!waitDone && !isJsonMode()) {
8141
- printError(`Deploy did not reach a terminal state within ${Math.round(totalTimeoutMs / 1000)}s. Check \`mcp deployments get ${dep.deploymentId}\` and \`mcp deployments health ${dep.deploymentId}\`, or tail \`mcp deployments logs ${dep.deploymentId} --follow\`.`);
8142
- }
8143
- }
8144
- }
8145
- const deployFailed = dep.status === "failed" || Boolean(terminalEvent && isFailureEvent(terminalEvent));
8146
- let final = dep;
8147
- if (opts.wait && waitDone) {
8148
- const row = await refreshDeploymentRow({
8149
- deploymentId: dep.deploymentId,
8150
- orgId,
8151
- serverId
8152
- });
8153
- if (row)
8154
- final = { ...dep, ...row };
8155
- }
8156
- if (isJsonMode()) {
8157
- printJson({
8158
- pushed: true,
8159
- diff: result.diff,
8160
- enrichmentApplied: result.enrichmentApplied,
8161
- applied: result.applied,
8162
- deployment: final,
8163
- waitDone,
8164
- terminalEvent
8165
- });
8166
- if (opts.wait && (!waitDone || deployFailed))
8167
- throw new CliExitError(1);
8168
- return;
8169
- }
8170
- printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
8171
- printKeyValue({
8172
- id: dep.deploymentId,
8173
- ...final.deploymentUrl ? { url: final.deploymentUrl } : {},
8174
- target: final.target,
8175
- "access mode": final.accessMode,
8176
- status: final.status,
8177
- health: final.healthStatus,
8178
- ...terminalEvent ? {
8179
- "terminal event": `${terminalEvent.stage} (${terminalEvent.level}) — ${terminalEvent.message}`,
8180
- "terminal at": formatDate(terminalEvent.timestamp)
8181
- } : {}
8182
- });
8183
- if (!accessMode && dep.accessMode === "public" && await isOnlyDeployment({
8360
+ projectId = serverData.server.projectId;
8361
+ }
8362
+ const runtimeConfig = {
8363
+ upstreamBaseUrl: opts.upstreamBaseUrl ?? null,
8364
+ upstreamApiKey: opts.upstreamApiKey ?? null,
8365
+ upstreamApiKeyHeader: opts.upstreamApiKeyHeader ?? null,
8366
+ upstreamApiKeyPrefix: opts.upstreamApiKeyPrefix ?? null,
8367
+ upstreamCustomHeaders: null,
8368
+ additionalPlainTextBindings: null,
8369
+ additionalSecretBindings: null
8370
+ };
8371
+ const body = {
8372
+ organizationId: orgId,
8373
+ projectId,
8374
+ serverId,
8375
+ target,
8376
+ ...accessMode ? { accessMode } : {},
8377
+ ...environment ? { environment } : {},
8378
+ runtimeConfig
8379
+ };
8380
+ if (!isJsonMode()) {
8381
+ printStep(`Deploying ${c.bold(serverId)} to ${c.cyan(target)}${environment ? ` in ${c.cyan(environment)}` : ""}…`);
8382
+ }
8383
+ const data = await api.post("/api/v1/server/deploy", body);
8384
+ const dep = data.deployment;
8385
+ const outcome = await waitForDeploy({
8386
+ deployment: dep,
8387
+ orgId,
8388
+ wait: opts.wait,
8389
+ waitTimeout: opts.waitTimeout,
8390
+ announceTimeout: true
8391
+ });
8392
+ const { terminalEvent, waitDone, deployFailed } = outcome;
8393
+ let final = dep;
8394
+ if (opts.wait && waitDone) {
8395
+ const row = await refreshDeploymentRow({
8184
8396
  deploymentId: dep.deploymentId,
8185
8397
  orgId,
8186
8398
  serverId
8187
- })) {
8188
- printWarn("This first deployment is public — anyone with the URL can call it. Redeploy with --access-mode privateOrg or unlisted to require sign-in (recommended when the upstream API needs credentials).");
8189
- }
8190
- if (!opts.wait && dep.status === "queued") {
8191
- printInfo(`Deploy is running in the background — follow with \`mcp deployments get ${dep.deploymentId}\` or rerun with --wait.`);
8192
- }
8193
- if (opts.wait && !waitDone)
8194
- throw new CliExitError(1);
8195
- if (deployFailed) {
8196
- if (!reportDeployFailure(terminalEvent)) {
8197
- printError("Deploy failed.");
8198
- }
8199
- throw new CliExitError(1);
8200
- }
8201
- }));
8202
- servers.command("regenerate <server>").description("Re-fetch a server's source spec and rebuild its tools with the current codegen (preserves customizations). Works for spec-URL, OpenAPI, and GraphQL sources; a pasted or manual server has nothing to re-fetch. Wraps POST /api/v1/server/regenerate.").option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").option("--source-url <url>", "Override the stored spec source URL (needed for GraphQL servers whose introspection endpoint wasn't persisted)").addHelpText("after", [
8203
- "",
8204
- "Regenerate re-introspects/re-parses the source and rebuilds each tool with",
8205
- "the current codegen, so codegen fixes reach existing servers. It does not",
8206
- "deploy — run `mcp servers deploy` afterwards to ship the rebuilt bundle.",
8207
- "",
8208
- "Examples:",
8209
- " # Propagate codegen fixes into an existing server, then deploy:",
8210
- " $ mcp servers regenerate srv_123 && mcp servers deploy srv_123 --wait",
8211
- "",
8212
- " # GraphQL server whose introspection endpoint must be supplied:",
8213
- " $ mcp servers regenerate srv_123 --source-url https://api.example.com/graphql"
8214
- ].join(`
8215
- `)).action(runAction(async (serverRef, opts) => {
8216
- const orgId = await resolveOrgId(opts.org);
8217
- let projectId = await resolveOptionalProjectId(opts.project, orgId);
8218
- const serverId = await resolveServerId(serverRef, orgId, projectId);
8219
- if (!projectId) {
8220
- const serverData = await api.get("/api/v1/server", { organizationId: orgId, serverId });
8221
- projectId = serverData.server.projectId;
8222
- }
8223
- if (!isJsonMode()) {
8224
- printStep(`Regenerating ${c.bold(serverId)} from its source spec…`);
8225
- }
8226
- const data = await api.post("/api/v1/server/regenerate", {
8227
- organizationId: orgId,
8228
- projectId,
8229
- serverId,
8230
- ...opts.sourceUrl ? { sourceUrl: opts.sourceUrl } : {}
8231
- });
8232
- const r = data.regeneration;
8233
- if (isJsonMode()) {
8234
- printJson({ regeneration: r });
8235
- return;
8236
- }
8237
- printSuccess(`Regenerated ${c.bold(serverId)} (${r.sourceType}).`);
8238
- printKeyValue({
8239
- "tools updated": String(r.updated),
8240
- "tools added": String(r.inserted),
8241
- "tools dropped from spec": String(r.removed),
8242
- generation: r.generationStatus
8243
8399
  });
8244
- if (r.removed > 0) {
8245
- printInfo(c.dim(` → ${r.removed} operation(s) no longer in the spec were kept, not deleted (removal stays a manual action): ${r.removedKeys.slice(0, 5).join(", ")}`));
8246
- }
8247
- printInfo(c.dim(` → run \`mcp servers deploy ${serverId} --wait\` to ship the regenerated bundle.`));
8248
- }));
8400
+ if (row)
8401
+ final = { ...dep, ...row };
8402
+ }
8403
+ if (isJsonMode()) {
8404
+ printJson({ deployment: final, waitDone, terminalEvent });
8405
+ if (opts.wait && (!waitDone || deployFailed))
8406
+ throw new CliExitError(1);
8407
+ return;
8408
+ }
8409
+ printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
8410
+ printKeyValue({
8411
+ id: dep.deploymentId,
8412
+ ...environment ? { environment } : {},
8413
+ ...final.deploymentUrl ? { url: final.deploymentUrl } : {},
8414
+ exposure: formatDeploymentExposure(final),
8415
+ "access mode": dep.accessMode,
8416
+ status: final.status,
8417
+ health: final.healthStatus,
8418
+ ...terminalEventFields(terminalEvent)
8419
+ });
8420
+ await finishDeployReport({
8421
+ deployment: dep,
8422
+ accessModeRequested: accessMode,
8423
+ orgId,
8424
+ serverId,
8425
+ wait: opts.wait,
8426
+ outcome
8427
+ });
8249
8428
  }
8250
8429
 
8251
8430
  // src/commands/servers-traces.ts
@@ -9506,20 +9685,9 @@ function registerServerCommands(program) {
9506
9685
  }));
9507
9686
  }
9508
9687
 
9509
- // src/commands/tools-diff.ts
9510
- import { existsSync as existsSync11, readFileSync as readFileSync14 } from "node:fs";
9511
- import { relative } from "node:path";
9512
-
9513
- // src/lib/dev/handlers-sync.ts
9514
- import {
9515
- existsSync as existsSync10,
9516
- mkdirSync as mkdirSync7,
9517
- readFileSync as readFileSync13,
9518
- statSync,
9519
- writeFileSync as writeFileSync6
9520
- } from "node:fs";
9521
- import { createHash as createHash3 } from "node:crypto";
9522
- import { dirname as dirname5, join as join8 } from "node:path";
9688
+ // src/commands/tools-delete.ts
9689
+ import { existsSync as existsSync10 } from "node:fs";
9690
+ import { join as join8, relative } from "node:path";
9523
9691
 
9524
9692
  // src/lib/dev/state.ts
9525
9693
  import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "node:fs";
@@ -9921,6 +10089,9 @@ async function pushLocalToolEdit(args) {
9921
10089
  return { kind: "missing-file" };
9922
10090
  return { kind: "unknown-tool" };
9923
10091
  }
10092
+ if (local.view.name !== args.toolName) {
10093
+ return { kind: "rename-requested", from: args.toolName, to: local.view.name };
10094
+ }
9924
10095
  const newHash = hashLocalView(local.view);
9925
10096
  if (newHash === local.storedHash) {
9926
10097
  return { kind: "noop" };
@@ -9993,12 +10164,119 @@ async function fetchToolsForServer(args) {
9993
10164
  }));
9994
10165
  }
9995
10166
 
10167
+ // src/commands/tools-context.ts
10168
+ async function resolveToolsContext(opts = {}) {
10169
+ const cwd = process.cwd();
10170
+ const cached = readState(cwd);
10171
+ const serverRef = opts.serverOverride ?? cached?.serverId;
10172
+ if (!serverRef) {
10173
+ if (opts.requireServer === false)
10174
+ return null;
10175
+ printError("No server selected. Pass --server <server> or run from a directory with .mcpcloud/state.json.");
10176
+ throw new CliExitError(1);
10177
+ }
10178
+ const organizationId = opts.orgOverride ?? cached?.organizationId ?? await resolveOrgId(undefined);
10179
+ const serverId = opts.serverOverride ? await resolveServerId(opts.serverOverride, organizationId) : serverRef;
10180
+ let projectId = cached?.projectId;
10181
+ if (!projectId) {
10182
+ const data = await api.get("/api/v1/server", {
10183
+ organizationId,
10184
+ serverId
10185
+ });
10186
+ projectId = data.server.projectId;
10187
+ }
10188
+ return { organizationId, projectId, serverId };
10189
+ }
10190
+
10191
+ // src/commands/tools-delete.ts
10192
+ var HELP_AFTER2 = [
10193
+ "",
10194
+ "This is the remove-and-re-add route: the tool is deleted, and a later",
10195
+ "`mcp servers push-spec <server> --apply` (or `servers regenerate`) re-creates",
10196
+ "it from the spec as a NEW tool — new id, codegen’s current name. The deleted",
10197
+ "tool’s access grants are removed and its saved test scenarios archived.",
10198
+ "To change only the name and keep the id, grants and scenarios, use",
10199
+ "`mcp tools rename` instead.",
10200
+ "",
10201
+ "The stored bundle is regenerated without the tool; the live server keeps",
10202
+ "serving it until `mcp servers deploy <server> --wait`.",
10203
+ "",
10204
+ "Examples:",
10205
+ " $ mcp tools delete getrecording --server srv_123 --confirm getrecording"
10206
+ ];
10207
+ function plural(count, noun) {
10208
+ return `${count} ${noun}${count === 1 ? "" : "s"}`;
10209
+ }
10210
+ function registerToolsDeleteCommand(tools) {
10211
+ tools.command("delete <name>").description('Delete one tool from a server (the remove-and-re-add route). Removes its access grants and archives its saved test scenarios. Requires --confirm "<name>".').option("--org <organizationId>", "Organization ID (defaults to .mcpcloud/state.json)").option("--server <server>", "Server (id, name, or slug; defaults to .mcpcloud/state.json)").option("--confirm <name>", "The exact tool name, typed to confirm the destructive action").addHelpText("after", HELP_AFTER2.join(`
10212
+ `)).action(runAction(async (name, opts) => {
10213
+ await runToolsDelete(name, opts);
10214
+ }));
10215
+ }
10216
+ async function runToolsDelete(toolName, opts) {
10217
+ assertConfirmMatches({
10218
+ entityKind: "tool",
10219
+ entityName: toolName,
10220
+ confirm: opts.confirm
10221
+ });
10222
+ const ctx = await resolveToolsContext({
10223
+ orgOverride: opts.org,
10224
+ serverOverride: opts.server
10225
+ });
10226
+ if (!ctx)
10227
+ return;
10228
+ if (!isJsonMode()) {
10229
+ printStep(`Deleting tool ${c.bold(toolName)} from ${c.bold(ctx.serverId)}…`);
10230
+ }
10231
+ const res = await api.delete("/api/v1/server/tool", {
10232
+ organizationId: ctx.organizationId,
10233
+ serverId: ctx.serverId,
10234
+ toolName
10235
+ });
10236
+ if (isJsonMode()) {
10237
+ printJson(res);
10238
+ return;
10239
+ }
10240
+ printSuccess(`Deleted tool ${c.bold(res.deleted.name)} (${res.deleted.toolId}) from ${c.bold(ctx.serverId)}.`);
10241
+ printInfo(` Removed ${plural(res.removed.accessGrants, "access grant")}; archived ${plural(res.removed.testScenarios, "saved scenario")}.`);
10242
+ if (res.references.length > 0) {
10243
+ printWarn(`${plural(res.references.length, "reference")} still ${res.references.length === 1 ? "points" : "point"} at ${res.deleted.name} — update ${res.references.length === 1 ? "it" : "them"} by hand:`);
10244
+ for (const ref of res.references) {
10245
+ printInfo(` ${ref.kind.padEnd(11)} ${ref.name} (${ref.id})${ref.detail ? ` — ${ref.detail}` : ""}`);
10246
+ }
10247
+ }
10248
+ if (res.storedBundle) {
10249
+ printInfo(c.dim(" → stored bundle regenerated without it."));
10250
+ } else {
10251
+ printWarn(`stored bundle NOT regenerated${res.storeError ? `: ${res.storeError}` : ""}. ` + `Run \`mcp servers generate ${ctx.serverId} --store\` before deploying.`);
10252
+ }
10253
+ printInfo(` → The live server still serves ${res.deleted.name}. Run ${c.bold(`mcp servers deploy ${ctx.serverId} --wait`)} to remove it there.`);
10254
+ const cwd = process.cwd();
10255
+ const localFile = join8(serverToolsDir(devRoot(cwd), ctx.serverId), `${res.deleted.name}.md`);
10256
+ if (existsSync10(localFile)) {
10257
+ printInfo(` → ${relative(cwd, localFile)} is now stale — run ${c.bold(`mcp tools pull --server ${ctx.serverId}`)} to remove it.`);
10258
+ }
10259
+ }
10260
+
10261
+ // src/commands/tools-diff.ts
10262
+ import { existsSync as existsSync12, readFileSync as readFileSync14 } from "node:fs";
10263
+ import { relative as relative2 } from "node:path";
10264
+
9996
10265
  // src/lib/dev/handlers-sync.ts
10266
+ import {
10267
+ existsSync as existsSync11,
10268
+ mkdirSync as mkdirSync7,
10269
+ readFileSync as readFileSync13,
10270
+ statSync,
10271
+ writeFileSync as writeFileSync6
10272
+ } from "node:fs";
10273
+ import { createHash as createHash3 } from "node:crypto";
10274
+ import { dirname as dirname5, join as join9 } from "node:path";
9997
10275
  function toKebabSlug(value) {
9998
10276
  return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "generated-mcp-server";
9999
10277
  }
10000
10278
  function serverBundleHandlersDir(devRootDir) {
10001
- return join8(devRootDir, "server", "src", "tools");
10279
+ return join9(devRootDir, "server", "src", "tools");
10002
10280
  }
10003
10281
  function handlerSlugFromWatcherPath(rel) {
10004
10282
  if (!rel)
@@ -10027,8 +10305,8 @@ function hashSource(source) {
10027
10305
  }
10028
10306
  var lastPushedHash = new Map;
10029
10307
  async function pushLocalHandlerEdit(args) {
10030
- const filePath = join8(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
10031
- if (!existsSync10(filePath)) {
10308
+ const filePath = join9(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
10309
+ if (!existsSync11(filePath)) {
10032
10310
  return { kind: "missing-file" };
10033
10311
  }
10034
10312
  const resolved = resolveToolFromSlug(args.devRootDir, args.serverId, args.slug);
@@ -10179,7 +10457,7 @@ async function resetCloudHandler(args) {
10179
10457
  }
10180
10458
  }
10181
10459
  function writeLocalHandlerFromCloud(args) {
10182
- const filePath = join8(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
10460
+ const filePath = join9(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
10183
10461
  mkdirSync7(dirname5(filePath), { recursive: true });
10184
10462
  writeFileSync6(filePath, args.content, "utf-8");
10185
10463
  lastPushedHash.set(filePath, hashSource(args.content));
@@ -10190,8 +10468,8 @@ function handlerSlugFromToolName(toolName) {
10190
10468
  }
10191
10469
  var RECENT_EDIT_WINDOW_MS = 5 * 60 * 1000;
10192
10470
  function inspectLocalHandler(args) {
10193
- const filePath = join8(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
10194
- if (!existsSync10(filePath))
10471
+ const filePath = join9(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
10472
+ if (!existsSync11(filePath))
10195
10473
  return { kind: "no-local-file" };
10196
10474
  const cached = lastPushedHash.get(filePath);
10197
10475
  const onDisk = hashSource(readFileSync13(filePath, "utf-8"));
@@ -10223,30 +10501,6 @@ async function fetchHandlerOverrideStatuses(args) {
10223
10501
  }));
10224
10502
  }
10225
10503
 
10226
- // src/commands/tools-context.ts
10227
- async function resolveToolsContext(opts = {}) {
10228
- const cwd = process.cwd();
10229
- const cached = readState(cwd);
10230
- const serverRef = opts.serverOverride ?? cached?.serverId;
10231
- if (!serverRef) {
10232
- if (opts.requireServer === false)
10233
- return null;
10234
- printError("No server selected. Pass --server <server> or run from a directory with .mcpcloud/state.json.");
10235
- throw new CliExitError(1);
10236
- }
10237
- const organizationId = opts.orgOverride ?? cached?.organizationId ?? await resolveOrgId(undefined);
10238
- const serverId = opts.serverOverride ? await resolveServerId(opts.serverOverride, organizationId) : serverRef;
10239
- let projectId = cached?.projectId;
10240
- if (!projectId) {
10241
- const data = await api.get("/api/v1/server", {
10242
- organizationId,
10243
- serverId
10244
- });
10245
- projectId = data.server.projectId;
10246
- }
10247
- return { organizationId, projectId, serverId };
10248
- }
10249
-
10250
10504
  // src/commands/tools-diff.ts
10251
10505
  function registerToolsDiffCommand(tools) {
10252
10506
  tools.command("diff <name>").description("Compare a tool's local files (metadata .md + handler .ts) against the cloud version.").option("--org <organizationId>", "Organization ID (defaults to .mcpcloud/state.json)").option("--server <server>", "Server (id, name, or slug; defaults to .mcpcloud/state.json)").option("--metadata-only", "Skip the handler-source diff (faster — only one cloud round-trip).", false).option("--handler-only", "Skip the metadata diff (only compare the .ts handler).", false).addHelpText("after", [
@@ -10325,7 +10579,7 @@ async function runToolsDiff(args) {
10325
10579
  }
10326
10580
  function buildMetadataDiff(args) {
10327
10581
  const filePath = `${serverToolsDir(args.devRootDir, args.serverId)}/${args.cloud.name}.md`;
10328
- if (!existsSync11(filePath)) {
10582
+ if (!existsSync12(filePath)) {
10329
10583
  return {
10330
10584
  hasLocal: false,
10331
10585
  localPath: null,
@@ -10335,7 +10589,7 @@ function buildMetadataDiff(args) {
10335
10589
  diff: {}
10336
10590
  };
10337
10591
  }
10338
- const rel = relative(args.cwd, filePath) || filePath;
10592
+ const rel = relative2(args.cwd, filePath) || filePath;
10339
10593
  let local = null;
10340
10594
  let parseError = null;
10341
10595
  try {
@@ -10381,9 +10635,9 @@ async function buildHandlerDiff(args) {
10381
10635
  }
10382
10636
  const slug = handlerSlugFromToolName(args.toolName);
10383
10637
  const filePath = `${serverBundleHandlersDir(args.devRootDir)}/${slug}.ts`;
10384
- const hasLocal = existsSync11(filePath);
10638
+ const hasLocal = existsSync12(filePath);
10385
10639
  const localContent = hasLocal ? readFileSync14(filePath, "utf-8") : null;
10386
- const localPath = hasLocal ? relative(args.cwd, filePath) || filePath : null;
10640
+ const localPath = hasLocal ? relative2(args.cwd, filePath) || filePath : null;
10387
10641
  const changed = hasLocal ? localContent !== cloudResult.snapshot.content : true;
10388
10642
  return {
10389
10643
  kind: "ok",
@@ -10492,18 +10746,18 @@ function renderBool(value) {
10492
10746
  }
10493
10747
 
10494
10748
  // src/commands/tools-edit.ts
10495
- import { existsSync as existsSync13 } from "node:fs";
10496
- import { join as join10, relative as relative2 } from "node:path";
10749
+ import { existsSync as existsSync14 } from "node:fs";
10750
+ import { join as join11, relative as relative3 } from "node:path";
10497
10751
 
10498
10752
  // src/lib/editor-shell.ts
10499
10753
  import { spawn as spawn2 } from "node:child_process";
10500
- import { existsSync as existsSync12, readFileSync as readFileSync15 } from "node:fs";
10754
+ import { existsSync as existsSync13, readFileSync as readFileSync15 } from "node:fs";
10501
10755
  import { platform as platform2 } from "node:os";
10502
- import { delimiter, join as join9 } from "node:path";
10756
+ import { delimiter, join as join10 } from "node:path";
10503
10757
  var DEFAULT_FALLBACKS = ["vi"];
10504
10758
  function isExecutableFile(path) {
10505
10759
  try {
10506
- return existsSync12(path);
10760
+ return existsSync13(path);
10507
10761
  } catch {
10508
10762
  return false;
10509
10763
  }
@@ -10520,7 +10774,7 @@ function findOnPath(command) {
10520
10774
  if (!dir)
10521
10775
  continue;
10522
10776
  for (const ext of exts) {
10523
- const candidate = join9(dir, command + ext);
10777
+ const candidate = join10(dir, command + ext);
10524
10778
  if (isExecutableFile(candidate))
10525
10779
  return candidate;
10526
10780
  }
@@ -10548,7 +10802,7 @@ function defaultRun(cmd, args) {
10548
10802
  });
10549
10803
  }
10550
10804
  async function runEditor(args) {
10551
- if (!existsSync12(args.filePath)) {
10805
+ if (!existsSync13(args.filePath)) {
10552
10806
  return {
10553
10807
  ok: false,
10554
10808
  reason: "file-missing",
@@ -10616,13 +10870,13 @@ async function runToolsEdit(args) {
10616
10870
  if (!ctx)
10617
10871
  return;
10618
10872
  const cwd = process.cwd();
10619
- const filePath = join10(serverToolsDir(devRoot(cwd), ctx.serverId), `${args.toolName}.md`);
10620
- if (!existsSync13(filePath)) {
10621
- printError(`Tool file not found: ${relative2(cwd, filePath)}`);
10873
+ const filePath = join11(serverToolsDir(devRoot(cwd), ctx.serverId), `${args.toolName}.md`);
10874
+ if (!existsSync14(filePath)) {
10875
+ printError(`Tool file not found: ${relative3(cwd, filePath)}`);
10622
10876
  printInfo(` ${c.dim("Run")} ${c.bold(`mcp tools pull ${args.toolName}`)} ${c.dim("first to materialize it, or check the name is spelled correctly.")}`);
10623
10877
  throw new CliExitError(1);
10624
10878
  }
10625
- printStep(`Opening ${c.bold(relative2(cwd, filePath))} in editor…`);
10879
+ printStep(`Opening ${c.bold(relative3(cwd, filePath))} in editor…`);
10626
10880
  const editorResult = await runEditor({
10627
10881
  filePath,
10628
10882
  ...args.editorOverride ? { command: args.editorOverride } : {}
@@ -10655,6 +10909,12 @@ async function runToolsEdit(args) {
10655
10909
  printInfo(` ${c.dim("Re-open and fix the file, or run")} ${c.bold(`mcp tools pull ${args.toolName}`)} ${c.dim("to reset.")}`);
10656
10910
  throw new CliExitError(1);
10657
10911
  return;
10912
+ case "rename-requested":
10913
+ printError(`The name isn't changed by editing ${args.toolName}.md (you changed \`name:\` to ${c.bold(push.to)}) — nothing was pushed.`);
10914
+ printInfo(` ${c.dim("Rename it with")} ${c.bold(`mcp tools rename ${push.from} ${push.to} --server ${ctx.serverId}`)}${c.dim(", then")} ${c.bold(`mcp tools pull --server ${ctx.serverId}`)} ${c.dim("to refresh the local files.")}`);
10915
+ printInfo(` ${c.dim(`To push your other edits, set \`name:\` back to ${push.from} and save again.`)}`);
10916
+ throw new CliExitError(1);
10917
+ return;
10658
10918
  case "patched":
10659
10919
  if (isJsonMode()) {
10660
10920
  printJson({ ok: true, updatedAt: push.updatedAt, changedFields: push.changedFields });
@@ -11204,7 +11464,7 @@ async function runToolsHandlersList(args) {
11204
11464
  }
11205
11465
 
11206
11466
  // src/commands/tools-pull.ts
11207
- import { relative as relative3 } from "node:path";
11467
+ import { relative as relative4 } from "node:path";
11208
11468
  function registerToolsPullCommand(tools) {
11209
11469
  tools.command("pull [name]").description("Refresh local tool files from the cloud, overwriting any unsaved local edits. Use after `409 conflict` to re-sync.").option("--org <organizationId>", "Organization ID (defaults to .mcpcloud/state.json)").option("--server <server>", "Server (id, name, or slug; defaults to .mcpcloud/state.json)").option("--all", "Refresh every tool for the server (default when no name given)").addHelpText("after", [
11210
11470
  "",
@@ -11268,7 +11528,7 @@ async function runToolsPull(args) {
11268
11528
  });
11269
11529
  return;
11270
11530
  }
11271
- const dirRel = relative3(cwd, serverToolsDir(dest, ctx.serverId)) || ".";
11531
+ const dirRel = relative4(cwd, serverToolsDir(dest, ctx.serverId)) || ".";
11272
11532
  printSuccess(`Pulled ${result.filesWritten} tool${result.filesWritten === 1 ? "" : "s"} to ${c.bold(dirRel + "/")}.`);
11273
11533
  if (args.toolName) {
11274
11534
  printInfo(` ${c.dim("Edit the .md file and re-save to push your changes back.")}`);
@@ -11278,7 +11538,7 @@ async function runToolsPull(args) {
11278
11538
  }
11279
11539
 
11280
11540
  // src/commands/tools-pull-handler.ts
11281
- import { relative as relative4 } from "node:path";
11541
+ import { relative as relative5 } from "node:path";
11282
11542
  function registerToolsPullHandlerCommand(tools) {
11283
11543
  tools.command("pull-handler <name>").description("Refresh a tool's handler source from the cloud, overwriting any local edits. Use after `409 conflict` to re-sync.").option("--org <organizationId>", "Organization ID (defaults to .mcpcloud/state.json)").option("--server <server>", "Server (id, name, or slug; defaults to .mcpcloud/state.json)").option("--force", "Overwrite local file even if it has unsaved changes (default: refuse).", false).addHelpText("after", [
11284
11544
  "",
@@ -11365,7 +11625,7 @@ async function runPullHandler(args) {
11365
11625
  });
11366
11626
  return;
11367
11627
  }
11368
- const fileRel = relative4(cwd, written) || written;
11628
+ const fileRel = relative5(cwd, written) || written;
11369
11629
  printSuccess(`Pulled handler for ${c.bold(result.snapshot.toolName)} (version ${result.snapshot.version}) to ${c.bold(fileRel)}.`);
11370
11630
  if (result.snapshot.isDefault) {
11371
11631
  printInfo(` ${c.dim("Status: default (matches codegen output).")}`);
@@ -11376,8 +11636,274 @@ async function runPullHandler(args) {
11376
11636
  printInfo(` ${c.dim("Re-save the file to push your changes back.")}`);
11377
11637
  }
11378
11638
 
11639
+ // src/commands/tools-rename.ts
11640
+ import { existsSync as existsSync16 } from "node:fs";
11641
+ import { relative as relative6 } from "node:path";
11642
+
11643
+ // src/commands/tools-rename-input.ts
11644
+ import { existsSync as existsSync15, readFileSync as readFileSync16 } from "node:fs";
11645
+ import { isAbsolute as isAbsolute3, resolve as resolve7 } from "node:path";
11646
+ var MODES_HINT = "Pass exactly one of: <old> <new>, --from-operation-ids, or --map @renames.json.";
11647
+ function resolveRenameSelection(args) {
11648
+ const positional = args.oldName !== undefined || args.newName !== undefined;
11649
+ const modes = [positional, Boolean(args.fromOperationIds), args.map !== undefined];
11650
+ const chosen = modes.filter(Boolean).length;
11651
+ if (chosen === 0)
11652
+ throw new CliExitError(1, `Nothing to rename. ${MODES_HINT}`);
11653
+ if (chosen > 1)
11654
+ throw new CliExitError(1, `Choose one way to rename. ${MODES_HINT}`);
11655
+ if (args.fromOperationIds)
11656
+ return { fromOperationIds: true };
11657
+ if (args.map !== undefined) {
11658
+ return { renames: readRenameMap(args.map, args.cwd) };
11659
+ }
11660
+ const from = args.oldName?.trim() ?? "";
11661
+ const to = args.newName?.trim() ?? "";
11662
+ if (!from || !to) {
11663
+ throw new CliExitError(1, "Pass both the current name and the new one: mcp tools rename <old> <new> --server <server>.");
11664
+ }
11665
+ if (from === to) {
11666
+ throw new CliExitError(1, `"${from}" is already the tool's name.`);
11667
+ }
11668
+ return { renames: [{ from, to }] };
11669
+ }
11670
+ function readRenameMap(ref, cwd) {
11671
+ const raw = ref.trim().replace(/^@/, "");
11672
+ if (!raw)
11673
+ throw new CliExitError(1, "--map needs a file path, e.g. --map @renames.json.");
11674
+ const path = isAbsolute3(raw) ? raw : resolve7(cwd, raw);
11675
+ if (!existsSync15(path))
11676
+ throw new CliExitError(1, `--map file not found: ${path}`);
11677
+ let parsed;
11678
+ try {
11679
+ parsed = JSON.parse(readFileSync16(path, "utf-8"));
11680
+ } catch (err) {
11681
+ throw new CliExitError(1, `--map file is not valid JSON (${path}): ${err instanceof Error ? err.message : String(err)}`);
11682
+ }
11683
+ const pairs = parseRenameMap(parsed);
11684
+ if (pairs.length === 0) {
11685
+ throw new CliExitError(1, `--map file lists no renames: ${path}`);
11686
+ }
11687
+ return pairs;
11688
+ }
11689
+ var MAP_SHAPE = 'Expected {"oldName": "newName", …} or [{"from": "oldName", "to": "newName"}, …].';
11690
+ function parseRenameMap(value) {
11691
+ const entries = Array.isArray(value) ? value.map((item) => {
11692
+ if (!item || typeof item !== "object") {
11693
+ throw new CliExitError(1, `--map entry is not an object. ${MAP_SHAPE}`);
11694
+ }
11695
+ const record = item;
11696
+ return [record["from"], record["to"]];
11697
+ }) : value && typeof value === "object" ? Object.entries(value) : (() => {
11698
+ throw new CliExitError(1, `--map file has the wrong shape. ${MAP_SHAPE}`);
11699
+ })();
11700
+ return entries.map(([from, to]) => {
11701
+ if (typeof from !== "string" || !from.trim()) {
11702
+ throw new CliExitError(1, `--map entry has no current name. ${MAP_SHAPE}`);
11703
+ }
11704
+ if (typeof to !== "string" || !to.trim()) {
11705
+ throw new CliExitError(1, `--map entry for "${from}" has no new name. ${MAP_SHAPE}`);
11706
+ }
11707
+ return { from: from.trim(), to: to.trim() };
11708
+ });
11709
+ }
11710
+
11711
+ // src/commands/tools-rename-report.ts
11712
+ var SKIP_LABELS = {
11713
+ already_named: "already named",
11714
+ custom_name: "custom name, kept",
11715
+ graphql: "GraphQL tool",
11716
+ ambiguous: "ambiguous"
11717
+ };
11718
+ function plural2(count, noun) {
11719
+ return `${count} ${noun}${count === 1 ? "" : "s"}`;
11720
+ }
11721
+ function printRenameReport(res, ctx) {
11722
+ const { serverId } = ctx;
11723
+ if (res.renamed.length === 0) {
11724
+ printSuccess(ctx.fromOperationIds ? `Nothing to rename on ${c.bold(serverId)} — no tool name differs from its operationId only by case.` : `Nothing to rename on ${c.bold(serverId)}.`);
11725
+ } else {
11726
+ const count = plural2(res.renamed.length, "tool");
11727
+ printSuccess(res.dryRun ? `Would rename ${count} on ${c.bold(serverId)} — dry run, nothing written.` : `Renamed ${count} on ${c.bold(serverId)}.`);
11728
+ for (const entry of res.renamed)
11729
+ printInfo(` ${entry.from} → ${entry.to}`);
11730
+ }
11731
+ printSkipped(res.skipped);
11732
+ if (res.renamed.length > 0)
11733
+ printCarried(res);
11734
+ printReferences(res.references, serverId);
11735
+ if (res.droppedHandlerOverrides.length > 0) {
11736
+ printWarn(`${res.dryRun ? "Would drop" : "Dropped"} ${plural2(res.droppedHandlerOverrides.length, "edited handler override")} (the handler is regenerated from the spec): ${res.droppedHandlerOverrides.map((entry) => `${entry.toolName} (${entry.path})`).join(", ")}`);
11737
+ }
11738
+ if (res.renamed.length === 0)
11739
+ return;
11740
+ if (res.dryRun) {
11741
+ printInfo(c.dim(" → re-run without --dry-run to apply."));
11742
+ return;
11743
+ }
11744
+ printNextSteps(res, ctx);
11745
+ }
11746
+ function printSkipped(skipped) {
11747
+ if (skipped.length === 0)
11748
+ return;
11749
+ printInfo(` Skipped ${plural2(skipped.length, "tool")}:`);
11750
+ const groups = new Map;
11751
+ for (const entry of skipped) {
11752
+ const bucket = groups.get(entry.reason);
11753
+ if (bucket)
11754
+ bucket.push(entry);
11755
+ else
11756
+ groups.set(entry.reason, [entry]);
11757
+ }
11758
+ for (const [reason, entries] of groups) {
11759
+ const label = SKIP_LABELS[reason] ?? reason;
11760
+ const names = entries.map((entry) => reason === "custom_name" && entry.codegenName ? `${entry.name} (codegen: ${entry.codegenName})` : entry.name);
11761
+ printInfo(` ${label} (${entries.length}): ${formatNameList(names)}`);
11762
+ if (reason === "ambiguous") {
11763
+ for (const entry of entries) {
11764
+ if (entry.detail)
11765
+ printInfo(c.dim(` ${entry.name}: ${entry.detail}`));
11766
+ }
11767
+ }
11768
+ }
11769
+ }
11770
+ function printCarried(res) {
11771
+ const { accessGrants, testScenarios, compositions } = res.carried;
11772
+ printInfo(` ${res.dryRun ? "Would carry" : "Carried"} to the new names: ` + `${plural2(accessGrants, "access grant")}, ` + `${plural2(testScenarios, "saved test scenario")}, ` + `${plural2(compositions, "composition reference")}.`);
11773
+ }
11774
+ function printReferences(references, serverId) {
11775
+ if (references.length === 0)
11776
+ return;
11777
+ const prompts = references.filter((ref) => ref.kind === "prompt").length;
11778
+ const skills = references.length - prompts;
11779
+ const subject = [
11780
+ prompts > 0 ? plural2(prompts, "prompt") : null,
11781
+ skills > 0 ? plural2(skills, "skill") : null
11782
+ ].filter(Boolean).join(" and ");
11783
+ printWarn(`${subject} still ${references.length === 1 ? "mentions" : "mention"} an old tool name — that text is not rewritten; update it by hand:`);
11784
+ for (const ref of references) {
11785
+ printInfo(` ${ref.kind.padEnd(6)} ${ref.name} (${ref.id}): ${ref.toolNames.join(", ")}`);
11786
+ }
11787
+ printInfo(c.dim(` prompts: \`mcp servers prompts set ${serverId} <name>\` · skills: \`mcp skills set-content <skill>\``));
11788
+ }
11789
+ function printNextSteps(res, ctx) {
11790
+ if (res.storedBundle) {
11791
+ printInfo(c.dim(" → stored bundle regenerated with the new names."));
11792
+ } else {
11793
+ printWarn(`stored bundle NOT regenerated${res.storeError ? `: ${res.storeError}` : ""}. ` + `Run \`mcp servers generate ${ctx.serverId} --store\` before deploying.`);
11794
+ }
11795
+ printInfo(` → The live server still serves the old names. Run ${c.bold(`mcp servers deploy ${ctx.serverId} --wait`)} to ship the new ones — after that, agents calling an old name get unknown-tool.`);
11796
+ if (ctx.localToolsDir) {
11797
+ printInfo(` → Local tool files in ${ctx.localToolsDir}/ still use the old names — run ${c.bold(`mcp tools pull --server ${ctx.serverId}`)} to refresh them.`);
11798
+ }
11799
+ }
11800
+
11801
+ // src/commands/tools-rename.ts
11802
+ var HELP_AFTER3 = [
11803
+ "",
11804
+ "A rename keeps the tool id, so what points at the tool follows it: end-user",
11805
+ "and RBAC access grants, saved test scenarios, and parent-server composition",
11806
+ "allowlists are rewritten to the new name. Prompt and skill TEXT is not — the",
11807
+ "report lists what still mentions an old name.",
11808
+ "",
11809
+ "The stored bundle is regenerated with the new names; the LIVE server keeps",
11810
+ "the old ones until `mcp servers deploy <server> --wait`. After that deploy an",
11811
+ "agent calling an old name gets unknown-tool.",
11812
+ "",
11813
+ "--from-operation-ids adopts the current naming rule: codegen now keeps the",
11814
+ "operationId's case (getRecording), where servers created earlier were named",
11815
+ "getrecording. Only names that differ from codegen by case are renamed; a",
11816
+ "custom name is kept and reported. A tool with an edited handler override is",
11817
+ "refused unless --drop-handler-overrides discards the override.",
11818
+ "",
11819
+ "Examples:",
11820
+ " $ mcp tools rename getrecording getRecording --server srv_123",
11821
+ " $ mcp tools rename --from-operation-ids --server srv_123 --dry-run",
11822
+ " $ mcp tools rename --map @renames.json --server srv_123",
11823
+ ' # renames.json: {"getrecording": "getRecording"} or [{"from": "…", "to": "…"}]'
11824
+ ];
11825
+ function registerToolsRenameCommand(tools) {
11826
+ tools.command("rename [old] [new]").description("Rename tools on a server, keeping their ids — access grants, saved test scenarios and composition references follow. Regenerates the stored bundle; deploy to ship the new names.").option("--org <organizationId>", "Organization ID (defaults to .mcpcloud/state.json)").option("--server <server>", "Server (id, name, or slug; defaults to .mcpcloud/state.json)").option("--dry-run", "Report what would be renamed; nothing is written").option("--from-operation-ids", "Rename every OpenAPI tool whose name differs from its operationId only by case (getrecording → getRecording)").option("--map <file>", 'Rename several tools from a JSON file (@renames.json): {"old": "new", …} or [{"from", "to"}, …]').option("--drop-handler-overrides", "Allow renaming tools that carry an edited handler override by discarding the override (it is regenerated from the spec)").addHelpText("after", HELP_AFTER3.join(`
11827
+ `)).action(runAction(async (oldName, newName, opts) => {
11828
+ await runToolsRename(oldName, newName, opts);
11829
+ }));
11830
+ }
11831
+ async function runToolsRename(oldName, newName, opts) {
11832
+ const cwd = process.cwd();
11833
+ const selection = resolveRenameSelection({
11834
+ oldName,
11835
+ newName,
11836
+ fromOperationIds: opts.fromOperationIds,
11837
+ map: opts.map,
11838
+ cwd
11839
+ });
11840
+ const ctx = await resolveToolsContext({
11841
+ orgOverride: opts.org,
11842
+ serverOverride: opts.server
11843
+ });
11844
+ if (!ctx)
11845
+ return;
11846
+ if (!isJsonMode()) {
11847
+ printStep(`${opts.dryRun ? "Previewing renames" : "Renaming tools"} on ${c.bold(ctx.serverId)}…`);
11848
+ }
11849
+ let res;
11850
+ try {
11851
+ res = await api.post("/api/v1/server/tools/rename", {
11852
+ organizationId: ctx.organizationId,
11853
+ serverId: ctx.serverId,
11854
+ ...selectionBody(selection),
11855
+ ...opts.dryRun ? { dryRun: true } : {},
11856
+ ...opts.dropHandlerOverrides ? { dropHandlerOverrides: true } : {}
11857
+ });
11858
+ } catch (err) {
11859
+ if (!isJsonMode())
11860
+ reportRenameRefusal(err, ctx.serverId);
11861
+ throw err;
11862
+ }
11863
+ if (isJsonMode()) {
11864
+ printJson(res);
11865
+ return;
11866
+ }
11867
+ const localDir = serverToolsDir(devRoot(cwd), ctx.serverId);
11868
+ printRenameReport(res, {
11869
+ serverId: ctx.serverId,
11870
+ fromOperationIds: "fromOperationIds" in selection,
11871
+ localToolsDir: existsSync16(localDir) ? relative6(cwd, localDir) || "." : null
11872
+ });
11873
+ }
11874
+ function selectionBody(selection) {
11875
+ return "fromOperationIds" in selection ? { fromOperationIds: true } : { renames: selection.renames };
11876
+ }
11877
+ var PROBLEM_HINTS = {
11878
+ tool_not_found: (serverId) => `\`mcp tools list --server ${serverId}\` shows the current names.`,
11879
+ handler_override: () => "Re-run with --drop-handler-overrides to discard the edited handler (it is regenerated from the spec), or pull it first with `mcp tools pull-handler`.",
11880
+ name_conflict: () => "Pick a name no other tool on the server uses.",
11881
+ codegen_name_taken: () => "Codegen gives that name to a different operation in the spec, so this tool would inherit that operation's schema — choose another name."
11882
+ };
11883
+ function reportRenameRefusal(err, serverId) {
11884
+ if (!(err instanceof McpCloudApiError))
11885
+ return;
11886
+ if (err.error.code !== "tool_rename_invalid")
11887
+ return;
11888
+ const problems = err.error.details?.["problems"];
11889
+ if (!Array.isArray(problems) || problems.length === 0)
11890
+ return;
11891
+ printError("Rename refused — nothing was changed:");
11892
+ const hints = new Set;
11893
+ for (const raw of problems) {
11894
+ const pair = raw.to ? `${raw.from} → ${raw.to}` : raw.from;
11895
+ printInfo(` ${pair}: ${raw.message} (${raw.code})`);
11896
+ const hint = PROBLEM_HINTS[raw.code];
11897
+ if (hint)
11898
+ hints.add(hint(serverId));
11899
+ }
11900
+ for (const hint of hints)
11901
+ printInfo(c.dim(` → ${hint}`));
11902
+ throw new CliExitError(1);
11903
+ }
11904
+
11379
11905
  // src/commands/tools-reset-handler.ts
11380
- import { relative as relative5 } from "node:path";
11906
+ import { relative as relative7 } from "node:path";
11381
11907
  function registerToolsResetHandlerCommand(tools) {
11382
11908
  tools.command("reset-handler <name>").description("Drop the cloud handler override for a tool and re-materialize the codegen default. Prompts to confirm unless --yes is passed.").option("--org <organizationId>", "Organization ID (defaults to .mcpcloud/state.json)").option("--server <server>", "Server (id, name, or slug; defaults to .mcpcloud/state.json)").option("--yes", "Skip the confirmation prompt", false).option("--force", "Overwrite local file even if it has unsaved changes (default: refuse).", false).addHelpText("after", [
11383
11909
  "",
@@ -11488,7 +12014,7 @@ async function runResetHandler(args) {
11488
12014
  });
11489
12015
  return;
11490
12016
  }
11491
- const fileRel = relative5(cwd, written) || written;
12017
+ const fileRel = relative7(cwd, written) || written;
11492
12018
  if (result.snapshot.alreadyDefault) {
11493
12019
  printSuccess(`Handler for ${c.bold(result.snapshot.toolName)} was already at the codegen default (no content change). Local file refreshed at ${c.bold(fileRel)}.`);
11494
12020
  } else {
@@ -11498,7 +12024,7 @@ async function runResetHandler(args) {
11498
12024
  }
11499
12025
 
11500
12026
  // src/commands/tools-show.ts
11501
- import { existsSync as existsSync14 } from "node:fs";
12027
+ import { existsSync as existsSync17 } from "node:fs";
11502
12028
  function registerToolsShowCommand(tools) {
11503
12029
  tools.command("show <name>").description("Show a tool's full applied metadata (and any pending AI suggestion).").option("--org <organizationId>", "Organization ID (defaults to .mcpcloud/state.json)").option("--server <server>", "Server (id, name, or slug; defaults to .mcpcloud/state.json)").action(runAction(async (name, opts) => {
11504
12030
  await runToolsShow({
@@ -11526,7 +12052,7 @@ async function runToolsShow(args) {
11526
12052
  throw new CliExitError(1);
11527
12053
  }
11528
12054
  const devRootDir = devRoot(process.cwd());
11529
- const inDevTree = existsSync14(devRootDir);
12055
+ const inDevTree = existsSync17(devRootDir);
11530
12056
  const [detailed, handlerResult] = await Promise.all([
11531
12057
  fetchEnrichedToolDetail({
11532
12058
  organizationId: ctx.organizationId,
@@ -11669,6 +12195,8 @@ function registerToolCommands(program) {
11669
12195
  registerToolsShowCommand(tools);
11670
12196
  registerToolsDiffCommand(tools);
11671
12197
  registerToolsEditCommand(tools);
12198
+ registerToolsRenameCommand(tools);
12199
+ registerToolsDeleteCommand(tools);
11672
12200
  registerToolsEnrichCommand(tools);
11673
12201
  registerToolsPullCommand(tools);
11674
12202
  registerToolsPullHandlerCommand(tools);
@@ -11826,13 +12354,13 @@ function isAgentKey(value) {
11826
12354
  }
11827
12355
 
11828
12356
  // src/commands/skills-lifecycle.ts
11829
- import { readFileSync as readFileSync16 } from "node:fs";
12357
+ import { readFileSync as readFileSync17 } from "node:fs";
11830
12358
  function readValueOption2(raw) {
11831
12359
  if (raw === "@-") {
11832
- return readFileSync16(0, "utf8");
12360
+ return readFileSync17(0, "utf8");
11833
12361
  }
11834
12362
  if (raw.startsWith("@")) {
11835
- return readFileSync16(raw.slice(1), "utf8");
12363
+ return readFileSync17(raw.slice(1), "utf8");
11836
12364
  }
11837
12365
  return raw;
11838
12366
  }
@@ -12182,7 +12710,7 @@ function registerSkillMutationCommands(skills) {
12182
12710
  }
12183
12711
 
12184
12712
  // src/commands/skills-test-authoring.ts
12185
- import { readFileSync as readFileSync17 } from "node:fs";
12713
+ import { readFileSync as readFileSync18 } from "node:fs";
12186
12714
  var FIXTURE_MODES = [
12187
12715
  "baseline",
12188
12716
  "validationFailure",
@@ -12191,9 +12719,9 @@ var FIXTURE_MODES = [
12191
12719
  ];
12192
12720
  function readValueOption3(raw) {
12193
12721
  if (raw === "@-")
12194
- return readFileSync17(0, "utf8");
12722
+ return readFileSync18(0, "utf8");
12195
12723
  if (raw.startsWith("@"))
12196
- return readFileSync17(raw.slice(1), "utf8");
12724
+ return readFileSync18(raw.slice(1), "utf8");
12197
12725
  return raw;
12198
12726
  }
12199
12727
  function parseAssertionsOption(raw) {
@@ -12760,14 +13288,14 @@ function registerSkillInstallCommand(skills) {
12760
13288
 
12761
13289
  // src/commands/skills-pull.ts
12762
13290
  import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "node:fs";
12763
- import { dirname as dirname6, isAbsolute as isAbsolute3, join as join11, resolve as resolve7 } from "node:path";
13291
+ import { dirname as dirname6, isAbsolute as isAbsolute4, join as join12, resolve as resolve8 } from "node:path";
12764
13292
  function formatBytes2(bytes) {
12765
13293
  if (bytes < 1024)
12766
13294
  return `${bytes} B`;
12767
13295
  return `${(bytes / 1024).toFixed(1)} KB`;
12768
13296
  }
12769
13297
  function assertSafeRelativePath2(path) {
12770
- if (isAbsolute3(path) || path.split(/[\\/]/).includes("..")) {
13298
+ if (isAbsolute4(path) || path.split(/[\\/]/).includes("..")) {
12771
13299
  throw new Error(`Refusing to write unsafe package path: ${path}`);
12772
13300
  }
12773
13301
  }
@@ -12788,10 +13316,10 @@ function registerSkillPullCommand(skills) {
12788
13316
  registryArtifactId,
12789
13317
  ...opts.skillVersion ? { version: opts.skillVersion } : {}
12790
13318
  });
12791
- const dir = resolve7(opts.output ?? `./${result.skill.slug}`);
13319
+ const dir = resolve8(opts.output ?? `./${result.skill.slug}`);
12792
13320
  for (const file of result.files) {
12793
13321
  assertSafeRelativePath2(file.path);
12794
- const full = join11(dir, file.path);
13322
+ const full = join12(dir, file.path);
12795
13323
  mkdirSync8(dirname6(full), { recursive: true });
12796
13324
  writeFileSync7(full, file.content);
12797
13325
  }
@@ -12809,12 +13337,12 @@ function registerSkillPullCommand(skills) {
12809
13337
  const missing = result.dependencies.filter((entry) => !entry.installed);
12810
13338
  if (result.dependencies.length === 0) {
12811
13339
  printKeyValue({
12812
- Next: `Copy ${join11(dir, "SKILL.md")} into the skills directory your agent reads.`
13340
+ Next: `Copy ${join12(dir, "SKILL.md")} into the skills directory your agent reads.`
12813
13341
  });
12814
13342
  return;
12815
13343
  }
12816
13344
  printKeyValue({
12817
- Next: `Copy ${join11(dir, "SKILL.md")} into the skills directory your agent reads.`,
13345
+ Next: `Copy ${join12(dir, "SKILL.md")} into the skills directory your agent reads.`,
12818
13346
  Servers: result.dependencies.map((entry) => `${entry.name}${entry.installed ? "" : " (not installed)"}`).join(", ")
12819
13347
  });
12820
13348
  if (missing.length > 0) {
@@ -13246,18 +13774,18 @@ function registerSkillCommands(program) {
13246
13774
  }
13247
13775
 
13248
13776
  // src/lib/dev/prepare.ts
13249
- import { existsSync as existsSync21, readFileSync as readFileSync21, writeFileSync as writeFileSync12 } from "node:fs";
13250
- import { join as join18, relative as relative6, resolve as resolve8 } from "node:path";
13777
+ import { existsSync as existsSync24, readFileSync as readFileSync22, writeFileSync as writeFileSync12 } from "node:fs";
13778
+ import { join as join19, relative as relative8, resolve as resolve9 } from "node:path";
13251
13779
 
13252
13780
  // src/lib/dev/bundle-sync.ts
13253
13781
  import {
13254
- existsSync as existsSync15,
13782
+ existsSync as existsSync18,
13255
13783
  mkdirSync as mkdirSync9,
13256
- readFileSync as readFileSync18,
13784
+ readFileSync as readFileSync19,
13257
13785
  rmSync as rmSync2,
13258
13786
  writeFileSync as writeFileSync8
13259
13787
  } from "node:fs";
13260
- import { dirname as dirname7, join as join12 } from "node:path";
13788
+ import { dirname as dirname7, join as join13 } from "node:path";
13261
13789
  async function resolveServerProject(args) {
13262
13790
  const data = await api.get("/api/v1/server", {
13263
13791
  organizationId: args.organizationId,
@@ -13318,7 +13846,7 @@ async function fetchBundle(args) {
13318
13846
  return parsed;
13319
13847
  }
13320
13848
  function materializeBundle(destDir, bundle, options = {}) {
13321
- if (!existsSync15(destDir)) {
13849
+ if (!existsSync18(destDir)) {
13322
13850
  mkdirSync9(destDir, { recursive: true });
13323
13851
  }
13324
13852
  const preserve = options.preservePaths;
@@ -13334,7 +13862,7 @@ function materializeBundle(destDir, bundle, options = {}) {
13334
13862
  preservedCount += 1;
13335
13863
  continue;
13336
13864
  }
13337
- const target = join12(destDir, safePath);
13865
+ const target = join13(destDir, safePath);
13338
13866
  mkdirSync9(dirname7(target), { recursive: true });
13339
13867
  writeFileSync8(target, file.content, "utf-8");
13340
13868
  writtenCount += 1;
@@ -13364,7 +13892,7 @@ function pruneStaleFiles(rootDir, currentDir, kept) {
13364
13892
  const { relative } = __require("node:path");
13365
13893
  let removed = 0;
13366
13894
  for (const entry of readdirSync(currentDir)) {
13367
- const abs = join12(currentDir, entry);
13895
+ const abs = join13(currentDir, entry);
13368
13896
  const stat = statSync(abs);
13369
13897
  if (stat.isDirectory()) {
13370
13898
  removed += pruneStaleFiles(rootDir, abs, kept);
@@ -13388,12 +13916,12 @@ function pruneStaleFiles(rootDir, currentDir, kept) {
13388
13916
  // src/lib/dev/git-clone.ts
13389
13917
  import { spawnSync } from "node:child_process";
13390
13918
  import {
13391
- existsSync as existsSync16,
13919
+ existsSync as existsSync19,
13392
13920
  mkdirSync as mkdirSync10,
13393
- readFileSync as readFileSync19,
13921
+ readFileSync as readFileSync20,
13394
13922
  writeFileSync as writeFileSync9
13395
13923
  } from "node:fs";
13396
- import { dirname as dirname8, join as join13 } from "node:path";
13924
+ import { dirname as dirname8, join as join14 } from "node:path";
13397
13925
 
13398
13926
  class GitNotAvailableError extends Error {
13399
13927
  constructor() {
@@ -13484,12 +14012,12 @@ function isGitAvailable() {
13484
14012
  }
13485
14013
  }
13486
14014
  function applyLocalExcludes(repoDir) {
13487
- const excludePath = join13(repoDir, ".git", "info", "exclude");
13488
- const existing = existsSync16(excludePath) ? readFileSync19(excludePath, "utf-8") : "";
14015
+ const excludePath = join14(repoDir, ".git", "info", "exclude");
14016
+ const existing = existsSync19(excludePath) ? readFileSync20(excludePath, "utf-8") : "";
13489
14017
  const next = mergeLocalExcludes(existing);
13490
14018
  if (next === null)
13491
14019
  return;
13492
- if (!existsSync16(dirname8(excludePath))) {
14020
+ if (!existsSync19(dirname8(excludePath))) {
13493
14021
  mkdirSync10(dirname8(excludePath), { recursive: true });
13494
14022
  }
13495
14023
  writeFileSync9(excludePath, next, "utf-8");
@@ -13498,7 +14026,7 @@ function ensureClone(opts) {
13498
14026
  if (!isGitAvailable()) {
13499
14027
  throw new GitNotAvailableError;
13500
14028
  }
13501
- if (existsSync16(join13(opts.repoDir, ".git"))) {
14029
+ if (existsSync19(join14(opts.repoDir, ".git"))) {
13502
14030
  opts.onStep?.("Refreshing existing clone (git fetch)…");
13503
14031
  runGit(gitFetchArgs(opts.branch), opts.repoDir);
13504
14032
  applyLocalExcludes(opts.repoDir);
@@ -13514,12 +14042,12 @@ function ensureClone(opts) {
13514
14042
  }
13515
14043
 
13516
14044
  // src/lib/dev/host-runtime.ts
13517
- import { existsSync as existsSync18, mkdirSync as mkdirSync12, writeFileSync as writeFileSync11 } from "node:fs";
13518
- import { dirname as dirname9, join as join15 } from "node:path";
14045
+ import { existsSync as existsSync21, mkdirSync as mkdirSync12, writeFileSync as writeFileSync11 } from "node:fs";
14046
+ import { dirname as dirname9, join as join16 } from "node:path";
13519
14047
 
13520
14048
  // src/lib/dev/inspector-module.ts
13521
- import { existsSync as existsSync17, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "node:fs";
13522
- import { join as join14 } from "node:path";
14049
+ import { existsSync as existsSync20, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "node:fs";
14050
+ import { join as join15 } from "node:path";
13523
14051
 
13524
14052
  // src/lib/dev/inspector-assets.ts
13525
14053
  var INSPECTOR_HTML = `<!doctype html>
@@ -28667,9 +29195,9 @@ function buildInspectorModule() {
28667
29195
  `);
28668
29196
  }
28669
29197
  function writeInspectorModule(serverDir) {
28670
- if (!existsSync17(serverDir))
29198
+ if (!existsSync20(serverDir))
28671
29199
  mkdirSync11(serverDir, { recursive: true });
28672
- const target = join14(serverDir, INSPECTOR_FILE_NAME);
29200
+ const target = join15(serverDir, INSPECTOR_FILE_NAME);
28673
29201
  writeFileSync10(target, buildInspectorModule(), "utf-8");
28674
29202
  return target;
28675
29203
  }
@@ -28855,9 +29383,9 @@ function buildHostScript(options) {
28855
29383
  `);
28856
29384
  }
28857
29385
  function writeHostScript(serverDir, options) {
28858
- if (!existsSync18(serverDir))
29386
+ if (!existsSync21(serverDir))
28859
29387
  mkdirSync12(serverDir, { recursive: true });
28860
- const target = join15(serverDir, HOST_FILE_NAME);
29388
+ const target = join16(serverDir, HOST_FILE_NAME);
28861
29389
  mkdirSync12(dirname9(target), { recursive: true });
28862
29390
  writeFileSync11(target, buildHostScript(options), "utf-8");
28863
29391
  if (options.inspector) {
@@ -28869,38 +29397,38 @@ function writeHostScript(serverDir, options) {
28869
29397
  // src/lib/dev/inspector-store.ts
28870
29398
  import {
28871
29399
  appendFileSync as appendFileSync2,
28872
- existsSync as existsSync19,
29400
+ existsSync as existsSync22,
28873
29401
  mkdirSync as mkdirSync13,
28874
- readFileSync as readFileSync20,
29402
+ readFileSync as readFileSync21,
28875
29403
  readdirSync as readdirSync2,
28876
29404
  renameSync,
28877
29405
  statSync as statSync2
28878
29406
  } from "node:fs";
28879
- import { join as join16 } from "node:path";
29407
+ import { join as join17 } from "node:path";
28880
29408
  var INSPECTOR_DIRNAME = "inspector";
28881
29409
  var ACTIVE_FILE = "calls.ndjson";
28882
29410
  var ROTATED_PREFIX = "calls.";
28883
29411
  var ROTATED_SUFFIX = ".ndjson";
28884
29412
  var ROTATION_LIMIT_BYTES = 50 * 1024 * 1024;
28885
29413
  function inspectorDir(cwd) {
28886
- return join16(devRoot(cwd), INSPECTOR_DIRNAME);
29414
+ return join17(devRoot(cwd), INSPECTOR_DIRNAME);
28887
29415
  }
28888
29416
  function ensureInspectorDir(dir) {
28889
- if (!existsSync19(dir))
29417
+ if (!existsSync22(dir))
28890
29418
  mkdirSync13(dir, { recursive: true });
28891
29419
  }
28892
29420
  function activeFilePath(dir) {
28893
- return join16(dir, ACTIVE_FILE);
29421
+ return join17(dir, ACTIVE_FILE);
28894
29422
  }
28895
29423
  function isRotatedName(name) {
28896
29424
  return name !== ACTIVE_FILE && name.startsWith(ROTATED_PREFIX) && name.endsWith(ROTATED_SUFFIX);
28897
29425
  }
28898
29426
  function listRotatedFiles(dir) {
28899
- if (!existsSync19(dir))
29427
+ if (!existsSync22(dir))
28900
29428
  return [];
28901
29429
  const rotated = readdirSync2(dir).filter(isRotatedName);
28902
29430
  rotated.sort();
28903
- return rotated.map((n) => join16(dir, n));
29431
+ return rotated.map((n) => join17(dir, n));
28904
29432
  }
28905
29433
  function parseLines(text) {
28906
29434
  if (!text)
@@ -28917,13 +29445,13 @@ function parseLines(text) {
28917
29445
  return out;
28918
29446
  }
28919
29447
  function readAll(dir) {
28920
- if (!existsSync19(dir))
29448
+ if (!existsSync22(dir))
28921
29449
  return [];
28922
29450
  const out = [];
28923
29451
  for (const path of [...listRotatedFiles(dir), activeFilePath(dir)]) {
28924
- if (!existsSync19(path))
29452
+ if (!existsSync22(path))
28925
29453
  continue;
28926
- out.push(...parseLines(readFileSync20(path, "utf-8")));
29454
+ out.push(...parseLines(readFileSync21(path, "utf-8")));
28927
29455
  }
28928
29456
  return out;
28929
29457
  }
@@ -28973,8 +29501,8 @@ function parseDuration(input) {
28973
29501
 
28974
29502
  // src/lib/dev/local-runtime.ts
28975
29503
  import { spawn as spawn4 } from "node:child_process";
28976
- import { existsSync as existsSync20 } from "node:fs";
28977
- import { join as join17 } from "node:path";
29504
+ import { existsSync as existsSync23 } from "node:fs";
29505
+ import { join as join18 } from "node:path";
28978
29506
  function detectRuntime(preferred = "auto") {
28979
29507
  if (preferred === "bun" || preferred === "node")
28980
29508
  return preferred;
@@ -28985,7 +29513,7 @@ function detectRuntime(preferred = "auto") {
28985
29513
  return "node";
28986
29514
  }
28987
29515
  function startRuntime(options) {
28988
- if (!existsSync20(options.hostScriptPath)) {
29516
+ if (!existsSync23(options.hostScriptPath)) {
28989
29517
  throw new Error(`Host script not found: ${options.hostScriptPath}`);
28990
29518
  }
28991
29519
  const runtime = detectRuntime(options.runtime);
@@ -29026,11 +29554,11 @@ function startRuntime(options) {
29026
29554
  };
29027
29555
  }
29028
29556
  async function installDependencies(options) {
29029
- const pkgPath = join17(options.serverDir, "package.json");
29030
- if (!existsSync20(pkgPath))
29557
+ const pkgPath = join18(options.serverDir, "package.json");
29558
+ if (!existsSync23(pkgPath))
29031
29559
  return { ran: false, exitCode: null };
29032
- const nodeModules = join17(options.serverDir, "node_modules");
29033
- if (options.skipIfPresent !== false && existsSync20(nodeModules)) {
29560
+ const nodeModules = join18(options.serverDir, "node_modules");
29561
+ if (options.skipIfPresent !== false && existsSync23(nodeModules)) {
29034
29562
  return { ran: false, exitCode: 0 };
29035
29563
  }
29036
29564
  options.onStep?.("Installing dependencies (bun install)…");
@@ -29069,22 +29597,22 @@ function missingEnvBindingNames(envExample, envJson) {
29069
29597
  return names;
29070
29598
  }
29071
29599
  function printEnvBindingHint(dest, envPath, cwd) {
29072
- const examplePath = join18(dest, ".env.example");
29073
- if (!existsSync21(examplePath))
29600
+ const examplePath = join19(dest, ".env.example");
29601
+ if (!existsSync24(examplePath))
29074
29602
  return;
29075
29603
  let example = "";
29076
29604
  let envJson = "{}";
29077
29605
  try {
29078
- example = readFileSync21(examplePath, "utf-8");
29079
- if (existsSync21(envPath))
29080
- envJson = readFileSync21(envPath, "utf-8");
29606
+ example = readFileSync22(examplePath, "utf-8");
29607
+ if (existsSync24(envPath))
29608
+ envJson = readFileSync22(envPath, "utf-8");
29081
29609
  } catch {
29082
29610
  return;
29083
29611
  }
29084
29612
  const missing = missingEnvBindingNames(example, envJson);
29085
29613
  if (missing.length === 0)
29086
29614
  return;
29087
- printInfo(` → this server expects ${missing.map((n) => c.bold(n)).join(", ")} — add value${missing.length === 1 ? "" : "s"} to ${relative6(cwd, envPath)} (upstream calls 401 until then)`);
29615
+ printInfo(` → this server expects ${missing.map((n) => c.bold(n)).join(", ")} — add value${missing.length === 1 ? "" : "s"} to ${relative8(cwd, envPath)} (upstream calls 401 until then)`);
29088
29616
  }
29089
29617
  var ENTRY_CANDIDATES = ["src/worker.ts", "src/server.ts", "src/index.ts"];
29090
29618
  async function prepareDev(opts) {
@@ -29111,9 +29639,9 @@ async function prepareDev(opts) {
29111
29639
  projectId = projectId ?? gitLink.projectId;
29112
29640
  serverName = `${gitLink.owner}/${gitLink.name}`;
29113
29641
  const registered = readConfig().gitRepoDirs?.[opts.serverId];
29114
- const chosen = opts.repoDirOverride ? resolve8(cwd, opts.repoDirOverride) : registered;
29642
+ const chosen = opts.repoDirOverride ? resolve9(cwd, opts.repoDirOverride) : registered;
29115
29643
  repoDir = chosen ?? gitCloneDir(cwd, gitLink.owner, gitLink.name);
29116
- if (chosen && existsSync21(join18(repoDir, ".git"))) {
29644
+ if (chosen && existsSync24(join19(repoDir, ".git"))) {
29117
29645
  const origin = getOriginUrl(repoDir);
29118
29646
  if (!origin || !originMatchesRepo(origin, gitLink.owner, gitLink.name)) {
29119
29647
  printError(`${repoDir} is a checkout of ${origin ?? "an unknown remote"}, not ${gitLink.owner}/${gitLink.name}. Pass a checkout of the linked repo, or drop --repo to use the default clone.`);
@@ -29123,7 +29651,7 @@ async function prepareDev(opts) {
29123
29651
  if (registered && !opts.repoDirOverride) {
29124
29652
  printInfo(` → using registered checkout ${repoDir}`);
29125
29653
  }
29126
- dest = gitLink.pathPrefix ? join18(repoDir, gitLink.pathPrefix) : repoDir;
29654
+ dest = gitLink.pathPrefix ? join19(repoDir, gitLink.pathPrefix) : repoDir;
29127
29655
  printStep(`Git-linked server — cloning ${gitLink.owner}/${gitLink.name}@${gitLink.branch}…`);
29128
29656
  try {
29129
29657
  const result = ensureClone({
@@ -29132,7 +29660,7 @@ async function prepareDev(opts) {
29132
29660
  branch: gitLink.branch,
29133
29661
  onStep: (m) => printStep(m)
29134
29662
  });
29135
- printSuccess(result.cloned ? `Cloned to ${relative6(cwd, repoDir)}` : `Using existing clone at ${relative6(cwd, repoDir)}`);
29663
+ printSuccess(result.cloned ? `Cloned to ${relative8(cwd, repoDir)}` : `Using existing clone at ${relative8(cwd, repoDir)}`);
29136
29664
  const cfg = readConfig();
29137
29665
  if (cfg.gitRepoDirs?.[opts.serverId] !== repoDir) {
29138
29666
  cfg.gitRepoDirs = { ...cfg.gitRepoDirs, [opts.serverId]: repoDir };
@@ -29148,10 +29676,10 @@ async function prepareDev(opts) {
29148
29676
  }
29149
29677
  process.exit(1);
29150
29678
  }
29151
- if (!existsSync21(env)) {
29679
+ if (!existsSync24(env)) {
29152
29680
  writeFileSync12(env, `{}
29153
29681
  `, "utf-8");
29154
- printInfo(` → seeded ${relative6(cwd, env)} (empty; fill secrets here)`);
29682
+ printInfo(` → seeded ${relative8(cwd, env)} (empty; fill secrets here)`);
29155
29683
  }
29156
29684
  printEnvBindingHint(dest, env, cwd);
29157
29685
  printInfo(` ${"Open the clone in your editor; commit + push to sync (config under mcpcloud/ is read-only)."}`);
@@ -29174,10 +29702,10 @@ async function prepareDev(opts) {
29174
29702
  });
29175
29703
  const result = materializeBundle(dest, bundle, { prune: true });
29176
29704
  printInfo(` → wrote ${result.filesWritten} files (pruned ${result.filesRemoved})`);
29177
- if (!existsSync21(env)) {
29705
+ if (!existsSync24(env)) {
29178
29706
  writeFileSync12(env, `{}
29179
29707
  `, "utf-8");
29180
- printInfo(` → seeded ${relative6(cwd, env)} (empty; fill secrets here)`);
29708
+ printInfo(` → seeded ${relative8(cwd, env)} (empty; fill secrets here)`);
29181
29709
  }
29182
29710
  printEnvBindingHint(dest, env, cwd);
29183
29711
  printStep("Fetching tool metadata…");
@@ -29193,7 +29721,7 @@ async function prepareDev(opts) {
29193
29721
  });
29194
29722
  printInfo(` → wrote ${toolsResult.filesWritten} editable tool file${toolsResult.filesWritten === 1 ? "" : "s"}${toolsResult.filesRemoved > 0 ? ` (pruned ${toolsResult.filesRemoved})` : ""}`);
29195
29723
  } else {
29196
- if (!existsSync21(dest)) {
29724
+ if (!existsSync24(dest)) {
29197
29725
  printError("No cached bundle found. Run once without --offline first.");
29198
29726
  process.exit(1);
29199
29727
  }
@@ -29202,7 +29730,7 @@ async function prepareDev(opts) {
29202
29730
  process.exit(1);
29203
29731
  }
29204
29732
  }
29205
- const entryRel = ENTRY_CANDIDATES.find((c2) => existsSync21(join18(dest, c2)));
29733
+ const entryRel = ENTRY_CANDIDATES.find((c2) => existsSync24(join19(dest, c2)));
29206
29734
  if (!entryRel) {
29207
29735
  printError("Could not locate an entry file (expected src/worker.ts).");
29208
29736
  process.exit(1);
@@ -29250,8 +29778,8 @@ async function prepareDev(opts) {
29250
29778
 
29251
29779
  // src/lib/dev/tree-sync.ts
29252
29780
  import { createHash as createHash4 } from "node:crypto";
29253
- import { existsSync as existsSync22, mkdirSync as mkdirSync14, readdirSync as readdirSync3, readFileSync as readFileSync22, writeFileSync as writeFileSync13 } from "node:fs";
29254
- import { join as join19 } from "node:path";
29781
+ import { existsSync as existsSync25, mkdirSync as mkdirSync14, readdirSync as readdirSync3, readFileSync as readFileSync23, writeFileSync as writeFileSync13 } from "node:fs";
29782
+ import { join as join20 } from "node:path";
29255
29783
  var AUTHORING_GUIDE_FILE = "AGENTS.md";
29256
29784
  var STRUCTURAL_FRONTMATTER_KEYS = [
29257
29785
  "inputSchema",
@@ -29266,13 +29794,13 @@ function isEmptyPlan(plan) {
29266
29794
  }
29267
29795
  function listToolFiles(devRootDir, serverId) {
29268
29796
  const dir = serverToolsDir(devRootDir, serverId);
29269
- if (!existsSync22(dir))
29797
+ if (!existsSync25(dir))
29270
29798
  return [];
29271
29799
  return readdirSync3(dir).filter((entry) => entry.endsWith(".md")).filter((entry) => entry !== AUTHORING_GUIDE_FILE).map((entry) => entry.slice(0, -3)).sort();
29272
29800
  }
29273
29801
  function readToolFile(devRootDir, serverId, toolName) {
29274
- const file = join19(serverToolsDir(devRootDir, serverId), `${toolName}.md`);
29275
- const raw = readFileSync22(file, "utf-8");
29802
+ const file = join20(serverToolsDir(devRootDir, serverId), `${toolName}.md`);
29803
+ const raw = readFileSync23(file, "utf-8");
29276
29804
  try {
29277
29805
  return { raw, view: parseToolMarkdown(raw) };
29278
29806
  } catch (err) {
@@ -29298,7 +29826,7 @@ async function writeAuthoringGuide(args) {
29298
29826
  return null;
29299
29827
  const dir = serverToolsDir(args.devRootDir, args.serverId);
29300
29828
  mkdirSync14(dir, { recursive: true });
29301
- const guidePath = join19(dir, AUTHORING_GUIDE_FILE);
29829
+ const guidePath = join20(dir, AUTHORING_GUIDE_FILE);
29302
29830
  writeFileSync13(guidePath, markdown, "utf8");
29303
29831
  return guidePath;
29304
29832
  } catch {
@@ -29350,6 +29878,14 @@ function planPush(args) {
29350
29878
  changes.push({ kind: "new-tool", toolName });
29351
29879
  continue;
29352
29880
  }
29881
+ if (parsed.view.name !== toolName) {
29882
+ blockers.push({
29883
+ reason: `\`name:\` was changed to ${parsed.view.name}; editing the file does not rename a tool`,
29884
+ remedy: `mcp tools rename ${toolName} ${parsed.view.name}`,
29885
+ toolName
29886
+ });
29887
+ continue;
29888
+ }
29353
29889
  if (hashLocalView(parsed.view) !== tracked.descriptionHash) {
29354
29890
  changes.push({
29355
29891
  changedFields: changedFieldNames(parsed.view, tracked.descriptionHash),
@@ -29381,18 +29917,18 @@ function changedFieldNames(view, storedHash) {
29381
29917
  }
29382
29918
  var HANDLER_MANIFEST_FILE = ".handlers-state.json";
29383
29919
  function handlerManifestPath(devRootDir, serverId) {
29384
- return join19(serverToolsDir(devRootDir, serverId), HANDLER_MANIFEST_FILE);
29920
+ return join20(serverToolsDir(devRootDir, serverId), HANDLER_MANIFEST_FILE);
29385
29921
  }
29386
29922
  function hashSource2(source) {
29387
29923
  return createHash4("sha256").update(source).digest("hex");
29388
29924
  }
29389
29925
  function listHandlerFiles(devRootDir) {
29390
29926
  const dir = serverBundleHandlersDir(devRootDir);
29391
- if (!existsSync22(dir))
29927
+ if (!existsSync25(dir))
29392
29928
  return [];
29393
29929
  return readdirSync3(dir).filter((entry) => entry.endsWith(".ts") && !entry.endsWith(".definition.ts")).sort().map((entry) => ({
29394
29930
  slug: entry.slice(0, -3),
29395
- source: readFileSync22(join19(dir, entry), "utf-8")
29931
+ source: readFileSync23(join20(dir, entry), "utf-8")
29396
29932
  }));
29397
29933
  }
29398
29934
  function writeHandlerManifest(args) {
@@ -29401,7 +29937,7 @@ function writeHandlerManifest(args) {
29401
29937
  handlers[file.slug] = hashSource2(file.source);
29402
29938
  }
29403
29939
  const dir = serverToolsDir(args.devRootDir, args.serverId);
29404
- if (!existsSync22(dir))
29940
+ if (!existsSync25(dir))
29405
29941
  mkdirSync14(dir, { recursive: true });
29406
29942
  const manifest = {
29407
29943
  handlers,
@@ -29412,23 +29948,23 @@ function writeHandlerManifest(args) {
29412
29948
  return Object.keys(handlers).length;
29413
29949
  }
29414
29950
  function recordHandlerPushed(args) {
29415
- const file = join19(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
29416
- if (!existsSync22(file))
29951
+ const file = join20(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
29952
+ if (!existsSync25(file))
29417
29953
  return;
29418
29954
  const manifest = readHandlerManifest(args.devRootDir, args.serverId) ?? {
29419
29955
  handlers: {},
29420
29956
  serverId: args.serverId,
29421
29957
  version: 1
29422
29958
  };
29423
- manifest.handlers[args.slug] = hashSource2(readFileSync22(file, "utf-8"));
29959
+ manifest.handlers[args.slug] = hashSource2(readFileSync23(file, "utf-8"));
29424
29960
  writeFileSync13(handlerManifestPath(args.devRootDir, args.serverId), JSON.stringify(manifest, null, 2), "utf-8");
29425
29961
  }
29426
29962
  function readHandlerManifest(devRootDir, serverId) {
29427
29963
  const file = handlerManifestPath(devRootDir, serverId);
29428
- if (!existsSync22(file))
29964
+ if (!existsSync25(file))
29429
29965
  return null;
29430
29966
  try {
29431
- const parsed = JSON.parse(readFileSync22(file, "utf-8"));
29967
+ const parsed = JSON.parse(readFileSync23(file, "utf-8"));
29432
29968
  return parsed.version === 1 && parsed.handlers ? parsed : null;
29433
29969
  } catch {
29434
29970
  return null;
@@ -29513,12 +30049,22 @@ function describeToolPush(toolName, result) {
29513
30049
  ok: false,
29514
30050
  target: toolName
29515
30051
  };
29516
- default:
30052
+ case "rename-requested":
30053
+ return {
30054
+ detail: `editing \`name:\` does not rename — use \`mcp tools rename ${result.from} ${result.to}\``,
30055
+ ok: false,
30056
+ target: toolName
30057
+ };
30058
+ case "api-error":
29517
30059
  return {
29518
30060
  detail: `${result.message} (HTTP ${result.status})`,
29519
30061
  ok: false,
29520
30062
  target: toolName
29521
30063
  };
30064
+ default: {
30065
+ const exhaustive = result;
30066
+ return exhaustive;
30067
+ }
29522
30068
  }
29523
30069
  }
29524
30070
  function describeHandlerPush(toolName, result) {
@@ -30188,9 +30734,9 @@ function registerDomainCommands(program) {
30188
30734
 
30189
30735
  // src/commands/config.ts
30190
30736
  import { homedir as homedir2 } from "node:os";
30191
- import { join as join20 } from "node:path";
30737
+ import { join as join21 } from "node:path";
30192
30738
  function configFilePath() {
30193
- return join20(homedir2(), ".mcpcloud", "config.json");
30739
+ return join21(homedir2(), ".mcpcloud", "config.json");
30194
30740
  }
30195
30741
  function previewKey2(key) {
30196
30742
  if (!key)
@@ -30435,30 +30981,30 @@ function registerConfigCommands(program) {
30435
30981
  }
30436
30982
 
30437
30983
  // src/commands/dev.ts
30438
- import { existsSync as existsSync35 } from "node:fs";
30439
- import { isAbsolute as isAbsolute4, relative as relative7, resolve as resolve10 } from "node:path";
30984
+ import { existsSync as existsSync38 } from "node:fs";
30985
+ import { isAbsolute as isAbsolute5, relative as relative9, resolve as resolve11 } from "node:path";
30440
30986
 
30441
30987
  // src/lib/dev/sessions.ts
30442
30988
  import {
30443
- existsSync as existsSync23,
30989
+ existsSync as existsSync26,
30444
30990
  mkdirSync as mkdirSync15,
30445
- readFileSync as readFileSync23,
30991
+ readFileSync as readFileSync24,
30446
30992
  readdirSync as readdirSync4,
30447
30993
  unlinkSync,
30448
30994
  writeFileSync as writeFileSync14
30449
30995
  } from "node:fs";
30450
30996
  import { homedir as homedir3 } from "node:os";
30451
- import { join as join21 } from "node:path";
30997
+ import { join as join22 } from "node:path";
30452
30998
  var SESSIONS_DIR_NAME = "dev-sessions";
30453
30999
  function sessionsDir() {
30454
- return join21(homedir3(), ".mcpcloud", SESSIONS_DIR_NAME);
31000
+ return join22(homedir3(), ".mcpcloud", SESSIONS_DIR_NAME);
30455
31001
  }
30456
31002
  function sessionFile(pid) {
30457
- return join21(sessionsDir(), `${pid}.json`);
31003
+ return join22(sessionsDir(), `${pid}.json`);
30458
31004
  }
30459
31005
  function ensureDir() {
30460
31006
  const dir = sessionsDir();
30461
- if (!existsSync23(dir))
31007
+ if (!existsSync26(dir))
30462
31008
  mkdirSync15(dir, { recursive: true, mode: 448 });
30463
31009
  }
30464
31010
  function recordSession(record) {
@@ -30470,7 +31016,7 @@ function recordSession(record) {
30470
31016
  }
30471
31017
  function removeSession(pid) {
30472
31018
  const path = sessionFile(pid);
30473
- if (existsSync23(path)) {
31019
+ if (existsSync26(path)) {
30474
31020
  try {
30475
31021
  unlinkSync(path);
30476
31022
  } catch {}
@@ -30478,15 +31024,15 @@ function removeSession(pid) {
30478
31024
  }
30479
31025
  function listSessions() {
30480
31026
  const dir = sessionsDir();
30481
- if (!existsSync23(dir))
31027
+ if (!existsSync26(dir))
30482
31028
  return [];
30483
31029
  const live = [];
30484
31030
  for (const entry of readdirSync4(dir)) {
30485
31031
  if (!entry.endsWith(".json"))
30486
31032
  continue;
30487
- const path = join21(dir, entry);
31033
+ const path = join22(dir, entry);
30488
31034
  try {
30489
- const parsed = JSON.parse(readFileSync23(path, "utf-8"));
31035
+ const parsed = JSON.parse(readFileSync24(path, "utf-8"));
30490
31036
  if (typeof parsed.pid !== "number") {
30491
31037
  unlinkSync(path);
30492
31038
  continue;
@@ -30823,13 +31369,13 @@ function formatSummary(record) {
30823
31369
 
30824
31370
  // src/commands/dev-tail.ts
30825
31371
  import {
30826
- existsSync as existsSync24,
31372
+ existsSync as existsSync27,
30827
31373
  openSync,
30828
31374
  readSync,
30829
31375
  closeSync,
30830
31376
  statSync as statSync3
30831
31377
  } from "node:fs";
30832
- import { join as join22 } from "node:path";
31378
+ import { join as join23 } from "node:path";
30833
31379
  var DEFAULT_INTERVAL_MS = 250;
30834
31380
  var MAX_READ_CHUNK = 64 * 1024;
30835
31381
  function registerDevTailCommand(dev) {
@@ -30838,14 +31384,14 @@ function registerDevTailCommand(dev) {
30838
31384
  async function runDevTail(opts) {
30839
31385
  const cwd = process.cwd();
30840
31386
  const dir = inspectorDir(cwd);
30841
- const activePath = join22(dir, ACTIVE_FILE);
31387
+ const activePath = join23(dir, ACTIVE_FILE);
30842
31388
  const intervalMs = parseInterval(opts.intervalMs);
30843
- if (!existsSync24(dir)) {
31389
+ if (!existsSync27(dir)) {
30844
31390
  printInfo(`No inspector history yet at ${dir}. Run \`mcp dev\` to start capturing.`);
30845
31391
  throw new CliExitError(0);
30846
31392
  }
30847
31393
  let cursor = 0;
30848
- if (existsSync24(activePath)) {
31394
+ if (existsSync27(activePath)) {
30849
31395
  try {
30850
31396
  cursor = opts.fromStart ? 0 : statSync3(activePath).size;
30851
31397
  } catch {
@@ -30861,7 +31407,7 @@ async function runDevTail(opts) {
30861
31407
  process.on("SIGTERM", stop);
30862
31408
  while (!stopped) {
30863
31409
  let size = 0;
30864
- if (existsSync24(activePath)) {
31410
+ if (existsSync27(activePath)) {
30865
31411
  try {
30866
31412
  size = statSync3(activePath).size;
30867
31413
  } catch {
@@ -31106,8 +31652,8 @@ function startGitPullPoll(args) {
31106
31652
 
31107
31653
  // src/lib/dev/editor.ts
31108
31654
  import { spawn as spawn5 } from "node:child_process";
31109
- import { existsSync as existsSync25, statSync as statSync4 } from "node:fs";
31110
- import { delimiter as delimiter2, join as join23 } from "node:path";
31655
+ import { existsSync as existsSync28, statSync as statSync4 } from "node:fs";
31656
+ import { delimiter as delimiter2, join as join24 } from "node:path";
31111
31657
  import { platform as platform3 } from "node:os";
31112
31658
  var TARGET_LABEL = {
31113
31659
  tools: "tool metadata",
@@ -31143,8 +31689,8 @@ function findOnPath2(command) {
31143
31689
  if (!dir)
31144
31690
  continue;
31145
31691
  for (const ext of exts) {
31146
- const candidate = join23(dir, command + ext);
31147
- if (existsSync25(candidate) && isExecutable(candidate))
31692
+ const candidate = join24(dir, command + ext);
31693
+ if (existsSync28(candidate) && isExecutable(candidate))
31148
31694
  return candidate;
31149
31695
  }
31150
31696
  }
@@ -31433,11 +31979,11 @@ function parseOpenChoice(raw) {
31433
31979
  }
31434
31980
 
31435
31981
  // src/lib/dev/file-watcher.ts
31436
- import { existsSync as existsSync26, statSync as statSync5, watch as fsWatch2 } from "node:fs";
31437
- import { join as join24 } from "node:path";
31982
+ import { existsSync as existsSync29, statSync as statSync5, watch as fsWatch2 } from "node:fs";
31983
+ import { join as join25 } from "node:path";
31438
31984
  var DEFAULT_IGNORE = ["node_modules", ".git", "_mcpsh_host.mjs"];
31439
31985
  function watchDir(options) {
31440
- if (!existsSync26(options.rootDir)) {
31986
+ if (!existsSync29(options.rootDir)) {
31441
31987
  throw new Error(`watchDir: directory does not exist: ${options.rootDir}`);
31442
31988
  }
31443
31989
  const ignore = new Set([...DEFAULT_IGNORE, ...options.ignore ?? []]);
@@ -31473,7 +32019,7 @@ function watchDir(options) {
31473
32019
  for (const entry of readdirSync(options.rootDir)) {
31474
32020
  if (ignore.has(entry))
31475
32021
  continue;
31476
- const sub = join24(options.rootDir, entry);
32022
+ const sub = join25(options.rootDir, entry);
31477
32023
  try {
31478
32024
  const stat = statSync5(sub);
31479
32025
  if (!stat.isDirectory())
@@ -31521,10 +32067,10 @@ function isToolHandlerPath(rel) {
31521
32067
  }
31522
32068
 
31523
32069
  // src/lib/dev/tools-watcher.ts
31524
- import { existsSync as existsSync27 } from "node:fs";
32070
+ import { existsSync as existsSync30 } from "node:fs";
31525
32071
  function startToolsWatcher(args) {
31526
32072
  const rootDir = serverToolsDir(devRoot(args.cwd), args.serverId);
31527
- if (!existsSync27(rootDir))
32073
+ if (!existsSync30(rootDir))
31528
32074
  return null;
31529
32075
  let inflight = false;
31530
32076
  let pendingNames = new Set;
@@ -31589,6 +32135,10 @@ async function handleOne(args) {
31589
32135
  printError(`Couldn't parse ${args.toolName}.md: ${result.message}`);
31590
32136
  printInfo(c.dim(` → fix the file and save again, or run \`mcp tools pull ${args.toolName}\` to reset.`));
31591
32137
  return;
32138
+ case "rename-requested":
32139
+ printWarn(`${args.toolName}.md: \`name:\` was changed to ${c.bold(result.to)}, but editing the file does not rename a tool — nothing was pushed.`);
32140
+ printInfo(c.dim(` → run \`mcp tools rename ${result.from} ${result.to} --server ${args.serverId}\`, then \`mcp tools pull\`; or set \`name:\` back to ${result.from} to push your other edits.`));
32141
+ return;
31592
32142
  case "patched": {
31593
32143
  printSuccess(`Saved ${c.bold(args.toolName)} ${c.dim(`(${result.changedFields.length} field${result.changedFields.length === 1 ? "" : "s"} patched)`)}`);
31594
32144
  const hotPatched = await maybeHotReload({
@@ -31639,10 +32189,10 @@ async function maybeHotReload(args) {
31639
32189
  }
31640
32190
 
31641
32191
  // src/lib/dev/handlers-watcher.ts
31642
- import { existsSync as existsSync28 } from "node:fs";
32192
+ import { existsSync as existsSync31 } from "node:fs";
31643
32193
  function startHandlersWatcher(args) {
31644
32194
  const rootDir = serverBundleHandlersDir(devRoot(args.cwd));
31645
- if (!existsSync28(rootDir))
32195
+ if (!existsSync31(rootDir))
31646
32196
  return null;
31647
32197
  let inflight = false;
31648
32198
  let pendingSlugs = new Set;
@@ -31849,24 +32399,24 @@ function createBurstGuard() {
31849
32399
  }
31850
32400
 
31851
32401
  // src/lib/dev/agent-connectors/claude-code.ts
31852
- import { existsSync as existsSync30, unlinkSync as unlinkSync2, writeFileSync as writeFileSync16 } from "node:fs";
32402
+ import { existsSync as existsSync33, unlinkSync as unlinkSync2, writeFileSync as writeFileSync16 } from "node:fs";
31853
32403
  import { homedir as homedir4 } from "node:os";
31854
- import { join as join26 } from "node:path";
32404
+ import { join as join27 } from "node:path";
31855
32405
 
31856
32406
  // src/lib/dev/agent-connectors/json-config-utils.ts
31857
- import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync24, writeFileSync as writeFileSync15 } from "node:fs";
31858
- import { dirname as dirname10, join as join25, basename } from "node:path";
32407
+ import { existsSync as existsSync32, mkdirSync as mkdirSync16, readFileSync as readFileSync25, writeFileSync as writeFileSync15 } from "node:fs";
32408
+ import { dirname as dirname10, join as join26, basename } from "node:path";
31859
32409
  function readJsonFile(path) {
31860
- if (!existsSync29(path))
32410
+ if (!existsSync32(path))
31861
32411
  return { ok: true, value: {} };
31862
32412
  try {
31863
- const raw = readFileSync24(path, "utf-8");
32413
+ const raw = readFileSync25(path, "utf-8");
31864
32414
  if (!raw.trim())
31865
32415
  return { ok: true, value: {} };
31866
32416
  return { ok: true, value: JSON.parse(raw) };
31867
32417
  } catch {
31868
32418
  try {
31869
- return { ok: false, raw: readFileSync24(path, "utf-8") };
32419
+ return { ok: false, raw: readFileSync25(path, "utf-8") };
31870
32420
  } catch {
31871
32421
  return { ok: false };
31872
32422
  }
@@ -31881,25 +32431,25 @@ function atomicWriteJson(path, value) {
31881
32431
  renameSync(tmp, path);
31882
32432
  }
31883
32433
  function backupConfig(args) {
31884
- if (!existsSync29(args.configPath)) {
32434
+ if (!existsSync32(args.configPath)) {
31885
32435
  return { backupPath: null, existed: false };
31886
32436
  }
31887
32437
  mkdirSync16(args.backupsDir, { recursive: true });
31888
32438
  const filename = `${args.agentId}__${basename(args.configPath)}.bak`;
31889
- const backupPath = join25(args.backupsDir, filename);
31890
- if (!existsSync29(backupPath)) {
31891
- const raw = readFileSync24(args.configPath, "utf-8");
32439
+ const backupPath = join26(args.backupsDir, filename);
32440
+ if (!existsSync32(backupPath)) {
32441
+ const raw = readFileSync25(args.configPath, "utf-8");
31892
32442
  writeFileSync15(backupPath, raw, "utf-8");
31893
32443
  }
31894
32444
  return { backupPath, existed: true };
31895
32445
  }
31896
32446
  function restoreFromBackup(args) {
31897
32447
  const filename = `${args.agentId}__${basename(args.configPath)}.bak`;
31898
- const backupPath = join25(args.backupsDir, filename);
31899
- if (!existsSync29(backupPath)) {
32448
+ const backupPath = join26(args.backupsDir, filename);
32449
+ if (!existsSync32(backupPath)) {
31900
32450
  return { restored: false };
31901
32451
  }
31902
- const raw = readFileSync24(backupPath, "utf-8");
32452
+ const raw = readFileSync25(backupPath, "utf-8");
31903
32453
  mkdirSync16(dirname10(args.configPath), { recursive: true });
31904
32454
  writeFileSync15(args.configPath, raw, "utf-8");
31905
32455
  return { restored: true };
@@ -31907,15 +32457,15 @@ function restoreFromBackup(args) {
31907
32457
 
31908
32458
  // src/lib/dev/agent-connectors/claude-code.ts
31909
32459
  function configPath() {
31910
- return join26(homedir4(), ".claude.json");
32460
+ return join27(homedir4(), ".claude.json");
31911
32461
  }
31912
32462
  function legacyConfigPath() {
31913
- return join26(homedir4(), ".claude", "mcp.json");
32463
+ return join27(homedir4(), ".claude", "mcp.json");
31914
32464
  }
31915
32465
  function resolveConfigPath() {
31916
- if (existsSync30(configPath()))
32466
+ if (existsSync33(configPath()))
31917
32467
  return configPath();
31918
- if (existsSync30(legacyConfigPath()))
32468
+ if (existsSync33(legacyConfigPath()))
31919
32469
  return legacyConfigPath();
31920
32470
  return configPath();
31921
32471
  }
@@ -31925,8 +32475,8 @@ var claudeCodeConnector = {
31925
32475
  hotkey: "c",
31926
32476
  describeLocation: () => resolveConfigPath(),
31927
32477
  async detect() {
31928
- const claudeDir = join26(homedir4(), ".claude");
31929
- if (existsSync30(configPath()) || existsSync30(legacyConfigPath()) || existsSync30(claudeDir)) {
32478
+ const claudeDir = join27(homedir4(), ".claude");
32479
+ if (existsSync33(configPath()) || existsSync33(legacyConfigPath()) || existsSync33(claudeDir)) {
31930
32480
  return { installed: true, note: "Found Claude Code config" };
31931
32481
  }
31932
32482
  return { installed: false };
@@ -31964,7 +32514,7 @@ var claudeCodeConnector = {
31964
32514
  backupsDir: args.backupsDir,
31965
32515
  agentId: this.id
31966
32516
  });
31967
- if (!restored.restored && existsSync30(path)) {
32517
+ if (!restored.restored && existsSync33(path)) {
31968
32518
  const after = readJsonFile(path);
31969
32519
  if (after.ok && after.value && typeof after.value === "object") {
31970
32520
  const root = after.value;
@@ -31986,16 +32536,16 @@ var claudeCodeConnector = {
31986
32536
 
31987
32537
  // src/lib/dev/agent-connectors/codex.ts
31988
32538
  import {
31989
- existsSync as existsSync31,
32539
+ existsSync as existsSync34,
31990
32540
  mkdirSync as mkdirSync17,
31991
- readFileSync as readFileSync25,
32541
+ readFileSync as readFileSync26,
31992
32542
  unlinkSync as unlinkSync3,
31993
32543
  writeFileSync as writeFileSync17
31994
32544
  } from "node:fs";
31995
32545
  import { homedir as homedir5 } from "node:os";
31996
- import { dirname as dirname11, join as join27 } from "node:path";
32546
+ import { dirname as dirname11, join as join28 } from "node:path";
31997
32547
  function configPath2() {
31998
- return join27(homedir5(), ".codex", "config.toml");
32548
+ return join28(homedir5(), ".codex", "config.toml");
31999
32549
  }
32000
32550
  var SECTION_PREFIX = "mcp_servers.";
32001
32551
  function buildSection(name, url) {
@@ -32033,7 +32583,7 @@ var codexConnector = {
32033
32583
  hotkey: "x",
32034
32584
  describeLocation: () => configPath2(),
32035
32585
  async detect() {
32036
- if (existsSync31(join27(homedir5(), ".codex")) || existsSync31(configPath2())) {
32586
+ if (existsSync34(join28(homedir5(), ".codex")) || existsSync34(configPath2())) {
32037
32587
  return { installed: true, note: "Found ~/.codex/" };
32038
32588
  }
32039
32589
  return { installed: false };
@@ -32046,8 +32596,8 @@ var codexConnector = {
32046
32596
  agentId: this.id
32047
32597
  });
32048
32598
  let existing = "";
32049
- if (existsSync31(path)) {
32050
- existing = readFileSync25(path, "utf-8");
32599
+ if (existsSync34(path)) {
32600
+ existing = readFileSync26(path, "utf-8");
32051
32601
  }
32052
32602
  const conflict = findSectionRange(existing, args.name) !== null;
32053
32603
  let next = existing;
@@ -32068,8 +32618,8 @@ var codexConnector = {
32068
32618
  },
32069
32619
  async remove(args) {
32070
32620
  const path = configPath2();
32071
- if (existsSync31(path)) {
32072
- const existing = readFileSync25(path, "utf-8");
32621
+ if (existsSync34(path)) {
32622
+ const existing = readFileSync26(path, "utf-8");
32073
32623
  const range = findSectionRange(existing, args.name);
32074
32624
  if (range) {
32075
32625
  const next = existing.slice(0, range.start) + existing.slice(range.end);
@@ -32081,8 +32631,8 @@ var codexConnector = {
32081
32631
  backupsDir: args.backupsDir,
32082
32632
  agentId: this.id
32083
32633
  });
32084
- if (!restored.restored && existsSync31(path)) {
32085
- const after = readFileSync25(path, "utf-8");
32634
+ if (!restored.restored && existsSync34(path)) {
32635
+ const after = readFileSync26(path, "utf-8");
32086
32636
  if (!after.trim()) {
32087
32637
  try {
32088
32638
  unlinkSync3(path);
@@ -32094,11 +32644,11 @@ var codexConnector = {
32094
32644
  };
32095
32645
 
32096
32646
  // src/lib/dev/agent-connectors/continue.ts
32097
- import { existsSync as existsSync32, unlinkSync as unlinkSync4, writeFileSync as writeFileSync18 } from "node:fs";
32647
+ import { existsSync as existsSync35, unlinkSync as unlinkSync4, writeFileSync as writeFileSync18 } from "node:fs";
32098
32648
  import { homedir as homedir6 } from "node:os";
32099
- import { join as join28 } from "node:path";
32649
+ import { join as join29 } from "node:path";
32100
32650
  function configPath3() {
32101
- return join28(homedir6(), ".continue", "config.json");
32651
+ return join29(homedir6(), ".continue", "config.json");
32102
32652
  }
32103
32653
  function isContinueServerEntry(value) {
32104
32654
  return Boolean(value && typeof value === "object" && typeof value.name === "string");
@@ -32109,8 +32659,8 @@ var continueConnector = {
32109
32659
  hotkey: "n",
32110
32660
  describeLocation: () => configPath3(),
32111
32661
  async detect() {
32112
- const dir = join28(homedir6(), ".continue");
32113
- if (existsSync32(dir) || existsSync32(configPath3())) {
32662
+ const dir = join29(homedir6(), ".continue");
32663
+ if (existsSync35(dir) || existsSync35(configPath3())) {
32114
32664
  return { installed: true, note: "Found ~/.continue/" };
32115
32665
  }
32116
32666
  return { installed: false };
@@ -32148,7 +32698,7 @@ var continueConnector = {
32148
32698
  backupsDir: args.backupsDir,
32149
32699
  agentId: this.id
32150
32700
  });
32151
- if (!restored.restored && existsSync32(path)) {
32701
+ if (!restored.restored && existsSync35(path)) {
32152
32702
  const after = readJsonFile(path);
32153
32703
  if (after.ok && after.value && typeof after.value === "object") {
32154
32704
  const root = after.value;
@@ -32169,11 +32719,11 @@ var continueConnector = {
32169
32719
  };
32170
32720
 
32171
32721
  // src/lib/dev/agent-connectors/cursor.ts
32172
- import { existsSync as existsSync33, unlinkSync as unlinkSync5, writeFileSync as writeFileSync19 } from "node:fs";
32722
+ import { existsSync as existsSync36, unlinkSync as unlinkSync5, writeFileSync as writeFileSync19 } from "node:fs";
32173
32723
  import { homedir as homedir7 } from "node:os";
32174
- import { join as join29 } from "node:path";
32724
+ import { join as join30 } from "node:path";
32175
32725
  function globalConfigPath() {
32176
- return join29(homedir7(), ".cursor", "mcp.json");
32726
+ return join30(homedir7(), ".cursor", "mcp.json");
32177
32727
  }
32178
32728
  var cursorConnector = {
32179
32729
  id: "cursor",
@@ -32181,9 +32731,9 @@ var cursorConnector = {
32181
32731
  hotkey: "u",
32182
32732
  describeLocation: () => globalConfigPath(),
32183
32733
  async detect() {
32184
- const cursorDir = join29(homedir7(), ".cursor");
32185
- const macAppSupport = join29(homedir7(), "Library", "Application Support", "Cursor");
32186
- if (existsSync33(cursorDir) || existsSync33(macAppSupport)) {
32734
+ const cursorDir = join30(homedir7(), ".cursor");
32735
+ const macAppSupport = join30(homedir7(), "Library", "Application Support", "Cursor");
32736
+ if (existsSync36(cursorDir) || existsSync36(macAppSupport)) {
32187
32737
  return { installed: true, note: "Found Cursor config dir" };
32188
32738
  }
32189
32739
  return { installed: false };
@@ -32221,7 +32771,7 @@ var cursorConnector = {
32221
32771
  backupsDir: args.backupsDir,
32222
32772
  agentId: this.id
32223
32773
  });
32224
- if (!restored.restored && existsSync33(path)) {
32774
+ if (!restored.restored && existsSync36(path)) {
32225
32775
  const after = readJsonFile(path);
32226
32776
  if (after.ok && after.value && typeof after.value === "object") {
32227
32777
  const root = after.value;
@@ -32242,30 +32792,30 @@ var cursorConnector = {
32242
32792
  };
32243
32793
 
32244
32794
  // src/lib/dev/agent-connectors/vscode-copilot.ts
32245
- import { existsSync as existsSync34, unlinkSync as unlinkSync6, writeFileSync as writeFileSync20 } from "node:fs";
32795
+ import { existsSync as existsSync37, unlinkSync as unlinkSync6, writeFileSync as writeFileSync20 } from "node:fs";
32246
32796
  import { homedir as homedir8, platform as platform4 } from "node:os";
32247
- import { join as join30, resolve as resolve9 } from "node:path";
32797
+ import { join as join31, resolve as resolve10 } from "node:path";
32248
32798
  function userLevelConfigPath() {
32249
32799
  const home = homedir8();
32250
32800
  const p = platform4();
32251
32801
  if (p === "darwin")
32252
- return join30(home, "Library", "Application Support", "Code", "User", "mcp.json");
32802
+ return join31(home, "Library", "Application Support", "Code", "User", "mcp.json");
32253
32803
  if (p === "win32") {
32254
- const appData = process.env["APPDATA"] ?? join30(home, "AppData", "Roaming");
32255
- return join30(appData, "Code", "User", "mcp.json");
32804
+ const appData = process.env["APPDATA"] ?? join31(home, "AppData", "Roaming");
32805
+ return join31(appData, "Code", "User", "mcp.json");
32256
32806
  }
32257
- const xdg = process.env["XDG_CONFIG_HOME"] ?? join30(home, ".config");
32258
- return join30(xdg, "Code", "User", "mcp.json");
32807
+ const xdg = process.env["XDG_CONFIG_HOME"] ?? join31(home, ".config");
32808
+ return join31(xdg, "Code", "User", "mcp.json");
32259
32809
  }
32260
32810
  function isHomeDir(cwd) {
32261
- return resolve9(cwd) === resolve9(homedir8());
32811
+ return resolve10(cwd) === resolve10(homedir8());
32262
32812
  }
32263
32813
  function hasWorkspaceVscode(cwd) {
32264
- return existsSync34(join30(cwd, ".vscode"));
32814
+ return existsSync37(join31(cwd, ".vscode"));
32265
32815
  }
32266
32816
  function configPath4(cwd) {
32267
32817
  if (!isHomeDir(cwd) && hasWorkspaceVscode(cwd)) {
32268
- return join30(cwd, ".vscode", "mcp.json");
32818
+ return join31(cwd, ".vscode", "mcp.json");
32269
32819
  }
32270
32820
  return userLevelConfigPath();
32271
32821
  }
@@ -32279,7 +32829,7 @@ var vscodeCopilotConnector = {
32279
32829
  if (!isHomeDir(here) && hasWorkspaceVscode(here)) {
32280
32830
  return { installed: true, note: "Found .vscode/ in current dir" };
32281
32831
  }
32282
- if (existsSync34(userLevelConfigPath().replace(/mcp\.json$/, ""))) {
32832
+ if (existsSync37(userLevelConfigPath().replace(/mcp\.json$/, ""))) {
32283
32833
  return { installed: true, note: "Found VS Code user profile" };
32284
32834
  }
32285
32835
  return { installed: false };
@@ -32317,7 +32867,7 @@ var vscodeCopilotConnector = {
32317
32867
  backupsDir: args.backupsDir,
32318
32868
  agentId: this.id
32319
32869
  });
32320
- if (!restored.restored && existsSync34(path)) {
32870
+ if (!restored.restored && existsSync37(path)) {
32321
32871
  const after = readJsonFile(path);
32322
32872
  if (after.ok && after.value && typeof after.value === "object") {
32323
32873
  const root = after.value;
@@ -32471,7 +33021,7 @@ function parseGraceMs(value, fallbackMs) {
32471
33021
  return Math.round(n * 1000);
32472
33022
  }
32473
33023
  function resolveSpecPath(cwd, raw) {
32474
- return isAbsolute4(raw) ? raw : resolve10(cwd, raw);
33024
+ return isAbsolute5(raw) ? raw : resolve11(cwd, raw);
32475
33025
  }
32476
33026
  function buildConnectionName(serverId) {
32477
33027
  const slug = serverId.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24) || "mcp-server";
@@ -32635,7 +33185,7 @@ async function runDev(opts) {
32635
33185
  let specPath = null;
32636
33186
  if (opts.spec) {
32637
33187
  const resolved = resolveSpecPath(cwd, opts.spec);
32638
- if (!existsSync35(resolved)) {
33188
+ if (!existsSync38(resolved)) {
32639
33189
  printError(`Spec file not found: ${resolved}`);
32640
33190
  process.exit(1);
32641
33191
  }
@@ -32846,7 +33396,7 @@ async function runWithRuntime(args) {
32846
33396
  return;
32847
33397
  regenInFlight = true;
32848
33398
  try {
32849
- printStep(`Spec changed (${relative7(args.cwd, args.specPath)}) — regenerating…`);
33399
+ printStep(`Spec changed (${relative9(args.cwd, args.specPath)}) — regenerating…`);
32850
33400
  const result = await regenerateDevBundle({
32851
33401
  organizationId: args.organizationId,
32852
33402
  serverId: args.serverId,
@@ -32912,9 +33462,9 @@ async function runWithRuntime(args) {
32912
33462
  if (args.inspector) {
32913
33463
  printInfo(` ${c.dim("Inspector: ")} ${c.cyan(`${baseUrl}/__inspect`)}`);
32914
33464
  }
32915
- printInfo(` ${c.dim(`Edit files in ${relative7(args.cwd, args.prepared.destDir)}/ to hot-reload.`)}`);
33465
+ printInfo(` ${c.dim(`Edit files in ${relative9(args.cwd, args.prepared.destDir)}/ to hot-reload.`)}`);
32916
33466
  if (args.gitNative && args.prepared.repoDir) {
32917
- printInfo(` ${c.dim(`Git-native: edit + commit + push in ${relative7(args.cwd, args.prepared.repoDir)}/ — the webhook reconciles src/**.`)}`);
33467
+ printInfo(` ${c.dim(`Git-native: edit + commit + push in ${relative9(args.cwd, args.prepared.repoDir)}/ — the webhook reconciles src/**.`)}`);
32918
33468
  printInfo(` ${c.dim("Config under mcpcloud/ is read-only; change it in the dashboard. Deploy when ready (mcp servers deploy).")}`);
32919
33469
  }
32920
33470
  if (toolsWatcher) {
@@ -32924,7 +33474,7 @@ async function runWithRuntime(args) {
32924
33474
  printInfo(` ${c.dim(`Edit tool handlers in .mcpcloud/server/src/tools/<slug>.ts (changes push to cloud on save).`)}`);
32925
33475
  }
32926
33476
  if (specWatcher) {
32927
- printInfo(` ${c.dim(`Spec watch: ${relative7(args.cwd, args.specPath)}`)}`);
33477
+ printInfo(` ${c.dim(`Spec watch: ${relative9(args.cwd, args.specPath)}`)}`);
32928
33478
  }
32929
33479
  const gitPullPoller = args.gitNative ? startGitPullPoll({
32930
33480
  fetchStatus: () => resolveServerGitPullStatus({
@@ -33565,13 +34115,13 @@ async function confirmRollback(deploymentId) {
33565
34115
  }
33566
34116
 
33567
34117
  // src/commands/doctor.ts
33568
- import { existsSync as existsSync37, statSync as statSync6, readFileSync as readFileSync27 } from "node:fs";
34118
+ import { existsSync as existsSync40, statSync as statSync6, readFileSync as readFileSync28 } from "node:fs";
33569
34119
  import { homedir as homedir9, platform as platform6 } from "node:os";
33570
- import { join as join32, delimiter as delimiter3 } from "node:path";
34120
+ import { join as join33, delimiter as delimiter3 } from "node:path";
33571
34121
 
33572
34122
  // src/lib/version-check.ts
33573
- import { existsSync as existsSync36, mkdirSync as mkdirSync18, readFileSync as readFileSync26, writeFileSync as writeFileSync21 } from "node:fs";
33574
- import { join as join31 } from "node:path";
34123
+ import { existsSync as existsSync39, mkdirSync as mkdirSync18, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "node:fs";
34124
+ import { join as join32 } from "node:path";
33575
34125
  var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
33576
34126
  var FETCH_TIMEOUT_MS2 = 2000;
33577
34127
  var REGISTRY_URL = "https://registry.npmjs.org/@mcpcloud/cli/latest";
@@ -33579,13 +34129,13 @@ function cacheDir() {
33579
34129
  return configDir();
33580
34130
  }
33581
34131
  function cacheFile() {
33582
- return join31(cacheDir(), "version-check.json");
34132
+ return join32(cacheDir(), "version-check.json");
33583
34133
  }
33584
34134
  function readCache() {
33585
- if (!existsSync36(cacheFile()))
34135
+ if (!existsSync39(cacheFile()))
33586
34136
  return null;
33587
34137
  try {
33588
- const raw = JSON.parse(readFileSync26(cacheFile(), "utf-8"));
34138
+ const raw = JSON.parse(readFileSync27(cacheFile(), "utf-8"));
33589
34139
  if (typeof raw.latest !== "string" || typeof raw.fetchedAt !== "number")
33590
34140
  return null;
33591
34141
  return { latest: raw.latest, fetchedAt: raw.fetchedAt };
@@ -33595,7 +34145,7 @@ function readCache() {
33595
34145
  }
33596
34146
  function writeCache(entry) {
33597
34147
  try {
33598
- if (!existsSync36(cacheDir()))
34148
+ if (!existsSync39(cacheDir()))
33599
34149
  mkdirSync18(cacheDir(), { recursive: true, mode: 448 });
33600
34150
  writeFileSync21(cacheFile(), JSON.stringify(entry, null, 2), { mode: 384 });
33601
34151
  } catch {}
@@ -33708,8 +34258,8 @@ function checkNode() {
33708
34258
  }
33709
34259
  function checkConfigFile() {
33710
34260
  const t0 = Date.now();
33711
- const path = join32(homedir9(), ".mcpcloud", "config.json");
33712
- if (!existsSync37(path)) {
34261
+ const path = join33(homedir9(), ".mcpcloud", "config.json");
34262
+ if (!existsSync40(path)) {
33713
34263
  return {
33714
34264
  name: "Config file",
33715
34265
  status: "warn",
@@ -33731,7 +34281,7 @@ function checkConfigFile() {
33731
34281
  } catch {}
33732
34282
  }
33733
34283
  try {
33734
- JSON.parse(readFileSync27(path, "utf-8"));
34284
+ JSON.parse(readFileSync28(path, "utf-8"));
33735
34285
  } catch (err) {
33736
34286
  return {
33737
34287
  name: "Config file",
@@ -33862,8 +34412,8 @@ function checkClaudeCli() {
33862
34412
  for (const dir of PATH.split(delimiter3)) {
33863
34413
  if (!dir)
33864
34414
  continue;
33865
- const candidate = join32(dir, exe);
33866
- if (existsSync37(candidate)) {
34415
+ const candidate = join33(dir, exe);
34416
+ if (existsSync40(candidate)) {
33867
34417
  return {
33868
34418
  name: "claude CLI",
33869
34419
  status: "pass",
@@ -34144,6 +34694,7 @@ var TOPICS = {
34144
34694
  " authentication_required Run `mcp login` or set MCPCLOUD_API_KEY.",
34145
34695
  " invalid_api_key The key is invalid or revoked. Run `mcp login`.",
34146
34696
  " organization_not_selected Pass --org <id> or run `mcp config set-org <id>`.",
34697
+ " organization_access_denied Not a member of that org — pass --org <id>; `mcp orgs list` shows yours.",
34147
34698
  " rate_limited Honor the Retry-After header. The CLI auto-retries once.",
34148
34699
  " deployment_failed Check `error.details.findings` (--json) for release-gate context.",
34149
34700
  " idempotency_key_conflict Same key reused with a different body. Generate a fresh one.",
@@ -34613,8 +35164,8 @@ function registerInstallationCommands(program) {
34613
35164
  }
34614
35165
 
34615
35166
  // src/commands/invoke.ts
34616
- import { existsSync as existsSync38, readFileSync as readFileSync28 } from "node:fs";
34617
- import { join as join33 } from "node:path";
35167
+ import { existsSync as existsSync41, readFileSync as readFileSync29 } from "node:fs";
35168
+ import { join as join34 } from "node:path";
34618
35169
 
34619
35170
  // src/lib/dev/local-invoke.ts
34620
35171
  var READY_TIMEOUT_MS = 30000;
@@ -34706,9 +35257,9 @@ function selectCodeModeInvocation(args) {
34706
35257
  }
34707
35258
  function readCodeOption(raw) {
34708
35259
  if (raw === "@-")
34709
- return readFileSync28(0, "utf-8");
35260
+ return readFileSync29(0, "utf-8");
34710
35261
  if (raw.startsWith("@"))
34711
- return readFileSync28(raw.slice(1), "utf-8");
35262
+ return readFileSync29(raw.slice(1), "utf-8");
34712
35263
  return raw;
34713
35264
  }
34714
35265
  function parseTimeoutMs4(value, fallbackMs) {
@@ -34990,14 +35541,14 @@ async function runLocalInvoke(args) {
34990
35541
  throw new CliExitError(1);
34991
35542
  }
34992
35543
  const env = {
34993
- ...args.envFile ? parseEnvFile(readFileSync28(args.envFile, "utf-8")) : {},
35544
+ ...args.envFile ? parseEnvFile(readFileSync29(args.envFile, "utf-8")) : {},
34994
35545
  ...parseEnvPairs(args.envPairs)
34995
35546
  };
34996
35547
  const port = await findAvailablePort({ range: 40, start: 43117 });
34997
35548
  const prepared = await prepareDev({
34998
35549
  cwd,
34999
35550
  inspector: false,
35000
- offline: existsSync38(join33(devRoot(cwd), "server")),
35551
+ offline: existsSync41(join34(devRoot(cwd), "server")),
35001
35552
  organizationId: target.organizationId,
35002
35553
  port,
35003
35554
  serverId: target.serverId,
@@ -35979,38 +36530,38 @@ function registerConnectionsCommands(program) {
35979
36530
  // src/lib/plugins.ts
35980
36531
  import { spawn as spawn7 } from "node:child_process";
35981
36532
  import {
35982
- existsSync as existsSync39,
36533
+ existsSync as existsSync42,
35983
36534
  mkdirSync as mkdirSync19,
35984
36535
  readdirSync as readdirSync5,
35985
- readFileSync as readFileSync29,
36536
+ readFileSync as readFileSync30,
35986
36537
  rmSync as rmSync3,
35987
36538
  statSync as statSync7
35988
36539
  } from "node:fs";
35989
- import { join as join34 } from "node:path";
36540
+ import { join as join35 } from "node:path";
35990
36541
  import { pathToFileURL } from "node:url";
35991
36542
  function pluginsDir() {
35992
- return join34(configDir(), "plugins");
36543
+ return join35(configDir(), "plugins");
35993
36544
  }
35994
36545
  function ensureDir2() {
35995
36546
  const dir = pluginsDir();
35996
- if (!existsSync39(dir))
36547
+ if (!existsSync42(dir))
35997
36548
  mkdirSync19(dir, { recursive: true, mode: 448 });
35998
36549
  }
35999
36550
  function readManifestFromPackageDir(packageDir) {
36000
- const pkgPath = join34(packageDir, "package.json");
36001
- if (!existsSync39(pkgPath))
36551
+ const pkgPath = join35(packageDir, "package.json");
36552
+ if (!existsSync42(pkgPath))
36002
36553
  return null;
36003
36554
  let pkg;
36004
36555
  try {
36005
- pkg = JSON.parse(readFileSync29(pkgPath, "utf-8"));
36556
+ pkg = JSON.parse(readFileSync30(pkgPath, "utf-8"));
36006
36557
  } catch {
36007
36558
  return null;
36008
36559
  }
36009
36560
  if (!pkg.name)
36010
36561
  return null;
36011
36562
  const relEntry = pkg.mcpsh?.register ?? pkg.mcp?.register ?? pkg.main ?? "index.js";
36012
- const entry = join34(packageDir, relEntry);
36013
- if (!existsSync39(entry))
36563
+ const entry = join35(packageDir, relEntry);
36564
+ if (!existsSync42(entry))
36014
36565
  return null;
36015
36566
  return {
36016
36567
  name: pkg.name,
@@ -36021,11 +36572,11 @@ function readManifestFromPackageDir(packageDir) {
36021
36572
  }
36022
36573
  function listInstalledPlugins() {
36023
36574
  const dir = pluginsDir();
36024
- if (!existsSync39(dir))
36575
+ if (!existsSync42(dir))
36025
36576
  return [];
36026
36577
  const out = [];
36027
36578
  for (const entry of readdirSync5(dir)) {
36028
- const full = join34(dir, entry);
36579
+ const full = join35(dir, entry);
36029
36580
  let s;
36030
36581
  try {
36031
36582
  s = statSync7(full);
@@ -36036,7 +36587,7 @@ function listInstalledPlugins() {
36036
36587
  continue;
36037
36588
  if (entry.startsWith("@")) {
36038
36589
  for (const child of readdirSync5(full)) {
36039
- const m = readManifestFromPackageDir(join34(full, child));
36590
+ const m = readManifestFromPackageDir(join35(full, child));
36040
36591
  if (m)
36041
36592
  out.push(m);
36042
36593
  }
@@ -36100,12 +36651,12 @@ async function installPlugin(packageSpec) {
36100
36651
  if (code !== 0) {
36101
36652
  throw new Error(`npm install ${packageSpec} exited with code ${code}.`);
36102
36653
  }
36103
- const nm = join34(dir, "node_modules");
36104
- if (!existsSync39(nm)) {
36654
+ const nm = join35(dir, "node_modules");
36655
+ if (!existsSync42(nm)) {
36105
36656
  throw new Error(`npm install ran but produced no node_modules under ${dir}.`);
36106
36657
  }
36107
36658
  const baseName = packageSpec.replace(/@[^@/]+$/, "");
36108
- const candidatePath = baseName.startsWith("@") ? join34(nm, baseName.split("/")[0], baseName.split("/")[1] ?? "") : join34(nm, baseName);
36659
+ const candidatePath = baseName.startsWith("@") ? join35(nm, baseName.split("/")[0], baseName.split("/")[1] ?? "") : join35(nm, baseName);
36109
36660
  const manifest = readManifestFromPackageDir(candidatePath);
36110
36661
  if (!manifest) {
36111
36662
  throw new Error(`Installed but could not read plugin manifest at ${candidatePath}.`);
@@ -36113,26 +36664,26 @@ async function installPlugin(packageSpec) {
36113
36664
  return { name: manifest.name, entry: manifest.entry };
36114
36665
  }
36115
36666
  function removePlugin(name) {
36116
- const nm = join34(pluginsDir(), "node_modules");
36117
- if (!existsSync39(nm))
36667
+ const nm = join35(pluginsDir(), "node_modules");
36668
+ if (!existsSync42(nm))
36118
36669
  return false;
36119
- const target = name.startsWith("@") ? join34(nm, name.split("/")[0], name.split("/")[1] ?? "") : join34(nm, name);
36120
- if (!existsSync39(target))
36670
+ const target = name.startsWith("@") ? join35(nm, name.split("/")[0], name.split("/")[1] ?? "") : join35(nm, name);
36671
+ if (!existsSync42(target))
36121
36672
  return false;
36122
36673
  rmSync3(target, { recursive: true, force: true });
36123
36674
  return true;
36124
36675
  }
36125
36676
  function listInstalledPluginsCombined() {
36126
36677
  const direct = listInstalledPlugins();
36127
- const nm = join34(pluginsDir(), "node_modules");
36128
- if (!existsSync39(nm))
36678
+ const nm = join35(pluginsDir(), "node_modules");
36679
+ if (!existsSync42(nm))
36129
36680
  return direct;
36130
36681
  const seen = new Set(direct.map((p) => p.name));
36131
36682
  const out = [...direct];
36132
36683
  for (const entry of readdirSync5(nm)) {
36133
36684
  if (entry === ".bin" || entry === ".package-lock.json")
36134
36685
  continue;
36135
- const full = join34(nm, entry);
36686
+ const full = join35(nm, entry);
36136
36687
  let s;
36137
36688
  try {
36138
36689
  s = statSync7(full);
@@ -36143,7 +36694,7 @@ function listInstalledPluginsCombined() {
36143
36694
  continue;
36144
36695
  if (entry.startsWith("@")) {
36145
36696
  for (const child of readdirSync5(full)) {
36146
- const m = readManifestFromPackageDir(join34(full, child));
36697
+ const m = readManifestFromPackageDir(join35(full, child));
36147
36698
  if (m && !seen.has(m.name)) {
36148
36699
  seen.add(m.name);
36149
36700
  out.push(m);
@@ -36366,7 +36917,7 @@ async function runResume(selection, deps) {
36366
36917
  return mapMutationError(err, `resume ${selection.name}`);
36367
36918
  }
36368
36919
  }
36369
- async function runDeploy(selection, deps) {
36920
+ async function runDeploy2(selection, deps) {
36370
36921
  try {
36371
36922
  await deps.api.post("/api/v1/server/deploy", {
36372
36923
  organizationId: deps.organizationId,
@@ -36736,8 +37287,13 @@ var CLI_EXAMPLES = {
36736
37287
  "servers pause": ["mcp servers pause srv_abc"],
36737
37288
  "servers resume": ["mcp servers resume srv_abc"],
36738
37289
  "servers push-spec": [
36739
- "mcp servers push-spec ./openapi.json --project proj_abc",
36740
- 'mcp servers push-spec ./openapi.json --project proj_abc --name "Pages API"'
37290
+ "mcp servers push-spec srv_abc --spec ./openapi.json",
37291
+ "mcp servers push-spec srv_abc --spec ./openapi.json --apply --deploy --wait",
37292
+ "mcp servers push-spec srv_abc --spec ./openapi.json --apply --rename-to-operation-ids"
37293
+ ],
37294
+ "servers regenerate": [
37295
+ "mcp servers regenerate srv_abc --dry-run",
37296
+ "mcp servers regenerate srv_abc && mcp servers deploy srv_abc --wait"
36741
37297
  ],
36742
37298
  "servers env list": ["mcp servers env list srv_abc"],
36743
37299
  "servers env set": [
@@ -36761,6 +37317,14 @@ var CLI_EXAMPLES = {
36761
37317
  "tools pull": ["mcp tools pull srv_abc"],
36762
37318
  "tools pull-handler": ["mcp tools pull-handler srv_abc query_users"],
36763
37319
  "tools reset-handler": ["mcp tools reset-handler srv_abc query_users"],
37320
+ "tools rename": [
37321
+ "mcp tools rename getrecording getRecording --server srv_abc",
37322
+ "mcp tools rename --from-operation-ids --server srv_abc --dry-run",
37323
+ "mcp tools rename --map @renames.json --server srv_abc"
37324
+ ],
37325
+ "tools delete": [
37326
+ "mcp tools delete query_users --server srv_abc --confirm query_users"
37327
+ ],
36764
37328
  invoke: ["mcp invoke srv_abc query_users @args.json"],
36765
37329
  "skills list": ["mcp skills list"],
36766
37330
  "skills connect": [
@@ -37735,12 +38299,12 @@ function displayValueFor(field, value) {
37735
38299
 
37736
38300
  // src/lib/tui/state-store.ts
37737
38301
  import {
37738
- existsSync as existsSync40,
38302
+ existsSync as existsSync43,
37739
38303
  mkdirSync as mkdirSync20,
37740
- readFileSync as readFileSync30,
38304
+ readFileSync as readFileSync31,
37741
38305
  writeFileSync as writeFileSync22
37742
38306
  } from "node:fs";
37743
- import { join as join35 } from "node:path";
38307
+ import { join as join36 } from "node:path";
37744
38308
  var ALL_TABS = [
37745
38309
  "servers",
37746
38310
  "projects",
@@ -37750,7 +38314,7 @@ var ALL_TABS = [
37750
38314
  "devSessions"
37751
38315
  ];
37752
38316
  function tuiStateFile() {
37753
- return join35(configDir(), "tui-state.json");
38317
+ return join36(configDir(), "tui-state.json");
37754
38318
  }
37755
38319
  function isTab(v) {
37756
38320
  return typeof v === "string" && ALL_TABS.includes(v);
@@ -37815,11 +38379,11 @@ function sanitizeTuiState(input) {
37815
38379
  }
37816
38380
  function readTuiState() {
37817
38381
  const path = tuiStateFile();
37818
- if (!existsSync40(path))
38382
+ if (!existsSync43(path))
37819
38383
  return {};
37820
38384
  let parsed;
37821
38385
  try {
37822
- parsed = JSON.parse(readFileSync30(path, "utf-8"));
38386
+ parsed = JSON.parse(readFileSync31(path, "utf-8"));
37823
38387
  } catch {
37824
38388
  return {};
37825
38389
  }
@@ -37827,7 +38391,7 @@ function readTuiState() {
37827
38391
  }
37828
38392
  function writeTuiState(state) {
37829
38393
  try {
37830
- if (!existsSync40(configDir())) {
38394
+ if (!existsSync43(configDir())) {
37831
38395
  mkdirSync20(configDir(), { recursive: true, mode: 448 });
37832
38396
  }
37833
38397
  writeFileSync22(tuiStateFile(), JSON.stringify(state, null, 2), {
@@ -39758,7 +40322,7 @@ function registerUiCommand(program) {
39758
40322
  openConfirmAction({
39759
40323
  prompt: `Deploy ${selection.name}?`,
39760
40324
  detail: "Triggers a workersDev build from the current bundle. Takes ~10–30s.",
39761
- run: () => runDeploy(selection, actionDeps)
40325
+ run: () => runDeploy2(selection, actionDeps)
39762
40326
  });
39763
40327
  return true;
39764
40328
  }