@mcpcloud/cli 0.9.1 → 0.10.1-next-20260715014243
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 +350 -142
- package/dist/index.js +576 -205
- 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
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
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
|
|
2181
|
+
return DEFAULT_APP_URL;
|
|
2156
2182
|
} catch {}
|
|
2157
2183
|
}
|
|
2158
|
-
return
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,18 +3941,68 @@ 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;
|
|
3888
3965
|
return false;
|
|
3889
3966
|
}
|
|
3967
|
+
var SUCCESS_STAGES = new Set(["active"]);
|
|
3968
|
+
var SUCCESS_LEVELS = new Set(["success"]);
|
|
3969
|
+
function isSuccessEvent(event) {
|
|
3970
|
+
if (SUCCESS_STAGES.has(event.stage))
|
|
3971
|
+
return true;
|
|
3972
|
+
if (SUCCESS_LEVELS.has(event.level))
|
|
3973
|
+
return true;
|
|
3974
|
+
const m = event.message.toLowerCase();
|
|
3975
|
+
if (m.includes("deployment active") || m.includes("deploy succeeded"))
|
|
3976
|
+
return true;
|
|
3977
|
+
return false;
|
|
3978
|
+
}
|
|
3979
|
+
function isFailureEvent(event) {
|
|
3980
|
+
return isTerminalEvent(event) && !isSuccessEvent(event);
|
|
3981
|
+
}
|
|
3982
|
+
function formatEventDetails(details) {
|
|
3983
|
+
if (details == null)
|
|
3984
|
+
return null;
|
|
3985
|
+
if (typeof details === "string") {
|
|
3986
|
+
const trimmed = details.trim();
|
|
3987
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
3988
|
+
}
|
|
3989
|
+
if (typeof details === "object") {
|
|
3990
|
+
const record = details;
|
|
3991
|
+
for (const key of ["error", "message", "reason", "detail"]) {
|
|
3992
|
+
const value = record[key];
|
|
3993
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
3994
|
+
return value.trim();
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
try {
|
|
3998
|
+
const json = JSON.stringify(details);
|
|
3999
|
+
return json && json !== "{}" ? json : null;
|
|
4000
|
+
} catch {
|
|
4001
|
+
return null;
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
return null;
|
|
4005
|
+
}
|
|
3890
4006
|
async function followDeploymentEvents(args) {
|
|
3891
4007
|
const isDone = args.isDone ?? isTerminalEvent;
|
|
3892
4008
|
const totalTimeoutMs = args.totalTimeoutMs ?? 5 * 60000;
|
|
@@ -3922,7 +4038,9 @@ function formatEventLine(event) {
|
|
|
3922
4038
|
const ts = new Date(event.timestamp).toISOString();
|
|
3923
4039
|
const level = (event.level || "info").toUpperCase().padEnd(7);
|
|
3924
4040
|
const stage = (event.stage || "").padEnd(12);
|
|
3925
|
-
|
|
4041
|
+
const base = `[${ts}] ${level} ${stage} ${event.message}`;
|
|
4042
|
+
const details = formatEventDetails(event.details);
|
|
4043
|
+
return details && details !== event.message ? `${base} — ${details}` : base;
|
|
3926
4044
|
}
|
|
3927
4045
|
async function tailDeploymentLogs(args) {
|
|
3928
4046
|
const params = {
|
|
@@ -4456,11 +4574,12 @@ function validateChoice(name, value, allowed) {
|
|
|
4456
4574
|
}
|
|
4457
4575
|
return v;
|
|
4458
4576
|
}
|
|
4459
|
-
function
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
const
|
|
4463
|
-
|
|
4577
|
+
function reportDeployFailure(terminalEvent) {
|
|
4578
|
+
if (!terminalEvent || !isFailureEvent(terminalEvent))
|
|
4579
|
+
return false;
|
|
4580
|
+
const reason = formatEventDetails(terminalEvent.details);
|
|
4581
|
+
printError(`Deploy failed: ${reason ?? terminalEvent.message}`);
|
|
4582
|
+
return true;
|
|
4464
4583
|
}
|
|
4465
4584
|
function registerServerLifecycleCommands(servers) {
|
|
4466
4585
|
servers.command("deploy <serverId>").description("Deploy a server's generated bundle to Cloudflare Workers (closes the spec → deploy loop)").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Project ID (defaults to the server's project)").option("--target <target>", `Deploy target: ${DEPLOY_TARGETS.join(" | ")}`, "workersDev").option("--access-mode <mode>", `Access mode: ${ACCESS_MODES.join(" | ")}`, "public").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", [
|
|
@@ -4513,36 +4632,41 @@ function registerServerLifecycleCommands(servers) {
|
|
|
4513
4632
|
let terminalEvent = null;
|
|
4514
4633
|
let waitDone = !opts.wait;
|
|
4515
4634
|
if (opts.wait) {
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4635
|
+
if (isTerminalDeploymentStatus(dep.status)) {
|
|
4636
|
+
waitDone = true;
|
|
4637
|
+
} else {
|
|
4638
|
+
const totalTimeoutMs = Math.max(10, Number.parseInt(opts.waitTimeout, 10) || 300) * 1000;
|
|
4639
|
+
if (!isJsonMode()) {
|
|
4640
|
+
printStep(`Following events for ${c.bold(dep.deploymentId)} (timeout ${Math.round(totalTimeoutMs / 1000)}s)…`);
|
|
4641
|
+
}
|
|
4642
|
+
const result = await followDeploymentEvents({
|
|
4643
|
+
organizationId: orgId,
|
|
4644
|
+
deploymentId: dep.deploymentId,
|
|
4645
|
+
totalTimeoutMs,
|
|
4646
|
+
onEvent: (event) => {
|
|
4647
|
+
if (!isJsonMode()) {
|
|
4648
|
+
printInfo(formatEventLine(event));
|
|
4649
|
+
}
|
|
4527
4650
|
}
|
|
4651
|
+
});
|
|
4652
|
+
terminalEvent = result.terminalEvent;
|
|
4653
|
+
waitDone = result.done;
|
|
4654
|
+
if (!waitDone && !isJsonMode()) {
|
|
4655
|
+
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
4656
|
}
|
|
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
4657
|
}
|
|
4535
4658
|
}
|
|
4659
|
+
const deployFailed = dep.status === "failed" || Boolean(terminalEvent && isFailureEvent(terminalEvent));
|
|
4536
4660
|
if (isJsonMode()) {
|
|
4537
4661
|
printJson({ deployment: dep, waitDone, terminalEvent });
|
|
4538
|
-
if (opts.wait && !waitDone)
|
|
4662
|
+
if (opts.wait && (!waitDone || deployFailed))
|
|
4539
4663
|
throw new CliExitError(1);
|
|
4540
4664
|
return;
|
|
4541
4665
|
}
|
|
4542
4666
|
printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
|
|
4543
4667
|
printKeyValue({
|
|
4544
4668
|
id: dep.deploymentId,
|
|
4545
|
-
url: dep.deploymentUrl,
|
|
4669
|
+
...dep.deploymentUrl ? { url: dep.deploymentUrl } : {},
|
|
4546
4670
|
target: dep.target,
|
|
4547
4671
|
"access mode": dep.accessMode,
|
|
4548
4672
|
status: dep.status,
|
|
@@ -4552,8 +4676,17 @@ function registerServerLifecycleCommands(servers) {
|
|
|
4552
4676
|
"terminal at": formatDate(terminalEvent.timestamp)
|
|
4553
4677
|
} : {}
|
|
4554
4678
|
});
|
|
4679
|
+
if (!opts.wait && dep.status === "queued") {
|
|
4680
|
+
printInfo(`Deploy is running in the background — follow with \`mcp deployments get ${dep.deploymentId}\` or rerun with --wait.`);
|
|
4681
|
+
}
|
|
4555
4682
|
if (opts.wait && !waitDone)
|
|
4556
4683
|
throw new CliExitError(1);
|
|
4684
|
+
if (deployFailed) {
|
|
4685
|
+
if (!reportDeployFailure(terminalEvent)) {
|
|
4686
|
+
printError("Deploy failed.");
|
|
4687
|
+
}
|
|
4688
|
+
throw new CliExitError(1);
|
|
4689
|
+
}
|
|
4557
4690
|
}));
|
|
4558
4691
|
servers.command("push-spec <serverId>").description("Push a local OpenAPI spec to the cloud (regenerates the bundle; optionally deploys)").option("--org <organizationId>", "Organization ID").option("--spec <path>", "Path to the OpenAPI/Swagger spec file", "./openapi.yaml").option("--enrich-on-change", "Preserve per-tool enrichment when the spec changes").option("--deploy", "Immediately deploy the regenerated bundle to Cloudflare Workers").option("--target <target>", `Deploy target (with --deploy): ${DEPLOY_TARGETS.join(" | ")}`, "workersDev").option("--access-mode <mode>", `Access mode (with --deploy): ${ACCESS_MODES.join(" | ")}`, "public").option("--wait", "With --deploy: tail deployment events until terminal state").option("--wait-timeout <seconds>", "Maximum seconds to wait for terminal state (default 300)", "300").addHelpText("after", [
|
|
4559
4692
|
"",
|
|
@@ -4643,25 +4776,30 @@ function registerServerLifecycleCommands(servers) {
|
|
|
4643
4776
|
let terminalEvent = null;
|
|
4644
4777
|
let waitDone = !opts.wait;
|
|
4645
4778
|
if (opts.wait) {
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4779
|
+
if (isTerminalDeploymentStatus(dep.status)) {
|
|
4780
|
+
waitDone = true;
|
|
4781
|
+
} else {
|
|
4782
|
+
const totalTimeoutMs = Math.max(10, Number.parseInt(opts.waitTimeout, 10) || 300) * 1000;
|
|
4783
|
+
if (!isJsonMode()) {
|
|
4784
|
+
printStep(`Following events for ${c.bold(dep.deploymentId)}…`);
|
|
4785
|
+
}
|
|
4786
|
+
const r = await followDeploymentEvents({
|
|
4787
|
+
organizationId: orgId,
|
|
4788
|
+
deploymentId: dep.deploymentId,
|
|
4789
|
+
totalTimeoutMs,
|
|
4790
|
+
onEvent: (event) => {
|
|
4791
|
+
if (!isJsonMode())
|
|
4792
|
+
printInfo(formatEventLine(event));
|
|
4793
|
+
}
|
|
4794
|
+
});
|
|
4795
|
+
terminalEvent = r.terminalEvent;
|
|
4796
|
+
waitDone = r.done;
|
|
4797
|
+
if (!waitDone && !isJsonMode()) {
|
|
4798
|
+
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
4799
|
}
|
|
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
4800
|
}
|
|
4664
4801
|
}
|
|
4802
|
+
const deployFailed = dep.status === "failed" || Boolean(terminalEvent && isFailureEvent(terminalEvent));
|
|
4665
4803
|
if (isJsonMode()) {
|
|
4666
4804
|
printJson({
|
|
4667
4805
|
pushed: true,
|
|
@@ -4671,14 +4809,14 @@ function registerServerLifecycleCommands(servers) {
|
|
|
4671
4809
|
waitDone,
|
|
4672
4810
|
terminalEvent
|
|
4673
4811
|
});
|
|
4674
|
-
if (opts.wait && !waitDone)
|
|
4812
|
+
if (opts.wait && (!waitDone || deployFailed))
|
|
4675
4813
|
throw new CliExitError(1);
|
|
4676
4814
|
return;
|
|
4677
4815
|
}
|
|
4678
4816
|
printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
|
|
4679
4817
|
printKeyValue({
|
|
4680
4818
|
id: dep.deploymentId,
|
|
4681
|
-
url: dep.deploymentUrl,
|
|
4819
|
+
...dep.deploymentUrl ? { url: dep.deploymentUrl } : {},
|
|
4682
4820
|
target: dep.target,
|
|
4683
4821
|
"access mode": dep.accessMode,
|
|
4684
4822
|
status: dep.status,
|
|
@@ -4688,8 +4826,17 @@ function registerServerLifecycleCommands(servers) {
|
|
|
4688
4826
|
"terminal at": formatDate(terminalEvent.timestamp)
|
|
4689
4827
|
} : {}
|
|
4690
4828
|
});
|
|
4829
|
+
if (!opts.wait && dep.status === "queued") {
|
|
4830
|
+
printInfo(`Deploy is running in the background — follow with \`mcp deployments get ${dep.deploymentId}\` or rerun with --wait.`);
|
|
4831
|
+
}
|
|
4691
4832
|
if (opts.wait && !waitDone)
|
|
4692
4833
|
throw new CliExitError(1);
|
|
4834
|
+
if (deployFailed) {
|
|
4835
|
+
if (!reportDeployFailure(terminalEvent)) {
|
|
4836
|
+
printError("Deploy failed.");
|
|
4837
|
+
}
|
|
4838
|
+
throw new CliExitError(1);
|
|
4839
|
+
}
|
|
4693
4840
|
}));
|
|
4694
4841
|
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
4842
|
"",
|
|
@@ -5533,6 +5680,9 @@ import { dirname as dirname5, join as join8 } from "node:path";
|
|
|
5533
5680
|
// src/lib/dev/state.ts
|
|
5534
5681
|
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
5535
5682
|
import { join as join6, resolve as resolve5 } from "node:path";
|
|
5683
|
+
function cachedStateMatchesTarget(cached2, target) {
|
|
5684
|
+
return cached2 !== null && cached2.serverId === target.serverId && cached2.organizationId === target.organizationId;
|
|
5685
|
+
}
|
|
5536
5686
|
function devRoot(cwd) {
|
|
5537
5687
|
return join6(cwd, ".mcpcloud");
|
|
5538
5688
|
}
|
|
@@ -7441,7 +7591,7 @@ function diffLine2(label, applied, suggested) {
|
|
|
7441
7591
|
// src/commands/tools.ts
|
|
7442
7592
|
function registerToolCommands(program2) {
|
|
7443
7593
|
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
|
|
7594
|
+
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
7595
|
const orgId = await resolveOrgId(opts.org);
|
|
7446
7596
|
if (!opts.project && !opts.server) {
|
|
7447
7597
|
throw new Error("Pass either --project <projectId> or --server <serverId>.");
|
|
@@ -7458,10 +7608,11 @@ function registerToolCommands(program2) {
|
|
|
7458
7608
|
organizationId: orgId,
|
|
7459
7609
|
projectId
|
|
7460
7610
|
});
|
|
7461
|
-
|
|
7611
|
+
const rows = opts.server ? data.tools.filter((t) => t.serverId === opts.server) : data.tools;
|
|
7612
|
+
printList(rows.map((t) => ({
|
|
7462
7613
|
name: t.name,
|
|
7463
|
-
method: t.method,
|
|
7464
|
-
path: t.path,
|
|
7614
|
+
method: t.endpoint?.method ?? t.method ?? "—",
|
|
7615
|
+
path: t.endpoint?.path ?? t.path ?? "—",
|
|
7465
7616
|
enrichment: t.enrichmentStatus ?? "—",
|
|
7466
7617
|
description: t.description.slice(0, 60) + (t.description.length > 60 ? "…" : "")
|
|
7467
7618
|
})), [
|
|
@@ -7470,7 +7621,7 @@ function registerToolCommands(program2) {
|
|
|
7470
7621
|
{ key: "path", label: "Path", width: 32 },
|
|
7471
7622
|
{ key: "enrichment", label: "Enrichment", width: 12 },
|
|
7472
7623
|
{ key: "description", label: "Description", width: 60 }
|
|
7473
|
-
], data);
|
|
7624
|
+
], opts.server ? { ...data, tools: rows } : data);
|
|
7474
7625
|
}));
|
|
7475
7626
|
registerToolsShowCommand(tools);
|
|
7476
7627
|
registerToolsDiffCommand(tools);
|
|
@@ -9035,7 +9186,7 @@ function previewKey2(key) {
|
|
|
9035
9186
|
}
|
|
9036
9187
|
function registerConfigCommands(program2) {
|
|
9037
9188
|
const config = program2.command("config").description("Manage CLI configuration in ~/.mcpcloud/config.json");
|
|
9038
|
-
|
|
9189
|
+
const showActiveProfile = runAction(() => {
|
|
9039
9190
|
const cfg = readConfig();
|
|
9040
9191
|
const profile2 = getActiveProfileName();
|
|
9041
9192
|
const safe = {
|
|
@@ -9052,14 +9203,16 @@ function registerConfigCommands(program2) {
|
|
|
9052
9203
|
printKeyValue({
|
|
9053
9204
|
path: safe.path,
|
|
9054
9205
|
profile: safe.profile,
|
|
9055
|
-
"base url": safe.baseUrl ?? "—",
|
|
9206
|
+
"base url": safe.baseUrl ?? "— (built-in default)",
|
|
9056
9207
|
"app url": cfg.appUrl ?? "— (auto-derived)",
|
|
9057
9208
|
"default org": safe.defaultOrganizationId ?? "—",
|
|
9058
9209
|
"api key": safe.apiKeyPreview ?? "—",
|
|
9059
9210
|
editor: cfg.editorCommand ?? "code",
|
|
9060
9211
|
"editor open": cfg.editorOpenPreference ?? "ask"
|
|
9061
9212
|
});
|
|
9062
|
-
})
|
|
9213
|
+
});
|
|
9214
|
+
config.command("show").description("Print the active profile (API key is redacted)").action(showActiveProfile);
|
|
9215
|
+
config.command("current").description("Alias for `config show` — print the active profile").action(showActiveProfile);
|
|
9063
9216
|
config.command("set-url <url>").description("Save the API base URL to ~/.mcpcloud/config.json").action(runAction((url) => {
|
|
9064
9217
|
try {
|
|
9065
9218
|
new URL(url);
|
|
@@ -9629,7 +9782,7 @@ async function runDevReplay(opts) {
|
|
|
9629
9782
|
printInfo("No matching records in .mcpcloud/inspector/. Use --from / --since / --filter, or run something first.");
|
|
9630
9783
|
return;
|
|
9631
9784
|
}
|
|
9632
|
-
const baseUrl = opts.url ??
|
|
9785
|
+
const baseUrl = opts.url ?? resolveBaseUrl2(cwd);
|
|
9633
9786
|
if (!opts.dryRun && !baseUrl) {
|
|
9634
9787
|
printError("No running mcp dev session in this directory. Start `mcp dev`, or pass --url <baseUrl>.");
|
|
9635
9788
|
throw new CliExitError(1);
|
|
@@ -9707,7 +9860,7 @@ function parseLimit(raw) {
|
|
|
9707
9860
|
}
|
|
9708
9861
|
return n;
|
|
9709
9862
|
}
|
|
9710
|
-
function
|
|
9863
|
+
function resolveBaseUrl2(cwd) {
|
|
9711
9864
|
const sessions = listSessions().filter((s) => s.cwd === cwd);
|
|
9712
9865
|
if (sessions.length === 0)
|
|
9713
9866
|
return null;
|
|
@@ -11220,8 +11373,13 @@ async function fetchBundle(args) {
|
|
|
11220
11373
|
} catch {
|
|
11221
11374
|
body = null;
|
|
11222
11375
|
}
|
|
11223
|
-
const
|
|
11224
|
-
|
|
11376
|
+
const envelope = body?.error;
|
|
11377
|
+
const requestId = envelope?.requestId ?? res.headers?.get?.("x-request-id") ?? null;
|
|
11378
|
+
throw new McpCloudApiError(res.status, {
|
|
11379
|
+
code: envelope?.code ?? `http_${res.status}`,
|
|
11380
|
+
message: envelope?.message ?? `Bundle download failed (${res.status}).`,
|
|
11381
|
+
...requestId ? { requestId } : {}
|
|
11382
|
+
});
|
|
11225
11383
|
}
|
|
11226
11384
|
const text = await res.text();
|
|
11227
11385
|
const parsed = JSON.parse(text);
|
|
@@ -25781,7 +25939,7 @@ if (!root) throw new Error("Missing #root mount node");
|
|
|
25781
25939
|
clientExports.createRoot(root).render(
|
|
25782
25940
|
/* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.StrictMode, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(App, {}) })
|
|
25783
25941
|
);</script>
|
|
25784
|
-
<style rel="stylesheet" crossorigin>/*! tailwindcss v4.3.
|
|
25942
|
+
<style rel="stylesheet" crossorigin>/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
|
|
25785
25943
|
@layer properties {
|
|
25786
25944
|
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
|
|
25787
25945
|
*, :before, :after, ::backdrop {
|
|
@@ -26116,7 +26274,7 @@ clientExports.createRoot(root).render(
|
|
|
26116
26274
|
}
|
|
26117
26275
|
|
|
26118
26276
|
.top-0 {
|
|
26119
|
-
top:
|
|
26277
|
+
top: 0;
|
|
26120
26278
|
}
|
|
26121
26279
|
|
|
26122
26280
|
.z-10 {
|
|
@@ -26124,7 +26282,7 @@ clientExports.createRoot(root).render(
|
|
|
26124
26282
|
}
|
|
26125
26283
|
|
|
26126
26284
|
.m-0 {
|
|
26127
|
-
margin:
|
|
26285
|
+
margin: 0;
|
|
26128
26286
|
}
|
|
26129
26287
|
|
|
26130
26288
|
.mx-auto {
|
|
@@ -26136,7 +26294,7 @@ clientExports.createRoot(root).render(
|
|
|
26136
26294
|
}
|
|
26137
26295
|
|
|
26138
26296
|
.mt-1 {
|
|
26139
|
-
margin-top:
|
|
26297
|
+
margin-top: var(--spacing);
|
|
26140
26298
|
}
|
|
26141
26299
|
|
|
26142
26300
|
.mt-3 {
|
|
@@ -26144,7 +26302,7 @@ clientExports.createRoot(root).render(
|
|
|
26144
26302
|
}
|
|
26145
26303
|
|
|
26146
26304
|
.mb-1 {
|
|
26147
|
-
margin-bottom:
|
|
26305
|
+
margin-bottom: var(--spacing);
|
|
26148
26306
|
}
|
|
26149
26307
|
|
|
26150
26308
|
.mb-2\\.5 {
|
|
@@ -26152,7 +26310,7 @@ clientExports.createRoot(root).render(
|
|
|
26152
26310
|
}
|
|
26153
26311
|
|
|
26154
26312
|
.ml-1 {
|
|
26155
|
-
margin-left:
|
|
26313
|
+
margin-left: var(--spacing);
|
|
26156
26314
|
}
|
|
26157
26315
|
|
|
26158
26316
|
.ml-2 {
|
|
@@ -26201,7 +26359,7 @@ clientExports.createRoot(root).render(
|
|
|
26201
26359
|
}
|
|
26202
26360
|
|
|
26203
26361
|
.min-w-0 {
|
|
26204
|
-
min-width:
|
|
26362
|
+
min-width: 0;
|
|
26205
26363
|
}
|
|
26206
26364
|
|
|
26207
26365
|
.min-w-\\[240px\\] {
|
|
@@ -26253,7 +26411,7 @@ clientExports.createRoot(root).render(
|
|
|
26253
26411
|
}
|
|
26254
26412
|
|
|
26255
26413
|
.gap-1 {
|
|
26256
|
-
gap:
|
|
26414
|
+
gap: var(--spacing);
|
|
26257
26415
|
}
|
|
26258
26416
|
|
|
26259
26417
|
.gap-1\\.5 {
|
|
@@ -26329,7 +26487,7 @@ clientExports.createRoot(root).render(
|
|
|
26329
26487
|
}
|
|
26330
26488
|
|
|
26331
26489
|
.p-0 {
|
|
26332
|
-
padding:
|
|
26490
|
+
padding: 0;
|
|
26333
26491
|
}
|
|
26334
26492
|
|
|
26335
26493
|
.p-2 {
|
|
@@ -26341,7 +26499,7 @@ clientExports.createRoot(root).render(
|
|
|
26341
26499
|
}
|
|
26342
26500
|
|
|
26343
26501
|
.px-1 {
|
|
26344
|
-
padding-inline:
|
|
26502
|
+
padding-inline: var(--spacing);
|
|
26345
26503
|
}
|
|
26346
26504
|
|
|
26347
26505
|
.px-1\\.5 {
|
|
@@ -26369,7 +26527,7 @@ clientExports.createRoot(root).render(
|
|
|
26369
26527
|
}
|
|
26370
26528
|
|
|
26371
26529
|
.py-1 {
|
|
26372
|
-
padding-block:
|
|
26530
|
+
padding-block: var(--spacing);
|
|
26373
26531
|
}
|
|
26374
26532
|
|
|
26375
26533
|
.py-2 {
|
|
@@ -27495,8 +27653,12 @@ async function prepareDev(opts) {
|
|
|
27495
27653
|
const env = envFile(cwd);
|
|
27496
27654
|
const backups = backupsDir(cwd);
|
|
27497
27655
|
const cached2 = readState(cwd);
|
|
27498
|
-
|
|
27499
|
-
|
|
27656
|
+
const cacheIsForTarget = cachedStateMatchesTarget(cached2, {
|
|
27657
|
+
serverId: opts.serverId,
|
|
27658
|
+
organizationId: opts.organizationId
|
|
27659
|
+
});
|
|
27660
|
+
let projectId = cacheIsForTarget ? cached2.projectId : undefined;
|
|
27661
|
+
let serverName = cacheIsForTarget ? cached2.serverName : undefined;
|
|
27500
27662
|
let gitNative = false;
|
|
27501
27663
|
let repoDir;
|
|
27502
27664
|
const gitLink = opts.offline ? null : await resolveServerGitLink({
|
|
@@ -28463,7 +28625,8 @@ async function runDev(opts) {
|
|
|
28463
28625
|
const cwd = process.cwd();
|
|
28464
28626
|
const cached2 = readState(cwd);
|
|
28465
28627
|
let serverId = opts.server ?? cached2?.serverId;
|
|
28466
|
-
|
|
28628
|
+
const serverCameFromCache = !opts.server && Boolean(cached2?.serverId);
|
|
28629
|
+
let organizationId = opts.org ?? (serverCameFromCache ? cached2?.organizationId ?? undefined : undefined);
|
|
28467
28630
|
if (!opts.server && cached2?.serverId && canRunInteractive()) {
|
|
28468
28631
|
const choice = await promptResumeOrSwitch({
|
|
28469
28632
|
serverId: cached2.serverId,
|
|
@@ -29575,19 +29738,15 @@ function checkActiveProfile() {
|
|
|
29575
29738
|
ms: Date.now() - t0
|
|
29576
29739
|
};
|
|
29577
29740
|
}
|
|
29741
|
+
var BASE_URL_SOURCE_LABEL = {
|
|
29742
|
+
flag: "--base-url flag",
|
|
29743
|
+
env: "env var",
|
|
29744
|
+
profile: "profile",
|
|
29745
|
+
default: "built-in default"
|
|
29746
|
+
};
|
|
29578
29747
|
function checkBaseUrl() {
|
|
29579
29748
|
const t0 = Date.now();
|
|
29580
|
-
const
|
|
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
|
-
}
|
|
29749
|
+
const { url, source } = resolveBaseUrl();
|
|
29591
29750
|
try {
|
|
29592
29751
|
new URL(url);
|
|
29593
29752
|
} catch {
|
|
@@ -29598,11 +29757,11 @@ function checkBaseUrl() {
|
|
|
29598
29757
|
ms: Date.now() - t0
|
|
29599
29758
|
};
|
|
29600
29759
|
}
|
|
29601
|
-
const
|
|
29760
|
+
const prodMarker = isProductionBaseUrl(url) ? " (production)" : "";
|
|
29602
29761
|
return {
|
|
29603
29762
|
name: "Base URL",
|
|
29604
29763
|
status: "pass",
|
|
29605
|
-
detail: `${url} (from ${source})`,
|
|
29764
|
+
detail: `${url}${prodMarker} (from ${BASE_URL_SOURCE_LABEL[source]})`,
|
|
29606
29765
|
ms: Date.now() - t0
|
|
29607
29766
|
};
|
|
29608
29767
|
}
|
|
@@ -30006,7 +30165,7 @@ function registerHelpCommand(program2) {
|
|
|
30006
30165
|
});
|
|
30007
30166
|
}
|
|
30008
30167
|
|
|
30009
|
-
// src/commands/init.ts
|
|
30168
|
+
// src/commands/init-shared.ts
|
|
30010
30169
|
import { createInterface as createInterface2 } from "node:readline";
|
|
30011
30170
|
function prompt2(question) {
|
|
30012
30171
|
if (isNonInteractive()) {
|
|
@@ -30022,7 +30181,10 @@ function prompt2(question) {
|
|
|
30022
30181
|
}
|
|
30023
30182
|
async function pickProject(orgId, override) {
|
|
30024
30183
|
if (override) {
|
|
30025
|
-
const data = await api.get("/api/v1/project", {
|
|
30184
|
+
const data = await api.get("/api/v1/project", {
|
|
30185
|
+
organizationId: orgId,
|
|
30186
|
+
projectId: override
|
|
30187
|
+
});
|
|
30026
30188
|
return { project: data.project, created: false };
|
|
30027
30189
|
}
|
|
30028
30190
|
const list = await api.get("/api/v1/projects", { organizationId: orgId, limit: "25" });
|
|
@@ -30075,67 +30237,15 @@ async function pickServerName(override) {
|
|
|
30075
30237
|
throw new Error("Server name must not be empty.");
|
|
30076
30238
|
return name;
|
|
30077
30239
|
}
|
|
30078
|
-
function
|
|
30079
|
-
|
|
30080
|
-
|
|
30081
|
-
|
|
30082
|
-
|
|
30083
|
-
|
|
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
|
-
}));
|
|
30240
|
+
function reportProject(project, created) {
|
|
30241
|
+
if (isJsonMode())
|
|
30242
|
+
return;
|
|
30243
|
+
if (created) {
|
|
30244
|
+
printSuccess(`Project ${c.bold(project.name)} created (${project.id}).`);
|
|
30245
|
+
}
|
|
30138
30246
|
}
|
|
30247
|
+
|
|
30248
|
+
// src/commands/init-spec.ts
|
|
30139
30249
|
function validateSourceType2(value) {
|
|
30140
30250
|
if (value === undefined)
|
|
30141
30251
|
return;
|
|
@@ -30145,6 +30255,52 @@ function validateSourceType2(value) {
|
|
|
30145
30255
|
}
|
|
30146
30256
|
return v2;
|
|
30147
30257
|
}
|
|
30258
|
+
async function deployIngestedServer(args) {
|
|
30259
|
+
return await api.post("/api/v1/server/deploy", {
|
|
30260
|
+
organizationId: args.orgId,
|
|
30261
|
+
projectId: args.projectId,
|
|
30262
|
+
serverId: args.serverId,
|
|
30263
|
+
target: "workersDev",
|
|
30264
|
+
accessMode: "public",
|
|
30265
|
+
runtimeConfig: {
|
|
30266
|
+
upstreamBaseUrl: null,
|
|
30267
|
+
upstreamApiKey: null,
|
|
30268
|
+
upstreamApiKeyHeader: null,
|
|
30269
|
+
upstreamApiKeyPrefix: null,
|
|
30270
|
+
upstreamCustomHeaders: null,
|
|
30271
|
+
additionalPlainTextBindings: null,
|
|
30272
|
+
additionalSecretBindings: null
|
|
30273
|
+
}
|
|
30274
|
+
});
|
|
30275
|
+
}
|
|
30276
|
+
async function runDeployStep(args) {
|
|
30277
|
+
const { orgId, projectId, serverId, opts } = args;
|
|
30278
|
+
if (!isJsonMode())
|
|
30279
|
+
printStep(`Deploying ${c.bold(serverId)}…`);
|
|
30280
|
+
const deployData = await deployIngestedServer({ orgId, projectId, serverId });
|
|
30281
|
+
const dep = deployData.deployment;
|
|
30282
|
+
let waitDone = !opts.wait;
|
|
30283
|
+
let terminalEvent = null;
|
|
30284
|
+
if (opts.wait) {
|
|
30285
|
+
if (isTerminalDeploymentStatus(dep.status)) {
|
|
30286
|
+
waitDone = true;
|
|
30287
|
+
} else {
|
|
30288
|
+
const totalTimeoutMs = Math.max(10, Number.parseInt(opts.waitTimeout, 10) || 300) * 1000;
|
|
30289
|
+
const followed = await followDeploymentEvents({
|
|
30290
|
+
organizationId: orgId,
|
|
30291
|
+
deploymentId: dep.deploymentId,
|
|
30292
|
+
totalTimeoutMs,
|
|
30293
|
+
onEvent: (event) => {
|
|
30294
|
+
if (!isJsonMode())
|
|
30295
|
+
printInfo(formatEventLine(event));
|
|
30296
|
+
}
|
|
30297
|
+
});
|
|
30298
|
+
waitDone = followed.done;
|
|
30299
|
+
terminalEvent = followed.terminalEvent;
|
|
30300
|
+
}
|
|
30301
|
+
}
|
|
30302
|
+
return { deployment: dep, waitDone, terminalEvent };
|
|
30303
|
+
}
|
|
30148
30304
|
async function runSpecInit(args) {
|
|
30149
30305
|
const { orgId, opts } = args;
|
|
30150
30306
|
if (opts.name && !isJsonMode()) {
|
|
@@ -30153,46 +30309,36 @@ async function runSpecInit(args) {
|
|
|
30153
30309
|
const sourceType = validateSourceType2(opts.sourceType);
|
|
30154
30310
|
const payload = await readSpecSource(opts.fromSpec, sourceType);
|
|
30155
30311
|
const { project, created: projectCreated } = await pickProject(orgId, opts.project);
|
|
30156
|
-
|
|
30157
|
-
printSuccess(`Project ${c.bold(project.name)} created (${project.id}).`);
|
|
30158
|
-
}
|
|
30312
|
+
reportProject(project, projectCreated);
|
|
30159
30313
|
if (!isJsonMode()) {
|
|
30160
30314
|
printStep(`Importing ${c.bold(payload.sourceType)} spec into ${c.bold(project.name)}…`);
|
|
30161
30315
|
}
|
|
30162
|
-
const ingest = await api.post("/api/v1/server/ingest", buildIngestBody({
|
|
30163
|
-
organizationId: orgId,
|
|
30164
|
-
projectId: project.id,
|
|
30165
|
-
payload
|
|
30166
|
-
}));
|
|
30316
|
+
const ingest = await api.post("/api/v1/server/ingest", buildIngestBody({ organizationId: orgId, projectId: project.id, payload }));
|
|
30167
30317
|
const server = ingest.server;
|
|
30168
30318
|
if (!isJsonMode()) {
|
|
30169
30319
|
printSuccess(`Server ${c.bold(server.id)} created from spec.`);
|
|
30170
30320
|
}
|
|
30171
30321
|
let deployResult = null;
|
|
30172
30322
|
if (opts.deploy) {
|
|
30173
|
-
|
|
30174
|
-
|
|
30175
|
-
|
|
30176
|
-
|
|
30177
|
-
|
|
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
|
-
}
|
|
30323
|
+
try {
|
|
30324
|
+
deployResult = await runDeployStep({
|
|
30325
|
+
orgId,
|
|
30326
|
+
projectId: project.id,
|
|
30327
|
+
serverId: server.id,
|
|
30328
|
+
opts
|
|
30191
30329
|
});
|
|
30192
|
-
|
|
30193
|
-
|
|
30330
|
+
} catch (err) {
|
|
30331
|
+
reportDeployFailure2({
|
|
30332
|
+
serverId: server.id,
|
|
30333
|
+
projectId: project.id,
|
|
30334
|
+
project,
|
|
30335
|
+
projectCreated,
|
|
30336
|
+
apiSource: ingest.apiSource,
|
|
30337
|
+
server,
|
|
30338
|
+
err
|
|
30339
|
+
});
|
|
30340
|
+
throw err;
|
|
30194
30341
|
}
|
|
30195
|
-
deployResult = { deployment: deployData.deployment, waitDone, terminalEvent };
|
|
30196
30342
|
}
|
|
30197
30343
|
if (isJsonMode()) {
|
|
30198
30344
|
printJson({
|
|
@@ -30203,26 +30349,19 @@ async function runSpecInit(args) {
|
|
|
30203
30349
|
server,
|
|
30204
30350
|
deploy: deployResult
|
|
30205
30351
|
});
|
|
30352
|
+
if (deployResult?.terminalEvent && isFailureEvent(deployResult.terminalEvent))
|
|
30353
|
+
throw new CliExitError(1);
|
|
30206
30354
|
if (deployResult && opts.wait && !deployResult.waitDone)
|
|
30207
30355
|
throw new CliExitError(1);
|
|
30208
30356
|
return;
|
|
30209
30357
|
}
|
|
30210
30358
|
if (deployResult) {
|
|
30211
|
-
|
|
30212
|
-
|
|
30213
|
-
|
|
30214
|
-
|
|
30215
|
-
|
|
30216
|
-
url: dep.deploymentUrl ?? "—",
|
|
30217
|
-
target: dep.target,
|
|
30218
|
-
"access mode": dep.accessMode,
|
|
30219
|
-
status: dep.status
|
|
30359
|
+
reportDeploySuccess({
|
|
30360
|
+
serverId: server.id,
|
|
30361
|
+
projectId: project.id,
|
|
30362
|
+
result: deployResult,
|
|
30363
|
+
opts
|
|
30220
30364
|
});
|
|
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>\`.`);
|
|
30226
30365
|
return;
|
|
30227
30366
|
}
|
|
30228
30367
|
printInfo("");
|
|
@@ -30230,6 +30369,117 @@ async function runSpecInit(args) {
|
|
|
30230
30369
|
printStep(`Generate the bundle: \`mcp servers generate ${server.id} --out ./build\``);
|
|
30231
30370
|
printStep(`Deploy it: \`mcp servers deploy ${server.id} --wait\``);
|
|
30232
30371
|
}
|
|
30372
|
+
function resumeDeployCommand(serverId, projectId) {
|
|
30373
|
+
return `mcp servers deploy ${serverId} --project ${projectId} --wait`;
|
|
30374
|
+
}
|
|
30375
|
+
function reportDeployFailure2(args) {
|
|
30376
|
+
const resume = resumeDeployCommand(args.serverId, args.projectId);
|
|
30377
|
+
if (isJsonMode()) {
|
|
30378
|
+
const message = args.err instanceof Error ? args.err.message : String(args.err);
|
|
30379
|
+
printJson({
|
|
30380
|
+
project: args.project,
|
|
30381
|
+
projectCreated: args.projectCreated,
|
|
30382
|
+
apiSource: args.apiSource,
|
|
30383
|
+
server: args.server,
|
|
30384
|
+
deploy: null,
|
|
30385
|
+
deployFailed: true,
|
|
30386
|
+
deployError: { message },
|
|
30387
|
+
resumeCommand: resume
|
|
30388
|
+
});
|
|
30389
|
+
return;
|
|
30390
|
+
}
|
|
30391
|
+
printError(`Server ${c.bold(args.serverId)} was created, but the deploy step failed.`);
|
|
30392
|
+
printInfo("");
|
|
30393
|
+
printInfo("Created so far:");
|
|
30394
|
+
printKeyValue({ "server id": args.serverId, "project id": args.projectId });
|
|
30395
|
+
printInfo("");
|
|
30396
|
+
printInfo(`Resume the deploy once the cause is cleared:`);
|
|
30397
|
+
printStep(resume);
|
|
30398
|
+
printInfo("");
|
|
30399
|
+
}
|
|
30400
|
+
function reportDeploySuccess(args) {
|
|
30401
|
+
const { serverId, projectId, result, opts } = args;
|
|
30402
|
+
const dep = result.deployment;
|
|
30403
|
+
printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
|
|
30404
|
+
printKeyValue({
|
|
30405
|
+
"server id": serverId,
|
|
30406
|
+
"deployment id": dep.deploymentId,
|
|
30407
|
+
url: dep.deploymentUrl ?? "—",
|
|
30408
|
+
target: dep.target,
|
|
30409
|
+
"access mode": dep.accessMode,
|
|
30410
|
+
status: dep.status
|
|
30411
|
+
});
|
|
30412
|
+
if (result.terminalEvent && isFailureEvent(result.terminalEvent)) {
|
|
30413
|
+
const reason = formatEventDetails(result.terminalEvent.details);
|
|
30414
|
+
printError(`Deploy failed: ${reason ?? result.terminalEvent.message}`);
|
|
30415
|
+
printStep(`Resume once the cause is cleared: \`mcp servers deploy ${serverId} --project ${projectId} --wait\``);
|
|
30416
|
+
throw new CliExitError(1);
|
|
30417
|
+
}
|
|
30418
|
+
if (opts.wait && !result.waitDone) {
|
|
30419
|
+
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\`.`);
|
|
30420
|
+
throw new CliExitError(1);
|
|
30421
|
+
}
|
|
30422
|
+
printStep(`Try it: \`mcp invoke ${serverId} <tool>\`.`);
|
|
30423
|
+
}
|
|
30424
|
+
|
|
30425
|
+
// src/commands/init.ts
|
|
30426
|
+
function registerInitCommand(program2) {
|
|
30427
|
+
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", [
|
|
30428
|
+
"",
|
|
30429
|
+
"Two modes:",
|
|
30430
|
+
" Skeleton (default): resolve org → pick/create project → create an empty server.",
|
|
30431
|
+
" Spec (--from-spec): resolve org → pick/create project → ingest the spec into a",
|
|
30432
|
+
" generated server, then optionally --deploy [--wait].",
|
|
30433
|
+
"",
|
|
30434
|
+
"Examples:",
|
|
30435
|
+
" $ mcp init",
|
|
30436
|
+
' $ mcp init --org org_acme --project proj_123 --name "Stripe MCP"',
|
|
30437
|
+
" $ mcp init --from-spec ./openapi.yaml --project proj_123",
|
|
30438
|
+
" $ mcp init --from-spec https://api.example.com/openapi.json --project proj_123 --deploy --wait"
|
|
30439
|
+
].join(`
|
|
30440
|
+
`)).action(runAction(runInit));
|
|
30441
|
+
}
|
|
30442
|
+
async function runInit(opts) {
|
|
30443
|
+
const orgId = await resolveOrgId(opts.org);
|
|
30444
|
+
if (opts.fromSpec) {
|
|
30445
|
+
await runSpecInit({ orgId, opts });
|
|
30446
|
+
return;
|
|
30447
|
+
}
|
|
30448
|
+
await runSkeletonInit({ orgId, opts });
|
|
30449
|
+
}
|
|
30450
|
+
async function runSkeletonInit(args) {
|
|
30451
|
+
const { orgId, opts } = args;
|
|
30452
|
+
if (!isJsonMode()) {
|
|
30453
|
+
const orgs = await api.get("/api/v1/organizations");
|
|
30454
|
+
const org = orgs.organizations.find((o) => o.id === orgId);
|
|
30455
|
+
printStep(`Using organization ${c.bold(org?.name ?? orgId)} ${c.dim("(" + orgId + ")")}`);
|
|
30456
|
+
}
|
|
30457
|
+
const { project, created: projectCreated } = await pickProject(orgId, opts.project);
|
|
30458
|
+
if (!isJsonMode()) {
|
|
30459
|
+
if (projectCreated) {
|
|
30460
|
+
printSuccess(`Project ${c.bold(project.name)} created (${project.id}).`);
|
|
30461
|
+
} else {
|
|
30462
|
+
printStep(`Using project ${c.bold(project.name)} ${c.dim("(" + project.id + ")")}`);
|
|
30463
|
+
}
|
|
30464
|
+
}
|
|
30465
|
+
const serverName = await pickServerName(opts.name);
|
|
30466
|
+
if (!isJsonMode()) {
|
|
30467
|
+
printStep(`Creating server skeleton ${c.bold(serverName)}…`);
|
|
30468
|
+
}
|
|
30469
|
+
const serverData = await api.post("/api/v1/servers", { organizationId: orgId, projectId: project.id, name: serverName });
|
|
30470
|
+
const server = serverData.server;
|
|
30471
|
+
if (isJsonMode()) {
|
|
30472
|
+
printJson({ organizationId: orgId, project, projectCreated, server });
|
|
30473
|
+
return;
|
|
30474
|
+
}
|
|
30475
|
+
printSuccess(`Server ${c.bold(server.id)} ready.`);
|
|
30476
|
+
printInfo("");
|
|
30477
|
+
printInfo("Next steps:");
|
|
30478
|
+
printInfo(` 1. Import an API spec: \`mcp servers ingest --project ${project.id} --spec ./openapi.yaml\``);
|
|
30479
|
+
printInfo(` ${c.dim("(or re-run `mcp init --from-spec ./openapi.yaml` for the guided flow)")}`);
|
|
30480
|
+
printInfo(` 2. Run \`mcp dev --spec ./openapi.yaml\` to iterate locally.`);
|
|
30481
|
+
printInfo(` 3. \`mcp servers deploy ${server.id} --wait\` to ship it.`);
|
|
30482
|
+
}
|
|
30233
30483
|
|
|
30234
30484
|
// src/commands/installation.ts
|
|
30235
30485
|
function registerInstallationCommands(program2) {
|
|
@@ -30920,6 +31170,7 @@ function registerUsageCommands(program2) {
|
|
|
30920
31170
|
allowed: t.allowedCount,
|
|
30921
31171
|
"billable invocations": usageData.usage.invocations?.billableCount ?? t.allowedToolCallCount ?? 0,
|
|
30922
31172
|
"invocation cost": formatMicros(usageData.usage.invocations?.billedMicros),
|
|
31173
|
+
"projected monthly": formatMicros(usageData.usage.invocations?.projectedMonthlyMicros),
|
|
30923
31174
|
"rate-limited": t.rateLimitedCount,
|
|
30924
31175
|
unauthorized: t.unauthorizedCount,
|
|
30925
31176
|
failed: t.failedCount,
|
|
@@ -31127,6 +31378,73 @@ function registerOrgCommands(program2) {
|
|
|
31127
31378
|
}));
|
|
31128
31379
|
}
|
|
31129
31380
|
|
|
31381
|
+
// src/commands/connections.ts
|
|
31382
|
+
async function readKeyFromStdin() {
|
|
31383
|
+
const chunks = [];
|
|
31384
|
+
for await (const chunk of process.stdin) {
|
|
31385
|
+
chunks.push(Buffer.from(chunk));
|
|
31386
|
+
}
|
|
31387
|
+
return Buffer.concat(chunks).toString("utf8").trim();
|
|
31388
|
+
}
|
|
31389
|
+
function registerConnectionsCommands(program2) {
|
|
31390
|
+
const connections = program2.command("connections").description("Manage your per-server upstream API keys (BYOK) from the terminal").addHelpText("after", [
|
|
31391
|
+
"",
|
|
31392
|
+
"Examples:",
|
|
31393
|
+
" $ mcp connections list",
|
|
31394
|
+
" $ mcp connections add <serverId> --key sk_live_…",
|
|
31395
|
+
' $ echo "$UPSTREAM_KEY" | mcp connections add <serverId> --key-stdin',
|
|
31396
|
+
" $ mcp connections remove <serverId>"
|
|
31397
|
+
].join(`
|
|
31398
|
+
`));
|
|
31399
|
+
connections.command("list").description("List your stored upstream keys across servers (hints only)").action(runAction(async () => {
|
|
31400
|
+
const data = await api.get("/api/v1/upstream-key");
|
|
31401
|
+
if (isJsonMode()) {
|
|
31402
|
+
printJson(data);
|
|
31403
|
+
return;
|
|
31404
|
+
}
|
|
31405
|
+
printList(data.keys.map((entry) => ({
|
|
31406
|
+
server: entry.serverName ?? entry.serverId,
|
|
31407
|
+
key: `••••${entry.keyHint}`,
|
|
31408
|
+
status: entry.status,
|
|
31409
|
+
"last used": entry.lastUsedAt ? formatDate(entry.lastUsedAt) : "—",
|
|
31410
|
+
updated: formatDate(entry.updatedAt)
|
|
31411
|
+
})), [
|
|
31412
|
+
{ key: "server", label: "Server", width: 32 },
|
|
31413
|
+
{ key: "key", label: "Key", width: 10 },
|
|
31414
|
+
{ key: "status", label: "Status", width: 10 },
|
|
31415
|
+
{ key: "last used", label: "Last used", width: 22 },
|
|
31416
|
+
{ key: "updated", label: "Updated", width: 22 }
|
|
31417
|
+
], data);
|
|
31418
|
+
}));
|
|
31419
|
+
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) => {
|
|
31420
|
+
const apiKey = opts.keyStdin ? await readKeyFromStdin() : opts.key ?? "";
|
|
31421
|
+
if (!apiKey.trim()) {
|
|
31422
|
+
throw new Error("Provide the key via --key <apiKey> or pipe it with --key-stdin.");
|
|
31423
|
+
}
|
|
31424
|
+
const data = await api.post("/api/v1/upstream-key", { serverId, apiKey });
|
|
31425
|
+
if (isJsonMode()) {
|
|
31426
|
+
printJson(data);
|
|
31427
|
+
return;
|
|
31428
|
+
}
|
|
31429
|
+
printKeyValue({
|
|
31430
|
+
saved: String(data.saved),
|
|
31431
|
+
key: `••••${data.keyHint}`,
|
|
31432
|
+
note: "Stored encrypted; your next tool call uses it immediately."
|
|
31433
|
+
});
|
|
31434
|
+
}));
|
|
31435
|
+
connections.command("remove <serverId>").description("Revoke your stored upstream key for a server").action(runAction(async (serverId) => {
|
|
31436
|
+
const data = await api.delete("/api/v1/upstream-key", { serverId });
|
|
31437
|
+
if (isJsonMode()) {
|
|
31438
|
+
printJson(data);
|
|
31439
|
+
return;
|
|
31440
|
+
}
|
|
31441
|
+
printKeyValue({
|
|
31442
|
+
revoked: String(data.revoked),
|
|
31443
|
+
note: data.revoked ? "Tool calls will prompt for a fresh key on next use." : "No active key was stored for this server."
|
|
31444
|
+
});
|
|
31445
|
+
}));
|
|
31446
|
+
}
|
|
31447
|
+
|
|
31130
31448
|
// src/lib/plugins.ts
|
|
31131
31449
|
import { spawn as spawn7 } from "node:child_process";
|
|
31132
31450
|
import {
|
|
@@ -35733,7 +36051,7 @@ function registerUpdateCommand(program2) {
|
|
|
35733
36051
|
// src/create-program.ts
|
|
35734
36052
|
function createProgram() {
|
|
35735
36053
|
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
|
|
36054
|
+
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
36055
|
const opts = thisCommand.optsWithGlobals();
|
|
35738
36056
|
setJsonMode(Boolean(opts.json));
|
|
35739
36057
|
setBaseUrlOverride(opts.baseUrl);
|
|
@@ -35753,21 +36071,22 @@ function createProgram() {
|
|
|
35753
36071
|
}).addHelpText("after", [
|
|
35754
36072
|
"",
|
|
35755
36073
|
"Environment variables:",
|
|
35756
|
-
" MCPCLOUD_API_KEY API key used for authentication (
|
|
35757
|
-
" MCPCLOUD_BASE_URL API base URL (
|
|
36074
|
+
" MCPCLOUD_API_KEY API key used for authentication (or run `mcp login`)",
|
|
36075
|
+
" MCPCLOUD_BASE_URL Override the API base URL (defaults to MCPCloud production)",
|
|
35758
36076
|
" MCPCLOUD_ORG_ID Default organization ID",
|
|
35759
36077
|
' MCPCLOUD_PROFILE Active config profile name (default: "default")',
|
|
35760
36078
|
" CI When truthy, enables CI mode automatically (no color, ASCII glyphs, no prompts)",
|
|
35761
36079
|
"",
|
|
35762
36080
|
"Examples:",
|
|
35763
|
-
" $
|
|
35764
|
-
" $ mcp config set-url https://your-deployment.example.com # persist it",
|
|
35765
|
-
" $ mcp login",
|
|
36081
|
+
" $ mcp login # sign in (production, out of the box)",
|
|
35766
36082
|
" $ mcp whoami",
|
|
35767
36083
|
" $ mcp skills connect skill_123 --agent claude-code --apply",
|
|
35768
36084
|
" $ mcp --json servers list",
|
|
35769
36085
|
" $ mcp servers list --field id,name --format jsonl",
|
|
35770
|
-
" $ mcp servers list --filter status=active --no-table"
|
|
36086
|
+
" $ mcp servers list --filter status=active --no-table",
|
|
36087
|
+
"",
|
|
36088
|
+
"Self-host / staging: point the CLI elsewhere with MCPCLOUD_BASE_URL,",
|
|
36089
|
+
"`mcp config set-url <url>`, or --base-url <url> (each overrides the default)."
|
|
35771
36090
|
].join(`
|
|
35772
36091
|
`));
|
|
35773
36092
|
registerAuthCommands(program2);
|
|
@@ -35781,6 +36100,7 @@ function createProgram() {
|
|
|
35781
36100
|
registerDeploymentCommands(program2);
|
|
35782
36101
|
registerOrgCommands(program2);
|
|
35783
36102
|
registerUsageCommands(program2);
|
|
36103
|
+
registerConnectionsCommands(program2);
|
|
35784
36104
|
registerMetricsCommands(program2);
|
|
35785
36105
|
registerDevCommand(program2);
|
|
35786
36106
|
registerCompletionCommand(program2);
|
|
@@ -35798,8 +36118,59 @@ function createProgram() {
|
|
|
35798
36118
|
return program2;
|
|
35799
36119
|
}
|
|
35800
36120
|
|
|
36121
|
+
// src/lib/first-run.ts
|
|
36122
|
+
function shouldShowFirstRunGreeting(input) {
|
|
36123
|
+
if (input.hasCommand)
|
|
36124
|
+
return false;
|
|
36125
|
+
if (!input.isTty)
|
|
36126
|
+
return false;
|
|
36127
|
+
if (input.ciMode)
|
|
36128
|
+
return false;
|
|
36129
|
+
if (input.jsonMode)
|
|
36130
|
+
return false;
|
|
36131
|
+
if (input.authenticated)
|
|
36132
|
+
return false;
|
|
36133
|
+
if (input.hasSavedConfig)
|
|
36134
|
+
return false;
|
|
36135
|
+
return true;
|
|
36136
|
+
}
|
|
36137
|
+
function argvHasCommand(argv) {
|
|
36138
|
+
return argv.slice(2).some((token) => !token.startsWith("-"));
|
|
36139
|
+
}
|
|
36140
|
+
function firstRunGreeting() {
|
|
36141
|
+
return [
|
|
36142
|
+
"Welcome to MCPCloud — turn an API spec into a deployed MCP server.",
|
|
36143
|
+
"",
|
|
36144
|
+
"Get started:",
|
|
36145
|
+
" 1. mcp login Sign in (opens your browser)",
|
|
36146
|
+
" 2. mcp init Scaffold a server from an OpenAPI / GraphQL spec",
|
|
36147
|
+
" 3. mcp servers deploy Ship it to the edge",
|
|
36148
|
+
"",
|
|
36149
|
+
"Run `mcp --help` for the full command reference.",
|
|
36150
|
+
""
|
|
36151
|
+
].join(`
|
|
36152
|
+
`);
|
|
36153
|
+
}
|
|
36154
|
+
|
|
35801
36155
|
// src/index.ts
|
|
36156
|
+
function maybeGreetFirstRun(argv) {
|
|
36157
|
+
const cfg = readConfig();
|
|
36158
|
+
const show = shouldShowFirstRunGreeting({
|
|
36159
|
+
hasCommand: argvHasCommand(argv),
|
|
36160
|
+
isTty: Boolean(process.stdout.isTTY),
|
|
36161
|
+
ciMode: isCiEnv() || argv.includes("--ci"),
|
|
36162
|
+
jsonMode: argv.includes("--json"),
|
|
36163
|
+
authenticated: Boolean(getApiKey()),
|
|
36164
|
+
hasSavedConfig: Boolean(cfg.baseUrl || cfg.apiKey || cfg.defaultOrganizationId)
|
|
36165
|
+
});
|
|
36166
|
+
if (!show)
|
|
36167
|
+
return false;
|
|
36168
|
+
process.stdout.write(firstRunGreeting());
|
|
36169
|
+
return true;
|
|
36170
|
+
}
|
|
35802
36171
|
async function main() {
|
|
36172
|
+
if (maybeGreetFirstRun(process.argv))
|
|
36173
|
+
return;
|
|
35803
36174
|
const program2 = createProgram();
|
|
35804
36175
|
if (process.env["MCPSH_DISABLE_PLUGINS"] !== "1") {
|
|
35805
36176
|
await loadPlugins(program2);
|