@mcpcloud/cli 0.25.1 → 0.26.0-next-20260927194247

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 +2 -2
  2. package/dist/index.js +208 -68
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -103,7 +103,7 @@ mcp servers get my-api --probe # verify the endpoint serves what the con
103
103
  mcp invoke my-api getUser --args '{"id":"abc"}'
104
104
  ```
105
105
 
106
- Servers are addressable by id, name, or memorable slug throughout the CLI. Two verbs keep a server current after its API changes: `mcp servers push-spec` diffs a local spec against the stored tools (preview by default; `--apply` reconciles schemas by stable operation key, preserving curated descriptions, tags, risk classes, and enrichment), and `mcp servers regenerate` re-fetches the original source spec and rebuilds with the current codegen (`--dry-run` previews the per-tool changes first).
106
+ Servers are addressable by id, name, or memorable slug throughout the CLI. Two verbs keep a server current after its API changes: `mcp servers push-spec` diffs a local spec against the stored tools (preview by default; `--apply` reconciles schemas by stable operation key, preserving curated descriptions, tags, risk classes, and enrichment), and `mcp servers regenerate` re-fetches the original source spec and rebuilds with the current codegen (`--dry-run` previews the per-tool changes first), storing the spec document it read so the generated code and the drift check follow it. To read a server from a different spec URL from now on, `mcp servers update <server> --source-url <url>` checks the new URL, shows what a regenerate from it would change, and stores it (`--dry-run` checks without storing); then regenerate and deploy. Operations a spec marks `x-mcp: false` never become tools.
107
107
 
108
108
  Neither verb renames a tool — both match tools on their stable operation key. Codegen now keeps an operationId's own case (`getRecording`); servers created before that change keep their lowercased names (`getrecording`), and both previews say how many. `mcp tools rename --from-operation-ids --server <server>` (or `push-spec --rename-to-operation-ids`) adopts the new names while keeping tool ids, access grants, and saved scenarios; `mcp tools rename <old> <new>` renames one tool. Deploy afterwards — the live server keeps the old names until then.
109
109
 
@@ -565,7 +565,7 @@ _Generated from the live command tree by `bun run docs:readme` — do not edit b
565
565
  | `mcp servers test-suites run <server>` | Run the server’s test scenarios and report the suite result |
566
566
  | `mcp servers traces <server>` | Show recent tool-call traces from the edge — what a caller sent and what came back. Pass --request to open one call in full. |
567
567
  | `mcp servers transfer <server>` | Move a server into another organization you administer, keeping its id, slug, deployment, custom domain, end-user keys and git link. Shows the plan; add --yes to execute. |
568
- | `mcp servers update <server>` | Update a server (name, description, tool surface, upstream key mode, runtime token TTL) |
568
+ | `mcp servers update <server>` | Update a server (name, description, tool surface, upstream key mode, runtime token TTL, spec source URL) |
569
569
  | `mcp servers variables` | Inspect and set the sandbox variables that scenario arguments reference |
570
570
  | `mcp servers variables list <server>` | List a server’s sandbox variables and which ones still need a value |
571
571
  | `mcp servers variables set <server>` | Set or clear sandbox variables (repeat --set; an empty value clears) |
package/dist/index.js CHANGED
@@ -8278,11 +8278,16 @@ async function runPushSpec(serverRef, opts) {
8278
8278
 
8279
8279
  // src/commands/servers-regenerate.ts
8280
8280
  function registerServerRegenerateCommand(servers) {
8281
- 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", [
8281
+ 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>", "Read the spec from this URL for this run only: the tools are rebuilt from it, but the stored source and its document are left as they are (needed for GraphQL servers whose introspection endpoint wasn't persisted). To change the stored source, use `mcp servers update <server> --source-url <url>`.").option("--dry-run", "Report what would change (per tool) without writing anything").addHelpText("after", [
8282
8282
  "",
8283
8283
  "Regenerate re-introspects/re-parses the source and rebuilds each tool with",
8284
- "the current codegen, so codegen fixes reach existing servers. It does not",
8285
- "deploy — run `mcp servers deploy` afterwards to ship the rebuilt bundle.",
8284
+ "the current codegen, so codegen fixes AND upstream spec changes reach",
8285
+ "existing servers. It stores the spec document it read — codegen builds",
8286
+ "from that document and the daily drift check compares against it — and",
8287
+ "resets the drift check. It does not deploy — run `mcp servers deploy`",
8288
+ "afterwards to ship the rebuilt bundle.",
8289
+ "With --source-url for a different URL, the document is NOT stored: an",
8290
+ "override is for one run, and the stored source keeps pointing where it did.",
8286
8291
  "It never renames a tool: names that predate the current naming rule",
8287
8292
  "(getrecording vs getRecording) are reported, and",
8288
8293
  "`mcp tools rename --from-operation-ids` adopts the rule.",
@@ -8343,7 +8348,8 @@ function printRegeneration(r, serverId) {
8343
8348
  [`tools ${dryRun ? "that would be added" : "added"}`]: String(r.inserted),
8344
8349
  ...r.unchanged !== undefined ? { "tools unchanged": String(r.unchanged) } : {},
8345
8350
  "tools dropped from spec": String(r.removed),
8346
- generation: r.generationStatus
8351
+ generation: r.generationStatus,
8352
+ ...r.storedDocument ? { "spec document": storedDocumentLine(r.storedDocument) } : {}
8347
8353
  });
8348
8354
  for (const change of r.changes ?? []) {
8349
8355
  printInfo(` ~ ${change.name}${change.fields.length > 0 ? ` (${change.fields.join(", ")})` : ""}`);
@@ -8360,8 +8366,21 @@ function printRegeneration(r, serverId) {
8360
8366
  });
8361
8367
  if (drift)
8362
8368
  printWarn(` ! ${drift}`);
8369
+ if (r.storedDocument === "keptForOverride") {
8370
+ printInfo(c.dim(` → to read ${serverId} from that URL from now on: \`mcp servers update ${serverId} --source-url <url>\`, then regenerate.`));
8371
+ }
8363
8372
  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.`));
8364
8373
  }
8374
+ function storedDocumentLine(disposition) {
8375
+ switch (disposition) {
8376
+ case "refreshed":
8377
+ return "stored — codegen builds from it; drift check reset";
8378
+ case "wouldRefresh":
8379
+ return "would be stored (and the drift check reset)";
8380
+ case "keptForOverride":
8381
+ return "kept — --source-url reads another URL for this run only";
8382
+ }
8383
+ }
8365
8384
 
8366
8385
  // src/commands/servers-lifecycle.ts
8367
8386
  function registerServerLifecycleCommands(servers) {
@@ -8711,6 +8730,185 @@ function registerServerTransferCommand(servers) {
8711
8730
  }));
8712
8731
  }
8713
8732
 
8733
+ // src/commands/servers-update-source.ts
8734
+ async function changeServerSource(args) {
8735
+ const data = await api.post("/api/v1/server/source", {
8736
+ organizationId: args.orgId,
8737
+ projectId: args.projectId,
8738
+ serverId: args.serverId,
8739
+ sourceUrl: args.sourceUrl,
8740
+ ...args.dryRun ? { dryRun: true } : {}
8741
+ });
8742
+ return data.source;
8743
+ }
8744
+ function printSourceChange(change, serverId) {
8745
+ printSuccess(change.applied ? `Spec source for ${c.bold(serverId)} is now ${change.sourceUrl}.` : `Spec source preview for ${c.bold(serverId)} — nothing stored.`);
8746
+ const doc = change.document;
8747
+ printKeyValue({
8748
+ previous: change.previousSourceUrl ?? "— (none stored)",
8749
+ source: change.urlChanged ? change.sourceUrl : `${change.sourceUrl} (unchanged — re-read)`,
8750
+ type: change.sourceType,
8751
+ document: `${doc.title ?? "Untitled"}${doc.version ? ` ${doc.version}` : ""} · ${doc.endpointCount} operation(s)`,
8752
+ "left out (x-mcp: false)": change.excludedOperations.length > 0 ? formatNameList(change.excludedOperations) : "none",
8753
+ "drift check": driftLine(change)
8754
+ });
8755
+ for (const warning of doc.warnings.slice(0, 3)) {
8756
+ printWarn(` ! ${warning}`);
8757
+ }
8758
+ const r = change.regeneration;
8759
+ printInfo("");
8760
+ printInfo("What a regenerate from this source would do:");
8761
+ printKeyValue({
8762
+ "tools that would change": String(r.updated),
8763
+ "tools that would be added": String(r.inserted),
8764
+ ...r.unchanged !== undefined ? { "tools unchanged": String(r.unchanged) } : {},
8765
+ "tools dropped from spec": String(r.removed),
8766
+ "input schemas that change": String(change.inputSchemaChanges.length)
8767
+ });
8768
+ for (const item of r.changes ?? []) {
8769
+ printInfo(` ~ ${item.name}${item.fields.length > 0 ? ` (${item.fields.join(", ")})` : ""}`);
8770
+ }
8771
+ for (const name of r.insertedNames ?? [])
8772
+ printInfo(` + ${name}`);
8773
+ if (change.inputSchemaChanges.length > 0) {
8774
+ printInfo(` ~ input schema from the new document: ${formatNameList(change.inputSchemaChanges)}`);
8775
+ }
8776
+ if (r.removed > 0) {
8777
+ printInfo(c.dim(` → ${r.removed} tool(s) the new source no longer offers would be kept, not deleted (removal stays a manual action): ${formatNameList(r.removedKeys, 5)}`));
8778
+ }
8779
+ const drift = nameDriftWarning({
8780
+ drift: r.nameDrift,
8781
+ serverId,
8782
+ offerPushSpecFlag: false
8783
+ });
8784
+ if (drift)
8785
+ printWarn(` ! ${drift}`);
8786
+ printInfo(c.dim(change.applied ? ` → run \`mcp servers regenerate ${serverId}\`, then \`mcp servers deploy ${serverId} --wait\` to ship the new source. Nothing live changes until then.` : ` → run the same command without --dry-run to store this source.`));
8787
+ }
8788
+ function driftLine(change) {
8789
+ if (!change.driftCheck.enabled)
8790
+ return "not enabled for this source";
8791
+ return change.driftCheck.reset ? "reset — the next daily check reads the new source" : "would be reset";
8792
+ }
8793
+
8794
+ // src/commands/servers-update.ts
8795
+ function registerServerUpdateCommand(servers) {
8796
+ servers.command("update <server>").description("Update a server (name, description, tool surface, upstream key mode, runtime token TTL, spec source URL)").option("--org <organizationId>", "Organization ID").option("--name <name>", "New display name").option("--description <text>", "New description").option("--discovery-mode <all|wrapper>", 'Tool surface: "all" registers every operation; "wrapper" registers searchTools/getToolDefinition/useTool INSTEAD (operations are called through useTool).').option("--code-mode <off|opt-in|default>", "Code Mode: expose codemode_execute so an agent can script several calls in one sandboxed run. Served by the edge, so it takes effect on the next deploy.").option("--upstream-api-key-mode <shared|perMember>", "Whose upstream credential the server uses: one shared key, or each member their own (Pro and above). perMember needs a protected access mode (privateOrg or unlisted) — refused while the active deployment is public.").option("--runtime-token-ttl <seconds|default>", 'Runtime token lifetime in seconds (60-86400; the default AND cap for minted tokens). Pass "default" to restore the 300s platform default.').option("--source-url <url>", "Read this server’s spec from a different URL from now on. The URL is fetched and parsed as the server’s source type first, and the report shows what a regenerate from it would change. Tools are not touched until you regenerate.").option("--dry-run", "With --source-url: fetch, parse and report without storing anything").addHelpText("after", [
8797
+ "",
8798
+ "Notes:",
8799
+ " Runtime defaults (oauth, deploymentDefaults, runtime bindings) are not",
8800
+ " v1 PATCH-able. Change them via `mcp servers deploy --upstream-*`.",
8801
+ " --discovery-mode and --code-mode change the server's TOOL SURFACE and",
8802
+ " reach clients on the next `mcp servers deploy`. Check what is live with",
8803
+ " `mcp servers get <server> --probe`.",
8804
+ "",
8805
+ "Changing the spec source (--source-url):",
8806
+ " Stores the URL together with the document it returns (codegen builds",
8807
+ " from that document) and resets the drift check so the next daily check",
8808
+ " reads the new source. Operations the spec marks `x-mcp: false` are left",
8809
+ " out. The live server changes only when you regenerate and deploy:",
8810
+ " $ mcp servers update srv_123 --source-url https://api.example.com/openapi.mcp.json --dry-run",
8811
+ " $ mcp servers update srv_123 --source-url https://api.example.com/openapi.mcp.json",
8812
+ " $ mcp servers regenerate srv_123 && mcp servers deploy srv_123 --wait",
8813
+ " `regenerate --source-url` is different: a one-run override that stores",
8814
+ " nothing."
8815
+ ].join(`
8816
+ `)).action(runAction(async (serverRef, opts) => {
8817
+ await runUpdate(serverRef, opts);
8818
+ }));
8819
+ }
8820
+ async function runUpdate(serverRef, opts) {
8821
+ const settings = buildSettingsPatch(opts);
8822
+ const hasSettings = Object.keys(settings).length > 0;
8823
+ if (opts.dryRun && opts.sourceUrl === undefined) {
8824
+ throw new Error("--dry-run previews a --source-url change; pass --source-url <url>.");
8825
+ }
8826
+ if (opts.dryRun && hasSettings) {
8827
+ throw new Error("--dry-run only previews --source-url. Run the other changes without --dry-run.");
8828
+ }
8829
+ if (!hasSettings && opts.sourceUrl === undefined) {
8830
+ throw new Error("Provide at least one of --name, --description, --discovery-mode, --code-mode, --upstream-api-key-mode, --runtime-token-ttl, or --source-url.");
8831
+ }
8832
+ const orgId = await resolveOrgId(opts.org);
8833
+ const serverId = await resolveServerId(serverRef, orgId);
8834
+ const serverData = await api.get("/api/v1/server", { organizationId: orgId, serverId });
8835
+ const projectId = serverData.server.projectId;
8836
+ let source = null;
8837
+ if (opts.sourceUrl !== undefined) {
8838
+ if (!isJsonMode()) {
8839
+ printStep(`${opts.dryRun ? "Checking" : "Reading"} ${c.bold(opts.sourceUrl)} for ${c.bold(serverId)}…`);
8840
+ }
8841
+ source = await changeServerSource({
8842
+ orgId,
8843
+ projectId,
8844
+ serverId,
8845
+ sourceUrl: opts.sourceUrl,
8846
+ dryRun: Boolean(opts.dryRun)
8847
+ });
8848
+ }
8849
+ let updated = null;
8850
+ if (hasSettings) {
8851
+ updated = await api.patch("/api/v1/server", {
8852
+ organizationId: orgId,
8853
+ projectId,
8854
+ serverId,
8855
+ ...settings
8856
+ });
8857
+ }
8858
+ if (isJsonMode()) {
8859
+ printJson({
8860
+ ...updated ? { server: updated.server } : {},
8861
+ ...source ? { source } : {}
8862
+ });
8863
+ return;
8864
+ }
8865
+ if (updated) {
8866
+ const s = updated.server;
8867
+ printSuccess(`Server ${s.id} updated.`);
8868
+ printKeyValue({
8869
+ id: s.id,
8870
+ name: s.name,
8871
+ description: s.description ?? "—",
8872
+ status: s.status,
8873
+ updated: formatDate(s.updatedAt)
8874
+ });
8875
+ }
8876
+ if (source)
8877
+ printSourceChange(source, serverId);
8878
+ }
8879
+ function buildSettingsPatch(opts) {
8880
+ const body = {};
8881
+ if (opts.name !== undefined)
8882
+ body["name"] = opts.name;
8883
+ if (opts.description !== undefined)
8884
+ body["description"] = opts.description;
8885
+ if (opts.discoveryMode !== undefined) {
8886
+ body["discoveryMode"] = validateChoice("discovery-mode", opts.discoveryMode, ["all", "wrapper"]);
8887
+ }
8888
+ if (opts.codeMode !== undefined) {
8889
+ body["codeModeMode"] = validateChoice("code-mode", opts.codeMode, [
8890
+ "off",
8891
+ "opt-in",
8892
+ "default"
8893
+ ]);
8894
+ }
8895
+ if (opts.upstreamApiKeyMode !== undefined) {
8896
+ body["upstreamApiKeyMode"] = validateChoice("upstream-api-key-mode", opts.upstreamApiKeyMode, ["shared", "perMember"]);
8897
+ }
8898
+ if (opts.runtimeTokenTtl !== undefined) {
8899
+ if (opts.runtimeTokenTtl === "default") {
8900
+ body["runtimeTokenTtlSeconds"] = null;
8901
+ } else {
8902
+ const ttl = Number(opts.runtimeTokenTtl);
8903
+ if (!Number.isInteger(ttl)) {
8904
+ throw new Error('--runtime-token-ttl must be an integer number of seconds, or "default".');
8905
+ }
8906
+ body["runtimeTokenTtlSeconds"] = ttl;
8907
+ }
8908
+ }
8909
+ return body;
8910
+ }
8911
+
8714
8912
  // src/commands/servers-mutations.ts
8715
8913
  function registerServerMutationCommands(servers) {
8716
8914
  servers.command("create").description("Create an empty server skeleton in a project").requiredOption("--project <project>", "Project (id or name)").requiredOption("--name <name>", "Display name (1–100 chars)").option("--org <organizationId>", "Organization ID").option("--description <text>", "Optional description").option("--git-repo <owner/name>", "Host the generated code on this GitHub repo (links now, mirrors after first generation)").option("--git-path-prefix <prefix>", "Sync into this path inside the repo").option("--no-git-auto-sync", "Do not mirror commits automatically").addHelpText("after", [
@@ -8761,70 +8959,7 @@ function registerServerMutationCommands(servers) {
8761
8959
  created: formatDate(s.createdAt)
8762
8960
  });
8763
8961
  }));
8764
- servers.command("update <server>").description("Update a server (name, description, tool surface, upstream key mode, runtime token TTL)").option("--org <organizationId>", "Organization ID").option("--name <name>", "New display name").option("--description <text>", "New description").option("--discovery-mode <all|wrapper>", 'Tool surface: "all" registers every operation; "wrapper" registers searchTools/getToolDefinition/useTool INSTEAD (operations are called through useTool).').option("--code-mode <off|opt-in|default>", "Code Mode: expose codemode_execute so an agent can script several calls in one sandboxed run. Served by the edge, so it takes effect on the next deploy.").option("--upstream-api-key-mode <shared|perMember>", "Whose upstream credential the server uses: one shared key, or each member their own (Pro and above). perMember needs a protected access mode (privateOrg or unlisted) — refused while the active deployment is public.").option("--runtime-token-ttl <seconds|default>", 'Runtime token lifetime in seconds (60-86400; the default AND cap for minted tokens). Pass "default" to restore the 300s platform default.').addHelpText("after", [
8765
- "",
8766
- "Notes:",
8767
- " Runtime defaults (oauth, deploymentDefaults, runtime bindings) are not",
8768
- " v1 PATCH-able. Change them via `mcp servers deploy --upstream-*`.",
8769
- " --discovery-mode and --code-mode change the server's TOOL SURFACE and",
8770
- " reach clients on the next `mcp servers deploy`. Check what is live with",
8771
- " `mcp servers get <server> --probe`."
8772
- ].join(`
8773
- `)).action(runAction(async (serverRef, opts) => {
8774
- const orgId = await resolveOrgId(opts.org);
8775
- const serverId = await resolveServerId(serverRef, orgId);
8776
- const serverData = await api.get("/api/v1/server", { organizationId: orgId, serverId });
8777
- const body = {
8778
- organizationId: orgId,
8779
- projectId: serverData.server.projectId,
8780
- serverId
8781
- };
8782
- if (opts.name !== undefined)
8783
- body["name"] = opts.name;
8784
- if (opts.description !== undefined)
8785
- body["description"] = opts.description;
8786
- if (opts.discoveryMode !== undefined) {
8787
- body["discoveryMode"] = validateChoice("discovery-mode", opts.discoveryMode, ["all", "wrapper"]);
8788
- }
8789
- if (opts.codeMode !== undefined) {
8790
- body["codeModeMode"] = validateChoice("code-mode", opts.codeMode, [
8791
- "off",
8792
- "opt-in",
8793
- "default"
8794
- ]);
8795
- }
8796
- if (opts.upstreamApiKeyMode !== undefined) {
8797
- body["upstreamApiKeyMode"] = validateChoice("upstream-api-key-mode", opts.upstreamApiKeyMode, ["shared", "perMember"]);
8798
- }
8799
- if (opts.runtimeTokenTtl !== undefined) {
8800
- if (opts.runtimeTokenTtl === "default") {
8801
- body["runtimeTokenTtlSeconds"] = null;
8802
- } else {
8803
- const ttl = Number(opts.runtimeTokenTtl);
8804
- if (!Number.isInteger(ttl)) {
8805
- throw new Error('--runtime-token-ttl must be an integer number of seconds, or "default".');
8806
- }
8807
- body["runtimeTokenTtlSeconds"] = ttl;
8808
- }
8809
- }
8810
- if (Object.keys(body).length === 3) {
8811
- throw new Error("Provide at least one of --name, --description, --discovery-mode, --code-mode, --upstream-api-key-mode, or --runtime-token-ttl.");
8812
- }
8813
- const data = await api.patch("/api/v1/server", body);
8814
- if (isJsonMode()) {
8815
- printJson(data);
8816
- return;
8817
- }
8818
- const s = data.server;
8819
- printSuccess(`Server ${s.id} updated.`);
8820
- printKeyValue({
8821
- id: s.id,
8822
- name: s.name,
8823
- description: s.description ?? "—",
8824
- status: s.status,
8825
- updated: formatDate(s.updatedAt)
8826
- });
8827
- }));
8962
+ registerServerUpdateCommand(servers);
8828
8963
  servers.command("pause <server>").description("Pause the server's latest active deployment (traffic stops; worker is preserved)").option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").addHelpText("after", [
8829
8964
  "",
8830
8965
  "Notes:",
@@ -37337,6 +37472,11 @@ var CLI_EXAMPLES = {
37337
37472
  "mcp servers regenerate srv_abc --dry-run",
37338
37473
  "mcp servers regenerate srv_abc && mcp servers deploy srv_abc --wait"
37339
37474
  ],
37475
+ "servers update": [
37476
+ 'mcp servers update srv_abc --name "Payments API"',
37477
+ "mcp servers update srv_abc --source-url https://api.example.com/openapi.mcp.json --dry-run",
37478
+ "mcp servers update srv_abc --source-url https://api.example.com/openapi.mcp.json"
37479
+ ],
37340
37480
  "servers env list": ["mcp servers env list srv_abc"],
37341
37481
  "servers env set": [
37342
37482
  "mcp servers env set srv_abc OPENAI_API_KEY=sk-...",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcpcloud/cli",
3
- "version": "0.25.1",
3
+ "version": "0.26.0-next-20260927194247",
4
4
  "description": "The official CLI for MCPCloud — manage projects, servers, skills, and API keys from the terminal",
5
5
  "type": "module",
6
6
  "bin": {