@mcpcloud/cli 0.4.0 → 0.5.0
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/dist/index.js +1923 -797
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2548,8 +2548,31 @@ function isJsonMode() {
|
|
|
2548
2548
|
return jsonMode;
|
|
2549
2549
|
}
|
|
2550
2550
|
var ciMode = false;
|
|
2551
|
+
var nonInteractive = false;
|
|
2552
|
+
var quiet = false;
|
|
2551
2553
|
var warningsAsErrors = false;
|
|
2552
2554
|
var warningCount = 0;
|
|
2555
|
+
|
|
2556
|
+
class CliExitError extends Error {
|
|
2557
|
+
code;
|
|
2558
|
+
constructor(code, message) {
|
|
2559
|
+
super(message ?? "");
|
|
2560
|
+
this.code = code;
|
|
2561
|
+
this.name = "CliExitError";
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
function setNonInteractive(enabled) {
|
|
2565
|
+
nonInteractive = enabled;
|
|
2566
|
+
}
|
|
2567
|
+
function isNonInteractive() {
|
|
2568
|
+
return nonInteractive || ciMode;
|
|
2569
|
+
}
|
|
2570
|
+
function setQuiet(enabled) {
|
|
2571
|
+
quiet = enabled;
|
|
2572
|
+
}
|
|
2573
|
+
function isQuiet() {
|
|
2574
|
+
return quiet;
|
|
2575
|
+
}
|
|
2553
2576
|
function setCiMode(enabled) {
|
|
2554
2577
|
ciMode = enabled;
|
|
2555
2578
|
}
|
|
@@ -2680,6 +2703,12 @@ var c = {
|
|
|
2680
2703
|
function printJson(data) {
|
|
2681
2704
|
console.log(JSON.stringify(data, null, 2));
|
|
2682
2705
|
}
|
|
2706
|
+
function printJsonLine(data) {
|
|
2707
|
+
console.log(JSON.stringify(data));
|
|
2708
|
+
}
|
|
2709
|
+
function printLine(text) {
|
|
2710
|
+
console.log(text);
|
|
2711
|
+
}
|
|
2683
2712
|
function printTable(rows, columns) {
|
|
2684
2713
|
if (rows.length === 0) {
|
|
2685
2714
|
console.log("No results.");
|
|
@@ -2756,11 +2785,11 @@ function printKeyValue(record) {
|
|
|
2756
2785
|
}
|
|
2757
2786
|
}
|
|
2758
2787
|
function printSuccess(msg) {
|
|
2759
|
-
if (!jsonMode)
|
|
2788
|
+
if (!jsonMode && !quiet)
|
|
2760
2789
|
console.log(`${c.green(glyph("ok"))} ${msg}`);
|
|
2761
2790
|
}
|
|
2762
2791
|
function printInfo(msg) {
|
|
2763
|
-
if (!jsonMode)
|
|
2792
|
+
if (!jsonMode && !quiet)
|
|
2764
2793
|
console.log(msg);
|
|
2765
2794
|
}
|
|
2766
2795
|
function printError(msg) {
|
|
@@ -2772,7 +2801,7 @@ function printWarn(msg) {
|
|
|
2772
2801
|
console.log(`${c.yellow(glyph("warn"))} ${msg}`);
|
|
2773
2802
|
}
|
|
2774
2803
|
function printStep(msg) {
|
|
2775
|
-
if (!jsonMode)
|
|
2804
|
+
if (!jsonMode && !quiet)
|
|
2776
2805
|
console.log(`${c.cyan(glyph("step"))} ${msg}`);
|
|
2777
2806
|
}
|
|
2778
2807
|
function formatDate(ms) {
|
|
@@ -2786,6 +2815,7 @@ var ERROR_REMEDIATION = {
|
|
|
2786
2815
|
api_key_invalid: "The supplied key is invalid or revoked. Run `mcp login`.",
|
|
2787
2816
|
revoked_api_key: "This key was revoked. Run `mcp login` to mint a new one.",
|
|
2788
2817
|
expired_token: "Your session expired. Re-run the command (the CLI will refresh it).",
|
|
2818
|
+
expired_api_key: "This API key expired. Run `mcp api-keys rotate <id>` (or `mcp login`) to mint a fresh one.",
|
|
2789
2819
|
api_key_name_required: "Pass --name <…> when creating an API key.",
|
|
2790
2820
|
api_key_id_required: "Pass an apiKeyId. List existing keys with `mcp api-keys list`.",
|
|
2791
2821
|
api_key_not_found: "No such API key for this user. Run `mcp api-keys list` to see live IDs.",
|
|
@@ -2868,6 +2898,11 @@ function describeError(err) {
|
|
|
2868
2898
|
return { message: String(err) };
|
|
2869
2899
|
}
|
|
2870
2900
|
function handleError(err) {
|
|
2901
|
+
if (err instanceof CliExitError) {
|
|
2902
|
+
if (err.message)
|
|
2903
|
+
printError(err.message);
|
|
2904
|
+
process.exit(err.code);
|
|
2905
|
+
}
|
|
2871
2906
|
const info = describeError(err);
|
|
2872
2907
|
if (jsonMode) {
|
|
2873
2908
|
printJson({ error: info });
|
|
@@ -3518,6 +3553,17 @@ function registerAuthCommands(program2) {
|
|
|
3518
3553
|
}));
|
|
3519
3554
|
}
|
|
3520
3555
|
|
|
3556
|
+
// src/lib/delete-confirm.ts
|
|
3557
|
+
function assertConfirmMatches(args) {
|
|
3558
|
+
const provided = args.confirm?.trim();
|
|
3559
|
+
if (!provided) {
|
|
3560
|
+
throw new Error(`Refusing to delete ${args.entityKind} "${args.entityName}". Re-run with --confirm "${args.entityName}" to proceed.`);
|
|
3561
|
+
}
|
|
3562
|
+
if (provided !== args.entityName) {
|
|
3563
|
+
throw new Error(`--confirm "${provided}" does not match the ${args.entityKind} name "${args.entityName}". Deletion aborted.`);
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3521
3567
|
// src/lib/org.ts
|
|
3522
3568
|
class NoOrganizationError extends Error {
|
|
3523
3569
|
constructor() {
|
|
@@ -3734,6 +3780,29 @@ function registerProjectCommands(program2) {
|
|
|
3734
3780
|
updated: formatDate(s.updatedAt)
|
|
3735
3781
|
});
|
|
3736
3782
|
}));
|
|
3783
|
+
projects.command("delete <projectId>").description('Permanently delete a project. Requires --confirm "<exact name>" and that the project is empty of servers/skills.').requiredOption("--confirm <name>", "The exact project name, typed to confirm the destructive action").option("--org <organizationId>", "Organization ID").addHelpText("after", [
|
|
3784
|
+
"",
|
|
3785
|
+
"Examples:",
|
|
3786
|
+
' $ mcp projects delete proj_123 --confirm "Acme APIs"'
|
|
3787
|
+
].join(`
|
|
3788
|
+
`)).action(runAction(async (projectId, opts) => {
|
|
3789
|
+
const orgId = await resolveOrgId(opts.org);
|
|
3790
|
+
const detail = await api.get("/api/v1/project", {
|
|
3791
|
+
organizationId: orgId,
|
|
3792
|
+
projectId
|
|
3793
|
+
});
|
|
3794
|
+
assertConfirmMatches({
|
|
3795
|
+
entityKind: "project",
|
|
3796
|
+
entityName: detail.project.name,
|
|
3797
|
+
confirm: opts.confirm
|
|
3798
|
+
});
|
|
3799
|
+
const data = await api.delete("/api/v1/project", { organizationId: orgId, projectId });
|
|
3800
|
+
if (isJsonMode()) {
|
|
3801
|
+
printJson(data);
|
|
3802
|
+
return;
|
|
3803
|
+
}
|
|
3804
|
+
printSuccess(`Deleted project ${data.deleted.name} (${data.deleted.id}).`);
|
|
3805
|
+
}));
|
|
3737
3806
|
}
|
|
3738
3807
|
|
|
3739
3808
|
// src/lib/duration.ts
|
|
@@ -3761,6 +3830,12 @@ function parseSince(raw, now = Date.now) {
|
|
|
3761
3830
|
}
|
|
3762
3831
|
|
|
3763
3832
|
// src/lib/deployment-events.ts
|
|
3833
|
+
function emitEvent(event) {
|
|
3834
|
+
if (isJsonMode())
|
|
3835
|
+
printJsonLine(event);
|
|
3836
|
+
else
|
|
3837
|
+
printLine(formatEventLine(event));
|
|
3838
|
+
}
|
|
3764
3839
|
var TERMINAL_STAGES = new Set([
|
|
3765
3840
|
"active",
|
|
3766
3841
|
"failed",
|
|
@@ -3841,18 +3916,14 @@ async function tailDeploymentLogs(args) {
|
|
|
3841
3916
|
return;
|
|
3842
3917
|
}
|
|
3843
3918
|
for (const event of initial.events)
|
|
3844
|
-
|
|
3919
|
+
printLine(formatEventLine(event));
|
|
3845
3920
|
return;
|
|
3846
3921
|
}
|
|
3847
3922
|
if (!isJsonMode() && initial.events.length > 0) {
|
|
3848
3923
|
printStep(`Tailing ${c.bold(args.deploymentId)} (Ctrl-C to stop)…`);
|
|
3849
3924
|
}
|
|
3850
|
-
for (const event of initial.events)
|
|
3851
|
-
|
|
3852
|
-
console.log(JSON.stringify(event));
|
|
3853
|
-
else
|
|
3854
|
-
console.log(formatEventLine(event));
|
|
3855
|
-
}
|
|
3925
|
+
for (const event of initial.events)
|
|
3926
|
+
emitEvent(event);
|
|
3856
3927
|
const lastTimestamp = initial.events[initial.events.length - 1]?.timestamp ?? initial.nextCursor ?? sinceMs ?? Date.now();
|
|
3857
3928
|
const controller = new AbortController;
|
|
3858
3929
|
const onSigint = () => controller.abort();
|
|
@@ -3865,12 +3936,7 @@ async function tailDeploymentLogs(args) {
|
|
|
3865
3936
|
since: lastTimestamp,
|
|
3866
3937
|
isDone: () => false,
|
|
3867
3938
|
totalTimeoutMs,
|
|
3868
|
-
onEvent:
|
|
3869
|
-
if (isJsonMode())
|
|
3870
|
-
console.log(JSON.stringify(event));
|
|
3871
|
-
else
|
|
3872
|
-
console.log(formatEventLine(event));
|
|
3873
|
-
},
|
|
3939
|
+
onEvent: emitEvent,
|
|
3874
3940
|
signal: controller.signal
|
|
3875
3941
|
});
|
|
3876
3942
|
} finally {
|
|
@@ -4063,7 +4129,7 @@ async function resolveSetValue(args) {
|
|
|
4063
4129
|
const decision = decideSetValueSource(args);
|
|
4064
4130
|
if (decision.kind === "error") {
|
|
4065
4131
|
printError(decision.message);
|
|
4066
|
-
|
|
4132
|
+
throw new CliExitError(1);
|
|
4067
4133
|
}
|
|
4068
4134
|
if (decision.kind === "positional")
|
|
4069
4135
|
return decision.value;
|
|
@@ -4071,7 +4137,7 @@ async function resolveSetValue(args) {
|
|
|
4071
4137
|
const path = resolve(process.cwd(), decision.path);
|
|
4072
4138
|
if (!existsSync3(path)) {
|
|
4073
4139
|
printError(`--from-file path not found: ${path}`);
|
|
4074
|
-
|
|
4140
|
+
throw new CliExitError(1);
|
|
4075
4141
|
}
|
|
4076
4142
|
return readFileSync3(path, "utf-8");
|
|
4077
4143
|
}
|
|
@@ -4116,7 +4182,7 @@ async function runUnsetEnv(args) {
|
|
|
4116
4182
|
const ok = await confirmUnset(args.bindingName, args.serverId);
|
|
4117
4183
|
if (!ok) {
|
|
4118
4184
|
printInfo(c.dim("Aborted."));
|
|
4119
|
-
|
|
4185
|
+
throw new CliExitError(1);
|
|
4120
4186
|
}
|
|
4121
4187
|
}
|
|
4122
4188
|
const orgId = await resolveOrgId(args.orgOverride);
|
|
@@ -4439,7 +4505,7 @@ function registerServerLifecycleCommands(servers) {
|
|
|
4439
4505
|
if (isJsonMode()) {
|
|
4440
4506
|
printJson({ deployment: dep, waitDone, terminalEvent });
|
|
4441
4507
|
if (opts.wait && !waitDone)
|
|
4442
|
-
|
|
4508
|
+
throw new CliExitError(1);
|
|
4443
4509
|
return;
|
|
4444
4510
|
}
|
|
4445
4511
|
printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
|
|
@@ -4456,7 +4522,7 @@ function registerServerLifecycleCommands(servers) {
|
|
|
4456
4522
|
} : {}
|
|
4457
4523
|
});
|
|
4458
4524
|
if (opts.wait && !waitDone)
|
|
4459
|
-
|
|
4525
|
+
throw new CliExitError(1);
|
|
4460
4526
|
}));
|
|
4461
4527
|
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", [
|
|
4462
4528
|
"",
|
|
@@ -4575,7 +4641,7 @@ function registerServerLifecycleCommands(servers) {
|
|
|
4575
4641
|
terminalEvent
|
|
4576
4642
|
});
|
|
4577
4643
|
if (opts.wait && !waitDone)
|
|
4578
|
-
|
|
4644
|
+
throw new CliExitError(1);
|
|
4579
4645
|
return;
|
|
4580
4646
|
}
|
|
4581
4647
|
printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
|
|
@@ -4592,7 +4658,7 @@ function registerServerLifecycleCommands(servers) {
|
|
|
4592
4658
|
} : {}
|
|
4593
4659
|
});
|
|
4594
4660
|
if (opts.wait && !waitDone)
|
|
4595
|
-
|
|
4661
|
+
throw new CliExitError(1);
|
|
4596
4662
|
}));
|
|
4597
4663
|
}
|
|
4598
4664
|
|
|
@@ -4759,6 +4825,212 @@ function registerServerMutationCommands(servers) {
|
|
|
4759
4825
|
"resumed at": formatDate(d.resumedAt)
|
|
4760
4826
|
});
|
|
4761
4827
|
}));
|
|
4828
|
+
servers.command("delete <serverId>").description('Permanently delete a server and all its data. Requires --confirm "<exact name>".').requiredOption("--confirm <name>", "The exact server name, typed to confirm the destructive action").option("--org <organizationId>", "Organization ID").option("--force", "Delete even if a Worker is still live (skips the active-deployment guard)").addHelpText("after", [
|
|
4829
|
+
"",
|
|
4830
|
+
"This cascades: tools, versions, deployments, events, OAuth connections,",
|
|
4831
|
+
"test runs, rollups, and the dispatch KV / dedicated-container instance.",
|
|
4832
|
+
"",
|
|
4833
|
+
"Examples:",
|
|
4834
|
+
' $ mcp servers delete srv_123 --confirm "Stripe MCP"',
|
|
4835
|
+
' $ mcp servers delete srv_123 --confirm "Stripe MCP" --force'
|
|
4836
|
+
].join(`
|
|
4837
|
+
`)).action(runAction(async (serverId, opts) => {
|
|
4838
|
+
const orgId = await resolveOrgId(opts.org);
|
|
4839
|
+
const detail = await api.get("/api/v1/server", {
|
|
4840
|
+
organizationId: orgId,
|
|
4841
|
+
serverId
|
|
4842
|
+
});
|
|
4843
|
+
assertConfirmMatches({
|
|
4844
|
+
entityKind: "server",
|
|
4845
|
+
entityName: detail.server.name,
|
|
4846
|
+
confirm: opts.confirm
|
|
4847
|
+
});
|
|
4848
|
+
const data = await api.delete("/api/v1/server", {
|
|
4849
|
+
organizationId: orgId,
|
|
4850
|
+
serverId,
|
|
4851
|
+
...opts.force ? { force: true } : {}
|
|
4852
|
+
});
|
|
4853
|
+
if (isJsonMode()) {
|
|
4854
|
+
printJson(data);
|
|
4855
|
+
return;
|
|
4856
|
+
}
|
|
4857
|
+
printSuccess(`Deleted server ${c.bold(data.deleted.name)} (${data.deleted.id}).`);
|
|
4858
|
+
}));
|
|
4859
|
+
}
|
|
4860
|
+
|
|
4861
|
+
// src/lib/spec-pipeline.ts
|
|
4862
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4863
|
+
import { dirname as dirname4, join as join5, resolve as resolve4 } from "node:path";
|
|
4864
|
+
var SOURCE_TYPES = ["openapi", "graphql", "url"];
|
|
4865
|
+
function isHttpUrl(value) {
|
|
4866
|
+
return /^https?:\/\//i.test(value.trim());
|
|
4867
|
+
}
|
|
4868
|
+
function looksLikeGraphql(spec) {
|
|
4869
|
+
return /\b(type\s+Query|type\s+Mutation|schema\s*\{|extend\s+type)\b/.test(spec);
|
|
4870
|
+
}
|
|
4871
|
+
async function readSpecSource(spec, override, options = {}) {
|
|
4872
|
+
const trimmedSpec = spec.trim();
|
|
4873
|
+
if (override === "url" || !override && isHttpUrl(trimmedSpec)) {
|
|
4874
|
+
if (!isHttpUrl(trimmedSpec)) {
|
|
4875
|
+
throw new Error(`--source-type url requires an http(s) --spec URL, got: ${spec}`);
|
|
4876
|
+
}
|
|
4877
|
+
return { sourceType: "url", sourceUrl: trimmedSpec };
|
|
4878
|
+
}
|
|
4879
|
+
let content;
|
|
4880
|
+
let pathHint = "";
|
|
4881
|
+
if (trimmedSpec === "@-" || trimmedSpec === "-") {
|
|
4882
|
+
const reader = options.readStdin ?? defaultStdinReader;
|
|
4883
|
+
content = await reader();
|
|
4884
|
+
} else {
|
|
4885
|
+
const filePath = resolve4(options.cwd ?? process.cwd(), trimmedSpec);
|
|
4886
|
+
if (!existsSync6(filePath)) {
|
|
4887
|
+
throw new Error(`Spec file not found: ${filePath}`);
|
|
4888
|
+
}
|
|
4889
|
+
content = readFileSync5(filePath, "utf-8");
|
|
4890
|
+
pathHint = filePath;
|
|
4891
|
+
}
|
|
4892
|
+
if (!content.trim()) {
|
|
4893
|
+
throw new Error("The spec is empty — nothing to ingest.");
|
|
4894
|
+
}
|
|
4895
|
+
let sourceType;
|
|
4896
|
+
if (override === "openapi" || override === "graphql") {
|
|
4897
|
+
sourceType = override;
|
|
4898
|
+
} else if (/\.(graphql|gql)$/i.test(pathHint) || looksLikeGraphql(content)) {
|
|
4899
|
+
sourceType = "graphql";
|
|
4900
|
+
} else {
|
|
4901
|
+
sourceType = "openapi";
|
|
4902
|
+
}
|
|
4903
|
+
return { sourceType, rawSpec: content };
|
|
4904
|
+
}
|
|
4905
|
+
function buildIngestBody(args) {
|
|
4906
|
+
const body = {
|
|
4907
|
+
organizationId: args.organizationId,
|
|
4908
|
+
projectId: args.projectId,
|
|
4909
|
+
sourceType: args.payload.sourceType
|
|
4910
|
+
};
|
|
4911
|
+
if (args.payload.sourceType === "url") {
|
|
4912
|
+
body["sourceUrl"] = args.payload.sourceUrl;
|
|
4913
|
+
} else {
|
|
4914
|
+
body["rawSpec"] = args.payload.rawSpec;
|
|
4915
|
+
}
|
|
4916
|
+
return body;
|
|
4917
|
+
}
|
|
4918
|
+
function writeBundleToDisk(bundle, outDir, cwd = process.cwd()) {
|
|
4919
|
+
const root = resolve4(cwd, outDir);
|
|
4920
|
+
const written = [];
|
|
4921
|
+
for (const file of bundle.files) {
|
|
4922
|
+
const dest = join5(root, file.path);
|
|
4923
|
+
mkdirSync4(dirname4(dest), { recursive: true });
|
|
4924
|
+
writeFileSync3(dest, file.content, "utf-8");
|
|
4925
|
+
written.push(dest);
|
|
4926
|
+
}
|
|
4927
|
+
return written;
|
|
4928
|
+
}
|
|
4929
|
+
async function defaultStdinReader() {
|
|
4930
|
+
const chunks = [];
|
|
4931
|
+
return new Promise((res, rej) => {
|
|
4932
|
+
process.stdin.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
4933
|
+
process.stdin.on("end", () => res(Buffer.concat(chunks).toString("utf-8")));
|
|
4934
|
+
process.stdin.on("error", rej);
|
|
4935
|
+
});
|
|
4936
|
+
}
|
|
4937
|
+
|
|
4938
|
+
// src/commands/servers-spec.ts
|
|
4939
|
+
function validateSourceType(value) {
|
|
4940
|
+
if (value === undefined)
|
|
4941
|
+
return;
|
|
4942
|
+
const v = value.trim().toLowerCase();
|
|
4943
|
+
if (!SOURCE_TYPES.includes(v)) {
|
|
4944
|
+
throw new Error(`Invalid --source-type: ${value}. Expected one of: ${SOURCE_TYPES.join(", ")}.`);
|
|
4945
|
+
}
|
|
4946
|
+
return v;
|
|
4947
|
+
}
|
|
4948
|
+
function registerServerSpecCommands(servers) {
|
|
4949
|
+
servers.command("ingest").description("Create a server from an OpenAPI/GraphQL spec (file, URL, or @- stdin). Wraps POST /api/v1/server/ingest.").requiredOption("--project <projectId>", "Project to create the server in").requiredOption("--spec <file|url>", "Spec source: a local file, an http(s) URL (fetched server-side), or @- for stdin").option("--org <organizationId>", "Organization ID").option("--source-type <type>", `Override spec classification: ${SOURCE_TYPES.join(" | ")} (auto-detected by default)`).addHelpText("after", [
|
|
4950
|
+
"",
|
|
4951
|
+
"Examples:",
|
|
4952
|
+
" $ mcp servers ingest --project proj_123 --spec ./openapi.yaml",
|
|
4953
|
+
" $ mcp servers ingest --project proj_123 --spec https://api.example.com/openapi.json",
|
|
4954
|
+
" $ cat schema.graphql | mcp servers ingest --project proj_123 --spec @-"
|
|
4955
|
+
].join(`
|
|
4956
|
+
`)).action(runAction(async (opts) => {
|
|
4957
|
+
const orgId = await resolveOrgId(opts.org);
|
|
4958
|
+
const sourceType = validateSourceType(opts.sourceType);
|
|
4959
|
+
const payload = await readSpecSource(opts.spec, sourceType);
|
|
4960
|
+
if (!isJsonMode()) {
|
|
4961
|
+
printStep(`Importing ${c.bold(payload.sourceType)} spec into project ${c.dim(opts.project)}…`);
|
|
4962
|
+
}
|
|
4963
|
+
const data = await api.post("/api/v1/server/ingest", buildIngestBody({
|
|
4964
|
+
organizationId: orgId,
|
|
4965
|
+
projectId: opts.project,
|
|
4966
|
+
payload
|
|
4967
|
+
}));
|
|
4968
|
+
if (isJsonMode()) {
|
|
4969
|
+
printJson(data);
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4972
|
+
printSuccess(`Server ${c.bold(data.server.id)} created from spec.`);
|
|
4973
|
+
printKeyValue({
|
|
4974
|
+
"server id": data.server.id,
|
|
4975
|
+
"server name": data.server.name,
|
|
4976
|
+
"api source": data.apiSource.id ?? "—",
|
|
4977
|
+
project: opts.project
|
|
4978
|
+
});
|
|
4979
|
+
printStep(`Next: \`mcp servers generate ${data.server.id} --out ./out\` or \`mcp servers deploy ${data.server.id} --wait\`.`);
|
|
4980
|
+
}));
|
|
4981
|
+
servers.command("generate <serverId>").description("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.").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Project ID (looked up from the server when omitted)").option("--out <dir>", "Write the generated files to this directory instead of just summarizing").addHelpText("after", [
|
|
4982
|
+
"",
|
|
4983
|
+
"Notes:",
|
|
4984
|
+
" Codegen runs on the builder Worker (arch-evolution P7f). If generate",
|
|
4985
|
+
" fails with a builder error, the environment’s builder Worker is down.",
|
|
4986
|
+
"",
|
|
4987
|
+
"Examples:",
|
|
4988
|
+
" $ mcp servers generate srv_123 # summarize the bundle",
|
|
4989
|
+
" $ mcp servers generate srv_123 --out ./build # write files to disk",
|
|
4990
|
+
" $ mcp --json servers generate srv_123 | jq '.bundle.files[].path'"
|
|
4991
|
+
].join(`
|
|
4992
|
+
`)).action(runAction(async (serverId, opts) => {
|
|
4993
|
+
const orgId = await resolveOrgId(opts.org);
|
|
4994
|
+
let projectId = opts.project;
|
|
4995
|
+
if (!projectId) {
|
|
4996
|
+
const detail = await api.get("/api/v1/server", { organizationId: orgId, serverId });
|
|
4997
|
+
projectId = detail.server.projectId;
|
|
4998
|
+
}
|
|
4999
|
+
if (!isJsonMode()) {
|
|
5000
|
+
printStep(`Generating bundle for ${c.bold(serverId)}…`);
|
|
5001
|
+
}
|
|
5002
|
+
const data = await api.post("/api/v1/server/generate", { organizationId: orgId, projectId, serverId });
|
|
5003
|
+
if (opts.out) {
|
|
5004
|
+
const written = writeBundleToDisk(data.bundle, opts.out);
|
|
5005
|
+
if (isJsonMode()) {
|
|
5006
|
+
printJson({
|
|
5007
|
+
server: data.server,
|
|
5008
|
+
outDir: opts.out,
|
|
5009
|
+
files: written
|
|
5010
|
+
});
|
|
5011
|
+
return;
|
|
5012
|
+
}
|
|
5013
|
+
printSuccess(`Wrote ${written.length} file(s) to ${c.bold(opts.out)}.`);
|
|
5014
|
+
for (const file of data.bundle.files) {
|
|
5015
|
+
printStep(file.path);
|
|
5016
|
+
}
|
|
5017
|
+
return;
|
|
5018
|
+
}
|
|
5019
|
+
if (isJsonMode()) {
|
|
5020
|
+
printJson(data);
|
|
5021
|
+
return;
|
|
5022
|
+
}
|
|
5023
|
+
printSuccess(`Generated ${data.bundle.files.length} file(s) for ${c.bold(data.server.name)} (not written — pass --out <dir>).`);
|
|
5024
|
+
printKeyValue({
|
|
5025
|
+
"entry file": data.bundle.entryFile,
|
|
5026
|
+
"schema version": data.bundle.schemaVersion,
|
|
5027
|
+
version: data.server.version,
|
|
5028
|
+
files: data.bundle.files.length
|
|
5029
|
+
});
|
|
5030
|
+
for (const file of data.bundle.files) {
|
|
5031
|
+
printStep(`${file.path} ${c.dim(`(${file.content.length} bytes)`)}`);
|
|
5032
|
+
}
|
|
5033
|
+
}));
|
|
4762
5034
|
}
|
|
4763
5035
|
|
|
4764
5036
|
// src/commands/servers-tests.ts
|
|
@@ -4981,6 +5253,7 @@ function registerServerCommands(program2) {
|
|
|
4981
5253
|
registerServerExportCommands(servers);
|
|
4982
5254
|
registerServerLifecycleCommands(servers);
|
|
4983
5255
|
registerServerMutationCommands(servers);
|
|
5256
|
+
registerServerSpecCommands(servers);
|
|
4984
5257
|
registerServerTestCommands(servers);
|
|
4985
5258
|
servers.command("list").description("List servers").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Filter by project ID").option("--limit <n>", "Maximum results (default 25)", parsePositiveIntOption("limit"), "25").addHelpText("after", [
|
|
4986
5259
|
"",
|
|
@@ -5162,48 +5435,48 @@ function registerServerCommands(program2) {
|
|
|
5162
5435
|
}
|
|
5163
5436
|
|
|
5164
5437
|
// src/commands/tools-diff.ts
|
|
5165
|
-
import { existsSync as
|
|
5438
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
|
|
5166
5439
|
import { relative } from "node:path";
|
|
5167
5440
|
|
|
5168
5441
|
// src/lib/dev/handlers-sync.ts
|
|
5169
5442
|
import {
|
|
5170
|
-
existsSync as
|
|
5171
|
-
mkdirSync as
|
|
5172
|
-
readFileSync as
|
|
5443
|
+
existsSync as existsSync9,
|
|
5444
|
+
mkdirSync as mkdirSync7,
|
|
5445
|
+
readFileSync as readFileSync8,
|
|
5173
5446
|
statSync,
|
|
5174
|
-
writeFileSync as
|
|
5447
|
+
writeFileSync as writeFileSync6
|
|
5175
5448
|
} from "node:fs";
|
|
5176
5449
|
import { createHash as createHash3 } from "node:crypto";
|
|
5177
|
-
import { dirname as
|
|
5450
|
+
import { dirname as dirname5, join as join8 } from "node:path";
|
|
5178
5451
|
|
|
5179
5452
|
// src/lib/dev/state.ts
|
|
5180
|
-
import { existsSync as
|
|
5181
|
-
import { join as
|
|
5453
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
5454
|
+
import { join as join6, resolve as resolve5 } from "node:path";
|
|
5182
5455
|
function devRoot(cwd) {
|
|
5183
|
-
return
|
|
5456
|
+
return join6(cwd, ".mcpcloud");
|
|
5184
5457
|
}
|
|
5185
5458
|
function stateFile(cwd) {
|
|
5186
|
-
return
|
|
5459
|
+
return join6(devRoot(cwd), "state.json");
|
|
5187
5460
|
}
|
|
5188
5461
|
function serverDir(cwd) {
|
|
5189
|
-
return
|
|
5462
|
+
return join6(devRoot(cwd), "server");
|
|
5190
5463
|
}
|
|
5191
5464
|
function gitCloneDir(cwd, owner, name) {
|
|
5192
5465
|
const safe = `${owner}__${name}`.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
5193
|
-
return
|
|
5466
|
+
return join6(devRoot(cwd), "git", safe);
|
|
5194
5467
|
}
|
|
5195
5468
|
function envFile(cwd) {
|
|
5196
|
-
return
|
|
5469
|
+
return join6(devRoot(cwd), "env.json");
|
|
5197
5470
|
}
|
|
5198
5471
|
function backupsDir(cwd) {
|
|
5199
|
-
return
|
|
5472
|
+
return join6(devRoot(cwd), "agent-backups");
|
|
5200
5473
|
}
|
|
5201
5474
|
function readState(cwd) {
|
|
5202
5475
|
const path = stateFile(cwd);
|
|
5203
|
-
if (!
|
|
5476
|
+
if (!existsSync7(path))
|
|
5204
5477
|
return null;
|
|
5205
5478
|
try {
|
|
5206
|
-
const parsed = JSON.parse(
|
|
5479
|
+
const parsed = JSON.parse(readFileSync6(path, "utf-8"));
|
|
5207
5480
|
if (!parsed || typeof parsed !== "object")
|
|
5208
5481
|
return null;
|
|
5209
5482
|
const s = parsed;
|
|
@@ -5216,19 +5489,19 @@ function readState(cwd) {
|
|
|
5216
5489
|
}
|
|
5217
5490
|
}
|
|
5218
5491
|
function writeState(cwd, state) {
|
|
5219
|
-
if (!
|
|
5220
|
-
|
|
5492
|
+
if (!existsSync7(devRoot(cwd))) {
|
|
5493
|
+
mkdirSync5(devRoot(cwd), { recursive: true });
|
|
5221
5494
|
}
|
|
5222
|
-
|
|
5495
|
+
writeFileSync4(stateFile(cwd), JSON.stringify(state, null, 2), "utf-8");
|
|
5223
5496
|
}
|
|
5224
5497
|
function ensureGitignore(cwd) {
|
|
5225
|
-
const ignoreFile =
|
|
5226
|
-
if (
|
|
5498
|
+
const ignoreFile = join6(devRoot(cwd), ".gitignore");
|
|
5499
|
+
if (existsSync7(ignoreFile))
|
|
5227
5500
|
return;
|
|
5228
|
-
if (!
|
|
5229
|
-
|
|
5501
|
+
if (!existsSync7(devRoot(cwd))) {
|
|
5502
|
+
mkdirSync5(devRoot(cwd), { recursive: true });
|
|
5230
5503
|
}
|
|
5231
|
-
|
|
5504
|
+
writeFileSync4(ignoreFile, [
|
|
5232
5505
|
"# generated by `mcp dev`",
|
|
5233
5506
|
"server/",
|
|
5234
5507
|
"git/",
|
|
@@ -5242,14 +5515,14 @@ function ensureGitignore(cwd) {
|
|
|
5242
5515
|
|
|
5243
5516
|
// src/lib/dev/tools-sync.ts
|
|
5244
5517
|
import {
|
|
5245
|
-
existsSync as
|
|
5246
|
-
mkdirSync as
|
|
5247
|
-
readFileSync as
|
|
5518
|
+
existsSync as existsSync8,
|
|
5519
|
+
mkdirSync as mkdirSync6,
|
|
5520
|
+
readFileSync as readFileSync7,
|
|
5248
5521
|
readdirSync,
|
|
5249
5522
|
rmSync,
|
|
5250
|
-
writeFileSync as
|
|
5523
|
+
writeFileSync as writeFileSync5
|
|
5251
5524
|
} from "node:fs";
|
|
5252
|
-
import { join as
|
|
5525
|
+
import { join as join7 } from "node:path";
|
|
5253
5526
|
var TOOLS_DIR_NAME = "tools";
|
|
5254
5527
|
var RISK_CLASSES = [
|
|
5255
5528
|
"read",
|
|
@@ -5258,10 +5531,10 @@ var RISK_CLASSES = [
|
|
|
5258
5531
|
"external_effect"
|
|
5259
5532
|
];
|
|
5260
5533
|
function toolsDir(devRootDir) {
|
|
5261
|
-
return
|
|
5534
|
+
return join7(devRootDir, TOOLS_DIR_NAME);
|
|
5262
5535
|
}
|
|
5263
5536
|
function serverToolsDir(devRootDir, serverId) {
|
|
5264
|
-
return
|
|
5537
|
+
return join7(toolsDir(devRootDir), serverId);
|
|
5265
5538
|
}
|
|
5266
5539
|
function escapeFrontmatterScalar(raw) {
|
|
5267
5540
|
if (raw === "")
|
|
@@ -5451,14 +5724,14 @@ function arraysEqual(a, b) {
|
|
|
5451
5724
|
}
|
|
5452
5725
|
var STATE_FILE = ".tools-state.json";
|
|
5453
5726
|
function toolsStateFile(devRootDir, serverId) {
|
|
5454
|
-
return
|
|
5727
|
+
return join7(serverToolsDir(devRootDir, serverId), STATE_FILE);
|
|
5455
5728
|
}
|
|
5456
5729
|
function readToolsState(devRootDir, serverId) {
|
|
5457
5730
|
const file = toolsStateFile(devRootDir, serverId);
|
|
5458
|
-
if (!
|
|
5731
|
+
if (!existsSync8(file))
|
|
5459
5732
|
return null;
|
|
5460
5733
|
try {
|
|
5461
|
-
const parsed = JSON.parse(
|
|
5734
|
+
const parsed = JSON.parse(readFileSync7(file, "utf-8"));
|
|
5462
5735
|
if (!parsed || typeof parsed !== "object")
|
|
5463
5736
|
return null;
|
|
5464
5737
|
const s = parsed;
|
|
@@ -5472,9 +5745,9 @@ function readToolsState(devRootDir, serverId) {
|
|
|
5472
5745
|
}
|
|
5473
5746
|
function writeToolsState(devRootDir, state) {
|
|
5474
5747
|
const dir = serverToolsDir(devRootDir, state.serverId);
|
|
5475
|
-
if (!
|
|
5476
|
-
|
|
5477
|
-
|
|
5748
|
+
if (!existsSync8(dir))
|
|
5749
|
+
mkdirSync6(dir, { recursive: true });
|
|
5750
|
+
writeFileSync5(toolsStateFile(devRootDir, state.serverId), JSON.stringify(state, null, 2), "utf-8");
|
|
5478
5751
|
}
|
|
5479
5752
|
function hashLocalView(view) {
|
|
5480
5753
|
return JSON.stringify({
|
|
@@ -5486,14 +5759,14 @@ function hashLocalView(view) {
|
|
|
5486
5759
|
}
|
|
5487
5760
|
function materializeTools(args) {
|
|
5488
5761
|
const dir = serverToolsDir(args.devRootDir, args.serverId);
|
|
5489
|
-
if (!
|
|
5490
|
-
|
|
5762
|
+
if (!existsSync8(dir))
|
|
5763
|
+
mkdirSync6(dir, { recursive: true });
|
|
5491
5764
|
const kept = new Set;
|
|
5492
5765
|
let filesWritten = 0;
|
|
5493
5766
|
for (const tool of args.tools) {
|
|
5494
5767
|
const filename = `${tool.name}.md`;
|
|
5495
5768
|
kept.add(filename);
|
|
5496
|
-
|
|
5769
|
+
writeFileSync5(join7(dir, filename), renderToolMarkdown(tool), "utf-8");
|
|
5497
5770
|
filesWritten += 1;
|
|
5498
5771
|
}
|
|
5499
5772
|
let filesRemoved = 0;
|
|
@@ -5505,7 +5778,7 @@ function materializeTools(args) {
|
|
|
5505
5778
|
if (kept.has(entry))
|
|
5506
5779
|
continue;
|
|
5507
5780
|
try {
|
|
5508
|
-
rmSync(
|
|
5781
|
+
rmSync(join7(dir, entry), { force: true });
|
|
5509
5782
|
filesRemoved += 1;
|
|
5510
5783
|
} catch {}
|
|
5511
5784
|
}
|
|
@@ -5531,14 +5804,14 @@ function materializeTools(args) {
|
|
|
5531
5804
|
return { filesWritten, filesRemoved };
|
|
5532
5805
|
}
|
|
5533
5806
|
function readLocalTool(devRootDir, serverId, toolName) {
|
|
5534
|
-
const filePath =
|
|
5535
|
-
if (!
|
|
5807
|
+
const filePath = join7(serverToolsDir(devRootDir, serverId), `${toolName}.md`);
|
|
5808
|
+
if (!existsSync8(filePath))
|
|
5536
5809
|
return null;
|
|
5537
5810
|
const state = readToolsState(devRootDir, serverId);
|
|
5538
5811
|
const stateEntry = state?.tools[toolName];
|
|
5539
5812
|
if (!stateEntry)
|
|
5540
5813
|
return null;
|
|
5541
|
-
const view = parseToolMarkdown(
|
|
5814
|
+
const view = parseToolMarkdown(readFileSync7(filePath, "utf-8"));
|
|
5542
5815
|
return {
|
|
5543
5816
|
view,
|
|
5544
5817
|
cloudId: stateEntry.id,
|
|
@@ -5568,8 +5841,8 @@ async function pushLocalToolEdit(args) {
|
|
|
5568
5841
|
};
|
|
5569
5842
|
}
|
|
5570
5843
|
if (!local) {
|
|
5571
|
-
const filePath =
|
|
5572
|
-
if (!
|
|
5844
|
+
const filePath = join7(serverToolsDir(args.devRootDir, args.serverId), `${args.toolName}.md`);
|
|
5845
|
+
if (!existsSync8(filePath))
|
|
5573
5846
|
return { kind: "missing-file" };
|
|
5574
5847
|
return { kind: "unknown-tool" };
|
|
5575
5848
|
}
|
|
@@ -5652,7 +5925,7 @@ function toKebabSlug(value) {
|
|
|
5652
5925
|
return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "generated-mcp-server";
|
|
5653
5926
|
}
|
|
5654
5927
|
function serverBundleHandlersDir(devRootDir) {
|
|
5655
|
-
return
|
|
5928
|
+
return join8(devRootDir, "server", "src", "tools");
|
|
5656
5929
|
}
|
|
5657
5930
|
function handlerSlugFromWatcherPath(rel) {
|
|
5658
5931
|
if (!rel)
|
|
@@ -5681,15 +5954,15 @@ function hashSource(source) {
|
|
|
5681
5954
|
}
|
|
5682
5955
|
var lastPushedHash = new Map;
|
|
5683
5956
|
async function pushLocalHandlerEdit(args) {
|
|
5684
|
-
const filePath =
|
|
5685
|
-
if (!
|
|
5957
|
+
const filePath = join8(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
|
|
5958
|
+
if (!existsSync9(filePath)) {
|
|
5686
5959
|
return { kind: "missing-file" };
|
|
5687
5960
|
}
|
|
5688
5961
|
const resolved = resolveToolFromSlug(args.devRootDir, args.serverId, args.slug);
|
|
5689
5962
|
if (!resolved) {
|
|
5690
5963
|
return { kind: "unknown-tool", slug: args.slug };
|
|
5691
5964
|
}
|
|
5692
|
-
const source =
|
|
5965
|
+
const source = readFileSync8(filePath, "utf-8");
|
|
5693
5966
|
const hash = hashSource(source);
|
|
5694
5967
|
if (lastPushedHash.get(filePath) === hash) {
|
|
5695
5968
|
return { kind: "noop" };
|
|
@@ -5833,9 +6106,9 @@ async function resetCloudHandler(args) {
|
|
|
5833
6106
|
}
|
|
5834
6107
|
}
|
|
5835
6108
|
function writeLocalHandlerFromCloud(args) {
|
|
5836
|
-
const filePath =
|
|
5837
|
-
|
|
5838
|
-
|
|
6109
|
+
const filePath = join8(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
|
|
6110
|
+
mkdirSync7(dirname5(filePath), { recursive: true });
|
|
6111
|
+
writeFileSync6(filePath, args.content, "utf-8");
|
|
5839
6112
|
lastPushedHash.set(filePath, hashSource(args.content));
|
|
5840
6113
|
return filePath;
|
|
5841
6114
|
}
|
|
@@ -5844,11 +6117,11 @@ function handlerSlugFromToolName(toolName) {
|
|
|
5844
6117
|
}
|
|
5845
6118
|
var RECENT_EDIT_WINDOW_MS = 5 * 60 * 1000;
|
|
5846
6119
|
function inspectLocalHandler(args) {
|
|
5847
|
-
const filePath =
|
|
5848
|
-
if (!
|
|
6120
|
+
const filePath = join8(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
|
|
6121
|
+
if (!existsSync9(filePath))
|
|
5849
6122
|
return { kind: "no-local-file" };
|
|
5850
6123
|
const cached2 = lastPushedHash.get(filePath);
|
|
5851
|
-
const onDisk = hashSource(
|
|
6124
|
+
const onDisk = hashSource(readFileSync8(filePath, "utf-8"));
|
|
5852
6125
|
if (cached2 !== undefined) {
|
|
5853
6126
|
return cached2 === onDisk ? { kind: "matches-cache" } : { kind: "differs-from-cache" };
|
|
5854
6127
|
}
|
|
@@ -5886,7 +6159,7 @@ async function resolveToolsContext(opts = {}) {
|
|
|
5886
6159
|
if (opts.requireServer === false)
|
|
5887
6160
|
return null;
|
|
5888
6161
|
printError("No server selected. Pass --server <serverId> or run from a directory with .mcpcloud/state.json.");
|
|
5889
|
-
|
|
6162
|
+
throw new CliExitError(1);
|
|
5890
6163
|
}
|
|
5891
6164
|
const organizationId = opts.orgOverride ?? cached2?.organizationId ?? await resolveOrgId(undefined);
|
|
5892
6165
|
let projectId = cached2?.projectId;
|
|
@@ -5916,7 +6189,7 @@ function registerToolsDiffCommand(tools) {
|
|
|
5916
6189
|
`)).action(runAction(async (name, opts) => {
|
|
5917
6190
|
if (opts.metadataOnly && opts.handlerOnly) {
|
|
5918
6191
|
printError("Pass at most one of --metadata-only or --handler-only.");
|
|
5919
|
-
|
|
6192
|
+
throw new CliExitError(1);
|
|
5920
6193
|
}
|
|
5921
6194
|
await runToolsDiff({
|
|
5922
6195
|
toolName: name,
|
|
@@ -5944,7 +6217,7 @@ async function runToolsDiff(args) {
|
|
|
5944
6217
|
const sample = tools.slice(0, 5).map((t) => t.name).join(", ");
|
|
5945
6218
|
printInfo(` ${c.dim("Available:")} ${sample}${tools.length > 5 ? c.dim(`, +${tools.length - 5} more`) : ""}`);
|
|
5946
6219
|
}
|
|
5947
|
-
|
|
6220
|
+
throw new CliExitError(1);
|
|
5948
6221
|
}
|
|
5949
6222
|
const metadata = args.includeMetadata ? buildMetadataDiff({ devRootDir: dev, cwd, serverId: ctx.serverId, cloud }) : null;
|
|
5950
6223
|
const handler = args.includeHandler ? await buildHandlerDiff({
|
|
@@ -5978,7 +6251,7 @@ async function runToolsDiff(args) {
|
|
|
5978
6251
|
}
|
|
5979
6252
|
function buildMetadataDiff(args) {
|
|
5980
6253
|
const filePath = `${serverToolsDir(args.devRootDir, args.serverId)}/${args.cloud.name}.md`;
|
|
5981
|
-
if (!
|
|
6254
|
+
if (!existsSync10(filePath)) {
|
|
5982
6255
|
return {
|
|
5983
6256
|
hasLocal: false,
|
|
5984
6257
|
localPath: null,
|
|
@@ -5992,7 +6265,7 @@ function buildMetadataDiff(args) {
|
|
|
5992
6265
|
let local = null;
|
|
5993
6266
|
let parseError = null;
|
|
5994
6267
|
try {
|
|
5995
|
-
local = parseToolMarkdown(
|
|
6268
|
+
local = parseToolMarkdown(readFileSync9(filePath, "utf-8"));
|
|
5996
6269
|
} catch (err) {
|
|
5997
6270
|
parseError = err instanceof Error ? err.message : String(err);
|
|
5998
6271
|
}
|
|
@@ -6034,8 +6307,8 @@ async function buildHandlerDiff(args) {
|
|
|
6034
6307
|
}
|
|
6035
6308
|
const slug = handlerSlugFromToolName(args.toolName);
|
|
6036
6309
|
const filePath = `${serverBundleHandlersDir(args.devRootDir)}/${slug}.ts`;
|
|
6037
|
-
const hasLocal =
|
|
6038
|
-
const localContent = hasLocal ?
|
|
6310
|
+
const hasLocal = existsSync10(filePath);
|
|
6311
|
+
const localContent = hasLocal ? readFileSync9(filePath, "utf-8") : null;
|
|
6039
6312
|
const localPath = hasLocal ? relative(args.cwd, filePath) || filePath : null;
|
|
6040
6313
|
const changed = hasLocal ? localContent !== cloudResult.snapshot.content : true;
|
|
6041
6314
|
return {
|
|
@@ -6145,18 +6418,18 @@ function renderBool(value) {
|
|
|
6145
6418
|
}
|
|
6146
6419
|
|
|
6147
6420
|
// src/commands/tools-edit.ts
|
|
6148
|
-
import { existsSync as
|
|
6149
|
-
import { join as
|
|
6421
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
6422
|
+
import { join as join10, relative as relative2 } from "node:path";
|
|
6150
6423
|
|
|
6151
6424
|
// src/lib/editor-shell.ts
|
|
6152
6425
|
import { spawn as spawn2 } from "node:child_process";
|
|
6153
|
-
import { existsSync as
|
|
6426
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
|
|
6154
6427
|
import { platform as platform2 } from "node:os";
|
|
6155
|
-
import { delimiter, join as
|
|
6428
|
+
import { delimiter, join as join9 } from "node:path";
|
|
6156
6429
|
var DEFAULT_FALLBACKS = ["vi"];
|
|
6157
6430
|
function isExecutableFile(path) {
|
|
6158
6431
|
try {
|
|
6159
|
-
return
|
|
6432
|
+
return existsSync11(path);
|
|
6160
6433
|
} catch {
|
|
6161
6434
|
return false;
|
|
6162
6435
|
}
|
|
@@ -6173,7 +6446,7 @@ function findOnPath(command) {
|
|
|
6173
6446
|
if (!dir)
|
|
6174
6447
|
continue;
|
|
6175
6448
|
for (const ext of exts) {
|
|
6176
|
-
const candidate =
|
|
6449
|
+
const candidate = join9(dir, command + ext);
|
|
6177
6450
|
if (isExecutableFile(candidate))
|
|
6178
6451
|
return candidate;
|
|
6179
6452
|
}
|
|
@@ -6194,14 +6467,14 @@ function resolveEditorCommand(args) {
|
|
|
6194
6467
|
return { command: DEFAULT_FALLBACKS[0], source: "fallback" };
|
|
6195
6468
|
}
|
|
6196
6469
|
function defaultRun(cmd, args) {
|
|
6197
|
-
return new Promise((
|
|
6470
|
+
return new Promise((resolve6, reject) => {
|
|
6198
6471
|
const child = spawn2(cmd, args, { stdio: "inherit" });
|
|
6199
6472
|
child.on("error", (err) => reject(err));
|
|
6200
|
-
child.on("exit", (code) =>
|
|
6473
|
+
child.on("exit", (code) => resolve6(code ?? 0));
|
|
6201
6474
|
});
|
|
6202
6475
|
}
|
|
6203
6476
|
async function runEditor(args) {
|
|
6204
|
-
if (!
|
|
6477
|
+
if (!existsSync11(args.filePath)) {
|
|
6205
6478
|
return {
|
|
6206
6479
|
ok: false,
|
|
6207
6480
|
reason: "file-missing",
|
|
@@ -6209,8 +6482,8 @@ async function runEditor(args) {
|
|
|
6209
6482
|
};
|
|
6210
6483
|
}
|
|
6211
6484
|
const { command } = resolveEditorCommand({ command: args.command });
|
|
6212
|
-
const
|
|
6213
|
-
const resolved =
|
|
6485
|
+
const resolve6 = args.resolveCommand ?? findOnPath;
|
|
6486
|
+
const resolved = resolve6(command);
|
|
6214
6487
|
if (!resolved) {
|
|
6215
6488
|
return {
|
|
6216
6489
|
ok: false,
|
|
@@ -6236,7 +6509,7 @@ async function runEditor(args) {
|
|
|
6236
6509
|
message: `${command} exited with code ${exitCode}. The file may be unsaved; re-run after fixing.`
|
|
6237
6510
|
};
|
|
6238
6511
|
}
|
|
6239
|
-
const contents =
|
|
6512
|
+
const contents = readFileSync10(args.filePath, "utf-8");
|
|
6240
6513
|
return { ok: true, contents, command };
|
|
6241
6514
|
}
|
|
6242
6515
|
|
|
@@ -6260,7 +6533,7 @@ function registerToolsEditCommand(tools) {
|
|
|
6260
6533
|
async function runToolsEdit(args) {
|
|
6261
6534
|
if (isJsonMode()) {
|
|
6262
6535
|
printError("`mcp tools edit` is interactive and cannot be used with --json.");
|
|
6263
|
-
|
|
6536
|
+
throw new CliExitError(1);
|
|
6264
6537
|
}
|
|
6265
6538
|
const ctx = await resolveToolsContext({
|
|
6266
6539
|
orgOverride: args.orgOverride,
|
|
@@ -6269,11 +6542,11 @@ async function runToolsEdit(args) {
|
|
|
6269
6542
|
if (!ctx)
|
|
6270
6543
|
return;
|
|
6271
6544
|
const cwd = process.cwd();
|
|
6272
|
-
const filePath =
|
|
6273
|
-
if (!
|
|
6545
|
+
const filePath = join10(serverToolsDir(devRoot(cwd), ctx.serverId), `${args.toolName}.md`);
|
|
6546
|
+
if (!existsSync12(filePath)) {
|
|
6274
6547
|
printError(`Tool file not found: ${relative2(cwd, filePath)}`);
|
|
6275
6548
|
printInfo(` ${c.dim("Run")} ${c.bold(`mcp tools pull ${args.toolName}`)} ${c.dim("first to materialize it, or check the name is spelled correctly.")}`);
|
|
6276
|
-
|
|
6549
|
+
throw new CliExitError(1);
|
|
6277
6550
|
}
|
|
6278
6551
|
printStep(`Opening ${c.bold(relative2(cwd, filePath))} in editor…`);
|
|
6279
6552
|
const editorResult = await runEditor({
|
|
@@ -6282,7 +6555,7 @@ async function runToolsEdit(args) {
|
|
|
6282
6555
|
});
|
|
6283
6556
|
if (!editorResult.ok) {
|
|
6284
6557
|
printError(editorResult.message);
|
|
6285
|
-
|
|
6558
|
+
throw new CliExitError(1);
|
|
6286
6559
|
}
|
|
6287
6560
|
printStep(`Pushing edit: ${c.bold(args.toolName)}…`);
|
|
6288
6561
|
const push = await pushLocalToolEdit({
|
|
@@ -6298,7 +6571,7 @@ async function runToolsEdit(args) {
|
|
|
6298
6571
|
return;
|
|
6299
6572
|
case "missing-file":
|
|
6300
6573
|
printError(`${args.toolName}.md disappeared after the editor exited.`);
|
|
6301
|
-
|
|
6574
|
+
throw new CliExitError(1);
|
|
6302
6575
|
return;
|
|
6303
6576
|
case "unknown-tool":
|
|
6304
6577
|
printWarn(`${args.toolName}.md is not tracked in .tools-state.json — restart \`mcp dev\` or \`mcp tools pull\` to re-materialize.`);
|
|
@@ -6306,7 +6579,7 @@ async function runToolsEdit(args) {
|
|
|
6306
6579
|
case "parse-error":
|
|
6307
6580
|
printError(`Couldn't parse ${args.toolName}.md: ${push.message}`);
|
|
6308
6581
|
printInfo(` ${c.dim("Re-open and fix the file, or run")} ${c.bold(`mcp tools pull ${args.toolName}`)} ${c.dim("to reset.")}`);
|
|
6309
|
-
|
|
6582
|
+
throw new CliExitError(1);
|
|
6310
6583
|
return;
|
|
6311
6584
|
case "patched":
|
|
6312
6585
|
if (isJsonMode()) {
|
|
@@ -6319,11 +6592,11 @@ async function runToolsEdit(args) {
|
|
|
6319
6592
|
case "conflict":
|
|
6320
6593
|
printError(`Conflict on ${c.bold(args.toolName)}: the cloud was updated by someone else since you read it.`);
|
|
6321
6594
|
printInfo(` ${c.dim("Run:")} ${c.bold(`mcp tools pull ${args.toolName}`)} ${c.dim("to refresh, then re-apply your changes via")} ${c.bold(`mcp tools edit ${args.toolName}`)}${c.dim(".")}`);
|
|
6322
|
-
|
|
6595
|
+
throw new CliExitError(1);
|
|
6323
6596
|
return;
|
|
6324
6597
|
case "api-error":
|
|
6325
6598
|
printError(`Failed to save ${args.toolName} (HTTP ${push.status}): ${push.message}`);
|
|
6326
|
-
|
|
6599
|
+
throw new CliExitError(1);
|
|
6327
6600
|
return;
|
|
6328
6601
|
default: {
|
|
6329
6602
|
const _exhaustive = push;
|
|
@@ -6426,7 +6699,7 @@ async function runToolsEnrich(args) {
|
|
|
6426
6699
|
const sample = list.slice(0, 5).map((t) => t.name).join(", ");
|
|
6427
6700
|
printInfo(` ${c.dim("Available:")} ${sample}${list.length > 5 ? c.dim(`, +${list.length - 5} more`) : ""}`);
|
|
6428
6701
|
}
|
|
6429
|
-
|
|
6702
|
+
throw new CliExitError(1);
|
|
6430
6703
|
}
|
|
6431
6704
|
if (!isJsonMode()) {
|
|
6432
6705
|
printStep(`Running AI enrichment for ${c.bold(args.toolName)}${args.modelId ? c.dim(` (model ${args.modelId})`) : ""}…`);
|
|
@@ -6446,7 +6719,7 @@ async function runToolsEnrich(args) {
|
|
|
6446
6719
|
} else {
|
|
6447
6720
|
printError(`Enrichment failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
6448
6721
|
}
|
|
6449
|
-
|
|
6722
|
+
throw new CliExitError(1);
|
|
6450
6723
|
}
|
|
6451
6724
|
const detailed = await fetchEnrichedToolByName({
|
|
6452
6725
|
organizationId: ctx.organizationId,
|
|
@@ -6456,11 +6729,11 @@ async function runToolsEnrich(args) {
|
|
|
6456
6729
|
});
|
|
6457
6730
|
if (!detailed) {
|
|
6458
6731
|
printError("Enrichment ran, but the tool record could not be re-fetched. Try `mcp tools show` manually.");
|
|
6459
|
-
|
|
6732
|
+
throw new CliExitError(1);
|
|
6460
6733
|
}
|
|
6461
6734
|
if (!detailed.enrichment) {
|
|
6462
6735
|
printError("Enrichment ran without producing a suggestion. The AI may have failed silently — check `mcp tools show` or the dashboard.");
|
|
6463
|
-
|
|
6736
|
+
throw new CliExitError(1);
|
|
6464
6737
|
}
|
|
6465
6738
|
if (isJsonMode()) {
|
|
6466
6739
|
const json = {
|
|
@@ -6499,7 +6772,7 @@ async function runToolsEnrich(args) {
|
|
|
6499
6772
|
const applyResult = await applySuggestion(detailed, ctx);
|
|
6500
6773
|
if (!applyResult.ok) {
|
|
6501
6774
|
printError(applyResult.message);
|
|
6502
|
-
|
|
6775
|
+
throw new CliExitError(1);
|
|
6503
6776
|
}
|
|
6504
6777
|
printSuccess(`Applied ${c.bold(args.toolName)} ${c.dim(`(${applyResult.changedFields.length} field${applyResult.changedFields.length === 1 ? "" : "s"} patched)`)}`);
|
|
6505
6778
|
printInfo(` ${c.dim("Run")} ${c.bold(`mcp tools pull ${args.toolName}`)} ${c.dim("to refresh your local copy.")}`);
|
|
@@ -6702,7 +6975,7 @@ async function runToolsPull(args) {
|
|
|
6702
6975
|
const sample = forServer.slice(0, 5).map((t) => t.name).join(", ");
|
|
6703
6976
|
printInfo(` ${c.dim("Available:")} ${sample}${forServer.length > 5 ? c.dim(`, +${forServer.length - 5} more`) : ""}`);
|
|
6704
6977
|
}
|
|
6705
|
-
|
|
6978
|
+
throw new CliExitError(1);
|
|
6706
6979
|
}
|
|
6707
6980
|
if (forServer.length === 0) {
|
|
6708
6981
|
if (isJsonMode()) {
|
|
@@ -6777,13 +7050,13 @@ async function runPullHandler(args) {
|
|
|
6777
7050
|
if (state.kind === "differs-from-cache") {
|
|
6778
7051
|
printError(`${c.bold(slug2)}.ts has unsaved local edits (content differs from the last push). Pull would overwrite them.`);
|
|
6779
7052
|
printInfo(` ${c.dim("Save the file (the watcher pushes to cloud), or re-run with")} ${c.bold("--force")} ${c.dim("to discard local edits.")}`);
|
|
6780
|
-
|
|
7053
|
+
throw new CliExitError(1);
|
|
6781
7054
|
}
|
|
6782
7055
|
if (state.kind === "no-cache-entry" && state.recentlyModified) {
|
|
6783
7056
|
const ageSec = Math.floor(state.ageMs / 1000);
|
|
6784
7057
|
printError(`${c.bold(slug2)}.ts was modified ${ageSec}s ago and this CLI session has no record of pushing it (likely a fresh dev start). Pull would overwrite recent edits.`);
|
|
6785
7058
|
printInfo(` ${c.dim("Save in the watcher session you edited from, or re-run with")} ${c.bold("--force")} ${c.dim("to discard local edits.")}`);
|
|
6786
|
-
|
|
7059
|
+
throw new CliExitError(1);
|
|
6787
7060
|
}
|
|
6788
7061
|
}
|
|
6789
7062
|
if (!isJsonMode())
|
|
@@ -6797,15 +7070,15 @@ async function runPullHandler(args) {
|
|
|
6797
7070
|
});
|
|
6798
7071
|
if (result.kind === "unknown-tool") {
|
|
6799
7072
|
printError(`Tool ${c.bold(args.toolName)} not found in local .tools-state.json. ` + `Run \`mcp dev\` first to hydrate the bundle, or check the spelling (use the tool name, not the file slug).`);
|
|
6800
|
-
|
|
7073
|
+
throw new CliExitError(1);
|
|
6801
7074
|
}
|
|
6802
7075
|
if (result.kind === "handler-row-not-hydrated") {
|
|
6803
7076
|
printError(`No generated bundle exists for this server yet — codegen has never been run. ` + `Run \`mcp servers generate\` (or use the dashboard) to produce a bundle, then retry.`);
|
|
6804
|
-
|
|
7077
|
+
throw new CliExitError(1);
|
|
6805
7078
|
}
|
|
6806
7079
|
if (result.kind === "api-error") {
|
|
6807
7080
|
printError(`Failed to fetch handler (${result.status}${result.code ? ` ${result.code}` : ""}): ${result.message}`);
|
|
6808
|
-
|
|
7081
|
+
throw new CliExitError(1);
|
|
6809
7082
|
}
|
|
6810
7083
|
const slug = handlerSlugFromToolName(result.snapshot.toolName);
|
|
6811
7084
|
const written = writeLocalHandlerFromCloud({
|
|
@@ -6889,13 +7162,13 @@ async function runResetHandler(args) {
|
|
|
6889
7162
|
if (state.kind === "differs-from-cache") {
|
|
6890
7163
|
printError(`${c.bold(slug2)}.ts has unsaved local edits. Reset would overwrite them.`);
|
|
6891
7164
|
printInfo(` ${c.dim("Save first (the watcher pushes to cloud), or re-run with")} ${c.bold("--force")} ${c.dim("to discard local edits.")}`);
|
|
6892
|
-
|
|
7165
|
+
throw new CliExitError(1);
|
|
6893
7166
|
}
|
|
6894
7167
|
if (state.kind === "no-cache-entry" && state.recentlyModified) {
|
|
6895
7168
|
const ageSec = Math.floor(state.ageMs / 1000);
|
|
6896
7169
|
printError(`${c.bold(slug2)}.ts was modified ${ageSec}s ago and this CLI session has no record of pushing it. Reset would overwrite recent edits.`);
|
|
6897
7170
|
printInfo(` ${c.dim("Save in the watcher session you edited from, or re-run with")} ${c.bold("--force")} ${c.dim("to discard local edits.")}`);
|
|
6898
|
-
|
|
7171
|
+
throw new CliExitError(1);
|
|
6899
7172
|
}
|
|
6900
7173
|
}
|
|
6901
7174
|
if (!args.assumeYes && !isJsonMode()) {
|
|
@@ -6916,20 +7189,20 @@ async function runResetHandler(args) {
|
|
|
6916
7189
|
});
|
|
6917
7190
|
if (result.kind === "unknown-tool") {
|
|
6918
7191
|
printError(`Tool ${c.bold(args.toolName)} not found in local .tools-state.json. ` + `Run \`mcp dev\` first to hydrate, or check the spelling (use the tool name, not the file slug).`);
|
|
6919
|
-
|
|
7192
|
+
throw new CliExitError(1);
|
|
6920
7193
|
}
|
|
6921
7194
|
if (result.kind === "handler-row-not-hydrated") {
|
|
6922
7195
|
printError(`No generated bundle exists for this server yet — codegen has never been run. ` + `Run \`mcp servers generate\` (or use the dashboard) to produce a bundle, then retry.`);
|
|
6923
|
-
|
|
7196
|
+
throw new CliExitError(1);
|
|
6924
7197
|
}
|
|
6925
7198
|
if (result.kind === "conflict") {
|
|
6926
7199
|
printError(`Conflict resetting ${c.bold(result.toolName)} (cloud now at version ${result.currentVersion ?? "?"}).`);
|
|
6927
7200
|
printInfo(` ${c.dim("Re-run `mcp tools pull-handler " + result.toolName + "` then retry.")}`);
|
|
6928
|
-
|
|
7201
|
+
throw new CliExitError(1);
|
|
6929
7202
|
}
|
|
6930
7203
|
if (result.kind === "api-error") {
|
|
6931
7204
|
printError(`Failed to reset handler (${result.status}${result.code ? ` ${result.code}` : ""}): ${result.message}`);
|
|
6932
|
-
|
|
7205
|
+
throw new CliExitError(1);
|
|
6933
7206
|
}
|
|
6934
7207
|
const slug = handlerSlugFromToolName(result.snapshot.toolName);
|
|
6935
7208
|
const written = writeLocalHandlerFromCloud({
|
|
@@ -6986,7 +7259,7 @@ async function runToolsShow(args) {
|
|
|
6986
7259
|
const sample = tools.slice(0, 5).map((t) => t.name).join(", ");
|
|
6987
7260
|
printInfo(` ${c.dim("Available:")} ${sample}${tools.length > 5 ? c.dim(`, +${tools.length - 5} more`) : ""}`);
|
|
6988
7261
|
}
|
|
6989
|
-
|
|
7262
|
+
throw new CliExitError(1);
|
|
6990
7263
|
}
|
|
6991
7264
|
const [detailed, handlerResult] = await Promise.all([
|
|
6992
7265
|
fetchEnrichedToolDetail({
|
|
@@ -7399,6 +7672,29 @@ function registerSkillMutationCommands(skills) {
|
|
|
7399
7672
|
"archived at": s.archivedAt ? formatDate(s.archivedAt) : "—"
|
|
7400
7673
|
});
|
|
7401
7674
|
}));
|
|
7675
|
+
skills.command("delete <skillId>").description('Permanently delete a draft skill. Requires --confirm "<exact name>". Published skills with versions/installs are rejected (archive instead).').requiredOption("--confirm <name>", "The exact skill name, typed to confirm the destructive action").option("--org <organizationId>", "Organization ID").addHelpText("after", [
|
|
7676
|
+
"",
|
|
7677
|
+
"Examples:",
|
|
7678
|
+
' $ mcp skills delete skill_123 --confirm "Weather Reporter"'
|
|
7679
|
+
].join(`
|
|
7680
|
+
`)).action(runAction(async (skillId, opts) => {
|
|
7681
|
+
const orgId = await resolveOrgId(opts.org);
|
|
7682
|
+
const detail = await api.get("/api/v1/skill", {
|
|
7683
|
+
organizationId: orgId,
|
|
7684
|
+
skillId
|
|
7685
|
+
});
|
|
7686
|
+
assertConfirmMatches({
|
|
7687
|
+
entityKind: "skill",
|
|
7688
|
+
entityName: detail.skill.name,
|
|
7689
|
+
confirm: opts.confirm
|
|
7690
|
+
});
|
|
7691
|
+
const data = await api.delete("/api/v1/skill", { organizationId: orgId, skillId });
|
|
7692
|
+
if (isJsonMode()) {
|
|
7693
|
+
printJson(data);
|
|
7694
|
+
return;
|
|
7695
|
+
}
|
|
7696
|
+
printSuccess(`Deleted skill ${data.deleted.name} (${data.deleted.id}).`);
|
|
7697
|
+
}));
|
|
7402
7698
|
skills.command("install <skillId>").description("Install a skill into the caller's project").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Project ID (defaults to the skill's project)").option("--pin <semver>", "Pin a specific skill version (sent as `version` on the wire)").option("--config <json>", "Per-installation runtime config (passed verbatim to the skill)").action(runAction(async (skillId, opts) => {
|
|
7403
7699
|
const orgId = await resolveOrgId(opts.org);
|
|
7404
7700
|
const { projectId } = await resolveSkillProject(orgId, skillId, opts.project);
|
|
@@ -7609,42 +7905,471 @@ function registerSkillTestCommands(skills) {
|
|
|
7609
7905
|
}));
|
|
7610
7906
|
}
|
|
7611
7907
|
|
|
7612
|
-
// src/
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7908
|
+
// src/lib/mcp-invoke.ts
|
|
7909
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
|
|
7910
|
+
import { resolve as resolve6 } from "node:path";
|
|
7911
|
+
var DEFAULT_TIMEOUT_MS3 = 60000;
|
|
7912
|
+
var invokeRequestId = 1;
|
|
7913
|
+
async function invokeMcpTool(args) {
|
|
7914
|
+
const startedAt = Date.now();
|
|
7915
|
+
const controller = new AbortController;
|
|
7916
|
+
const timer = setTimeout(() => controller.abort(), args.timeoutMs ?? DEFAULT_TIMEOUT_MS3);
|
|
7917
|
+
const headers = {
|
|
7918
|
+
"content-type": "application/json",
|
|
7919
|
+
accept: "application/json, text/event-stream",
|
|
7920
|
+
...args.headers
|
|
7921
|
+
};
|
|
7922
|
+
const body = JSON.stringify({
|
|
7923
|
+
jsonrpc: "2.0",
|
|
7924
|
+
id: `mcp-cli-invoke-${invokeRequestId++}`,
|
|
7925
|
+
method: "tools/call",
|
|
7926
|
+
params: { name: args.toolName, arguments: args.toolArguments }
|
|
7621
7927
|
});
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
"
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7928
|
+
try {
|
|
7929
|
+
const res = await fetch(args.url, {
|
|
7930
|
+
method: "POST",
|
|
7931
|
+
headers,
|
|
7932
|
+
body,
|
|
7933
|
+
signal: controller.signal
|
|
7934
|
+
});
|
|
7935
|
+
const rawText = await res.text();
|
|
7936
|
+
const contentType = (res.headers.get("content-type") || "").toLowerCase();
|
|
7937
|
+
const jsonText = contentType.includes("text/event-stream") ? unwrapEventStream(rawText) : rawText;
|
|
7938
|
+
const parsed = parseJsonRpc(jsonText);
|
|
7939
|
+
const durationMs = Date.now() - startedAt;
|
|
7940
|
+
if (!parsed.ok) {
|
|
7941
|
+
if (!res.ok) {
|
|
7942
|
+
return {
|
|
7943
|
+
ok: false,
|
|
7944
|
+
reason: "http-error",
|
|
7945
|
+
status: res.status,
|
|
7946
|
+
message: parsed.error?.message ?? (rawText.slice(0, 200) || res.statusText),
|
|
7947
|
+
durationMs
|
|
7948
|
+
};
|
|
7949
|
+
}
|
|
7950
|
+
return {
|
|
7951
|
+
ok: false,
|
|
7952
|
+
reason: "rpc-error",
|
|
7953
|
+
error: parsed.error ?? {
|
|
7954
|
+
code: -32603,
|
|
7955
|
+
message: "Malformed JSON-RPC response.",
|
|
7956
|
+
data: rawText
|
|
7957
|
+
},
|
|
7958
|
+
durationMs
|
|
7959
|
+
};
|
|
7960
|
+
}
|
|
7961
|
+
return {
|
|
7962
|
+
ok: true,
|
|
7963
|
+
result: parsed.result,
|
|
7964
|
+
durationMs
|
|
7965
|
+
};
|
|
7966
|
+
} catch (err) {
|
|
7967
|
+
const durationMs = Date.now() - startedAt;
|
|
7968
|
+
const aborted = err instanceof Error && (err.name === "AbortError" || /abort/i.test(err.message));
|
|
7969
|
+
return {
|
|
7970
|
+
ok: false,
|
|
7971
|
+
reason: "http-error",
|
|
7972
|
+
status: 0,
|
|
7973
|
+
message: aborted ? `Request timed out after ${args.timeoutMs ?? DEFAULT_TIMEOUT_MS3}ms.` : err instanceof Error ? err.message : String(err),
|
|
7974
|
+
durationMs
|
|
7975
|
+
};
|
|
7976
|
+
} finally {
|
|
7977
|
+
clearTimeout(timer);
|
|
7978
|
+
}
|
|
7979
|
+
}
|
|
7980
|
+
async function listMcpTools(args) {
|
|
7981
|
+
const startedAt = Date.now();
|
|
7982
|
+
const controller = new AbortController;
|
|
7983
|
+
const timer = setTimeout(() => controller.abort(), args.timeoutMs ?? DEFAULT_TIMEOUT_MS3);
|
|
7984
|
+
const headers = {
|
|
7985
|
+
"content-type": "application/json",
|
|
7986
|
+
accept: "application/json, text/event-stream",
|
|
7987
|
+
...args.headers
|
|
7988
|
+
};
|
|
7989
|
+
const body = JSON.stringify({
|
|
7990
|
+
jsonrpc: "2.0",
|
|
7991
|
+
id: `mcp-cli-list-${invokeRequestId++}`,
|
|
7992
|
+
method: "tools/list",
|
|
7993
|
+
params: {}
|
|
7994
|
+
});
|
|
7995
|
+
try {
|
|
7996
|
+
const res = await fetch(args.url, {
|
|
7997
|
+
method: "POST",
|
|
7998
|
+
headers,
|
|
7999
|
+
body,
|
|
8000
|
+
signal: controller.signal
|
|
8001
|
+
});
|
|
8002
|
+
const rawText = await res.text();
|
|
8003
|
+
const contentType = (res.headers.get("content-type") || "").toLowerCase();
|
|
8004
|
+
const jsonText = contentType.includes("text/event-stream") ? unwrapEventStream(rawText) : rawText;
|
|
8005
|
+
const parsed = parseJsonRpc(jsonText);
|
|
8006
|
+
const durationMs = Date.now() - startedAt;
|
|
8007
|
+
if (!parsed.ok) {
|
|
8008
|
+
if (!res.ok) {
|
|
8009
|
+
return {
|
|
8010
|
+
ok: false,
|
|
8011
|
+
reason: "http-error",
|
|
8012
|
+
status: res.status,
|
|
8013
|
+
message: parsed.error?.message ?? (rawText.slice(0, 200) || res.statusText),
|
|
8014
|
+
durationMs
|
|
8015
|
+
};
|
|
8016
|
+
}
|
|
8017
|
+
return {
|
|
8018
|
+
ok: false,
|
|
8019
|
+
reason: "rpc-error",
|
|
8020
|
+
error: parsed.error ?? {
|
|
8021
|
+
code: -32603,
|
|
8022
|
+
message: "Malformed JSON-RPC response.",
|
|
8023
|
+
data: rawText
|
|
8024
|
+
},
|
|
8025
|
+
durationMs
|
|
8026
|
+
};
|
|
8027
|
+
}
|
|
8028
|
+
const rawTools = parsed.result.tools;
|
|
8029
|
+
const tools = Array.isArray(rawTools) ? rawTools.flatMap((entry) => {
|
|
8030
|
+
if (!entry || typeof entry !== "object")
|
|
8031
|
+
return [];
|
|
8032
|
+
const name = entry.name;
|
|
8033
|
+
if (typeof name !== "string" || !name)
|
|
8034
|
+
return [];
|
|
8035
|
+
const description = entry.description;
|
|
8036
|
+
return [
|
|
8037
|
+
{
|
|
8038
|
+
name,
|
|
8039
|
+
...typeof description === "string" ? { description } : {}
|
|
8040
|
+
}
|
|
8041
|
+
];
|
|
8042
|
+
}) : [];
|
|
8043
|
+
return { ok: true, tools, durationMs };
|
|
8044
|
+
} catch (err) {
|
|
8045
|
+
const durationMs = Date.now() - startedAt;
|
|
8046
|
+
const aborted = err instanceof Error && (err.name === "AbortError" || /abort/i.test(err.message));
|
|
8047
|
+
return {
|
|
8048
|
+
ok: false,
|
|
8049
|
+
reason: "http-error",
|
|
8050
|
+
status: 0,
|
|
8051
|
+
message: aborted ? `Request timed out after ${args.timeoutMs ?? DEFAULT_TIMEOUT_MS3}ms.` : err instanceof Error ? err.message : String(err),
|
|
8052
|
+
durationMs
|
|
8053
|
+
};
|
|
8054
|
+
} finally {
|
|
8055
|
+
clearTimeout(timer);
|
|
8056
|
+
}
|
|
8057
|
+
}
|
|
8058
|
+
function parseJsonRpc(text) {
|
|
8059
|
+
if (!text.trim()) {
|
|
8060
|
+
return {
|
|
8061
|
+
ok: false,
|
|
8062
|
+
result: {},
|
|
8063
|
+
error: { code: -32700, message: "Response body was empty." }
|
|
8064
|
+
};
|
|
8065
|
+
}
|
|
8066
|
+
let parsed;
|
|
8067
|
+
try {
|
|
8068
|
+
parsed = JSON.parse(text);
|
|
8069
|
+
} catch (err) {
|
|
8070
|
+
return {
|
|
8071
|
+
ok: false,
|
|
8072
|
+
result: {},
|
|
8073
|
+
error: {
|
|
8074
|
+
code: -32700,
|
|
8075
|
+
message: `Response was not JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
8076
|
+
}
|
|
8077
|
+
};
|
|
8078
|
+
}
|
|
8079
|
+
if (!parsed || typeof parsed !== "object") {
|
|
8080
|
+
return {
|
|
8081
|
+
ok: false,
|
|
8082
|
+
result: {},
|
|
8083
|
+
error: { code: -32603, message: "Malformed JSON-RPC response.", data: parsed }
|
|
8084
|
+
};
|
|
8085
|
+
}
|
|
8086
|
+
const obj = parsed;
|
|
8087
|
+
if ("result" in obj && obj.result && typeof obj.result === "object") {
|
|
8088
|
+
return { ok: true, result: obj.result };
|
|
8089
|
+
}
|
|
8090
|
+
if ("error" in obj && obj.error && typeof obj.error === "object") {
|
|
8091
|
+
const errObj = obj.error;
|
|
8092
|
+
return {
|
|
8093
|
+
ok: false,
|
|
8094
|
+
result: {},
|
|
8095
|
+
error: {
|
|
8096
|
+
code: typeof errObj.code === "number" ? errObj.code : -32603,
|
|
8097
|
+
message: typeof errObj.message === "string" ? errObj.message : "RPC error.",
|
|
8098
|
+
data: errObj.data
|
|
8099
|
+
}
|
|
8100
|
+
};
|
|
8101
|
+
}
|
|
8102
|
+
return {
|
|
8103
|
+
ok: false,
|
|
8104
|
+
result: {},
|
|
8105
|
+
error: { code: -32603, message: "Response had no result or error field." }
|
|
8106
|
+
};
|
|
8107
|
+
}
|
|
8108
|
+
function unwrapEventStream(text) {
|
|
8109
|
+
const parts = [];
|
|
8110
|
+
for (const line of text.split(/\r?\n/)) {
|
|
8111
|
+
if (line.startsWith("data:"))
|
|
8112
|
+
parts.push(line.slice(5).replace(/^ /, ""));
|
|
8113
|
+
}
|
|
8114
|
+
return parts.join("");
|
|
8115
|
+
}
|
|
8116
|
+
async function parseToolArguments(opts) {
|
|
8117
|
+
const raw = opts.raw?.trim();
|
|
8118
|
+
if (!raw)
|
|
8119
|
+
return { ok: true, value: {} };
|
|
8120
|
+
let source;
|
|
8121
|
+
if (raw === "@-" || raw === "-") {
|
|
8122
|
+
const reader = opts.readStdin ?? defaultStdinReader2;
|
|
8123
|
+
source = await reader();
|
|
8124
|
+
} else if (raw.startsWith("@")) {
|
|
8125
|
+
const filePath = resolve6(opts.cwd ?? process.cwd(), raw.slice(1));
|
|
8126
|
+
if (!existsSync13(filePath)) {
|
|
8127
|
+
return { ok: false, reason: "file-not-found", path: filePath };
|
|
8128
|
+
}
|
|
8129
|
+
source = readFileSync11(filePath, "utf-8");
|
|
8130
|
+
} else {
|
|
8131
|
+
source = raw;
|
|
8132
|
+
}
|
|
8133
|
+
let parsed;
|
|
8134
|
+
try {
|
|
8135
|
+
parsed = JSON.parse(source);
|
|
8136
|
+
} catch (err) {
|
|
8137
|
+
return {
|
|
8138
|
+
ok: false,
|
|
8139
|
+
reason: "parse-error",
|
|
8140
|
+
message: err instanceof Error ? err.message : String(err)
|
|
8141
|
+
};
|
|
8142
|
+
}
|
|
8143
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8144
|
+
return { ok: false, reason: "not-an-object" };
|
|
8145
|
+
}
|
|
8146
|
+
return { ok: true, value: parsed };
|
|
8147
|
+
}
|
|
8148
|
+
async function defaultStdinReader2() {
|
|
8149
|
+
const chunks = [];
|
|
8150
|
+
return new Promise((res, rej) => {
|
|
8151
|
+
process.stdin.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
8152
|
+
process.stdin.on("end", () => res(Buffer.concat(chunks).toString("utf-8")));
|
|
8153
|
+
process.stdin.on("error", rej);
|
|
8154
|
+
});
|
|
8155
|
+
}
|
|
8156
|
+
function formatToolCallResult(result) {
|
|
8157
|
+
const lines = [];
|
|
8158
|
+
const blocks = result.content ?? [];
|
|
8159
|
+
for (const block of blocks) {
|
|
8160
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
8161
|
+
lines.push(block.text);
|
|
8162
|
+
continue;
|
|
8163
|
+
}
|
|
8164
|
+
if (block.type === "image") {
|
|
8165
|
+
const sized = typeof block.data === "string" ? `${block.data.length} bytes` : "?";
|
|
8166
|
+
lines.push(`[image ${block.mimeType ?? "application/octet-stream"} · ${sized} base64]`);
|
|
8167
|
+
continue;
|
|
8168
|
+
}
|
|
8169
|
+
if (block.type === "audio") {
|
|
8170
|
+
const sized = typeof block.data === "string" ? `${block.data.length} bytes` : "?";
|
|
8171
|
+
lines.push(`[audio ${block.mimeType ?? "application/octet-stream"} · ${sized} base64]`);
|
|
8172
|
+
continue;
|
|
8173
|
+
}
|
|
8174
|
+
if (block.type === "resource") {
|
|
8175
|
+
const r = block.resource ?? {};
|
|
8176
|
+
const inlineText = typeof r.text === "string" ? `
|
|
8177
|
+
${r.text}` : "";
|
|
8178
|
+
lines.push(`[resource ${r.uri ?? "<no uri>"}${r.mimeType ? " · " + r.mimeType : ""}]${inlineText}`);
|
|
8179
|
+
continue;
|
|
8180
|
+
}
|
|
8181
|
+
lines.push(`[${block.type} block: ${truncateJsonPreview(block)}]`);
|
|
8182
|
+
}
|
|
8183
|
+
if (lines.length === 0 && result.structuredContent !== undefined) {
|
|
8184
|
+
lines.push(`[structured]`);
|
|
8185
|
+
lines.push(JSON.stringify(result.structuredContent, null, 2));
|
|
8186
|
+
}
|
|
8187
|
+
if (lines.length === 0) {
|
|
8188
|
+
lines.push("(no content)");
|
|
8189
|
+
}
|
|
8190
|
+
return { lines, isToolError: result.isError === true };
|
|
8191
|
+
}
|
|
8192
|
+
function truncateJsonPreview(value) {
|
|
8193
|
+
const json = (() => {
|
|
8194
|
+
try {
|
|
8195
|
+
return JSON.stringify(value);
|
|
8196
|
+
} catch {
|
|
8197
|
+
return "<unserializable>";
|
|
8198
|
+
}
|
|
8199
|
+
})();
|
|
8200
|
+
return json.length > 100 ? json.slice(0, 97) + "…" : json;
|
|
8201
|
+
}
|
|
8202
|
+
|
|
8203
|
+
// src/commands/skills-invoke.ts
|
|
8204
|
+
function parseTimeoutMs(value, fallbackMs) {
|
|
8205
|
+
if (!value)
|
|
8206
|
+
return fallbackMs;
|
|
8207
|
+
const n = Number(value);
|
|
8208
|
+
if (!Number.isFinite(n) || n <= 0 || n > 600) {
|
|
8209
|
+
throw new Error(`Invalid --timeout: ${value} (expected seconds, 1–600)`);
|
|
8210
|
+
}
|
|
8211
|
+
return Math.round(n * 1000);
|
|
8212
|
+
}
|
|
8213
|
+
function registerSkillInvokeCommand(skills) {
|
|
8214
|
+
skills.command("invoke <skillId>").description("Run a deployed skill from the terminal. Resolves the skill’s MCP endpoint, auto-discovers its single tool, and calls it.").option("--org <organizationId>", "Organization ID (overrides default)").option("--input <json>", "Skill input as inline JSON, `@path/to/input.json`, or `@-` for stdin. Defaults to `{}`.").option("--tool <name>", "Override the auto-discovered tool name (a deployed skill normally exposes exactly one)").option("--timeout <seconds>", "Request timeout in seconds (default 60, max 600)").option("--raw", "Print the raw JSON-RPC `result` envelope").addHelpText("after", [
|
|
8215
|
+
"",
|
|
8216
|
+
"Examples:",
|
|
8217
|
+
` $ mcp skills invoke skill_123 --input '{"topic":"weather"}'`,
|
|
8218
|
+
" $ mcp skills invoke skill_123 --input @./input.json",
|
|
8219
|
+
` $ echo '{"q":"hi"}' | mcp skills invoke skill_123 --input @-`,
|
|
8220
|
+
" $ mcp --json skills invoke skill_123",
|
|
8221
|
+
"",
|
|
8222
|
+
"Exit codes:",
|
|
8223
|
+
" 0 the call succeeded (the skill may still report isError=true)",
|
|
8224
|
+
" 1 the skill has no active deployment, the input was malformed,",
|
|
8225
|
+
" or the HTTP/JSON-RPC layer errored"
|
|
8226
|
+
].join(`
|
|
8227
|
+
`)).action(runAction(async (skillId, opts) => {
|
|
8228
|
+
await runSkillInvoke(skillId, opts);
|
|
8229
|
+
}));
|
|
8230
|
+
}
|
|
8231
|
+
async function runSkillInvoke(skillId, opts) {
|
|
8232
|
+
const orgId = await resolveOrgId(opts.org);
|
|
8233
|
+
const parsedInput = await parseToolArguments({ raw: opts.input });
|
|
8234
|
+
if (!parsedInput.ok) {
|
|
8235
|
+
failOnInputError(parsedInput);
|
|
8236
|
+
}
|
|
8237
|
+
const data = await api.get("/api/v1/skill", {
|
|
8238
|
+
organizationId: orgId,
|
|
8239
|
+
skillId
|
|
8240
|
+
});
|
|
8241
|
+
const active = data.skill.activeDeployment;
|
|
8242
|
+
if (!active || !active.mcpUrl) {
|
|
8243
|
+
printError(`Skill ${c.bold(data.skill.name)} has no active deployment. Deploy it first, then retry.`);
|
|
8244
|
+
throw new CliExitError(1);
|
|
8245
|
+
}
|
|
8246
|
+
if (active.status !== "active") {
|
|
8247
|
+
printWarn(`Skill deployment is ${active.status}, not active — the call may be refused.`);
|
|
8248
|
+
}
|
|
8249
|
+
const timeoutMs = parseTimeoutMs(opts.timeout, 60000);
|
|
8250
|
+
const toolName = opts.tool ?? await discoverSkillTool(active.mcpUrl, timeoutMs);
|
|
8251
|
+
const result = await invokeMcpTool({
|
|
8252
|
+
url: active.mcpUrl,
|
|
8253
|
+
toolName,
|
|
8254
|
+
toolArguments: parsedInput.ok ? parsedInput.value : {},
|
|
8255
|
+
timeoutMs
|
|
8256
|
+
});
|
|
8257
|
+
if (isJsonMode()) {
|
|
8258
|
+
printJson({
|
|
8259
|
+
skillId: data.skill.id,
|
|
8260
|
+
slug: data.skill.slug,
|
|
8261
|
+
deploymentId: active.id,
|
|
8262
|
+
toolName,
|
|
8263
|
+
...invokeResultToJson(result)
|
|
8264
|
+
});
|
|
8265
|
+
if (!result.ok)
|
|
8266
|
+
throw new CliExitError(1);
|
|
8267
|
+
return;
|
|
8268
|
+
}
|
|
8269
|
+
if (!result.ok) {
|
|
8270
|
+
if (result.reason === "rpc-error") {
|
|
8271
|
+
printError(`RPC error from ${c.bold(toolName)}: ${result.error.message}` + (result.error.code ? c.dim(` (code ${result.error.code})`) : ""));
|
|
8272
|
+
} else {
|
|
8273
|
+
const statusLine = result.status > 0 ? `HTTP ${result.status}` : "network error";
|
|
8274
|
+
printError(`${statusLine} from skill runtime: ${result.message}`);
|
|
8275
|
+
}
|
|
8276
|
+
throw new CliExitError(1);
|
|
8277
|
+
}
|
|
8278
|
+
if (opts.raw) {
|
|
8279
|
+
printInfo(JSON.stringify(result.result, null, 2));
|
|
8280
|
+
return;
|
|
8281
|
+
}
|
|
8282
|
+
const formatted = formatToolCallResult(result.result);
|
|
8283
|
+
for (const line of formatted.lines)
|
|
8284
|
+
printInfo(line);
|
|
8285
|
+
if (formatted.isToolError) {
|
|
8286
|
+
printWarn(`Skill reported isError=true. Inspect ${c.bold("--json")} or ${c.bold("--raw")} for the full envelope.`);
|
|
8287
|
+
}
|
|
8288
|
+
}
|
|
8289
|
+
async function discoverSkillTool(mcpUrl, timeoutMs) {
|
|
8290
|
+
const listed = await listMcpTools({ url: mcpUrl, timeoutMs });
|
|
8291
|
+
if (!listed.ok) {
|
|
8292
|
+
const detail = listed.reason === "rpc-error" ? listed.error.message : `${listed.status > 0 ? `HTTP ${listed.status}` : "network error"}: ${listed.message}`;
|
|
8293
|
+
printError(`Could not list the skill's tools to auto-discover the entry point (${detail}). Pass --tool <name> to call it directly.`);
|
|
8294
|
+
throw new CliExitError(1);
|
|
8295
|
+
}
|
|
8296
|
+
if (listed.tools.length === 0) {
|
|
8297
|
+
printError("The skill deployment advertises no tools. It may still be warming up — retry shortly.");
|
|
8298
|
+
throw new CliExitError(1);
|
|
8299
|
+
}
|
|
8300
|
+
if (listed.tools.length > 1) {
|
|
8301
|
+
printError(`The skill exposes ${listed.tools.length} tools (${listed.tools.map((tool) => tool.name).join(", ")}). Pass --tool <name> to choose one.`);
|
|
8302
|
+
throw new CliExitError(1);
|
|
8303
|
+
}
|
|
8304
|
+
return listed.tools[0].name;
|
|
8305
|
+
}
|
|
8306
|
+
function failOnInputError(parsed) {
|
|
8307
|
+
if (parsed.ok)
|
|
8308
|
+
throw new Error("unreachable");
|
|
8309
|
+
if (parsed.reason === "parse-error") {
|
|
8310
|
+
printError(`Failed to parse --input JSON: ${parsed.message}`);
|
|
8311
|
+
} else if (parsed.reason === "file-not-found") {
|
|
8312
|
+
printError(`--input file not found: ${parsed.path}`);
|
|
8313
|
+
} else if (parsed.reason === "not-an-object") {
|
|
8314
|
+
printError("--input must decode to a JSON object (not array / scalar).");
|
|
8315
|
+
}
|
|
8316
|
+
throw new CliExitError(1);
|
|
8317
|
+
}
|
|
8318
|
+
function invokeResultToJson(result) {
|
|
8319
|
+
if (result.ok) {
|
|
8320
|
+
return { ok: true, durationMs: result.durationMs, result: result.result };
|
|
8321
|
+
}
|
|
8322
|
+
if (result.reason === "rpc-error") {
|
|
8323
|
+
return {
|
|
8324
|
+
ok: false,
|
|
8325
|
+
durationMs: result.durationMs,
|
|
8326
|
+
error: { kind: "rpc-error", ...result.error }
|
|
8327
|
+
};
|
|
8328
|
+
}
|
|
8329
|
+
return {
|
|
8330
|
+
ok: false,
|
|
8331
|
+
durationMs: result.durationMs,
|
|
8332
|
+
error: { kind: "http-error", status: result.status, message: result.message }
|
|
8333
|
+
};
|
|
8334
|
+
}
|
|
8335
|
+
|
|
8336
|
+
// src/commands/skills.ts
|
|
8337
|
+
async function runClaudeMcpAdd(connectionName, mcpUrl) {
|
|
8338
|
+
return new Promise((resolve7) => {
|
|
8339
|
+
const child = spawn3("claude", ["mcp", "add", "--transport", "http", connectionName, mcpUrl], {
|
|
8340
|
+
stdio: "inherit",
|
|
8341
|
+
shell: false
|
|
8342
|
+
});
|
|
8343
|
+
child.on("error", () => resolve7(127));
|
|
8344
|
+
child.on("exit", (code) => resolve7(code ?? 1));
|
|
8345
|
+
});
|
|
8346
|
+
}
|
|
8347
|
+
function registerSkillCommands(program2) {
|
|
8348
|
+
const skills = program2.command("skills").description("Manage skills");
|
|
8349
|
+
registerSkillMutationCommands(skills);
|
|
8350
|
+
registerSkillTestCommands(skills);
|
|
8351
|
+
registerSkillInvokeCommand(skills);
|
|
8352
|
+
skills.command("list").description("List skills in an organization").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Filter by project ID").option("--limit <n>", "Maximum results (default 25)", parsePositiveIntOption("limit"), "25").addHelpText("after", [
|
|
8353
|
+
"",
|
|
8354
|
+
"Examples:",
|
|
8355
|
+
" $ mcp skills list",
|
|
8356
|
+
" $ mcp skills list --filter status=published",
|
|
8357
|
+
" $ mcp skills list --field id,name --format jsonl"
|
|
8358
|
+
].join(`
|
|
8359
|
+
`)).action(runAction(async (opts) => {
|
|
8360
|
+
const orgId = await resolveOrgId(opts.org);
|
|
8361
|
+
const params = {
|
|
8362
|
+
organizationId: orgId,
|
|
8363
|
+
limit: opts.limit
|
|
8364
|
+
};
|
|
8365
|
+
if (opts.project)
|
|
8366
|
+
params["projectId"] = opts.project;
|
|
8367
|
+
const data = await api.get("/api/v1/skills", params);
|
|
8368
|
+
printList(data.skills.map((s) => ({
|
|
8369
|
+
id: s.id,
|
|
8370
|
+
name: s.name,
|
|
8371
|
+
project: s.projectName,
|
|
8372
|
+
status: s.status,
|
|
7648
8373
|
version: s.version,
|
|
7649
8374
|
updated: formatDate(s.updatedAt)
|
|
7650
8375
|
})), [
|
|
@@ -7686,7 +8411,7 @@ function registerSkillCommands(program2) {
|
|
|
7686
8411
|
skills.command("connect <skillId>").description("Print (or apply) the MCP configuration that connects this skill to your coding agent").option("--org <organizationId>", "Organization ID").option("--agent <agent>", `Agent preset: ${AGENT_KEYS.join(", ")}`, "claude-code").option("--name <name>", "Override the connection name shown in the agent (defaults to the skill slug)").option("--apply", 'For claude-code: invoke "claude mcp add" automatically. Other agents print only.').action(runAction(async (skillId, opts) => {
|
|
7687
8412
|
if (!isAgentKey(opts.agent)) {
|
|
7688
8413
|
printError(`Unknown --agent '${opts.agent}'. Valid values: ${AGENT_KEYS.join(", ")}.`);
|
|
7689
|
-
|
|
8414
|
+
throw new CliExitError(1);
|
|
7690
8415
|
}
|
|
7691
8416
|
const agent = opts.agent;
|
|
7692
8417
|
const orgId = await resolveOrgId(opts.org);
|
|
@@ -7707,7 +8432,7 @@ function registerSkillCommands(program2) {
|
|
|
7707
8432
|
} else {
|
|
7708
8433
|
printError(`Skill '${s.name}' has no active deployment. Deploy it first, then re-run "mcp skills connect".`);
|
|
7709
8434
|
}
|
|
7710
|
-
|
|
8435
|
+
throw new CliExitError(1);
|
|
7711
8436
|
}
|
|
7712
8437
|
const connectionName = opts.name?.trim() || s.slug || s.name;
|
|
7713
8438
|
const preset = getAgentPreset({
|
|
@@ -7726,7 +8451,7 @@ function registerSkillCommands(program2) {
|
|
|
7726
8451
|
} else {
|
|
7727
8452
|
printError(`--apply is only supported for --agent claude-code right now. For ${preset.label}, copy the snippet into ${preset.location}.`);
|
|
7728
8453
|
}
|
|
7729
|
-
|
|
8454
|
+
throw new CliExitError(1);
|
|
7730
8455
|
}
|
|
7731
8456
|
if (!isJsonMode()) {
|
|
7732
8457
|
printInfo(`Applying via "claude mcp add --transport http ${connectionName} ${deployment.mcpUrl}"…`);
|
|
@@ -7742,7 +8467,7 @@ function registerSkillCommands(program2) {
|
|
|
7742
8467
|
} else {
|
|
7743
8468
|
printError('Could not find the "claude" CLI on PATH. Install Claude Code or rerun without --apply.');
|
|
7744
8469
|
}
|
|
7745
|
-
|
|
8470
|
+
throw new CliExitError(1);
|
|
7746
8471
|
}
|
|
7747
8472
|
if (code !== 0) {
|
|
7748
8473
|
if (isJsonMode()) {
|
|
@@ -7754,7 +8479,7 @@ function registerSkillCommands(program2) {
|
|
|
7754
8479
|
} else {
|
|
7755
8480
|
printError(`"claude mcp add" exited with code ${code}.`);
|
|
7756
8481
|
}
|
|
7757
|
-
|
|
8482
|
+
throw new CliExitError(code || 1);
|
|
7758
8483
|
}
|
|
7759
8484
|
if (isJsonMode()) {
|
|
7760
8485
|
printJson({
|
|
@@ -7878,6 +8603,29 @@ function registerSkillCommands(program2) {
|
|
|
7878
8603
|
}
|
|
7879
8604
|
|
|
7880
8605
|
// src/commands/api-keys.ts
|
|
8606
|
+
var MAX_EXPIRY_DAYS = 365;
|
|
8607
|
+
function parseExpiresInDays(raw) {
|
|
8608
|
+
if (raw === undefined)
|
|
8609
|
+
return;
|
|
8610
|
+
const trimmed = raw.trim().toLowerCase();
|
|
8611
|
+
const match = /^(\d+)\s*(d|w|m)?$/.exec(trimmed);
|
|
8612
|
+
if (!match) {
|
|
8613
|
+
throw new Error(`Invalid --expires-in: ${raw}. Expected a duration like 90d, 12w, 6m, or a day count.`);
|
|
8614
|
+
}
|
|
8615
|
+
const value = Number.parseInt(match[1], 10);
|
|
8616
|
+
const unit = match[2] ?? "d";
|
|
8617
|
+
let days;
|
|
8618
|
+
if (unit === "w")
|
|
8619
|
+
days = value * 7;
|
|
8620
|
+
else if (unit === "m")
|
|
8621
|
+
days = value * 30;
|
|
8622
|
+
else
|
|
8623
|
+
days = value;
|
|
8624
|
+
if (days < 1 || days > MAX_EXPIRY_DAYS) {
|
|
8625
|
+
throw new Error(`--expires-in must resolve to between 1 and ${MAX_EXPIRY_DAYS} days (got ${days}).`);
|
|
8626
|
+
}
|
|
8627
|
+
return days;
|
|
8628
|
+
}
|
|
7881
8629
|
function formatPreview(key) {
|
|
7882
8630
|
return `${key.keyPrefix}…${key.lastFour}`;
|
|
7883
8631
|
}
|
|
@@ -7890,37 +8638,92 @@ function registerApiKeyCommands(program2) {
|
|
|
7890
8638
|
name: k.name,
|
|
7891
8639
|
preview: formatPreview(k),
|
|
7892
8640
|
created: formatDate(k.createdAt),
|
|
7893
|
-
"last used": k.lastUsedAt ? formatDate(k.lastUsedAt) : "—"
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
{ key: "
|
|
7897
|
-
{ key: "
|
|
7898
|
-
{ key: "
|
|
7899
|
-
{ key: "
|
|
7900
|
-
|
|
8641
|
+
"last used": k.lastUsedAt ? formatDate(k.lastUsedAt) : "—",
|
|
8642
|
+
expires: k.expiresAt ? formatDate(k.expiresAt) : "never"
|
|
8643
|
+
})), [
|
|
8644
|
+
{ key: "id", label: "ID", width: 20 },
|
|
8645
|
+
{ key: "name", label: "Name", width: 24 },
|
|
8646
|
+
{ key: "preview", label: "Preview", width: 18 },
|
|
8647
|
+
{ key: "created", label: "Created", width: 22 },
|
|
8648
|
+
{ key: "last used", label: "Last Used", width: 22 },
|
|
8649
|
+
{ key: "expires", label: "Expires", width: 22 }
|
|
8650
|
+
], data);
|
|
8651
|
+
}));
|
|
8652
|
+
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) => {
|
|
8653
|
+
const name = opts.name.trim();
|
|
8654
|
+
if (!name)
|
|
8655
|
+
throw new Error("--name must not be empty.");
|
|
8656
|
+
const expiresInDays = parseExpiresInDays(opts.expiresIn);
|
|
8657
|
+
const data = await api.post("/api/v1/api-keys", {
|
|
8658
|
+
name,
|
|
8659
|
+
...expiresInDays !== undefined ? { expiresInDays } : {}
|
|
8660
|
+
});
|
|
8661
|
+
if (isJsonMode()) {
|
|
8662
|
+
printJson(data);
|
|
8663
|
+
return;
|
|
8664
|
+
}
|
|
8665
|
+
printSuccess("API key created — copy the secret below now, it will not be shown again.");
|
|
8666
|
+
console.log("");
|
|
8667
|
+
printKeyValue({
|
|
8668
|
+
id: data.apiKeyId,
|
|
8669
|
+
name: data.name,
|
|
8670
|
+
key: data.apiKey,
|
|
8671
|
+
"key prefix": data.keyPrefix,
|
|
8672
|
+
"last four": data.lastFour,
|
|
8673
|
+
expires: data.expiresAt ? formatDate(data.expiresAt) : "never"
|
|
8674
|
+
});
|
|
8675
|
+
console.log("");
|
|
8676
|
+
printInfo("Save it via `mcp login --key <secret>` or export MCPCLOUD_API_KEY=<secret>.");
|
|
8677
|
+
}));
|
|
8678
|
+
apiKeys.command("describe <apiKeyId>").description("Show one API key (status, expiry, last used) — never the secret").action(runAction(async (apiKeyId) => {
|
|
8679
|
+
const data = await api.get("/api/v1/api-keys", { apiKeyId });
|
|
8680
|
+
if (isJsonMode()) {
|
|
8681
|
+
printJson(data);
|
|
8682
|
+
return;
|
|
8683
|
+
}
|
|
8684
|
+
const k = data.apiKey;
|
|
8685
|
+
printKeyValue({
|
|
8686
|
+
id: k.id,
|
|
8687
|
+
name: k.name,
|
|
8688
|
+
preview: formatPreview(k),
|
|
8689
|
+
status: k.status,
|
|
8690
|
+
created: formatDate(k.createdAt),
|
|
8691
|
+
"last used": k.lastUsedAt ? formatDate(k.lastUsedAt) : "—",
|
|
8692
|
+
expires: k.expiresAt ? formatDate(k.expiresAt) : "never",
|
|
8693
|
+
revoked: k.revokedAt ? formatDate(k.revokedAt) : "—"
|
|
8694
|
+
});
|
|
7901
8695
|
}));
|
|
7902
|
-
apiKeys.command("
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
8696
|
+
apiKeys.command("rotate <apiKeyId>").description("Mint a replacement key and grace-expire the old one (24h window). 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", [
|
|
8697
|
+
"",
|
|
8698
|
+
"The old key keeps working for 24h so running automation can swap to the",
|
|
8699
|
+
"new secret before the old one stops. Update your secrets within that window.",
|
|
8700
|
+
"",
|
|
8701
|
+
"Examples:",
|
|
8702
|
+
" $ mcp api-keys rotate key_123",
|
|
8703
|
+
" $ mcp api-keys rotate key_123 --expires-in 90d"
|
|
8704
|
+
].join(`
|
|
8705
|
+
`)).action(runAction(async (apiKeyId, opts) => {
|
|
8706
|
+
const expiresInDays = parseExpiresInDays(opts.expiresIn);
|
|
8707
|
+
const data = await api.patch("/api/v1/api-keys", {
|
|
8708
|
+
apiKeyId,
|
|
8709
|
+
...expiresInDays !== undefined ? { expiresInDays } : {}
|
|
7908
8710
|
});
|
|
7909
8711
|
if (isJsonMode()) {
|
|
7910
8712
|
printJson(data);
|
|
7911
8713
|
return;
|
|
7912
8714
|
}
|
|
7913
|
-
printSuccess("API key
|
|
8715
|
+
printSuccess("API key rotated — copy the new secret below now, it will not be shown again.");
|
|
7914
8716
|
console.log("");
|
|
7915
8717
|
printKeyValue({
|
|
7916
|
-
id: data.apiKeyId,
|
|
8718
|
+
"new id": data.apiKeyId,
|
|
7917
8719
|
name: data.name,
|
|
7918
8720
|
key: data.apiKey,
|
|
7919
8721
|
"key prefix": data.keyPrefix,
|
|
7920
|
-
"last four": data.lastFour
|
|
8722
|
+
"last four": data.lastFour,
|
|
8723
|
+
expires: data.expiresAt ? formatDate(data.expiresAt) : "never",
|
|
8724
|
+
"old key": data.rotatedFrom?.apiKeyId ?? "—",
|
|
8725
|
+
"old key valid until": data.rotatedFrom ? formatDate(data.rotatedFrom.gracePeriodEndsAt) : "—"
|
|
7921
8726
|
});
|
|
7922
|
-
console.log("");
|
|
7923
|
-
printInfo("Save it via `mcp login --key <secret>` or export MCPCLOUD_API_KEY=<secret>.");
|
|
7924
8727
|
}));
|
|
7925
8728
|
apiKeys.command("revoke <apiKeyId>").description("Permanently revoke an API key").action(runAction(async (apiKeyId) => {
|
|
7926
8729
|
const data = await api.delete("/api/v1/api-keys", { apiKeyId });
|
|
@@ -7934,9 +8737,9 @@ function registerApiKeyCommands(program2) {
|
|
|
7934
8737
|
|
|
7935
8738
|
// src/commands/config.ts
|
|
7936
8739
|
import { homedir as homedir2 } from "node:os";
|
|
7937
|
-
import { join as
|
|
8740
|
+
import { join as join11 } from "node:path";
|
|
7938
8741
|
function configFilePath() {
|
|
7939
|
-
return
|
|
8742
|
+
return join11(homedir2(), ".mcpcloud", "config.json");
|
|
7940
8743
|
}
|
|
7941
8744
|
function previewKey2(key) {
|
|
7942
8745
|
if (!key)
|
|
@@ -8083,353 +8886,136 @@ function registerConfigCommands(program2) {
|
|
|
8083
8886
|
cleared.push("open preference");
|
|
8084
8887
|
if (hadCmd)
|
|
8085
8888
|
cleared.push("editor command");
|
|
8086
|
-
if (hadTarget)
|
|
8087
|
-
cleared.push("open target");
|
|
8088
|
-
const what = cleared.length === 0 ? "nothing (already empty)" : cleared.join(" + ");
|
|
8089
|
-
printSuccess(`Cleared ${what}. \`mcp dev\` will re-detect editors and prompt next session.`);
|
|
8090
|
-
}));
|
|
8091
|
-
config.command("set-editor-command <command>").description("Override the editor launch command (default: code; e.g. cursor, code-insiders)").action(runAction((command) => {
|
|
8092
|
-
const cfg = readConfig();
|
|
8093
|
-
cfg.editorCommand = command.trim();
|
|
8094
|
-
writeConfig(cfg);
|
|
8095
|
-
if (isJsonMode()) {
|
|
8096
|
-
printJson({ editorCommand: cfg.editorCommand });
|
|
8097
|
-
return;
|
|
8098
|
-
}
|
|
8099
|
-
printSuccess(`Saved editor command: ${cfg.editorCommand}`);
|
|
8100
|
-
}));
|
|
8101
|
-
config.command("use <name>").description("Switch the active profile (e.g. staging, prod)").action(runAction((name) => {
|
|
8102
|
-
setCurrentProfile(name);
|
|
8103
|
-
if (isJsonMode()) {
|
|
8104
|
-
printJson({ currentProfile: name });
|
|
8105
|
-
return;
|
|
8106
|
-
}
|
|
8107
|
-
printSuccess(`Switched to profile: ${name}`);
|
|
8108
|
-
}));
|
|
8109
|
-
const profile = config.command("profile").description("Manage named config profiles (apiKey + baseUrl + defaultOrganizationId)");
|
|
8110
|
-
profile.command("list").description("List all configured profiles").action(runAction(() => {
|
|
8111
|
-
const profiles = listProfiles();
|
|
8112
|
-
if (isJsonMode()) {
|
|
8113
|
-
printJson({ profiles, currentProfile: getActiveProfileName() });
|
|
8114
|
-
return;
|
|
8115
|
-
}
|
|
8116
|
-
if (profiles.length === 0) {
|
|
8117
|
-
console.log("No profiles configured. Run `mcp config profile add <name>` to create one.");
|
|
8118
|
-
return;
|
|
8119
|
-
}
|
|
8120
|
-
printTable(profiles.map((p) => ({
|
|
8121
|
-
current: p.isCurrent ? c.green("✓") : "",
|
|
8122
|
-
name: p.name,
|
|
8123
|
-
baseUrl: p.baseUrl ?? "—",
|
|
8124
|
-
"default org": p.defaultOrganizationId ?? "—",
|
|
8125
|
-
"api key": p.hasApiKey ? c.dim("saved") : c.dim("—")
|
|
8126
|
-
})), [
|
|
8127
|
-
{ key: "current", label: "", width: 3 },
|
|
8128
|
-
{ key: "name", label: "Name", width: 18 },
|
|
8129
|
-
{ key: "baseUrl", label: "Base URL", width: 36 },
|
|
8130
|
-
{ key: "default org", label: "Default org", width: 22 },
|
|
8131
|
-
{ key: "api key", label: "API key", width: 10 }
|
|
8132
|
-
]);
|
|
8133
|
-
}));
|
|
8134
|
-
profile.command("add <name>").description("Create a new profile (use --url / --key / --app / --org to seed values)").option("--url <url>", "API base URL for the new profile").option("--key <apiKey>", "API key for the new profile (writes to config.json mode 0600)").option("--app <url>", "Dashboard origin override for the new profile").option("--org <organizationId>", "Default organization ID for the new profile").action(runAction((name, opts) => {
|
|
8135
|
-
if (opts.url) {
|
|
8136
|
-
try {
|
|
8137
|
-
new URL(opts.url);
|
|
8138
|
-
} catch {
|
|
8139
|
-
throw new Error(`Invalid --url: ${opts.url}`);
|
|
8140
|
-
}
|
|
8141
|
-
}
|
|
8142
|
-
if (opts.app) {
|
|
8143
|
-
try {
|
|
8144
|
-
new URL(opts.app);
|
|
8145
|
-
} catch {
|
|
8146
|
-
throw new Error(`Invalid --app: ${opts.app}`);
|
|
8147
|
-
}
|
|
8148
|
-
}
|
|
8149
|
-
const seed = {};
|
|
8150
|
-
if (opts.url)
|
|
8151
|
-
seed.baseUrl = opts.url;
|
|
8152
|
-
if (opts.key)
|
|
8153
|
-
seed.apiKey = opts.key;
|
|
8154
|
-
if (opts.app)
|
|
8155
|
-
seed.appUrl = opts.app.replace(/\/+$/, "");
|
|
8156
|
-
if (opts.org)
|
|
8157
|
-
seed.defaultOrganizationId = opts.org;
|
|
8158
|
-
addProfile(name, seed);
|
|
8159
|
-
if (isJsonMode()) {
|
|
8160
|
-
printJson({ added: name.trim(), seeded: Object.keys(seed) });
|
|
8161
|
-
return;
|
|
8162
|
-
}
|
|
8163
|
-
printSuccess(`Created profile: ${name.trim()}`);
|
|
8164
|
-
if (opts.key) {
|
|
8165
|
-
console.log(c.dim(" Tip: passing --key writes the secret to your shell history. Prefer `mcp --profile <name> login` for interactive setup."));
|
|
8166
|
-
}
|
|
8167
|
-
console.log(c.dim(` Switch to it with \`mcp config use ${name.trim()}\`.`));
|
|
8168
|
-
}));
|
|
8169
|
-
profile.command("remove <name>").description("Delete a profile (cannot remove the active or only-remaining profile)").action(runAction((name) => {
|
|
8170
|
-
removeProfile(name);
|
|
8171
|
-
if (isJsonMode()) {
|
|
8172
|
-
printJson({ removed: name });
|
|
8173
|
-
return;
|
|
8174
|
-
}
|
|
8175
|
-
printSuccess(`Removed profile: ${name}`);
|
|
8176
|
-
}));
|
|
8177
|
-
}
|
|
8178
|
-
|
|
8179
|
-
// src/commands/dev.ts
|
|
8180
|
-
import { existsSync as existsSync32 } from "node:fs";
|
|
8181
|
-
import { isAbsolute as isAbsolute3, relative as relative7, resolve as resolve7 } from "node:path";
|
|
8182
|
-
|
|
8183
|
-
// src/lib/mcp-invoke.ts
|
|
8184
|
-
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
|
|
8185
|
-
import { resolve as resolve5 } from "node:path";
|
|
8186
|
-
var DEFAULT_TIMEOUT_MS3 = 60000;
|
|
8187
|
-
var invokeRequestId = 1;
|
|
8188
|
-
async function invokeMcpTool(args) {
|
|
8189
|
-
const startedAt = Date.now();
|
|
8190
|
-
const controller = new AbortController;
|
|
8191
|
-
const timer = setTimeout(() => controller.abort(), args.timeoutMs ?? DEFAULT_TIMEOUT_MS3);
|
|
8192
|
-
const headers = {
|
|
8193
|
-
"content-type": "application/json",
|
|
8194
|
-
accept: "application/json, text/event-stream",
|
|
8195
|
-
...args.headers
|
|
8196
|
-
};
|
|
8197
|
-
const body = JSON.stringify({
|
|
8198
|
-
jsonrpc: "2.0",
|
|
8199
|
-
id: `mcp-cli-invoke-${invokeRequestId++}`,
|
|
8200
|
-
method: "tools/call",
|
|
8201
|
-
params: { name: args.toolName, arguments: args.toolArguments }
|
|
8202
|
-
});
|
|
8203
|
-
try {
|
|
8204
|
-
const res = await fetch(args.url, {
|
|
8205
|
-
method: "POST",
|
|
8206
|
-
headers,
|
|
8207
|
-
body,
|
|
8208
|
-
signal: controller.signal
|
|
8209
|
-
});
|
|
8210
|
-
const rawText = await res.text();
|
|
8211
|
-
const contentType = (res.headers.get("content-type") || "").toLowerCase();
|
|
8212
|
-
const jsonText = contentType.includes("text/event-stream") ? unwrapEventStream(rawText) : rawText;
|
|
8213
|
-
const parsed = parseJsonRpc(jsonText);
|
|
8214
|
-
const durationMs = Date.now() - startedAt;
|
|
8215
|
-
if (!parsed.ok) {
|
|
8216
|
-
if (!res.ok) {
|
|
8217
|
-
return {
|
|
8218
|
-
ok: false,
|
|
8219
|
-
reason: "http-error",
|
|
8220
|
-
status: res.status,
|
|
8221
|
-
message: parsed.error?.message ?? (rawText.slice(0, 200) || res.statusText),
|
|
8222
|
-
durationMs
|
|
8223
|
-
};
|
|
8224
|
-
}
|
|
8225
|
-
return {
|
|
8226
|
-
ok: false,
|
|
8227
|
-
reason: "rpc-error",
|
|
8228
|
-
error: parsed.error ?? {
|
|
8229
|
-
code: -32603,
|
|
8230
|
-
message: "Malformed JSON-RPC response.",
|
|
8231
|
-
data: rawText
|
|
8232
|
-
},
|
|
8233
|
-
durationMs
|
|
8234
|
-
};
|
|
8235
|
-
}
|
|
8236
|
-
return {
|
|
8237
|
-
ok: true,
|
|
8238
|
-
result: parsed.result,
|
|
8239
|
-
durationMs
|
|
8240
|
-
};
|
|
8241
|
-
} catch (err) {
|
|
8242
|
-
const durationMs = Date.now() - startedAt;
|
|
8243
|
-
const aborted = err instanceof Error && (err.name === "AbortError" || /abort/i.test(err.message));
|
|
8244
|
-
return {
|
|
8245
|
-
ok: false,
|
|
8246
|
-
reason: "http-error",
|
|
8247
|
-
status: 0,
|
|
8248
|
-
message: aborted ? `Request timed out after ${args.timeoutMs ?? DEFAULT_TIMEOUT_MS3}ms.` : err instanceof Error ? err.message : String(err),
|
|
8249
|
-
durationMs
|
|
8250
|
-
};
|
|
8251
|
-
} finally {
|
|
8252
|
-
clearTimeout(timer);
|
|
8253
|
-
}
|
|
8254
|
-
}
|
|
8255
|
-
function parseJsonRpc(text) {
|
|
8256
|
-
if (!text.trim()) {
|
|
8257
|
-
return {
|
|
8258
|
-
ok: false,
|
|
8259
|
-
result: {},
|
|
8260
|
-
error: { code: -32700, message: "Response body was empty." }
|
|
8261
|
-
};
|
|
8262
|
-
}
|
|
8263
|
-
let parsed;
|
|
8264
|
-
try {
|
|
8265
|
-
parsed = JSON.parse(text);
|
|
8266
|
-
} catch (err) {
|
|
8267
|
-
return {
|
|
8268
|
-
ok: false,
|
|
8269
|
-
result: {},
|
|
8270
|
-
error: {
|
|
8271
|
-
code: -32700,
|
|
8272
|
-
message: `Response was not JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
8273
|
-
}
|
|
8274
|
-
};
|
|
8275
|
-
}
|
|
8276
|
-
if (!parsed || typeof parsed !== "object") {
|
|
8277
|
-
return {
|
|
8278
|
-
ok: false,
|
|
8279
|
-
result: {},
|
|
8280
|
-
error: { code: -32603, message: "Malformed JSON-RPC response.", data: parsed }
|
|
8281
|
-
};
|
|
8282
|
-
}
|
|
8283
|
-
const obj = parsed;
|
|
8284
|
-
if ("result" in obj && obj.result && typeof obj.result === "object") {
|
|
8285
|
-
return { ok: true, result: obj.result };
|
|
8286
|
-
}
|
|
8287
|
-
if ("error" in obj && obj.error && typeof obj.error === "object") {
|
|
8288
|
-
const errObj = obj.error;
|
|
8289
|
-
return {
|
|
8290
|
-
ok: false,
|
|
8291
|
-
result: {},
|
|
8292
|
-
error: {
|
|
8293
|
-
code: typeof errObj.code === "number" ? errObj.code : -32603,
|
|
8294
|
-
message: typeof errObj.message === "string" ? errObj.message : "RPC error.",
|
|
8295
|
-
data: errObj.data
|
|
8296
|
-
}
|
|
8297
|
-
};
|
|
8298
|
-
}
|
|
8299
|
-
return {
|
|
8300
|
-
ok: false,
|
|
8301
|
-
result: {},
|
|
8302
|
-
error: { code: -32603, message: "Response had no result or error field." }
|
|
8303
|
-
};
|
|
8304
|
-
}
|
|
8305
|
-
function unwrapEventStream(text) {
|
|
8306
|
-
const parts = [];
|
|
8307
|
-
for (const line of text.split(/\r?\n/)) {
|
|
8308
|
-
if (line.startsWith("data:"))
|
|
8309
|
-
parts.push(line.slice(5).replace(/^ /, ""));
|
|
8310
|
-
}
|
|
8311
|
-
return parts.join("");
|
|
8312
|
-
}
|
|
8313
|
-
async function parseToolArguments(opts) {
|
|
8314
|
-
const raw = opts.raw?.trim();
|
|
8315
|
-
if (!raw)
|
|
8316
|
-
return { ok: true, value: {} };
|
|
8317
|
-
let source;
|
|
8318
|
-
if (raw === "@-" || raw === "-") {
|
|
8319
|
-
const reader = opts.readStdin ?? defaultStdinReader;
|
|
8320
|
-
source = await reader();
|
|
8321
|
-
} else if (raw.startsWith("@")) {
|
|
8322
|
-
const filePath = resolve5(opts.cwd ?? process.cwd(), raw.slice(1));
|
|
8323
|
-
if (!existsSync12(filePath)) {
|
|
8324
|
-
return { ok: false, reason: "file-not-found", path: filePath };
|
|
8889
|
+
if (hadTarget)
|
|
8890
|
+
cleared.push("open target");
|
|
8891
|
+
const what = cleared.length === 0 ? "nothing (already empty)" : cleared.join(" + ");
|
|
8892
|
+
printSuccess(`Cleared ${what}. \`mcp dev\` will re-detect editors and prompt next session.`);
|
|
8893
|
+
}));
|
|
8894
|
+
config.command("set-editor-command <command>").description("Override the editor launch command (default: code; e.g. cursor, code-insiders)").action(runAction((command) => {
|
|
8895
|
+
const cfg = readConfig();
|
|
8896
|
+
cfg.editorCommand = command.trim();
|
|
8897
|
+
writeConfig(cfg);
|
|
8898
|
+
if (isJsonMode()) {
|
|
8899
|
+
printJson({ editorCommand: cfg.editorCommand });
|
|
8900
|
+
return;
|
|
8325
8901
|
}
|
|
8326
|
-
|
|
8327
|
-
}
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
} catch (err) {
|
|
8334
|
-
return {
|
|
8335
|
-
ok: false,
|
|
8336
|
-
reason: "parse-error",
|
|
8337
|
-
message: err instanceof Error ? err.message : String(err)
|
|
8338
|
-
};
|
|
8339
|
-
}
|
|
8340
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8341
|
-
return { ok: false, reason: "not-an-object" };
|
|
8342
|
-
}
|
|
8343
|
-
return { ok: true, value: parsed };
|
|
8344
|
-
}
|
|
8345
|
-
async function defaultStdinReader() {
|
|
8346
|
-
const chunks = [];
|
|
8347
|
-
return new Promise((res, rej) => {
|
|
8348
|
-
process.stdin.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
8349
|
-
process.stdin.on("end", () => res(Buffer.concat(chunks).toString("utf-8")));
|
|
8350
|
-
process.stdin.on("error", rej);
|
|
8351
|
-
});
|
|
8352
|
-
}
|
|
8353
|
-
function formatToolCallResult(result) {
|
|
8354
|
-
const lines = [];
|
|
8355
|
-
const blocks = result.content ?? [];
|
|
8356
|
-
for (const block of blocks) {
|
|
8357
|
-
if (block.type === "text" && typeof block.text === "string") {
|
|
8358
|
-
lines.push(block.text);
|
|
8359
|
-
continue;
|
|
8902
|
+
printSuccess(`Saved editor command: ${cfg.editorCommand}`);
|
|
8903
|
+
}));
|
|
8904
|
+
config.command("use <name>").description("Switch the active profile (e.g. staging, prod)").action(runAction((name) => {
|
|
8905
|
+
setCurrentProfile(name);
|
|
8906
|
+
if (isJsonMode()) {
|
|
8907
|
+
printJson({ currentProfile: name });
|
|
8908
|
+
return;
|
|
8360
8909
|
}
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8910
|
+
printSuccess(`Switched to profile: ${name}`);
|
|
8911
|
+
}));
|
|
8912
|
+
const profile = config.command("profile").description("Manage named config profiles (apiKey + baseUrl + defaultOrganizationId)");
|
|
8913
|
+
profile.command("list").description("List all configured profiles").action(runAction(() => {
|
|
8914
|
+
const profiles = listProfiles();
|
|
8915
|
+
if (isJsonMode()) {
|
|
8916
|
+
printJson({ profiles, currentProfile: getActiveProfileName() });
|
|
8917
|
+
return;
|
|
8365
8918
|
}
|
|
8366
|
-
if (
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
continue;
|
|
8919
|
+
if (profiles.length === 0) {
|
|
8920
|
+
console.log("No profiles configured. Run `mcp config profile add <name>` to create one.");
|
|
8921
|
+
return;
|
|
8370
8922
|
}
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8923
|
+
printTable(profiles.map((p) => ({
|
|
8924
|
+
current: p.isCurrent ? c.green("✓") : "",
|
|
8925
|
+
name: p.name,
|
|
8926
|
+
baseUrl: p.baseUrl ?? "—",
|
|
8927
|
+
"default org": p.defaultOrganizationId ?? "—",
|
|
8928
|
+
"api key": p.hasApiKey ? c.dim("saved") : c.dim("—")
|
|
8929
|
+
})), [
|
|
8930
|
+
{ key: "current", label: "", width: 3 },
|
|
8931
|
+
{ key: "name", label: "Name", width: 18 },
|
|
8932
|
+
{ key: "baseUrl", label: "Base URL", width: 36 },
|
|
8933
|
+
{ key: "default org", label: "Default org", width: 22 },
|
|
8934
|
+
{ key: "api key", label: "API key", width: 10 }
|
|
8935
|
+
]);
|
|
8936
|
+
}));
|
|
8937
|
+
profile.command("add <name>").description("Create a new profile (use --url / --key / --app / --org to seed values)").option("--url <url>", "API base URL for the new profile").option("--key <apiKey>", "API key for the new profile (writes to config.json mode 0600)").option("--app <url>", "Dashboard origin override for the new profile").option("--org <organizationId>", "Default organization ID for the new profile").action(runAction((name, opts) => {
|
|
8938
|
+
if (opts.url) {
|
|
8939
|
+
try {
|
|
8940
|
+
new URL(opts.url);
|
|
8941
|
+
} catch {
|
|
8942
|
+
throw new Error(`Invalid --url: ${opts.url}`);
|
|
8943
|
+
}
|
|
8377
8944
|
}
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
if (lines.length === 0) {
|
|
8385
|
-
lines.push("(no content)");
|
|
8386
|
-
}
|
|
8387
|
-
return { lines, isToolError: result.isError === true };
|
|
8388
|
-
}
|
|
8389
|
-
function truncateJsonPreview(value) {
|
|
8390
|
-
const json = (() => {
|
|
8391
|
-
try {
|
|
8392
|
-
return JSON.stringify(value);
|
|
8393
|
-
} catch {
|
|
8394
|
-
return "<unserializable>";
|
|
8945
|
+
if (opts.app) {
|
|
8946
|
+
try {
|
|
8947
|
+
new URL(opts.app);
|
|
8948
|
+
} catch {
|
|
8949
|
+
throw new Error(`Invalid --app: ${opts.app}`);
|
|
8950
|
+
}
|
|
8395
8951
|
}
|
|
8396
|
-
|
|
8397
|
-
|
|
8952
|
+
const seed = {};
|
|
8953
|
+
if (opts.url)
|
|
8954
|
+
seed.baseUrl = opts.url;
|
|
8955
|
+
if (opts.key)
|
|
8956
|
+
seed.apiKey = opts.key;
|
|
8957
|
+
if (opts.app)
|
|
8958
|
+
seed.appUrl = opts.app.replace(/\/+$/, "");
|
|
8959
|
+
if (opts.org)
|
|
8960
|
+
seed.defaultOrganizationId = opts.org;
|
|
8961
|
+
addProfile(name, seed);
|
|
8962
|
+
if (isJsonMode()) {
|
|
8963
|
+
printJson({ added: name.trim(), seeded: Object.keys(seed) });
|
|
8964
|
+
return;
|
|
8965
|
+
}
|
|
8966
|
+
printSuccess(`Created profile: ${name.trim()}`);
|
|
8967
|
+
if (opts.key) {
|
|
8968
|
+
console.log(c.dim(" Tip: passing --key writes the secret to your shell history. Prefer `mcp --profile <name> login` for interactive setup."));
|
|
8969
|
+
}
|
|
8970
|
+
console.log(c.dim(` Switch to it with \`mcp config use ${name.trim()}\`.`));
|
|
8971
|
+
}));
|
|
8972
|
+
profile.command("remove <name>").description("Delete a profile (cannot remove the active or only-remaining profile)").action(runAction((name) => {
|
|
8973
|
+
removeProfile(name);
|
|
8974
|
+
if (isJsonMode()) {
|
|
8975
|
+
printJson({ removed: name });
|
|
8976
|
+
return;
|
|
8977
|
+
}
|
|
8978
|
+
printSuccess(`Removed profile: ${name}`);
|
|
8979
|
+
}));
|
|
8398
8980
|
}
|
|
8399
8981
|
|
|
8982
|
+
// src/commands/dev.ts
|
|
8983
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
8984
|
+
import { isAbsolute as isAbsolute3, relative as relative7, resolve as resolve8 } from "node:path";
|
|
8985
|
+
|
|
8400
8986
|
// src/lib/dev/sessions.ts
|
|
8401
8987
|
import {
|
|
8402
|
-
existsSync as
|
|
8403
|
-
mkdirSync as
|
|
8404
|
-
readFileSync as
|
|
8988
|
+
existsSync as existsSync14,
|
|
8989
|
+
mkdirSync as mkdirSync8,
|
|
8990
|
+
readFileSync as readFileSync12,
|
|
8405
8991
|
readdirSync as readdirSync2,
|
|
8406
8992
|
unlinkSync,
|
|
8407
|
-
writeFileSync as
|
|
8993
|
+
writeFileSync as writeFileSync7
|
|
8408
8994
|
} from "node:fs";
|
|
8409
8995
|
import { homedir as homedir3 } from "node:os";
|
|
8410
|
-
import { join as
|
|
8996
|
+
import { join as join12 } from "node:path";
|
|
8411
8997
|
var SESSIONS_DIR_NAME = "dev-sessions";
|
|
8412
8998
|
function sessionsDir() {
|
|
8413
|
-
return
|
|
8999
|
+
return join12(homedir3(), ".mcpcloud", SESSIONS_DIR_NAME);
|
|
8414
9000
|
}
|
|
8415
9001
|
function sessionFile(pid) {
|
|
8416
|
-
return
|
|
9002
|
+
return join12(sessionsDir(), `${pid}.json`);
|
|
8417
9003
|
}
|
|
8418
9004
|
function ensureDir() {
|
|
8419
9005
|
const dir = sessionsDir();
|
|
8420
|
-
if (!
|
|
8421
|
-
|
|
9006
|
+
if (!existsSync14(dir))
|
|
9007
|
+
mkdirSync8(dir, { recursive: true, mode: 448 });
|
|
8422
9008
|
}
|
|
8423
9009
|
function recordSession(record) {
|
|
8424
9010
|
ensureDir();
|
|
8425
|
-
|
|
9011
|
+
writeFileSync7(sessionFile(record.pid), JSON.stringify(record, null, 2), {
|
|
8426
9012
|
encoding: "utf-8",
|
|
8427
9013
|
mode: 384
|
|
8428
9014
|
});
|
|
8429
9015
|
}
|
|
8430
9016
|
function removeSession(pid) {
|
|
8431
9017
|
const path = sessionFile(pid);
|
|
8432
|
-
if (
|
|
9018
|
+
if (existsSync14(path)) {
|
|
8433
9019
|
try {
|
|
8434
9020
|
unlinkSync(path);
|
|
8435
9021
|
} catch {}
|
|
@@ -8437,15 +9023,15 @@ function removeSession(pid) {
|
|
|
8437
9023
|
}
|
|
8438
9024
|
function listSessions() {
|
|
8439
9025
|
const dir = sessionsDir();
|
|
8440
|
-
if (!
|
|
9026
|
+
if (!existsSync14(dir))
|
|
8441
9027
|
return [];
|
|
8442
9028
|
const live = [];
|
|
8443
9029
|
for (const entry of readdirSync2(dir)) {
|
|
8444
9030
|
if (!entry.endsWith(".json"))
|
|
8445
9031
|
continue;
|
|
8446
|
-
const path =
|
|
9032
|
+
const path = join12(dir, entry);
|
|
8447
9033
|
try {
|
|
8448
|
-
const parsed = JSON.parse(
|
|
9034
|
+
const parsed = JSON.parse(readFileSync12(path, "utf-8"));
|
|
8449
9035
|
if (typeof parsed.pid !== "number") {
|
|
8450
9036
|
unlinkSync(path);
|
|
8451
9037
|
continue;
|
|
@@ -8505,7 +9091,7 @@ async function killSession(pid, timeoutMs = 3000) {
|
|
|
8505
9091
|
}
|
|
8506
9092
|
|
|
8507
9093
|
// src/commands/dev-invoke.ts
|
|
8508
|
-
function
|
|
9094
|
+
function parseTimeoutMs2(value, fallbackMs) {
|
|
8509
9095
|
if (!value)
|
|
8510
9096
|
return fallbackMs;
|
|
8511
9097
|
const n = Number(value);
|
|
@@ -8532,7 +9118,7 @@ function registerDevInvokeCommand(dev) {
|
|
|
8532
9118
|
toolName: tool,
|
|
8533
9119
|
argsRaw: opts.args,
|
|
8534
9120
|
urlOverride: opts.url,
|
|
8535
|
-
timeoutMs:
|
|
9121
|
+
timeoutMs: parseTimeoutMs2(opts.timeout, 60000),
|
|
8536
9122
|
raw: Boolean(opts.raw)
|
|
8537
9123
|
});
|
|
8538
9124
|
}));
|
|
@@ -8548,12 +9134,12 @@ async function runDevInvoke(args) {
|
|
|
8548
9134
|
} else if (parsedArgs.reason === "not-an-object") {
|
|
8549
9135
|
printError("--args must decode to a JSON object (not array / scalar).");
|
|
8550
9136
|
}
|
|
8551
|
-
|
|
9137
|
+
throw new CliExitError(1);
|
|
8552
9138
|
}
|
|
8553
9139
|
const baseUrl = args.urlOverride ?? resolveDevBaseUrl(cwd);
|
|
8554
9140
|
if (!baseUrl) {
|
|
8555
9141
|
printError("No running `mcp dev` session in this directory. Start one, or pass --url <baseUrl>.");
|
|
8556
|
-
|
|
9142
|
+
throw new CliExitError(1);
|
|
8557
9143
|
}
|
|
8558
9144
|
const mcpUrl = baseUrl.endsWith("/mcp") ? baseUrl : `${baseUrl.replace(/\/$/, "")}/mcp`;
|
|
8559
9145
|
const result = await invokeMcpTool({
|
|
@@ -8581,10 +9167,10 @@ function renderDevInvokeResult(args) {
|
|
|
8581
9167
|
printJson({
|
|
8582
9168
|
toolName: args.toolName,
|
|
8583
9169
|
url: args.url,
|
|
8584
|
-
...
|
|
9170
|
+
...invokeResultToJson2(args.result)
|
|
8585
9171
|
});
|
|
8586
9172
|
if (!args.result.ok)
|
|
8587
|
-
|
|
9173
|
+
throw new CliExitError(1);
|
|
8588
9174
|
return;
|
|
8589
9175
|
}
|
|
8590
9176
|
if (!args.result.ok) {
|
|
@@ -8598,7 +9184,7 @@ function renderDevInvokeResult(args) {
|
|
|
8598
9184
|
const statusLine = args.result.status > 0 ? `HTTP ${args.result.status}` : "network error";
|
|
8599
9185
|
printError(`${statusLine} from local runtime: ${args.result.message}`);
|
|
8600
9186
|
}
|
|
8601
|
-
|
|
9187
|
+
throw new CliExitError(1);
|
|
8602
9188
|
}
|
|
8603
9189
|
if (args.raw) {
|
|
8604
9190
|
printInfo(JSON.stringify(args.result.result, null, 2));
|
|
@@ -8612,7 +9198,7 @@ function renderDevInvokeResult(args) {
|
|
|
8612
9198
|
printWarn(`Tool reported isError=true. Inspect ${c.bold("--json")} or ${c.bold("--raw")} for the full envelope.`);
|
|
8613
9199
|
}
|
|
8614
9200
|
}
|
|
8615
|
-
function
|
|
9201
|
+
function invokeResultToJson2(result) {
|
|
8616
9202
|
if (result.ok) {
|
|
8617
9203
|
return { ok: true, durationMs: result.durationMs, result: result.result };
|
|
8618
9204
|
}
|
|
@@ -8637,38 +9223,38 @@ function invokeResultToJson(result) {
|
|
|
8637
9223
|
// src/lib/dev/inspector-store.ts
|
|
8638
9224
|
import {
|
|
8639
9225
|
appendFileSync as appendFileSync2,
|
|
8640
|
-
existsSync as
|
|
8641
|
-
mkdirSync as
|
|
8642
|
-
readFileSync as
|
|
9226
|
+
existsSync as existsSync15,
|
|
9227
|
+
mkdirSync as mkdirSync9,
|
|
9228
|
+
readFileSync as readFileSync13,
|
|
8643
9229
|
readdirSync as readdirSync3,
|
|
8644
9230
|
renameSync,
|
|
8645
9231
|
statSync as statSync2
|
|
8646
9232
|
} from "node:fs";
|
|
8647
|
-
import { join as
|
|
9233
|
+
import { join as join13 } from "node:path";
|
|
8648
9234
|
var INSPECTOR_DIRNAME = "inspector";
|
|
8649
9235
|
var ACTIVE_FILE = "calls.ndjson";
|
|
8650
9236
|
var ROTATED_PREFIX = "calls.";
|
|
8651
9237
|
var ROTATED_SUFFIX = ".ndjson";
|
|
8652
9238
|
var ROTATION_LIMIT_BYTES = 50 * 1024 * 1024;
|
|
8653
9239
|
function inspectorDir(cwd) {
|
|
8654
|
-
return
|
|
9240
|
+
return join13(devRoot(cwd), INSPECTOR_DIRNAME);
|
|
8655
9241
|
}
|
|
8656
9242
|
function ensureInspectorDir(dir) {
|
|
8657
|
-
if (!
|
|
8658
|
-
|
|
9243
|
+
if (!existsSync15(dir))
|
|
9244
|
+
mkdirSync9(dir, { recursive: true });
|
|
8659
9245
|
}
|
|
8660
9246
|
function activeFilePath(dir) {
|
|
8661
|
-
return
|
|
9247
|
+
return join13(dir, ACTIVE_FILE);
|
|
8662
9248
|
}
|
|
8663
9249
|
function isRotatedName(name) {
|
|
8664
9250
|
return name !== ACTIVE_FILE && name.startsWith(ROTATED_PREFIX) && name.endsWith(ROTATED_SUFFIX);
|
|
8665
9251
|
}
|
|
8666
9252
|
function listRotatedFiles(dir) {
|
|
8667
|
-
if (!
|
|
9253
|
+
if (!existsSync15(dir))
|
|
8668
9254
|
return [];
|
|
8669
9255
|
const rotated = readdirSync3(dir).filter(isRotatedName);
|
|
8670
9256
|
rotated.sort();
|
|
8671
|
-
return rotated.map((n) =>
|
|
9257
|
+
return rotated.map((n) => join13(dir, n));
|
|
8672
9258
|
}
|
|
8673
9259
|
function parseLines(text) {
|
|
8674
9260
|
if (!text)
|
|
@@ -8685,13 +9271,13 @@ function parseLines(text) {
|
|
|
8685
9271
|
return out;
|
|
8686
9272
|
}
|
|
8687
9273
|
function readAll(dir) {
|
|
8688
|
-
if (!
|
|
9274
|
+
if (!existsSync15(dir))
|
|
8689
9275
|
return [];
|
|
8690
9276
|
const out = [];
|
|
8691
9277
|
for (const path of [...listRotatedFiles(dir), activeFilePath(dir)]) {
|
|
8692
|
-
if (!
|
|
9278
|
+
if (!existsSync15(path))
|
|
8693
9279
|
continue;
|
|
8694
|
-
out.push(...parseLines(
|
|
9280
|
+
out.push(...parseLines(readFileSync13(path, "utf-8")));
|
|
8695
9281
|
}
|
|
8696
9282
|
return out;
|
|
8697
9283
|
}
|
|
@@ -8759,7 +9345,7 @@ async function runDevReplay(opts) {
|
|
|
8759
9345
|
const baseUrl = opts.url ?? resolveBaseUrl(cwd);
|
|
8760
9346
|
if (!opts.dryRun && !baseUrl) {
|
|
8761
9347
|
printError("No running mcp dev session in this directory. Start `mcp dev`, or pass --url <baseUrl>.");
|
|
8762
|
-
|
|
9348
|
+
throw new CliExitError(1);
|
|
8763
9349
|
}
|
|
8764
9350
|
const results = [];
|
|
8765
9351
|
for (const record of records) {
|
|
@@ -8887,13 +9473,13 @@ function formatSummary(record) {
|
|
|
8887
9473
|
|
|
8888
9474
|
// src/commands/dev-tail.ts
|
|
8889
9475
|
import {
|
|
8890
|
-
existsSync as
|
|
9476
|
+
existsSync as existsSync16,
|
|
8891
9477
|
openSync,
|
|
8892
9478
|
readSync,
|
|
8893
9479
|
closeSync,
|
|
8894
9480
|
statSync as statSync3
|
|
8895
9481
|
} from "node:fs";
|
|
8896
|
-
import { join as
|
|
9482
|
+
import { join as join14 } from "node:path";
|
|
8897
9483
|
var DEFAULT_INTERVAL_MS = 250;
|
|
8898
9484
|
var MAX_READ_CHUNK = 64 * 1024;
|
|
8899
9485
|
function registerDevTailCommand(dev) {
|
|
@@ -8902,14 +9488,14 @@ function registerDevTailCommand(dev) {
|
|
|
8902
9488
|
async function runDevTail(opts) {
|
|
8903
9489
|
const cwd = process.cwd();
|
|
8904
9490
|
const dir = inspectorDir(cwd);
|
|
8905
|
-
const activePath =
|
|
9491
|
+
const activePath = join14(dir, ACTIVE_FILE);
|
|
8906
9492
|
const intervalMs = parseInterval(opts.intervalMs);
|
|
8907
|
-
if (!
|
|
9493
|
+
if (!existsSync16(dir)) {
|
|
8908
9494
|
printInfo(`No inspector history yet at ${dir}. Run \`mcp dev\` to start capturing.`);
|
|
8909
|
-
|
|
9495
|
+
throw new CliExitError(0);
|
|
8910
9496
|
}
|
|
8911
9497
|
let cursor = 0;
|
|
8912
|
-
if (
|
|
9498
|
+
if (existsSync16(activePath)) {
|
|
8913
9499
|
try {
|
|
8914
9500
|
cursor = opts.fromStart ? 0 : statSync3(activePath).size;
|
|
8915
9501
|
} catch {
|
|
@@ -8925,7 +9511,7 @@ async function runDevTail(opts) {
|
|
|
8925
9511
|
process.on("SIGTERM", stop);
|
|
8926
9512
|
while (!stopped) {
|
|
8927
9513
|
let size = 0;
|
|
8928
|
-
if (
|
|
9514
|
+
if (existsSync16(activePath)) {
|
|
8929
9515
|
try {
|
|
8930
9516
|
size = statSync3(activePath).size;
|
|
8931
9517
|
} catch {
|
|
@@ -9010,7 +9596,7 @@ function emitLines(text, filter) {
|
|
|
9010
9596
|
}
|
|
9011
9597
|
}
|
|
9012
9598
|
function sleep(ms) {
|
|
9013
|
-
return new Promise((
|
|
9599
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
9014
9600
|
}
|
|
9015
9601
|
|
|
9016
9602
|
// ../../node_modules/.bun/@clack+core@1.3.1/node_modules/@clack/core/dist/index.mjs
|
|
@@ -10296,13 +10882,13 @@ async function runInteractiveBootstrap() {
|
|
|
10296
10882
|
|
|
10297
10883
|
// src/lib/dev/bundle-sync.ts
|
|
10298
10884
|
import {
|
|
10299
|
-
existsSync as
|
|
10300
|
-
mkdirSync as
|
|
10301
|
-
readFileSync as
|
|
10885
|
+
existsSync as existsSync17,
|
|
10886
|
+
mkdirSync as mkdirSync10,
|
|
10887
|
+
readFileSync as readFileSync14,
|
|
10302
10888
|
rmSync as rmSync2,
|
|
10303
|
-
writeFileSync as
|
|
10889
|
+
writeFileSync as writeFileSync8
|
|
10304
10890
|
} from "node:fs";
|
|
10305
|
-
import { dirname as
|
|
10891
|
+
import { dirname as dirname6, join as join15 } from "node:path";
|
|
10306
10892
|
async function resolveServerProject(args) {
|
|
10307
10893
|
const data = await api.get("/api/v1/server", {
|
|
10308
10894
|
organizationId: args.organizationId,
|
|
@@ -10358,8 +10944,8 @@ async function fetchBundle(args) {
|
|
|
10358
10944
|
return parsed;
|
|
10359
10945
|
}
|
|
10360
10946
|
function materializeBundle(destDir, bundle, options = {}) {
|
|
10361
|
-
if (!
|
|
10362
|
-
|
|
10947
|
+
if (!existsSync17(destDir)) {
|
|
10948
|
+
mkdirSync10(destDir, { recursive: true });
|
|
10363
10949
|
}
|
|
10364
10950
|
const preserve = options.preservePaths;
|
|
10365
10951
|
const kept = new Set;
|
|
@@ -10374,9 +10960,9 @@ function materializeBundle(destDir, bundle, options = {}) {
|
|
|
10374
10960
|
preservedCount += 1;
|
|
10375
10961
|
continue;
|
|
10376
10962
|
}
|
|
10377
|
-
const target =
|
|
10378
|
-
|
|
10379
|
-
|
|
10963
|
+
const target = join15(destDir, safePath);
|
|
10964
|
+
mkdirSync10(dirname6(target), { recursive: true });
|
|
10965
|
+
writeFileSync8(target, file.content, "utf-8");
|
|
10380
10966
|
writtenCount += 1;
|
|
10381
10967
|
}
|
|
10382
10968
|
let removed = 0;
|
|
@@ -10404,7 +10990,7 @@ function pruneStaleFiles(rootDir, currentDir, kept) {
|
|
|
10404
10990
|
const { relative: relative6 } = __require("node:path");
|
|
10405
10991
|
let removed = 0;
|
|
10406
10992
|
for (const entry of readdirSync4(currentDir)) {
|
|
10407
|
-
const abs =
|
|
10993
|
+
const abs = join15(currentDir, entry);
|
|
10408
10994
|
const stat = statSync4(abs);
|
|
10409
10995
|
if (stat.isDirectory()) {
|
|
10410
10996
|
removed += pruneStaleFiles(rootDir, abs, kept);
|
|
@@ -10470,8 +11056,8 @@ function startGitPullPoll(args) {
|
|
|
10470
11056
|
|
|
10471
11057
|
// src/lib/dev/editor.ts
|
|
10472
11058
|
import { spawn as spawn4 } from "node:child_process";
|
|
10473
|
-
import { existsSync as
|
|
10474
|
-
import { delimiter as delimiter2, join as
|
|
11059
|
+
import { existsSync as existsSync18, statSync as statSync4 } from "node:fs";
|
|
11060
|
+
import { delimiter as delimiter2, join as join16 } from "node:path";
|
|
10475
11061
|
import { platform as platform3 } from "node:os";
|
|
10476
11062
|
var TARGET_LABEL = {
|
|
10477
11063
|
tools: "tool metadata",
|
|
@@ -10507,8 +11093,8 @@ function findOnPath2(command) {
|
|
|
10507
11093
|
if (!dir)
|
|
10508
11094
|
continue;
|
|
10509
11095
|
for (const ext of exts) {
|
|
10510
|
-
const candidate =
|
|
10511
|
-
if (
|
|
11096
|
+
const candidate = join16(dir, command + ext);
|
|
11097
|
+
if (existsSync18(candidate) && isExecutable(candidate))
|
|
10512
11098
|
return candidate;
|
|
10513
11099
|
}
|
|
10514
11100
|
}
|
|
@@ -10788,11 +11374,11 @@ function parseOpenChoice(raw) {
|
|
|
10788
11374
|
}
|
|
10789
11375
|
|
|
10790
11376
|
// src/lib/dev/file-watcher.ts
|
|
10791
|
-
import { existsSync as
|
|
10792
|
-
import { join as
|
|
11377
|
+
import { existsSync as existsSync19, statSync as statSync5, watch as fsWatch2 } from "node:fs";
|
|
11378
|
+
import { join as join17 } from "node:path";
|
|
10793
11379
|
var DEFAULT_IGNORE = ["node_modules", ".git", "_mcpsh_host.mjs"];
|
|
10794
11380
|
function watchDir(options) {
|
|
10795
|
-
if (!
|
|
11381
|
+
if (!existsSync19(options.rootDir)) {
|
|
10796
11382
|
throw new Error(`watchDir: directory does not exist: ${options.rootDir}`);
|
|
10797
11383
|
}
|
|
10798
11384
|
const ignore = new Set([...DEFAULT_IGNORE, ...options.ignore ?? []]);
|
|
@@ -10828,7 +11414,7 @@ function watchDir(options) {
|
|
|
10828
11414
|
for (const entry of readdirSync4(options.rootDir)) {
|
|
10829
11415
|
if (ignore.has(entry))
|
|
10830
11416
|
continue;
|
|
10831
|
-
const sub =
|
|
11417
|
+
const sub = join17(options.rootDir, entry);
|
|
10832
11418
|
try {
|
|
10833
11419
|
const stat = statSync5(sub);
|
|
10834
11420
|
if (!stat.isDirectory())
|
|
@@ -10876,10 +11462,10 @@ function isToolHandlerPath(rel) {
|
|
|
10876
11462
|
}
|
|
10877
11463
|
|
|
10878
11464
|
// src/lib/dev/tools-watcher.ts
|
|
10879
|
-
import { existsSync as
|
|
11465
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
10880
11466
|
function startToolsWatcher(args) {
|
|
10881
11467
|
const rootDir = serverToolsDir(devRoot(args.cwd), args.serverId);
|
|
10882
|
-
if (!
|
|
11468
|
+
if (!existsSync20(rootDir))
|
|
10883
11469
|
return null;
|
|
10884
11470
|
let inflight = false;
|
|
10885
11471
|
let pendingNames = new Set;
|
|
@@ -10994,10 +11580,10 @@ async function maybeHotReload(args) {
|
|
|
10994
11580
|
}
|
|
10995
11581
|
|
|
10996
11582
|
// src/lib/dev/handlers-watcher.ts
|
|
10997
|
-
import { existsSync as
|
|
11583
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
10998
11584
|
function startHandlersWatcher(args) {
|
|
10999
11585
|
const rootDir = serverBundleHandlersDir(devRoot(args.cwd));
|
|
11000
|
-
if (!
|
|
11586
|
+
if (!existsSync21(rootDir))
|
|
11001
11587
|
return null;
|
|
11002
11588
|
let inflight = false;
|
|
11003
11589
|
let pendingSlugs = new Set;
|
|
@@ -11094,8 +11680,8 @@ async function handleOne2(args) {
|
|
|
11094
11680
|
|
|
11095
11681
|
// src/lib/dev/local-runtime.ts
|
|
11096
11682
|
import { spawn as spawn5 } from "node:child_process";
|
|
11097
|
-
import { existsSync as
|
|
11098
|
-
import { join as
|
|
11683
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
11684
|
+
import { join as join18 } from "node:path";
|
|
11099
11685
|
function detectRuntime(preferred = "auto") {
|
|
11100
11686
|
if (preferred === "bun" || preferred === "node")
|
|
11101
11687
|
return preferred;
|
|
@@ -11106,7 +11692,7 @@ function detectRuntime(preferred = "auto") {
|
|
|
11106
11692
|
return "node";
|
|
11107
11693
|
}
|
|
11108
11694
|
function startRuntime(options) {
|
|
11109
|
-
if (!
|
|
11695
|
+
if (!existsSync22(options.hostScriptPath)) {
|
|
11110
11696
|
throw new Error(`Host script not found: ${options.hostScriptPath}`);
|
|
11111
11697
|
}
|
|
11112
11698
|
const runtime = detectRuntime(options.runtime);
|
|
@@ -11132,52 +11718,52 @@ function startRuntime(options) {
|
|
|
11132
11718
|
if (child.exitCode !== null)
|
|
11133
11719
|
return;
|
|
11134
11720
|
child.kill("SIGTERM");
|
|
11135
|
-
await new Promise((
|
|
11721
|
+
await new Promise((resolve7) => {
|
|
11136
11722
|
const timer = setTimeout(() => {
|
|
11137
11723
|
if (child.exitCode === null)
|
|
11138
11724
|
child.kill("SIGKILL");
|
|
11139
|
-
|
|
11725
|
+
resolve7();
|
|
11140
11726
|
}, 3000);
|
|
11141
11727
|
child.once("exit", () => {
|
|
11142
11728
|
clearTimeout(timer);
|
|
11143
|
-
|
|
11729
|
+
resolve7();
|
|
11144
11730
|
});
|
|
11145
11731
|
});
|
|
11146
11732
|
}
|
|
11147
11733
|
};
|
|
11148
11734
|
}
|
|
11149
11735
|
async function installDependencies(options) {
|
|
11150
|
-
const pkgPath =
|
|
11151
|
-
if (!
|
|
11736
|
+
const pkgPath = join18(options.serverDir, "package.json");
|
|
11737
|
+
if (!existsSync22(pkgPath))
|
|
11152
11738
|
return { ran: false, exitCode: null };
|
|
11153
|
-
const nodeModules =
|
|
11154
|
-
if (options.skipIfPresent !== false &&
|
|
11739
|
+
const nodeModules = join18(options.serverDir, "node_modules");
|
|
11740
|
+
if (options.skipIfPresent !== false && existsSync22(nodeModules)) {
|
|
11155
11741
|
return { ran: false, exitCode: 0 };
|
|
11156
11742
|
}
|
|
11157
11743
|
options.onStep?.("Installing dependencies (bun install)…");
|
|
11158
|
-
return await new Promise((
|
|
11744
|
+
return await new Promise((resolve7) => {
|
|
11159
11745
|
const child = spawn5("bun", ["install", "--silent"], {
|
|
11160
11746
|
cwd: options.serverDir,
|
|
11161
11747
|
stdio: "inherit"
|
|
11162
11748
|
});
|
|
11163
|
-
child.on("error", () =>
|
|
11164
|
-
child.on("exit", (code) =>
|
|
11749
|
+
child.on("error", () => resolve7({ ran: true, exitCode: 127 }));
|
|
11750
|
+
child.on("exit", (code) => resolve7({ ran: true, exitCode: code }));
|
|
11165
11751
|
});
|
|
11166
11752
|
}
|
|
11167
11753
|
|
|
11168
11754
|
// src/lib/dev/prepare.ts
|
|
11169
|
-
import { existsSync as
|
|
11170
|
-
import { join as
|
|
11755
|
+
import { existsSync as existsSync26, writeFileSync as writeFileSync12 } from "node:fs";
|
|
11756
|
+
import { join as join22, relative as relative6 } from "node:path";
|
|
11171
11757
|
|
|
11172
11758
|
// src/lib/dev/git-clone.ts
|
|
11173
11759
|
import { spawnSync } from "node:child_process";
|
|
11174
11760
|
import {
|
|
11175
|
-
existsSync as
|
|
11176
|
-
mkdirSync as
|
|
11177
|
-
readFileSync as
|
|
11178
|
-
writeFileSync as
|
|
11761
|
+
existsSync as existsSync23,
|
|
11762
|
+
mkdirSync as mkdirSync11,
|
|
11763
|
+
readFileSync as readFileSync15,
|
|
11764
|
+
writeFileSync as writeFileSync9
|
|
11179
11765
|
} from "node:fs";
|
|
11180
|
-
import { dirname as
|
|
11766
|
+
import { dirname as dirname7, join as join19 } from "node:path";
|
|
11181
11767
|
|
|
11182
11768
|
class GitNotAvailableError extends Error {
|
|
11183
11769
|
constructor() {
|
|
@@ -11250,21 +11836,21 @@ function isGitAvailable() {
|
|
|
11250
11836
|
}
|
|
11251
11837
|
}
|
|
11252
11838
|
function applyLocalExcludes(repoDir) {
|
|
11253
|
-
const excludePath =
|
|
11254
|
-
const existing =
|
|
11839
|
+
const excludePath = join19(repoDir, ".git", "info", "exclude");
|
|
11840
|
+
const existing = existsSync23(excludePath) ? readFileSync15(excludePath, "utf-8") : "";
|
|
11255
11841
|
const next = mergeLocalExcludes(existing);
|
|
11256
11842
|
if (next === null)
|
|
11257
11843
|
return;
|
|
11258
|
-
if (!
|
|
11259
|
-
|
|
11844
|
+
if (!existsSync23(dirname7(excludePath))) {
|
|
11845
|
+
mkdirSync11(dirname7(excludePath), { recursive: true });
|
|
11260
11846
|
}
|
|
11261
|
-
|
|
11847
|
+
writeFileSync9(excludePath, next, "utf-8");
|
|
11262
11848
|
}
|
|
11263
11849
|
function ensureClone(opts) {
|
|
11264
11850
|
if (!isGitAvailable()) {
|
|
11265
11851
|
throw new GitNotAvailableError;
|
|
11266
11852
|
}
|
|
11267
|
-
if (
|
|
11853
|
+
if (existsSync23(join19(opts.repoDir, ".git"))) {
|
|
11268
11854
|
opts.onStep?.("Refreshing existing clone (git fetch)…");
|
|
11269
11855
|
runGit(gitFetchArgs(opts.branch), opts.repoDir);
|
|
11270
11856
|
applyLocalExcludes(opts.repoDir);
|
|
@@ -11280,12 +11866,12 @@ function ensureClone(opts) {
|
|
|
11280
11866
|
}
|
|
11281
11867
|
|
|
11282
11868
|
// src/lib/dev/host-runtime.ts
|
|
11283
|
-
import { existsSync as
|
|
11284
|
-
import { dirname as
|
|
11869
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "node:fs";
|
|
11870
|
+
import { dirname as dirname8, join as join21 } from "node:path";
|
|
11285
11871
|
|
|
11286
11872
|
// src/lib/dev/inspector-module.ts
|
|
11287
|
-
import { existsSync as
|
|
11288
|
-
import { join as
|
|
11873
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "node:fs";
|
|
11874
|
+
import { join as join20 } from "node:path";
|
|
11289
11875
|
|
|
11290
11876
|
// src/lib/dev/inspector-assets.ts
|
|
11291
11877
|
var INSPECTOR_HTML = `<!doctype html>
|
|
@@ -26420,10 +27006,10 @@ function buildInspectorModule() {
|
|
|
26420
27006
|
`);
|
|
26421
27007
|
}
|
|
26422
27008
|
function writeInspectorModule(serverDir2) {
|
|
26423
|
-
if (!
|
|
26424
|
-
|
|
26425
|
-
const target =
|
|
26426
|
-
|
|
27009
|
+
if (!existsSync24(serverDir2))
|
|
27010
|
+
mkdirSync12(serverDir2, { recursive: true });
|
|
27011
|
+
const target = join20(serverDir2, INSPECTOR_FILE_NAME);
|
|
27012
|
+
writeFileSync10(target, buildInspectorModule(), "utf-8");
|
|
26427
27013
|
return target;
|
|
26428
27014
|
}
|
|
26429
27015
|
|
|
@@ -26602,11 +27188,11 @@ function buildHostScript(options) {
|
|
|
26602
27188
|
`);
|
|
26603
27189
|
}
|
|
26604
27190
|
function writeHostScript(serverDir2, options) {
|
|
26605
|
-
if (!
|
|
26606
|
-
|
|
26607
|
-
const target =
|
|
26608
|
-
|
|
26609
|
-
|
|
27191
|
+
if (!existsSync25(serverDir2))
|
|
27192
|
+
mkdirSync13(serverDir2, { recursive: true });
|
|
27193
|
+
const target = join21(serverDir2, HOST_FILE_NAME);
|
|
27194
|
+
mkdirSync13(dirname8(target), { recursive: true });
|
|
27195
|
+
writeFileSync11(target, buildHostScript(options), "utf-8");
|
|
26610
27196
|
if (options.inspector) {
|
|
26611
27197
|
writeInspectorModule(serverDir2);
|
|
26612
27198
|
}
|
|
@@ -26635,7 +27221,7 @@ async function prepareDev(opts) {
|
|
|
26635
27221
|
projectId = projectId ?? gitLink.projectId;
|
|
26636
27222
|
serverName = `${gitLink.owner}/${gitLink.name}`;
|
|
26637
27223
|
repoDir = gitCloneDir(cwd, gitLink.owner, gitLink.name);
|
|
26638
|
-
dest = gitLink.pathPrefix ?
|
|
27224
|
+
dest = gitLink.pathPrefix ? join22(repoDir, gitLink.pathPrefix) : repoDir;
|
|
26639
27225
|
printStep(`Git-linked server — cloning ${gitLink.owner}/${gitLink.name}@${gitLink.branch}…`);
|
|
26640
27226
|
try {
|
|
26641
27227
|
const result = ensureClone({
|
|
@@ -26655,8 +27241,8 @@ async function prepareDev(opts) {
|
|
|
26655
27241
|
}
|
|
26656
27242
|
process.exit(1);
|
|
26657
27243
|
}
|
|
26658
|
-
if (!
|
|
26659
|
-
|
|
27244
|
+
if (!existsSync26(env)) {
|
|
27245
|
+
writeFileSync12(env, `{}
|
|
26660
27246
|
`, "utf-8");
|
|
26661
27247
|
printInfo(` → seeded ${relative6(cwd, env)} (empty; fill secrets here)`);
|
|
26662
27248
|
}
|
|
@@ -26680,8 +27266,8 @@ async function prepareDev(opts) {
|
|
|
26680
27266
|
});
|
|
26681
27267
|
const result = materializeBundle(dest, bundle, { prune: true });
|
|
26682
27268
|
printInfo(` → wrote ${result.filesWritten} files (pruned ${result.filesRemoved})`);
|
|
26683
|
-
if (!
|
|
26684
|
-
|
|
27269
|
+
if (!existsSync26(env)) {
|
|
27270
|
+
writeFileSync12(env, `{}
|
|
26685
27271
|
`, "utf-8");
|
|
26686
27272
|
printInfo(` → seeded ${relative6(cwd, env)} (empty; fill secrets here)`);
|
|
26687
27273
|
}
|
|
@@ -26698,7 +27284,7 @@ async function prepareDev(opts) {
|
|
|
26698
27284
|
});
|
|
26699
27285
|
printInfo(` → wrote ${toolsResult.filesWritten} editable tool file${toolsResult.filesWritten === 1 ? "" : "s"}${toolsResult.filesRemoved > 0 ? ` (pruned ${toolsResult.filesRemoved})` : ""}`);
|
|
26700
27286
|
} else {
|
|
26701
|
-
if (!
|
|
27287
|
+
if (!existsSync26(dest)) {
|
|
26702
27288
|
printError("No cached bundle found. Run once without --offline first.");
|
|
26703
27289
|
process.exit(1);
|
|
26704
27290
|
}
|
|
@@ -26707,7 +27293,7 @@ async function prepareDev(opts) {
|
|
|
26707
27293
|
process.exit(1);
|
|
26708
27294
|
}
|
|
26709
27295
|
}
|
|
26710
|
-
const entryRel = ENTRY_CANDIDATES.find((c2) =>
|
|
27296
|
+
const entryRel = ENTRY_CANDIDATES.find((c2) => existsSync26(join22(dest, c2)));
|
|
26711
27297
|
if (!entryRel) {
|
|
26712
27298
|
printError("Could not locate an entry file (expected src/worker.ts).");
|
|
26713
27299
|
process.exit(1);
|
|
@@ -26865,73 +27451,73 @@ function createBurstGuard() {
|
|
|
26865
27451
|
}
|
|
26866
27452
|
|
|
26867
27453
|
// src/lib/dev/agent-connectors/claude-code.ts
|
|
26868
|
-
import { existsSync as
|
|
27454
|
+
import { existsSync as existsSync28, unlinkSync as unlinkSync2, writeFileSync as writeFileSync14 } from "node:fs";
|
|
26869
27455
|
import { homedir as homedir4 } from "node:os";
|
|
26870
|
-
import { join as
|
|
27456
|
+
import { join as join24 } from "node:path";
|
|
26871
27457
|
|
|
26872
27458
|
// src/lib/dev/agent-connectors/json-config-utils.ts
|
|
26873
|
-
import { existsSync as
|
|
26874
|
-
import { dirname as
|
|
27459
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync16, writeFileSync as writeFileSync13 } from "node:fs";
|
|
27460
|
+
import { dirname as dirname9, join as join23, basename } from "node:path";
|
|
26875
27461
|
function readJsonFile(path) {
|
|
26876
|
-
if (!
|
|
27462
|
+
if (!existsSync27(path))
|
|
26877
27463
|
return { ok: true, value: {} };
|
|
26878
27464
|
try {
|
|
26879
|
-
const raw =
|
|
27465
|
+
const raw = readFileSync16(path, "utf-8");
|
|
26880
27466
|
if (!raw.trim())
|
|
26881
27467
|
return { ok: true, value: {} };
|
|
26882
27468
|
return { ok: true, value: JSON.parse(raw) };
|
|
26883
27469
|
} catch {
|
|
26884
27470
|
try {
|
|
26885
|
-
return { ok: false, raw:
|
|
27471
|
+
return { ok: false, raw: readFileSync16(path, "utf-8") };
|
|
26886
27472
|
} catch {
|
|
26887
27473
|
return { ok: false };
|
|
26888
27474
|
}
|
|
26889
27475
|
}
|
|
26890
27476
|
}
|
|
26891
27477
|
function atomicWriteJson(path, value) {
|
|
26892
|
-
|
|
27478
|
+
mkdirSync14(dirname9(path), { recursive: true });
|
|
26893
27479
|
const tmp = `${path}.mcpsh-${process.pid}-${Date.now()}.tmp`;
|
|
26894
|
-
|
|
27480
|
+
writeFileSync13(tmp, JSON.stringify(value, null, 2) + `
|
|
26895
27481
|
`, "utf-8");
|
|
26896
27482
|
const { renameSync: renameSync2 } = __require("node:fs");
|
|
26897
27483
|
renameSync2(tmp, path);
|
|
26898
27484
|
}
|
|
26899
27485
|
function backupConfig(args) {
|
|
26900
|
-
if (!
|
|
27486
|
+
if (!existsSync27(args.configPath)) {
|
|
26901
27487
|
return { backupPath: null, existed: false };
|
|
26902
27488
|
}
|
|
26903
|
-
|
|
27489
|
+
mkdirSync14(args.backupsDir, { recursive: true });
|
|
26904
27490
|
const filename = `${args.agentId}__${basename(args.configPath)}.bak`;
|
|
26905
|
-
const backupPath =
|
|
26906
|
-
if (!
|
|
26907
|
-
const raw =
|
|
26908
|
-
|
|
27491
|
+
const backupPath = join23(args.backupsDir, filename);
|
|
27492
|
+
if (!existsSync27(backupPath)) {
|
|
27493
|
+
const raw = readFileSync16(args.configPath, "utf-8");
|
|
27494
|
+
writeFileSync13(backupPath, raw, "utf-8");
|
|
26909
27495
|
}
|
|
26910
27496
|
return { backupPath, existed: true };
|
|
26911
27497
|
}
|
|
26912
27498
|
function restoreFromBackup(args) {
|
|
26913
27499
|
const filename = `${args.agentId}__${basename(args.configPath)}.bak`;
|
|
26914
|
-
const backupPath =
|
|
26915
|
-
if (!
|
|
27500
|
+
const backupPath = join23(args.backupsDir, filename);
|
|
27501
|
+
if (!existsSync27(backupPath)) {
|
|
26916
27502
|
return { restored: false };
|
|
26917
27503
|
}
|
|
26918
|
-
const raw =
|
|
26919
|
-
|
|
26920
|
-
|
|
27504
|
+
const raw = readFileSync16(backupPath, "utf-8");
|
|
27505
|
+
mkdirSync14(dirname9(args.configPath), { recursive: true });
|
|
27506
|
+
writeFileSync13(args.configPath, raw, "utf-8");
|
|
26921
27507
|
return { restored: true };
|
|
26922
27508
|
}
|
|
26923
27509
|
|
|
26924
27510
|
// src/lib/dev/agent-connectors/claude-code.ts
|
|
26925
27511
|
function configPath() {
|
|
26926
|
-
return
|
|
27512
|
+
return join24(homedir4(), ".claude.json");
|
|
26927
27513
|
}
|
|
26928
27514
|
function legacyConfigPath() {
|
|
26929
|
-
return
|
|
27515
|
+
return join24(homedir4(), ".claude", "mcp.json");
|
|
26930
27516
|
}
|
|
26931
27517
|
function resolveConfigPath() {
|
|
26932
|
-
if (
|
|
27518
|
+
if (existsSync28(configPath()))
|
|
26933
27519
|
return configPath();
|
|
26934
|
-
if (
|
|
27520
|
+
if (existsSync28(legacyConfigPath()))
|
|
26935
27521
|
return legacyConfigPath();
|
|
26936
27522
|
return configPath();
|
|
26937
27523
|
}
|
|
@@ -26941,8 +27527,8 @@ var claudeCodeConnector = {
|
|
|
26941
27527
|
hotkey: "c",
|
|
26942
27528
|
describeLocation: () => resolveConfigPath(),
|
|
26943
27529
|
async detect() {
|
|
26944
|
-
const claudeDir =
|
|
26945
|
-
if (
|
|
27530
|
+
const claudeDir = join24(homedir4(), ".claude");
|
|
27531
|
+
if (existsSync28(configPath()) || existsSync28(legacyConfigPath()) || existsSync28(claudeDir)) {
|
|
26946
27532
|
return { installed: true, note: "Found Claude Code config" };
|
|
26947
27533
|
}
|
|
26948
27534
|
return { installed: false };
|
|
@@ -26980,7 +27566,7 @@ var claudeCodeConnector = {
|
|
|
26980
27566
|
backupsDir: args.backupsDir,
|
|
26981
27567
|
agentId: this.id
|
|
26982
27568
|
});
|
|
26983
|
-
if (!restored.restored &&
|
|
27569
|
+
if (!restored.restored && existsSync28(path)) {
|
|
26984
27570
|
const after = readJsonFile(path);
|
|
26985
27571
|
if (after.ok && after.value && typeof after.value === "object") {
|
|
26986
27572
|
const root = after.value;
|
|
@@ -26990,7 +27576,7 @@ var claudeCodeConnector = {
|
|
|
26990
27576
|
try {
|
|
26991
27577
|
unlinkSync2(path);
|
|
26992
27578
|
} catch {
|
|
26993
|
-
|
|
27579
|
+
writeFileSync14(path, `{}
|
|
26994
27580
|
`, "utf-8");
|
|
26995
27581
|
}
|
|
26996
27582
|
}
|
|
@@ -27002,16 +27588,16 @@ var claudeCodeConnector = {
|
|
|
27002
27588
|
|
|
27003
27589
|
// src/lib/dev/agent-connectors/codex.ts
|
|
27004
27590
|
import {
|
|
27005
|
-
existsSync as
|
|
27006
|
-
mkdirSync as
|
|
27007
|
-
readFileSync as
|
|
27591
|
+
existsSync as existsSync29,
|
|
27592
|
+
mkdirSync as mkdirSync15,
|
|
27593
|
+
readFileSync as readFileSync17,
|
|
27008
27594
|
unlinkSync as unlinkSync3,
|
|
27009
|
-
writeFileSync as
|
|
27595
|
+
writeFileSync as writeFileSync15
|
|
27010
27596
|
} from "node:fs";
|
|
27011
27597
|
import { homedir as homedir5 } from "node:os";
|
|
27012
|
-
import { dirname as
|
|
27598
|
+
import { dirname as dirname10, join as join25 } from "node:path";
|
|
27013
27599
|
function configPath2() {
|
|
27014
|
-
return
|
|
27600
|
+
return join25(homedir5(), ".codex", "config.toml");
|
|
27015
27601
|
}
|
|
27016
27602
|
var SECTION_PREFIX = "mcp_servers.";
|
|
27017
27603
|
function buildSection(name, url) {
|
|
@@ -27049,7 +27635,7 @@ var codexConnector = {
|
|
|
27049
27635
|
hotkey: "x",
|
|
27050
27636
|
describeLocation: () => configPath2(),
|
|
27051
27637
|
async detect() {
|
|
27052
|
-
if (
|
|
27638
|
+
if (existsSync29(join25(homedir5(), ".codex")) || existsSync29(configPath2())) {
|
|
27053
27639
|
return { installed: true, note: "Found ~/.codex/" };
|
|
27054
27640
|
}
|
|
27055
27641
|
return { installed: false };
|
|
@@ -27062,8 +27648,8 @@ var codexConnector = {
|
|
|
27062
27648
|
agentId: this.id
|
|
27063
27649
|
});
|
|
27064
27650
|
let existing = "";
|
|
27065
|
-
if (
|
|
27066
|
-
existing =
|
|
27651
|
+
if (existsSync29(path)) {
|
|
27652
|
+
existing = readFileSync17(path, "utf-8");
|
|
27067
27653
|
}
|
|
27068
27654
|
const conflict = findSectionRange(existing, args.name) !== null;
|
|
27069
27655
|
let next = existing;
|
|
@@ -27078,18 +27664,18 @@ var codexConnector = {
|
|
|
27078
27664
|
next += `
|
|
27079
27665
|
`;
|
|
27080
27666
|
next += buildSection(args.name, args.url);
|
|
27081
|
-
|
|
27082
|
-
|
|
27667
|
+
mkdirSync15(dirname10(path), { recursive: true });
|
|
27668
|
+
writeFileSync15(path, next, "utf-8");
|
|
27083
27669
|
return { added: !conflict, conflict };
|
|
27084
27670
|
},
|
|
27085
27671
|
async remove(args) {
|
|
27086
27672
|
const path = configPath2();
|
|
27087
|
-
if (
|
|
27088
|
-
const existing =
|
|
27673
|
+
if (existsSync29(path)) {
|
|
27674
|
+
const existing = readFileSync17(path, "utf-8");
|
|
27089
27675
|
const range = findSectionRange(existing, args.name);
|
|
27090
27676
|
if (range) {
|
|
27091
27677
|
const next = existing.slice(0, range.start) + existing.slice(range.end);
|
|
27092
|
-
|
|
27678
|
+
writeFileSync15(path, next, "utf-8");
|
|
27093
27679
|
}
|
|
27094
27680
|
}
|
|
27095
27681
|
const restored = restoreFromBackup({
|
|
@@ -27097,8 +27683,8 @@ var codexConnector = {
|
|
|
27097
27683
|
backupsDir: args.backupsDir,
|
|
27098
27684
|
agentId: this.id
|
|
27099
27685
|
});
|
|
27100
|
-
if (!restored.restored &&
|
|
27101
|
-
const after =
|
|
27686
|
+
if (!restored.restored && existsSync29(path)) {
|
|
27687
|
+
const after = readFileSync17(path, "utf-8");
|
|
27102
27688
|
if (!after.trim()) {
|
|
27103
27689
|
try {
|
|
27104
27690
|
unlinkSync3(path);
|
|
@@ -27110,11 +27696,11 @@ var codexConnector = {
|
|
|
27110
27696
|
};
|
|
27111
27697
|
|
|
27112
27698
|
// src/lib/dev/agent-connectors/continue.ts
|
|
27113
|
-
import { existsSync as
|
|
27699
|
+
import { existsSync as existsSync30, unlinkSync as unlinkSync4, writeFileSync as writeFileSync16 } from "node:fs";
|
|
27114
27700
|
import { homedir as homedir6 } from "node:os";
|
|
27115
|
-
import { join as
|
|
27701
|
+
import { join as join26 } from "node:path";
|
|
27116
27702
|
function configPath3() {
|
|
27117
|
-
return
|
|
27703
|
+
return join26(homedir6(), ".continue", "config.json");
|
|
27118
27704
|
}
|
|
27119
27705
|
function isContinueServerEntry(value) {
|
|
27120
27706
|
return Boolean(value && typeof value === "object" && typeof value.name === "string");
|
|
@@ -27125,8 +27711,8 @@ var continueConnector = {
|
|
|
27125
27711
|
hotkey: "n",
|
|
27126
27712
|
describeLocation: () => configPath3(),
|
|
27127
27713
|
async detect() {
|
|
27128
|
-
const dir =
|
|
27129
|
-
if (
|
|
27714
|
+
const dir = join26(homedir6(), ".continue");
|
|
27715
|
+
if (existsSync30(dir) || existsSync30(configPath3())) {
|
|
27130
27716
|
return { installed: true, note: "Found ~/.continue/" };
|
|
27131
27717
|
}
|
|
27132
27718
|
return { installed: false };
|
|
@@ -27164,7 +27750,7 @@ var continueConnector = {
|
|
|
27164
27750
|
backupsDir: args.backupsDir,
|
|
27165
27751
|
agentId: this.id
|
|
27166
27752
|
});
|
|
27167
|
-
if (!restored.restored &&
|
|
27753
|
+
if (!restored.restored && existsSync30(path)) {
|
|
27168
27754
|
const after = readJsonFile(path);
|
|
27169
27755
|
if (after.ok && after.value && typeof after.value === "object") {
|
|
27170
27756
|
const root = after.value;
|
|
@@ -27174,7 +27760,7 @@ var continueConnector = {
|
|
|
27174
27760
|
try {
|
|
27175
27761
|
unlinkSync4(path);
|
|
27176
27762
|
} catch {
|
|
27177
|
-
|
|
27763
|
+
writeFileSync16(path, `{}
|
|
27178
27764
|
`, "utf-8");
|
|
27179
27765
|
}
|
|
27180
27766
|
}
|
|
@@ -27185,11 +27771,11 @@ var continueConnector = {
|
|
|
27185
27771
|
};
|
|
27186
27772
|
|
|
27187
27773
|
// src/lib/dev/agent-connectors/cursor.ts
|
|
27188
|
-
import { existsSync as
|
|
27774
|
+
import { existsSync as existsSync31, unlinkSync as unlinkSync5, writeFileSync as writeFileSync17 } from "node:fs";
|
|
27189
27775
|
import { homedir as homedir7 } from "node:os";
|
|
27190
|
-
import { join as
|
|
27776
|
+
import { join as join27 } from "node:path";
|
|
27191
27777
|
function globalConfigPath() {
|
|
27192
|
-
return
|
|
27778
|
+
return join27(homedir7(), ".cursor", "mcp.json");
|
|
27193
27779
|
}
|
|
27194
27780
|
var cursorConnector = {
|
|
27195
27781
|
id: "cursor",
|
|
@@ -27197,9 +27783,9 @@ var cursorConnector = {
|
|
|
27197
27783
|
hotkey: "u",
|
|
27198
27784
|
describeLocation: () => globalConfigPath(),
|
|
27199
27785
|
async detect() {
|
|
27200
|
-
const cursorDir =
|
|
27201
|
-
const macAppSupport =
|
|
27202
|
-
if (
|
|
27786
|
+
const cursorDir = join27(homedir7(), ".cursor");
|
|
27787
|
+
const macAppSupport = join27(homedir7(), "Library", "Application Support", "Cursor");
|
|
27788
|
+
if (existsSync31(cursorDir) || existsSync31(macAppSupport)) {
|
|
27203
27789
|
return { installed: true, note: "Found Cursor config dir" };
|
|
27204
27790
|
}
|
|
27205
27791
|
return { installed: false };
|
|
@@ -27237,7 +27823,7 @@ var cursorConnector = {
|
|
|
27237
27823
|
backupsDir: args.backupsDir,
|
|
27238
27824
|
agentId: this.id
|
|
27239
27825
|
});
|
|
27240
|
-
if (!restored.restored &&
|
|
27826
|
+
if (!restored.restored && existsSync31(path)) {
|
|
27241
27827
|
const after = readJsonFile(path);
|
|
27242
27828
|
if (after.ok && after.value && typeof after.value === "object") {
|
|
27243
27829
|
const root = after.value;
|
|
@@ -27247,7 +27833,7 @@ var cursorConnector = {
|
|
|
27247
27833
|
try {
|
|
27248
27834
|
unlinkSync5(path);
|
|
27249
27835
|
} catch {
|
|
27250
|
-
|
|
27836
|
+
writeFileSync17(path, `{}
|
|
27251
27837
|
`, "utf-8");
|
|
27252
27838
|
}
|
|
27253
27839
|
}
|
|
@@ -27258,30 +27844,30 @@ var cursorConnector = {
|
|
|
27258
27844
|
};
|
|
27259
27845
|
|
|
27260
27846
|
// src/lib/dev/agent-connectors/vscode-copilot.ts
|
|
27261
|
-
import { existsSync as
|
|
27847
|
+
import { existsSync as existsSync32, unlinkSync as unlinkSync6, writeFileSync as writeFileSync18 } from "node:fs";
|
|
27262
27848
|
import { homedir as homedir8, platform as platform4 } from "node:os";
|
|
27263
|
-
import { join as
|
|
27849
|
+
import { join as join28, resolve as resolve7 } from "node:path";
|
|
27264
27850
|
function userLevelConfigPath() {
|
|
27265
27851
|
const home = homedir8();
|
|
27266
27852
|
const p2 = platform4();
|
|
27267
27853
|
if (p2 === "darwin")
|
|
27268
|
-
return
|
|
27854
|
+
return join28(home, "Library", "Application Support", "Code", "User", "mcp.json");
|
|
27269
27855
|
if (p2 === "win32") {
|
|
27270
|
-
const appData = process.env["APPDATA"] ??
|
|
27271
|
-
return
|
|
27856
|
+
const appData = process.env["APPDATA"] ?? join28(home, "AppData", "Roaming");
|
|
27857
|
+
return join28(appData, "Code", "User", "mcp.json");
|
|
27272
27858
|
}
|
|
27273
|
-
const xdg = process.env["XDG_CONFIG_HOME"] ??
|
|
27274
|
-
return
|
|
27859
|
+
const xdg = process.env["XDG_CONFIG_HOME"] ?? join28(home, ".config");
|
|
27860
|
+
return join28(xdg, "Code", "User", "mcp.json");
|
|
27275
27861
|
}
|
|
27276
27862
|
function isHomeDir(cwd) {
|
|
27277
|
-
return
|
|
27863
|
+
return resolve7(cwd) === resolve7(homedir8());
|
|
27278
27864
|
}
|
|
27279
27865
|
function hasWorkspaceVscode(cwd) {
|
|
27280
|
-
return
|
|
27866
|
+
return existsSync32(join28(cwd, ".vscode"));
|
|
27281
27867
|
}
|
|
27282
27868
|
function configPath4(cwd) {
|
|
27283
27869
|
if (!isHomeDir(cwd) && hasWorkspaceVscode(cwd)) {
|
|
27284
|
-
return
|
|
27870
|
+
return join28(cwd, ".vscode", "mcp.json");
|
|
27285
27871
|
}
|
|
27286
27872
|
return userLevelConfigPath();
|
|
27287
27873
|
}
|
|
@@ -27295,7 +27881,7 @@ var vscodeCopilotConnector = {
|
|
|
27295
27881
|
if (!isHomeDir(here) && hasWorkspaceVscode(here)) {
|
|
27296
27882
|
return { installed: true, note: "Found .vscode/ in current dir" };
|
|
27297
27883
|
}
|
|
27298
|
-
if (
|
|
27884
|
+
if (existsSync32(userLevelConfigPath().replace(/mcp\.json$/, ""))) {
|
|
27299
27885
|
return { installed: true, note: "Found VS Code user profile" };
|
|
27300
27886
|
}
|
|
27301
27887
|
return { installed: false };
|
|
@@ -27333,7 +27919,7 @@ var vscodeCopilotConnector = {
|
|
|
27333
27919
|
backupsDir: args.backupsDir,
|
|
27334
27920
|
agentId: this.id
|
|
27335
27921
|
});
|
|
27336
|
-
if (!restored.restored &&
|
|
27922
|
+
if (!restored.restored && existsSync32(path)) {
|
|
27337
27923
|
const after = readJsonFile(path);
|
|
27338
27924
|
if (after.ok && after.value && typeof after.value === "object") {
|
|
27339
27925
|
const root = after.value;
|
|
@@ -27343,7 +27929,7 @@ var vscodeCopilotConnector = {
|
|
|
27343
27929
|
try {
|
|
27344
27930
|
unlinkSync6(path);
|
|
27345
27931
|
} catch {
|
|
27346
|
-
|
|
27932
|
+
writeFileSync18(path, `{}
|
|
27347
27933
|
`, "utf-8");
|
|
27348
27934
|
}
|
|
27349
27935
|
}
|
|
@@ -27487,7 +28073,7 @@ function parseGraceMs(value, fallbackMs) {
|
|
|
27487
28073
|
return Math.round(n * 1000);
|
|
27488
28074
|
}
|
|
27489
28075
|
function resolveSpecPath(cwd, raw) {
|
|
27490
|
-
return isAbsolute3(raw) ? raw :
|
|
28076
|
+
return isAbsolute3(raw) ? raw : resolve8(cwd, raw);
|
|
27491
28077
|
}
|
|
27492
28078
|
function buildConnectionName(serverId) {
|
|
27493
28079
|
const slug = serverId.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24) || "mcp-server";
|
|
@@ -27646,7 +28232,7 @@ async function runDev(opts) {
|
|
|
27646
28232
|
let specPath = null;
|
|
27647
28233
|
if (opts.spec) {
|
|
27648
28234
|
const resolved = resolveSpecPath(cwd, opts.spec);
|
|
27649
|
-
if (!
|
|
28235
|
+
if (!existsSync33(resolved)) {
|
|
27650
28236
|
printError(`Spec file not found: ${resolved}`);
|
|
27651
28237
|
process.exit(1);
|
|
27652
28238
|
}
|
|
@@ -28407,7 +28993,7 @@ function registerDeploymentCommands(program2) {
|
|
|
28407
28993
|
const ok = await confirmRollback(deploymentId);
|
|
28408
28994
|
if (!ok) {
|
|
28409
28995
|
printInfo(c.dim("Aborted."));
|
|
28410
|
-
|
|
28996
|
+
throw new CliExitError(1);
|
|
28411
28997
|
}
|
|
28412
28998
|
}
|
|
28413
28999
|
const orgId = await resolveOrgId(opts.org);
|
|
@@ -28416,20 +29002,57 @@ function registerDeploymentCommands(program2) {
|
|
|
28416
29002
|
printJson(data);
|
|
28417
29003
|
return;
|
|
28418
29004
|
}
|
|
28419
|
-
const d = data.deployment;
|
|
28420
|
-
printSuccess(`Deployment ${d.deploymentId} rolled back to active.`);
|
|
29005
|
+
const d = data.deployment;
|
|
29006
|
+
printSuccess(`Deployment ${d.deploymentId} rolled back to active.`);
|
|
29007
|
+
printKeyValue({
|
|
29008
|
+
"deployment id": d.deploymentId,
|
|
29009
|
+
server: d.serverId,
|
|
29010
|
+
"previous status": d.previousStatus,
|
|
29011
|
+
status: d.status,
|
|
29012
|
+
"access mode": d.accessMode,
|
|
29013
|
+
url: d.deploymentUrl ?? "—",
|
|
29014
|
+
target: d.target,
|
|
29015
|
+
version: d.version,
|
|
29016
|
+
demoted: d.demotedDeploymentId ?? "—",
|
|
29017
|
+
"rolled back at": formatDate(d.rolledBackAt)
|
|
29018
|
+
});
|
|
29019
|
+
}));
|
|
29020
|
+
deployments.command("health <deploymentId>").description("Show a deployment's last health-check result (status, HTTP code, when it was checked)").option("--org <organizationId>", "Organization ID").addHelpText("after", [
|
|
29021
|
+
"",
|
|
29022
|
+
"Exit codes:",
|
|
29023
|
+
" 0 the deployment reported a healthy status",
|
|
29024
|
+
" 1 the deployment is unhealthy, unknown, or not found",
|
|
29025
|
+
"",
|
|
29026
|
+
"Examples:",
|
|
29027
|
+
" $ mcp deployments health dep_123",
|
|
29028
|
+
' $ mcp --quiet deployments health dep_123 && echo "deploy is green"',
|
|
29029
|
+
" $ mcp --json deployments health dep_123 | jq '.health.status'"
|
|
29030
|
+
].join(`
|
|
29031
|
+
`)).action(runAction(async (deploymentId, opts) => {
|
|
29032
|
+
const orgId = await resolveOrgId(opts.org);
|
|
29033
|
+
const data = await api.get("/api/v1/deployment/health", { organizationId: orgId, deploymentId });
|
|
29034
|
+
const isHealthy = data.health.status === "healthy";
|
|
29035
|
+
if (isQuiet()) {
|
|
29036
|
+
if (!isHealthy)
|
|
29037
|
+
throw new CliExitError(1);
|
|
29038
|
+
return;
|
|
29039
|
+
}
|
|
29040
|
+
if (isJsonMode()) {
|
|
29041
|
+
printJson(data);
|
|
29042
|
+
if (!isHealthy)
|
|
29043
|
+
throw new CliExitError(1);
|
|
29044
|
+
return;
|
|
29045
|
+
}
|
|
28421
29046
|
printKeyValue({
|
|
28422
|
-
|
|
28423
|
-
|
|
28424
|
-
"
|
|
28425
|
-
|
|
28426
|
-
"
|
|
28427
|
-
|
|
28428
|
-
target: d.target,
|
|
28429
|
-
version: d.version,
|
|
28430
|
-
demoted: d.demotedDeploymentId ?? "—",
|
|
28431
|
-
"rolled back at": formatDate(d.rolledBackAt)
|
|
29047
|
+
deployment: `${data.deployment.serverName} (${data.deployment.id})`,
|
|
29048
|
+
status: data.health.status,
|
|
29049
|
+
"http status": data.health.statusCode ?? "—",
|
|
29050
|
+
"checked at": data.health.checkedAt ? formatDate(data.health.checkedAt) : "—",
|
|
29051
|
+
"health error": data.health.error ?? "—",
|
|
29052
|
+
"deploy error": data.health.deploymentError ?? "—"
|
|
28432
29053
|
});
|
|
29054
|
+
if (!isHealthy)
|
|
29055
|
+
throw new CliExitError(1);
|
|
28433
29056
|
}));
|
|
28434
29057
|
deployments.command("logs <deploymentId>").description("Show recent deployment events; pass --follow to tail new ones as they arrive").option("--org <organizationId>", "Organization ID").option("--limit <n>", "Number of recent events on the initial read (default 50)", parsePositiveIntOption("limit"), "50").option("--follow", "Tail new events as they arrive (long-poll). Ctrl-C to stop.").option("--since <when>", "Only show events after this point. Accepts relative (5m, 1h, 30s), Unix-ms, or ISO-8601.").option("--follow-timeout <seconds>", "Maximum seconds to keep --follow open before exiting (default: 1 hour).", "3600").addHelpText("after", [
|
|
28435
29058
|
"",
|
|
@@ -28462,13 +29085,13 @@ async function confirmRollback(deploymentId) {
|
|
|
28462
29085
|
}
|
|
28463
29086
|
|
|
28464
29087
|
// src/commands/doctor.ts
|
|
28465
|
-
import { existsSync as
|
|
29088
|
+
import { existsSync as existsSync35, statSync as statSync6, readFileSync as readFileSync19 } from "node:fs";
|
|
28466
29089
|
import { homedir as homedir9, platform as platform6 } from "node:os";
|
|
28467
|
-
import { join as
|
|
29090
|
+
import { join as join30, delimiter as delimiter3 } from "node:path";
|
|
28468
29091
|
|
|
28469
29092
|
// src/lib/version-check.ts
|
|
28470
|
-
import { existsSync as
|
|
28471
|
-
import { join as
|
|
29093
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync16, readFileSync as readFileSync18, writeFileSync as writeFileSync19 } from "node:fs";
|
|
29094
|
+
import { join as join29 } from "node:path";
|
|
28472
29095
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
28473
29096
|
var FETCH_TIMEOUT_MS2 = 2000;
|
|
28474
29097
|
var REGISTRY_URL = "https://registry.npmjs.org/@mcpcloud/cli/latest";
|
|
@@ -28476,13 +29099,13 @@ function cacheDir() {
|
|
|
28476
29099
|
return configDir();
|
|
28477
29100
|
}
|
|
28478
29101
|
function cacheFile() {
|
|
28479
|
-
return
|
|
29102
|
+
return join29(cacheDir(), "version-check.json");
|
|
28480
29103
|
}
|
|
28481
29104
|
function readCache() {
|
|
28482
|
-
if (!
|
|
29105
|
+
if (!existsSync34(cacheFile()))
|
|
28483
29106
|
return null;
|
|
28484
29107
|
try {
|
|
28485
|
-
const raw = JSON.parse(
|
|
29108
|
+
const raw = JSON.parse(readFileSync18(cacheFile(), "utf-8"));
|
|
28486
29109
|
if (typeof raw.latest !== "string" || typeof raw.fetchedAt !== "number")
|
|
28487
29110
|
return null;
|
|
28488
29111
|
return { latest: raw.latest, fetchedAt: raw.fetchedAt };
|
|
@@ -28492,9 +29115,9 @@ function readCache() {
|
|
|
28492
29115
|
}
|
|
28493
29116
|
function writeCache(entry) {
|
|
28494
29117
|
try {
|
|
28495
|
-
if (!
|
|
28496
|
-
|
|
28497
|
-
|
|
29118
|
+
if (!existsSync34(cacheDir()))
|
|
29119
|
+
mkdirSync16(cacheDir(), { recursive: true, mode: 448 });
|
|
29120
|
+
writeFileSync19(cacheFile(), JSON.stringify(entry, null, 2), { mode: 384 });
|
|
28498
29121
|
} catch {}
|
|
28499
29122
|
}
|
|
28500
29123
|
function compareVersions(a, b) {
|
|
@@ -28605,8 +29228,8 @@ function checkNode() {
|
|
|
28605
29228
|
}
|
|
28606
29229
|
function checkConfigFile() {
|
|
28607
29230
|
const t0 = Date.now();
|
|
28608
|
-
const path =
|
|
28609
|
-
if (!
|
|
29231
|
+
const path = join30(homedir9(), ".mcpcloud", "config.json");
|
|
29232
|
+
if (!existsSync35(path)) {
|
|
28610
29233
|
return {
|
|
28611
29234
|
name: "Config file",
|
|
28612
29235
|
status: "warn",
|
|
@@ -28628,7 +29251,7 @@ function checkConfigFile() {
|
|
|
28628
29251
|
} catch {}
|
|
28629
29252
|
}
|
|
28630
29253
|
try {
|
|
28631
|
-
JSON.parse(
|
|
29254
|
+
JSON.parse(readFileSync19(path, "utf-8"));
|
|
28632
29255
|
} catch (err) {
|
|
28633
29256
|
return {
|
|
28634
29257
|
name: "Config file",
|
|
@@ -28763,8 +29386,8 @@ function checkClaudeCli() {
|
|
|
28763
29386
|
for (const dir of PATH.split(delimiter3)) {
|
|
28764
29387
|
if (!dir)
|
|
28765
29388
|
continue;
|
|
28766
|
-
const candidate =
|
|
28767
|
-
if (
|
|
29389
|
+
const candidate = join30(dir, exe);
|
|
29390
|
+
if (existsSync35(candidate)) {
|
|
28768
29391
|
return {
|
|
28769
29392
|
name: "claude CLI",
|
|
28770
29393
|
status: "pass",
|
|
@@ -29099,14 +29722,14 @@ function registerHelpCommand(program2) {
|
|
|
29099
29722
|
// src/commands/init.ts
|
|
29100
29723
|
import { createInterface as createInterface2 } from "node:readline";
|
|
29101
29724
|
function prompt2(question) {
|
|
29102
|
-
if (
|
|
29725
|
+
if (isNonInteractive()) {
|
|
29103
29726
|
throw new Error("Interactive prompts are disabled in CI mode. Pass --org / --project / --name explicitly or run `mcp init` outside CI.");
|
|
29104
29727
|
}
|
|
29105
29728
|
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
29106
|
-
return new Promise((
|
|
29729
|
+
return new Promise((resolve9) => {
|
|
29107
29730
|
rl.question(question, (answer) => {
|
|
29108
29731
|
rl.close();
|
|
29109
|
-
|
|
29732
|
+
resolve9(answer.trim());
|
|
29110
29733
|
});
|
|
29111
29734
|
});
|
|
29112
29735
|
}
|
|
@@ -29117,7 +29740,7 @@ async function pickProject(orgId, override) {
|
|
|
29117
29740
|
}
|
|
29118
29741
|
const list = await api.get("/api/v1/projects", { organizationId: orgId, limit: "25" });
|
|
29119
29742
|
if (list.projects.length === 0) {
|
|
29120
|
-
if (
|
|
29743
|
+
if (isNonInteractive()) {
|
|
29121
29744
|
throw new Error("Organization has no projects yet. Pass --project <id> to reuse an existing one or run outside CI to create one.");
|
|
29122
29745
|
}
|
|
29123
29746
|
printInfo(c.dim("No projects yet — creating one."));
|
|
@@ -29127,7 +29750,7 @@ async function pickProject(orgId, override) {
|
|
|
29127
29750
|
const created = await api.post("/api/v1/projects", { organizationId: orgId, name });
|
|
29128
29751
|
return { project: created.project, created: true };
|
|
29129
29752
|
}
|
|
29130
|
-
if (
|
|
29753
|
+
if (isNonInteractive()) {
|
|
29131
29754
|
throw new Error("Multiple projects found. Pass --project <id> to pick one in CI mode.");
|
|
29132
29755
|
}
|
|
29133
29756
|
printInfo("");
|
|
@@ -29157,7 +29780,7 @@ async function pickServerName(override) {
|
|
|
29157
29780
|
throw new Error("--name must not be empty.");
|
|
29158
29781
|
return trimmed;
|
|
29159
29782
|
}
|
|
29160
|
-
if (
|
|
29783
|
+
if (isNonInteractive()) {
|
|
29161
29784
|
throw new Error("Interactive prompts are disabled in CI mode. Pass --name <serverName>.");
|
|
29162
29785
|
}
|
|
29163
29786
|
const name = await prompt2("Server name: ");
|
|
@@ -29166,27 +29789,25 @@ async function pickServerName(override) {
|
|
|
29166
29789
|
return name;
|
|
29167
29790
|
}
|
|
29168
29791
|
function registerInitCommand(program2) {
|
|
29169
|
-
program2.command("init").description("Guided onboarding: pick org → pick/create project → create
|
|
29170
|
-
"",
|
|
29171
|
-
"What v0 does:",
|
|
29172
|
-
" 1. Resolves the target organization (--org or default).",
|
|
29173
|
-
" 2. Lets you pick or create a project.",
|
|
29174
|
-
" 3. Creates an empty server skeleton in that project.",
|
|
29792
|
+
program2.command("init").description("Guided onboarding: pick org → pick/create project → create a server (empty skeleton, or generated from an OpenAPI/GraphQL spec via --from-spec)").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Reuse an existing project instead of creating one").option("--name <serverName>", "Name for the new server (skeleton path only)").option("--from-spec <file|url>", "Build the server from an OpenAPI/GraphQL spec: a local file, http(s) URL (fetched server-side), or @- for stdin").option("--source-type <type>", `Override spec classification: ${SOURCE_TYPES.join(" | ")} (auto-detected by default)`).option("--deploy", "After importing the spec, deploy the server").option("--wait", "With --deploy, follow events until the deploy is terminal").option("--wait-timeout <seconds>", "Max seconds to wait for a terminal deploy state (default 300)", "300").addHelpText("after", [
|
|
29175
29793
|
"",
|
|
29176
|
-
"
|
|
29177
|
-
"
|
|
29178
|
-
"
|
|
29179
|
-
"
|
|
29794
|
+
"Two modes:",
|
|
29795
|
+
" Skeleton (default): resolve org → pick/create project → create an empty server.",
|
|
29796
|
+
" Spec (--from-spec): resolve org → pick/create project → ingest the spec into a",
|
|
29797
|
+
" generated server, then optionally --deploy [--wait].",
|
|
29180
29798
|
"",
|
|
29181
29799
|
"Examples:",
|
|
29182
29800
|
" $ mcp init",
|
|
29183
|
-
' $ mcp init --org org_acme --project proj_123 --name "Stripe MCP"'
|
|
29801
|
+
' $ mcp init --org org_acme --project proj_123 --name "Stripe MCP"',
|
|
29802
|
+
" $ mcp init --from-spec ./openapi.yaml --project proj_123",
|
|
29803
|
+
" $ mcp init --from-spec https://api.example.com/openapi.json --project proj_123 --deploy --wait"
|
|
29184
29804
|
].join(`
|
|
29185
29805
|
`)).action(runAction(async (opts) => {
|
|
29186
|
-
if (opts.nonInteractive) {
|
|
29187
|
-
setCiMode(true);
|
|
29188
|
-
}
|
|
29189
29806
|
const orgId = await resolveOrgId(opts.org);
|
|
29807
|
+
if (opts.fromSpec) {
|
|
29808
|
+
await runSpecInit({ orgId, opts });
|
|
29809
|
+
return;
|
|
29810
|
+
}
|
|
29190
29811
|
if (!isJsonMode()) {
|
|
29191
29812
|
const orgs = await api.get("/api/v1/organizations");
|
|
29192
29813
|
const org = orgs.organizations.find((o) => o.id === orgId);
|
|
@@ -29222,12 +29843,103 @@ function registerInitCommand(program2) {
|
|
|
29222
29843
|
printSuccess(`Server ${c.bold(server.id)} ready.`);
|
|
29223
29844
|
printInfo("");
|
|
29224
29845
|
printInfo("Next steps:");
|
|
29225
|
-
printInfo(` 1. Import an API spec
|
|
29226
|
-
printInfo(` ${c.dim("(
|
|
29846
|
+
printInfo(` 1. Import an API spec: \`mcp servers ingest --project ${project.id} --spec ./openapi.yaml\``);
|
|
29847
|
+
printInfo(` ${c.dim("(or re-run `mcp init --from-spec ./openapi.yaml` for the guided flow)")}`);
|
|
29227
29848
|
printInfo(` 2. Run \`mcp dev --spec ./openapi.yaml\` to iterate locally.`);
|
|
29228
29849
|
printInfo(` 3. \`mcp servers deploy ${server.id} --wait\` to ship it.`);
|
|
29229
29850
|
}));
|
|
29230
29851
|
}
|
|
29852
|
+
function validateSourceType2(value) {
|
|
29853
|
+
if (value === undefined)
|
|
29854
|
+
return;
|
|
29855
|
+
const v2 = value.trim().toLowerCase();
|
|
29856
|
+
if (!SOURCE_TYPES.includes(v2)) {
|
|
29857
|
+
throw new Error(`Invalid --source-type: ${value}. Expected one of: ${SOURCE_TYPES.join(", ")}.`);
|
|
29858
|
+
}
|
|
29859
|
+
return v2;
|
|
29860
|
+
}
|
|
29861
|
+
async function runSpecInit(args) {
|
|
29862
|
+
const { orgId, opts } = args;
|
|
29863
|
+
const sourceType = validateSourceType2(opts.sourceType);
|
|
29864
|
+
const payload = await readSpecSource(opts.fromSpec, sourceType);
|
|
29865
|
+
const { project, created: projectCreated } = await pickProject(orgId, opts.project);
|
|
29866
|
+
if (!isJsonMode() && projectCreated) {
|
|
29867
|
+
printSuccess(`Project ${c.bold(project.name)} created (${project.id}).`);
|
|
29868
|
+
}
|
|
29869
|
+
if (!isJsonMode()) {
|
|
29870
|
+
printStep(`Importing ${c.bold(payload.sourceType)} spec into ${c.bold(project.name)}…`);
|
|
29871
|
+
}
|
|
29872
|
+
const ingest = await api.post("/api/v1/server/ingest", buildIngestBody({
|
|
29873
|
+
organizationId: orgId,
|
|
29874
|
+
projectId: project.id,
|
|
29875
|
+
payload
|
|
29876
|
+
}));
|
|
29877
|
+
const server = ingest.server;
|
|
29878
|
+
if (!isJsonMode()) {
|
|
29879
|
+
printSuccess(`Server ${c.bold(server.id)} created from spec.`);
|
|
29880
|
+
}
|
|
29881
|
+
let deployResult = null;
|
|
29882
|
+
if (opts.deploy) {
|
|
29883
|
+
if (!isJsonMode())
|
|
29884
|
+
printStep(`Deploying ${c.bold(server.id)}…`);
|
|
29885
|
+
const deployData = await api.post("/api/v1/server/deploy", {
|
|
29886
|
+
organizationId: orgId,
|
|
29887
|
+
serverId: server.id
|
|
29888
|
+
});
|
|
29889
|
+
let waitDone = !opts.wait;
|
|
29890
|
+
let terminalEvent = null;
|
|
29891
|
+
if (opts.wait) {
|
|
29892
|
+
const totalTimeoutMs = Math.max(10, Number.parseInt(opts.waitTimeout, 10) || 300) * 1000;
|
|
29893
|
+
const followed = await followDeploymentEvents({
|
|
29894
|
+
organizationId: orgId,
|
|
29895
|
+
deploymentId: deployData.deployment.deploymentId,
|
|
29896
|
+
totalTimeoutMs,
|
|
29897
|
+
onEvent: (event) => {
|
|
29898
|
+
if (!isJsonMode())
|
|
29899
|
+
printInfo(formatEventLine(event));
|
|
29900
|
+
}
|
|
29901
|
+
});
|
|
29902
|
+
waitDone = followed.done;
|
|
29903
|
+
terminalEvent = followed.terminalEvent;
|
|
29904
|
+
}
|
|
29905
|
+
deployResult = { deployment: deployData.deployment, waitDone, terminalEvent };
|
|
29906
|
+
}
|
|
29907
|
+
if (isJsonMode()) {
|
|
29908
|
+
printJson({
|
|
29909
|
+
organizationId: orgId,
|
|
29910
|
+
project,
|
|
29911
|
+
projectCreated,
|
|
29912
|
+
apiSource: ingest.apiSource,
|
|
29913
|
+
server,
|
|
29914
|
+
deploy: deployResult
|
|
29915
|
+
});
|
|
29916
|
+
if (deployResult && opts.wait && !deployResult.waitDone)
|
|
29917
|
+
throw new CliExitError(1);
|
|
29918
|
+
return;
|
|
29919
|
+
}
|
|
29920
|
+
if (deployResult) {
|
|
29921
|
+
const dep = deployResult.deployment;
|
|
29922
|
+
printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
|
|
29923
|
+
printKeyValue({
|
|
29924
|
+
"server id": server.id,
|
|
29925
|
+
"deployment id": dep.deploymentId,
|
|
29926
|
+
url: dep.deploymentUrl ?? "—",
|
|
29927
|
+
target: dep.target,
|
|
29928
|
+
"access mode": dep.accessMode,
|
|
29929
|
+
status: dep.status
|
|
29930
|
+
});
|
|
29931
|
+
if (opts.wait && !deployResult.waitDone) {
|
|
29932
|
+
printError(`Deploy did not reach a terminal state in time. Watch with \`mcp deployments logs ${dep.deploymentId} --follow\`.`);
|
|
29933
|
+
throw new CliExitError(1);
|
|
29934
|
+
}
|
|
29935
|
+
printStep(`Try it: \`mcp invoke ${server.id} <tool>\`.`);
|
|
29936
|
+
return;
|
|
29937
|
+
}
|
|
29938
|
+
printInfo("");
|
|
29939
|
+
printInfo("Next steps:");
|
|
29940
|
+
printStep(`Generate the bundle: \`mcp servers generate ${server.id} --out ./build\``);
|
|
29941
|
+
printStep(`Deploy it: \`mcp servers deploy ${server.id} --wait\``);
|
|
29942
|
+
}
|
|
29231
29943
|
|
|
29232
29944
|
// src/commands/installation.ts
|
|
29233
29945
|
function registerInstallationCommands(program2) {
|
|
@@ -29369,7 +30081,7 @@ function mapApiError2(err, context) {
|
|
|
29369
30081
|
}
|
|
29370
30082
|
|
|
29371
30083
|
// src/commands/invoke.ts
|
|
29372
|
-
function
|
|
30084
|
+
function parseTimeoutMs3(value, fallbackMs) {
|
|
29373
30085
|
if (!value)
|
|
29374
30086
|
return fallbackMs;
|
|
29375
30087
|
const n = Number(value);
|
|
@@ -29399,7 +30111,7 @@ function registerInvokeCommand(program2) {
|
|
|
29399
30111
|
orgOverride: opts.org,
|
|
29400
30112
|
deploymentOverride: opts.deployment,
|
|
29401
30113
|
argsRaw: opts.args,
|
|
29402
|
-
timeoutMs:
|
|
30114
|
+
timeoutMs: parseTimeoutMs3(opts.timeout, 60000),
|
|
29403
30115
|
raw: Boolean(opts.raw)
|
|
29404
30116
|
});
|
|
29405
30117
|
}));
|
|
@@ -29417,7 +30129,7 @@ async function runInvoke(args) {
|
|
|
29417
30129
|
});
|
|
29418
30130
|
if (!resolved.ok) {
|
|
29419
30131
|
printError(resolved.message);
|
|
29420
|
-
|
|
30132
|
+
throw new CliExitError(1);
|
|
29421
30133
|
}
|
|
29422
30134
|
const target = resolved.target;
|
|
29423
30135
|
const result = await invokeMcpTool({
|
|
@@ -29447,7 +30159,7 @@ function failOnArgsError(parsed) {
|
|
|
29447
30159
|
} else if (parsed.reason === "not-an-object") {
|
|
29448
30160
|
printError("--args must decode to a JSON object (not array / scalar).");
|
|
29449
30161
|
}
|
|
29450
|
-
|
|
30162
|
+
throw new CliExitError(1);
|
|
29451
30163
|
}
|
|
29452
30164
|
function renderInvokeResult(args) {
|
|
29453
30165
|
if (isJsonMode()) {
|
|
@@ -29457,10 +30169,10 @@ function renderInvokeResult(args) {
|
|
|
29457
30169
|
deploymentId: args.deploymentId,
|
|
29458
30170
|
accessMode: args.accessMode,
|
|
29459
30171
|
exchangedRuntimeToken: args.exchangedRuntimeToken,
|
|
29460
|
-
...
|
|
30172
|
+
...invokeResultToJson3(args.result)
|
|
29461
30173
|
});
|
|
29462
30174
|
if (!args.result.ok)
|
|
29463
|
-
|
|
30175
|
+
throw new CliExitError(1);
|
|
29464
30176
|
return;
|
|
29465
30177
|
}
|
|
29466
30178
|
if (!args.result.ok) {
|
|
@@ -29474,7 +30186,7 @@ function renderInvokeResult(args) {
|
|
|
29474
30186
|
const statusLine = args.result.status > 0 ? `HTTP ${args.result.status}` : "network error";
|
|
29475
30187
|
printError(`${statusLine} from deployment: ${args.result.message}`);
|
|
29476
30188
|
}
|
|
29477
|
-
|
|
30189
|
+
throw new CliExitError(1);
|
|
29478
30190
|
}
|
|
29479
30191
|
if (args.raw) {
|
|
29480
30192
|
printInfo(JSON.stringify(args.result.result, null, 2));
|
|
@@ -29488,7 +30200,7 @@ function renderInvokeResult(args) {
|
|
|
29488
30200
|
printWarn(`Tool reported isError=true. Inspect ${c.bold("--json")} or ${c.bold("--raw")} for the full envelope.`);
|
|
29489
30201
|
}
|
|
29490
30202
|
}
|
|
29491
|
-
function
|
|
30203
|
+
function invokeResultToJson3(result) {
|
|
29492
30204
|
if (result.ok) {
|
|
29493
30205
|
return {
|
|
29494
30206
|
ok: true,
|
|
@@ -29667,7 +30379,106 @@ function registerMarketplaceCommands(program2) {
|
|
|
29667
30379
|
}
|
|
29668
30380
|
}
|
|
29669
30381
|
printInfo("");
|
|
29670
|
-
printInfo(c.dim(
|
|
30382
|
+
printInfo(c.dim(` Install it: \`mcp marketplace install ${a.id}\` · Fork it: \`mcp marketplace fork ${a.id} --project <id>\``));
|
|
30383
|
+
}));
|
|
30384
|
+
marketplace.command("install <artifactId>").description("Install a published registry artifact (server or skill) into your organization").option("--org <organizationId>", "Organization ID").option("--semver <semver>", "Install a specific version (default: latest)").addHelpText("after", [
|
|
30385
|
+
"",
|
|
30386
|
+
"Notes:",
|
|
30387
|
+
" Paid listings must be purchased from the dashboard first. Private-org",
|
|
30388
|
+
" artifacts from another org require the Pro plan.",
|
|
30389
|
+
"",
|
|
30390
|
+
"Examples:",
|
|
30391
|
+
" $ mcp marketplace install rga_123",
|
|
30392
|
+
" $ mcp marketplace install rga_123 --semver 1.2.0"
|
|
30393
|
+
].join(`
|
|
30394
|
+
`)).action(runAction(async (artifactId, opts) => {
|
|
30395
|
+
const orgId = await resolveOrgId(opts.org);
|
|
30396
|
+
const data = await api.post("/api/v1/registry/install", {
|
|
30397
|
+
organizationId: orgId,
|
|
30398
|
+
artifactId,
|
|
30399
|
+
...opts.semver ? { version: opts.semver } : {}
|
|
30400
|
+
});
|
|
30401
|
+
if (isJsonMode()) {
|
|
30402
|
+
printJson(data);
|
|
30403
|
+
return;
|
|
30404
|
+
}
|
|
30405
|
+
const i = data.installation;
|
|
30406
|
+
printSuccess(`Installed ${c.bold(i.artifactType)} (v${i.version}) into your organization.`);
|
|
30407
|
+
printKeyValue({
|
|
30408
|
+
"installation id": i.id,
|
|
30409
|
+
type: i.artifactType,
|
|
30410
|
+
artifact: i.primaryArtifactId,
|
|
30411
|
+
version: i.version,
|
|
30412
|
+
status: i.status
|
|
30413
|
+
});
|
|
30414
|
+
}));
|
|
30415
|
+
marketplace.command("fork <artifactId>").description("Fork a registry artifact into one of your projects as an editable draft").requiredOption("--project <projectId>", "Project to fork the artifact into").option("--org <organizationId>", "Organization ID").addHelpText("after", [
|
|
30416
|
+
"",
|
|
30417
|
+
"Examples:",
|
|
30418
|
+
" $ mcp marketplace fork rga_123 --project proj_456"
|
|
30419
|
+
].join(`
|
|
30420
|
+
`)).action(runAction(async (artifactId, opts) => {
|
|
30421
|
+
const orgId = await resolveOrgId(opts.org);
|
|
30422
|
+
const data = await api.post("/api/v1/registry/fork", {
|
|
30423
|
+
organizationId: orgId,
|
|
30424
|
+
sourceArtifactId: artifactId,
|
|
30425
|
+
targetProjectId: opts.project
|
|
30426
|
+
});
|
|
30427
|
+
if (isJsonMode()) {
|
|
30428
|
+
printJson(data);
|
|
30429
|
+
return;
|
|
30430
|
+
}
|
|
30431
|
+
printSuccess(`Forked ${c.bold(data.fork.artifactType)} → ${data.fork.forkedId} (draft).`);
|
|
30432
|
+
printStep(data.fork.artifactType === "server" ? `Iterate: \`mcp dev\` then \`mcp servers deploy ${data.fork.forkedId} --wait\`.` : `Iterate on skill ${data.fork.forkedId} then \`mcp skills deploy\`.`);
|
|
30433
|
+
}));
|
|
30434
|
+
marketplace.command("publish <serverId|skillId>").description("Publish a deployed server or a versioned skill to the registry (requires org admin)").requiredOption("--type <type>", "Artifact type: server | skill").requiredOption("--project <projectId>", "The artifact's project ID").option("--semver <semver>", "Version to publish (required for servers; skills use their current version)").option("--visibility <visibility>", "public (marketplace) | private (your org only). Default public.", "public").option("--changelog <text>", "Optional changelog for this version").option("--org <organizationId>", "Organization ID").addHelpText("after", [
|
|
30435
|
+
"",
|
|
30436
|
+
"Notes:",
|
|
30437
|
+
" Servers must be successfully deployed before publishing. Publishing to",
|
|
30438
|
+
" the public marketplace or a private-org registry requires the Pro plan.",
|
|
30439
|
+
"",
|
|
30440
|
+
"Examples:",
|
|
30441
|
+
" $ mcp marketplace publish srv_123 --type server --project proj_1 --semver 1.0.0",
|
|
30442
|
+
" $ mcp marketplace publish skill_9 --type skill --project proj_1 --visibility private"
|
|
30443
|
+
].join(`
|
|
30444
|
+
`)).action(runAction(async (id, opts) => {
|
|
30445
|
+
const orgId = await resolveOrgId(opts.org);
|
|
30446
|
+
const type = opts.type.trim().toLowerCase();
|
|
30447
|
+
if (type !== "server" && type !== "skill") {
|
|
30448
|
+
throw new Error(`Invalid --type: ${opts.type}. Expected one of: server, skill.`);
|
|
30449
|
+
}
|
|
30450
|
+
const visibility = opts.visibility.trim().toLowerCase();
|
|
30451
|
+
if (visibility !== "public" && visibility !== "private") {
|
|
30452
|
+
throw new Error(`Invalid --visibility: ${opts.visibility}. Expected one of: public, private.`);
|
|
30453
|
+
}
|
|
30454
|
+
const access = visibility === "private" ? "privateOrg" : "public";
|
|
30455
|
+
const body = {
|
|
30456
|
+
organizationId: orgId,
|
|
30457
|
+
artifactType: type,
|
|
30458
|
+
projectId: opts.project,
|
|
30459
|
+
access,
|
|
30460
|
+
...opts.changelog ? { changelog: opts.changelog } : {}
|
|
30461
|
+
};
|
|
30462
|
+
if (type === "server") {
|
|
30463
|
+
if (!opts.semver) {
|
|
30464
|
+
throw new Error("--semver <semver> is required when --type server.");
|
|
30465
|
+
}
|
|
30466
|
+
body["serverId"] = id;
|
|
30467
|
+
body["semver"] = opts.semver;
|
|
30468
|
+
} else {
|
|
30469
|
+
body["skillId"] = id;
|
|
30470
|
+
}
|
|
30471
|
+
const data = await api.post("/api/v1/registry/publish", body);
|
|
30472
|
+
if (isJsonMode()) {
|
|
30473
|
+
printJson(data);
|
|
30474
|
+
return;
|
|
30475
|
+
}
|
|
30476
|
+
printSuccess(`Published ${c.bold(data.artifact.name)} to the ${data.artifact.access === "public" ? "marketplace" : "private registry"}.`);
|
|
30477
|
+
printKeyValue({
|
|
30478
|
+
"artifact id": data.artifact.id,
|
|
30479
|
+
name: data.artifact.name,
|
|
30480
|
+
access: data.artifact.access
|
|
30481
|
+
});
|
|
29671
30482
|
}));
|
|
29672
30483
|
}
|
|
29673
30484
|
|
|
@@ -29700,43 +30511,349 @@ function registerOAuthCommands(program2) {
|
|
|
29700
30511
|
updated: formatDate(c2.updatedAt)
|
|
29701
30512
|
});
|
|
29702
30513
|
}));
|
|
30514
|
+
oauth.command("connections").description("List OAuth provider connections across the organization").option("--org <organizationId>", "Organization ID").option("--status <status>", "Filter: all | connected | expired | error | revoked (default connected)", "connected").option("--server <serverId>", "Restrict to one server").addHelpText("after", [
|
|
30515
|
+
"",
|
|
30516
|
+
"Examples:",
|
|
30517
|
+
" $ mcp oauth connections",
|
|
30518
|
+
" $ mcp oauth connections --status all",
|
|
30519
|
+
" $ mcp --json oauth connections | jq '.connections[].id'"
|
|
30520
|
+
].join(`
|
|
30521
|
+
`)).action(runAction(async (opts) => {
|
|
30522
|
+
const orgId = await resolveOrgId(opts.org);
|
|
30523
|
+
const params = {
|
|
30524
|
+
organizationId: orgId,
|
|
30525
|
+
status: opts.status
|
|
30526
|
+
};
|
|
30527
|
+
if (opts.server)
|
|
30528
|
+
params["serverId"] = opts.server;
|
|
30529
|
+
const data = await api.get("/api/v1/oauth/connections", params);
|
|
30530
|
+
printList(data.connections.map((conn) => ({
|
|
30531
|
+
id: conn.id,
|
|
30532
|
+
provider: conn.providerKey,
|
|
30533
|
+
server: conn.serverName ?? "—",
|
|
30534
|
+
status: conn.status,
|
|
30535
|
+
scopes: conn.scopes.length,
|
|
30536
|
+
updated: formatDate(conn.updatedAt)
|
|
30537
|
+
})), [
|
|
30538
|
+
{ key: "id", label: "ID", width: 22 },
|
|
30539
|
+
{ key: "provider", label: "Provider", width: 16 },
|
|
30540
|
+
{ key: "server", label: "Server", width: 22 },
|
|
30541
|
+
{ key: "status", label: "Status", width: 12 },
|
|
30542
|
+
{ key: "scopes", label: "Scopes", width: 8 },
|
|
30543
|
+
{ key: "updated", label: "Updated", width: 22 }
|
|
30544
|
+
], data);
|
|
30545
|
+
}));
|
|
30546
|
+
}
|
|
30547
|
+
|
|
30548
|
+
// src/commands/usage.ts
|
|
30549
|
+
var MAX_WINDOW_DAYS = 90;
|
|
30550
|
+
function parseWindowDays(raw) {
|
|
30551
|
+
if (!raw)
|
|
30552
|
+
return 30;
|
|
30553
|
+
const trimmed = raw.trim().toLowerCase();
|
|
30554
|
+
const match = /^(\d+)\s*(d|w|h)?$/.exec(trimmed);
|
|
30555
|
+
if (!match) {
|
|
30556
|
+
throw new Error(`Invalid --since: ${raw}. Expected a window like 30d, 4w, or a day count.`);
|
|
30557
|
+
}
|
|
30558
|
+
const value = Number.parseInt(match[1], 10);
|
|
30559
|
+
const unit = match[2] ?? "d";
|
|
30560
|
+
let days;
|
|
30561
|
+
if (unit === "w")
|
|
30562
|
+
days = value * 7;
|
|
30563
|
+
else if (unit === "h")
|
|
30564
|
+
days = Math.ceil(value / 24);
|
|
30565
|
+
else
|
|
30566
|
+
days = value;
|
|
30567
|
+
if (days < 1)
|
|
30568
|
+
days = 1;
|
|
30569
|
+
if (days > MAX_WINDOW_DAYS)
|
|
30570
|
+
days = MAX_WINDOW_DAYS;
|
|
30571
|
+
return days;
|
|
30572
|
+
}
|
|
30573
|
+
function formatMicros(micros) {
|
|
30574
|
+
if (micros == null)
|
|
30575
|
+
return "—";
|
|
30576
|
+
return `$${(micros / 1e6).toFixed(2)}`;
|
|
30577
|
+
}
|
|
30578
|
+
function registerUsageCommands(program2) {
|
|
30579
|
+
const usage = program2.command("usage").description("Show organization usage (tool-call volume) and credit balance from the terminal").option("--org <organizationId>", "Organization ID").option("--since <window>", "Aggregation window: 30d, 4w, or a day count (1–90, default 30d)").addHelpText("after", [
|
|
30580
|
+
"",
|
|
30581
|
+
"Examples:",
|
|
30582
|
+
" $ mcp usage # last 30 days of request volume + credit balance",
|
|
30583
|
+
" $ mcp usage --since 7d",
|
|
30584
|
+
" $ mcp --json usage | jq '.usage.totals.requestCount'",
|
|
30585
|
+
" $ mcp usage ledger --limit 20 # recent credit ledger entries"
|
|
30586
|
+
].join(`
|
|
30587
|
+
`)).action(runAction(async (opts) => {
|
|
30588
|
+
const orgId = await resolveOrgId(opts.org);
|
|
30589
|
+
const windowDays = parseWindowDays(opts.since);
|
|
30590
|
+
const [usageData, creditsData] = await Promise.all([
|
|
30591
|
+
api.get("/api/v1/billing/usage", {
|
|
30592
|
+
organizationId: orgId,
|
|
30593
|
+
windowDays: String(windowDays)
|
|
30594
|
+
}),
|
|
30595
|
+
api.get("/api/v1/credits/ledger", {
|
|
30596
|
+
organizationId: orgId,
|
|
30597
|
+
limit: "1"
|
|
30598
|
+
})
|
|
30599
|
+
]);
|
|
30600
|
+
if (isJsonMode()) {
|
|
30601
|
+
printJson({
|
|
30602
|
+
organization: usageData.organization,
|
|
30603
|
+
usage: usageData.usage,
|
|
30604
|
+
account: creditsData.account
|
|
30605
|
+
});
|
|
30606
|
+
return;
|
|
30607
|
+
}
|
|
30608
|
+
const t = usageData.usage.totals;
|
|
30609
|
+
printKeyValue({
|
|
30610
|
+
organization: `${usageData.organization.name ?? "—"} (${usageData.organization.id})`,
|
|
30611
|
+
plan: usageData.organization.plan ?? "—",
|
|
30612
|
+
window: `${usageData.usage.windowDays}d (since ${formatDate(usageData.usage.windowStartedAt)})`,
|
|
30613
|
+
requests: t.requestCount,
|
|
30614
|
+
allowed: t.allowedCount,
|
|
30615
|
+
"rate-limited": t.rateLimitedCount,
|
|
30616
|
+
unauthorized: t.unauthorizedCount,
|
|
30617
|
+
failed: t.failedCount,
|
|
30618
|
+
"avg cpu ms": usageData.usage.averageCpuTimeMs == null ? "—" : usageData.usage.averageCpuTimeMs.toFixed(1),
|
|
30619
|
+
"avg duration ms": usageData.usage.averageDurationMs == null ? "—" : usageData.usage.averageDurationMs.toFixed(1),
|
|
30620
|
+
"credit balance": formatMicros(creditsData.account?.balanceMicros),
|
|
30621
|
+
"credits granted": formatMicros(creditsData.account?.totalGrantedMicros),
|
|
30622
|
+
"credits debited": formatMicros(creditsData.account?.totalDebitedMicros)
|
|
30623
|
+
});
|
|
30624
|
+
}));
|
|
30625
|
+
usage.command("ledger").description("List organization credit ledger entries (most recent first)").option("--org <organizationId>", "Organization ID").option("--limit <n>", "Maximum entries (1–100, default 50)", "50").option("--cursor <createdAtMs>", "Pagination cursor: the createdAt ms from a previous nextCursor").action(runAction(async (opts) => {
|
|
30626
|
+
const orgId = await resolveOrgId(opts.org);
|
|
30627
|
+
const params = {
|
|
30628
|
+
organizationId: orgId,
|
|
30629
|
+
limit: opts.limit
|
|
30630
|
+
};
|
|
30631
|
+
if (opts.cursor)
|
|
30632
|
+
params["cursor"] = opts.cursor;
|
|
30633
|
+
const data = await api.get("/api/v1/credits/ledger", params);
|
|
30634
|
+
printList(data.entries.map((entry) => ({
|
|
30635
|
+
id: entry.id,
|
|
30636
|
+
kind: entry.entryKind,
|
|
30637
|
+
amount: formatMicros(entry.amountMicros),
|
|
30638
|
+
balance: formatMicros(entry.balanceAfterMicros),
|
|
30639
|
+
source: entry.source ?? "—",
|
|
30640
|
+
note: entry.note ?? "—",
|
|
30641
|
+
created: formatDate(entry.createdAt)
|
|
30642
|
+
})), [
|
|
30643
|
+
{ key: "id", label: "ID", width: 22 },
|
|
30644
|
+
{ key: "kind", label: "Kind", width: 14 },
|
|
30645
|
+
{ key: "amount", label: "Amount", width: 12 },
|
|
30646
|
+
{ key: "balance", label: "Balance", width: 12 },
|
|
30647
|
+
{ key: "source", label: "Source", width: 16 },
|
|
30648
|
+
{ key: "note", label: "Note", width: 24 },
|
|
30649
|
+
{ key: "created", label: "Created", width: 22 }
|
|
30650
|
+
], data);
|
|
30651
|
+
}));
|
|
30652
|
+
}
|
|
30653
|
+
|
|
30654
|
+
// src/commands/metrics.ts
|
|
30655
|
+
function formatPercent(rate) {
|
|
30656
|
+
if (rate == null)
|
|
30657
|
+
return "—";
|
|
30658
|
+
return `${(rate * 100).toFixed(2)}%`;
|
|
30659
|
+
}
|
|
30660
|
+
function formatMs(value) {
|
|
30661
|
+
if (value == null)
|
|
30662
|
+
return "—";
|
|
30663
|
+
return `${value.toFixed(1)}ms`;
|
|
30664
|
+
}
|
|
30665
|
+
function registerMetricsCommands(program2) {
|
|
30666
|
+
const metrics = program2.command("metrics").description("Aggregate deployment metrics (request volume, error rate)");
|
|
30667
|
+
metrics.command("deployment <deploymentId>").description("Show aggregate request metrics for a deployment over a window (CI deploy gate)").option("--org <organizationId>", "Organization ID").option("--since <window>", "Aggregation window: 24h, 7d, 4w, or a day count (1–90, default 7d)").option("--max-error-rate <ratio>", "Exit non-zero if the error rate exceeds this fraction (e.g. 0.05). For CI gates.").addHelpText("after", [
|
|
30668
|
+
"",
|
|
30669
|
+
"Exit codes:",
|
|
30670
|
+
" 0 metrics fetched (and error rate within --max-error-rate if set)",
|
|
30671
|
+
" 1 error rate exceeded --max-error-rate",
|
|
30672
|
+
"",
|
|
30673
|
+
"Examples:",
|
|
30674
|
+
" $ mcp metrics deployment dep_123 --since 24h",
|
|
30675
|
+
" $ mcp metrics deployment dep_123 --since 7d --max-error-rate 0.02",
|
|
30676
|
+
" $ mcp --json metrics deployment dep_123 | jq '.metrics.errorRate'"
|
|
30677
|
+
].join(`
|
|
30678
|
+
`)).action(runAction(async (deploymentId, opts) => {
|
|
30679
|
+
const orgId = await resolveOrgId(opts.org);
|
|
30680
|
+
const windowDays = parseWindowDays(opts.since);
|
|
30681
|
+
const data = await api.get("/api/v1/deployment/metrics", {
|
|
30682
|
+
organizationId: orgId,
|
|
30683
|
+
deploymentId,
|
|
30684
|
+
windowDays: String(windowDays)
|
|
30685
|
+
});
|
|
30686
|
+
let maxErrorRate = null;
|
|
30687
|
+
if (opts.maxErrorRate !== undefined) {
|
|
30688
|
+
const parsed = Number(opts.maxErrorRate);
|
|
30689
|
+
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
|
|
30690
|
+
throw new Error(`Invalid --max-error-rate: ${opts.maxErrorRate}. Expected a fraction between 0 and 1.`);
|
|
30691
|
+
}
|
|
30692
|
+
maxErrorRate = parsed;
|
|
30693
|
+
}
|
|
30694
|
+
const errorRate = data.metrics.errorRate;
|
|
30695
|
+
const breached = maxErrorRate !== null && errorRate !== null && errorRate > maxErrorRate;
|
|
30696
|
+
if (isJsonMode()) {
|
|
30697
|
+
printJson(data);
|
|
30698
|
+
if (breached)
|
|
30699
|
+
throw new CliExitError(1);
|
|
30700
|
+
return;
|
|
30701
|
+
}
|
|
30702
|
+
const m2 = data.metrics;
|
|
30703
|
+
printKeyValue({
|
|
30704
|
+
deployment: `${data.deployment.serverName} (${data.deployment.id})`,
|
|
30705
|
+
window: `${m2.windowDays}d (since ${formatDate(m2.windowStartedAt)})`,
|
|
30706
|
+
requests: m2.totals.requestCount,
|
|
30707
|
+
allowed: m2.totals.allowedCount,
|
|
30708
|
+
"rate-limited": m2.totals.rateLimitedCount,
|
|
30709
|
+
unauthorized: m2.totals.unauthorizedCount,
|
|
30710
|
+
failed: m2.totals.failedCount,
|
|
30711
|
+
"error rate": formatPercent(errorRate),
|
|
30712
|
+
"avg duration": formatMs(m2.averageDurationMs),
|
|
30713
|
+
"avg cpu": formatMs(m2.averageCpuTimeMs),
|
|
30714
|
+
"last event": m2.lastEventAt ? formatDate(m2.lastEventAt) : "—"
|
|
30715
|
+
});
|
|
30716
|
+
if (breached)
|
|
30717
|
+
throw new CliExitError(1);
|
|
30718
|
+
}));
|
|
30719
|
+
}
|
|
30720
|
+
|
|
30721
|
+
// src/commands/orgs.ts
|
|
30722
|
+
var ACCESS_STATES = ["active", "disabled", "all"];
|
|
30723
|
+
function registerOrgCommands(program2) {
|
|
30724
|
+
const orgs = program2.command("orgs").description("List and inspect the organizations you belong to");
|
|
30725
|
+
orgs.command("list").description("List organizations you can access").addHelpText("after", [
|
|
30726
|
+
"",
|
|
30727
|
+
"Examples:",
|
|
30728
|
+
" $ mcp orgs list",
|
|
30729
|
+
" $ mcp --json orgs list | jq '.organizations[].id'"
|
|
30730
|
+
].join(`
|
|
30731
|
+
`)).action(runAction(async () => {
|
|
30732
|
+
const data = await api.get("/api/v1/organizations");
|
|
30733
|
+
printList(data.organizations.map((org) => ({
|
|
30734
|
+
id: org.id,
|
|
30735
|
+
name: org.name ?? "—",
|
|
30736
|
+
slug: org.slug ?? "—",
|
|
30737
|
+
plan: org.plan ?? "—",
|
|
30738
|
+
role: org.role,
|
|
30739
|
+
default: org.isDefault || org.id === data.defaultOrganizationId ? "*" : ""
|
|
30740
|
+
})), [
|
|
30741
|
+
{ key: "id", label: "ID", width: 22 },
|
|
30742
|
+
{ key: "name", label: "Name", width: 24 },
|
|
30743
|
+
{ key: "slug", label: "Slug", width: 18 },
|
|
30744
|
+
{ key: "plan", label: "Plan", width: 12 },
|
|
30745
|
+
{ key: "role", label: "Role", width: 10 },
|
|
30746
|
+
{ key: "default", label: "Default", width: 7 }
|
|
30747
|
+
], data);
|
|
30748
|
+
}));
|
|
30749
|
+
orgs.command("get [organizationId]").description("Show one organization (defaults to your active org)").action(runAction(async (organizationId) => {
|
|
30750
|
+
const orgId = await resolveOrgId(organizationId);
|
|
30751
|
+
const data = await api.get("/api/v1/organization", {
|
|
30752
|
+
organizationId: orgId
|
|
30753
|
+
});
|
|
30754
|
+
if (isJsonMode()) {
|
|
30755
|
+
printJson(data);
|
|
30756
|
+
return;
|
|
30757
|
+
}
|
|
30758
|
+
const org = data.organization;
|
|
30759
|
+
printKeyValue({
|
|
30760
|
+
id: org.id,
|
|
30761
|
+
name: org.name ?? "—",
|
|
30762
|
+
slug: org.slug ?? "—",
|
|
30763
|
+
plan: org.plan ?? "—",
|
|
30764
|
+
"your role": org.role
|
|
30765
|
+
});
|
|
30766
|
+
}));
|
|
30767
|
+
orgs.command("members [organizationId]").description("List members of an organization").option("--access-state <state>", `Filter: ${ACCESS_STATES.join(" | ")} (default active)`, "active").option("--limit <n>", "Maximum members (1–100, default 50)", "50").option("--cursor <cursor>", "Pagination cursor from a previous nextCursor").action(runAction(async (organizationId, opts) => {
|
|
30768
|
+
const orgId = await resolveOrgId(organizationId);
|
|
30769
|
+
const params = {
|
|
30770
|
+
organizationId: orgId,
|
|
30771
|
+
accessState: opts.accessState,
|
|
30772
|
+
limit: opts.limit
|
|
30773
|
+
};
|
|
30774
|
+
if (opts.cursor)
|
|
30775
|
+
params["cursor"] = opts.cursor;
|
|
30776
|
+
const data = await api.get("/api/v1/organization/members", params);
|
|
30777
|
+
printList(data.members.map((member) => ({
|
|
30778
|
+
userId: member.userId,
|
|
30779
|
+
name: member.name ?? "—",
|
|
30780
|
+
email: member.email ?? "—",
|
|
30781
|
+
role: member.role,
|
|
30782
|
+
access: member.accessState
|
|
30783
|
+
})), [
|
|
30784
|
+
{ key: "userId", label: "User ID", width: 22 },
|
|
30785
|
+
{ key: "name", label: "Name", width: 20 },
|
|
30786
|
+
{ key: "email", label: "Email", width: 28 },
|
|
30787
|
+
{ key: "role", label: "Role", width: 10 },
|
|
30788
|
+
{ key: "access", label: "Access", width: 10 }
|
|
30789
|
+
], data);
|
|
30790
|
+
}));
|
|
30791
|
+
orgs.command("usage [organizationId]").description("Show request-volume usage for an organization").option("--since <window>", "Aggregation window: 30d, 4w, or a day count (1–90, default 30d)").action(runAction(async (organizationId, opts) => {
|
|
30792
|
+
const orgId = await resolveOrgId(organizationId);
|
|
30793
|
+
const windowDays = parseWindowDays(opts.since);
|
|
30794
|
+
const data = await api.get("/api/v1/organization/usage", { organizationId: orgId, windowDays: String(windowDays) });
|
|
30795
|
+
if (isJsonMode()) {
|
|
30796
|
+
printJson(data);
|
|
30797
|
+
return;
|
|
30798
|
+
}
|
|
30799
|
+
const t = data.usage.totals;
|
|
30800
|
+
printKeyValue({
|
|
30801
|
+
organization: `${data.organization.name ?? "—"} (${data.organization.id})`,
|
|
30802
|
+
window: `${data.usage.windowDays}d (since ${formatDate(data.usage.windowStartedAt)})`,
|
|
30803
|
+
requests: t.requestCount,
|
|
30804
|
+
allowed: t.allowedCount,
|
|
30805
|
+
"rate-limited": t.rateLimitedCount,
|
|
30806
|
+
unauthorized: t.unauthorizedCount,
|
|
30807
|
+
failed: t.failedCount
|
|
30808
|
+
});
|
|
30809
|
+
}));
|
|
30810
|
+
orgs.command("use <organizationId>").description("Set the default organization for this profile (discoverable alias of `mcp config set-org`)").action(runAction((organizationId) => {
|
|
30811
|
+
const config = readConfig();
|
|
30812
|
+
config.defaultOrganizationId = organizationId;
|
|
30813
|
+
writeConfig(config);
|
|
30814
|
+
if (isJsonMode()) {
|
|
30815
|
+
printJson({ defaultOrganizationId: organizationId });
|
|
30816
|
+
return;
|
|
30817
|
+
}
|
|
30818
|
+
printSuccess(`Default organization set to ${organizationId}`);
|
|
30819
|
+
}));
|
|
29703
30820
|
}
|
|
29704
30821
|
|
|
29705
30822
|
// src/lib/plugins.ts
|
|
29706
30823
|
import { spawn as spawn7 } from "node:child_process";
|
|
29707
30824
|
import {
|
|
29708
|
-
existsSync as
|
|
29709
|
-
mkdirSync as
|
|
30825
|
+
existsSync as existsSync36,
|
|
30826
|
+
mkdirSync as mkdirSync17,
|
|
29710
30827
|
readdirSync as readdirSync4,
|
|
29711
|
-
readFileSync as
|
|
30828
|
+
readFileSync as readFileSync20,
|
|
29712
30829
|
rmSync as rmSync3,
|
|
29713
30830
|
statSync as statSync7
|
|
29714
30831
|
} from "node:fs";
|
|
29715
|
-
import { join as
|
|
30832
|
+
import { join as join31 } from "node:path";
|
|
29716
30833
|
import { pathToFileURL } from "node:url";
|
|
29717
30834
|
function pluginsDir() {
|
|
29718
|
-
return
|
|
30835
|
+
return join31(configDir(), "plugins");
|
|
29719
30836
|
}
|
|
29720
30837
|
function ensureDir2() {
|
|
29721
30838
|
const dir = pluginsDir();
|
|
29722
|
-
if (!
|
|
29723
|
-
|
|
30839
|
+
if (!existsSync36(dir))
|
|
30840
|
+
mkdirSync17(dir, { recursive: true, mode: 448 });
|
|
29724
30841
|
}
|
|
29725
30842
|
function readManifestFromPackageDir(packageDir) {
|
|
29726
|
-
const pkgPath =
|
|
29727
|
-
if (!
|
|
30843
|
+
const pkgPath = join31(packageDir, "package.json");
|
|
30844
|
+
if (!existsSync36(pkgPath))
|
|
29728
30845
|
return null;
|
|
29729
30846
|
let pkg;
|
|
29730
30847
|
try {
|
|
29731
|
-
pkg = JSON.parse(
|
|
30848
|
+
pkg = JSON.parse(readFileSync20(pkgPath, "utf-8"));
|
|
29732
30849
|
} catch {
|
|
29733
30850
|
return null;
|
|
29734
30851
|
}
|
|
29735
30852
|
if (!pkg.name)
|
|
29736
30853
|
return null;
|
|
29737
|
-
const relEntry = pkg.mcpsh?.register ?? pkg.main ?? "index.js";
|
|
29738
|
-
const entry =
|
|
29739
|
-
if (!
|
|
30854
|
+
const relEntry = pkg.mcpsh?.register ?? pkg.mcp?.register ?? pkg.main ?? "index.js";
|
|
30855
|
+
const entry = join31(packageDir, relEntry);
|
|
30856
|
+
if (!existsSync36(entry))
|
|
29740
30857
|
return null;
|
|
29741
30858
|
return {
|
|
29742
30859
|
name: pkg.name,
|
|
@@ -29747,11 +30864,11 @@ function readManifestFromPackageDir(packageDir) {
|
|
|
29747
30864
|
}
|
|
29748
30865
|
function listInstalledPlugins() {
|
|
29749
30866
|
const dir = pluginsDir();
|
|
29750
|
-
if (!
|
|
30867
|
+
if (!existsSync36(dir))
|
|
29751
30868
|
return [];
|
|
29752
30869
|
const out = [];
|
|
29753
30870
|
for (const entry of readdirSync4(dir)) {
|
|
29754
|
-
const full =
|
|
30871
|
+
const full = join31(dir, entry);
|
|
29755
30872
|
let s;
|
|
29756
30873
|
try {
|
|
29757
30874
|
s = statSync7(full);
|
|
@@ -29762,7 +30879,7 @@ function listInstalledPlugins() {
|
|
|
29762
30879
|
continue;
|
|
29763
30880
|
if (entry.startsWith("@")) {
|
|
29764
30881
|
for (const child of readdirSync4(full)) {
|
|
29765
|
-
const m2 = readManifestFromPackageDir(
|
|
30882
|
+
const m2 = readManifestFromPackageDir(join31(full, child));
|
|
29766
30883
|
if (m2)
|
|
29767
30884
|
out.push(m2);
|
|
29768
30885
|
}
|
|
@@ -29797,15 +30914,15 @@ async function loadPlugins(program2) {
|
|
|
29797
30914
|
}
|
|
29798
30915
|
var PACKAGE_SPEC_PATTERN = /^(@[a-z0-9~-][\w.~-]*\/)?[a-z0-9~-][\w.~-]*(@[-\w.^~><=*+|]+)?$/i;
|
|
29799
30916
|
function runNpm(args, cwd) {
|
|
29800
|
-
return new Promise((
|
|
30917
|
+
return new Promise((resolve9) => {
|
|
29801
30918
|
const isWindows = process.platform === "win32";
|
|
29802
30919
|
const child = spawn7(isWindows ? "npm.cmd" : "npm", args, {
|
|
29803
30920
|
shell: isWindows,
|
|
29804
30921
|
cwd,
|
|
29805
30922
|
stdio: "inherit"
|
|
29806
30923
|
});
|
|
29807
|
-
child.on("close", (code) =>
|
|
29808
|
-
child.on("error", () =>
|
|
30924
|
+
child.on("close", (code) => resolve9(code ?? 1));
|
|
30925
|
+
child.on("error", () => resolve9(1));
|
|
29809
30926
|
});
|
|
29810
30927
|
}
|
|
29811
30928
|
async function installPlugin(packageSpec) {
|
|
@@ -29826,12 +30943,12 @@ async function installPlugin(packageSpec) {
|
|
|
29826
30943
|
if (code !== 0) {
|
|
29827
30944
|
throw new Error(`npm install ${packageSpec} exited with code ${code}.`);
|
|
29828
30945
|
}
|
|
29829
|
-
const nm =
|
|
29830
|
-
if (!
|
|
30946
|
+
const nm = join31(dir, "node_modules");
|
|
30947
|
+
if (!existsSync36(nm)) {
|
|
29831
30948
|
throw new Error(`npm install ran but produced no node_modules under ${dir}.`);
|
|
29832
30949
|
}
|
|
29833
30950
|
const baseName = packageSpec.replace(/@[^@/]+$/, "");
|
|
29834
|
-
const candidatePath = baseName.startsWith("@") ?
|
|
30951
|
+
const candidatePath = baseName.startsWith("@") ? join31(nm, baseName.split("/")[0], baseName.split("/")[1] ?? "") : join31(nm, baseName);
|
|
29835
30952
|
const manifest = readManifestFromPackageDir(candidatePath);
|
|
29836
30953
|
if (!manifest) {
|
|
29837
30954
|
throw new Error(`Installed but could not read plugin manifest at ${candidatePath}.`);
|
|
@@ -29839,26 +30956,26 @@ async function installPlugin(packageSpec) {
|
|
|
29839
30956
|
return { name: manifest.name, entry: manifest.entry };
|
|
29840
30957
|
}
|
|
29841
30958
|
function removePlugin(name) {
|
|
29842
|
-
const nm =
|
|
29843
|
-
if (!
|
|
30959
|
+
const nm = join31(pluginsDir(), "node_modules");
|
|
30960
|
+
if (!existsSync36(nm))
|
|
29844
30961
|
return false;
|
|
29845
|
-
const target = name.startsWith("@") ?
|
|
29846
|
-
if (!
|
|
30962
|
+
const target = name.startsWith("@") ? join31(nm, name.split("/")[0], name.split("/")[1] ?? "") : join31(nm, name);
|
|
30963
|
+
if (!existsSync36(target))
|
|
29847
30964
|
return false;
|
|
29848
30965
|
rmSync3(target, { recursive: true, force: true });
|
|
29849
30966
|
return true;
|
|
29850
30967
|
}
|
|
29851
30968
|
function listInstalledPluginsCombined() {
|
|
29852
30969
|
const direct = listInstalledPlugins();
|
|
29853
|
-
const nm =
|
|
29854
|
-
if (!
|
|
30970
|
+
const nm = join31(pluginsDir(), "node_modules");
|
|
30971
|
+
if (!existsSync36(nm))
|
|
29855
30972
|
return direct;
|
|
29856
30973
|
const seen = new Set(direct.map((p2) => p2.name));
|
|
29857
30974
|
const out = [...direct];
|
|
29858
30975
|
for (const entry of readdirSync4(nm)) {
|
|
29859
30976
|
if (entry === ".bin" || entry === ".package-lock.json")
|
|
29860
30977
|
continue;
|
|
29861
|
-
const full =
|
|
30978
|
+
const full = join31(nm, entry);
|
|
29862
30979
|
let s;
|
|
29863
30980
|
try {
|
|
29864
30981
|
s = statSync7(full);
|
|
@@ -29869,7 +30986,7 @@ function listInstalledPluginsCombined() {
|
|
|
29869
30986
|
continue;
|
|
29870
30987
|
if (entry.startsWith("@")) {
|
|
29871
30988
|
for (const child of readdirSync4(full)) {
|
|
29872
|
-
const m2 = readManifestFromPackageDir(
|
|
30989
|
+
const m2 = readManifestFromPackageDir(join31(full, child));
|
|
29873
30990
|
if (m2 && !seen.has(m2.name)) {
|
|
29874
30991
|
seen.add(m2.name);
|
|
29875
30992
|
out.push(m2);
|
|
@@ -29899,7 +31016,7 @@ function registerPluginCommands(program2) {
|
|
|
29899
31016
|
" {",
|
|
29900
31017
|
' "name": "mcpsh-plugin-foo",',
|
|
29901
31018
|
' "main": "index.js",',
|
|
29902
|
-
' "
|
|
31019
|
+
' "mcpsh": { "register": "index.js" }',
|
|
29903
31020
|
" }",
|
|
29904
31021
|
"",
|
|
29905
31022
|
" // index.js",
|
|
@@ -29908,7 +31025,11 @@ function registerPluginCommands(program2) {
|
|
|
29908
31025
|
" const orgs = await api.get('/api/v1/organizations')",
|
|
29909
31026
|
" console.log(orgs)",
|
|
29910
31027
|
" })",
|
|
29911
|
-
" }"
|
|
31028
|
+
" }",
|
|
31029
|
+
"",
|
|
31030
|
+
"Trust model: plugins run IN-PROCESS with the CLI — same API client, same",
|
|
31031
|
+
"credentials, same filesystem access. Install only plugins you trust.",
|
|
31032
|
+
' (The legacy "mcp.register" key is still accepted for older plugins.)'
|
|
29912
31033
|
].join(`
|
|
29913
31034
|
`));
|
|
29914
31035
|
plugin.command("install <package>").description("Install a plugin from npm (uses npm under the hood)").addHelpText("after", [
|
|
@@ -30325,7 +31446,7 @@ function clipboardCandidatesFor(platform7, waylandDisplay) {
|
|
|
30325
31446
|
return linux;
|
|
30326
31447
|
}
|
|
30327
31448
|
async function defaultIsExecutable(binary) {
|
|
30328
|
-
return await new Promise((
|
|
31449
|
+
return await new Promise((resolve9) => {
|
|
30329
31450
|
const isWin = process.platform === "win32";
|
|
30330
31451
|
const cmd = isWin ? "where" : "command";
|
|
30331
31452
|
const args = isWin ? [binary] : ["-v", binary];
|
|
@@ -30333,8 +31454,8 @@ async function defaultIsExecutable(binary) {
|
|
|
30333
31454
|
shell: !isWin,
|
|
30334
31455
|
stdio: "ignore"
|
|
30335
31456
|
});
|
|
30336
|
-
child.on("error", () =>
|
|
30337
|
-
child.on("exit", (code) =>
|
|
31457
|
+
child.on("error", () => resolve9(false));
|
|
31458
|
+
child.on("exit", (code) => resolve9(code === 0));
|
|
30338
31459
|
});
|
|
30339
31460
|
}
|
|
30340
31461
|
async function detectClipboard(probe) {
|
|
@@ -30356,7 +31477,7 @@ async function copyToClipboard(text, probe) {
|
|
|
30356
31477
|
};
|
|
30357
31478
|
}
|
|
30358
31479
|
const spawnImpl = probe?.spawnImpl ?? spawn8;
|
|
30359
|
-
return await new Promise((
|
|
31480
|
+
return await new Promise((resolve9) => {
|
|
30360
31481
|
const child = spawnImpl(candidate.binary, candidate.args, {
|
|
30361
31482
|
stdio: ["pipe", "ignore", "pipe"]
|
|
30362
31483
|
});
|
|
@@ -30365,7 +31486,7 @@ async function copyToClipboard(text, probe) {
|
|
|
30365
31486
|
stderr += chunk.toString("utf-8");
|
|
30366
31487
|
});
|
|
30367
31488
|
child.on("error", (err) => {
|
|
30368
|
-
|
|
31489
|
+
resolve9({
|
|
30369
31490
|
kind: "failed",
|
|
30370
31491
|
binary: candidate.binary,
|
|
30371
31492
|
message: err instanceof Error ? err.message : String(err)
|
|
@@ -30373,9 +31494,9 @@ async function copyToClipboard(text, probe) {
|
|
|
30373
31494
|
});
|
|
30374
31495
|
child.on("exit", (code) => {
|
|
30375
31496
|
if (code === 0) {
|
|
30376
|
-
|
|
31497
|
+
resolve9({ kind: "ok", binary: candidate.binary });
|
|
30377
31498
|
} else {
|
|
30378
|
-
|
|
31499
|
+
resolve9({
|
|
30379
31500
|
kind: "failed",
|
|
30380
31501
|
binary: candidate.binary,
|
|
30381
31502
|
message: stderr.trim() || `${candidate.binary} exited with status ${code ?? "?"}`
|
|
@@ -30385,7 +31506,7 @@ async function copyToClipboard(text, probe) {
|
|
|
30385
31506
|
try {
|
|
30386
31507
|
child.stdin?.end(text);
|
|
30387
31508
|
} catch (err) {
|
|
30388
|
-
|
|
31509
|
+
resolve9({
|
|
30389
31510
|
kind: "failed",
|
|
30390
31511
|
binary: candidate.binary,
|
|
30391
31512
|
message: err instanceof Error ? err.message : String(err)
|
|
@@ -31455,12 +32576,12 @@ function displayValueFor(field, value) {
|
|
|
31455
32576
|
|
|
31456
32577
|
// src/lib/tui/state-store.ts
|
|
31457
32578
|
import {
|
|
31458
|
-
existsSync as
|
|
31459
|
-
mkdirSync as
|
|
31460
|
-
readFileSync as
|
|
31461
|
-
writeFileSync as
|
|
32579
|
+
existsSync as existsSync37,
|
|
32580
|
+
mkdirSync as mkdirSync18,
|
|
32581
|
+
readFileSync as readFileSync21,
|
|
32582
|
+
writeFileSync as writeFileSync20
|
|
31462
32583
|
} from "node:fs";
|
|
31463
|
-
import { join as
|
|
32584
|
+
import { join as join32 } from "node:path";
|
|
31464
32585
|
var ALL_TABS = [
|
|
31465
32586
|
"servers",
|
|
31466
32587
|
"projects",
|
|
@@ -31470,7 +32591,7 @@ var ALL_TABS = [
|
|
|
31470
32591
|
"devSessions"
|
|
31471
32592
|
];
|
|
31472
32593
|
function tuiStateFile() {
|
|
31473
|
-
return
|
|
32594
|
+
return join32(configDir(), "tui-state.json");
|
|
31474
32595
|
}
|
|
31475
32596
|
function isTab(v2) {
|
|
31476
32597
|
return typeof v2 === "string" && ALL_TABS.includes(v2);
|
|
@@ -31535,11 +32656,11 @@ function sanitizeTuiState(input) {
|
|
|
31535
32656
|
}
|
|
31536
32657
|
function readTuiState() {
|
|
31537
32658
|
const path = tuiStateFile();
|
|
31538
|
-
if (!
|
|
32659
|
+
if (!existsSync37(path))
|
|
31539
32660
|
return {};
|
|
31540
32661
|
let parsed;
|
|
31541
32662
|
try {
|
|
31542
|
-
parsed = JSON.parse(
|
|
32663
|
+
parsed = JSON.parse(readFileSync21(path, "utf-8"));
|
|
31543
32664
|
} catch {
|
|
31544
32665
|
return {};
|
|
31545
32666
|
}
|
|
@@ -31547,10 +32668,10 @@ function readTuiState() {
|
|
|
31547
32668
|
}
|
|
31548
32669
|
function writeTuiState(state) {
|
|
31549
32670
|
try {
|
|
31550
|
-
if (!
|
|
31551
|
-
|
|
32671
|
+
if (!existsSync37(configDir())) {
|
|
32672
|
+
mkdirSync18(configDir(), { recursive: true, mode: 448 });
|
|
31552
32673
|
}
|
|
31553
|
-
|
|
32674
|
+
writeFileSync20(tuiStateFile(), JSON.stringify(state, null, 2), {
|
|
31554
32675
|
encoding: "utf-8",
|
|
31555
32676
|
mode: 384
|
|
31556
32677
|
});
|
|
@@ -33555,7 +34676,7 @@ function registerUiCommand(program2) {
|
|
|
33555
34676
|
}
|
|
33556
34677
|
return false;
|
|
33557
34678
|
};
|
|
33558
|
-
await new Promise((
|
|
34679
|
+
await new Promise((resolve9) => {
|
|
33559
34680
|
const flushAndExit = () => {
|
|
33560
34681
|
try {
|
|
33561
34682
|
if (persistTimer) {
|
|
@@ -33565,7 +34686,7 @@ function registerUiCommand(program2) {
|
|
|
33565
34686
|
writeTuiState(snapshotForPersist());
|
|
33566
34687
|
} catch {}
|
|
33567
34688
|
shutdown(handles);
|
|
33568
|
-
|
|
34689
|
+
resolve9();
|
|
33569
34690
|
};
|
|
33570
34691
|
const executePaletteCommand = (id) => {
|
|
33571
34692
|
if (id.startsWith("tab:")) {
|
|
@@ -34174,7 +35295,7 @@ function registerUiCommand(program2) {
|
|
|
34174
35295
|
|
|
34175
35296
|
// src/commands/update.ts
|
|
34176
35297
|
import { spawn as spawn9 } from "node:child_process";
|
|
34177
|
-
import { dirname as
|
|
35298
|
+
import { dirname as dirname11 } from "node:path";
|
|
34178
35299
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
34179
35300
|
function detectInstallContext() {
|
|
34180
35301
|
const override = process.env["MCPSH_PACKAGE_MANAGER"]?.trim().toLowerCase();
|
|
@@ -34187,7 +35308,7 @@ function detectInstallContext() {
|
|
|
34187
35308
|
}
|
|
34188
35309
|
const here = (() => {
|
|
34189
35310
|
try {
|
|
34190
|
-
return
|
|
35311
|
+
return dirname11(fileURLToPath2(import.meta.url));
|
|
34191
35312
|
} catch {
|
|
34192
35313
|
return process.argv[1] ?? "";
|
|
34193
35314
|
}
|
|
@@ -34235,10 +35356,10 @@ function buildCommand(manager) {
|
|
|
34235
35356
|
}
|
|
34236
35357
|
}
|
|
34237
35358
|
function runShell(command) {
|
|
34238
|
-
return new Promise((
|
|
35359
|
+
return new Promise((resolve9) => {
|
|
34239
35360
|
const child = spawn9(command, { shell: true, stdio: "inherit" });
|
|
34240
|
-
child.on("close", (code) =>
|
|
34241
|
-
child.on("error", () =>
|
|
35361
|
+
child.on("close", (code) => resolve9(code ?? 1));
|
|
35362
|
+
child.on("error", () => resolve9(1));
|
|
34242
35363
|
});
|
|
34243
35364
|
}
|
|
34244
35365
|
function registerUpdateCommand(program2) {
|
|
@@ -34304,12 +35425,14 @@ function registerUpdateCommand(program2) {
|
|
|
34304
35425
|
// src/create-program.ts
|
|
34305
35426
|
function createProgram() {
|
|
34306
35427
|
const program2 = new Command;
|
|
34307
|
-
program2.name("mcp").description("The official CLI for MCPCloud — manage projects, MCP servers, skills, and API keys from the terminal").version(getCliVersion(), "-v, --version", "Print the CLI version").option("--json", "Output raw JSON (useful for scripting)").option("--base-url <url>", "API base URL for this invocation. Falls back to MCPCLOUD_BASE_URL or `mcp config set-url`.").option("--profile <name>", "Run against a named config profile for this invocation (overrides MCPCLOUD_PROFILE and the saved current profile).").option("--ci", "Force CI mode for this invocation. Auto-detected when CI=true. Disables color, swaps Unicode glyphs for ASCII, refuses interactive prompts, and always surfaces request IDs.").option("--warnings-as-errors", "Exit non-zero if any warning is emitted during the command (CI ergonomics).").option("--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) => {
|
|
35428
|
+
program2.name("mcp").description("The official CLI for MCPCloud — manage projects, MCP servers, skills, and API keys from the terminal").version(getCliVersion(), "-v, --version", "Print the CLI version").option("--json", "Output raw JSON (useful for scripting)").option("--base-url <url>", "API base URL for this invocation. Falls back to MCPCLOUD_BASE_URL or `mcp config set-url`.").option("--profile <name>", "Run against a named config profile for this invocation (overrides MCPCLOUD_PROFILE and the saved current profile).").option("--ci", "Force CI mode for this invocation. Auto-detected when CI=true. Disables color, swaps Unicode glyphs for ASCII, refuses interactive prompts, and always surfaces request IDs.").option("--warnings-as-errors", "Exit non-zero if any warning is emitted during the command (CI ergonomics).").option("--non-interactive", "Refuse interactive prompts (fail fast on missing input) without the color/glyph changes of --ci.").option("--quiet", "Suppress success/step/info chrome; data and errors still print. Pairs with exit codes for health/existence gates.").option("--format <mode>", "Output format for list commands: table (default), tsv, jsonl, json.").option("--no-table", "Shorthand for --format tsv. Tab-separated rows for awk/cut/grep pipelines.").option("--field <names>", "Comma-separated list of fields to include in list output (repeatable).", (value, prev) => prev ? [...prev, value] : [value]).option("--filter <key=substring>", "Substring filter for list rows (repeatable). Case-insensitive.", (value, prev) => prev ? [...prev, value] : [value]).option("--idempotency-key <key>", "Stable Idempotency-Key for mutations (1–255 ASCII chars, no whitespace). When omitted, the CLI auto-generates a UUID per invocation so internal retries on transient failures stay safe.").option("--debug", "Print every HTTP request/response with secrets redacted. Equivalent to MCPCLOUD_LOG=debug.").hook("preAction", (thisCommand) => {
|
|
34308
35429
|
const opts = thisCommand.optsWithGlobals();
|
|
34309
35430
|
setJsonMode(Boolean(opts.json));
|
|
34310
35431
|
setBaseUrlOverride(opts.baseUrl);
|
|
34311
35432
|
setProfileOverride(opts.profile);
|
|
34312
35433
|
setCiMode(Boolean(opts.ci) || isCiEnv());
|
|
35434
|
+
setNonInteractive(Boolean(opts.nonInteractive));
|
|
35435
|
+
setQuiet(Boolean(opts.quiet));
|
|
34313
35436
|
setWarningsAsErrors(Boolean(opts.warningsAsErrors));
|
|
34314
35437
|
setListFormat(opts.format);
|
|
34315
35438
|
setNoTable(opts.table === false);
|
|
@@ -34347,6 +35470,9 @@ function createProgram() {
|
|
|
34347
35470
|
registerSkillCommands(program2);
|
|
34348
35471
|
registerApiKeyCommands(program2);
|
|
34349
35472
|
registerDeploymentCommands(program2);
|
|
35473
|
+
registerOrgCommands(program2);
|
|
35474
|
+
registerUsageCommands(program2);
|
|
35475
|
+
registerMetricsCommands(program2);
|
|
34350
35476
|
registerDevCommand(program2);
|
|
34351
35477
|
registerCompletionCommand(program2);
|
|
34352
35478
|
registerDoctorCommand(program2);
|