@mcpcloud/cli 0.11.0 → 0.12.0-next-20260721180156
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 +1 -0
- package/dist/index.js +126 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -159,6 +159,7 @@ _Generated from the live command tree by `bun run docs:readme` — do not edit b
|
|
|
159
159
|
|
|
160
160
|
| Command | Description |
|
|
161
161
|
| --- | --- |
|
|
162
|
+
| `mcp analytics` | Show the workspace analytics rollup (readiness, funnel, usage) |
|
|
162
163
|
| `mcp completion <shell>` | Print a shell completion script (bash, zsh, or fish) |
|
|
163
164
|
| `mcp doctor` | Diagnose the CLI environment: Node, config, base URL, API key, claude CLI, and updates |
|
|
164
165
|
| `mcp help [topic]` | Show extended help for a topic (auth, profiles, agents, errors) |
|
package/dist/index.js
CHANGED
|
@@ -4919,6 +4919,10 @@ var ERROR_REMEDIATION = {
|
|
|
4919
4919
|
organization_access_required: "Not a member of that organization — a stale saved org is the usual cause after switching accounts. Run `mcp orgs list`, then `mcp config set-org <name>`.",
|
|
4920
4920
|
organization_mismatch: "The deployment belongs to a different org. Pass --org <correct-id>.",
|
|
4921
4921
|
insufficient_role: "Your role doesn't permit this action. Contact an org owner.",
|
|
4922
|
+
insufficient_scope: "This key is scoped. Mint one with the needed scope (`mcp api-keys create --scope <scope>` or `--preset <preset>`), or rotate from a signed-in browser session to widen.",
|
|
4923
|
+
scope_widening_requires_session: "Widening a key must be done from a signed-in browser session (Settings → API keys). Narrowing works from anywhere.",
|
|
4924
|
+
api_key_org_mismatch: "This key is pinned to a different organization. Pass the pinned org, or mint an unpinned key.",
|
|
4925
|
+
invalid_organization: "Pass an organization you belong to — check `mcp orgs list`.",
|
|
4922
4926
|
resource_not_found: "No such resource for this org. Verify the ID with the matching `list` command.",
|
|
4923
4927
|
project_id_required: "Pass --project <id> or look up tools via `mcp tools list --server <id>`.",
|
|
4924
4928
|
deployment_id_required: "Pass a deploymentId. List with `mcp servers list` to find one.",
|
|
@@ -5022,6 +5026,16 @@ function handleError(err) {
|
|
|
5022
5026
|
if (info.status)
|
|
5023
5027
|
parts.push(`(status: ${info.status})`);
|
|
5024
5028
|
printError(parts.join(" "));
|
|
5029
|
+
if (info.code === "insufficient_scope" && info.details) {
|
|
5030
|
+
const required = info.details["requiredScopes"];
|
|
5031
|
+
const granted = info.details["grantedScopes"];
|
|
5032
|
+
if (Array.isArray(required) && required.length > 0) {
|
|
5033
|
+
console.error(` this key lacks: ${required.join(" or ")}`);
|
|
5034
|
+
}
|
|
5035
|
+
if (Array.isArray(granted)) {
|
|
5036
|
+
console.error(` it has: ${granted.length > 0 ? granted.join(" ") : "(no scopes)"}`);
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5025
5039
|
const remediation = getRemediation(info.code);
|
|
5026
5040
|
if (remediation)
|
|
5027
5041
|
console.error(` ${remediation}`);
|
|
@@ -11380,6 +11394,26 @@ function parseExpiresInDays(raw) {
|
|
|
11380
11394
|
function formatPreview(key) {
|
|
11381
11395
|
return `${key.keyPrefix}…${key.lastFour}`;
|
|
11382
11396
|
}
|
|
11397
|
+
function formatGrants(key) {
|
|
11398
|
+
if (key.scopes == null)
|
|
11399
|
+
return key.preset === "full" ? "full" : "full (legacy)";
|
|
11400
|
+
if (key.preset && key.preset !== "custom")
|
|
11401
|
+
return key.preset;
|
|
11402
|
+
return `custom (${key.scopes.length})`;
|
|
11403
|
+
}
|
|
11404
|
+
function collectScope(value, previous) {
|
|
11405
|
+
return [...previous, value];
|
|
11406
|
+
}
|
|
11407
|
+
function grantBodyFromOptions(opts) {
|
|
11408
|
+
if (opts.preset && opts.scope && opts.scope.length > 0) {
|
|
11409
|
+
throw new Error("--preset and --scope are mutually exclusive.");
|
|
11410
|
+
}
|
|
11411
|
+
return {
|
|
11412
|
+
...opts.preset ? { preset: opts.preset } : {},
|
|
11413
|
+
...opts.scope && opts.scope.length > 0 ? { scopes: opts.scope } : {},
|
|
11414
|
+
...opts.clearOrg ? { orgId: null } : opts.org ? { orgId: opts.org } : {}
|
|
11415
|
+
};
|
|
11416
|
+
}
|
|
11383
11417
|
var API_KEY_KIND = { label: "api key", listHint: "mcp api-keys list" };
|
|
11384
11418
|
async function resolveApiKeyId(reference) {
|
|
11385
11419
|
const ref = reference.trim();
|
|
@@ -11406,26 +11440,29 @@ function registerApiKeyCommands(program2) {
|
|
|
11406
11440
|
id: k2.id,
|
|
11407
11441
|
name: k2.name,
|
|
11408
11442
|
preview: formatPreview(k2),
|
|
11409
|
-
|
|
11443
|
+
grants: formatGrants(k2),
|
|
11444
|
+
org: k2.orgId ?? "—",
|
|
11410
11445
|
"last used": k2.lastUsedAt ? formatDate(k2.lastUsedAt) : "—",
|
|
11411
11446
|
expires: k2.expiresAt ? formatDate(k2.expiresAt) : "never"
|
|
11412
11447
|
})), [
|
|
11413
11448
|
{ key: "id", label: "ID", width: 20 },
|
|
11414
11449
|
{ key: "name", label: "Name", width: 24 },
|
|
11415
11450
|
{ key: "preview", label: "Preview", width: 18 },
|
|
11416
|
-
{ key: "
|
|
11451
|
+
{ key: "grants", label: "Grants", width: 14 },
|
|
11452
|
+
{ key: "org", label: "Org", width: 20 },
|
|
11417
11453
|
{ key: "last used", label: "Last Used", width: 22 },
|
|
11418
11454
|
{ key: "expires", label: "Expires", width: 22 }
|
|
11419
11455
|
], data);
|
|
11420
11456
|
}));
|
|
11421
|
-
apiKeys.command("create").description("Create a new API key (the secret is shown once)").requiredOption("--name <name>", "Display name for the key").option("--expires-in <duration>", "Auto-expire the key after this long: 90d, 12w, 6m, or a day count (1–365). Default: never.").action(runAction(async (opts) => {
|
|
11457
|
+
apiKeys.command("create").description("Create a new API key (the secret is shown once)").requiredOption("--name <name>", "Display name for the key").option("--expires-in <duration>", "Auto-expire the key after this long: 90d, 12w, 6m, or a day count (1–365). Default: never.").option("--preset <preset>", "Scope preset: full, agent, read-only, or ci-deploy. Default: full authority.").option("--scope <scope>", "Grant a single scope (repeatable, e.g. --scope servers:read --scope servers:deploy). Mutually exclusive with --preset.", collectScope, []).option("--org <orgId>", "Pin the key to one organization (must be one you belong to).").action(runAction(async (opts) => {
|
|
11422
11458
|
const name = opts.name.trim();
|
|
11423
11459
|
if (!name)
|
|
11424
11460
|
throw new Error("--name must not be empty.");
|
|
11425
11461
|
const expiresInDays = parseExpiresInDays(opts.expiresIn);
|
|
11426
11462
|
const data = await api.post("/api/v1/api-keys", {
|
|
11427
11463
|
name,
|
|
11428
|
-
...expiresInDays !== undefined ? { expiresInDays } : {}
|
|
11464
|
+
...expiresInDays !== undefined ? { expiresInDays } : {},
|
|
11465
|
+
...grantBodyFromOptions(opts)
|
|
11429
11466
|
});
|
|
11430
11467
|
if (isJsonMode()) {
|
|
11431
11468
|
printJson(data);
|
|
@@ -11439,6 +11476,9 @@ function registerApiKeyCommands(program2) {
|
|
|
11439
11476
|
key: data.apiKey,
|
|
11440
11477
|
"key prefix": data.keyPrefix,
|
|
11441
11478
|
"last four": data.lastFour,
|
|
11479
|
+
grants: formatGrants(data),
|
|
11480
|
+
scopes: data.scopes?.join(" ") ?? "all (full authority)",
|
|
11481
|
+
org: data.orgId ?? "any you belong to",
|
|
11442
11482
|
expires: data.expiresAt ? formatDate(data.expiresAt) : "never"
|
|
11443
11483
|
});
|
|
11444
11484
|
console.log("");
|
|
@@ -11457,27 +11497,36 @@ function registerApiKeyCommands(program2) {
|
|
|
11457
11497
|
name: k2.name,
|
|
11458
11498
|
preview: formatPreview(k2),
|
|
11459
11499
|
status: k2.status,
|
|
11500
|
+
grants: formatGrants(k2),
|
|
11501
|
+
scopes: k2.scopes?.join(" ") ?? "all (full authority)",
|
|
11502
|
+
org: k2.orgId ?? "any you belong to",
|
|
11460
11503
|
created: formatDate(k2.createdAt),
|
|
11461
11504
|
"last used": k2.lastUsedAt ? formatDate(k2.lastUsedAt) : "—",
|
|
11462
11505
|
expires: k2.expiresAt ? formatDate(k2.expiresAt) : "never",
|
|
11463
11506
|
revoked: k2.revokedAt ? formatDate(k2.revokedAt) : "—"
|
|
11464
11507
|
});
|
|
11465
11508
|
}));
|
|
11466
|
-
apiKeys.command("rotate <apiKey>").description("Mint a replacement key and grace-expire the old one (24h window; accepts id or name). The new secret is shown once.").option("--expires-in <duration>", "Expiry for the NEW key: 90d, 12w, 6m, or a day count (1–365). Default: never.").addHelpText("after", [
|
|
11509
|
+
apiKeys.command("rotate <apiKey>").description("Mint a replacement key and grace-expire the old one (24h window; accepts id or name). The new secret is shown once.").option("--expires-in <duration>", "Expiry for the NEW key: 90d, 12w, 6m, or a day count (1–365). Default: never.").option("--preset <preset>", "Re-scope the new key to a preset (narrowing works from anywhere; widening needs a browser session).").option("--scope <scope>", "Re-scope the new key to explicit scopes (repeatable). Mutually exclusive with --preset.", collectScope, []).option("--org <orgId>", "Pin the new key to one organization.").option("--clear-org", "Remove the organization pin (requires a browser session).").addHelpText("after", [
|
|
11467
11510
|
"",
|
|
11468
11511
|
"The old key keeps working for 24h so running automation can swap to the",
|
|
11469
11512
|
"new secret before the old one stops. Update your secrets within that window.",
|
|
11470
11513
|
"",
|
|
11514
|
+
"Scopes: omitted flags inherit the old key’s grants. Narrowing (fewer",
|
|
11515
|
+
"scopes, adding an org pin) works from any credential; widening (more",
|
|
11516
|
+
"scopes, clearing/changing a pin) must come from a signed-in session.",
|
|
11517
|
+
"",
|
|
11471
11518
|
"Examples:",
|
|
11472
11519
|
" $ mcp api-keys rotate key_123",
|
|
11473
|
-
' $ mcp api-keys rotate "CI deploy key" --expires-in 90d'
|
|
11520
|
+
' $ mcp api-keys rotate "CI deploy key" --expires-in 90d',
|
|
11521
|
+
" $ mcp api-keys rotate ci --preset ci-deploy --org org_abc"
|
|
11474
11522
|
].join(`
|
|
11475
11523
|
`)).action(runAction(async (apiKeyRef, opts) => {
|
|
11476
11524
|
const apiKeyId = await resolveApiKeyId(apiKeyRef);
|
|
11477
11525
|
const expiresInDays = parseExpiresInDays(opts.expiresIn);
|
|
11478
11526
|
const data = await api.patch("/api/v1/api-keys", {
|
|
11479
11527
|
apiKeyId,
|
|
11480
|
-
...expiresInDays !== undefined ? { expiresInDays } : {}
|
|
11528
|
+
...expiresInDays !== undefined ? { expiresInDays } : {},
|
|
11529
|
+
...grantBodyFromOptions(opts)
|
|
11481
11530
|
});
|
|
11482
11531
|
if (isJsonMode()) {
|
|
11483
11532
|
printJson(data);
|
|
@@ -11491,6 +11540,9 @@ function registerApiKeyCommands(program2) {
|
|
|
11491
11540
|
key: data.apiKey,
|
|
11492
11541
|
"key prefix": data.keyPrefix,
|
|
11493
11542
|
"last four": data.lastFour,
|
|
11543
|
+
grants: formatGrants(data),
|
|
11544
|
+
scopes: data.scopes?.join(" ") ?? "all (full authority)",
|
|
11545
|
+
org: data.orgId ?? "any you belong to",
|
|
11494
11546
|
expires: data.expiresAt ? formatDate(data.expiresAt) : "never",
|
|
11495
11547
|
"old key": data.rotatedFrom?.apiKeyId ?? "—",
|
|
11496
11548
|
"old key valid until": data.rotatedFrom ? formatDate(data.rotatedFrom.gracePeriodEndsAt) : "—"
|
|
@@ -32931,6 +32983,72 @@ function registerOrgCommands(program2) {
|
|
|
32931
32983
|
}));
|
|
32932
32984
|
}
|
|
32933
32985
|
|
|
32986
|
+
// src/commands/analytics.ts
|
|
32987
|
+
function parseWindowDays2(raw) {
|
|
32988
|
+
if (!raw)
|
|
32989
|
+
return;
|
|
32990
|
+
const match = /^(\d{1,2})d?$/.exec(raw.trim());
|
|
32991
|
+
const days = match ? Number.parseInt(match[1], 10) : Number.NaN;
|
|
32992
|
+
if (!Number.isFinite(days) || days < 1 || days > 90) {
|
|
32993
|
+
throw new Error("--since must be a day count between 1 and 90 (e.g. 14d).");
|
|
32994
|
+
}
|
|
32995
|
+
return days;
|
|
32996
|
+
}
|
|
32997
|
+
function registerAnalyticsCommands(program2) {
|
|
32998
|
+
program2.command("analytics").description("Show the workspace analytics rollup (readiness, funnel, usage)").option("--org <organizationId>", "Organization ID").option("--since <window>", "Window in days over closed days (1–90, default 14)").addHelpText("after", [
|
|
32999
|
+
"",
|
|
33000
|
+
"Examples:",
|
|
33001
|
+
" $ mcp analytics # 14-day rollup",
|
|
33002
|
+
" $ mcp analytics --since 30d",
|
|
33003
|
+
" $ mcp --json analytics | jq '.summary.usage.totals'"
|
|
33004
|
+
].join(`
|
|
33005
|
+
`)).action(runAction(async (opts) => {
|
|
33006
|
+
const orgId = await resolveOrgId(opts.org);
|
|
33007
|
+
const windowDays = parseWindowDays2(opts.since);
|
|
33008
|
+
const data = await api.get("/api/v1/analytics/summary", {
|
|
33009
|
+
organizationId: orgId,
|
|
33010
|
+
...windowDays ? { windowDays: String(windowDays) } : {}
|
|
33011
|
+
});
|
|
33012
|
+
if (isJsonMode()) {
|
|
33013
|
+
printJson(data);
|
|
33014
|
+
return;
|
|
33015
|
+
}
|
|
33016
|
+
const { summary } = data;
|
|
33017
|
+
printKeyValue({
|
|
33018
|
+
Window: `${summary.windowDays}d (closed days)`,
|
|
33019
|
+
Servers: `${summary.readiness.total} total · ${summary.readiness.built} built · ${summary.readiness.deployed} deployed · ${summary.readiness.called} called`,
|
|
33020
|
+
Status: `${summary.status.live} live · ${summary.status.draft} draft · ${summary.status.needsAttention} need attention`,
|
|
33021
|
+
Funnel: `imported ${summary.funnel.imported} → built ${summary.funnel.built} → deployed ${summary.funnel.deployed} → installed ${summary.funnel.installed} → first call ${summary.funnel.firstCall}`,
|
|
33022
|
+
Projects: String(summary.projectsTotal)
|
|
33023
|
+
});
|
|
33024
|
+
if (!summary.usage) {
|
|
33025
|
+
console.log(`
|
|
33026
|
+
Usage detail requires admin organization access.`);
|
|
33027
|
+
return;
|
|
33028
|
+
}
|
|
33029
|
+
const { usage } = summary;
|
|
33030
|
+
printKeyValue({
|
|
33031
|
+
"Tool calls": `${usage.totals.toolCalls} (${usage.totals.toolErrors} errors · ${(usage.totals.errorRate * 100).toFixed(1)}%)`,
|
|
33032
|
+
Latency: `p50 ${usage.latency.p50Ms ?? "—"}ms · p95 ${usage.latency.p95Ms ?? "—"}ms`,
|
|
33033
|
+
Edge: `${usage.edge.requests} requests · ${usage.edge.unauthorized} unauthorized · ${usage.edge.rateLimited} rate-limited · ${usage.edge.failed} failed`,
|
|
33034
|
+
"Projection savings": `${usage.projectionSavedBytes} bytes`
|
|
33035
|
+
});
|
|
33036
|
+
if (usage.topTools.length) {
|
|
33037
|
+
printList(usage.topTools, [
|
|
33038
|
+
{ key: "toolName", label: "Tool", width: 36 },
|
|
33039
|
+
{ key: "calls", label: "Calls", width: 8 },
|
|
33040
|
+
{ key: "errors", label: "Errors", width: 8 }
|
|
33041
|
+
], usage.topTools);
|
|
33042
|
+
}
|
|
33043
|
+
if (usage.topServers.length) {
|
|
33044
|
+
printList(usage.topServers, [
|
|
33045
|
+
{ key: "name", label: "Server", width: 36 },
|
|
33046
|
+
{ key: "requests", label: "Requests", width: 10 }
|
|
33047
|
+
], usage.topServers);
|
|
33048
|
+
}
|
|
33049
|
+
}));
|
|
33050
|
+
}
|
|
33051
|
+
|
|
32934
33052
|
// src/commands/connections.ts
|
|
32935
33053
|
async function readKeyFromStdin() {
|
|
32936
33054
|
const chunks = [];
|
|
@@ -37656,6 +37774,7 @@ function createProgram() {
|
|
|
37656
37774
|
registerDeploymentCommands(program2);
|
|
37657
37775
|
registerOrgCommands(program2);
|
|
37658
37776
|
registerUsageCommands(program2);
|
|
37777
|
+
registerAnalyticsCommands(program2);
|
|
37659
37778
|
registerConnectionsCommands(program2);
|
|
37660
37779
|
registerMetricsCommands(program2);
|
|
37661
37780
|
registerDevCommand(program2);
|
package/package.json
CHANGED