@mcpcloud/cli 0.17.2 → 0.18.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/README.md +15 -1
- package/dist/index.js +945 -202
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5180,6 +5180,68 @@ async function resolveOrgId(orgId) {
|
|
|
5180
5180
|
return first.id;
|
|
5181
5181
|
}
|
|
5182
5182
|
|
|
5183
|
+
// src/lib/paginate.ts
|
|
5184
|
+
var MAX_PAGE_SIZE = 100;
|
|
5185
|
+
var MAX_PAGES = 200;
|
|
5186
|
+
async function fetchAllPages(path, params, key, options = {}) {
|
|
5187
|
+
const items = [];
|
|
5188
|
+
let cursor = null;
|
|
5189
|
+
for (let page = 0;page < MAX_PAGES; page += 1) {
|
|
5190
|
+
const remaining = options.max === undefined ? MAX_PAGE_SIZE : options.max - items.length;
|
|
5191
|
+
if (remaining <= 0)
|
|
5192
|
+
break;
|
|
5193
|
+
const data = await api.get(path, {
|
|
5194
|
+
...params,
|
|
5195
|
+
limit: String(Math.min(MAX_PAGE_SIZE, remaining)),
|
|
5196
|
+
...cursor ? { cursor } : {}
|
|
5197
|
+
});
|
|
5198
|
+
const rows = data[key];
|
|
5199
|
+
if (Array.isArray(rows))
|
|
5200
|
+
items.push(...rows);
|
|
5201
|
+
const next = typeof data.nextCursor === "string" ? data.nextCursor : null;
|
|
5202
|
+
if (data.hasMore !== true || !next)
|
|
5203
|
+
break;
|
|
5204
|
+
cursor = next;
|
|
5205
|
+
}
|
|
5206
|
+
return options.max === undefined ? items : items.slice(0, options.max);
|
|
5207
|
+
}
|
|
5208
|
+
async function fetchAllPagesWithEnvelope(path, params, key, options = {}) {
|
|
5209
|
+
let envelope = {};
|
|
5210
|
+
let captured = false;
|
|
5211
|
+
const rows = [];
|
|
5212
|
+
let cursor = null;
|
|
5213
|
+
for (let page = 0;page < MAX_PAGES; page += 1) {
|
|
5214
|
+
const remaining = options.max === undefined ? MAX_PAGE_SIZE : options.max - rows.length;
|
|
5215
|
+
if (remaining <= 0)
|
|
5216
|
+
break;
|
|
5217
|
+
const data = await api.get(path, {
|
|
5218
|
+
...params,
|
|
5219
|
+
limit: String(Math.min(MAX_PAGE_SIZE, remaining)),
|
|
5220
|
+
...cursor ? { cursor } : {}
|
|
5221
|
+
});
|
|
5222
|
+
if (!captured) {
|
|
5223
|
+
const { nextCursor: _n, hasMore: _h, ...rest } = data;
|
|
5224
|
+
envelope = rest;
|
|
5225
|
+
captured = true;
|
|
5226
|
+
}
|
|
5227
|
+
const pageRows = data[key];
|
|
5228
|
+
if (Array.isArray(pageRows))
|
|
5229
|
+
rows.push(...pageRows);
|
|
5230
|
+
const next = typeof data.nextCursor === "string" ? data.nextCursor : null;
|
|
5231
|
+
if (data.hasMore !== true || !next)
|
|
5232
|
+
break;
|
|
5233
|
+
cursor = next;
|
|
5234
|
+
}
|
|
5235
|
+
const capped = options.max === undefined ? rows : rows.slice(0, options.max);
|
|
5236
|
+
return { rows: capped, payload: { ...envelope, [key]: capped } };
|
|
5237
|
+
}
|
|
5238
|
+
function parseRowCap(raw) {
|
|
5239
|
+
if (raw === undefined)
|
|
5240
|
+
return;
|
|
5241
|
+
const parsed = Number.parseInt(raw, 10);
|
|
5242
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
5243
|
+
}
|
|
5244
|
+
|
|
5183
5245
|
// src/lib/resolve-match.ts
|
|
5184
5246
|
var ID_SHAPE = /^[a-z0-9]{32}$/;
|
|
5185
5247
|
function isIdShape(ref) {
|
|
@@ -5392,25 +5454,21 @@ async function probeById(spec, scope, id) {
|
|
|
5392
5454
|
});
|
|
5393
5455
|
}
|
|
5394
5456
|
function candidateSlug(kind, row) {
|
|
5457
|
+
if (typeof row.slug === "string" && row.slug.trim()) {
|
|
5458
|
+
return row.slug.trim().toLowerCase();
|
|
5459
|
+
}
|
|
5395
5460
|
if (kind === "server") {
|
|
5396
5461
|
return deriveServerSlug(typeof row.deploymentUrl === "string" ? row.deploymentUrl : undefined);
|
|
5397
5462
|
}
|
|
5398
|
-
if (kind === "project" && typeof row.slug === "string" && row.slug.trim()) {
|
|
5399
|
-
return row.slug.trim().toLowerCase();
|
|
5400
|
-
}
|
|
5401
5463
|
return;
|
|
5402
5464
|
}
|
|
5403
5465
|
async function loadCandidates(kind, spec, scope) {
|
|
5404
5466
|
const params = {
|
|
5405
|
-
organizationId: scope.organizationId
|
|
5406
|
-
limit: "100"
|
|
5467
|
+
organizationId: scope.organizationId
|
|
5407
5468
|
};
|
|
5408
5469
|
if (scope.projectId && kind !== "project")
|
|
5409
5470
|
params["projectId"] = scope.projectId;
|
|
5410
|
-
const
|
|
5411
|
-
const rows = data?.[spec.listKey];
|
|
5412
|
-
if (!Array.isArray(rows))
|
|
5413
|
-
return [];
|
|
5471
|
+
const rows = await fetchAllPages(spec.listPath, params, spec.listKey);
|
|
5414
5472
|
return rows.filter((row) => typeof row?.id === "string").map((row) => ({
|
|
5415
5473
|
id: row.id,
|
|
5416
5474
|
name: typeof row.name === "string" ? row.name : "",
|
|
@@ -6245,11 +6303,8 @@ function registerProjectCommands(program2) {
|
|
|
6245
6303
|
].join(`
|
|
6246
6304
|
`)).action(runAction(async (opts) => {
|
|
6247
6305
|
const orgId = await resolveOrgId(opts.org);
|
|
6248
|
-
const
|
|
6249
|
-
|
|
6250
|
-
limit: opts.limit
|
|
6251
|
-
});
|
|
6252
|
-
printList(data.projects.map((p2) => ({
|
|
6306
|
+
const { rows: projectRows, payload } = await fetchAllPagesWithEnvelope("/api/v1/projects", { organizationId: orgId }, "projects", { max: parseRowCap(opts.limit) });
|
|
6307
|
+
printList(projectRows.map((p2) => ({
|
|
6253
6308
|
id: p2.id,
|
|
6254
6309
|
name: p2.name,
|
|
6255
6310
|
slug: p2.slug,
|
|
@@ -6261,7 +6316,7 @@ function registerProjectCommands(program2) {
|
|
|
6261
6316
|
{ key: "slug", label: "Slug", width: 20 },
|
|
6262
6317
|
{ key: "servers", label: "Servers", width: 8 },
|
|
6263
6318
|
{ key: "created", label: "Created", width: 20 }
|
|
6264
|
-
],
|
|
6319
|
+
], payload);
|
|
6265
6320
|
}));
|
|
6266
6321
|
projects.command("get <project>").description("Get details for a single project (accepts id or name)").option("--org <organizationId>", "Organization ID").action(runAction(async (projectRef, opts) => {
|
|
6267
6322
|
const orgId = await resolveOrgId(opts.org);
|
|
@@ -6634,8 +6689,483 @@ async function tailDeploymentLogs(args) {
|
|
|
6634
6689
|
}
|
|
6635
6690
|
}
|
|
6636
6691
|
|
|
6692
|
+
// src/commands/servers-deps.ts
|
|
6693
|
+
var DEPS_PATH = "/api/v1/server/dependencies";
|
|
6694
|
+
function reportApiError(err, action) {
|
|
6695
|
+
if (err instanceof McpCloudApiError) {
|
|
6696
|
+
printError(`${action} failed (HTTP ${err.status}): ${err.error.message}`);
|
|
6697
|
+
const reason = err.error.details?.reason;
|
|
6698
|
+
if (typeof reason === "string") {
|
|
6699
|
+
printInfo(` ${c.dim("reason:")} ${reason}`);
|
|
6700
|
+
}
|
|
6701
|
+
} else {
|
|
6702
|
+
printError(`${action} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
6703
|
+
}
|
|
6704
|
+
throw new CliExitError(1);
|
|
6705
|
+
}
|
|
6706
|
+
function printDependencies(deps) {
|
|
6707
|
+
if (deps.length === 0) {
|
|
6708
|
+
printInfo(c.dim(" No dependencies. Add one with `mcp servers deps add`."));
|
|
6709
|
+
return;
|
|
6710
|
+
}
|
|
6711
|
+
printList(deps.map((dep) => ({
|
|
6712
|
+
id: dep.id,
|
|
6713
|
+
child: dep.childName ?? dep.childServerId ?? "—",
|
|
6714
|
+
prefix: dep.toolPrefix,
|
|
6715
|
+
tools: dep.includedTools ? dep.includedTools.join(", ") : "all",
|
|
6716
|
+
enabled: dep.enabled ? "yes" : "no"
|
|
6717
|
+
})), [
|
|
6718
|
+
{ key: "id", label: "ID", width: 34 },
|
|
6719
|
+
{ key: "child", label: "Child", width: 26 },
|
|
6720
|
+
{ key: "prefix", label: "Prefix", width: 16 },
|
|
6721
|
+
{ key: "tools", label: "Tools", width: 28 },
|
|
6722
|
+
{ key: "enabled", label: "Enabled", width: 8 }
|
|
6723
|
+
], { dependencies: deps });
|
|
6724
|
+
}
|
|
6725
|
+
function registerServerDepsCommands(servers) {
|
|
6726
|
+
const deps = servers.command("deps").description("Compose another server's tools into this one (list / add / rm)");
|
|
6727
|
+
deps.command("list [server]").description("List the servers this one composes.").option("--org <organizationId>", "Organization ID").action(runAction(async (server, opts) => {
|
|
6728
|
+
const orgId = await resolveOrgId(opts.org);
|
|
6729
|
+
const serverId = await resolveServerId(requireServer(server), orgId);
|
|
6730
|
+
try {
|
|
6731
|
+
const data = await api.get(DEPS_PATH, {
|
|
6732
|
+
organizationId: orgId,
|
|
6733
|
+
serverId
|
|
6734
|
+
});
|
|
6735
|
+
if (isJsonMode()) {
|
|
6736
|
+
printJson(data);
|
|
6737
|
+
return;
|
|
6738
|
+
}
|
|
6739
|
+
printDependencies(data.dependencies);
|
|
6740
|
+
} catch (err) {
|
|
6741
|
+
reportApiError(err, "Listing dependencies");
|
|
6742
|
+
}
|
|
6743
|
+
}));
|
|
6744
|
+
deps.command("add <server> <child>").description("Compose <child>'s tools into <server>. Both accept an id, name or slug.").option("--org <organizationId>", "Organization ID").option("--prefix <prefix>", "Prefix for the re-exposed tool names (defaults to the child's slug).").option("--tools <names>", "Comma-separated child tool names to expose; omit for all of them.").addHelpText("after", [
|
|
6745
|
+
"",
|
|
6746
|
+
"Examples:",
|
|
6747
|
+
" $ mcp servers deps add my-api billing-api",
|
|
6748
|
+
" $ mcp servers deps add my-api billing-api --prefix billing_",
|
|
6749
|
+
" $ mcp servers deps add my-api billing-api --tools getCharges,createRefund"
|
|
6750
|
+
].join(`
|
|
6751
|
+
`)).action(runAction(async (server, child, opts) => {
|
|
6752
|
+
const orgId = await resolveOrgId(opts.org);
|
|
6753
|
+
const serverId = await resolveServerId(server, orgId);
|
|
6754
|
+
const childServerId = await resolveServerId(child, orgId);
|
|
6755
|
+
const includedTools = (opts.tools ?? "").split(",").map((name) => name.trim()).filter(Boolean);
|
|
6756
|
+
try {
|
|
6757
|
+
const data = await api.post(DEPS_PATH, {
|
|
6758
|
+
organizationId: orgId,
|
|
6759
|
+
serverId,
|
|
6760
|
+
childServerId,
|
|
6761
|
+
...opts.prefix ? { toolPrefix: opts.prefix } : {},
|
|
6762
|
+
...includedTools.length > 0 ? { includedTools } : {}
|
|
6763
|
+
});
|
|
6764
|
+
if (isJsonMode()) {
|
|
6765
|
+
printJson({ ok: true, ...data });
|
|
6766
|
+
return;
|
|
6767
|
+
}
|
|
6768
|
+
const dep = data.dependency;
|
|
6769
|
+
printSuccess(`Composed ${c.bold(dep.childName ?? child)} into ${c.bold(server)} ${c.dim(`(prefix ${dep.toolPrefix})`)}`);
|
|
6770
|
+
printInfo(` ${c.dim("Its tools appear on the parent as")} ${c.bold(`${dep.toolPrefix}<tool>`)} ${c.dim("after the next policy sync — no redeploy needed.")}`);
|
|
6771
|
+
} catch (err) {
|
|
6772
|
+
reportApiError(err, "Binding the dependency");
|
|
6773
|
+
}
|
|
6774
|
+
}));
|
|
6775
|
+
deps.command("rm <server> <dependencyId>").description("Remove a composed dependency (id from `deps list`).").option("--org <organizationId>", "Organization ID").action(runAction(async (server, dependencyId, opts) => {
|
|
6776
|
+
const orgId = await resolveOrgId(opts.org);
|
|
6777
|
+
const serverId = await resolveServerId(server, orgId);
|
|
6778
|
+
try {
|
|
6779
|
+
const data = await api.delete(`${DEPS_PATH}?organizationId=${encodeURIComponent(orgId)}&serverId=${encodeURIComponent(serverId)}&dependencyId=${encodeURIComponent(dependencyId)}`);
|
|
6780
|
+
if (isJsonMode()) {
|
|
6781
|
+
printJson({ ok: true, ...data });
|
|
6782
|
+
return;
|
|
6783
|
+
}
|
|
6784
|
+
printSuccess(`Removed ${c.bold(data.removed.childName ?? data.removed.id)} ${c.dim(`(prefix ${data.removed.toolPrefix})`)}`);
|
|
6785
|
+
} catch (err) {
|
|
6786
|
+
reportApiError(err, "Removing the dependency");
|
|
6787
|
+
}
|
|
6788
|
+
}));
|
|
6789
|
+
}
|
|
6790
|
+
function requireServer(server) {
|
|
6791
|
+
if (server)
|
|
6792
|
+
return server;
|
|
6793
|
+
printError("Name the server whose dependencies you want to list.");
|
|
6794
|
+
throw new CliExitError(1);
|
|
6795
|
+
}
|
|
6796
|
+
|
|
6797
|
+
// src/commands/servers-prompts.ts
|
|
6798
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
6799
|
+
|
|
6800
|
+
// src/lib/server-scope.ts
|
|
6801
|
+
async function resolveServerScope(serverRef, org) {
|
|
6802
|
+
const organizationId = await resolveOrgId(org);
|
|
6803
|
+
const serverId = await resolveServerId(serverRef, organizationId);
|
|
6804
|
+
const detail = await api.get("/api/v1/server", {
|
|
6805
|
+
organizationId,
|
|
6806
|
+
serverId
|
|
6807
|
+
});
|
|
6808
|
+
return {
|
|
6809
|
+
organizationId,
|
|
6810
|
+
projectId: detail.server.projectId,
|
|
6811
|
+
serverId,
|
|
6812
|
+
serverName: detail.server.name
|
|
6813
|
+
};
|
|
6814
|
+
}
|
|
6815
|
+
|
|
6816
|
+
// src/commands/servers-prompts.ts
|
|
6817
|
+
var PROMPT_PATH = "/api/v1/server/prompt";
|
|
6818
|
+
function reportApiError2(err, action) {
|
|
6819
|
+
if (err instanceof McpCloudApiError) {
|
|
6820
|
+
printError(`${action} failed (HTTP ${err.status}): ${err.error.message}`);
|
|
6821
|
+
const details = err.error.details;
|
|
6822
|
+
if (typeof details?.field === "string") {
|
|
6823
|
+
printInfo(` ${c.dim("field:")} ${details.field}`);
|
|
6824
|
+
}
|
|
6825
|
+
} else {
|
|
6826
|
+
printError(`${action} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
6827
|
+
}
|
|
6828
|
+
throw new CliExitError(1);
|
|
6829
|
+
}
|
|
6830
|
+
function countArguments(argumentsJson) {
|
|
6831
|
+
try {
|
|
6832
|
+
const parsed = JSON.parse(argumentsJson || "[]");
|
|
6833
|
+
return Array.isArray(parsed) ? String(parsed.length) : "?";
|
|
6834
|
+
} catch {
|
|
6835
|
+
return "?";
|
|
6836
|
+
}
|
|
6837
|
+
}
|
|
6838
|
+
function readTemplate(opts) {
|
|
6839
|
+
if (opts.templateFile)
|
|
6840
|
+
return readFileSync3(opts.templateFile, "utf8");
|
|
6841
|
+
if (opts.template !== undefined)
|
|
6842
|
+
return opts.template;
|
|
6843
|
+
printError("A template is required — pass --template <markdown> or --template-file <path>.");
|
|
6844
|
+
throw new CliExitError(1);
|
|
6845
|
+
}
|
|
6846
|
+
function registerServerPromptCommands(servers) {
|
|
6847
|
+
const prompts = servers.command("prompts").description("Author the prompts a client reads as this server’s workflows (list / set / rm)");
|
|
6848
|
+
prompts.command("list <server>").description("List the prompts authored on a server.").option("--org <organizationId>", "Organization ID").action(runAction(async (server, opts) => {
|
|
6849
|
+
const scope = await resolveServerScope(server, opts.org);
|
|
6850
|
+
try {
|
|
6851
|
+
const data = await api.get(PROMPT_PATH, {
|
|
6852
|
+
organizationId: scope.organizationId,
|
|
6853
|
+
projectId: scope.projectId,
|
|
6854
|
+
serverId: scope.serverId
|
|
6855
|
+
});
|
|
6856
|
+
if (isJsonMode()) {
|
|
6857
|
+
printJson(data);
|
|
6858
|
+
return;
|
|
6859
|
+
}
|
|
6860
|
+
if (data.prompts.length === 0) {
|
|
6861
|
+
printInfo(c.dim(" No prompts. Add one with `mcp servers prompts set`."));
|
|
6862
|
+
return;
|
|
6863
|
+
}
|
|
6864
|
+
printList(data.prompts.map((prompt2) => ({
|
|
6865
|
+
name: prompt2.name,
|
|
6866
|
+
args: countArguments(prompt2.argumentsJson),
|
|
6867
|
+
description: prompt2.description ?? "—"
|
|
6868
|
+
})), [
|
|
6869
|
+
{ key: "name", label: "NAME", width: 28 },
|
|
6870
|
+
{ key: "args", label: "ARGS", width: 6 },
|
|
6871
|
+
{ key: "description", label: "DESCRIPTION", width: 48 }
|
|
6872
|
+
]);
|
|
6873
|
+
} catch (err) {
|
|
6874
|
+
reportApiError2(err, "Listing prompts");
|
|
6875
|
+
}
|
|
6876
|
+
}));
|
|
6877
|
+
prompts.command("set <server> <name>").description("Create or replace a prompt by name. Re-running with the same name updates it.").option("--org <organizationId>", "Organization ID").option("--template <markdown>", "Prompt body, inline").option("--template-file <path>", "Prompt body, read from a file").option("--arguments <json>", "JSON array of {name, description, required}; defaults to []").option("--description <text>", "What the prompt is for").addHelpText("after", [
|
|
6878
|
+
"",
|
|
6879
|
+
"Examples:",
|
|
6880
|
+
" $ mcp servers prompts set my-api triage --template-file ./triage.md",
|
|
6881
|
+
` $ mcp servers prompts set my-api triage --template 'Summarize {{issue}}.' \\`,
|
|
6882
|
+
` --arguments '[{"name":"issue","required":true}]'`,
|
|
6883
|
+
"",
|
|
6884
|
+
"Notes:",
|
|
6885
|
+
" --arguments is validated strictly. A placeholder in the template that",
|
|
6886
|
+
" is not a declared argument is reported, not refused — clients cannot",
|
|
6887
|
+
" fill those in."
|
|
6888
|
+
].join(`
|
|
6889
|
+
`)).action(runAction(async (server, name, opts) => {
|
|
6890
|
+
const scope = await resolveServerScope(server, opts.org);
|
|
6891
|
+
const templateMarkdown = readTemplate(opts);
|
|
6892
|
+
try {
|
|
6893
|
+
const data = await api.put(PROMPT_PATH, {
|
|
6894
|
+
argumentsJson: opts.arguments ?? "[]",
|
|
6895
|
+
description: opts.description ?? null,
|
|
6896
|
+
name,
|
|
6897
|
+
organizationId: scope.organizationId,
|
|
6898
|
+
projectId: scope.projectId,
|
|
6899
|
+
serverId: scope.serverId,
|
|
6900
|
+
templateMarkdown
|
|
6901
|
+
});
|
|
6902
|
+
if (isJsonMode()) {
|
|
6903
|
+
printJson(data);
|
|
6904
|
+
return;
|
|
6905
|
+
}
|
|
6906
|
+
printSuccess(`${data.prompt.replaced ? "Updated" : "Created"} prompt ${c.bold(name)} on ${scope.serverName}.`);
|
|
6907
|
+
for (const notice of data.notices ?? []) {
|
|
6908
|
+
printWarn(notice.message);
|
|
6909
|
+
}
|
|
6910
|
+
printInfo(c.dim(" Not live yet: run `mcp servers generate <server> --store`, then deploy."));
|
|
6911
|
+
} catch (err) {
|
|
6912
|
+
reportApiError2(err, "Saving the prompt");
|
|
6913
|
+
}
|
|
6914
|
+
}));
|
|
6915
|
+
prompts.command("rm <server> <name>").description("Remove a prompt by name.").option("--org <organizationId>", "Organization ID").action(runAction(async (server, name, opts) => {
|
|
6916
|
+
const scope = await resolveServerScope(server, opts.org);
|
|
6917
|
+
try {
|
|
6918
|
+
const data = await api.delete(`${PROMPT_PATH}?organizationId=${encodeURIComponent(scope.organizationId)}&projectId=${encodeURIComponent(scope.projectId)}&serverId=${encodeURIComponent(scope.serverId)}&name=${encodeURIComponent(name)}`);
|
|
6919
|
+
if (isJsonMode()) {
|
|
6920
|
+
printJson(data);
|
|
6921
|
+
return;
|
|
6922
|
+
}
|
|
6923
|
+
printSuccess(`Removed prompt ${c.bold(name)}.`);
|
|
6924
|
+
} catch (err) {
|
|
6925
|
+
reportApiError2(err, "Removing the prompt");
|
|
6926
|
+
}
|
|
6927
|
+
}));
|
|
6928
|
+
}
|
|
6929
|
+
|
|
6930
|
+
// src/commands/servers-resources.ts
|
|
6931
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
6932
|
+
var RESOURCE_PATH = "/api/v1/server/resource";
|
|
6933
|
+
function reportApiError3(err, action) {
|
|
6934
|
+
if (err instanceof McpCloudApiError) {
|
|
6935
|
+
printError(`${action} failed (HTTP ${err.status}): ${err.error.message}`);
|
|
6936
|
+
} else {
|
|
6937
|
+
printError(`${action} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
6938
|
+
}
|
|
6939
|
+
throw new CliExitError(1);
|
|
6940
|
+
}
|
|
6941
|
+
function registerServerResourceCommands(servers) {
|
|
6942
|
+
const resources = servers.command("resources").description("Author the resources this server exposes to clients (list / set / rm)");
|
|
6943
|
+
resources.command("list <server>").description("List the resources authored on a server.").option("--org <organizationId>", "Organization ID").action(runAction(async (server, opts) => {
|
|
6944
|
+
const scope = await resolveServerScope(server, opts.org);
|
|
6945
|
+
try {
|
|
6946
|
+
const data = await api.get(RESOURCE_PATH, {
|
|
6947
|
+
organizationId: scope.organizationId,
|
|
6948
|
+
projectId: scope.projectId,
|
|
6949
|
+
serverId: scope.serverId
|
|
6950
|
+
});
|
|
6951
|
+
if (isJsonMode()) {
|
|
6952
|
+
printJson(data);
|
|
6953
|
+
return;
|
|
6954
|
+
}
|
|
6955
|
+
if (data.resources.length === 0) {
|
|
6956
|
+
printInfo(c.dim(" No resources. Add one with `mcp servers resources set`."));
|
|
6957
|
+
return;
|
|
6958
|
+
}
|
|
6959
|
+
printList(data.resources.map((resource) => ({
|
|
6960
|
+
uri: resource.uri,
|
|
6961
|
+
name: resource.name,
|
|
6962
|
+
type: resource.resourceType,
|
|
6963
|
+
mime: resource.mimeType
|
|
6964
|
+
})), [
|
|
6965
|
+
{ key: "uri", label: "URI", width: 40 },
|
|
6966
|
+
{ key: "name", label: "NAME", width: 24 },
|
|
6967
|
+
{ key: "type", label: "TYPE", width: 9 },
|
|
6968
|
+
{ key: "mime", label: "MIME", width: 20 }
|
|
6969
|
+
]);
|
|
6970
|
+
} catch (err) {
|
|
6971
|
+
reportApiError3(err, "Listing resources");
|
|
6972
|
+
}
|
|
6973
|
+
}));
|
|
6974
|
+
resources.command("set <server> <uri>").description("Create or replace a resource by URI. Re-running with the same URI updates it.").option("--org <organizationId>", "Organization ID").option("--name <name>", "Human-readable name (defaults to the URI)").option("--description <text>", "What the resource holds").option("--mime <type>", "MIME type", "text/markdown").option("--content <text>", "Body of a static resource, inline").option("--content-file <path>", "Body of a static resource, from a file").option("--upstream <template>", "Upstream path template — makes this a DYNAMIC resource fetched per read").addHelpText("after", [
|
|
6975
|
+
"",
|
|
6976
|
+
"Examples:",
|
|
6977
|
+
" $ mcp servers resources set my-api docs://api/limits --content-file ./limits.md",
|
|
6978
|
+
" $ mcp servers resources set my-api docs://api/status --upstream /status",
|
|
6979
|
+
"",
|
|
6980
|
+
"Notes:",
|
|
6981
|
+
" A resource is static (a body you supply) or dynamic (fetched from the",
|
|
6982
|
+
" upstream on each read). Passing --upstream makes it dynamic; otherwise",
|
|
6983
|
+
" content is required."
|
|
6984
|
+
].join(`
|
|
6985
|
+
`)).action(runAction(async (server, uri, opts) => {
|
|
6986
|
+
const scope = await resolveServerScope(server, opts.org);
|
|
6987
|
+
const staticContent = opts.contentFile ? readFileSync4(opts.contentFile, "utf8") : opts.content ?? null;
|
|
6988
|
+
const resourceType = opts.upstream ? "dynamic" : "static";
|
|
6989
|
+
if (resourceType === "static" && !staticContent?.trim()) {
|
|
6990
|
+
printError("A static resource needs a body — pass --content or --content-file, or --upstream to make it dynamic.");
|
|
6991
|
+
throw new CliExitError(1);
|
|
6992
|
+
}
|
|
6993
|
+
try {
|
|
6994
|
+
const data = await api.put(RESOURCE_PATH, {
|
|
6995
|
+
description: opts.description ?? null,
|
|
6996
|
+
mimeType: opts.mime ?? "text/markdown",
|
|
6997
|
+
name: opts.name ?? uri,
|
|
6998
|
+
organizationId: scope.organizationId,
|
|
6999
|
+
projectId: scope.projectId,
|
|
7000
|
+
resourceType,
|
|
7001
|
+
serverId: scope.serverId,
|
|
7002
|
+
staticContent,
|
|
7003
|
+
upstreamPathTemplate: opts.upstream ?? null,
|
|
7004
|
+
uri
|
|
7005
|
+
});
|
|
7006
|
+
if (isJsonMode()) {
|
|
7007
|
+
printJson(data);
|
|
7008
|
+
return;
|
|
7009
|
+
}
|
|
7010
|
+
printSuccess(`${data.resource.replaced ? "Updated" : "Created"} ${resourceType} resource ${c.bold(uri)} on ${scope.serverName}.`);
|
|
7011
|
+
printInfo(c.dim(" Not live yet: run `mcp servers generate <server> --store`, then deploy."));
|
|
7012
|
+
} catch (err) {
|
|
7013
|
+
reportApiError3(err, "Saving the resource");
|
|
7014
|
+
}
|
|
7015
|
+
}));
|
|
7016
|
+
resources.command("rm <server> <uri>").description("Remove a resource by URI.").option("--org <organizationId>", "Organization ID").action(runAction(async (server, uri, opts) => {
|
|
7017
|
+
const scope = await resolveServerScope(server, opts.org);
|
|
7018
|
+
try {
|
|
7019
|
+
const data = await api.delete(`${RESOURCE_PATH}?organizationId=${encodeURIComponent(scope.organizationId)}&projectId=${encodeURIComponent(scope.projectId)}&serverId=${encodeURIComponent(scope.serverId)}&uri=${encodeURIComponent(uri)}`);
|
|
7020
|
+
if (isJsonMode()) {
|
|
7021
|
+
printJson(data);
|
|
7022
|
+
return;
|
|
7023
|
+
}
|
|
7024
|
+
printSuccess(`Removed resource ${c.bold(uri)}.`);
|
|
7025
|
+
} catch (err) {
|
|
7026
|
+
reportApiError3(err, "Removing the resource");
|
|
7027
|
+
}
|
|
7028
|
+
}));
|
|
7029
|
+
}
|
|
7030
|
+
|
|
7031
|
+
// src/commands/servers-shape.ts
|
|
7032
|
+
var SCORE_PATH = "/api/v1/server/usability-score";
|
|
7033
|
+
var SHAPE_PATH = "/api/v1/server/shape";
|
|
7034
|
+
function reportApiError4(err, action) {
|
|
7035
|
+
if (err instanceof McpCloudApiError) {
|
|
7036
|
+
printError(`${action} failed (HTTP ${err.status}): ${err.error.message}`);
|
|
7037
|
+
} else {
|
|
7038
|
+
printError(`${action} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
7039
|
+
}
|
|
7040
|
+
throw new CliExitError(1);
|
|
7041
|
+
}
|
|
7042
|
+
function formatPercent(value) {
|
|
7043
|
+
return typeof value === "number" ? `${Math.round(value * 100)}%` : "—";
|
|
7044
|
+
}
|
|
7045
|
+
function registerServerShapingCommands(servers) {
|
|
7046
|
+
servers.command("score <server>").description("Score whether an agent handed this catalog picks the right tool (starts a run, or reads the last one)").option("--org <organizationId>", "Organization ID").option("--start", "Start a new scoring run instead of reading the last").option("--runs <count>", "Runs to average, 2-5 (default 3)").addHelpText("after", [
|
|
7047
|
+
"",
|
|
7048
|
+
"Examples:",
|
|
7049
|
+
" $ mcp servers score my-api # read the last score",
|
|
7050
|
+
" $ mcp servers score my-api --start # start a new run",
|
|
7051
|
+
"",
|
|
7052
|
+
"Notes:",
|
|
7053
|
+
" A score is reported WITH its spread across runs. A move smaller than",
|
|
7054
|
+
" the spread is noise, not an improvement.",
|
|
7055
|
+
" Starting a run makes one model call per scenario per run and returns",
|
|
7056
|
+
" immediately; read the result with the same command."
|
|
7057
|
+
].join(`
|
|
7058
|
+
`)).action(runAction(async (server, opts) => {
|
|
7059
|
+
const scope = await resolveServerScope(server, opts.org);
|
|
7060
|
+
const query = {
|
|
7061
|
+
organizationId: scope.organizationId,
|
|
7062
|
+
projectId: scope.projectId,
|
|
7063
|
+
serverId: scope.serverId
|
|
7064
|
+
};
|
|
7065
|
+
if (opts.start) {
|
|
7066
|
+
try {
|
|
7067
|
+
const data = await api.post(SCORE_PATH, {
|
|
7068
|
+
...query,
|
|
7069
|
+
...opts.runs ? { runCount: Number(opts.runs) } : {}
|
|
7070
|
+
});
|
|
7071
|
+
if (isJsonMode()) {
|
|
7072
|
+
printJson(data);
|
|
7073
|
+
return;
|
|
7074
|
+
}
|
|
7075
|
+
printSuccess(`Scoring started for ${scope.serverName}.`);
|
|
7076
|
+
printInfo(c.dim(` ${data.message}`));
|
|
7077
|
+
} catch (err) {
|
|
7078
|
+
reportApiError4(err, "Starting the scoring run");
|
|
7079
|
+
}
|
|
7080
|
+
return;
|
|
7081
|
+
}
|
|
7082
|
+
try {
|
|
7083
|
+
const data = await api.get(SCORE_PATH, query);
|
|
7084
|
+
if (isJsonMode()) {
|
|
7085
|
+
printJson(data);
|
|
7086
|
+
return;
|
|
7087
|
+
}
|
|
7088
|
+
if (data.batch.length === 0) {
|
|
7089
|
+
printInfo(c.dim(" Not measured yet. Run `mcp servers score <server> --start`."));
|
|
7090
|
+
return;
|
|
7091
|
+
}
|
|
7092
|
+
const accuracies = data.batch.map((run) => run.accuracy).filter((value) => value !== null);
|
|
7093
|
+
const min = Math.min(...accuracies);
|
|
7094
|
+
const max = Math.max(...accuracies);
|
|
7095
|
+
const mean = accuracies.reduce((sum, value) => sum + value, 0) / accuracies.length;
|
|
7096
|
+
printSuccess(`${formatPercent(mean)} ±${formatPercent(max - min)} across ${data.batch.length} runs`);
|
|
7097
|
+
printInfo(c.dim(` ${data.batch[0]?.scored ?? 0} scenarios · ${data.catalogSize ?? "—"} tools · judged by ${data.modelKey ?? "—"}`));
|
|
7098
|
+
} catch (err) {
|
|
7099
|
+
reportApiError4(err, "Reading the score");
|
|
7100
|
+
}
|
|
7101
|
+
}));
|
|
7102
|
+
servers.command("shape <server>").description("Propose task-shaped tools over this server’s endpoints (starts a run, or reads the last one)").option("--org <organizationId>", "Organization ID").option("--start", "Start a new shaping run instead of reading the last").addHelpText("after", [
|
|
7103
|
+
"",
|
|
7104
|
+
"Examples:",
|
|
7105
|
+
" $ mcp servers shape my-api --start # propose task tools",
|
|
7106
|
+
" $ mcp servers shape my-api # read the proposals",
|
|
7107
|
+
"",
|
|
7108
|
+
"Notes:",
|
|
7109
|
+
" Proposals are inspectable artifacts. Nothing is created until a",
|
|
7110
|
+
" proposal is accepted in the dashboard."
|
|
7111
|
+
].join(`
|
|
7112
|
+
`)).action(runAction(async (server, opts) => {
|
|
7113
|
+
const scope = await resolveServerScope(server, opts.org);
|
|
7114
|
+
const query = {
|
|
7115
|
+
organizationId: scope.organizationId,
|
|
7116
|
+
projectId: scope.projectId,
|
|
7117
|
+
serverId: scope.serverId
|
|
7118
|
+
};
|
|
7119
|
+
if (opts.start) {
|
|
7120
|
+
try {
|
|
7121
|
+
const data = await api.post(SHAPE_PATH, query);
|
|
7122
|
+
if (isJsonMode()) {
|
|
7123
|
+
printJson(data);
|
|
7124
|
+
return;
|
|
7125
|
+
}
|
|
7126
|
+
printSuccess(`Shaping started for ${scope.serverName}.`);
|
|
7127
|
+
printInfo(c.dim(` ${data.message}`));
|
|
7128
|
+
} catch (err) {
|
|
7129
|
+
reportApiError4(err, "Starting the shaping run");
|
|
7130
|
+
}
|
|
7131
|
+
return;
|
|
7132
|
+
}
|
|
7133
|
+
try {
|
|
7134
|
+
const data = await api.get(SHAPE_PATH, query);
|
|
7135
|
+
if (isJsonMode()) {
|
|
7136
|
+
printJson(data);
|
|
7137
|
+
return;
|
|
7138
|
+
}
|
|
7139
|
+
if (!data.run) {
|
|
7140
|
+
printInfo(c.dim(" No shaping run yet. Run `mcp servers shape <server> --start`."));
|
|
7141
|
+
return;
|
|
7142
|
+
}
|
|
7143
|
+
const reviewable = data.proposals.filter((p2) => !p2.rejectionReason);
|
|
7144
|
+
const refused = data.proposals.filter((p2) => p2.rejectionReason);
|
|
7145
|
+
printList(reviewable.map((proposal) => ({
|
|
7146
|
+
name: proposal.name,
|
|
7147
|
+
effect: proposal.effectClass ?? "—",
|
|
7148
|
+
plan: JSON.parse(proposal.planJson).map((step) => step.operationName).join(" → ")
|
|
7149
|
+
})), [
|
|
7150
|
+
{ key: "name", label: "PROPOSAL", width: 30 },
|
|
7151
|
+
{ key: "effect", label: "EFFECT", width: 16 },
|
|
7152
|
+
{ key: "plan", label: "PLAN", width: 46 }
|
|
7153
|
+
]);
|
|
7154
|
+
if (refused.length > 0) {
|
|
7155
|
+
printWarn(`${refused.length} proposal${refused.length === 1 ? "" : "s"} refused by validation: ${refused.map((p2) => p2.name).join(", ")}`);
|
|
7156
|
+
}
|
|
7157
|
+
if (data.run.failedClusterKeys.length > 0) {
|
|
7158
|
+
printWarn(`${data.run.failedClusterKeys.length} endpoint group(s) produced nothing: ${data.run.failedClusterKeys.join(", ")}`);
|
|
7159
|
+
}
|
|
7160
|
+
printInfo(c.dim(" Accept a proposal in the dashboard; nothing is created here."));
|
|
7161
|
+
} catch (err) {
|
|
7162
|
+
reportApiError4(err, "Reading shaping proposals");
|
|
7163
|
+
}
|
|
7164
|
+
}));
|
|
7165
|
+
}
|
|
7166
|
+
|
|
6637
7167
|
// src/commands/servers-env.ts
|
|
6638
|
-
import { existsSync as existsSync3, readFileSync as
|
|
7168
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "node:fs";
|
|
6639
7169
|
import { resolve } from "node:path";
|
|
6640
7170
|
function registerServerEnvCommands(servers) {
|
|
6641
7171
|
const env = servers.command("env").description("Manage server runtime environment bindings (encrypted secrets injected by the proxy).");
|
|
@@ -6784,7 +7314,7 @@ async function resolveSetValue(args) {
|
|
|
6784
7314
|
printError(`--from-file path not found: ${path}`);
|
|
6785
7315
|
throw new CliExitError(1);
|
|
6786
7316
|
}
|
|
6787
|
-
return
|
|
7317
|
+
return readFileSync5(path, "utf-8");
|
|
6788
7318
|
}
|
|
6789
7319
|
return await readStdin();
|
|
6790
7320
|
}
|
|
@@ -6940,7 +7470,7 @@ function registerServerSandboxVariableCommands(servers) {
|
|
|
6940
7470
|
}
|
|
6941
7471
|
|
|
6942
7472
|
// src/lib/mcp-invoke.ts
|
|
6943
|
-
import { existsSync as existsSync4, readFileSync as
|
|
7473
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "node:fs";
|
|
6944
7474
|
import { resolve as resolve2 } from "node:path";
|
|
6945
7475
|
var DEFAULT_TIMEOUT_MS3 = 60000;
|
|
6946
7476
|
var invokeRequestId = 1;
|
|
@@ -7160,7 +7690,7 @@ async function parseToolArguments(opts) {
|
|
|
7160
7690
|
if (!existsSync4(filePath)) {
|
|
7161
7691
|
return { ok: false, reason: "file-not-found", path: filePath };
|
|
7162
7692
|
}
|
|
7163
|
-
source =
|
|
7693
|
+
source = readFileSync6(filePath, "utf-8");
|
|
7164
7694
|
} else {
|
|
7165
7695
|
source = raw;
|
|
7166
7696
|
}
|
|
@@ -7709,7 +8239,7 @@ function formatDeploymentExposure(deployment) {
|
|
|
7709
8239
|
import { createHash as createHash2 } from "node:crypto";
|
|
7710
8240
|
import {
|
|
7711
8241
|
existsSync as existsSync5,
|
|
7712
|
-
readFileSync as
|
|
8242
|
+
readFileSync as readFileSync7,
|
|
7713
8243
|
watch as fsWatch
|
|
7714
8244
|
} from "node:fs";
|
|
7715
8245
|
import { dirname as dirname3 } from "node:path";
|
|
@@ -7763,7 +8293,7 @@ function summarizeDiff(diff) {
|
|
|
7763
8293
|
`) };
|
|
7764
8294
|
}
|
|
7765
8295
|
function readSpecFile(specPath) {
|
|
7766
|
-
const content =
|
|
8296
|
+
const content = readFileSync7(specPath, "utf-8");
|
|
7767
8297
|
return { content, hash: hashSpecContent(content) };
|
|
7768
8298
|
}
|
|
7769
8299
|
function watchSpecFile(options) {
|
|
@@ -7783,7 +8313,7 @@ function watchSpecFile(options) {
|
|
|
7783
8313
|
return;
|
|
7784
8314
|
let content;
|
|
7785
8315
|
try {
|
|
7786
|
-
content =
|
|
8316
|
+
content = readFileSync7(options.specPath, "utf-8");
|
|
7787
8317
|
} catch {
|
|
7788
8318
|
return;
|
|
7789
8319
|
}
|
|
@@ -8418,7 +8948,7 @@ function registerServerMutationCommands(servers) {
|
|
|
8418
8948
|
}
|
|
8419
8949
|
|
|
8420
8950
|
// src/lib/spec-pipeline.ts
|
|
8421
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as
|
|
8951
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8422
8952
|
import { dirname as dirname4, join as join5, resolve as resolve5 } from "node:path";
|
|
8423
8953
|
var SOURCE_TYPES = ["openapi", "graphql", "url"];
|
|
8424
8954
|
function isHttpUrl(value) {
|
|
@@ -8451,7 +8981,7 @@ async function readSpecSource(spec, override, options = {}) {
|
|
|
8451
8981
|
if (!existsSync7(filePath)) {
|
|
8452
8982
|
throw new Error(`Spec file not found: ${filePath}`);
|
|
8453
8983
|
}
|
|
8454
|
-
content =
|
|
8984
|
+
content = readFileSync8(filePath, "utf-8");
|
|
8455
8985
|
pathHint = filePath;
|
|
8456
8986
|
}
|
|
8457
8987
|
if (!content.trim()) {
|
|
@@ -8542,7 +9072,7 @@ function registerServerSpecCommands(servers) {
|
|
|
8542
9072
|
});
|
|
8543
9073
|
printStep(`Next: \`mcp servers generate ${data.server.id} --out ./out\` or \`mcp servers deploy ${data.server.id} --wait\`.`);
|
|
8544
9074
|
}));
|
|
8545
|
-
servers.command("generate <server>").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 <project>", "Project (id or name; looked up from the server when omitted)").option("--out <dir>", "Write the generated files to this directory instead of just summarizing").addHelpText("after", [
|
|
9075
|
+
servers.command("generate <server>").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 <project>", "Project (id or name; looked up from the server when omitted)").option("--out <dir>", "Write the generated files to this directory instead of just summarizing").option("--store", "Persist the bundle as the one `deploy` will ship. Required after editing prompts, resources or tool metadata: deploy reuses a server's existing bundle unchanged, so without this the change never reaches the deployed worker.").addHelpText("after", [
|
|
8546
9076
|
"",
|
|
8547
9077
|
"Notes:",
|
|
8548
9078
|
" Codegen runs on the builder Worker (arch-evolution P7f). If generate",
|
|
@@ -8551,6 +9081,7 @@ function registerServerSpecCommands(servers) {
|
|
|
8551
9081
|
"Examples:",
|
|
8552
9082
|
" $ mcp servers generate srv_123 # summarize the bundle",
|
|
8553
9083
|
" $ mcp servers generate srv_123 --out ./build # write files to disk",
|
|
9084
|
+
" $ mcp servers generate srv_123 --store # make it the bundle deploy ships",
|
|
8554
9085
|
" $ mcp --json servers generate srv_123 | jq '.bundle.files[].path'"
|
|
8555
9086
|
].join(`
|
|
8556
9087
|
`)).action(runAction(async (serverRef, opts) => {
|
|
@@ -8564,7 +9095,23 @@ function registerServerSpecCommands(servers) {
|
|
|
8564
9095
|
if (!isJsonMode()) {
|
|
8565
9096
|
printStep(`Generating bundle for ${c.bold(serverId)}…`);
|
|
8566
9097
|
}
|
|
8567
|
-
const data = await api.post("/api/v1/server/generate", {
|
|
9098
|
+
const data = await api.post("/api/v1/server/generate", {
|
|
9099
|
+
organizationId: orgId,
|
|
9100
|
+
projectId,
|
|
9101
|
+
serverId,
|
|
9102
|
+
...opts.store ? { store: true } : {}
|
|
9103
|
+
});
|
|
9104
|
+
if (!isJsonMode() && data.storeHint) {
|
|
9105
|
+
printWarn(data.storeHint);
|
|
9106
|
+
}
|
|
9107
|
+
if (!data.bundle) {
|
|
9108
|
+
if (isJsonMode()) {
|
|
9109
|
+
printJson(data);
|
|
9110
|
+
return;
|
|
9111
|
+
}
|
|
9112
|
+
printSuccess(data.stored ? `Stored the bundle for ${c.bold(data.server.name)} — deploy now ships it.` : `Generated for ${c.bold(data.server.name)} (${data.generationStatus ?? "no files returned"}).`);
|
|
9113
|
+
return;
|
|
9114
|
+
}
|
|
8568
9115
|
if (opts.out) {
|
|
8569
9116
|
const written = writeBundleToDisk(data.bundle, opts.out);
|
|
8570
9117
|
if (isJsonMode()) {
|
|
@@ -8599,7 +9146,7 @@ function registerServerSpecCommands(servers) {
|
|
|
8599
9146
|
}
|
|
8600
9147
|
|
|
8601
9148
|
// src/commands/servers-test-authoring.ts
|
|
8602
|
-
import { readFileSync as
|
|
9149
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
8603
9150
|
var EXECUTION_PROFILES = [
|
|
8604
9151
|
"mockBindings",
|
|
8605
9152
|
"previewBindings",
|
|
@@ -8617,9 +9164,9 @@ function parseTimeoutMs(value, fallbackMs) {
|
|
|
8617
9164
|
}
|
|
8618
9165
|
function readValueOption(raw) {
|
|
8619
9166
|
if (raw === "@-")
|
|
8620
|
-
return
|
|
9167
|
+
return readFileSync9(0, "utf8");
|
|
8621
9168
|
if (raw.startsWith("@"))
|
|
8622
|
-
return
|
|
9169
|
+
return readFileSync9(raw.slice(1), "utf8");
|
|
8623
9170
|
return raw;
|
|
8624
9171
|
}
|
|
8625
9172
|
async function resolveServerAndProject(serverRef, org) {
|
|
@@ -8979,6 +9526,10 @@ function registerServerTestCommands(servers) {
|
|
|
8979
9526
|
// src/commands/servers.ts
|
|
8980
9527
|
function registerServerCommands(program2) {
|
|
8981
9528
|
const servers = program2.command("servers").description("Manage MCP servers");
|
|
9529
|
+
registerServerDepsCommands(servers);
|
|
9530
|
+
registerServerPromptCommands(servers);
|
|
9531
|
+
registerServerResourceCommands(servers);
|
|
9532
|
+
registerServerShapingCommands(servers);
|
|
8982
9533
|
registerServerEnvCommands(servers);
|
|
8983
9534
|
registerServerSandboxVariableCommands(servers);
|
|
8984
9535
|
registerServerGetCommand(servers);
|
|
@@ -8998,14 +9549,11 @@ function registerServerCommands(program2) {
|
|
|
8998
9549
|
`)).action(runAction(async (opts) => {
|
|
8999
9550
|
const orgId = await resolveOrgId(opts.org);
|
|
9000
9551
|
const projectId = await resolveOptionalProjectId(opts.project, orgId);
|
|
9001
|
-
const params = {
|
|
9002
|
-
organizationId: orgId,
|
|
9003
|
-
limit: opts.limit
|
|
9004
|
-
};
|
|
9552
|
+
const params = { organizationId: orgId };
|
|
9005
9553
|
if (projectId)
|
|
9006
9554
|
params["projectId"] = projectId;
|
|
9007
|
-
const
|
|
9008
|
-
printList(
|
|
9555
|
+
const { rows: serverRows, payload } = await fetchAllPagesWithEnvelope("/api/v1/servers", params, "servers", { max: parseRowCap(opts.limit) });
|
|
9556
|
+
printList(serverRows.map((s) => ({
|
|
9009
9557
|
id: s.id,
|
|
9010
9558
|
name: s.name,
|
|
9011
9559
|
slug: deriveServerSlug(s.deploymentUrl) ?? "—",
|
|
@@ -9021,7 +9569,7 @@ function registerServerCommands(program2) {
|
|
|
9021
9569
|
{ key: "status", label: "Status", width: 10 },
|
|
9022
9570
|
{ key: "version", label: "Version", width: 8 },
|
|
9023
9571
|
{ key: "deployed", label: "Last Deployed", width: 22 }
|
|
9024
|
-
],
|
|
9572
|
+
], payload);
|
|
9025
9573
|
}));
|
|
9026
9574
|
servers.command("logs <server>").description("Show recent deployment events for a server's latest deployment").option("--org <organizationId>", "Organization ID").option("--limit <n>", "Number of recent events (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").action(runAction(async (serverRef, opts) => {
|
|
9027
9575
|
const orgId = await resolveOrgId(opts.org);
|
|
@@ -9150,14 +9698,14 @@ function registerServerCommands(program2) {
|
|
|
9150
9698
|
}
|
|
9151
9699
|
|
|
9152
9700
|
// src/commands/tools-diff.ts
|
|
9153
|
-
import { existsSync as existsSync11, readFileSync as
|
|
9701
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "node:fs";
|
|
9154
9702
|
import { relative } from "node:path";
|
|
9155
9703
|
|
|
9156
9704
|
// src/lib/dev/handlers-sync.ts
|
|
9157
9705
|
import {
|
|
9158
9706
|
existsSync as existsSync10,
|
|
9159
9707
|
mkdirSync as mkdirSync7,
|
|
9160
|
-
readFileSync as
|
|
9708
|
+
readFileSync as readFileSync12,
|
|
9161
9709
|
statSync,
|
|
9162
9710
|
writeFileSync as writeFileSync6
|
|
9163
9711
|
} from "node:fs";
|
|
@@ -9165,7 +9713,7 @@ import { createHash as createHash3 } from "node:crypto";
|
|
|
9165
9713
|
import { dirname as dirname5, join as join8 } from "node:path";
|
|
9166
9714
|
|
|
9167
9715
|
// src/lib/dev/state.ts
|
|
9168
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as
|
|
9716
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
|
|
9169
9717
|
import { join as join6, resolve as resolve6 } from "node:path";
|
|
9170
9718
|
function cachedStateMatchesTarget(cached2, target) {
|
|
9171
9719
|
return cached2 !== null && cached2.serverId === target.serverId && cached2.organizationId === target.organizationId;
|
|
@@ -9194,7 +9742,7 @@ function readState(cwd) {
|
|
|
9194
9742
|
if (!existsSync8(path))
|
|
9195
9743
|
return null;
|
|
9196
9744
|
try {
|
|
9197
|
-
const parsed = JSON.parse(
|
|
9745
|
+
const parsed = JSON.parse(readFileSync10(path, "utf-8"));
|
|
9198
9746
|
if (!parsed || typeof parsed !== "object")
|
|
9199
9747
|
return null;
|
|
9200
9748
|
const s = parsed;
|
|
@@ -9235,7 +9783,7 @@ function ensureGitignore(cwd) {
|
|
|
9235
9783
|
import {
|
|
9236
9784
|
existsSync as existsSync9,
|
|
9237
9785
|
mkdirSync as mkdirSync6,
|
|
9238
|
-
readFileSync as
|
|
9786
|
+
readFileSync as readFileSync11,
|
|
9239
9787
|
readdirSync,
|
|
9240
9788
|
rmSync,
|
|
9241
9789
|
writeFileSync as writeFileSync5
|
|
@@ -9449,7 +9997,7 @@ function readToolsState(devRootDir, serverId) {
|
|
|
9449
9997
|
if (!existsSync9(file))
|
|
9450
9998
|
return null;
|
|
9451
9999
|
try {
|
|
9452
|
-
const parsed = JSON.parse(
|
|
10000
|
+
const parsed = JSON.parse(readFileSync11(file, "utf-8"));
|
|
9453
10001
|
if (!parsed || typeof parsed !== "object")
|
|
9454
10002
|
return null;
|
|
9455
10003
|
const s = parsed;
|
|
@@ -9529,7 +10077,7 @@ function readLocalTool(devRootDir, serverId, toolName) {
|
|
|
9529
10077
|
const stateEntry = state?.tools[toolName];
|
|
9530
10078
|
if (!stateEntry)
|
|
9531
10079
|
return null;
|
|
9532
|
-
const view = parseToolMarkdown(
|
|
10080
|
+
const view = parseToolMarkdown(readFileSync11(filePath, "utf-8"));
|
|
9533
10081
|
return {
|
|
9534
10082
|
view,
|
|
9535
10083
|
cloudId: stateEntry.id,
|
|
@@ -9592,6 +10140,7 @@ async function pushLocalToolEdit(args) {
|
|
|
9592
10140
|
kind: "patched",
|
|
9593
10141
|
updatedAt: result.tool.updatedAt,
|
|
9594
10142
|
changedFields: result.patchedFields,
|
|
10143
|
+
notices: result.notices ?? [],
|
|
9595
10144
|
view: local.view
|
|
9596
10145
|
};
|
|
9597
10146
|
} catch (err) {
|
|
@@ -9622,11 +10171,8 @@ function toolNameFromWatcherPath(rel) {
|
|
|
9622
10171
|
return rel.slice(0, -3);
|
|
9623
10172
|
}
|
|
9624
10173
|
async function fetchToolsForServer(args) {
|
|
9625
|
-
const
|
|
9626
|
-
|
|
9627
|
-
projectId: args.projectId
|
|
9628
|
-
});
|
|
9629
|
-
return data.tools.filter((t) => t.serverId === args.serverId).map((t) => ({
|
|
10174
|
+
const tools = await fetchAllPages("/api/v1/project/tools", { organizationId: args.organizationId, projectId: args.projectId }, "tools");
|
|
10175
|
+
return tools.filter((t) => t.serverId === args.serverId).map((t) => ({
|
|
9630
10176
|
id: t.id,
|
|
9631
10177
|
serverId: t.serverId,
|
|
9632
10178
|
name: t.name,
|
|
@@ -9680,7 +10226,7 @@ async function pushLocalHandlerEdit(args) {
|
|
|
9680
10226
|
if (!resolved) {
|
|
9681
10227
|
return { kind: "unknown-tool", slug: args.slug };
|
|
9682
10228
|
}
|
|
9683
|
-
const source =
|
|
10229
|
+
const source = readFileSync12(filePath, "utf-8");
|
|
9684
10230
|
const hash = hashSource(source);
|
|
9685
10231
|
if (lastPushedHash.get(filePath) === hash) {
|
|
9686
10232
|
return { kind: "noop" };
|
|
@@ -9839,7 +10385,7 @@ function inspectLocalHandler(args) {
|
|
|
9839
10385
|
if (!existsSync10(filePath))
|
|
9840
10386
|
return { kind: "no-local-file" };
|
|
9841
10387
|
const cached2 = lastPushedHash.get(filePath);
|
|
9842
|
-
const onDisk = hashSource(
|
|
10388
|
+
const onDisk = hashSource(readFileSync12(filePath, "utf-8"));
|
|
9843
10389
|
if (cached2 !== undefined) {
|
|
9844
10390
|
return cached2 === onDisk ? { kind: "matches-cache" } : { kind: "differs-from-cache" };
|
|
9845
10391
|
}
|
|
@@ -9984,7 +10530,7 @@ function buildMetadataDiff(args) {
|
|
|
9984
10530
|
let local = null;
|
|
9985
10531
|
let parseError = null;
|
|
9986
10532
|
try {
|
|
9987
|
-
local = parseToolMarkdown(
|
|
10533
|
+
local = parseToolMarkdown(readFileSync13(filePath, "utf-8"));
|
|
9988
10534
|
} catch (err) {
|
|
9989
10535
|
parseError = err instanceof Error ? err.message : String(err);
|
|
9990
10536
|
}
|
|
@@ -10027,7 +10573,7 @@ async function buildHandlerDiff(args) {
|
|
|
10027
10573
|
const slug = handlerSlugFromToolName(args.toolName);
|
|
10028
10574
|
const filePath = `${serverBundleHandlersDir(args.devRootDir)}/${slug}.ts`;
|
|
10029
10575
|
const hasLocal = existsSync11(filePath);
|
|
10030
|
-
const localContent = hasLocal ?
|
|
10576
|
+
const localContent = hasLocal ? readFileSync13(filePath, "utf-8") : null;
|
|
10031
10577
|
const localPath = hasLocal ? relative(args.cwd, filePath) || filePath : null;
|
|
10032
10578
|
const changed = hasLocal ? localContent !== cloudResult.snapshot.content : true;
|
|
10033
10579
|
return {
|
|
@@ -10142,7 +10688,7 @@ import { join as join10, relative as relative2 } from "node:path";
|
|
|
10142
10688
|
|
|
10143
10689
|
// src/lib/editor-shell.ts
|
|
10144
10690
|
import { spawn as spawn2 } from "node:child_process";
|
|
10145
|
-
import { existsSync as existsSync12, readFileSync as
|
|
10691
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "node:fs";
|
|
10146
10692
|
import { platform as platform2 } from "node:os";
|
|
10147
10693
|
import { delimiter, join as join9 } from "node:path";
|
|
10148
10694
|
var DEFAULT_FALLBACKS = ["vi"];
|
|
@@ -10228,7 +10774,7 @@ async function runEditor(args) {
|
|
|
10228
10774
|
message: `${command} exited with code ${exitCode}. The file may be unsaved; re-run after fixing.`
|
|
10229
10775
|
};
|
|
10230
10776
|
}
|
|
10231
|
-
const contents =
|
|
10777
|
+
const contents = readFileSync14(args.filePath, "utf-8");
|
|
10232
10778
|
return { ok: true, contents, command };
|
|
10233
10779
|
}
|
|
10234
10780
|
|
|
@@ -10368,32 +10914,271 @@ function normalizeTool(raw) {
|
|
|
10368
10914
|
};
|
|
10369
10915
|
}
|
|
10370
10916
|
async function fetchEnrichedToolDetail(args) {
|
|
10371
|
-
const
|
|
10372
|
-
|
|
10373
|
-
projectId: args.projectId
|
|
10374
|
-
});
|
|
10375
|
-
const match = data.tools.find((t) => t.id === args.toolId);
|
|
10917
|
+
const tools = await fetchAllPages("/api/v1/project/tools", { organizationId: args.organizationId, projectId: args.projectId }, "tools");
|
|
10918
|
+
const match = tools.find((t) => t.id === args.toolId);
|
|
10376
10919
|
return match ? normalizeTool(match) : null;
|
|
10377
10920
|
}
|
|
10378
10921
|
async function fetchEnrichedToolByName(args) {
|
|
10379
|
-
const
|
|
10380
|
-
|
|
10381
|
-
projectId: args.projectId
|
|
10382
|
-
});
|
|
10383
|
-
const match = data.tools.find((t) => t.serverId === args.serverId && t.name === args.toolName);
|
|
10922
|
+
const tools = await fetchAllPages("/api/v1/project/tools", { organizationId: args.organizationId, projectId: args.projectId }, "tools");
|
|
10923
|
+
const match = tools.find((t) => t.serverId === args.serverId && t.name === args.toolName);
|
|
10384
10924
|
return match ? normalizeTool(match) : null;
|
|
10385
10925
|
}
|
|
10386
10926
|
|
|
10927
|
+
// src/commands/tools-enrich-batch.ts
|
|
10928
|
+
var POLL_INTERVAL_MS = 2000;
|
|
10929
|
+
var MS_PER_TOOL = 3 * 60 * 1000;
|
|
10930
|
+
var MIN_POLL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
10931
|
+
var MAX_POLL_TIMEOUT_MS = 90 * 60 * 1000;
|
|
10932
|
+
function pollTimeoutMsForJob(total) {
|
|
10933
|
+
const scaled = Math.max(0, total) * MS_PER_TOOL;
|
|
10934
|
+
return Math.min(MAX_POLL_TIMEOUT_MS, Math.max(MIN_POLL_TIMEOUT_MS, scaled));
|
|
10935
|
+
}
|
|
10936
|
+
function sleep(ms) {
|
|
10937
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
10938
|
+
}
|
|
10939
|
+
async function runToolsEnrichAll(args) {
|
|
10940
|
+
const toolIds = await resolveOnlyToolIds(args);
|
|
10941
|
+
let job;
|
|
10942
|
+
try {
|
|
10943
|
+
const started = await api.post("/api/v1/server/enrich", {
|
|
10944
|
+
organizationId: args.ctx.organizationId,
|
|
10945
|
+
projectId: args.ctx.projectId,
|
|
10946
|
+
serverId: args.ctx.serverId,
|
|
10947
|
+
apply: args.apply,
|
|
10948
|
+
...toolIds ? { toolIds } : {},
|
|
10949
|
+
...args.modelId ? { modelId: args.modelId } : {}
|
|
10950
|
+
});
|
|
10951
|
+
job = started.job;
|
|
10952
|
+
} catch (err) {
|
|
10953
|
+
if (err instanceof McpCloudApiError) {
|
|
10954
|
+
printError(`Enrichment job failed to start (HTTP ${err.status}): ${err.error.message}`);
|
|
10955
|
+
} else {
|
|
10956
|
+
printError(`Enrichment job failed to start: ${err instanceof Error ? err.message : String(err)}`);
|
|
10957
|
+
}
|
|
10958
|
+
throw new CliExitError(1);
|
|
10959
|
+
}
|
|
10960
|
+
if (!args.wait) {
|
|
10961
|
+
if (isJsonMode()) {
|
|
10962
|
+
printJson({ ok: true, job, waited: false });
|
|
10963
|
+
return;
|
|
10964
|
+
}
|
|
10965
|
+
printSuccess(`Queued ${job.total} tool${job.total === 1 ? "" : "s"} ${c.dim(`(job ${job.id})`)}`);
|
|
10966
|
+
printInfo(` ${c.dim("Poll with:")} ${c.bold(`mcp tools enrich --all --job ${job.id}`)}`);
|
|
10967
|
+
return;
|
|
10968
|
+
}
|
|
10969
|
+
if (!isJsonMode()) {
|
|
10970
|
+
printStep(`Enriching ${job.total} tool${job.total === 1 ? "" : "s"}${args.modelId ? c.dim(` (model ${args.modelId})`) : ""}…`);
|
|
10971
|
+
}
|
|
10972
|
+
const finalJob = await pollUntilTerminal(args.ctx, job);
|
|
10973
|
+
reportJob(finalJob, args.apply);
|
|
10974
|
+
}
|
|
10975
|
+
async function pollEnrichmentJob(ctx, jobId) {
|
|
10976
|
+
const job = await fetchJob(ctx, jobId);
|
|
10977
|
+
if (job.status === "queued" || job.status === "running") {
|
|
10978
|
+
reportJob(await pollUntilTerminal(ctx, job), job.total > 0);
|
|
10979
|
+
return;
|
|
10980
|
+
}
|
|
10981
|
+
reportJob(job, true);
|
|
10982
|
+
}
|
|
10983
|
+
async function fetchJob(ctx, jobId) {
|
|
10984
|
+
try {
|
|
10985
|
+
const data = await api.get("/api/v1/server/enrich", {
|
|
10986
|
+
organizationId: ctx.organizationId,
|
|
10987
|
+
jobId
|
|
10988
|
+
});
|
|
10989
|
+
return data.job;
|
|
10990
|
+
} catch (err) {
|
|
10991
|
+
if (err instanceof McpCloudApiError) {
|
|
10992
|
+
printError(`Could not read job ${jobId} (HTTP ${err.status}): ${err.error.message}`);
|
|
10993
|
+
} else {
|
|
10994
|
+
printError(`Could not read job ${jobId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10995
|
+
}
|
|
10996
|
+
throw new CliExitError(1);
|
|
10997
|
+
}
|
|
10998
|
+
}
|
|
10999
|
+
async function pollUntilTerminal(ctx, initial) {
|
|
11000
|
+
const timeoutMs = pollTimeoutMsForJob(initial.total);
|
|
11001
|
+
const deadline = Date.now() + timeoutMs;
|
|
11002
|
+
let job = initial;
|
|
11003
|
+
let lastCompleted = -1;
|
|
11004
|
+
while (job.status === "queued" || job.status === "running") {
|
|
11005
|
+
if (Date.now() > deadline) {
|
|
11006
|
+
printWarn(`Still running after ${Math.round(timeoutMs / 60000)} minutes — the job continues server-side.`);
|
|
11007
|
+
printInfo(` ${c.dim("Poll with:")} ${c.bold(`mcp tools enrich --all --job ${job.id}`)}`);
|
|
11008
|
+
throw new CliExitError(0);
|
|
11009
|
+
}
|
|
11010
|
+
await sleep(POLL_INTERVAL_MS);
|
|
11011
|
+
job = await fetchJob(ctx, job.id);
|
|
11012
|
+
if (!isJsonMode() && job.completed !== lastCompleted) {
|
|
11013
|
+
lastCompleted = job.completed;
|
|
11014
|
+
printInfo(c.dim(` ${job.completed}/${job.total} done · ${job.failed} failed`));
|
|
11015
|
+
}
|
|
11016
|
+
}
|
|
11017
|
+
return job;
|
|
11018
|
+
}
|
|
11019
|
+
function reportJob(job, applied) {
|
|
11020
|
+
const results = job.results ?? [];
|
|
11021
|
+
if (isJsonMode()) {
|
|
11022
|
+
printJson({ ok: job.failed === 0 && job.status === "completed", job });
|
|
11023
|
+
if (job.failed > 0 || job.status === "failed")
|
|
11024
|
+
throw new CliExitError(1);
|
|
11025
|
+
return;
|
|
11026
|
+
}
|
|
11027
|
+
if (job.status === "failed") {
|
|
11028
|
+
printError(`Enrichment job failed: ${job.error ?? "unknown error"}`);
|
|
11029
|
+
printInfo(c.dim(` ${job.succeeded} of ${job.total} tools completed before it stopped.`));
|
|
11030
|
+
throw new CliExitError(1);
|
|
11031
|
+
}
|
|
11032
|
+
const cachedCount = results.filter((entry) => entry.cached).length;
|
|
11033
|
+
if (job.failed === 0) {
|
|
11034
|
+
printSuccess(`Enriched ${job.succeeded} tool${job.succeeded === 1 ? "" : "s"} ${c.dim(`(${cachedCount} cached · ${applied ? "applied" : "proposed only"})`)}`);
|
|
11035
|
+
} else {
|
|
11036
|
+
printWarn(`Enriched ${job.succeeded} of ${job.total}; ${job.failed} failed ${c.dim(`(${cachedCount} cached)`)}`);
|
|
11037
|
+
for (const entry of results.filter((r) => r.status === "failed")) {
|
|
11038
|
+
printInfo(` ${c.dim("✗")} ${entry.toolName}: ${entry.error ?? "unknown error"}`);
|
|
11039
|
+
}
|
|
11040
|
+
}
|
|
11041
|
+
const writeFailures = results.filter((r) => r.status === "succeeded" && r.error);
|
|
11042
|
+
if (writeFailures.length > 0) {
|
|
11043
|
+
printWarn(`${writeFailures.length} enrichment${writeFailures.length === 1 ? "" : "s"} ran but could not be applied:`);
|
|
11044
|
+
for (const entry of writeFailures) {
|
|
11045
|
+
printInfo(` ${c.dim("!")} ${entry.toolName}: ${entry.error}`);
|
|
11046
|
+
}
|
|
11047
|
+
}
|
|
11048
|
+
if (job.failed > 0)
|
|
11049
|
+
throw new CliExitError(1);
|
|
11050
|
+
}
|
|
11051
|
+
async function resolveOnlyToolIds(args) {
|
|
11052
|
+
if (args.only.length === 0)
|
|
11053
|
+
return null;
|
|
11054
|
+
const list = await fetchToolsForServer(args.ctx);
|
|
11055
|
+
const byName = new Map(list.map((tool) => [tool.name, tool.id]));
|
|
11056
|
+
const ids = [];
|
|
11057
|
+
const missing = [];
|
|
11058
|
+
for (const name of args.only) {
|
|
11059
|
+
const id = byName.get(name);
|
|
11060
|
+
if (id)
|
|
11061
|
+
ids.push(id);
|
|
11062
|
+
else
|
|
11063
|
+
missing.push(name);
|
|
11064
|
+
}
|
|
11065
|
+
if (missing.length > 0) {
|
|
11066
|
+
printError(`Not found on this server: ${missing.join(", ")}`);
|
|
11067
|
+
if (list.length > 0) {
|
|
11068
|
+
const sample = list.slice(0, 5).map((tool) => tool.name).join(", ");
|
|
11069
|
+
printInfo(` ${c.dim("Available:")} ${sample}${list.length > 5 ? c.dim(`, +${list.length - 5} more`) : ""}`);
|
|
11070
|
+
}
|
|
11071
|
+
throw new CliExitError(1);
|
|
11072
|
+
}
|
|
11073
|
+
return ids;
|
|
11074
|
+
}
|
|
11075
|
+
|
|
11076
|
+
// src/commands/tools-enrich-apply.ts
|
|
11077
|
+
function printSuggestion(tool, suggestion) {
|
|
11078
|
+
const sDesc = suggestion.enrichedDescription?.trim() || null;
|
|
11079
|
+
const sTags = suggestion.suggestedTags ?? [];
|
|
11080
|
+
const sRisk = suggestion.riskClassification?.riskClass ?? null;
|
|
11081
|
+
const sIdem = suggestion.riskClassification?.idempotent ?? null;
|
|
11082
|
+
printInfo(c.bold("Suggested vs applied:"));
|
|
11083
|
+
printInfo(diffRow("description", tool.description, sDesc));
|
|
11084
|
+
printInfo(diffRow("tags", tool.semanticTags.join(", ") || null, sTags.join(", ") || null));
|
|
11085
|
+
printInfo(diffRow("riskClass", tool.riskClass, sRisk));
|
|
11086
|
+
printInfo(diffRow("idempotent", tool.idempotent === null ? null : String(tool.idempotent), sIdem === null ? null : String(sIdem)));
|
|
11087
|
+
}
|
|
11088
|
+
function diffRow(label, applied, suggested) {
|
|
11089
|
+
const left = c.dim(` ${label.padEnd(14)}`);
|
|
11090
|
+
if (applied === suggested) {
|
|
11091
|
+
return `${left}${c.dim(applied ?? "(none)")} ${c.dim("(unchanged)")}`;
|
|
11092
|
+
}
|
|
11093
|
+
return `${left}${applied ?? c.dim("(none)")} ${c.dim("→")} ${c.bold(suggested ?? c.dim("(clear)"))}`;
|
|
11094
|
+
}
|
|
11095
|
+
async function applySuggestion(tool, ctx) {
|
|
11096
|
+
if (!tool.enrichment) {
|
|
11097
|
+
return { ok: false, message: "No suggestion to apply." };
|
|
11098
|
+
}
|
|
11099
|
+
const desc = tool.enrichment.enrichedDescription?.trim() ?? null;
|
|
11100
|
+
const tags = tool.enrichment.suggestedTags;
|
|
11101
|
+
const risk = tool.enrichment.riskClassification?.riskClass ?? null;
|
|
11102
|
+
const idem = tool.enrichment.riskClassification?.idempotent ?? null;
|
|
11103
|
+
try {
|
|
11104
|
+
const result = await api.patch("/api/v1/server/tool", {
|
|
11105
|
+
organizationId: ctx.organizationId,
|
|
11106
|
+
projectId: ctx.projectId,
|
|
11107
|
+
serverId: ctx.serverId,
|
|
11108
|
+
toolId: tool.id,
|
|
11109
|
+
expectedUpdatedAt: tool.updatedAt,
|
|
11110
|
+
description: desc,
|
|
11111
|
+
semanticTags: tags,
|
|
11112
|
+
riskClass: risk,
|
|
11113
|
+
idempotent: idem
|
|
11114
|
+
});
|
|
11115
|
+
return {
|
|
11116
|
+
ok: true,
|
|
11117
|
+
updatedAt: result.tool.updatedAt,
|
|
11118
|
+
changedFields: result.patchedFields
|
|
11119
|
+
};
|
|
11120
|
+
} catch (err) {
|
|
11121
|
+
if (err instanceof McpCloudApiError) {
|
|
11122
|
+
if (err.error.code === "tool_metadata_conflict") {
|
|
11123
|
+
return {
|
|
11124
|
+
ok: false,
|
|
11125
|
+
message: `Conflict — the tool was modified by someone else after enrichment. Run \`mcp tools pull ${tool.name}\` and rerun \`mcp tools enrich ${tool.name} --apply\`.`
|
|
11126
|
+
};
|
|
11127
|
+
}
|
|
11128
|
+
return {
|
|
11129
|
+
ok: false,
|
|
11130
|
+
message: `Apply failed (HTTP ${err.status}): ${err.error.message}`
|
|
11131
|
+
};
|
|
11132
|
+
}
|
|
11133
|
+
return {
|
|
11134
|
+
ok: false,
|
|
11135
|
+
message: `Apply failed: ${err instanceof Error ? err.message : String(err)}`
|
|
11136
|
+
};
|
|
11137
|
+
}
|
|
11138
|
+
}
|
|
11139
|
+
|
|
10387
11140
|
// src/commands/tools-enrich.ts
|
|
10388
11141
|
function registerToolsEnrichCommand(tools) {
|
|
10389
|
-
tools.command("enrich
|
|
11142
|
+
tools.command("enrich [name]").description("Run AI enrichment for one tool (prints the suggested vs applied diff), or for the whole server with --all.").option("--org <organizationId>", "Organization ID (defaults to .mcpcloud/state.json)").option("--server <server>", "Server (id, name, or slug; defaults to .mcpcloud/state.json)").option("--all", "Enrich every tool on the server as one background job, applying each result. One request, not one per tool.").option("--only <names>", "With --all: comma-separated tool names to limit the job to.").option("--no-apply", "With --all: generate suggestions without writing them onto the tools.").option("--no-wait", "With --all: print the job id and exit instead of following it.").option("--job <jobId>", "Poll an enrichment job started earlier.").option("--apply", "Apply the suggestion immediately (PATCH /api/v1/server/tool with the suggested values).").option("--model <id>", "Use a specific model ID for the rewrite (defaults to org / dashboard config)").addHelpText("after", [
|
|
10390
11143
|
"",
|
|
10391
11144
|
"Examples:",
|
|
10392
11145
|
" $ mcp tools enrich get_user # generate + show suggestion",
|
|
10393
11146
|
" $ mcp tools enrich get_user --apply # generate + push to cloud",
|
|
10394
|
-
" $ mcp tools enrich
|
|
11147
|
+
" $ mcp tools enrich --all # whole server, one job",
|
|
11148
|
+
" $ mcp tools enrich --all --only a,b # just these two",
|
|
11149
|
+
" $ mcp tools enrich --all --no-wait # queue it and get the job id",
|
|
11150
|
+
" $ mcp tools enrich --job job_abc123 # follow a job started earlier"
|
|
10395
11151
|
].join(`
|
|
10396
11152
|
`)).action(runAction(async (name, opts) => {
|
|
11153
|
+
const batchMode = Boolean(opts.all) || Boolean(opts.job);
|
|
11154
|
+
if (name && batchMode) {
|
|
11155
|
+
printError("Pass a tool name or --all/--job, not both.");
|
|
11156
|
+
throw new CliExitError(1);
|
|
11157
|
+
}
|
|
11158
|
+
if (!name && !batchMode) {
|
|
11159
|
+
printError("Name a tool to enrich, or pass --all for every tool on the server.");
|
|
11160
|
+
throw new CliExitError(1);
|
|
11161
|
+
}
|
|
11162
|
+
if (batchMode) {
|
|
11163
|
+
const ctx = await resolveToolsContext({
|
|
11164
|
+
orgOverride: opts.org,
|
|
11165
|
+
serverOverride: opts.server
|
|
11166
|
+
});
|
|
11167
|
+
if (!ctx)
|
|
11168
|
+
return;
|
|
11169
|
+
if (opts.job) {
|
|
11170
|
+
await pollEnrichmentJob(ctx, opts.job);
|
|
11171
|
+
return;
|
|
11172
|
+
}
|
|
11173
|
+
await runToolsEnrichAll({
|
|
11174
|
+
ctx,
|
|
11175
|
+
only: (opts.only ?? "").split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0),
|
|
11176
|
+
apply: opts.apply !== false,
|
|
11177
|
+
modelId: opts.model,
|
|
11178
|
+
wait: opts.wait !== false
|
|
11179
|
+
});
|
|
11180
|
+
return;
|
|
11181
|
+
}
|
|
10397
11182
|
await runToolsEnrich({
|
|
10398
11183
|
toolName: name,
|
|
10399
11184
|
orgOverride: opts.org,
|
|
@@ -10430,6 +11215,7 @@ async function runToolsEnrich(args) {
|
|
|
10430
11215
|
projectId: ctx.projectId,
|
|
10431
11216
|
serverId: ctx.serverId,
|
|
10432
11217
|
toolId: target.id,
|
|
11218
|
+
apply: false,
|
|
10433
11219
|
...args.modelId ? { modelId: args.modelId } : {}
|
|
10434
11220
|
});
|
|
10435
11221
|
} catch (err) {
|
|
@@ -10496,68 +11282,6 @@ async function runToolsEnrich(args) {
|
|
|
10496
11282
|
printSuccess(`Applied ${c.bold(args.toolName)} ${c.dim(`(${applyResult.changedFields.length} field${applyResult.changedFields.length === 1 ? "" : "s"} patched)`)}`);
|
|
10497
11283
|
printInfo(` ${c.dim("Run")} ${c.bold(`mcp tools pull ${args.toolName}`)} ${c.dim("to refresh your local copy.")}`);
|
|
10498
11284
|
}
|
|
10499
|
-
function printSuggestion(tool, suggestion) {
|
|
10500
|
-
const sDesc = suggestion.enrichedDescription?.trim() || null;
|
|
10501
|
-
const sTags = suggestion.suggestedTags ?? [];
|
|
10502
|
-
const sRisk = suggestion.riskClassification?.riskClass ?? null;
|
|
10503
|
-
const sIdem = suggestion.riskClassification?.idempotent ?? null;
|
|
10504
|
-
printInfo(c.bold("Suggested vs applied:"));
|
|
10505
|
-
printInfo(diffRow("description", tool.description, sDesc));
|
|
10506
|
-
printInfo(diffRow("tags", tool.semanticTags.join(", ") || null, sTags.join(", ") || null));
|
|
10507
|
-
printInfo(diffRow("riskClass", tool.riskClass, sRisk));
|
|
10508
|
-
printInfo(diffRow("idempotent", tool.idempotent === null ? null : String(tool.idempotent), sIdem === null ? null : String(sIdem)));
|
|
10509
|
-
}
|
|
10510
|
-
function diffRow(label, applied, suggested) {
|
|
10511
|
-
const left = c.dim(` ${label.padEnd(14)}`);
|
|
10512
|
-
if (applied === suggested) {
|
|
10513
|
-
return `${left}${c.dim(applied ?? "(none)")} ${c.dim("(unchanged)")}`;
|
|
10514
|
-
}
|
|
10515
|
-
return `${left}${applied ?? c.dim("(none)")} ${c.dim("→")} ${c.bold(suggested ?? c.dim("(clear)"))}`;
|
|
10516
|
-
}
|
|
10517
|
-
async function applySuggestion(tool, ctx) {
|
|
10518
|
-
if (!tool.enrichment) {
|
|
10519
|
-
return { ok: false, message: "No suggestion to apply." };
|
|
10520
|
-
}
|
|
10521
|
-
const desc = tool.enrichment.enrichedDescription?.trim() ?? null;
|
|
10522
|
-
const tags = tool.enrichment.suggestedTags;
|
|
10523
|
-
const risk = tool.enrichment.riskClassification?.riskClass ?? null;
|
|
10524
|
-
const idem = tool.enrichment.riskClassification?.idempotent ?? null;
|
|
10525
|
-
try {
|
|
10526
|
-
const result = await api.patch("/api/v1/server/tool", {
|
|
10527
|
-
organizationId: ctx.organizationId,
|
|
10528
|
-
projectId: ctx.projectId,
|
|
10529
|
-
serverId: ctx.serverId,
|
|
10530
|
-
toolId: tool.id,
|
|
10531
|
-
expectedUpdatedAt: tool.updatedAt,
|
|
10532
|
-
description: desc,
|
|
10533
|
-
semanticTags: tags,
|
|
10534
|
-
riskClass: risk,
|
|
10535
|
-
idempotent: idem
|
|
10536
|
-
});
|
|
10537
|
-
return {
|
|
10538
|
-
ok: true,
|
|
10539
|
-
updatedAt: result.tool.updatedAt,
|
|
10540
|
-
changedFields: result.patchedFields
|
|
10541
|
-
};
|
|
10542
|
-
} catch (err) {
|
|
10543
|
-
if (err instanceof McpCloudApiError) {
|
|
10544
|
-
if (err.error.code === "tool_metadata_conflict") {
|
|
10545
|
-
return {
|
|
10546
|
-
ok: false,
|
|
10547
|
-
message: `Conflict — the tool was modified by someone else after enrichment. Run \`mcp tools pull ${tool.name}\` and rerun \`mcp tools enrich ${tool.name} --apply\`.`
|
|
10548
|
-
};
|
|
10549
|
-
}
|
|
10550
|
-
return {
|
|
10551
|
-
ok: false,
|
|
10552
|
-
message: `Apply failed (HTTP ${err.status}): ${err.error.message}`
|
|
10553
|
-
};
|
|
10554
|
-
}
|
|
10555
|
-
return {
|
|
10556
|
-
ok: false,
|
|
10557
|
-
message: `Apply failed: ${err instanceof Error ? err.message : String(err)}`
|
|
10558
|
-
};
|
|
10559
|
-
}
|
|
10560
|
-
}
|
|
10561
11285
|
|
|
10562
11286
|
// src/commands/tools-handlers.ts
|
|
10563
11287
|
var STATUS_LABEL = {
|
|
@@ -11077,7 +11801,6 @@ function diffLine2(label, applied, suggested) {
|
|
|
11077
11801
|
}
|
|
11078
11802
|
|
|
11079
11803
|
// src/commands/tools.ts
|
|
11080
|
-
var PROJECT_TOOLS_PAGE_LIMIT = 100;
|
|
11081
11804
|
function registerToolCommands(program2) {
|
|
11082
11805
|
const tools = program2.command("tools").description("Inspect and edit tools on a project or server");
|
|
11083
11806
|
tools.command("list").description("List tools for a project, or scope to a single server with --server").option("--org <organizationId>", "Organization ID").option("--project <project>", "List every tool in this project (id or name)").option("--server <server>", "List only the tools that belong to this server — id, name, or slug (the CLI resolves its project, fetches the project tools, then filters to this server)").action(runAction(async (opts) => {
|
|
@@ -11097,13 +11820,8 @@ function registerToolCommands(program2) {
|
|
|
11097
11820
|
});
|
|
11098
11821
|
projectId = serverData.server.projectId;
|
|
11099
11822
|
}
|
|
11100
|
-
const
|
|
11101
|
-
|
|
11102
|
-
organizationId: orgId,
|
|
11103
|
-
projectId
|
|
11104
|
-
});
|
|
11105
|
-
const truncated = data.tools.length >= PROJECT_TOOLS_PAGE_LIMIT;
|
|
11106
|
-
const rows = resolvedServerId ? data.tools.filter((t) => t.serverId === resolvedServerId) : data.tools;
|
|
11823
|
+
const { rows: allTools, payload } = await fetchAllPagesWithEnvelope("/api/v1/project/tools", { organizationId: orgId, projectId }, "tools");
|
|
11824
|
+
const rows = resolvedServerId ? allTools.filter((t) => t.serverId === resolvedServerId) : allTools;
|
|
11107
11825
|
printList(rows.map((t) => ({
|
|
11108
11826
|
name: t.name,
|
|
11109
11827
|
method: t.endpoint?.method ?? t.method ?? "—",
|
|
@@ -11116,10 +11834,7 @@ function registerToolCommands(program2) {
|
|
|
11116
11834
|
{ key: "path", label: "Path", width: 32 },
|
|
11117
11835
|
{ key: "enrichment", label: "Enrichment", width: 12 },
|
|
11118
11836
|
{ key: "description", label: "Description", width: 60 }
|
|
11119
|
-
], resolvedServerId ? { ...
|
|
11120
|
-
if (truncated) {
|
|
11121
|
-
printWarn(`Showing the first ${PROJECT_TOOLS_PAGE_LIMIT} tools — this project has more. ` + "Filter with --server, or read them from the deployed endpoint with `mcp invoke <server> --list`.");
|
|
11122
|
-
}
|
|
11837
|
+
], resolvedServerId ? { ...payload, tools: rows } : payload);
|
|
11123
11838
|
}));
|
|
11124
11839
|
registerToolsShowCommand(tools);
|
|
11125
11840
|
registerToolsDiffCommand(tools);
|
|
@@ -11281,13 +11996,13 @@ function isAgentKey(value) {
|
|
|
11281
11996
|
}
|
|
11282
11997
|
|
|
11283
11998
|
// src/commands/skills-lifecycle.ts
|
|
11284
|
-
import { readFileSync as
|
|
11999
|
+
import { readFileSync as readFileSync15 } from "node:fs";
|
|
11285
12000
|
function readValueOption2(raw) {
|
|
11286
12001
|
if (raw === "@-") {
|
|
11287
|
-
return
|
|
12002
|
+
return readFileSync15(0, "utf8");
|
|
11288
12003
|
}
|
|
11289
12004
|
if (raw.startsWith("@")) {
|
|
11290
|
-
return
|
|
12005
|
+
return readFileSync15(raw.slice(1), "utf8");
|
|
11291
12006
|
}
|
|
11292
12007
|
return raw;
|
|
11293
12008
|
}
|
|
@@ -11667,7 +12382,7 @@ function registerSkillMutationCommands(skills) {
|
|
|
11667
12382
|
}
|
|
11668
12383
|
|
|
11669
12384
|
// src/commands/skills-test-authoring.ts
|
|
11670
|
-
import { readFileSync as
|
|
12385
|
+
import { readFileSync as readFileSync16 } from "node:fs";
|
|
11671
12386
|
var FIXTURE_MODES = [
|
|
11672
12387
|
"baseline",
|
|
11673
12388
|
"validationFailure",
|
|
@@ -11676,9 +12391,9 @@ var FIXTURE_MODES = [
|
|
|
11676
12391
|
];
|
|
11677
12392
|
function readValueOption3(raw) {
|
|
11678
12393
|
if (raw === "@-")
|
|
11679
|
-
return
|
|
12394
|
+
return readFileSync16(0, "utf8");
|
|
11680
12395
|
if (raw.startsWith("@"))
|
|
11681
|
-
return
|
|
12396
|
+
return readFileSync16(raw.slice(1), "utf8");
|
|
11682
12397
|
return raw;
|
|
11683
12398
|
}
|
|
11684
12399
|
function parseAssertionsOption(raw) {
|
|
@@ -12247,14 +12962,11 @@ function registerSkillCommands(program2) {
|
|
|
12247
12962
|
`)).action(runAction(async (opts) => {
|
|
12248
12963
|
const orgId = await resolveOrgId(opts.org);
|
|
12249
12964
|
const projectId = await resolveOptionalProjectId(opts.project, orgId);
|
|
12250
|
-
const params = {
|
|
12251
|
-
organizationId: orgId,
|
|
12252
|
-
limit: opts.limit
|
|
12253
|
-
};
|
|
12965
|
+
const params = { organizationId: orgId };
|
|
12254
12966
|
if (projectId)
|
|
12255
12967
|
params["projectId"] = projectId;
|
|
12256
|
-
const
|
|
12257
|
-
printList(
|
|
12968
|
+
const { rows: skillRows, payload } = await fetchAllPagesWithEnvelope("/api/v1/skills", params, "skills", { max: parseRowCap(opts.limit) });
|
|
12969
|
+
printList(skillRows.map((s) => ({
|
|
12258
12970
|
id: s.id,
|
|
12259
12971
|
name: s.name,
|
|
12260
12972
|
project: s.projectName,
|
|
@@ -12268,7 +12980,7 @@ function registerSkillCommands(program2) {
|
|
|
12268
12980
|
{ key: "status", label: "Status", width: 10 },
|
|
12269
12981
|
{ key: "version", label: "Version", width: 8 },
|
|
12270
12982
|
{ key: "updated", label: "Updated", width: 22 }
|
|
12271
|
-
],
|
|
12983
|
+
], payload);
|
|
12272
12984
|
}));
|
|
12273
12985
|
skills.command("get <skill>").description("Get details for a single skill (accepts id or name)").option("--org <organizationId>", "Organization ID").action(runAction(async (skillRef, opts) => {
|
|
12274
12986
|
const orgId = await resolveOrgId(opts.org);
|
|
@@ -12526,7 +13238,7 @@ import { join as join18, relative as relative6 } from "node:path";
|
|
|
12526
13238
|
import {
|
|
12527
13239
|
existsSync as existsSync14,
|
|
12528
13240
|
mkdirSync as mkdirSync9,
|
|
12529
|
-
readFileSync as
|
|
13241
|
+
readFileSync as readFileSync17,
|
|
12530
13242
|
rmSync as rmSync2,
|
|
12531
13243
|
writeFileSync as writeFileSync8
|
|
12532
13244
|
} from "node:fs";
|
|
@@ -12663,7 +13375,7 @@ import { spawnSync } from "node:child_process";
|
|
|
12663
13375
|
import {
|
|
12664
13376
|
existsSync as existsSync15,
|
|
12665
13377
|
mkdirSync as mkdirSync10,
|
|
12666
|
-
readFileSync as
|
|
13378
|
+
readFileSync as readFileSync18,
|
|
12667
13379
|
writeFileSync as writeFileSync9
|
|
12668
13380
|
} from "node:fs";
|
|
12669
13381
|
import { dirname as dirname8, join as join13 } from "node:path";
|
|
@@ -12740,7 +13452,7 @@ function isGitAvailable() {
|
|
|
12740
13452
|
}
|
|
12741
13453
|
function applyLocalExcludes(repoDir) {
|
|
12742
13454
|
const excludePath = join13(repoDir, ".git", "info", "exclude");
|
|
12743
|
-
const existing = existsSync15(excludePath) ?
|
|
13455
|
+
const existing = existsSync15(excludePath) ? readFileSync18(excludePath, "utf-8") : "";
|
|
12744
13456
|
const next = mergeLocalExcludes(existing);
|
|
12745
13457
|
if (next === null)
|
|
12746
13458
|
return;
|
|
@@ -28038,6 +28750,12 @@ function buildHostScript(options) {
|
|
|
28038
28750
|
``,
|
|
28039
28751
|
`async function main() {`,
|
|
28040
28752
|
` const env = loadEnv()`,
|
|
28753
|
+
` try {`,
|
|
28754
|
+
` const rt = await import('@mcpcloud/runtime/node')`,
|
|
28755
|
+
` if (typeof rt.registerNodeCodeModeEngine === 'function') rt.registerNodeCodeModeEngine()`,
|
|
28756
|
+
` } catch (err) {`,
|
|
28757
|
+
` console.error('[mcp dev] code mode unavailable locally:', err?.message ?? err)`,
|
|
28758
|
+
` }`,
|
|
28041
28759
|
` const mod = await import(pathToFileURL(new URL(ENTRY, import.meta.url).pathname).href)`,
|
|
28042
28760
|
` const handler = mod.default`,
|
|
28043
28761
|
` if (!handler || typeof handler.fetch !== 'function') {`,
|
|
@@ -28108,7 +28826,7 @@ import {
|
|
|
28108
28826
|
appendFileSync as appendFileSync2,
|
|
28109
28827
|
existsSync as existsSync18,
|
|
28110
28828
|
mkdirSync as mkdirSync13,
|
|
28111
|
-
readFileSync as
|
|
28829
|
+
readFileSync as readFileSync19,
|
|
28112
28830
|
readdirSync as readdirSync2,
|
|
28113
28831
|
renameSync,
|
|
28114
28832
|
statSync as statSync2
|
|
@@ -28160,7 +28878,7 @@ function readAll(dir) {
|
|
|
28160
28878
|
for (const path of [...listRotatedFiles(dir), activeFilePath(dir)]) {
|
|
28161
28879
|
if (!existsSync18(path))
|
|
28162
28880
|
continue;
|
|
28163
|
-
out.push(...parseLines(
|
|
28881
|
+
out.push(...parseLines(readFileSync19(path, "utf-8")));
|
|
28164
28882
|
}
|
|
28165
28883
|
return out;
|
|
28166
28884
|
}
|
|
@@ -28427,8 +29145,9 @@ async function prepareDev(opts) {
|
|
|
28427
29145
|
|
|
28428
29146
|
// src/lib/dev/tree-sync.ts
|
|
28429
29147
|
import { createHash as createHash4 } from "node:crypto";
|
|
28430
|
-
import { existsSync as existsSync21, mkdirSync as mkdirSync14, readdirSync as readdirSync3, readFileSync as
|
|
29148
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync14, readdirSync as readdirSync3, readFileSync as readFileSync20, writeFileSync as writeFileSync13 } from "node:fs";
|
|
28431
29149
|
import { join as join19 } from "node:path";
|
|
29150
|
+
var AUTHORING_GUIDE_FILE = "AGENTS.md";
|
|
28432
29151
|
var STRUCTURAL_FRONTMATTER_KEYS = [
|
|
28433
29152
|
"inputSchema",
|
|
28434
29153
|
"outputSchema",
|
|
@@ -28444,11 +29163,11 @@ function listToolFiles(devRootDir, serverId) {
|
|
|
28444
29163
|
const dir = serverToolsDir(devRootDir, serverId);
|
|
28445
29164
|
if (!existsSync21(dir))
|
|
28446
29165
|
return [];
|
|
28447
|
-
return readdirSync3(dir).filter((entry) => entry.endsWith(".md")).map((entry) => entry.slice(0, -3)).sort();
|
|
29166
|
+
return readdirSync3(dir).filter((entry) => entry.endsWith(".md")).filter((entry) => entry !== AUTHORING_GUIDE_FILE).map((entry) => entry.slice(0, -3)).sort();
|
|
28448
29167
|
}
|
|
28449
29168
|
function readToolFile(devRootDir, serverId, toolName) {
|
|
28450
29169
|
const file = join19(serverToolsDir(devRootDir, serverId), `${toolName}.md`);
|
|
28451
|
-
const raw =
|
|
29170
|
+
const raw = readFileSync20(file, "utf-8");
|
|
28452
29171
|
try {
|
|
28453
29172
|
return { raw, view: parseToolMarkdown(raw) };
|
|
28454
29173
|
} catch (err) {
|
|
@@ -28466,6 +29185,21 @@ function findStructuralEdit(raw) {
|
|
|
28466
29185
|
}
|
|
28467
29186
|
return null;
|
|
28468
29187
|
}
|
|
29188
|
+
async function writeAuthoringGuide(args) {
|
|
29189
|
+
try {
|
|
29190
|
+
const response = await api.get("/api/v1/tool-authoring-guide");
|
|
29191
|
+
const markdown = response?.guide?.markdown;
|
|
29192
|
+
if (typeof markdown !== "string" || !markdown.trim())
|
|
29193
|
+
return null;
|
|
29194
|
+
const dir = serverToolsDir(args.devRootDir, args.serverId);
|
|
29195
|
+
mkdirSync14(dir, { recursive: true });
|
|
29196
|
+
const guidePath = join19(dir, AUTHORING_GUIDE_FILE);
|
|
29197
|
+
writeFileSync13(guidePath, markdown, "utf8");
|
|
29198
|
+
return guidePath;
|
|
29199
|
+
} catch {
|
|
29200
|
+
return null;
|
|
29201
|
+
}
|
|
29202
|
+
}
|
|
28469
29203
|
async function pullToolTree(args) {
|
|
28470
29204
|
const tools = await fetchToolsForServer({
|
|
28471
29205
|
organizationId: args.organizationId,
|
|
@@ -28477,7 +29211,11 @@ async function pullToolTree(args) {
|
|
|
28477
29211
|
serverId: args.serverId,
|
|
28478
29212
|
tools
|
|
28479
29213
|
});
|
|
28480
|
-
|
|
29214
|
+
const guidePath = await writeAuthoringGuide({
|
|
29215
|
+
devRootDir: args.devRootDir,
|
|
29216
|
+
serverId: args.serverId
|
|
29217
|
+
});
|
|
29218
|
+
return { ...result, guidePath, tools };
|
|
28481
29219
|
}
|
|
28482
29220
|
function planPush(args) {
|
|
28483
29221
|
const changes = [];
|
|
@@ -28549,7 +29287,7 @@ function listHandlerFiles(devRootDir) {
|
|
|
28549
29287
|
return [];
|
|
28550
29288
|
return readdirSync3(dir).filter((entry) => entry.endsWith(".ts") && !entry.endsWith(".definition.ts")).sort().map((entry) => ({
|
|
28551
29289
|
slug: entry.slice(0, -3),
|
|
28552
|
-
source:
|
|
29290
|
+
source: readFileSync20(join19(dir, entry), "utf-8")
|
|
28553
29291
|
}));
|
|
28554
29292
|
}
|
|
28555
29293
|
function writeHandlerManifest(args) {
|
|
@@ -28577,7 +29315,7 @@ function recordHandlerPushed(args) {
|
|
|
28577
29315
|
serverId: args.serverId,
|
|
28578
29316
|
version: 1
|
|
28579
29317
|
};
|
|
28580
|
-
manifest.handlers[args.slug] = hashSource2(
|
|
29318
|
+
manifest.handlers[args.slug] = hashSource2(readFileSync20(file, "utf-8"));
|
|
28581
29319
|
writeFileSync13(handlerManifestPath(args.devRootDir, args.serverId), JSON.stringify(manifest, null, 2), "utf-8");
|
|
28582
29320
|
}
|
|
28583
29321
|
function readHandlerManifest(devRootDir, serverId) {
|
|
@@ -28585,7 +29323,7 @@ function readHandlerManifest(devRootDir, serverId) {
|
|
|
28585
29323
|
if (!existsSync21(file))
|
|
28586
29324
|
return null;
|
|
28587
29325
|
try {
|
|
28588
|
-
const parsed = JSON.parse(
|
|
29326
|
+
const parsed = JSON.parse(readFileSync20(file, "utf-8"));
|
|
28589
29327
|
return parsed.version === 1 && parsed.handlers ? parsed : null;
|
|
28590
29328
|
} catch {
|
|
28591
29329
|
return null;
|
|
@@ -28649,6 +29387,7 @@ function describeToolPush(toolName, result) {
|
|
|
28649
29387
|
return {
|
|
28650
29388
|
detail: `updated ${result.changedFields.join(", ") || "metadata"}`,
|
|
28651
29389
|
ok: true,
|
|
29390
|
+
...result.notices.length > 0 ? { notices: result.notices.map((notice) => notice.message) } : {},
|
|
28652
29391
|
target: toolName
|
|
28653
29392
|
};
|
|
28654
29393
|
case "noop":
|
|
@@ -28797,6 +29536,7 @@ function registerTreeCommands(program2) {
|
|
|
28797
29536
|
name: target.serverName,
|
|
28798
29537
|
projectId: target.projectId
|
|
28799
29538
|
},
|
|
29539
|
+
authoringGuide: tools.guidePath,
|
|
28800
29540
|
toolFilesRemoved: tools.filesRemoved,
|
|
28801
29541
|
toolFilesWritten: tools.filesWritten,
|
|
28802
29542
|
treeDir: prepared.destDir
|
|
@@ -28809,6 +29549,9 @@ function registerTreeCommands(program2) {
|
|
|
28809
29549
|
handlers: handlerCount,
|
|
28810
29550
|
tree: prepared.destDir
|
|
28811
29551
|
});
|
|
29552
|
+
if (tools.guidePath) {
|
|
29553
|
+
printInfo(`Editing rules for agents: ${tools.guidePath}`);
|
|
29554
|
+
}
|
|
28812
29555
|
printInfo("Edit, then run `mcp push --dry-run`.");
|
|
28813
29556
|
}));
|
|
28814
29557
|
program2.command("push").description("Push local tool metadata and handler edits back to the cloud (diffed against your last pull)").option("--server <server>", "Server to push to (id or name)").option("--org <organizationId>", "Organization ID").option("--dry-run", "Print the change set and exit without writing").addHelpText("after", [
|
|
@@ -28881,6 +29624,9 @@ function registerTreeCommands(program2) {
|
|
|
28881
29624
|
}
|
|
28882
29625
|
for (const outcome of applied) {
|
|
28883
29626
|
printSuccess(`${outcome.target}: ${outcome.detail}`);
|
|
29627
|
+
for (const notice of outcome.notices ?? []) {
|
|
29628
|
+
printWarn(`${outcome.target}: ${notice}`);
|
|
29629
|
+
}
|
|
28884
29630
|
}
|
|
28885
29631
|
for (const outcome of refused) {
|
|
28886
29632
|
printError(`${outcome.target}: ${outcome.detail}`);
|
|
@@ -29553,7 +30299,7 @@ import { isAbsolute as isAbsolute4, relative as relative7, resolve as resolve9 }
|
|
|
29553
30299
|
import {
|
|
29554
30300
|
existsSync as existsSync22,
|
|
29555
30301
|
mkdirSync as mkdirSync15,
|
|
29556
|
-
readFileSync as
|
|
30302
|
+
readFileSync as readFileSync21,
|
|
29557
30303
|
readdirSync as readdirSync4,
|
|
29558
30304
|
unlinkSync,
|
|
29559
30305
|
writeFileSync as writeFileSync14
|
|
@@ -29597,7 +30343,7 @@ function listSessions() {
|
|
|
29597
30343
|
continue;
|
|
29598
30344
|
const path = join21(dir, entry);
|
|
29599
30345
|
try {
|
|
29600
|
-
const parsed = JSON.parse(
|
|
30346
|
+
const parsed = JSON.parse(readFileSync21(path, "utf-8"));
|
|
29601
30347
|
if (typeof parsed.pid !== "number") {
|
|
29602
30348
|
unlinkSync(path);
|
|
29603
30349
|
continue;
|
|
@@ -29995,7 +30741,7 @@ async function runDevTail(opts) {
|
|
|
29995
30741
|
emitLines(complete, opts.filter);
|
|
29996
30742
|
}
|
|
29997
30743
|
}
|
|
29998
|
-
await
|
|
30744
|
+
await sleep2(intervalMs);
|
|
29999
30745
|
}
|
|
30000
30746
|
}
|
|
30001
30747
|
function parseInterval(raw) {
|
|
@@ -30056,7 +30802,7 @@ function emitLines(text, filter) {
|
|
|
30056
30802
|
`);
|
|
30057
30803
|
}
|
|
30058
30804
|
}
|
|
30059
|
-
function
|
|
30805
|
+
function sleep2(ms) {
|
|
30060
30806
|
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
30061
30807
|
}
|
|
30062
30808
|
|
|
@@ -30956,19 +31702,19 @@ import { homedir as homedir4 } from "node:os";
|
|
|
30956
31702
|
import { join as join26 } from "node:path";
|
|
30957
31703
|
|
|
30958
31704
|
// src/lib/dev/agent-connectors/json-config-utils.ts
|
|
30959
|
-
import { existsSync as existsSync28, mkdirSync as mkdirSync16, readFileSync as
|
|
31705
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync16, readFileSync as readFileSync22, writeFileSync as writeFileSync15 } from "node:fs";
|
|
30960
31706
|
import { dirname as dirname10, join as join25, basename } from "node:path";
|
|
30961
31707
|
function readJsonFile(path) {
|
|
30962
31708
|
if (!existsSync28(path))
|
|
30963
31709
|
return { ok: true, value: {} };
|
|
30964
31710
|
try {
|
|
30965
|
-
const raw =
|
|
31711
|
+
const raw = readFileSync22(path, "utf-8");
|
|
30966
31712
|
if (!raw.trim())
|
|
30967
31713
|
return { ok: true, value: {} };
|
|
30968
31714
|
return { ok: true, value: JSON.parse(raw) };
|
|
30969
31715
|
} catch {
|
|
30970
31716
|
try {
|
|
30971
|
-
return { ok: false, raw:
|
|
31717
|
+
return { ok: false, raw: readFileSync22(path, "utf-8") };
|
|
30972
31718
|
} catch {
|
|
30973
31719
|
return { ok: false };
|
|
30974
31720
|
}
|
|
@@ -30990,7 +31736,7 @@ function backupConfig(args) {
|
|
|
30990
31736
|
const filename = `${args.agentId}__${basename(args.configPath)}.bak`;
|
|
30991
31737
|
const backupPath = join25(args.backupsDir, filename);
|
|
30992
31738
|
if (!existsSync28(backupPath)) {
|
|
30993
|
-
const raw =
|
|
31739
|
+
const raw = readFileSync22(args.configPath, "utf-8");
|
|
30994
31740
|
writeFileSync15(backupPath, raw, "utf-8");
|
|
30995
31741
|
}
|
|
30996
31742
|
return { backupPath, existed: true };
|
|
@@ -31001,7 +31747,7 @@ function restoreFromBackup(args) {
|
|
|
31001
31747
|
if (!existsSync28(backupPath)) {
|
|
31002
31748
|
return { restored: false };
|
|
31003
31749
|
}
|
|
31004
|
-
const raw =
|
|
31750
|
+
const raw = readFileSync22(backupPath, "utf-8");
|
|
31005
31751
|
mkdirSync16(dirname10(args.configPath), { recursive: true });
|
|
31006
31752
|
writeFileSync15(args.configPath, raw, "utf-8");
|
|
31007
31753
|
return { restored: true };
|
|
@@ -31090,7 +31836,7 @@ var claudeCodeConnector = {
|
|
|
31090
31836
|
import {
|
|
31091
31837
|
existsSync as existsSync30,
|
|
31092
31838
|
mkdirSync as mkdirSync17,
|
|
31093
|
-
readFileSync as
|
|
31839
|
+
readFileSync as readFileSync23,
|
|
31094
31840
|
unlinkSync as unlinkSync3,
|
|
31095
31841
|
writeFileSync as writeFileSync17
|
|
31096
31842
|
} from "node:fs";
|
|
@@ -31149,7 +31895,7 @@ var codexConnector = {
|
|
|
31149
31895
|
});
|
|
31150
31896
|
let existing = "";
|
|
31151
31897
|
if (existsSync30(path)) {
|
|
31152
|
-
existing =
|
|
31898
|
+
existing = readFileSync23(path, "utf-8");
|
|
31153
31899
|
}
|
|
31154
31900
|
const conflict = findSectionRange(existing, args.name) !== null;
|
|
31155
31901
|
let next = existing;
|
|
@@ -31171,7 +31917,7 @@ var codexConnector = {
|
|
|
31171
31917
|
async remove(args) {
|
|
31172
31918
|
const path = configPath2();
|
|
31173
31919
|
if (existsSync30(path)) {
|
|
31174
|
-
const existing =
|
|
31920
|
+
const existing = readFileSync23(path, "utf-8");
|
|
31175
31921
|
const range = findSectionRange(existing, args.name);
|
|
31176
31922
|
if (range) {
|
|
31177
31923
|
const next = existing.slice(0, range.start) + existing.slice(range.end);
|
|
@@ -31184,7 +31930,7 @@ var codexConnector = {
|
|
|
31184
31930
|
agentId: this.id
|
|
31185
31931
|
});
|
|
31186
31932
|
if (!restored.restored && existsSync30(path)) {
|
|
31187
|
-
const after =
|
|
31933
|
+
const after = readFileSync23(path, "utf-8");
|
|
31188
31934
|
if (!after.trim()) {
|
|
31189
31935
|
try {
|
|
31190
31936
|
unlinkSync3(path);
|
|
@@ -32437,12 +33183,8 @@ function registerDeploymentCommands(program2) {
|
|
|
32437
33183
|
deployments.command("list").description("List deployments in an organization").option("--org <organizationId>", "Organization ID").option("--status <status>", `Filter: ${DEPLOYMENT_STATUSES.join(" | ")}`, "active").option("--limit <n>", "Maximum results (default 25)", parsePositiveIntOption("limit"), "25").action(runAction(async (opts) => {
|
|
32438
33184
|
const orgId = await resolveOrgId(opts.org);
|
|
32439
33185
|
const status = validateChoice("status", opts.status, DEPLOYMENT_STATUSES);
|
|
32440
|
-
const
|
|
32441
|
-
|
|
32442
|
-
status,
|
|
32443
|
-
limit: opts.limit
|
|
32444
|
-
});
|
|
32445
|
-
printList(data.deployments.map((d) => ({
|
|
33186
|
+
const { rows: deploymentRows, payload } = await fetchAllPagesWithEnvelope("/api/v1/deployments", { organizationId: orgId, status }, "deployments", { max: parseRowCap(opts.limit) });
|
|
33187
|
+
printList(deploymentRows.map((d) => ({
|
|
32446
33188
|
id: d.id,
|
|
32447
33189
|
server: d.serverName,
|
|
32448
33190
|
project: d.projectName,
|
|
@@ -32460,7 +33202,7 @@ function registerDeploymentCommands(program2) {
|
|
|
32460
33202
|
{ key: "status", label: "Status", width: 10 },
|
|
32461
33203
|
{ key: "version", label: "Version", width: 10 },
|
|
32462
33204
|
{ key: "deployed", label: "Deployed", width: 22 }
|
|
32463
|
-
],
|
|
33205
|
+
], payload);
|
|
32464
33206
|
}));
|
|
32465
33207
|
deployments.command("get <deployment>").description("Show one deployment by id or selector (latest | active | previous, with --server)").option("--org <organizationId>", "Organization ID").option("--server <server>", SELECTOR_SERVER_HELP).addHelpText("after", SELECTOR_HELP).action(runAction(async (deploymentRef, opts) => {
|
|
32466
33208
|
const orgId = await resolveOrgId(opts.org);
|
|
@@ -32666,12 +33408,12 @@ async function confirmRollback(deploymentId) {
|
|
|
32666
33408
|
}
|
|
32667
33409
|
|
|
32668
33410
|
// src/commands/doctor.ts
|
|
32669
|
-
import { existsSync as existsSync36, statSync as statSync6, readFileSync as
|
|
33411
|
+
import { existsSync as existsSync36, statSync as statSync6, readFileSync as readFileSync25 } from "node:fs";
|
|
32670
33412
|
import { homedir as homedir9, platform as platform6 } from "node:os";
|
|
32671
33413
|
import { join as join32, delimiter as delimiter3 } from "node:path";
|
|
32672
33414
|
|
|
32673
33415
|
// src/lib/version-check.ts
|
|
32674
|
-
import { existsSync as existsSync35, mkdirSync as mkdirSync18, readFileSync as
|
|
33416
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync18, readFileSync as readFileSync24, writeFileSync as writeFileSync21 } from "node:fs";
|
|
32675
33417
|
import { join as join31 } from "node:path";
|
|
32676
33418
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
32677
33419
|
var FETCH_TIMEOUT_MS2 = 2000;
|
|
@@ -32686,7 +33428,7 @@ function readCache() {
|
|
|
32686
33428
|
if (!existsSync35(cacheFile()))
|
|
32687
33429
|
return null;
|
|
32688
33430
|
try {
|
|
32689
|
-
const raw = JSON.parse(
|
|
33431
|
+
const raw = JSON.parse(readFileSync24(cacheFile(), "utf-8"));
|
|
32690
33432
|
if (typeof raw.latest !== "string" || typeof raw.fetchedAt !== "number")
|
|
32691
33433
|
return null;
|
|
32692
33434
|
return { latest: raw.latest, fetchedAt: raw.fetchedAt };
|
|
@@ -32832,7 +33574,7 @@ function checkConfigFile() {
|
|
|
32832
33574
|
} catch {}
|
|
32833
33575
|
}
|
|
32834
33576
|
try {
|
|
32835
|
-
JSON.parse(
|
|
33577
|
+
JSON.parse(readFileSync25(path, "utf-8"));
|
|
32836
33578
|
} catch (err) {
|
|
32837
33579
|
return {
|
|
32838
33580
|
name: "Config file",
|
|
@@ -33678,7 +34420,7 @@ function registerInstallationCommands(program2) {
|
|
|
33678
34420
|
}
|
|
33679
34421
|
|
|
33680
34422
|
// src/commands/invoke.ts
|
|
33681
|
-
import { existsSync as existsSync37, readFileSync as
|
|
34423
|
+
import { existsSync as existsSync37, readFileSync as readFileSync26 } from "node:fs";
|
|
33682
34424
|
import { join as join33 } from "node:path";
|
|
33683
34425
|
|
|
33684
34426
|
// src/lib/dev/local-invoke.ts
|
|
@@ -33771,9 +34513,9 @@ function selectCodeModeInvocation(args) {
|
|
|
33771
34513
|
}
|
|
33772
34514
|
function readCodeOption(raw) {
|
|
33773
34515
|
if (raw === "@-")
|
|
33774
|
-
return
|
|
34516
|
+
return readFileSync26(0, "utf-8");
|
|
33775
34517
|
if (raw.startsWith("@"))
|
|
33776
|
-
return
|
|
34518
|
+
return readFileSync26(raw.slice(1), "utf-8");
|
|
33777
34519
|
return raw;
|
|
33778
34520
|
}
|
|
33779
34521
|
function parseTimeoutMs4(value, fallbackMs) {
|
|
@@ -34055,7 +34797,7 @@ async function runLocalInvoke(args) {
|
|
|
34055
34797
|
throw new CliExitError(1);
|
|
34056
34798
|
}
|
|
34057
34799
|
const env = {
|
|
34058
|
-
...args.envFile ? parseEnvFile(
|
|
34800
|
+
...args.envFile ? parseEnvFile(readFileSync26(args.envFile, "utf-8")) : {},
|
|
34059
34801
|
...parseEnvPairs(args.envPairs)
|
|
34060
34802
|
};
|
|
34061
34803
|
const port = await findAvailablePort({ range: 40, start: 43117 });
|
|
@@ -34653,7 +35395,7 @@ function registerUsageCommands(program2) {
|
|
|
34653
35395
|
}
|
|
34654
35396
|
|
|
34655
35397
|
// src/commands/metrics.ts
|
|
34656
|
-
function
|
|
35398
|
+
function formatPercent2(rate) {
|
|
34657
35399
|
if (rate == null)
|
|
34658
35400
|
return "—";
|
|
34659
35401
|
return `${(rate * 100).toFixed(2)}%`;
|
|
@@ -34715,7 +35457,7 @@ function registerMetricsCommands(program2) {
|
|
|
34715
35457
|
"rate-limited": m2.totals.rateLimitedCount,
|
|
34716
35458
|
unauthorized: m2.totals.unauthorizedCount,
|
|
34717
35459
|
failed: m2.totals.failedCount,
|
|
34718
|
-
"error rate":
|
|
35460
|
+
"error rate": formatPercent2(errorRate),
|
|
34719
35461
|
"avg duration": formatMs(m2.averageDurationMs),
|
|
34720
35462
|
"avg cpu": formatMs(m2.averageCpuTimeMs),
|
|
34721
35463
|
"last event": m2.lastEventAt ? formatDate(m2.lastEventAt) : "—"
|
|
@@ -35047,7 +35789,7 @@ import {
|
|
|
35047
35789
|
existsSync as existsSync38,
|
|
35048
35790
|
mkdirSync as mkdirSync19,
|
|
35049
35791
|
readdirSync as readdirSync5,
|
|
35050
|
-
readFileSync as
|
|
35792
|
+
readFileSync as readFileSync27,
|
|
35051
35793
|
rmSync as rmSync3,
|
|
35052
35794
|
statSync as statSync7
|
|
35053
35795
|
} from "node:fs";
|
|
@@ -35067,7 +35809,7 @@ function readManifestFromPackageDir(packageDir) {
|
|
|
35067
35809
|
return null;
|
|
35068
35810
|
let pkg;
|
|
35069
35811
|
try {
|
|
35070
|
-
pkg = JSON.parse(
|
|
35812
|
+
pkg = JSON.parse(readFileSync27(pkgPath, "utf-8"));
|
|
35071
35813
|
} catch {
|
|
35072
35814
|
return null;
|
|
35073
35815
|
}
|
|
@@ -35819,8 +36561,9 @@ var CLI_EXAMPLES = {
|
|
|
35819
36561
|
"tools handlers list": ["mcp tools handlers list srv_abc"],
|
|
35820
36562
|
"tools diff": ["mcp tools diff srv_abc query_users"],
|
|
35821
36563
|
"tools enrich": [
|
|
35822
|
-
"mcp tools enrich
|
|
35823
|
-
"mcp tools enrich
|
|
36564
|
+
"mcp tools enrich query_users",
|
|
36565
|
+
"mcp tools enrich --all --server srv_abc",
|
|
36566
|
+
"mcp tools enrich --all --only query_users,create_user"
|
|
35824
36567
|
],
|
|
35825
36568
|
"tools pull": ["mcp tools pull srv_abc"],
|
|
35826
36569
|
"tools pull-handler": ["mcp tools pull-handler srv_abc query_users"],
|
|
@@ -35861,7 +36604,7 @@ var PLAYBOOKS = [
|
|
|
35861
36604
|
{
|
|
35862
36605
|
description: "Re-write every tool description for agent comprehension.",
|
|
35863
36606
|
verb: "tools enrich",
|
|
35864
|
-
command: "mcp tools enrich srv_abc"
|
|
36607
|
+
command: "mcp tools enrich --all --server srv_abc"
|
|
35865
36608
|
},
|
|
35866
36609
|
{
|
|
35867
36610
|
description: "Deploy to the proxy.",
|
|
@@ -36800,7 +37543,7 @@ function displayValueFor(field, value) {
|
|
|
36800
37543
|
import {
|
|
36801
37544
|
existsSync as existsSync39,
|
|
36802
37545
|
mkdirSync as mkdirSync20,
|
|
36803
|
-
readFileSync as
|
|
37546
|
+
readFileSync as readFileSync28,
|
|
36804
37547
|
writeFileSync as writeFileSync22
|
|
36805
37548
|
} from "node:fs";
|
|
36806
37549
|
import { join as join35 } from "node:path";
|
|
@@ -36882,7 +37625,7 @@ function readTuiState() {
|
|
|
36882
37625
|
return {};
|
|
36883
37626
|
let parsed;
|
|
36884
37627
|
try {
|
|
36885
|
-
parsed = JSON.parse(
|
|
37628
|
+
parsed = JSON.parse(readFileSync28(path, "utf-8"));
|
|
36886
37629
|
} catch {
|
|
36887
37630
|
return {};
|
|
36888
37631
|
}
|