@mcpcloud/cli 0.7.0-next-20260628020545 → 0.9.1-next-20260709201721

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 (2) hide show
  1. package/dist/index.js +269 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4691,6 +4691,52 @@ function registerServerLifecycleCommands(servers) {
4691
4691
  if (opts.wait && !waitDone)
4692
4692
  throw new CliExitError(1);
4693
4693
  }));
4694
+ servers.command("regenerate <serverId>").description("Re-fetch a server's source spec and rebuild its tools with the current codegen (preserves customizations). Wraps POST /api/v1/server/regenerate.").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Project ID (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", [
4695
+ "",
4696
+ "Regenerate re-introspects/re-parses the source and rebuilds each tool with",
4697
+ "the current codegen, so codegen fixes reach existing servers. It does not",
4698
+ "deploy — run `mcp servers deploy` afterwards to ship the rebuilt bundle.",
4699
+ "",
4700
+ "Examples:",
4701
+ " # Propagate codegen fixes into an existing server, then deploy:",
4702
+ " $ mcp servers regenerate srv_123 && mcp servers deploy srv_123 --wait",
4703
+ "",
4704
+ " # GraphQL server whose introspection endpoint must be supplied:",
4705
+ " $ mcp servers regenerate srv_123 --source-url https://api.example.com/graphql"
4706
+ ].join(`
4707
+ `)).action(runAction(async (serverId, opts) => {
4708
+ const orgId = await resolveOrgId(opts.org);
4709
+ let projectId = opts.project;
4710
+ if (!projectId) {
4711
+ const serverData = await api.get("/api/v1/server", { organizationId: orgId, serverId });
4712
+ projectId = serverData.server.projectId;
4713
+ }
4714
+ if (!isJsonMode()) {
4715
+ printStep(`Regenerating ${c.bold(serverId)} from its source spec…`);
4716
+ }
4717
+ const data = await api.post("/api/v1/server/regenerate", {
4718
+ organizationId: orgId,
4719
+ projectId,
4720
+ serverId,
4721
+ ...opts.sourceUrl ? { sourceUrl: opts.sourceUrl } : {}
4722
+ });
4723
+ const r = data.regeneration;
4724
+ if (isJsonMode()) {
4725
+ printJson({ regeneration: r });
4726
+ return;
4727
+ }
4728
+ printSuccess(`Regenerated ${c.bold(serverId)} (${r.sourceType}).`);
4729
+ printKeyValue({
4730
+ "tools updated": String(r.updated),
4731
+ "tools added": String(r.inserted),
4732
+ "tools dropped from spec": String(r.removed),
4733
+ generation: r.generationStatus
4734
+ });
4735
+ if (r.removed > 0) {
4736
+ 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(", ")}`));
4737
+ }
4738
+ printInfo(c.dim(` → run \`mcp servers deploy ${serverId} --wait\` to ship the regenerated bundle.`));
4739
+ }));
4694
4740
  }
4695
4741
 
4696
4742
  // src/commands/servers-mutations.ts
@@ -4901,6 +4947,12 @@ function looksLikeGraphql(spec) {
4901
4947
  }
4902
4948
  async function readSpecSource(spec, override, options = {}) {
4903
4949
  const trimmedSpec = spec.trim();
4950
+ if (override === "graphql") {
4951
+ if (!isHttpUrl(trimmedSpec)) {
4952
+ throw new Error("--source-type graphql requires the GraphQL endpoint URL to introspect " + `(e.g. --spec https://api.example.com/graphql), got: ${spec}`);
4953
+ }
4954
+ return { sourceType: "graphql", sourceUrl: trimmedSpec };
4955
+ }
4904
4956
  if (override === "url" || !override && isHttpUrl(trimmedSpec)) {
4905
4957
  if (!isHttpUrl(trimmedSpec)) {
4906
4958
  throw new Error(`--source-type url requires an http(s) --spec URL, got: ${spec}`);
@@ -4923,15 +4975,12 @@ async function readSpecSource(spec, override, options = {}) {
4923
4975
  if (!content.trim()) {
4924
4976
  throw new Error("The spec is empty — nothing to ingest.");
4925
4977
  }
4926
- let sourceType;
4927
- if (override === "openapi" || override === "graphql") {
4928
- sourceType = override;
4929
- } else if (/\.(graphql|gql)$/i.test(pathHint) || looksLikeGraphql(content)) {
4930
- sourceType = "graphql";
4931
- } else {
4932
- sourceType = "openapi";
4978
+ if (override !== "openapi") {
4979
+ if (/\.(graphql|gql)$/i.test(pathHint) || looksLikeGraphql(content)) {
4980
+ throw new Error("This looks like a GraphQL schema. Import GraphQL from its endpoint URL: " + "`--spec https://api.example.com/graphql --source-type graphql` " + "(SDL-file import is not supported).");
4981
+ }
4933
4982
  }
4934
- return { sourceType, rawSpec: content };
4983
+ return { sourceType: "openapi", rawSpec: content };
4935
4984
  }
4936
4985
  function buildIngestBody(args) {
4937
4986
  const body = {
@@ -4939,10 +4988,10 @@ function buildIngestBody(args) {
4939
4988
  projectId: args.projectId,
4940
4989
  sourceType: args.payload.sourceType
4941
4990
  };
4942
- if (args.payload.sourceType === "url") {
4943
- body["sourceUrl"] = args.payload.sourceUrl;
4944
- } else {
4991
+ if (args.payload.sourceType === "openapi") {
4945
4992
  body["rawSpec"] = args.payload.rawSpec;
4993
+ } else {
4994
+ body["sourceUrl"] = args.payload.sourceUrl;
4946
4995
  }
4947
4996
  return body;
4948
4997
  }
@@ -4977,12 +5026,13 @@ function validateSourceType(value) {
4977
5026
  return v;
4978
5027
  }
4979
5028
  function registerServerSpecCommands(servers) {
4980
- servers.command("ingest").description("Create a server from an OpenAPI/GraphQL spec (file, URL, or @- stdin). Wraps POST /api/v1/server/ingest.").requiredOption("--project <projectId>", "Project to create the server in").requiredOption("--spec <file|url>", "Spec source: a local file, an http(s) URL (fetched server-side), or @- for stdin").option("--org <organizationId>", "Organization ID").option("--source-type <type>", `Override spec classification: ${SOURCE_TYPES.join(" | ")} (auto-detected by default)`).addHelpText("after", [
5029
+ servers.command("ingest").description("Create a server from an OpenAPI spec (file, URL, or @- stdin) or a GraphQL endpoint. Wraps POST /api/v1/server/ingest.").requiredOption("--project <projectId>", "Project to create the server in").requiredOption("--spec <file|url>", "OpenAPI spec (local file, http(s) URL, or @- for stdin), or — with --source-type graphql — the GraphQL endpoint URL to introspect").option("--org <organizationId>", "Organization ID").option("--source-type <type>", `Override spec classification: ${SOURCE_TYPES.join(" | ")} (OpenAPI auto-detected; graphql requires an endpoint URL)`).addHelpText("after", [
4981
5030
  "",
4982
5031
  "Examples:",
4983
5032
  " $ mcp servers ingest --project proj_123 --spec ./openapi.yaml",
4984
5033
  " $ mcp servers ingest --project proj_123 --spec https://api.example.com/openapi.json",
4985
- " $ cat schema.graphql | mcp servers ingest --project proj_123 --spec @-"
5034
+ " # GraphQL: introspect a live endpoint (POST) — pass the URL, not SDL.",
5035
+ " $ mcp servers ingest --project proj_123 --spec https://api.example.com/graphql --source-type graphql"
4986
5036
  ].join(`
4987
5037
  `)).action(runAction(async (opts) => {
4988
5038
  const orgId = await resolveOrgId(opts.org);
@@ -8766,6 +8816,210 @@ function registerApiKeyCommands(program2) {
8766
8816
  }));
8767
8817
  }
8768
8818
 
8819
+ // src/commands/domains-namespaces.ts
8820
+ var NAMESPACE_STATUS_LABELS = {
8821
+ pending_verification: "verifying",
8822
+ verified: "verified",
8823
+ removed: "removed"
8824
+ };
8825
+ function printNamespaceRecords(namespace) {
8826
+ printInfo(`Create these DNS records once for ${namespace.parentDomain} — every future subdomain is then covered:`);
8827
+ const rows = [
8828
+ {
8829
+ type: "TXT",
8830
+ name: namespace.verificationTxt.name,
8831
+ value: namespace.verificationTxt.value
8832
+ }
8833
+ ];
8834
+ if (namespace.wildcardCname) {
8835
+ rows.push({
8836
+ type: "CNAME",
8837
+ name: namespace.wildcardCname.name,
8838
+ value: namespace.wildcardCname.value
8839
+ });
8840
+ }
8841
+ printList(rows, [
8842
+ { key: "type", label: "Type", width: 6 },
8843
+ { key: "name", label: "Name", width: 44 },
8844
+ { key: "value", label: "Value", width: 44 }
8845
+ ], rows);
8846
+ }
8847
+ function registerDomainNamespaceCommands(domains) {
8848
+ const namespaces = domains.command("namespaces").description("Verify a parent domain once, then attach subdomains to any server with no further DNS work");
8849
+ namespaces.command("list").description("List the domain namespaces in an organization").option("--org <orgId>", "Organization id (defaults to your default org)").action(runAction(async (opts) => {
8850
+ const organizationId = await resolveOrgId(opts.org);
8851
+ const data = await api.get("/api/v1/domains/namespaces", { organization_id: organizationId });
8852
+ printList(data.namespaces.map((namespace) => ({
8853
+ parent: namespace.parentDomain,
8854
+ status: NAMESPACE_STATUS_LABELS[namespace.status],
8855
+ wildcard: namespace.wildcardConfigured ? "configured" : "—",
8856
+ verified: namespace.verifiedAt ? formatDate(namespace.verifiedAt) : "—"
8857
+ })), [
8858
+ { key: "parent", label: "Parent domain", width: 32 },
8859
+ { key: "status", label: "Status", width: 12 },
8860
+ { key: "wildcard", label: "Wildcard", width: 12 },
8861
+ { key: "verified", label: "Verified", width: 22 }
8862
+ ], data);
8863
+ }));
8864
+ namespaces.command("add <parentDomain>").description("Register a domain namespace and print its one-time DNS records (admin role required)").option("--org <orgId>", "Organization id (defaults to your default org)").action(runAction(async (parentDomain, opts) => {
8865
+ const organizationId = await resolveOrgId(opts.org);
8866
+ const namespace = await api.post("/api/v1/domains/namespaces", { organizationId, parentDomain });
8867
+ if (isJsonMode()) {
8868
+ printJson(namespace);
8869
+ return;
8870
+ }
8871
+ printSuccess(`${namespace.parentDomain} namespace added.`);
8872
+ printNamespaceRecords(namespace);
8873
+ printInfo("Verification runs automatically — or run `mcp domains namespaces verify <parentDomain>` to check now.");
8874
+ }));
8875
+ namespaces.command("verify <parentDomain>").description("Check the namespace verification records now").option("--org <orgId>", "Organization id (defaults to your default org)").action(runAction(async (parentDomain, opts) => {
8876
+ const organizationId = await resolveOrgId(opts.org);
8877
+ const data = await api.post("/api/v1/domains/namespaces/verify", {
8878
+ organizationId,
8879
+ parentDomain
8880
+ });
8881
+ if (isJsonMode()) {
8882
+ printJson(data);
8883
+ return;
8884
+ }
8885
+ if (data.verified) {
8886
+ printSuccess(`${data.namespace.parentDomain} is verified. Attach subdomains with \`mcp domains add <label>.${data.namespace.parentDomain} --server <id>\`.`);
8887
+ } else {
8888
+ printInfo(data.error ?? "Not verified yet — DNS may still be propagating.");
8889
+ printNamespaceRecords(data.namespace);
8890
+ }
8891
+ }));
8892
+ namespaces.command("remove <parentDomain>").description("Remove a domain namespace — existing attached hostnames keep working (admin role required)").option("--org <orgId>", "Organization id (defaults to your default org)").action(runAction(async (parentDomain, opts) => {
8893
+ const organizationId = await resolveOrgId(opts.org);
8894
+ const data = await api.delete("/api/v1/domains/namespaces", { organizationId, parentDomain });
8895
+ if (isJsonMode()) {
8896
+ printJson(data);
8897
+ return;
8898
+ }
8899
+ printSuccess(`${parentDomain} namespace removed. Hostnames already attached under it keep serving.`);
8900
+ }));
8901
+ }
8902
+
8903
+ // src/commands/domains.ts
8904
+ var STATUS_LABELS = {
8905
+ pending_verification: "verifying dns",
8906
+ pending_cert: "issuing certificate",
8907
+ active: "active",
8908
+ failed: "attention needed",
8909
+ removed: "removed"
8910
+ };
8911
+ function printDnsRecords(domain, cnameTarget) {
8912
+ printInfo(`Create these DNS records for ${domain.hostname}:`);
8913
+ const rows = [
8914
+ { type: "CNAME", name: domain.hostname, value: cnameTarget }
8915
+ ];
8916
+ if (domain.ownershipVerification) {
8917
+ rows.push({
8918
+ type: "TXT",
8919
+ name: domain.ownershipVerification.name,
8920
+ value: domain.ownershipVerification.value
8921
+ });
8922
+ }
8923
+ for (const record of domain.certificateValidationRecords) {
8924
+ rows.push({ type: "TXT", name: record.name, value: record.value });
8925
+ }
8926
+ printList(rows, [
8927
+ { key: "type", label: "Type", width: 6 },
8928
+ { key: "name", label: "Name", width: 44 },
8929
+ { key: "value", label: "Value", width: 44 }
8930
+ ], rows);
8931
+ }
8932
+ function printDomainStatus(domain) {
8933
+ printKeyValue({
8934
+ hostname: domain.hostname,
8935
+ status: STATUS_LABELS[domain.status],
8936
+ url: domain.status === "active" ? `https://${domain.hostname}/mcp` : "—",
8937
+ "last checked": domain.lastSyncedAt ? formatDate(domain.lastSyncedAt) : "—",
8938
+ ...domain.lastError ? { error: domain.lastError } : {}
8939
+ });
8940
+ }
8941
+ function registerDomainCommands(program2) {
8942
+ const domains = program2.command("domains").description("Serve servers from custom domains you own (Pro+)");
8943
+ registerDomainNamespaceCommands(domains);
8944
+ domains.command("list").description("List the custom domains in an organization").option("--org <orgId>", "Organization id (defaults to your default org)").action(runAction(async (opts) => {
8945
+ const organizationId = await resolveOrgId(opts.org);
8946
+ const data = await api.get("/api/v1/domains", {
8947
+ organization_id: organizationId
8948
+ });
8949
+ if (!data.allowed) {
8950
+ printInfo("Bring your own domain requires the Pro plan or higher for this organization.");
8951
+ if (isJsonMode())
8952
+ printJson(data);
8953
+ return;
8954
+ }
8955
+ printList(data.domains.map((domain) => ({
8956
+ hostname: domain.hostname,
8957
+ status: STATUS_LABELS[domain.status],
8958
+ server: domain.serverId,
8959
+ added: formatDate(domain.createdAt)
8960
+ })), [
8961
+ { key: "hostname", label: "Hostname", width: 36 },
8962
+ { key: "status", label: "Status", width: 20 },
8963
+ { key: "server", label: "Server", width: 34 },
8964
+ { key: "added", label: "Added", width: 22 }
8965
+ ], data);
8966
+ if (!isJsonMode()) {
8967
+ printInfo(`${data.used} of ${data.limit === null ? "unlimited" : data.limit} domains used.`);
8968
+ }
8969
+ }));
8970
+ domains.command("add <hostname>").description("Attach a custom domain to a server (admin role required)").requiredOption("--server <serverId>", "Server id to attach the domain to").option("--org <orgId>", "Organization id (defaults to your default org)").action(runAction(async (hostname2, opts) => {
8971
+ const organizationId = await resolveOrgId(opts.org);
8972
+ const data = await api.post("/api/v1/domains", {
8973
+ organizationId,
8974
+ serverId: opts.server,
8975
+ hostname: hostname2
8976
+ });
8977
+ if (isJsonMode()) {
8978
+ printJson(data);
8979
+ return;
8980
+ }
8981
+ printSuccess(`${data.domain.hostname} added.`);
8982
+ printDnsRecords(data.domain, data.cnameTarget);
8983
+ printInfo("Verification runs automatically once the records propagate — or run `mcp domains status <hostname>` to check now.");
8984
+ }));
8985
+ domains.command("status <hostname>").description("Recheck a custom domain against Cloudflare and show status").option("--org <orgId>", "Organization id (defaults to your default org)").action(runAction(async (hostname2, opts) => {
8986
+ const organizationId = await resolveOrgId(opts.org);
8987
+ const domain = await api.post("/api/v1/domains/recheck", { organizationId, hostname: hostname2 });
8988
+ if (isJsonMode()) {
8989
+ printJson(domain);
8990
+ return;
8991
+ }
8992
+ printDomainStatus(domain);
8993
+ if (domain.status === "active") {
8994
+ printSuccess("Domain is verified and serving traffic.");
8995
+ }
8996
+ }));
8997
+ domains.command("remove <hostname>").description("Remove a custom domain — traffic on it stops immediately (admin role required)").option("--org <orgId>", "Organization id (defaults to your default org)").action(runAction(async (hostname2, opts) => {
8998
+ const organizationId = await resolveOrgId(opts.org);
8999
+ const data = await api.delete("/api/v1/domains", {
9000
+ organizationId,
9001
+ hostname: hostname2
9002
+ });
9003
+ if (isJsonMode()) {
9004
+ printJson(data);
9005
+ return;
9006
+ }
9007
+ printSuccess(`${hostname2} removed. The platform URL is unaffected.`);
9008
+ }));
9009
+ domains.command("transfer <hostname>").description("Move a custom domain to another server without certificate churn (admin role required)").requiredOption("--server <serverId>", "Target server id").option("--org <orgId>", "Organization id (defaults to your default org)").action(runAction(async (hostname2, opts) => {
9010
+ const organizationId = await resolveOrgId(opts.org);
9011
+ const domain = await api.post("/api/v1/domains/transfer", { organizationId, serverId: opts.server, hostname: hostname2 });
9012
+ if (isJsonMode()) {
9013
+ printJson(domain);
9014
+ return;
9015
+ }
9016
+ printSuccess(`${domain.hostname} now serves server ${domain.serverId}.`);
9017
+ if (domain.status === "active") {
9018
+ printInfo("Routing re-pointed — no DNS or certificate changes needed.");
9019
+ }
9020
+ }));
9021
+ }
9022
+
8769
9023
  // src/commands/config.ts
8770
9024
  import { homedir as homedir2 } from "node:os";
8771
9025
  import { join as join11 } from "node:path";
@@ -29822,7 +30076,7 @@ async function pickServerName(override) {
29822
30076
  return name;
29823
30077
  }
29824
30078
  function registerInitCommand(program2) {
29825
- program2.command("init").description("Guided onboarding: pick org → pick/create project → create a server (empty skeleton, or generated from an OpenAPI/GraphQL spec via --from-spec)").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Reuse an existing project instead of creating one").option("--name <serverName>", "Name for the new server (skeleton path only)").option("--from-spec <file|url>", "Build the server from an OpenAPI/GraphQL spec: a local file, http(s) URL (fetched server-side), or @- for stdin").option("--source-type <type>", `Override spec classification: ${SOURCE_TYPES.join(" | ")} (auto-detected by default)`).option("--deploy", "After importing the spec, deploy the server").option("--wait", "With --deploy, follow events until the deploy is terminal").option("--wait-timeout <seconds>", "Max seconds to wait for a terminal deploy state (default 300)", "300").addHelpText("after", [
30079
+ program2.command("init").description("Guided onboarding: pick org → pick/create project → create a server (empty skeleton, or generated from an OpenAPI/GraphQL spec via --from-spec)").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Reuse an existing project instead of creating one").option("--name <serverName>", "Name for the new server (skeleton path only)").option("--from-spec <file|url>", "Build from an OpenAPI spec (local file, http(s) URL, or @- for stdin), or — with --source-type graphql — a GraphQL endpoint URL to introspect").option("--source-type <type>", `Override spec classification: ${SOURCE_TYPES.join(" | ")} (OpenAPI auto-detected; graphql requires an endpoint URL)`).option("--deploy", "After importing the spec, deploy the server").option("--wait", "With --deploy, follow events until the deploy is terminal").option("--wait-timeout <seconds>", "Max seconds to wait for a terminal deploy state (default 300)", "300").addHelpText("after", [
29826
30080
  "",
29827
30081
  "Two modes:",
29828
30082
  " Skeleton (default): resolve org → pick/create project → create an empty server.",
@@ -35523,6 +35777,7 @@ function createProgram() {
35523
35777
  registerToolCommands(program2);
35524
35778
  registerSkillCommands(program2);
35525
35779
  registerApiKeyCommands(program2);
35780
+ registerDomainCommands(program2);
35526
35781
  registerDeploymentCommands(program2);
35527
35782
  registerOrgCommands(program2);
35528
35783
  registerUsageCommands(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcpcloud/cli",
3
- "version": "0.7.0-next-20260628020545",
3
+ "version": "0.9.1-next-20260709201721",
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": {