@mcpcloud/cli 0.9.1 → 0.10.0-next-20260715004701

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 +350 -142
  2. package/dist/index.js +500 -198
  3. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -1972,6 +1972,9 @@ function configFile() {
1972
1972
  return join(configDir(), "config.json");
1973
1973
  }
1974
1974
  var DEFAULT_PROFILE_NAME = "default";
1975
+ var DEFAULT_BASE_URL = "https://majestic-lobster-858.convex.site";
1976
+ var PRODUCTION_BASE_URL = DEFAULT_BASE_URL;
1977
+ var DEFAULT_APP_URL = "https://mcpcloud.sh";
1975
1978
 
1976
1979
  class MissingApiKeyError extends Error {
1977
1980
  constructor() {
@@ -2100,6 +2103,14 @@ function getActiveProfileName() {
2100
2103
  const first = Object.keys(persisted.profiles ?? {})[0];
2101
2104
  return first ?? saved;
2102
2105
  }
2106
+ function resolveActiveProfile() {
2107
+ if (profileOverride)
2108
+ return { name: profileOverride, source: "flag" };
2109
+ const fromEnv = process.env["MCPCLOUD_PROFILE"]?.trim();
2110
+ if (fromEnv)
2111
+ return { name: fromEnv, source: "env" };
2112
+ return { name: getActiveProfileName(), source: "config" };
2113
+ }
2103
2114
  function getActiveProfile(persisted, name) {
2104
2115
  return persisted.profiles?.[name] ?? {};
2105
2116
  }
@@ -2121,11 +2132,26 @@ function writeConfig(config) {
2121
2132
  function getApiKey() {
2122
2133
  return process.env["MCPCLOUD_API_KEY"] ?? readConfig().apiKey;
2123
2134
  }
2135
+ function resolveBaseUrl() {
2136
+ if (baseUrlOverride)
2137
+ return { url: baseUrlOverride, source: "flag" };
2138
+ const env = process.env["MCPCLOUD_BASE_URL"]?.trim();
2139
+ if (env)
2140
+ return { url: env, source: "env" };
2141
+ const profile = readConfig().baseUrl;
2142
+ if (profile)
2143
+ return { url: profile, source: "profile" };
2144
+ return { url: DEFAULT_BASE_URL, source: "default" };
2145
+ }
2124
2146
  function getBaseUrl() {
2125
- const url = baseUrlOverride ?? process.env["MCPCLOUD_BASE_URL"] ?? readConfig().baseUrl;
2126
- if (!url)
2127
- throw new MissingBaseUrlError;
2128
- return url;
2147
+ return resolveBaseUrl().url;
2148
+ }
2149
+ function isProductionBaseUrl(url) {
2150
+ try {
2151
+ return new URL(url).host === new URL(PRODUCTION_BASE_URL).host;
2152
+ } catch {
2153
+ return false;
2154
+ }
2129
2155
  }
2130
2156
  function requireApiKey() {
2131
2157
  const key = getApiKey();
@@ -2152,10 +2178,10 @@ function getAppUrl() {
2152
2178
  if (host === "localhost" || host === "127.0.0.1")
2153
2179
  return "http://localhost:4000";
2154
2180
  if (host.endsWith(".convex.site") || host.endsWith(".convex.cloud"))
2155
- return "https://mcpcloud.sh";
2181
+ return DEFAULT_APP_URL;
2156
2182
  } catch {}
2157
2183
  }
2158
- return "https://mcpcloud.sh";
2184
+ return DEFAULT_APP_URL;
2159
2185
  }
2160
2186
  function listProfiles() {
2161
2187
  const persisted = readPersistedConfig();
@@ -2386,12 +2412,20 @@ function getRetryDelayMs(err) {
2386
2412
  }
2387
2413
  return 250 + Math.floor(Math.random() * 250);
2388
2414
  }
2415
+ var GATEWAY_STATUS_MESSAGES = {
2416
+ 502: "The gateway returned no response (HTTP 502 Bad Gateway). A long-running request — such as a first deploy that is still generating and uploading the bundle — can exceed the gateway timeout even though the operation keeps running server-side. Retry in a few seconds; for a deploy, re-run with `--wait` or follow progress with `mcp deployments logs <id> --follow`.",
2417
+ 503: "The service is temporarily unavailable (HTTP 503 Service Unavailable). This is usually transient — retry in a few seconds.",
2418
+ 504: "The gateway timed out waiting for the server (HTTP 504 Gateway Timeout). A long-running request may still be processing server-side. Retry in a few seconds; for a deploy, re-run with `--wait` or follow progress with `mcp deployments logs <id> --follow`."
2419
+ };
2420
+ function synthesizeStatusMessage(status, statusText) {
2421
+ return GATEWAY_STATUS_MESSAGES[status] ?? (statusText || `HTTP ${status}`);
2422
+ }
2389
2423
  async function parseErrorBody(res) {
2390
2424
  const headerRequestId = res.headers.get("x-request-id") ?? undefined;
2391
2425
  const retryAfter = parseRetryAfter(res.headers.get("retry-after"));
2392
2426
  const fallback = {
2393
2427
  code: `http_${res.status}`,
2394
- message: res.statusText || `HTTP ${res.status}`
2428
+ message: synthesizeStatusMessage(res.status, res.statusText)
2395
2429
  };
2396
2430
  if (headerRequestId)
2397
2431
  fallback.requestId = headerRequestId;
@@ -2834,7 +2868,12 @@ var ERROR_REMEDIATION = {
2834
2868
  invalid_status: "Invalid --status. Check the command help for accepted values.",
2835
2869
  deployment_not_found: "No such deployment in this org. Verify with `mcp servers list`.",
2836
2870
  server_not_found: "No such server in this org. Verify with `mcp servers list`.",
2871
+ artifact_not_found: "No such server/artifact in that project. If you switched servers, stale .mcpcloud/state.json can cause this — run `mcp dev init` to re-select.",
2872
+ bundle_not_found: "The server has no generated bundle yet. Run `mcp servers generate <id>` or deploy once, then retry.",
2837
2873
  missing_deployment_id: "Pass --deployment <id> or look it up with `mcp servers list`.",
2874
+ http_502: "The API gateway dropped the request. Retry once; if it persists, check https://mcpcloud.sh/status.",
2875
+ http_503: "Service temporarily unavailable. Honor Retry-After if present, then retry.",
2876
+ http_504: "The request timed out at the gateway. Retry; long operations may still be running — check with the matching `get` command.",
2838
2877
  rate_limited: "Rate limit hit. Honor the Retry-After header and retry.",
2839
2878
  rate_limit_exceeded: "Rate limit hit. Wait the Retry-After interval before retrying.",
2840
2879
  quota_exceeded: "Plan quota exceeded. Check usage in the dashboard or upgrade.",
@@ -2868,6 +2907,14 @@ function getRemediation(code) {
2868
2907
  return;
2869
2908
  return ERROR_REMEDIATION[code];
2870
2909
  }
2910
+ var DOCS_ORIGIN = "https://mcpcloud.sh";
2911
+ function absolutizeDocsUrl(url) {
2912
+ if (!url)
2913
+ return url;
2914
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(url))
2915
+ return url;
2916
+ return `${DOCS_ORIGIN}${url.startsWith("/") ? "" : "/"}${url}`;
2917
+ }
2871
2918
  function describeError(err) {
2872
2919
  if (err instanceof McpCloudApiError) {
2873
2920
  const out = {
@@ -2877,8 +2924,11 @@ function describeError(err) {
2877
2924
  };
2878
2925
  if (err.error.requestId)
2879
2926
  out.requestId = err.error.requestId;
2880
- if (err.error.docsUrl)
2881
- out.docsUrl = err.error.docsUrl;
2927
+ if (err.error.docsUrl) {
2928
+ const docsUrl = absolutizeDocsUrl(err.error.docsUrl);
2929
+ if (docsUrl)
2930
+ out.docsUrl = docsUrl;
2931
+ }
2882
2932
  if (err.error.details)
2883
2933
  out.details = err.error.details;
2884
2934
  return out;
@@ -3371,6 +3421,16 @@ function previewKey(key) {
3371
3421
  return `${key.slice(0, 4)}…`;
3372
3422
  return `${key.slice(0, 8)}…${key.slice(-4)}`;
3373
3423
  }
3424
+ function profileSourceLabel(source) {
3425
+ switch (source) {
3426
+ case "flag":
3427
+ return "--profile flag";
3428
+ case "env":
3429
+ return "MCPCLOUD_PROFILE";
3430
+ case "config":
3431
+ return "config default";
3432
+ }
3433
+ }
3374
3434
  function registerAuthCommands(program2) {
3375
3435
  program2.command("login").description("Sign in via browser (default) or paste an API key").option("--key <apiKey>", "Save this key directly (skips browser + prompt)").option("--paste", "Skip the browser flow and prompt for an API key instead").option("--no-browser", "Alias for --paste").option("--port <number>", "Loopback port to receive the callback (default: auto)").option("--app-url <url>", "Override the dashboard origin for this run (default: from MCPCLOUD_APP_URL or config)").option("--timeout <seconds>", "How long to wait for the browser flow (default 300)").addHelpText("after", [
3376
3436
  "",
@@ -3510,12 +3570,17 @@ function registerAuthCommands(program2) {
3510
3570
  `)).action(runAction(async () => {
3511
3571
  const me = await api.get("/api/v1/me");
3512
3572
  const orgs = await api.get("/api/v1/organizations");
3573
+ const profile = resolveActiveProfile();
3574
+ const baseUrl = getBaseUrl();
3575
+ const isProd = isProductionBaseUrl(baseUrl);
3513
3576
  if (isJsonMode()) {
3514
3577
  printJson({
3515
3578
  user: me.user,
3516
3579
  authMethod: me.authMethod,
3517
3580
  apiKey: me.apiKey,
3518
- baseUrl: getBaseUrl(),
3581
+ profile: { name: profile.name, source: profile.source },
3582
+ baseUrl,
3583
+ isProductionBaseUrl: isProd,
3519
3584
  defaultOrganizationId: me.defaultOrganizationId,
3520
3585
  organizations: orgs.organizations
3521
3586
  });
@@ -3526,7 +3591,8 @@ function registerAuthCommands(program2) {
3526
3591
  user: me.user.email ?? me.user.name ?? me.user.id,
3527
3592
  "auth method": me.authMethod ?? "—",
3528
3593
  "api key": apiKeyDisplay,
3529
- "base url": getBaseUrl(),
3594
+ profile: `${profile.name} (${profileSourceLabel(profile.source)})`,
3595
+ "base url": isProd ? `${baseUrl} (production)` : baseUrl,
3530
3596
  "default org": me.defaultOrganizationId ?? "—",
3531
3597
  "org count": orgs.organizations.length
3532
3598
  });
@@ -3875,13 +3941,24 @@ var TERMINAL_STAGES = new Set([
3875
3941
  "terminated"
3876
3942
  ]);
3877
3943
  var TERMINAL_LEVELS = new Set(["success", "failure", "error"]);
3944
+ var TERMINAL_DEPLOYMENT_STATUSES = new Set([
3945
+ "active",
3946
+ "failed",
3947
+ "paused",
3948
+ "undeployed"
3949
+ ]);
3950
+ function isTerminalDeploymentStatus(status) {
3951
+ return status !== undefined && TERMINAL_DEPLOYMENT_STATUSES.has(status);
3952
+ }
3878
3953
  function isTerminalEvent(event) {
3879
3954
  if (TERMINAL_STAGES.has(event.stage))
3880
3955
  return true;
3881
3956
  if (TERMINAL_LEVELS.has(event.level))
3882
3957
  return true;
3883
3958
  const m = event.message.toLowerCase();
3884
- if (m.includes("deployment active"))
3959
+ if (m.includes("deployment is active") || m.includes("deployment active"))
3960
+ return true;
3961
+ if (m.includes("deployment failed"))
3885
3962
  return true;
3886
3963
  if (m.includes("deploy succeeded") || m.includes("deploy failed"))
3887
3964
  return true;
@@ -4513,24 +4590,28 @@ function registerServerLifecycleCommands(servers) {
4513
4590
  let terminalEvent = null;
4514
4591
  let waitDone = !opts.wait;
4515
4592
  if (opts.wait) {
4516
- const totalTimeoutMs = Math.max(10, Number.parseInt(opts.waitTimeout, 10) || 300) * 1000;
4517
- if (!isJsonMode()) {
4518
- printStep(`Following events for ${c.bold(dep.deploymentId)} (timeout ${Math.round(totalTimeoutMs / 1000)}s)…`);
4519
- }
4520
- const result = await followDeploymentEvents({
4521
- organizationId: orgId,
4522
- deploymentId: dep.deploymentId,
4523
- totalTimeoutMs,
4524
- onEvent: (event) => {
4525
- if (!isJsonMode()) {
4526
- printInfo(formatEventLine2(event));
4593
+ if (isTerminalDeploymentStatus(dep.status)) {
4594
+ waitDone = true;
4595
+ } else {
4596
+ const totalTimeoutMs = Math.max(10, Number.parseInt(opts.waitTimeout, 10) || 300) * 1000;
4597
+ if (!isJsonMode()) {
4598
+ printStep(`Following events for ${c.bold(dep.deploymentId)} (timeout ${Math.round(totalTimeoutMs / 1000)}s)…`);
4599
+ }
4600
+ const result = await followDeploymentEvents({
4601
+ organizationId: orgId,
4602
+ deploymentId: dep.deploymentId,
4603
+ totalTimeoutMs,
4604
+ onEvent: (event) => {
4605
+ if (!isJsonMode()) {
4606
+ printInfo(formatEventLine2(event));
4607
+ }
4527
4608
  }
4609
+ });
4610
+ terminalEvent = result.terminalEvent;
4611
+ waitDone = result.done;
4612
+ if (!waitDone && !isJsonMode()) {
4613
+ 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\`.`);
4528
4614
  }
4529
- });
4530
- terminalEvent = result.terminalEvent;
4531
- waitDone = result.done;
4532
- if (!waitDone && !isJsonMode()) {
4533
- printError(`Deploy did not reach a terminal state within ${Math.round(totalTimeoutMs / 1000)}s. Run \`mcp --json servers test-runs get ${dep.deploymentId}\` (or the dashboard) to keep watching.`);
4534
4615
  }
4535
4616
  }
4536
4617
  if (isJsonMode()) {
@@ -4542,7 +4623,7 @@ function registerServerLifecycleCommands(servers) {
4542
4623
  printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
4543
4624
  printKeyValue({
4544
4625
  id: dep.deploymentId,
4545
- url: dep.deploymentUrl,
4626
+ ...dep.deploymentUrl ? { url: dep.deploymentUrl } : {},
4546
4627
  target: dep.target,
4547
4628
  "access mode": dep.accessMode,
4548
4629
  status: dep.status,
@@ -4552,6 +4633,9 @@ function registerServerLifecycleCommands(servers) {
4552
4633
  "terminal at": formatDate(terminalEvent.timestamp)
4553
4634
  } : {}
4554
4635
  });
4636
+ if (!opts.wait && dep.status === "queued") {
4637
+ printInfo(`Deploy is running in the background — follow with \`mcp deployments get ${dep.deploymentId}\` or rerun with --wait.`);
4638
+ }
4555
4639
  if (opts.wait && !waitDone)
4556
4640
  throw new CliExitError(1);
4557
4641
  }));
@@ -4643,23 +4727,27 @@ function registerServerLifecycleCommands(servers) {
4643
4727
  let terminalEvent = null;
4644
4728
  let waitDone = !opts.wait;
4645
4729
  if (opts.wait) {
4646
- const totalTimeoutMs = Math.max(10, Number.parseInt(opts.waitTimeout, 10) || 300) * 1000;
4647
- if (!isJsonMode()) {
4648
- printStep(`Following events for ${c.bold(dep.deploymentId)}…`);
4649
- }
4650
- const r = await followDeploymentEvents({
4651
- organizationId: orgId,
4652
- deploymentId: dep.deploymentId,
4653
- totalTimeoutMs,
4654
- onEvent: (event) => {
4655
- if (!isJsonMode())
4656
- printInfo(formatEventLine2(event));
4730
+ if (isTerminalDeploymentStatus(dep.status)) {
4731
+ waitDone = true;
4732
+ } else {
4733
+ const totalTimeoutMs = Math.max(10, Number.parseInt(opts.waitTimeout, 10) || 300) * 1000;
4734
+ if (!isJsonMode()) {
4735
+ printStep(`Following events for ${c.bold(dep.deploymentId)}…`);
4736
+ }
4737
+ const r = await followDeploymentEvents({
4738
+ organizationId: orgId,
4739
+ deploymentId: dep.deploymentId,
4740
+ totalTimeoutMs,
4741
+ onEvent: (event) => {
4742
+ if (!isJsonMode())
4743
+ printInfo(formatEventLine2(event));
4744
+ }
4745
+ });
4746
+ terminalEvent = r.terminalEvent;
4747
+ waitDone = r.done;
4748
+ if (!waitDone && !isJsonMode()) {
4749
+ 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\`.`);
4657
4750
  }
4658
- });
4659
- terminalEvent = r.terminalEvent;
4660
- waitDone = r.done;
4661
- if (!waitDone && !isJsonMode()) {
4662
- printError(`Deploy did not reach a terminal state within ${Math.round(totalTimeoutMs / 1000)}s.`);
4663
4751
  }
4664
4752
  }
4665
4753
  if (isJsonMode()) {
@@ -4678,7 +4766,7 @@ function registerServerLifecycleCommands(servers) {
4678
4766
  printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
4679
4767
  printKeyValue({
4680
4768
  id: dep.deploymentId,
4681
- url: dep.deploymentUrl,
4769
+ ...dep.deploymentUrl ? { url: dep.deploymentUrl } : {},
4682
4770
  target: dep.target,
4683
4771
  "access mode": dep.accessMode,
4684
4772
  status: dep.status,
@@ -4688,6 +4776,9 @@ function registerServerLifecycleCommands(servers) {
4688
4776
  "terminal at": formatDate(terminalEvent.timestamp)
4689
4777
  } : {}
4690
4778
  });
4779
+ if (!opts.wait && dep.status === "queued") {
4780
+ printInfo(`Deploy is running in the background — follow with \`mcp deployments get ${dep.deploymentId}\` or rerun with --wait.`);
4781
+ }
4691
4782
  if (opts.wait && !waitDone)
4692
4783
  throw new CliExitError(1);
4693
4784
  }));
@@ -5533,6 +5624,9 @@ import { dirname as dirname5, join as join8 } from "node:path";
5533
5624
  // src/lib/dev/state.ts
5534
5625
  import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
5535
5626
  import { join as join6, resolve as resolve5 } from "node:path";
5627
+ function cachedStateMatchesTarget(cached2, target) {
5628
+ return cached2 !== null && cached2.serverId === target.serverId && cached2.organizationId === target.organizationId;
5629
+ }
5536
5630
  function devRoot(cwd) {
5537
5631
  return join6(cwd, ".mcpcloud");
5538
5632
  }
@@ -7441,7 +7535,7 @@ function diffLine2(label, applied, suggested) {
7441
7535
  // src/commands/tools.ts
7442
7536
  function registerToolCommands(program2) {
7443
7537
  const tools = program2.command("tools").description("Inspect and edit tools on a project or server");
7444
- tools.command("list").description("List tools for a project (or pass --server to look up its project automatically)").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Project ID to list tools for").option("--server <serverId>", "Server ID the CLI fetches the server's projectId before listing tools").action(runAction(async (opts) => {
7538
+ tools.command("list").description("List tools for a project, or scope to a single server with --server").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "List every tool in this project").option("--server <serverId>", "List only the tools that belong to this server (the CLI resolves its project, fetches the project tools, then filters to this server)").action(runAction(async (opts) => {
7445
7539
  const orgId = await resolveOrgId(opts.org);
7446
7540
  if (!opts.project && !opts.server) {
7447
7541
  throw new Error("Pass either --project <projectId> or --server <serverId>.");
@@ -7458,10 +7552,11 @@ function registerToolCommands(program2) {
7458
7552
  organizationId: orgId,
7459
7553
  projectId
7460
7554
  });
7461
- printList(data.tools.map((t) => ({
7555
+ const rows = opts.server ? data.tools.filter((t) => t.serverId === opts.server) : data.tools;
7556
+ printList(rows.map((t) => ({
7462
7557
  name: t.name,
7463
- method: t.method,
7464
- path: t.path,
7558
+ method: t.endpoint?.method ?? t.method ?? "—",
7559
+ path: t.endpoint?.path ?? t.path ?? "—",
7465
7560
  enrichment: t.enrichmentStatus ?? "—",
7466
7561
  description: t.description.slice(0, 60) + (t.description.length > 60 ? "…" : "")
7467
7562
  })), [
@@ -7470,7 +7565,7 @@ function registerToolCommands(program2) {
7470
7565
  { key: "path", label: "Path", width: 32 },
7471
7566
  { key: "enrichment", label: "Enrichment", width: 12 },
7472
7567
  { key: "description", label: "Description", width: 60 }
7473
- ], data);
7568
+ ], opts.server ? { ...data, tools: rows } : data);
7474
7569
  }));
7475
7570
  registerToolsShowCommand(tools);
7476
7571
  registerToolsDiffCommand(tools);
@@ -9035,7 +9130,7 @@ function previewKey2(key) {
9035
9130
  }
9036
9131
  function registerConfigCommands(program2) {
9037
9132
  const config = program2.command("config").description("Manage CLI configuration in ~/.mcpcloud/config.json");
9038
- config.command("show").description("Print the active profile (API key is redacted)").action(runAction(() => {
9133
+ const showActiveProfile = runAction(() => {
9039
9134
  const cfg = readConfig();
9040
9135
  const profile2 = getActiveProfileName();
9041
9136
  const safe = {
@@ -9052,14 +9147,16 @@ function registerConfigCommands(program2) {
9052
9147
  printKeyValue({
9053
9148
  path: safe.path,
9054
9149
  profile: safe.profile,
9055
- "base url": safe.baseUrl ?? "—",
9150
+ "base url": safe.baseUrl ?? "— (built-in default)",
9056
9151
  "app url": cfg.appUrl ?? "— (auto-derived)",
9057
9152
  "default org": safe.defaultOrganizationId ?? "—",
9058
9153
  "api key": safe.apiKeyPreview ?? "—",
9059
9154
  editor: cfg.editorCommand ?? "code",
9060
9155
  "editor open": cfg.editorOpenPreference ?? "ask"
9061
9156
  });
9062
- }));
9157
+ });
9158
+ config.command("show").description("Print the active profile (API key is redacted)").action(showActiveProfile);
9159
+ config.command("current").description("Alias for `config show` — print the active profile").action(showActiveProfile);
9063
9160
  config.command("set-url <url>").description("Save the API base URL to ~/.mcpcloud/config.json").action(runAction((url) => {
9064
9161
  try {
9065
9162
  new URL(url);
@@ -9629,7 +9726,7 @@ async function runDevReplay(opts) {
9629
9726
  printInfo("No matching records in .mcpcloud/inspector/. Use --from / --since / --filter, or run something first.");
9630
9727
  return;
9631
9728
  }
9632
- const baseUrl = opts.url ?? resolveBaseUrl(cwd);
9729
+ const baseUrl = opts.url ?? resolveBaseUrl2(cwd);
9633
9730
  if (!opts.dryRun && !baseUrl) {
9634
9731
  printError("No running mcp dev session in this directory. Start `mcp dev`, or pass --url <baseUrl>.");
9635
9732
  throw new CliExitError(1);
@@ -9707,7 +9804,7 @@ function parseLimit(raw) {
9707
9804
  }
9708
9805
  return n;
9709
9806
  }
9710
- function resolveBaseUrl(cwd) {
9807
+ function resolveBaseUrl2(cwd) {
9711
9808
  const sessions = listSessions().filter((s) => s.cwd === cwd);
9712
9809
  if (sessions.length === 0)
9713
9810
  return null;
@@ -11220,8 +11317,13 @@ async function fetchBundle(args) {
11220
11317
  } catch {
11221
11318
  body = null;
11222
11319
  }
11223
- const message = body?.error?.message ?? `Bundle download failed (${res.status}).`;
11224
- throw new Error(message);
11320
+ const envelope = body?.error;
11321
+ const requestId = envelope?.requestId ?? res.headers?.get?.("x-request-id") ?? null;
11322
+ throw new McpCloudApiError(res.status, {
11323
+ code: envelope?.code ?? `http_${res.status}`,
11324
+ message: envelope?.message ?? `Bundle download failed (${res.status}).`,
11325
+ ...requestId ? { requestId } : {}
11326
+ });
11225
11327
  }
11226
11328
  const text = await res.text();
11227
11329
  const parsed = JSON.parse(text);
@@ -25781,7 +25883,7 @@ if (!root) throw new Error("Missing #root mount node");
25781
25883
  clientExports.createRoot(root).render(
25782
25884
  /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.StrictMode, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(App, {}) })
25783
25885
  );</script>
25784
- <style rel="stylesheet" crossorigin>/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
25886
+ <style rel="stylesheet" crossorigin>/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
25785
25887
  @layer properties {
25786
25888
  @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
25787
25889
  *, :before, :after, ::backdrop {
@@ -26116,7 +26218,7 @@ clientExports.createRoot(root).render(
26116
26218
  }
26117
26219
 
26118
26220
  .top-0 {
26119
- top: calc(var(--spacing) * 0);
26221
+ top: 0;
26120
26222
  }
26121
26223
 
26122
26224
  .z-10 {
@@ -26124,7 +26226,7 @@ clientExports.createRoot(root).render(
26124
26226
  }
26125
26227
 
26126
26228
  .m-0 {
26127
- margin: calc(var(--spacing) * 0);
26229
+ margin: 0;
26128
26230
  }
26129
26231
 
26130
26232
  .mx-auto {
@@ -26136,7 +26238,7 @@ clientExports.createRoot(root).render(
26136
26238
  }
26137
26239
 
26138
26240
  .mt-1 {
26139
- margin-top: calc(var(--spacing) * 1);
26241
+ margin-top: var(--spacing);
26140
26242
  }
26141
26243
 
26142
26244
  .mt-3 {
@@ -26144,7 +26246,7 @@ clientExports.createRoot(root).render(
26144
26246
  }
26145
26247
 
26146
26248
  .mb-1 {
26147
- margin-bottom: calc(var(--spacing) * 1);
26249
+ margin-bottom: var(--spacing);
26148
26250
  }
26149
26251
 
26150
26252
  .mb-2\\.5 {
@@ -26152,7 +26254,7 @@ clientExports.createRoot(root).render(
26152
26254
  }
26153
26255
 
26154
26256
  .ml-1 {
26155
- margin-left: calc(var(--spacing) * 1);
26257
+ margin-left: var(--spacing);
26156
26258
  }
26157
26259
 
26158
26260
  .ml-2 {
@@ -26201,7 +26303,7 @@ clientExports.createRoot(root).render(
26201
26303
  }
26202
26304
 
26203
26305
  .min-w-0 {
26204
- min-width: calc(var(--spacing) * 0);
26306
+ min-width: 0;
26205
26307
  }
26206
26308
 
26207
26309
  .min-w-\\[240px\\] {
@@ -26253,7 +26355,7 @@ clientExports.createRoot(root).render(
26253
26355
  }
26254
26356
 
26255
26357
  .gap-1 {
26256
- gap: calc(var(--spacing) * 1);
26358
+ gap: var(--spacing);
26257
26359
  }
26258
26360
 
26259
26361
  .gap-1\\.5 {
@@ -26329,7 +26431,7 @@ clientExports.createRoot(root).render(
26329
26431
  }
26330
26432
 
26331
26433
  .p-0 {
26332
- padding: calc(var(--spacing) * 0);
26434
+ padding: 0;
26333
26435
  }
26334
26436
 
26335
26437
  .p-2 {
@@ -26341,7 +26443,7 @@ clientExports.createRoot(root).render(
26341
26443
  }
26342
26444
 
26343
26445
  .px-1 {
26344
- padding-inline: calc(var(--spacing) * 1);
26446
+ padding-inline: var(--spacing);
26345
26447
  }
26346
26448
 
26347
26449
  .px-1\\.5 {
@@ -26369,7 +26471,7 @@ clientExports.createRoot(root).render(
26369
26471
  }
26370
26472
 
26371
26473
  .py-1 {
26372
- padding-block: calc(var(--spacing) * 1);
26474
+ padding-block: var(--spacing);
26373
26475
  }
26374
26476
 
26375
26477
  .py-2 {
@@ -27495,8 +27597,12 @@ async function prepareDev(opts) {
27495
27597
  const env = envFile(cwd);
27496
27598
  const backups = backupsDir(cwd);
27497
27599
  const cached2 = readState(cwd);
27498
- let projectId = cached2?.projectId;
27499
- let serverName = cached2?.serverName;
27600
+ const cacheIsForTarget = cachedStateMatchesTarget(cached2, {
27601
+ serverId: opts.serverId,
27602
+ organizationId: opts.organizationId
27603
+ });
27604
+ let projectId = cacheIsForTarget ? cached2.projectId : undefined;
27605
+ let serverName = cacheIsForTarget ? cached2.serverName : undefined;
27500
27606
  let gitNative = false;
27501
27607
  let repoDir;
27502
27608
  const gitLink = opts.offline ? null : await resolveServerGitLink({
@@ -28463,7 +28569,8 @@ async function runDev(opts) {
28463
28569
  const cwd = process.cwd();
28464
28570
  const cached2 = readState(cwd);
28465
28571
  let serverId = opts.server ?? cached2?.serverId;
28466
- let organizationId = opts.org ?? cached2?.organizationId ?? undefined;
28572
+ const serverCameFromCache = !opts.server && Boolean(cached2?.serverId);
28573
+ let organizationId = opts.org ?? (serverCameFromCache ? cached2?.organizationId ?? undefined : undefined);
28467
28574
  if (!opts.server && cached2?.serverId && canRunInteractive()) {
28468
28575
  const choice = await promptResumeOrSwitch({
28469
28576
  serverId: cached2.serverId,
@@ -29575,19 +29682,15 @@ function checkActiveProfile() {
29575
29682
  ms: Date.now() - t0
29576
29683
  };
29577
29684
  }
29685
+ var BASE_URL_SOURCE_LABEL = {
29686
+ flag: "--base-url flag",
29687
+ env: "env var",
29688
+ profile: "profile",
29689
+ default: "built-in default"
29690
+ };
29578
29691
  function checkBaseUrl() {
29579
29692
  const t0 = Date.now();
29580
- const fromOverride = process.env["MCPCLOUD_BASE_URL"];
29581
- const fromConfig = readConfig().baseUrl;
29582
- const url = fromOverride ?? fromConfig;
29583
- if (!url) {
29584
- return {
29585
- name: "Base URL",
29586
- status: "fail",
29587
- detail: "Not configured. Run `mcp config set-url <url>` or set MCPCLOUD_BASE_URL.",
29588
- ms: Date.now() - t0
29589
- };
29590
- }
29693
+ const { url, source } = resolveBaseUrl();
29591
29694
  try {
29592
29695
  new URL(url);
29593
29696
  } catch {
@@ -29598,11 +29701,11 @@ function checkBaseUrl() {
29598
29701
  ms: Date.now() - t0
29599
29702
  };
29600
29703
  }
29601
- const source = fromOverride ? "env var" : "profile";
29704
+ const prodMarker = isProductionBaseUrl(url) ? " (production)" : "";
29602
29705
  return {
29603
29706
  name: "Base URL",
29604
29707
  status: "pass",
29605
- detail: `${url} (from ${source})`,
29708
+ detail: `${url}${prodMarker} (from ${BASE_URL_SOURCE_LABEL[source]})`,
29606
29709
  ms: Date.now() - t0
29607
29710
  };
29608
29711
  }
@@ -30006,7 +30109,7 @@ function registerHelpCommand(program2) {
30006
30109
  });
30007
30110
  }
30008
30111
 
30009
- // src/commands/init.ts
30112
+ // src/commands/init-shared.ts
30010
30113
  import { createInterface as createInterface2 } from "node:readline";
30011
30114
  function prompt2(question) {
30012
30115
  if (isNonInteractive()) {
@@ -30022,7 +30125,10 @@ function prompt2(question) {
30022
30125
  }
30023
30126
  async function pickProject(orgId, override) {
30024
30127
  if (override) {
30025
- const data = await api.get("/api/v1/project", { organizationId: orgId, projectId: override });
30128
+ const data = await api.get("/api/v1/project", {
30129
+ organizationId: orgId,
30130
+ projectId: override
30131
+ });
30026
30132
  return { project: data.project, created: false };
30027
30133
  }
30028
30134
  const list = await api.get("/api/v1/projects", { organizationId: orgId, limit: "25" });
@@ -30075,67 +30181,15 @@ async function pickServerName(override) {
30075
30181
  throw new Error("Server name must not be empty.");
30076
30182
  return name;
30077
30183
  }
30078
- function registerInitCommand(program2) {
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", [
30080
- "",
30081
- "Two modes:",
30082
- " Skeleton (default): resolve org → pick/create project → create an empty server.",
30083
- " Spec (--from-spec): resolve org → pick/create project → ingest the spec into a",
30084
- " generated server, then optionally --deploy [--wait].",
30085
- "",
30086
- "Examples:",
30087
- " $ mcp init",
30088
- ' $ mcp init --org org_acme --project proj_123 --name "Stripe MCP"',
30089
- " $ mcp init --from-spec ./openapi.yaml --project proj_123",
30090
- " $ mcp init --from-spec https://api.example.com/openapi.json --project proj_123 --deploy --wait"
30091
- ].join(`
30092
- `)).action(runAction(async (opts) => {
30093
- const orgId = await resolveOrgId(opts.org);
30094
- if (opts.fromSpec) {
30095
- await runSpecInit({ orgId, opts });
30096
- return;
30097
- }
30098
- if (!isJsonMode()) {
30099
- const orgs = await api.get("/api/v1/organizations");
30100
- const org = orgs.organizations.find((o) => o.id === orgId);
30101
- printStep(`Using organization ${c.bold(org?.name ?? orgId)} ${c.dim("(" + orgId + ")")}`);
30102
- }
30103
- const { project, created: projectCreated } = await pickProject(orgId, opts.project);
30104
- if (!isJsonMode()) {
30105
- if (projectCreated) {
30106
- printSuccess(`Project ${c.bold(project.name)} created (${project.id}).`);
30107
- } else {
30108
- printStep(`Using project ${c.bold(project.name)} ${c.dim("(" + project.id + ")")}`);
30109
- }
30110
- }
30111
- const serverName = await pickServerName(opts.name);
30112
- if (!isJsonMode()) {
30113
- printStep(`Creating server skeleton ${c.bold(serverName)}…`);
30114
- }
30115
- const serverData = await api.post("/api/v1/servers", {
30116
- organizationId: orgId,
30117
- projectId: project.id,
30118
- name: serverName
30119
- });
30120
- const server = serverData.server;
30121
- if (isJsonMode()) {
30122
- printJson({
30123
- organizationId: orgId,
30124
- project,
30125
- projectCreated,
30126
- server
30127
- });
30128
- return;
30129
- }
30130
- printSuccess(`Server ${c.bold(server.id)} ready.`);
30131
- printInfo("");
30132
- printInfo("Next steps:");
30133
- printInfo(` 1. Import an API spec: \`mcp servers ingest --project ${project.id} --spec ./openapi.yaml\``);
30134
- printInfo(` ${c.dim("(or re-run `mcp init --from-spec ./openapi.yaml` for the guided flow)")}`);
30135
- printInfo(` 2. Run \`mcp dev --spec ./openapi.yaml\` to iterate locally.`);
30136
- printInfo(` 3. \`mcp servers deploy ${server.id} --wait\` to ship it.`);
30137
- }));
30184
+ function reportProject(project, created) {
30185
+ if (isJsonMode())
30186
+ return;
30187
+ if (created) {
30188
+ printSuccess(`Project ${c.bold(project.name)} created (${project.id}).`);
30189
+ }
30138
30190
  }
30191
+
30192
+ // src/commands/init-spec.ts
30139
30193
  function validateSourceType2(value) {
30140
30194
  if (value === undefined)
30141
30195
  return;
@@ -30145,6 +30199,52 @@ function validateSourceType2(value) {
30145
30199
  }
30146
30200
  return v2;
30147
30201
  }
30202
+ async function deployIngestedServer(args) {
30203
+ return await api.post("/api/v1/server/deploy", {
30204
+ organizationId: args.orgId,
30205
+ projectId: args.projectId,
30206
+ serverId: args.serverId,
30207
+ target: "workersDev",
30208
+ accessMode: "public",
30209
+ runtimeConfig: {
30210
+ upstreamBaseUrl: null,
30211
+ upstreamApiKey: null,
30212
+ upstreamApiKeyHeader: null,
30213
+ upstreamApiKeyPrefix: null,
30214
+ upstreamCustomHeaders: null,
30215
+ additionalPlainTextBindings: null,
30216
+ additionalSecretBindings: null
30217
+ }
30218
+ });
30219
+ }
30220
+ async function runDeployStep(args) {
30221
+ const { orgId, projectId, serverId, opts } = args;
30222
+ if (!isJsonMode())
30223
+ printStep(`Deploying ${c.bold(serverId)}…`);
30224
+ const deployData = await deployIngestedServer({ orgId, projectId, serverId });
30225
+ const dep = deployData.deployment;
30226
+ let waitDone = !opts.wait;
30227
+ let terminalEvent = null;
30228
+ if (opts.wait) {
30229
+ if (isTerminalDeploymentStatus(dep.status)) {
30230
+ waitDone = true;
30231
+ } else {
30232
+ const totalTimeoutMs = Math.max(10, Number.parseInt(opts.waitTimeout, 10) || 300) * 1000;
30233
+ const followed = await followDeploymentEvents({
30234
+ organizationId: orgId,
30235
+ deploymentId: dep.deploymentId,
30236
+ totalTimeoutMs,
30237
+ onEvent: (event) => {
30238
+ if (!isJsonMode())
30239
+ printInfo(formatEventLine(event));
30240
+ }
30241
+ });
30242
+ waitDone = followed.done;
30243
+ terminalEvent = followed.terminalEvent;
30244
+ }
30245
+ }
30246
+ return { deployment: dep, waitDone, terminalEvent };
30247
+ }
30148
30248
  async function runSpecInit(args) {
30149
30249
  const { orgId, opts } = args;
30150
30250
  if (opts.name && !isJsonMode()) {
@@ -30153,46 +30253,36 @@ async function runSpecInit(args) {
30153
30253
  const sourceType = validateSourceType2(opts.sourceType);
30154
30254
  const payload = await readSpecSource(opts.fromSpec, sourceType);
30155
30255
  const { project, created: projectCreated } = await pickProject(orgId, opts.project);
30156
- if (!isJsonMode() && projectCreated) {
30157
- printSuccess(`Project ${c.bold(project.name)} created (${project.id}).`);
30158
- }
30256
+ reportProject(project, projectCreated);
30159
30257
  if (!isJsonMode()) {
30160
30258
  printStep(`Importing ${c.bold(payload.sourceType)} spec into ${c.bold(project.name)}…`);
30161
30259
  }
30162
- const ingest = await api.post("/api/v1/server/ingest", buildIngestBody({
30163
- organizationId: orgId,
30164
- projectId: project.id,
30165
- payload
30166
- }));
30260
+ const ingest = await api.post("/api/v1/server/ingest", buildIngestBody({ organizationId: orgId, projectId: project.id, payload }));
30167
30261
  const server = ingest.server;
30168
30262
  if (!isJsonMode()) {
30169
30263
  printSuccess(`Server ${c.bold(server.id)} created from spec.`);
30170
30264
  }
30171
30265
  let deployResult = null;
30172
30266
  if (opts.deploy) {
30173
- if (!isJsonMode())
30174
- printStep(`Deploying ${c.bold(server.id)}…`);
30175
- const deployData = await api.post("/api/v1/server/deploy", {
30176
- organizationId: orgId,
30177
- serverId: server.id
30178
- });
30179
- let waitDone = !opts.wait;
30180
- let terminalEvent = null;
30181
- if (opts.wait) {
30182
- const totalTimeoutMs = Math.max(10, Number.parseInt(opts.waitTimeout, 10) || 300) * 1000;
30183
- const followed = await followDeploymentEvents({
30184
- organizationId: orgId,
30185
- deploymentId: deployData.deployment.deploymentId,
30186
- totalTimeoutMs,
30187
- onEvent: (event) => {
30188
- if (!isJsonMode())
30189
- printInfo(formatEventLine(event));
30190
- }
30267
+ try {
30268
+ deployResult = await runDeployStep({
30269
+ orgId,
30270
+ projectId: project.id,
30271
+ serverId: server.id,
30272
+ opts
30191
30273
  });
30192
- waitDone = followed.done;
30193
- terminalEvent = followed.terminalEvent;
30274
+ } catch (err) {
30275
+ reportDeployFailure({
30276
+ serverId: server.id,
30277
+ projectId: project.id,
30278
+ project,
30279
+ projectCreated,
30280
+ apiSource: ingest.apiSource,
30281
+ server,
30282
+ err
30283
+ });
30284
+ throw err;
30194
30285
  }
30195
- deployResult = { deployment: deployData.deployment, waitDone, terminalEvent };
30196
30286
  }
30197
30287
  if (isJsonMode()) {
30198
30288
  printJson({
@@ -30208,21 +30298,7 @@ async function runSpecInit(args) {
30208
30298
  return;
30209
30299
  }
30210
30300
  if (deployResult) {
30211
- const dep = deployResult.deployment;
30212
- printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
30213
- printKeyValue({
30214
- "server id": server.id,
30215
- "deployment id": dep.deploymentId,
30216
- url: dep.deploymentUrl ?? "—",
30217
- target: dep.target,
30218
- "access mode": dep.accessMode,
30219
- status: dep.status
30220
- });
30221
- if (opts.wait && !deployResult.waitDone) {
30222
- printError(`Deploy did not reach a terminal state in time. Watch with \`mcp deployments logs ${dep.deploymentId} --follow\`.`);
30223
- throw new CliExitError(1);
30224
- }
30225
- printStep(`Try it: \`mcp invoke ${server.id} <tool>\`.`);
30301
+ reportDeploySuccess({ serverId: server.id, result: deployResult, opts });
30226
30302
  return;
30227
30303
  }
30228
30304
  printInfo("");
@@ -30230,6 +30306,111 @@ async function runSpecInit(args) {
30230
30306
  printStep(`Generate the bundle: \`mcp servers generate ${server.id} --out ./build\``);
30231
30307
  printStep(`Deploy it: \`mcp servers deploy ${server.id} --wait\``);
30232
30308
  }
30309
+ function resumeDeployCommand(serverId, projectId) {
30310
+ return `mcp servers deploy ${serverId} --project ${projectId} --wait`;
30311
+ }
30312
+ function reportDeployFailure(args) {
30313
+ const resume = resumeDeployCommand(args.serverId, args.projectId);
30314
+ if (isJsonMode()) {
30315
+ const message = args.err instanceof Error ? args.err.message : String(args.err);
30316
+ printJson({
30317
+ project: args.project,
30318
+ projectCreated: args.projectCreated,
30319
+ apiSource: args.apiSource,
30320
+ server: args.server,
30321
+ deploy: null,
30322
+ deployFailed: true,
30323
+ deployError: { message },
30324
+ resumeCommand: resume
30325
+ });
30326
+ return;
30327
+ }
30328
+ printError(`Server ${c.bold(args.serverId)} was created, but the deploy step failed.`);
30329
+ printInfo("");
30330
+ printInfo("Created so far:");
30331
+ printKeyValue({ "server id": args.serverId, "project id": args.projectId });
30332
+ printInfo("");
30333
+ printInfo(`Resume the deploy once the cause is cleared:`);
30334
+ printStep(resume);
30335
+ printInfo("");
30336
+ }
30337
+ function reportDeploySuccess(args) {
30338
+ const { serverId, result, opts } = args;
30339
+ const dep = result.deployment;
30340
+ printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
30341
+ printKeyValue({
30342
+ "server id": serverId,
30343
+ "deployment id": dep.deploymentId,
30344
+ url: dep.deploymentUrl ?? "—",
30345
+ target: dep.target,
30346
+ "access mode": dep.accessMode,
30347
+ status: dep.status
30348
+ });
30349
+ if (opts.wait && !result.waitDone) {
30350
+ printError(`Deploy did not reach a terminal state in time. Check \`mcp deployments get ${dep.deploymentId}\` and \`mcp deployments health ${dep.deploymentId}\`, or tail \`mcp deployments logs ${dep.deploymentId} --follow\`.`);
30351
+ throw new CliExitError(1);
30352
+ }
30353
+ printStep(`Try it: \`mcp invoke ${serverId} <tool>\`.`);
30354
+ }
30355
+
30356
+ // src/commands/init.ts
30357
+ function registerInitCommand(program2) {
30358
+ 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", [
30359
+ "",
30360
+ "Two modes:",
30361
+ " Skeleton (default): resolve org → pick/create project → create an empty server.",
30362
+ " Spec (--from-spec): resolve org → pick/create project → ingest the spec into a",
30363
+ " generated server, then optionally --deploy [--wait].",
30364
+ "",
30365
+ "Examples:",
30366
+ " $ mcp init",
30367
+ ' $ mcp init --org org_acme --project proj_123 --name "Stripe MCP"',
30368
+ " $ mcp init --from-spec ./openapi.yaml --project proj_123",
30369
+ " $ mcp init --from-spec https://api.example.com/openapi.json --project proj_123 --deploy --wait"
30370
+ ].join(`
30371
+ `)).action(runAction(runInit));
30372
+ }
30373
+ async function runInit(opts) {
30374
+ const orgId = await resolveOrgId(opts.org);
30375
+ if (opts.fromSpec) {
30376
+ await runSpecInit({ orgId, opts });
30377
+ return;
30378
+ }
30379
+ await runSkeletonInit({ orgId, opts });
30380
+ }
30381
+ async function runSkeletonInit(args) {
30382
+ const { orgId, opts } = args;
30383
+ if (!isJsonMode()) {
30384
+ const orgs = await api.get("/api/v1/organizations");
30385
+ const org = orgs.organizations.find((o) => o.id === orgId);
30386
+ printStep(`Using organization ${c.bold(org?.name ?? orgId)} ${c.dim("(" + orgId + ")")}`);
30387
+ }
30388
+ const { project, created: projectCreated } = await pickProject(orgId, opts.project);
30389
+ if (!isJsonMode()) {
30390
+ if (projectCreated) {
30391
+ printSuccess(`Project ${c.bold(project.name)} created (${project.id}).`);
30392
+ } else {
30393
+ printStep(`Using project ${c.bold(project.name)} ${c.dim("(" + project.id + ")")}`);
30394
+ }
30395
+ }
30396
+ const serverName = await pickServerName(opts.name);
30397
+ if (!isJsonMode()) {
30398
+ printStep(`Creating server skeleton ${c.bold(serverName)}…`);
30399
+ }
30400
+ const serverData = await api.post("/api/v1/servers", { organizationId: orgId, projectId: project.id, name: serverName });
30401
+ const server = serverData.server;
30402
+ if (isJsonMode()) {
30403
+ printJson({ organizationId: orgId, project, projectCreated, server });
30404
+ return;
30405
+ }
30406
+ printSuccess(`Server ${c.bold(server.id)} ready.`);
30407
+ printInfo("");
30408
+ printInfo("Next steps:");
30409
+ printInfo(` 1. Import an API spec: \`mcp servers ingest --project ${project.id} --spec ./openapi.yaml\``);
30410
+ printInfo(` ${c.dim("(or re-run `mcp init --from-spec ./openapi.yaml` for the guided flow)")}`);
30411
+ printInfo(` 2. Run \`mcp dev --spec ./openapi.yaml\` to iterate locally.`);
30412
+ printInfo(` 3. \`mcp servers deploy ${server.id} --wait\` to ship it.`);
30413
+ }
30233
30414
 
30234
30415
  // src/commands/installation.ts
30235
30416
  function registerInstallationCommands(program2) {
@@ -30920,6 +31101,7 @@ function registerUsageCommands(program2) {
30920
31101
  allowed: t.allowedCount,
30921
31102
  "billable invocations": usageData.usage.invocations?.billableCount ?? t.allowedToolCallCount ?? 0,
30922
31103
  "invocation cost": formatMicros(usageData.usage.invocations?.billedMicros),
31104
+ "projected monthly": formatMicros(usageData.usage.invocations?.projectedMonthlyMicros),
30923
31105
  "rate-limited": t.rateLimitedCount,
30924
31106
  unauthorized: t.unauthorizedCount,
30925
31107
  failed: t.failedCount,
@@ -31127,6 +31309,73 @@ function registerOrgCommands(program2) {
31127
31309
  }));
31128
31310
  }
31129
31311
 
31312
+ // src/commands/connections.ts
31313
+ async function readKeyFromStdin() {
31314
+ const chunks = [];
31315
+ for await (const chunk of process.stdin) {
31316
+ chunks.push(Buffer.from(chunk));
31317
+ }
31318
+ return Buffer.concat(chunks).toString("utf8").trim();
31319
+ }
31320
+ function registerConnectionsCommands(program2) {
31321
+ const connections = program2.command("connections").description("Manage your per-server upstream API keys (BYOK) from the terminal").addHelpText("after", [
31322
+ "",
31323
+ "Examples:",
31324
+ " $ mcp connections list",
31325
+ " $ mcp connections add <serverId> --key sk_live_…",
31326
+ ' $ echo "$UPSTREAM_KEY" | mcp connections add <serverId> --key-stdin',
31327
+ " $ mcp connections remove <serverId>"
31328
+ ].join(`
31329
+ `));
31330
+ connections.command("list").description("List your stored upstream keys across servers (hints only)").action(runAction(async () => {
31331
+ const data = await api.get("/api/v1/upstream-key");
31332
+ if (isJsonMode()) {
31333
+ printJson(data);
31334
+ return;
31335
+ }
31336
+ printList(data.keys.map((entry) => ({
31337
+ server: entry.serverName ?? entry.serverId,
31338
+ key: `••••${entry.keyHint}`,
31339
+ status: entry.status,
31340
+ "last used": entry.lastUsedAt ? formatDate(entry.lastUsedAt) : "—",
31341
+ updated: formatDate(entry.updatedAt)
31342
+ })), [
31343
+ { key: "server", label: "Server", width: 32 },
31344
+ { key: "key", label: "Key", width: 10 },
31345
+ { key: "status", label: "Status", width: 10 },
31346
+ { key: "last used", label: "Last used", width: 22 },
31347
+ { key: "updated", label: "Updated", width: 22 }
31348
+ ], data);
31349
+ }));
31350
+ connections.command("add <serverId>").description("Save (or replace) your upstream API key for a server — rotation is just adding again").option("--key <apiKey>", "The upstream API key (visible in shell history)").option("--key-stdin", "Read the key from stdin instead (recommended for CI)").action(runAction(async (serverId, opts) => {
31351
+ const apiKey = opts.keyStdin ? await readKeyFromStdin() : opts.key ?? "";
31352
+ if (!apiKey.trim()) {
31353
+ throw new Error("Provide the key via --key <apiKey> or pipe it with --key-stdin.");
31354
+ }
31355
+ const data = await api.post("/api/v1/upstream-key", { serverId, apiKey });
31356
+ if (isJsonMode()) {
31357
+ printJson(data);
31358
+ return;
31359
+ }
31360
+ printKeyValue({
31361
+ saved: String(data.saved),
31362
+ key: `••••${data.keyHint}`,
31363
+ note: "Stored encrypted; your next tool call uses it immediately."
31364
+ });
31365
+ }));
31366
+ connections.command("remove <serverId>").description("Revoke your stored upstream key for a server").action(runAction(async (serverId) => {
31367
+ const data = await api.delete("/api/v1/upstream-key", { serverId });
31368
+ if (isJsonMode()) {
31369
+ printJson(data);
31370
+ return;
31371
+ }
31372
+ printKeyValue({
31373
+ revoked: String(data.revoked),
31374
+ note: data.revoked ? "Tool calls will prompt for a fresh key on next use." : "No active key was stored for this server."
31375
+ });
31376
+ }));
31377
+ }
31378
+
31130
31379
  // src/lib/plugins.ts
31131
31380
  import { spawn as spawn7 } from "node:child_process";
31132
31381
  import {
@@ -35733,7 +35982,7 @@ function registerUpdateCommand(program2) {
35733
35982
  // src/create-program.ts
35734
35983
  function createProgram() {
35735
35984
  const program2 = new Command;
35736
- program2.name("mcp").description("The official CLI for MCPCloud — manage projects, MCP servers, skills, and API keys from the terminal").version(getCliVersion(), "-v, --version", "Print the CLI version").option("--json", "Output raw JSON (useful for scripting)").option("--base-url <url>", "API base URL for this invocation. Falls back to MCPCLOUD_BASE_URL or `mcp config set-url`.").option("--profile <name>", "Run against a named config profile for this invocation (overrides MCPCLOUD_PROFILE and the saved current profile).").option("--ci", "Force CI mode for this invocation. Auto-detected when CI=true. Disables color, swaps Unicode glyphs for ASCII, refuses interactive prompts, and always surfaces request IDs.").option("--warnings-as-errors", "Exit non-zero if any warning is emitted during the command (CI ergonomics).").option("--non-interactive", "Refuse interactive prompts (fail fast on missing input) without the color/glyph changes of --ci.").option("--quiet", "Suppress success/step/info chrome; data and errors still print. Pairs with exit codes for health/existence gates.").option("--format <mode>", "Output format for list commands: table (default), tsv, jsonl, json.").option("--no-table", "Shorthand for --format tsv. Tab-separated rows for awk/cut/grep pipelines.").option("--field <names>", "Comma-separated list of fields to include in list output (repeatable).", (value, prev) => prev ? [...prev, value] : [value]).option("--filter <key=substring>", "Substring filter for list rows (repeatable). Case-insensitive.", (value, prev) => prev ? [...prev, value] : [value]).option("--idempotency-key <key>", "Stable Idempotency-Key for mutations (1–255 ASCII chars, no whitespace). When omitted, the CLI auto-generates a UUID per invocation so internal retries on transient failures stay safe.").option("--debug", "Print every HTTP request/response with secrets redacted. Equivalent to MCPCLOUD_LOG=debug.").hook("preAction", (thisCommand) => {
35985
+ program2.name("mcp").description("The official CLI for MCPCloud — manage projects, MCP servers, skills, and API keys from the terminal").version(getCliVersion(), "-v, --version", "Print the CLI version").option("--json", "Output raw JSON (useful for scripting)").option("--base-url <url>", "API base URL for this invocation. Falls back to MCPCLOUD_BASE_URL, `mcp config set-url`, then the built-in MCPCloud production default.").option("--profile <name>", "Run against a named config profile for this invocation (overrides MCPCLOUD_PROFILE and the saved current profile).").option("--ci", "Force CI mode for this invocation. Auto-detected when CI=true. Disables color, swaps Unicode glyphs for ASCII, refuses interactive prompts, and always surfaces request IDs.").option("--warnings-as-errors", "Exit non-zero if any warning is emitted during the command (CI ergonomics).").option("--non-interactive", "Refuse interactive prompts (fail fast on missing input) without the color/glyph changes of --ci.").option("--quiet", "Suppress success/step/info chrome; data and errors still print. Pairs with exit codes for health/existence gates.").option("--format <mode>", "Output format for list commands: table (default), tsv, jsonl, json.").option("--no-table", "Shorthand for --format tsv. Tab-separated rows for awk/cut/grep pipelines.").option("--field <names>", "Comma-separated list of fields to include in list output (repeatable).", (value, prev) => prev ? [...prev, value] : [value]).option("--filter <key=substring>", "Substring filter for list rows (repeatable). Case-insensitive.", (value, prev) => prev ? [...prev, value] : [value]).option("--idempotency-key <key>", "Stable Idempotency-Key for mutations (1–255 ASCII chars, no whitespace). When omitted, the CLI auto-generates a UUID per invocation so internal retries on transient failures stay safe.").option("--debug", "Print every HTTP request/response with secrets redacted. Equivalent to MCPCLOUD_LOG=debug.").hook("preAction", (thisCommand) => {
35737
35986
  const opts = thisCommand.optsWithGlobals();
35738
35987
  setJsonMode(Boolean(opts.json));
35739
35988
  setBaseUrlOverride(opts.baseUrl);
@@ -35753,21 +36002,22 @@ function createProgram() {
35753
36002
  }).addHelpText("after", [
35754
36003
  "",
35755
36004
  "Environment variables:",
35756
- " MCPCLOUD_API_KEY API key used for authentication (required)",
35757
- " MCPCLOUD_BASE_URL API base URL (required no default is shipped)",
36005
+ " MCPCLOUD_API_KEY API key used for authentication (or run `mcp login`)",
36006
+ " MCPCLOUD_BASE_URL Override the API base URL (defaults to MCPCloud production)",
35758
36007
  " MCPCLOUD_ORG_ID Default organization ID",
35759
36008
  ' MCPCLOUD_PROFILE Active config profile name (default: "default")',
35760
36009
  " CI When truthy, enables CI mode automatically (no color, ASCII glyphs, no prompts)",
35761
36010
  "",
35762
36011
  "Examples:",
35763
- " $ export MCPCLOUD_BASE_URL=https://your-deployment.example.com",
35764
- " $ mcp config set-url https://your-deployment.example.com # persist it",
35765
- " $ mcp login",
36012
+ " $ mcp login # sign in (production, out of the box)",
35766
36013
  " $ mcp whoami",
35767
36014
  " $ mcp skills connect skill_123 --agent claude-code --apply",
35768
36015
  " $ mcp --json servers list",
35769
36016
  " $ mcp servers list --field id,name --format jsonl",
35770
- " $ mcp servers list --filter status=active --no-table"
36017
+ " $ mcp servers list --filter status=active --no-table",
36018
+ "",
36019
+ "Self-host / staging: point the CLI elsewhere with MCPCLOUD_BASE_URL,",
36020
+ "`mcp config set-url <url>`, or --base-url <url> (each overrides the default)."
35771
36021
  ].join(`
35772
36022
  `));
35773
36023
  registerAuthCommands(program2);
@@ -35781,6 +36031,7 @@ function createProgram() {
35781
36031
  registerDeploymentCommands(program2);
35782
36032
  registerOrgCommands(program2);
35783
36033
  registerUsageCommands(program2);
36034
+ registerConnectionsCommands(program2);
35784
36035
  registerMetricsCommands(program2);
35785
36036
  registerDevCommand(program2);
35786
36037
  registerCompletionCommand(program2);
@@ -35798,8 +36049,59 @@ function createProgram() {
35798
36049
  return program2;
35799
36050
  }
35800
36051
 
36052
+ // src/lib/first-run.ts
36053
+ function shouldShowFirstRunGreeting(input) {
36054
+ if (input.hasCommand)
36055
+ return false;
36056
+ if (!input.isTty)
36057
+ return false;
36058
+ if (input.ciMode)
36059
+ return false;
36060
+ if (input.jsonMode)
36061
+ return false;
36062
+ if (input.authenticated)
36063
+ return false;
36064
+ if (input.hasSavedConfig)
36065
+ return false;
36066
+ return true;
36067
+ }
36068
+ function argvHasCommand(argv) {
36069
+ return argv.slice(2).some((token) => !token.startsWith("-"));
36070
+ }
36071
+ function firstRunGreeting() {
36072
+ return [
36073
+ "Welcome to MCPCloud — turn an API spec into a deployed MCP server.",
36074
+ "",
36075
+ "Get started:",
36076
+ " 1. mcp login Sign in (opens your browser)",
36077
+ " 2. mcp init Scaffold a server from an OpenAPI / GraphQL spec",
36078
+ " 3. mcp servers deploy Ship it to the edge",
36079
+ "",
36080
+ "Run `mcp --help` for the full command reference.",
36081
+ ""
36082
+ ].join(`
36083
+ `);
36084
+ }
36085
+
35801
36086
  // src/index.ts
36087
+ function maybeGreetFirstRun(argv) {
36088
+ const cfg = readConfig();
36089
+ const show = shouldShowFirstRunGreeting({
36090
+ hasCommand: argvHasCommand(argv),
36091
+ isTty: Boolean(process.stdout.isTTY),
36092
+ ciMode: isCiEnv() || argv.includes("--ci"),
36093
+ jsonMode: argv.includes("--json"),
36094
+ authenticated: Boolean(getApiKey()),
36095
+ hasSavedConfig: Boolean(cfg.baseUrl || cfg.apiKey || cfg.defaultOrganizationId)
36096
+ });
36097
+ if (!show)
36098
+ return false;
36099
+ process.stdout.write(firstRunGreeting());
36100
+ return true;
36101
+ }
35802
36102
  async function main() {
36103
+ if (maybeGreetFirstRun(process.argv))
36104
+ return;
35803
36105
  const program2 = createProgram();
35804
36106
  if (process.env["MCPSH_DISABLE_PLUGINS"] !== "1") {
35805
36107
  await loadPlugins(program2);