@mcpcloud/cli 0.22.3 → 0.23.0-next-20260916020438
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.
- package/README.md +7 -0
- package/dist/index.js +345 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -510,6 +510,11 @@ _Generated from the live command tree by `bun run docs:readme` — do not edit b
|
|
|
510
510
|
| `mcp servers env list <server>` | List a server's env bindings (metadata only; values are never returned). |
|
|
511
511
|
| `mcp servers env set <server> <bindingName> [value]` | Create or replace an env binding's value. Schedules a Cloudflare-runtime push when the server has an active deployment. |
|
|
512
512
|
| `mcp servers env unset <server> <bindingName>` | Remove an env binding. Idempotent — succeeds even if the binding is already gone. |
|
|
513
|
+
| `mcp servers environments` | Manage deployment lanes (dev, staging, …) — each a separate worker, hostname, config overlay and credential scope. |
|
|
514
|
+
| `mcp servers environments get <server> <name>` | Show one environment: overlay, hostname, credentials, lane deployment. |
|
|
515
|
+
| `mcp servers environments list <server>` | List a server's environments with their hostname, upstream, access mode and active deployment. |
|
|
516
|
+
| `mcp servers environments remove <server> <name>` | Delete an environment. Requires --confirm "<exact name>". Retire its live deployment first. |
|
|
517
|
+
| `mcp servers environments set <server> <name>` | Create or update an environment. Only the flags you pass are sent; everything else keeps its value. |
|
|
513
518
|
| `mcp servers export <server>` | Export a self-host bundle (Dockerfile + node entry + config) for a server |
|
|
514
519
|
| `mcp servers generate <server>` | Generate the server's TypeScript bundle (the exact code `deploy` ships) and optionally write it to disk — a dry-run for the codegen path. Wraps POST /api/v1/server/generate. |
|
|
515
520
|
| `mcp servers get <server>` | Get details for a single server (accepts id, name, or slug) |
|
|
@@ -523,6 +528,7 @@ _Generated from the live command tree by `bun run docs:readme` — do not edit b
|
|
|
523
528
|
| `mcp servers logs <server>` | Show recent deployment events for a server's latest deployment |
|
|
524
529
|
| `mcp servers oauth-connections <server>` | List OAuth provider connections bound to this server |
|
|
525
530
|
| `mcp servers pause <server>` | Pause the server's latest active deployment (traffic stops; worker is preserved) |
|
|
531
|
+
| `mcp servers promote <server>` | Ship the bytes a lane is serving into another lane. Config, secrets and member credentials stay with the target. |
|
|
526
532
|
| `mcp servers prompts` | Author the prompts a client reads as this server’s workflows (list / set / rm) |
|
|
527
533
|
| `mcp servers prompts list <server>` | List the prompts authored on a server. |
|
|
528
534
|
| `mcp servers prompts rm <server> <name>` | Remove a prompt by name. |
|
|
@@ -534,6 +540,7 @@ _Generated from the live command tree by `bun run docs:readme` — do not edit b
|
|
|
534
540
|
| `mcp servers resources rm <server> <uri>` | Remove a resource by URI. |
|
|
535
541
|
| `mcp servers resources set <server> <uri>` | Create or replace a resource by URI. Re-running with the same URI updates it. |
|
|
536
542
|
| `mcp servers resume <server>` | Resume the server's paused deployment (flips dispatch routing back on; only platformWorker deployments support instant resume) |
|
|
543
|
+
| `mcp servers retire <server>` | Tear down the deployment in one non-production lane. Requires --confirm "<exact environment name>". |
|
|
537
544
|
| `mcp servers score <server>` | Score whether an agent handed this catalog picks the right tool (starts a run, or reads the last one) |
|
|
538
545
|
| `mcp servers shape <server>` | Propose task-shaped tools over this server’s endpoints (starts a run, or reads the last one) |
|
|
539
546
|
| `mcp servers test-runs` | Inspect and trigger server sandbox test runs |
|
package/dist/index.js
CHANGED
|
@@ -6362,6 +6362,329 @@ async function confirmUnset(bindingName, serverId) {
|
|
|
6362
6362
|
return ch === "y";
|
|
6363
6363
|
}
|
|
6364
6364
|
|
|
6365
|
+
// src/lib/validate-choice.ts
|
|
6366
|
+
function validateChoice(name, value, allowed) {
|
|
6367
|
+
const trimmed = value.trim();
|
|
6368
|
+
const match = allowed.find((option) => option.toLowerCase() === trimmed.toLowerCase());
|
|
6369
|
+
if (!match) {
|
|
6370
|
+
throw new Error(`Invalid --${name}: ${value}. Expected one of: ${allowed.join(", ")}.`);
|
|
6371
|
+
}
|
|
6372
|
+
return match;
|
|
6373
|
+
}
|
|
6374
|
+
|
|
6375
|
+
// src/commands/servers-environments-shared.ts
|
|
6376
|
+
var PRODUCTION_ENVIRONMENT = "production";
|
|
6377
|
+
var ENVIRONMENTS_PATH = "/api/v1/server/environments";
|
|
6378
|
+
var PROMOTE_PATH = "/api/v1/server/promote";
|
|
6379
|
+
var RETIRE_PATH = "/api/v1/server/retire";
|
|
6380
|
+
var ENVIRONMENT_ACCESS_MODES = [
|
|
6381
|
+
"privateOrg",
|
|
6382
|
+
"unlisted",
|
|
6383
|
+
"public"
|
|
6384
|
+
];
|
|
6385
|
+
async function resolveServerScope2(serverRef, opts) {
|
|
6386
|
+
const orgId = await resolveOrgId(opts.org);
|
|
6387
|
+
const projectId = await resolveOptionalProjectId(opts.project, orgId);
|
|
6388
|
+
const serverId = await resolveServerId(serverRef, orgId, projectId);
|
|
6389
|
+
return { orgId, projectId, serverId };
|
|
6390
|
+
}
|
|
6391
|
+
var TIER_LABELS = {
|
|
6392
|
+
free: "Free",
|
|
6393
|
+
starter: "Starter",
|
|
6394
|
+
pro: "Pro",
|
|
6395
|
+
max: "Max",
|
|
6396
|
+
enterprise: "Enterprise"
|
|
6397
|
+
};
|
|
6398
|
+
function describeUpgradePath(details) {
|
|
6399
|
+
if (!details)
|
|
6400
|
+
return null;
|
|
6401
|
+
const billing = `${getAppUrl()}/settings/billing`;
|
|
6402
|
+
const tier = details["requiredTierKey"];
|
|
6403
|
+
if (typeof tier === "string" && tier.length > 0) {
|
|
6404
|
+
return `Upgrade to ${TIER_LABELS[tier] ?? tier} or higher: ${billing}`;
|
|
6405
|
+
}
|
|
6406
|
+
if (typeof details["max"] === "number") {
|
|
6407
|
+
return `Remove a lane, or raise the cap by upgrading: ${billing}`;
|
|
6408
|
+
}
|
|
6409
|
+
return null;
|
|
6410
|
+
}
|
|
6411
|
+
function reportEnvironmentApiError(action, err) {
|
|
6412
|
+
if (err instanceof McpCloudApiError) {
|
|
6413
|
+
printError(`${action} failed (HTTP ${err.status}): ${err.error.message}`);
|
|
6414
|
+
const upgrade = describeUpgradePath(err.error.details);
|
|
6415
|
+
if (upgrade)
|
|
6416
|
+
printInfo(` ${c.dim("→")} ${upgrade}`);
|
|
6417
|
+
if (err.error.docsUrl)
|
|
6418
|
+
printInfo(` ${c.dim("docs:")} ${err.error.docsUrl}`);
|
|
6419
|
+
} else {
|
|
6420
|
+
printError(`${action} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
6421
|
+
}
|
|
6422
|
+
throw new CliExitError(1);
|
|
6423
|
+
}
|
|
6424
|
+
var DEPLOY_CHANNELS = ["stable", "beta"];
|
|
6425
|
+
function resolveDeployEnvironment(args) {
|
|
6426
|
+
if (args.env !== undefined && args.channel !== undefined) {
|
|
6427
|
+
throw new Error("--env and --channel are mutually exclusive. --channel beta is the old spelling of --env beta.");
|
|
6428
|
+
}
|
|
6429
|
+
if (args.channel !== undefined) {
|
|
6430
|
+
const channel = validateChoice("channel", args.channel, DEPLOY_CHANNELS);
|
|
6431
|
+
return channel === "beta" ? "beta" : undefined;
|
|
6432
|
+
}
|
|
6433
|
+
if (args.env === undefined)
|
|
6434
|
+
return;
|
|
6435
|
+
const env = args.env.trim();
|
|
6436
|
+
if (env.length === 0) {
|
|
6437
|
+
throw new Error("--env needs an environment name.");
|
|
6438
|
+
}
|
|
6439
|
+
return env.toLowerCase() === PRODUCTION_ENVIRONMENT ? undefined : env;
|
|
6440
|
+
}
|
|
6441
|
+
function describeActiveDeployment(env) {
|
|
6442
|
+
const dep = env.activeDeployment;
|
|
6443
|
+
if (!dep)
|
|
6444
|
+
return "—";
|
|
6445
|
+
return `${dep.status} ${dep.version} (${dep.healthStatus})`;
|
|
6446
|
+
}
|
|
6447
|
+
function describeUpstream(env) {
|
|
6448
|
+
return env.upstreamBaseUrl ?? "(spec default)";
|
|
6449
|
+
}
|
|
6450
|
+
var NO_FALLBACK_SENTENCE = "Credentials never fall back between environments; a lane with no secrets deploys credential-less";
|
|
6451
|
+
function printEnvironmentDetail(env) {
|
|
6452
|
+
printKeyValue({
|
|
6453
|
+
name: env.name,
|
|
6454
|
+
"display name": env.displayName ?? "—",
|
|
6455
|
+
hostname: env.hostname ?? "—",
|
|
6456
|
+
upstream: describeUpstream(env),
|
|
6457
|
+
"key header": env.upstreamApiKeyHeader ?? "—",
|
|
6458
|
+
"key prefix": env.upstreamApiKeyPrefix ?? "—",
|
|
6459
|
+
"custom headers": env.upstreamCustomHeaders ?? "—",
|
|
6460
|
+
"plain bindings": env.additionalPlainTextBindings ?? "—",
|
|
6461
|
+
"access mode": env.defaultAccessMode,
|
|
6462
|
+
credentials: env.credentialState,
|
|
6463
|
+
"git branch": env.boundGitBranch ?? "—",
|
|
6464
|
+
"auto-deploy on push": env.autoDeployOnPush ? "yes" : "no",
|
|
6465
|
+
"active deployment": describeActiveDeployment(env),
|
|
6466
|
+
"runtime version": env.activeDeployment?.runtimeVersion ?? "—",
|
|
6467
|
+
updated: formatDate(env.updatedAt)
|
|
6468
|
+
});
|
|
6469
|
+
}
|
|
6470
|
+
|
|
6471
|
+
// src/commands/servers-environments-write.ts
|
|
6472
|
+
function registerServerEnvironmentWriteCommands(environments) {
|
|
6473
|
+
environments.command("set <server> <name>").description("Create or update an environment. Only the flags you pass are sent; everything else keeps its value.").option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").option("--display-name <label>", "Human label for the lane (UI only)").option("--upstream-base-url <url>", "Upstream API base URL this lane points at (omit to use the spec default)").option("--upstream-api-key-header <header>", "Header the lane injects its upstream key on (default: Authorization)").option("--upstream-api-key-prefix <prefix>", `Prefix for the lane's upstream key value (e.g. "Bearer ")`).option("--access-mode <mode>", `Default access mode for deploys into this lane: ${ENVIRONMENT_ACCESS_MODES.join(" | ")} (default: privateOrg)`).addHelpText("after", [
|
|
6474
|
+
"",
|
|
6475
|
+
`${NO_FALLBACK_SENTENCE}.`,
|
|
6476
|
+
"Set a lane's own upstream key with `mcp servers env set <server> <binding>`;",
|
|
6477
|
+
"until it has one, `environments list` shows Creds = none for that lane.",
|
|
6478
|
+
"",
|
|
6479
|
+
"Names are lowercase letters and digits, 2–16 characters, starting with a",
|
|
6480
|
+
"letter. production, prod, stable, live and main are reserved.",
|
|
6481
|
+
"",
|
|
6482
|
+
"Examples:",
|
|
6483
|
+
" $ mcp servers environments set srv_123 staging \\",
|
|
6484
|
+
" --upstream-base-url https://staging.api.example.com",
|
|
6485
|
+
" $ mcp servers envs set srv_123 dev --access-mode unlisted"
|
|
6486
|
+
].join(`
|
|
6487
|
+
`)).action(runAction(async (serverRef, name, opts) => {
|
|
6488
|
+
const accessMode = opts.accessMode ? validateChoice("access-mode", opts.accessMode, ENVIRONMENT_ACCESS_MODES) : undefined;
|
|
6489
|
+
const { orgId, serverId } = await resolveServerScope2(serverRef, opts);
|
|
6490
|
+
const body = {
|
|
6491
|
+
organizationId: orgId,
|
|
6492
|
+
serverId,
|
|
6493
|
+
name,
|
|
6494
|
+
...opts.displayName !== undefined ? { displayName: opts.displayName } : {},
|
|
6495
|
+
...opts.upstreamBaseUrl !== undefined ? { upstreamBaseUrl: opts.upstreamBaseUrl } : {},
|
|
6496
|
+
...opts.upstreamApiKeyHeader !== undefined ? { upstreamApiKeyHeader: opts.upstreamApiKeyHeader } : {},
|
|
6497
|
+
...opts.upstreamApiKeyPrefix !== undefined ? { upstreamApiKeyPrefix: opts.upstreamApiKeyPrefix } : {},
|
|
6498
|
+
...accessMode ? { defaultAccessMode: accessMode } : {}
|
|
6499
|
+
};
|
|
6500
|
+
let data;
|
|
6501
|
+
try {
|
|
6502
|
+
data = await api.put(ENVIRONMENTS_PATH, body);
|
|
6503
|
+
} catch (err) {
|
|
6504
|
+
reportEnvironmentApiError("Saving the environment", err);
|
|
6505
|
+
}
|
|
6506
|
+
if (isJsonMode()) {
|
|
6507
|
+
printJson({ serverId, environment: data.environment });
|
|
6508
|
+
return;
|
|
6509
|
+
}
|
|
6510
|
+
printSuccess(`Environment ${c.bold(data.environment.name)} saved on ${c.bold(serverId)}.`);
|
|
6511
|
+
printEnvironmentDetail(data.environment);
|
|
6512
|
+
if (data.environment.credentialState === "none") {
|
|
6513
|
+
printInfo(` ${c.dim(`${NO_FALLBACK_SENTENCE}.`)}`);
|
|
6514
|
+
}
|
|
6515
|
+
}));
|
|
6516
|
+
environments.command("remove <server> <name>").description('Delete an environment. Requires --confirm "<exact name>". Retire its live deployment first.').option("--confirm <name>", "The exact environment name, typed to confirm the destructive action").option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").addHelpText("after", [
|
|
6517
|
+
"",
|
|
6518
|
+
"The API refuses while a deployment is live in the lane — run",
|
|
6519
|
+
"`mcp servers retire <server> --env <name> --confirm <name>` first.",
|
|
6520
|
+
"",
|
|
6521
|
+
"Examples:",
|
|
6522
|
+
" $ mcp servers environments remove srv_123 dev --confirm dev"
|
|
6523
|
+
].join(`
|
|
6524
|
+
`)).action(runAction(async (serverRef, name, opts) => {
|
|
6525
|
+
assertConfirmMatches({
|
|
6526
|
+
entityKind: "environment",
|
|
6527
|
+
entityName: name,
|
|
6528
|
+
confirm: opts.confirm
|
|
6529
|
+
});
|
|
6530
|
+
const { orgId, serverId } = await resolveServerScope2(serverRef, opts);
|
|
6531
|
+
let data;
|
|
6532
|
+
try {
|
|
6533
|
+
data = await api.delete(ENVIRONMENTS_PATH, { organizationId: orgId, serverId, name });
|
|
6534
|
+
} catch (err) {
|
|
6535
|
+
reportEnvironmentApiError("Removing the environment", err);
|
|
6536
|
+
}
|
|
6537
|
+
if (isJsonMode()) {
|
|
6538
|
+
printJson({ serverId, name, removed: data.removed });
|
|
6539
|
+
return;
|
|
6540
|
+
}
|
|
6541
|
+
printSuccess(`Removed environment ${c.bold(name)} from ${c.bold(serverId)}.`);
|
|
6542
|
+
}));
|
|
6543
|
+
}
|
|
6544
|
+
|
|
6545
|
+
// src/commands/servers-environments.ts
|
|
6546
|
+
function describeLimit(max, used) {
|
|
6547
|
+
if (max === null)
|
|
6548
|
+
return `${used} environment(s) — no cap on this plan.`;
|
|
6549
|
+
return `${used} of ${max} environment(s) used on this plan.`;
|
|
6550
|
+
}
|
|
6551
|
+
async function loadEnvironments(serverRef, opts, action) {
|
|
6552
|
+
const { orgId, serverId } = await resolveServerScope2(serverRef, opts);
|
|
6553
|
+
try {
|
|
6554
|
+
const data = await api.get(ENVIRONMENTS_PATH, {
|
|
6555
|
+
organizationId: orgId,
|
|
6556
|
+
serverId
|
|
6557
|
+
});
|
|
6558
|
+
return { serverId, data };
|
|
6559
|
+
} catch (err) {
|
|
6560
|
+
reportEnvironmentApiError(action, err);
|
|
6561
|
+
}
|
|
6562
|
+
}
|
|
6563
|
+
function registerServerEnvironmentCommands(servers) {
|
|
6564
|
+
const environments = servers.command("environments").alias("envs").description("Manage deployment lanes (dev, staging, …) — each a separate worker, hostname, config overlay and credential scope.");
|
|
6565
|
+
environments.command("list <server>").description("List a server's environments with their hostname, upstream, access mode and active deployment.").option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").addHelpText("after", [
|
|
6566
|
+
"",
|
|
6567
|
+
"production is implicit and always present — it is what an ordinary",
|
|
6568
|
+
"deploy targets. Every other row is a lane you created.",
|
|
6569
|
+
"",
|
|
6570
|
+
"Examples:",
|
|
6571
|
+
" $ mcp servers environments list srv_123",
|
|
6572
|
+
" $ mcp --json servers envs list srv_123 | jq '.limit'"
|
|
6573
|
+
].join(`
|
|
6574
|
+
`)).action(runAction(async (serverRef, opts) => {
|
|
6575
|
+
const { serverId, data } = await loadEnvironments(serverRef, opts, "Listing environments");
|
|
6576
|
+
printList(data.environments.map((env) => ({
|
|
6577
|
+
name: env.name,
|
|
6578
|
+
hostname: env.hostname ?? "—",
|
|
6579
|
+
upstream: describeUpstream(env),
|
|
6580
|
+
access: env.defaultAccessMode,
|
|
6581
|
+
deployment: describeActiveDeployment(env),
|
|
6582
|
+
credentials: env.credentialState
|
|
6583
|
+
})), [
|
|
6584
|
+
{ key: "name", label: "Name", width: 14 },
|
|
6585
|
+
{ key: "hostname", label: "Hostname", width: 34 },
|
|
6586
|
+
{ key: "upstream", label: "Upstream", width: 30 },
|
|
6587
|
+
{ key: "access", label: "Access", width: 12 },
|
|
6588
|
+
{ key: "deployment", label: "Active Deployment", width: 26 },
|
|
6589
|
+
{ key: "credentials", label: "Creds", width: 8 }
|
|
6590
|
+
], { serverId, environments: data.environments, limit: data.limit });
|
|
6591
|
+
if (!isJsonMode() && getListFormat() === "table") {
|
|
6592
|
+
printInfo(c.dim(` ${describeLimit(data.limit.max, data.limit.used)}`));
|
|
6593
|
+
}
|
|
6594
|
+
}));
|
|
6595
|
+
environments.command("get <server> <name>").description("Show one environment: overlay, hostname, credentials, lane deployment.").option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").action(runAction(async (serverRef, name, opts) => {
|
|
6596
|
+
const { serverId, data } = await loadEnvironments(serverRef, opts, "Reading the environment");
|
|
6597
|
+
const env = data.environments.find((row) => row.name === name);
|
|
6598
|
+
if (!env) {
|
|
6599
|
+
reportEnvironmentApiError("Reading the environment", new Error(`No environment named "${name}" on ${serverId}. Existing: ${data.environments.map((row) => row.name).join(", ") || "(none)"}.`));
|
|
6600
|
+
}
|
|
6601
|
+
if (isJsonMode()) {
|
|
6602
|
+
printJson({ serverId, environment: env });
|
|
6603
|
+
return;
|
|
6604
|
+
}
|
|
6605
|
+
printEnvironmentDetail(env);
|
|
6606
|
+
}));
|
|
6607
|
+
registerServerEnvironmentWriteCommands(environments);
|
|
6608
|
+
}
|
|
6609
|
+
|
|
6610
|
+
// src/commands/servers-environments-promote.ts
|
|
6611
|
+
function registerServerPromoteCommands(servers) {
|
|
6612
|
+
servers.command("promote <server>").description("Ship the bytes a lane is serving into another lane. Config, secrets and member credentials stay with the target.").requiredOption("--from <environment>", "Source environment (lane) name").option("--to <environment>", "Destination environment (lane) name", PRODUCTION_ENVIRONMENT).option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").addHelpText("after", [
|
|
6613
|
+
"",
|
|
6614
|
+
"Promote works between any two lanes — dev to staging, staging to",
|
|
6615
|
+
"production, or production back into a lane to reset it. Only --from",
|
|
6616
|
+
"equal to --to is refused.",
|
|
6617
|
+
"",
|
|
6618
|
+
"Examples:",
|
|
6619
|
+
" $ mcp servers promote srv_123 --from staging",
|
|
6620
|
+
" $ mcp servers promote srv_123 --from dev --to staging"
|
|
6621
|
+
].join(`
|
|
6622
|
+
`)).action(runAction(async (serverRef, opts) => {
|
|
6623
|
+
if (opts.from === opts.to) {
|
|
6624
|
+
reportEnvironmentApiError("Promote", new Error(`--from and --to are both "${opts.from}". Pick two different lanes.`));
|
|
6625
|
+
}
|
|
6626
|
+
const { orgId, serverId } = await resolveServerScope2(serverRef, opts);
|
|
6627
|
+
if (!isJsonMode()) {
|
|
6628
|
+
printStep(`Promoting ${c.bold(opts.from)} → ${c.bold(opts.to)} on ${c.bold(serverId)}…`);
|
|
6629
|
+
}
|
|
6630
|
+
let data;
|
|
6631
|
+
try {
|
|
6632
|
+
data = await api.post(PROMOTE_PATH, {
|
|
6633
|
+
organizationId: orgId,
|
|
6634
|
+
serverId,
|
|
6635
|
+
from: opts.from,
|
|
6636
|
+
to: opts.to
|
|
6637
|
+
});
|
|
6638
|
+
} catch (err) {
|
|
6639
|
+
reportEnvironmentApiError("Promote", err);
|
|
6640
|
+
}
|
|
6641
|
+
if (isJsonMode()) {
|
|
6642
|
+
printJson({
|
|
6643
|
+
serverId,
|
|
6644
|
+
from: opts.from,
|
|
6645
|
+
to: opts.to,
|
|
6646
|
+
deploymentId: data.deploymentId,
|
|
6647
|
+
status: data.status
|
|
6648
|
+
});
|
|
6649
|
+
return;
|
|
6650
|
+
}
|
|
6651
|
+
printSuccess(`Promoted ${c.bold(opts.from)} → ${c.bold(opts.to)}: deployment ${c.bold(data.deploymentId)} (${data.status}).`);
|
|
6652
|
+
printInfo(` ${c.dim(`${opts.to} keeps its own upstream config and credentials — only the bytes moved.`)}`);
|
|
6653
|
+
}));
|
|
6654
|
+
servers.command("retire <server>").description('Tear down the deployment in one non-production lane. Requires --confirm "<exact environment name>".').requiredOption("--env <environment>", "Environment (lane) to retire").option("--confirm <name>", "The exact environment name, typed to confirm the destructive action").option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").addHelpText("after", [
|
|
6655
|
+
"",
|
|
6656
|
+
"The lane row survives — retire removes the running worker and its",
|
|
6657
|
+
"hostname so the lane can be redeployed later. Use",
|
|
6658
|
+
"`mcp servers environments remove` to delete the lane itself.",
|
|
6659
|
+
"",
|
|
6660
|
+
"Examples:",
|
|
6661
|
+
" $ mcp servers retire srv_123 --env staging --confirm staging"
|
|
6662
|
+
].join(`
|
|
6663
|
+
`)).action(runAction(async (serverRef, opts) => {
|
|
6664
|
+
assertConfirmMatches({
|
|
6665
|
+
entityKind: "environment deployment",
|
|
6666
|
+
entityName: opts.env,
|
|
6667
|
+
confirm: opts.confirm
|
|
6668
|
+
});
|
|
6669
|
+
const { orgId, serverId } = await resolveServerScope2(serverRef, opts);
|
|
6670
|
+
let data;
|
|
6671
|
+
try {
|
|
6672
|
+
data = await api.post(RETIRE_PATH, {
|
|
6673
|
+
organizationId: orgId,
|
|
6674
|
+
serverId,
|
|
6675
|
+
environment: opts.env
|
|
6676
|
+
});
|
|
6677
|
+
} catch (err) {
|
|
6678
|
+
reportEnvironmentApiError("Retire", err);
|
|
6679
|
+
}
|
|
6680
|
+
if (isJsonMode()) {
|
|
6681
|
+
printJson({ serverId, environment: opts.env, retired: data.retired });
|
|
6682
|
+
return;
|
|
6683
|
+
}
|
|
6684
|
+
printSuccess(`Retired the ${c.bold(opts.env)} deployment on ${c.bold(serverId)}.`);
|
|
6685
|
+
}));
|
|
6686
|
+
}
|
|
6687
|
+
|
|
6365
6688
|
// src/commands/servers-sandbox-variables.ts
|
|
6366
6689
|
function parseVariableAssignments(pairs) {
|
|
6367
6690
|
return pairs.map((pair) => {
|
|
@@ -7038,6 +7361,16 @@ async function probeServerSurface(args) {
|
|
|
7038
7361
|
reachable: true
|
|
7039
7362
|
};
|
|
7040
7363
|
}
|
|
7364
|
+
function printEnvironmentsBlock(environments) {
|
|
7365
|
+
if (!environments || environments.length === 0)
|
|
7366
|
+
return;
|
|
7367
|
+
printInfo("");
|
|
7368
|
+
printInfo("Environments");
|
|
7369
|
+
for (const env of environments) {
|
|
7370
|
+
printInfo(` ${env.name.padEnd(14)} ${env.hostname ?? "—"}`);
|
|
7371
|
+
}
|
|
7372
|
+
printInfo(" (mcp servers environments list <server> for upstream, access and credentials)");
|
|
7373
|
+
}
|
|
7041
7374
|
function registerServerGetCommand(servers) {
|
|
7042
7375
|
servers.command("get <server>").description("Get details for a single server (accepts id, name, or slug)").option("--org <organizationId>", "Organization ID").option("--probe", "Also call the deployed endpoint and report what it actually serves, flagging anything the configuration promises that the endpoint does not").addHelpText("after", [
|
|
7043
7376
|
"",
|
|
@@ -7088,6 +7421,7 @@ function registerServerGetCommand(servers) {
|
|
|
7088
7421
|
observed: probe.reachable ? describeObservedSurface(probe.observed) : `unreachable — ${probe.message}`
|
|
7089
7422
|
} : {}
|
|
7090
7423
|
});
|
|
7424
|
+
printEnvironmentsBlock(data.environments);
|
|
7091
7425
|
if (probe && !probe.reachable) {
|
|
7092
7426
|
printError(`Could not reach the endpoint: ${probe.message}`);
|
|
7093
7427
|
throw new CliExitError(1);
|
|
@@ -7179,16 +7513,6 @@ function registerServerExportCommands(servers) {
|
|
|
7179
7513
|
}));
|
|
7180
7514
|
}
|
|
7181
7515
|
|
|
7182
|
-
// src/lib/validate-choice.ts
|
|
7183
|
-
function validateChoice(name, value, allowed) {
|
|
7184
|
-
const trimmed = value.trim();
|
|
7185
|
-
const match = allowed.find((option) => option.toLowerCase() === trimmed.toLowerCase());
|
|
7186
|
-
if (!match) {
|
|
7187
|
-
throw new Error(`Invalid --${name}: ${value}. Expected one of: ${allowed.join(", ")}.`);
|
|
7188
|
-
}
|
|
7189
|
-
return match;
|
|
7190
|
-
}
|
|
7191
|
-
|
|
7192
7516
|
// src/commands/servers-lifecycle.ts
|
|
7193
7517
|
import { existsSync as existsSync6 } from "node:fs";
|
|
7194
7518
|
import { isAbsolute as isAbsolute2, resolve as resolve4 } from "node:path";
|
|
@@ -7367,7 +7691,7 @@ async function refreshDeploymentRow(args) {
|
|
|
7367
7691
|
}
|
|
7368
7692
|
}
|
|
7369
7693
|
function registerServerLifecycleCommands(servers) {
|
|
7370
|
-
servers.command("deploy <server>").description("Deploy a server's generated bundle to Cloudflare Workers (closes the spec → deploy loop)").option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").option("--target <target>", `Deploy target: ${DEPLOY_TARGETS.join(" | ")}`, "workersDev").option("--access-mode <mode>", `Access mode: ${ACCESS_MODES.join(" | ")} (default: keep the server's current mode). A server with per-member upstream keys needs privateOrg or unlisted — public has no caller identity.`).option("--upstream-base-url <url>", "Upstream API base URL bound into the worker").option("--upstream-api-key <key>", "Upstream API key (encrypted at rest server-side)").option("--upstream-api-key-header <header>", "Header name to inject the upstream API key on (default: Authorization)").option("--upstream-api-key-prefix <prefix>", 'Prefix for the upstream API key value (e.g. "Bearer ")').option("--wait", "Tail deployment events until the deploy reaches a terminal state").option("--wait-timeout <seconds>", "Maximum seconds to wait for terminal state (default 300)", "300").addHelpText("after", [
|
|
7694
|
+
servers.command("deploy <server>").description("Deploy a server's generated bundle to Cloudflare Workers (closes the spec → deploy loop)").option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").option("--target <target>", `Deploy target: ${DEPLOY_TARGETS.join(" | ")}`, "workersDev").option("--access-mode <mode>", `Access mode: ${ACCESS_MODES.join(" | ")} (default: keep the server's current mode). A server with per-member upstream keys needs privateOrg or unlisted — public has no caller identity.`).option("--env <environment>", "Deploy into a named environment (lane) — its own worker, hostname and credentials. Omit, or pass production, for the default lane.").option("--channel <channel>", "Deprecated alias for --env: beta means --env beta, stable means production. Mutually exclusive with --env.").option("--upstream-base-url <url>", "Upstream API base URL bound into the worker").option("--upstream-api-key <key>", "Upstream API key (encrypted at rest server-side)").option("--upstream-api-key-header <header>", "Header name to inject the upstream API key on (default: Authorization)").option("--upstream-api-key-prefix <prefix>", 'Prefix for the upstream API key value (e.g. "Bearer ")').option("--wait", "Tail deployment events until the deploy reaches a terminal state").option("--wait-timeout <seconds>", "Maximum seconds to wait for terminal state (default 300)", "300").addHelpText("after", [
|
|
7371
7695
|
"",
|
|
7372
7696
|
"Examples:",
|
|
7373
7697
|
" # Pair with `mcp dev --spec` to ship the current spec:",
|
|
@@ -7378,10 +7702,14 @@ function registerServerLifecycleCommands(servers) {
|
|
|
7378
7702
|
" --upstream-api-key $UPSTREAM_TOKEN --wait",
|
|
7379
7703
|
"",
|
|
7380
7704
|
" # Idempotent retry-safe deploy in CI:",
|
|
7381
|
-
" $ mcp --ci --idempotency-key $GITHUB_RUN_ID servers deploy srv_123 --wait"
|
|
7705
|
+
" $ mcp --ci --idempotency-key $GITHUB_RUN_ID servers deploy srv_123 --wait",
|
|
7706
|
+
"",
|
|
7707
|
+
" # Deploy into a lane (its own hostname, config and credentials):",
|
|
7708
|
+
" $ mcp servers deploy srv_123 --env staging --wait"
|
|
7382
7709
|
].join(`
|
|
7383
7710
|
`)).action(runAction(async (serverRef, opts) => {
|
|
7384
7711
|
const target = validateChoice("target", opts.target, DEPLOY_TARGETS);
|
|
7712
|
+
const environment = resolveDeployEnvironment(opts);
|
|
7385
7713
|
const accessMode = opts.accessMode ? validateChoice("access-mode", opts.accessMode, ACCESS_MODES) : undefined;
|
|
7386
7714
|
const orgId = await resolveOrgId(opts.org);
|
|
7387
7715
|
let projectId = await resolveOptionalProjectId(opts.project, orgId);
|
|
@@ -7408,10 +7736,11 @@ function registerServerLifecycleCommands(servers) {
|
|
|
7408
7736
|
serverId,
|
|
7409
7737
|
target,
|
|
7410
7738
|
...accessMode ? { accessMode } : {},
|
|
7739
|
+
...environment ? { environment } : {},
|
|
7411
7740
|
runtimeConfig
|
|
7412
7741
|
};
|
|
7413
7742
|
if (!isJsonMode()) {
|
|
7414
|
-
printStep(`Deploying ${c.bold(serverId)} to ${c.cyan(target)}…`);
|
|
7743
|
+
printStep(`Deploying ${c.bold(serverId)} to ${c.cyan(target)}${environment ? ` in ${c.cyan(environment)}` : ""}…`);
|
|
7415
7744
|
}
|
|
7416
7745
|
const data = await api.post("/api/v1/server/deploy", body);
|
|
7417
7746
|
const dep = data.deployment;
|
|
@@ -7462,6 +7791,7 @@ function registerServerLifecycleCommands(servers) {
|
|
|
7462
7791
|
printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
|
|
7463
7792
|
printKeyValue({
|
|
7464
7793
|
id: dep.deploymentId,
|
|
7794
|
+
...environment ? { environment } : {},
|
|
7465
7795
|
...final.deploymentUrl ? { url: final.deploymentUrl } : {},
|
|
7466
7796
|
exposure: formatDeploymentExposure(final),
|
|
7467
7797
|
"access mode": dep.accessMode,
|
|
@@ -8711,6 +9041,8 @@ function registerServerCommands(program) {
|
|
|
8711
9041
|
registerServerResourceCommands(servers);
|
|
8712
9042
|
registerServerShapingCommands(servers);
|
|
8713
9043
|
registerServerEnvCommands(servers);
|
|
9044
|
+
registerServerEnvironmentCommands(servers);
|
|
9045
|
+
registerServerPromoteCommands(servers);
|
|
8714
9046
|
registerServerSandboxVariableCommands(servers);
|
|
8715
9047
|
registerServerGetCommand(servers);
|
|
8716
9048
|
registerServerExportCommands(servers);
|
package/package.json
CHANGED