@mcpcloud/cli 0.24.1 → 0.25.0-next-20260923001855
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 +6 -2
- package/dist/index.js +1297 -733
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4155,6 +4155,45 @@ ${suggestions.map((s) => ` • ${describeCandidate(s)}`).join(`
|
|
|
4155
4155
|
Run \`${kind.listHint}\` to see available ${kind.label}s.`);
|
|
4156
4156
|
}
|
|
4157
4157
|
|
|
4158
|
+
// src/lib/resolve-cross-org.ts
|
|
4159
|
+
async function otherOrganizationIds(currentOrgId) {
|
|
4160
|
+
try {
|
|
4161
|
+
const data = await api.get("/api/v1/organizations");
|
|
4162
|
+
return data.organizations.filter((org) => org.id && org.id !== currentOrgId).map((org) => org.id).slice(0, 10);
|
|
4163
|
+
} catch {
|
|
4164
|
+
return [];
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
async function findInAnotherOrganization(ref, currentOrgId, loadIn) {
|
|
4168
|
+
const lower = ref.toLowerCase();
|
|
4169
|
+
for (const organizationId of await otherOrganizationIds(currentOrgId)) {
|
|
4170
|
+
let candidates;
|
|
4171
|
+
try {
|
|
4172
|
+
candidates = await loadIn(organizationId);
|
|
4173
|
+
} catch {
|
|
4174
|
+
continue;
|
|
4175
|
+
}
|
|
4176
|
+
const found = candidates.find((candidate) => candidate.id === ref) ?? candidates.find((candidate) => candidate.name.toLowerCase() === lower) ?? candidates.find((candidate) => candidate.slug === lower);
|
|
4177
|
+
if (found) {
|
|
4178
|
+
return { organizationId, id: found.id, name: found.name };
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
return null;
|
|
4182
|
+
}
|
|
4183
|
+
function isOrganizationAccessError(err) {
|
|
4184
|
+
return err instanceof McpCloudApiError && (err.error.code === "organization_access_denied" || err.error.code === "organization_access_required");
|
|
4185
|
+
}
|
|
4186
|
+
async function organizationAccessError(args) {
|
|
4187
|
+
if (args.crossOrganizationHint) {
|
|
4188
|
+
const elsewhere = await findInAnotherOrganization(args.ref, args.organizationId, args.loadIn);
|
|
4189
|
+
if (elsewhere) {
|
|
4190
|
+
return foundInAnotherOrganizationError(args.kind, args.ref, elsewhere);
|
|
4191
|
+
}
|
|
4192
|
+
}
|
|
4193
|
+
return new CliExitError(1, `You are not a member of organization ${args.organizationId} (from --org or your saved default).
|
|
4194
|
+
` + `If the ${args.kind.label} belongs to another organization, pass --org <id> — \`mcp orgs list\` shows yours.`);
|
|
4195
|
+
}
|
|
4196
|
+
|
|
4158
4197
|
// src/lib/resolve.ts
|
|
4159
4198
|
var KINDS = {
|
|
4160
4199
|
server: {
|
|
@@ -4244,30 +4283,6 @@ async function resolveQualified(kind, spec, ref, scope) {
|
|
|
4244
4283
|
const settled = await settleMatches(spec, ref, matches);
|
|
4245
4284
|
return { id: settled.id, matchedBy: "qualified", name: settled.name };
|
|
4246
4285
|
}
|
|
4247
|
-
async function otherOrganizationIds(currentOrgId) {
|
|
4248
|
-
try {
|
|
4249
|
-
const data = await api.get("/api/v1/organizations");
|
|
4250
|
-
return data.organizations.filter((org) => org.id && org.id !== currentOrgId).map((org) => org.id).slice(0, 10);
|
|
4251
|
-
} catch {
|
|
4252
|
-
return [];
|
|
4253
|
-
}
|
|
4254
|
-
}
|
|
4255
|
-
async function findInAnotherOrganization(kind, spec, ref, scope) {
|
|
4256
|
-
const lower = ref.toLowerCase();
|
|
4257
|
-
for (const organizationId of await otherOrganizationIds(scope.organizationId)) {
|
|
4258
|
-
let candidates;
|
|
4259
|
-
try {
|
|
4260
|
-
candidates = await loadCandidates(kind, spec, { organizationId });
|
|
4261
|
-
} catch {
|
|
4262
|
-
continue;
|
|
4263
|
-
}
|
|
4264
|
-
const found = candidates.find((candidate) => candidate.id === ref) ?? candidates.find((candidate) => candidate.name.toLowerCase() === lower) ?? candidates.find((candidate) => candidate.slug === lower);
|
|
4265
|
-
if (found) {
|
|
4266
|
-
return { organizationId, id: found.id, name: found.name };
|
|
4267
|
-
}
|
|
4268
|
-
}
|
|
4269
|
-
return null;
|
|
4270
|
-
}
|
|
4271
4286
|
async function resolveRef(kind, reference, scope, options = {}) {
|
|
4272
4287
|
const spec = KINDS[kind];
|
|
4273
4288
|
const ref = reference.trim();
|
|
@@ -4286,6 +4301,15 @@ async function resolveRef(kind, reference, scope, options = {}) {
|
|
|
4286
4301
|
await probeById(spec, scope, ref);
|
|
4287
4302
|
return { id: ref, matchedBy: "id" };
|
|
4288
4303
|
} catch (err) {
|
|
4304
|
+
if (isOrganizationAccessError(err)) {
|
|
4305
|
+
throw await organizationAccessError({
|
|
4306
|
+
kind: spec,
|
|
4307
|
+
ref,
|
|
4308
|
+
organizationId: scope.organizationId,
|
|
4309
|
+
crossOrganizationHint: options.crossOrganizationHint !== false,
|
|
4310
|
+
loadIn: (organizationId) => loadCandidates(kind, spec, { organizationId })
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4289
4313
|
if (!isNotFoundError(err)) {
|
|
4290
4314
|
if (err instanceof McpCloudApiError)
|
|
4291
4315
|
throw err;
|
|
@@ -4301,7 +4325,16 @@ async function resolveRef(kind, reference, scope, options = {}) {
|
|
|
4301
4325
|
let candidates;
|
|
4302
4326
|
try {
|
|
4303
4327
|
candidates = await loadCandidates(kind, spec, scope);
|
|
4304
|
-
} catch {
|
|
4328
|
+
} catch (err) {
|
|
4329
|
+
if (isOrganizationAccessError(err)) {
|
|
4330
|
+
throw await organizationAccessError({
|
|
4331
|
+
kind: spec,
|
|
4332
|
+
ref,
|
|
4333
|
+
organizationId: scope.organizationId,
|
|
4334
|
+
crossOrganizationHint: options.crossOrganizationHint !== false,
|
|
4335
|
+
loadIn: (organizationId) => loadCandidates(kind, spec, { organizationId })
|
|
4336
|
+
});
|
|
4337
|
+
}
|
|
4305
4338
|
return { id: ref, matchedBy: "verbatim" };
|
|
4306
4339
|
}
|
|
4307
4340
|
const byId = candidates.find((c) => c.id === ref);
|
|
@@ -4323,7 +4356,7 @@ async function resolveRef(kind, reference, scope, options = {}) {
|
|
|
4323
4356
|
return { id: ref, matchedBy: "verbatim" };
|
|
4324
4357
|
} catch {
|
|
4325
4358
|
if (options.crossOrganizationHint !== false) {
|
|
4326
|
-
const elsewhere = await findInAnotherOrganization(kind, spec,
|
|
4359
|
+
const elsewhere = await findInAnotherOrganization(ref, scope.organizationId, (organizationId) => loadCandidates(kind, spec, { organizationId }));
|
|
4327
4360
|
if (elsewhere) {
|
|
4328
4361
|
throw foundInAnotherOrganizationError(spec, ref, elsewhere);
|
|
4329
4362
|
}
|
|
@@ -7668,10 +7701,6 @@ function registerServerExportCommands(servers) {
|
|
|
7668
7701
|
}));
|
|
7669
7702
|
}
|
|
7670
7703
|
|
|
7671
|
-
// src/commands/servers-lifecycle.ts
|
|
7672
|
-
import { existsSync as existsSync6 } from "node:fs";
|
|
7673
|
-
import { isAbsolute as isAbsolute2, resolve as resolve4 } from "node:path";
|
|
7674
|
-
|
|
7675
7704
|
// src/lib/deployment-display.ts
|
|
7676
7705
|
function formatDeploymentExposure(deployment) {
|
|
7677
7706
|
const exposure = deployment.exposureTarget ?? null;
|
|
@@ -7689,6 +7718,118 @@ function formatDeploymentExposure(deployment) {
|
|
|
7689
7718
|
return target ?? "unknown";
|
|
7690
7719
|
}
|
|
7691
7720
|
|
|
7721
|
+
// src/commands/servers-deploy-shared.ts
|
|
7722
|
+
var DEPLOY_TARGETS = [
|
|
7723
|
+
"workersDev",
|
|
7724
|
+
"managedDomain"
|
|
7725
|
+
];
|
|
7726
|
+
var ACCESS_MODES = [
|
|
7727
|
+
"public",
|
|
7728
|
+
"privateOrg",
|
|
7729
|
+
"unlisted"
|
|
7730
|
+
];
|
|
7731
|
+
function emptyRuntimeConfig() {
|
|
7732
|
+
return {
|
|
7733
|
+
upstreamBaseUrl: null,
|
|
7734
|
+
upstreamApiKey: null,
|
|
7735
|
+
upstreamApiKeyHeader: null,
|
|
7736
|
+
upstreamApiKeyPrefix: null,
|
|
7737
|
+
upstreamCustomHeaders: null,
|
|
7738
|
+
additionalPlainTextBindings: null,
|
|
7739
|
+
additionalSecretBindings: null
|
|
7740
|
+
};
|
|
7741
|
+
}
|
|
7742
|
+
function reportDeployFailure(terminalEvent) {
|
|
7743
|
+
if (!terminalEvent || !isFailureEvent(terminalEvent))
|
|
7744
|
+
return false;
|
|
7745
|
+
const reason = formatEventDetails(terminalEvent.details);
|
|
7746
|
+
printError(`Deploy failed: ${reason ?? terminalEvent.message}`);
|
|
7747
|
+
return true;
|
|
7748
|
+
}
|
|
7749
|
+
async function isOnlyDeployment(args) {
|
|
7750
|
+
try {
|
|
7751
|
+
const listing = await api.get(`/api/v1/server/deployments?organizationId=${encodeURIComponent(args.orgId)}&serverId=${encodeURIComponent(args.serverId)}&limit=2`);
|
|
7752
|
+
const rows = listing.deployments;
|
|
7753
|
+
if (!Array.isArray(rows) || rows.length === 0)
|
|
7754
|
+
return false;
|
|
7755
|
+
return rows.every((row) => row.id === args.deploymentId);
|
|
7756
|
+
} catch {
|
|
7757
|
+
return false;
|
|
7758
|
+
}
|
|
7759
|
+
}
|
|
7760
|
+
async function refreshDeploymentRow(args) {
|
|
7761
|
+
try {
|
|
7762
|
+
const refreshed = await api.get(`/api/v1/server/deployments?organizationId=${encodeURIComponent(args.orgId)}&serverId=${encodeURIComponent(args.serverId)}&limit=5`);
|
|
7763
|
+
return (refreshed.deployments ?? []).find((entry) => entry.id === args.deploymentId) ?? null;
|
|
7764
|
+
} catch {
|
|
7765
|
+
return null;
|
|
7766
|
+
}
|
|
7767
|
+
}
|
|
7768
|
+
async function waitForDeploy(args) {
|
|
7769
|
+
const dep = args.deployment;
|
|
7770
|
+
let terminalEvent = null;
|
|
7771
|
+
let waitDone = !args.wait;
|
|
7772
|
+
if (args.wait) {
|
|
7773
|
+
if (isTerminalDeploymentStatus(dep.status)) {
|
|
7774
|
+
waitDone = true;
|
|
7775
|
+
} else {
|
|
7776
|
+
const totalTimeoutMs = Math.max(10, Number.parseInt(args.waitTimeout, 10) || 300) * 1000;
|
|
7777
|
+
if (!isJsonMode()) {
|
|
7778
|
+
printStep(`Following events for ${c.bold(dep.deploymentId)}${args.announceTimeout ? ` (timeout ${Math.round(totalTimeoutMs / 1000)}s)` : ""}…`);
|
|
7779
|
+
}
|
|
7780
|
+
const result = await followDeploymentEvents({
|
|
7781
|
+
organizationId: args.orgId,
|
|
7782
|
+
deploymentId: dep.deploymentId,
|
|
7783
|
+
totalTimeoutMs,
|
|
7784
|
+
onEvent: (event) => {
|
|
7785
|
+
if (!isJsonMode())
|
|
7786
|
+
printInfo(formatEventLine(event));
|
|
7787
|
+
}
|
|
7788
|
+
});
|
|
7789
|
+
terminalEvent = result.terminalEvent;
|
|
7790
|
+
waitDone = result.done;
|
|
7791
|
+
if (!waitDone && !isJsonMode()) {
|
|
7792
|
+
printError(`Deploy did not reach a terminal state within ${Math.round(totalTimeoutMs / 1000)}s. Check \`mcp deployments get ${dep.deploymentId}\` and \`mcp deployments health ${dep.deploymentId}\`, or tail \`mcp deployments logs ${dep.deploymentId} --follow\`.`);
|
|
7793
|
+
}
|
|
7794
|
+
}
|
|
7795
|
+
}
|
|
7796
|
+
const deployFailed = dep.status === "failed" || Boolean(terminalEvent && isFailureEvent(terminalEvent));
|
|
7797
|
+
return { terminalEvent, waitDone, deployFailed };
|
|
7798
|
+
}
|
|
7799
|
+
function terminalEventFields(terminalEvent) {
|
|
7800
|
+
if (!terminalEvent)
|
|
7801
|
+
return {};
|
|
7802
|
+
return {
|
|
7803
|
+
"terminal event": `${terminalEvent.stage} (${terminalEvent.level}) — ${terminalEvent.message}`,
|
|
7804
|
+
"terminal at": formatDate(terminalEvent.timestamp)
|
|
7805
|
+
};
|
|
7806
|
+
}
|
|
7807
|
+
async function finishDeployReport(args) {
|
|
7808
|
+
const dep = args.deployment;
|
|
7809
|
+
if (!args.accessModeRequested && dep.accessMode === "public" && await isOnlyDeployment({
|
|
7810
|
+
deploymentId: dep.deploymentId,
|
|
7811
|
+
orgId: args.orgId,
|
|
7812
|
+
serverId: args.serverId
|
|
7813
|
+
})) {
|
|
7814
|
+
printWarn("This first deployment is public — anyone with the URL can call it. Redeploy with --access-mode privateOrg or unlisted to require sign-in (recommended when the upstream API needs credentials).");
|
|
7815
|
+
}
|
|
7816
|
+
if (!args.wait && dep.status === "queued") {
|
|
7817
|
+
printInfo(`Deploy is running in the background — follow with \`mcp deployments get ${dep.deploymentId}\` or rerun with --wait.`);
|
|
7818
|
+
}
|
|
7819
|
+
if (args.wait && !args.outcome.waitDone)
|
|
7820
|
+
throw new CliExitError(1);
|
|
7821
|
+
if (args.outcome.deployFailed) {
|
|
7822
|
+
if (!reportDeployFailure(args.outcome.terminalEvent)) {
|
|
7823
|
+
printError("Deploy failed.");
|
|
7824
|
+
}
|
|
7825
|
+
throw new CliExitError(1);
|
|
7826
|
+
}
|
|
7827
|
+
}
|
|
7828
|
+
|
|
7829
|
+
// src/commands/servers-push-spec.ts
|
|
7830
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
7831
|
+
import { isAbsolute as isAbsolute2, resolve as resolve4 } from "node:path";
|
|
7832
|
+
|
|
7692
7833
|
// src/lib/dev/spec-watcher.ts
|
|
7693
7834
|
import { createHash as createHash2 } from "node:crypto";
|
|
7694
7835
|
import {
|
|
@@ -7706,7 +7847,8 @@ async function regenerateDevBundle(args) {
|
|
|
7706
7847
|
sourceType: args.sourceType ?? "openapi",
|
|
7707
7848
|
preserveEnrichment: args.preserveEnrichment ?? true,
|
|
7708
7849
|
...args.applySpec ? { applySpec: true } : {},
|
|
7709
|
-
...args.dryRunApply ? { dryRunApply: true } : {}
|
|
7850
|
+
...args.dryRunApply ? { dryRunApply: true } : {},
|
|
7851
|
+
...args.renameToOperationIds ? { renameToOperationIds: true } : {}
|
|
7710
7852
|
});
|
|
7711
7853
|
return {
|
|
7712
7854
|
ok: true,
|
|
@@ -7812,39 +7954,374 @@ function watchSpecFile(options) {
|
|
|
7812
7954
|
};
|
|
7813
7955
|
}
|
|
7814
7956
|
|
|
7815
|
-
// src/commands/servers-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
"
|
|
7819
|
-
|
|
7820
|
-
|
|
7957
|
+
// src/commands/servers-push-spec-deploy.ts
|
|
7958
|
+
async function deployPushedSpec(args) {
|
|
7959
|
+
const { result, orgId, serverId, target, accessMode, opts } = args;
|
|
7960
|
+
const serverData = await api.get("/api/v1/server", {
|
|
7961
|
+
organizationId: orgId,
|
|
7962
|
+
serverId
|
|
7963
|
+
});
|
|
7964
|
+
const body = {
|
|
7965
|
+
organizationId: orgId,
|
|
7966
|
+
projectId: serverData.server.projectId,
|
|
7967
|
+
serverId,
|
|
7968
|
+
target,
|
|
7969
|
+
...accessMode ? { accessMode } : {},
|
|
7970
|
+
runtimeConfig: emptyRuntimeConfig()
|
|
7971
|
+
};
|
|
7972
|
+
if (!isJsonMode()) {
|
|
7973
|
+
printStep(`Deploying ${c.bold(serverId)} to ${c.cyan(target)}…`);
|
|
7974
|
+
}
|
|
7975
|
+
const data = await api.post("/api/v1/server/deploy", body);
|
|
7976
|
+
const dep = data.deployment;
|
|
7977
|
+
const outcome = await waitForDeploy({
|
|
7978
|
+
deployment: dep,
|
|
7979
|
+
orgId,
|
|
7980
|
+
wait: opts.wait,
|
|
7981
|
+
waitTimeout: opts.waitTimeout
|
|
7982
|
+
});
|
|
7983
|
+
const { terminalEvent, waitDone, deployFailed } = outcome;
|
|
7984
|
+
let final = dep;
|
|
7985
|
+
if (opts.wait && waitDone) {
|
|
7986
|
+
const row = await refreshDeploymentRow({
|
|
7987
|
+
deploymentId: dep.deploymentId,
|
|
7988
|
+
orgId,
|
|
7989
|
+
serverId
|
|
7990
|
+
});
|
|
7991
|
+
if (row)
|
|
7992
|
+
final = { ...dep, ...row };
|
|
7993
|
+
}
|
|
7994
|
+
if (isJsonMode()) {
|
|
7995
|
+
printJson({
|
|
7996
|
+
pushed: true,
|
|
7997
|
+
diff: result.diff,
|
|
7998
|
+
enrichmentApplied: result.enrichmentApplied,
|
|
7999
|
+
applied: result.applied,
|
|
8000
|
+
deployment: final,
|
|
8001
|
+
waitDone,
|
|
8002
|
+
terminalEvent
|
|
8003
|
+
});
|
|
8004
|
+
if (opts.wait && (!waitDone || deployFailed))
|
|
8005
|
+
throw new CliExitError(1);
|
|
8006
|
+
return;
|
|
8007
|
+
}
|
|
8008
|
+
printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
|
|
8009
|
+
printKeyValue({
|
|
8010
|
+
id: dep.deploymentId,
|
|
8011
|
+
...final.deploymentUrl ? { url: final.deploymentUrl } : {},
|
|
8012
|
+
target: final.target,
|
|
8013
|
+
"access mode": final.accessMode,
|
|
8014
|
+
status: final.status,
|
|
8015
|
+
health: final.healthStatus,
|
|
8016
|
+
...terminalEventFields(terminalEvent)
|
|
8017
|
+
});
|
|
8018
|
+
await finishDeployReport({
|
|
8019
|
+
deployment: dep,
|
|
8020
|
+
accessModeRequested: accessMode,
|
|
8021
|
+
orgId,
|
|
8022
|
+
serverId,
|
|
8023
|
+
wait: opts.wait,
|
|
8024
|
+
outcome
|
|
8025
|
+
});
|
|
8026
|
+
}
|
|
8027
|
+
|
|
8028
|
+
// src/commands/tool-name-drift.ts
|
|
8029
|
+
function nameDriftWarning(args) {
|
|
8030
|
+
const drift = args.drift ?? [];
|
|
8031
|
+
const example = drift[0];
|
|
8032
|
+
if (!example)
|
|
8033
|
+
return null;
|
|
8034
|
+
const subject = drift.length === 1 ? "1 tool keeps its current name" : `${drift.length} tools keep their current names`;
|
|
8035
|
+
const alternative = args.offerPushSpecFlag ? " or re-run with --rename-to-operation-ids" : "";
|
|
8036
|
+
return `${subject} (the naming rule changed: codegen now keeps the operationId's case, ` + `e.g. ${example.name} → ${example.codegenName}). ` + `Run \`mcp tools rename --from-operation-ids --server ${args.serverId}\`${alternative} to adopt it.`;
|
|
8037
|
+
}
|
|
8038
|
+
function formatNameList(names, max = 8) {
|
|
8039
|
+
if (names.length <= max)
|
|
8040
|
+
return names.join(", ");
|
|
8041
|
+
return `${names.slice(0, max).join(", ")}, +${names.length - max} more`;
|
|
8042
|
+
}
|
|
8043
|
+
|
|
8044
|
+
// src/commands/servers-push-spec-report.ts
|
|
8045
|
+
function printSpecReconcile(args) {
|
|
8046
|
+
const { result, serverId } = args;
|
|
8047
|
+
const applied = result.applied;
|
|
8048
|
+
if (!applied) {
|
|
8049
|
+
printNameDiff(result);
|
|
8050
|
+
if (args.renameToOperationIds)
|
|
8051
|
+
printRenameUnsupported(serverId);
|
|
8052
|
+
} else {
|
|
8053
|
+
printAppliedSummary(applied);
|
|
8054
|
+
printRenames(applied, serverId, args.renameToOperationIds);
|
|
8055
|
+
if (applied.removedKeys.length > 0) {
|
|
8056
|
+
printWarn(` ! ${applied.removedKeys.length} tool(s) no longer in the spec, left in place: ${applied.removedKeys.join(", ")}`);
|
|
8057
|
+
}
|
|
8058
|
+
for (const warning of applied.warnings)
|
|
8059
|
+
printWarn(` ! ${warning}`);
|
|
8060
|
+
if (!args.renameToOperationIds) {
|
|
8061
|
+
const drift = nameDriftWarning({
|
|
8062
|
+
drift: applied.nameDrift,
|
|
8063
|
+
serverId,
|
|
8064
|
+
offerPushSpecFlag: true
|
|
8065
|
+
});
|
|
8066
|
+
if (drift)
|
|
8067
|
+
printWarn(` ! ${drift}`);
|
|
8068
|
+
}
|
|
8069
|
+
if (!applied.dryRun)
|
|
8070
|
+
printStoredBundle(applied, serverId);
|
|
8071
|
+
if (applied.dryRun) {
|
|
8072
|
+
printInfo(c.dim(" → re-run with --apply to write this to the server’s tools."));
|
|
8073
|
+
}
|
|
8074
|
+
}
|
|
8075
|
+
if (result.enrichmentApplied) {
|
|
8076
|
+
printInfo(c.dim(" → preserved per-tool enrichment."));
|
|
8077
|
+
}
|
|
8078
|
+
printSuccess(applied && !applied.dryRun ? "Spec applied." : "Bundle regenerated (preview).");
|
|
8079
|
+
}
|
|
8080
|
+
function printNameDiff(result) {
|
|
8081
|
+
const summary = summarizeDiff(result.diff);
|
|
8082
|
+
if (!summary.hasChanges) {
|
|
8083
|
+
printInfo(c.dim(" → no tool surface changes; bundle refreshed."));
|
|
8084
|
+
return;
|
|
8085
|
+
}
|
|
8086
|
+
printInfo(` → ${summary.total} tool change${summary.total === 1 ? "" : "s"} by name (nothing written):`);
|
|
8087
|
+
printInfo(summary.text);
|
|
8088
|
+
}
|
|
8089
|
+
function withNames(names, label) {
|
|
8090
|
+
const head = `${names.length} ${label}`;
|
|
8091
|
+
return names.length > 0 ? `${head} (${formatNameList(names)})` : head;
|
|
8092
|
+
}
|
|
8093
|
+
function printAppliedSummary(applied) {
|
|
8094
|
+
const lead = applied.dryRun ? "would apply (nothing written)" : "applied";
|
|
8095
|
+
printInfo(` → ${lead}: ${withNames(applied.inserted, "inserted")}, ` + `${withNames(applied.updated, "updated")}, ${applied.unchanged} unchanged`);
|
|
8096
|
+
}
|
|
8097
|
+
function printRenames(applied, serverId, requested) {
|
|
8098
|
+
if (!requested)
|
|
8099
|
+
return;
|
|
8100
|
+
const renamed = applied.renamed;
|
|
8101
|
+
if (renamed === undefined) {
|
|
8102
|
+
printRenameUnsupported(serverId);
|
|
8103
|
+
return;
|
|
8104
|
+
}
|
|
8105
|
+
if (applied.renameError) {
|
|
8106
|
+
printWarn(` ! the spec was ${applied.dryRun ? "previewed" : "applied"}, but no tools were renamed: ${applied.renameError} ` + `See \`mcp tools rename --from-operation-ids --server ${serverId} --dry-run\`.`);
|
|
8107
|
+
return;
|
|
8108
|
+
}
|
|
8109
|
+
if (renamed.length === 0) {
|
|
8110
|
+
printInfo(c.dim(" → no tools to rename; names already follow the operationId."));
|
|
8111
|
+
return;
|
|
8112
|
+
}
|
|
8113
|
+
printInfo(` → ${applied.dryRun ? "would rename" : "renamed"} ${renamed.length} tool${renamed.length === 1 ? "" : "s"} to the operationId's case:`);
|
|
8114
|
+
for (const entry of renamed)
|
|
8115
|
+
printInfo(` ${entry.from} → ${entry.to}`);
|
|
8116
|
+
if (!applied.dryRun) {
|
|
8117
|
+
printInfo(c.dim(" → the live server keeps the old names until the next deploy; after it, agents calling an old name get unknown-tool."));
|
|
8118
|
+
}
|
|
8119
|
+
}
|
|
8120
|
+
function printRenameUnsupported(serverId) {
|
|
8121
|
+
printWarn(` ! this backend did not report any renames, so it may not support --rename-to-operation-ids. Run \`mcp tools rename --from-operation-ids --server ${serverId}\` instead.`);
|
|
8122
|
+
}
|
|
8123
|
+
function printStoredBundle(applied, serverId) {
|
|
8124
|
+
if (applied.storedBundle) {
|
|
8125
|
+
printInfo(c.dim(" → stored bundle regenerated; a deploy now ships this spec."));
|
|
8126
|
+
return;
|
|
8127
|
+
}
|
|
8128
|
+
printWarn(` ! stored bundle NOT regenerated${applied.storeError ? `: ${applied.storeError}` : ""}. ` + `Deploy ships the stored bundle — run \`mcp servers generate ${serverId} --store\` before deploying.`);
|
|
8129
|
+
}
|
|
8130
|
+
|
|
8131
|
+
// src/commands/servers-push-spec.ts
|
|
8132
|
+
var HELP_AFTER = [
|
|
8133
|
+
"",
|
|
8134
|
+
"Examples:",
|
|
8135
|
+
" # Preview what the spec would change (writes nothing):",
|
|
8136
|
+
" $ mcp servers push-spec srv_123 --spec ./openapi.yaml",
|
|
8137
|
+
"",
|
|
8138
|
+
" # Actually reconcile the tools against the spec, then deploy:",
|
|
8139
|
+
" $ mcp servers push-spec srv_123 --spec ./openapi.yaml --apply --deploy --wait",
|
|
8140
|
+
"",
|
|
8141
|
+
" # Push and deploy in one step:",
|
|
8142
|
+
" $ mcp servers push-spec srv_123 --spec ./openapi.yaml --deploy --wait",
|
|
8143
|
+
"",
|
|
8144
|
+
" # Adopt the current naming rule while applying (getrecording → getRecording):",
|
|
8145
|
+
" $ mcp servers push-spec srv_123 --spec ./openapi.yaml --apply --rename-to-operation-ids",
|
|
8146
|
+
"",
|
|
8147
|
+
" # Idempotent retry-safe push in CI:",
|
|
8148
|
+
" $ mcp --ci --idempotency-key $GITHUB_RUN_ID servers push-spec srv_123 --deploy --wait",
|
|
8149
|
+
"",
|
|
8150
|
+
"Notes (--apply):",
|
|
8151
|
+
" Without --apply this regenerates the BUNDLE and previews the reconcile",
|
|
8152
|
+
" --apply would run (inserted / updated / unchanged) — it does not touch",
|
|
8153
|
+
" the stored tools, so a deploy afterwards still ships the old ones.",
|
|
8154
|
+
" --apply writes the tools AND regenerates the stored bundle (what",
|
|
8155
|
+
" deploy ships), so the next deploy advertises the new spec. If the",
|
|
8156
|
+
" bundle step fails you are told, and --deploy refuses to ship the",
|
|
8157
|
+
" stale one; `mcp servers generate <server> --store` repairs it.",
|
|
8158
|
+
" --apply reconciles: tools match on their stable operation key so",
|
|
8159
|
+
" renames survive, spec-derived shape is refreshed, and curated fields",
|
|
8160
|
+
" (description, tags, riskClass, projections, enrichment) are kept.",
|
|
8161
|
+
" Operations missing from the spec are REPORTED, never deleted.",
|
|
8162
|
+
" It is lossy for hand-edited input schemas — the spec becomes the",
|
|
8163
|
+
" truth — which is why it is opt-in.",
|
|
8164
|
+
"",
|
|
8165
|
+
"Notes (tool names):",
|
|
8166
|
+
" The reconcile never renames a tool. Codegen now keeps the operationId’s",
|
|
8167
|
+
" case (getRecording); servers created before that change keep their",
|
|
8168
|
+
" lowercased names (getrecording), and the preview says how many.",
|
|
8169
|
+
" --rename-to-operation-ids adopts the current rule for those tools,",
|
|
8170
|
+
" keeping tool ids, access grants and saved scenarios — the same rename",
|
|
8171
|
+
" `mcp tools rename --from-operation-ids` performs. The live server keeps",
|
|
8172
|
+
" the old names until the next deploy."
|
|
7821
8173
|
];
|
|
7822
|
-
function
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
return true;
|
|
8174
|
+
function registerServerPushSpecCommand(servers) {
|
|
8175
|
+
servers.command("push-spec <server>").description("Regenerate a server’s bundle from a local OpenAPI spec. Previews the tool changes; --apply writes them.").option("--org <organizationId>", "Organization ID").option("--apply", "Write the spec onto the server’s tools, refreshing their schemas. Without this the command only previews.").option("--spec <path>", "Path to the OpenAPI/Swagger spec file", "./openapi.yaml").option("--enrich-on-change", "Preserve per-tool enrichment when the spec changes").option("--rename-to-operation-ids", "Also rename tools whose names differ from codegen’s only by case (getrecording → getRecording). Previews without --apply.").option("--deploy", "Immediately deploy the regenerated bundle to Cloudflare Workers").option("--target <target>", `Deploy target (with --deploy): ${DEPLOY_TARGETS.join(" | ")}`, "workersDev").option("--access-mode <mode>", `Access mode (with --deploy): ${ACCESS_MODES.join(" | ")} (default: keep the server's current mode). Per-member upstream keys need privateOrg or unlisted.`).option("--wait", "With --deploy: tail deployment events until terminal state").option("--wait-timeout <seconds>", "Maximum seconds to wait for terminal state (default 300)", "300").addHelpText("after", HELP_AFTER.join(`
|
|
8176
|
+
`)).action(runAction(async (serverRef, opts) => {
|
|
8177
|
+
await runPushSpec(serverRef, opts);
|
|
8178
|
+
}));
|
|
7828
8179
|
}
|
|
7829
|
-
async function
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
8180
|
+
async function runPushSpec(serverRef, opts) {
|
|
8181
|
+
const cwd = process.cwd();
|
|
8182
|
+
const specPath = isAbsolute2(opts.spec) ? opts.spec : resolve4(cwd, opts.spec);
|
|
8183
|
+
if (!existsSync6(specPath)) {
|
|
8184
|
+
throw new Error(`Spec file not found: ${specPath}`);
|
|
8185
|
+
}
|
|
8186
|
+
const orgId = await resolveOrgId(opts.org);
|
|
8187
|
+
const serverId = await resolveServerId(serverRef, orgId);
|
|
8188
|
+
const target = validateChoice("target", opts.target, DEPLOY_TARGETS);
|
|
8189
|
+
const accessMode = opts.accessMode ? validateChoice("access-mode", opts.accessMode, ACCESS_MODES) : undefined;
|
|
8190
|
+
if (!isJsonMode()) {
|
|
8191
|
+
printStep(`Pushing spec ${c.bold(specPath)} to ${c.bold(serverId)}…`);
|
|
8192
|
+
}
|
|
8193
|
+
const { content } = readSpecFile(specPath);
|
|
8194
|
+
const result = await regenerateDevBundle({
|
|
8195
|
+
applySpec: true,
|
|
8196
|
+
dryRunApply: !opts.apply,
|
|
8197
|
+
organizationId: orgId,
|
|
8198
|
+
serverId,
|
|
8199
|
+
rawSpec: content,
|
|
8200
|
+
sourceType: "openapi",
|
|
8201
|
+
preserveEnrichment: Boolean(opts.enrichOnChange),
|
|
8202
|
+
renameToOperationIds: Boolean(opts.renameToOperationIds)
|
|
8203
|
+
});
|
|
8204
|
+
if (!result.ok) {
|
|
8205
|
+
const hint = result.code === "insufficient_scope" ? " push-spec --apply regenerates the server’s code, which needs the servers:build scope; mint the key with --preset ci-build or add --scope servers:build." : "";
|
|
8206
|
+
throw new Error(`Spec regen failed: ${result.message} (code: ${result.code})${hint}`);
|
|
8207
|
+
}
|
|
8208
|
+
if (!isJsonMode()) {
|
|
8209
|
+
printSpecReconcile({
|
|
8210
|
+
result,
|
|
8211
|
+
serverId,
|
|
8212
|
+
renameToOperationIds: Boolean(opts.renameToOperationIds)
|
|
8213
|
+
});
|
|
8214
|
+
}
|
|
8215
|
+
const appliedWithoutStore = result.applied !== null && !result.applied.dryRun && result.applied.storedBundle === false;
|
|
8216
|
+
if (!opts.deploy) {
|
|
8217
|
+
if (isJsonMode()) {
|
|
8218
|
+
printJson({
|
|
8219
|
+
applied: result.applied,
|
|
8220
|
+
diff: result.diff,
|
|
8221
|
+
enrichmentApplied: result.enrichmentApplied,
|
|
8222
|
+
pushed: Boolean(result.applied && !result.applied.dryRun)
|
|
8223
|
+
});
|
|
8224
|
+
} else if (appliedWithoutStore) {
|
|
8225
|
+
printInfo(c.dim(` → run \`mcp servers generate ${serverId} --store\`, then \`mcp servers deploy ${serverId} --wait\` to ship.`));
|
|
8226
|
+
} else {
|
|
8227
|
+
printInfo(c.dim(` → run \`mcp servers deploy ${serverId} --wait\` (or pass --deploy) to ship.`));
|
|
8228
|
+
}
|
|
8229
|
+
return;
|
|
7838
8230
|
}
|
|
8231
|
+
if (appliedWithoutStore) {
|
|
8232
|
+
throw new Error(`The spec was applied but the stored bundle was not regenerated${result.applied?.storeError ? ` (${result.applied.storeError})` : ""}. ` + `Run \`mcp servers generate ${serverId} --store\`, then deploy.`);
|
|
8233
|
+
}
|
|
8234
|
+
await deployPushedSpec({ result, orgId, serverId, target, accessMode, opts });
|
|
7839
8235
|
}
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
8236
|
+
|
|
8237
|
+
// src/commands/servers-regenerate.ts
|
|
8238
|
+
function registerServerRegenerateCommand(servers) {
|
|
8239
|
+
servers.command("regenerate <server>").description("Re-fetch a server's source spec and rebuild its tools with the current codegen (preserves customizations). Works for spec-URL, OpenAPI, and GraphQL sources; a pasted or manual server has nothing to re-fetch. Wraps POST /api/v1/server/regenerate.").option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").option("--source-url <url>", "Override the stored spec source URL (needed for GraphQL servers whose introspection endpoint wasn't persisted)").option("--dry-run", "Report what would change (per tool) without writing anything").addHelpText("after", [
|
|
8240
|
+
"",
|
|
8241
|
+
"Regenerate re-introspects/re-parses the source and rebuilds each tool with",
|
|
8242
|
+
"the current codegen, so codegen fixes reach existing servers. It does not",
|
|
8243
|
+
"deploy — run `mcp servers deploy` afterwards to ship the rebuilt bundle.",
|
|
8244
|
+
"It never renames a tool: names that predate the current naming rule",
|
|
8245
|
+
"(getrecording vs getRecording) are reported, and",
|
|
8246
|
+
"`mcp tools rename --from-operation-ids` adopts the rule.",
|
|
8247
|
+
"",
|
|
8248
|
+
"Examples:",
|
|
8249
|
+
" # See which tools the current codegen would change, writing nothing:",
|
|
8250
|
+
" $ mcp servers regenerate srv_123 --dry-run",
|
|
8251
|
+
"",
|
|
8252
|
+
" # Propagate codegen fixes into an existing server, then deploy:",
|
|
8253
|
+
" $ mcp servers regenerate srv_123 && mcp servers deploy srv_123 --wait",
|
|
8254
|
+
"",
|
|
8255
|
+
" # GraphQL server whose introspection endpoint must be supplied:",
|
|
8256
|
+
" $ mcp servers regenerate srv_123 --source-url https://api.example.com/graphql"
|
|
8257
|
+
].join(`
|
|
8258
|
+
`)).action(runAction(async (serverRef, opts) => {
|
|
8259
|
+
await runRegenerate(serverRef, opts);
|
|
8260
|
+
}));
|
|
8261
|
+
}
|
|
8262
|
+
async function runRegenerate(serverRef, opts) {
|
|
8263
|
+
const orgId = await resolveOrgId(opts.org);
|
|
8264
|
+
let projectId = await resolveOptionalProjectId(opts.project, orgId);
|
|
8265
|
+
const serverId = await resolveServerId(serverRef, orgId, projectId);
|
|
8266
|
+
if (!projectId) {
|
|
8267
|
+
const serverData = await api.get("/api/v1/server", { organizationId: orgId, serverId });
|
|
8268
|
+
projectId = serverData.server.projectId;
|
|
8269
|
+
}
|
|
8270
|
+
if (!isJsonMode()) {
|
|
8271
|
+
printStep(opts.dryRun ? `Previewing a regenerate of ${c.bold(serverId)} from its source spec…` : `Regenerating ${c.bold(serverId)} from its source spec…`);
|
|
8272
|
+
}
|
|
8273
|
+
const data = await api.post("/api/v1/server/regenerate", {
|
|
8274
|
+
organizationId: orgId,
|
|
8275
|
+
projectId,
|
|
8276
|
+
serverId,
|
|
8277
|
+
...opts.sourceUrl ? { sourceUrl: opts.sourceUrl } : {},
|
|
8278
|
+
...opts.dryRun ? { dryRun: true } : {}
|
|
8279
|
+
});
|
|
8280
|
+
const r = data.regeneration;
|
|
8281
|
+
const previewIgnored = Boolean(opts.dryRun) && r.dryRun !== true;
|
|
8282
|
+
if (isJsonMode()) {
|
|
8283
|
+
printJson({ regeneration: r });
|
|
8284
|
+
if (previewIgnored)
|
|
8285
|
+
throw new CliExitError(1);
|
|
8286
|
+
return;
|
|
7846
8287
|
}
|
|
8288
|
+
if (previewIgnored) {
|
|
8289
|
+
printError("This backend does not support --dry-run and REGENERATED the server. The report below is what was written.");
|
|
8290
|
+
}
|
|
8291
|
+
printRegeneration(r, serverId);
|
|
8292
|
+
if (previewIgnored)
|
|
8293
|
+
throw new CliExitError(1);
|
|
7847
8294
|
}
|
|
8295
|
+
function printRegeneration(r, serverId) {
|
|
8296
|
+
const dryRun = r.dryRun === true;
|
|
8297
|
+
printSuccess(dryRun ? `Regenerate preview for ${c.bold(serverId)} (${r.sourceType}) — nothing written.` : `Regenerated ${c.bold(serverId)} (${r.sourceType}).`);
|
|
8298
|
+
const verb = dryRun ? "that would change" : "updated";
|
|
8299
|
+
printKeyValue({
|
|
8300
|
+
[`tools ${verb}`]: String(r.updated),
|
|
8301
|
+
[`tools ${dryRun ? "that would be added" : "added"}`]: String(r.inserted),
|
|
8302
|
+
...r.unchanged !== undefined ? { "tools unchanged": String(r.unchanged) } : {},
|
|
8303
|
+
"tools dropped from spec": String(r.removed),
|
|
8304
|
+
generation: r.generationStatus
|
|
8305
|
+
});
|
|
8306
|
+
for (const change of r.changes ?? []) {
|
|
8307
|
+
printInfo(` ~ ${change.name}${change.fields.length > 0 ? ` (${change.fields.join(", ")})` : ""}`);
|
|
8308
|
+
}
|
|
8309
|
+
for (const name of r.insertedNames ?? [])
|
|
8310
|
+
printInfo(` + ${name}`);
|
|
8311
|
+
if (r.removed > 0) {
|
|
8312
|
+
printInfo(c.dim(` → ${r.removed} operation(s) no longer in the spec ${dryRun ? "would be" : "were"} kept, not deleted (removal stays a manual action): ${formatNameList(r.removedKeys, 5)}`));
|
|
8313
|
+
}
|
|
8314
|
+
const drift = nameDriftWarning({
|
|
8315
|
+
drift: r.nameDrift,
|
|
8316
|
+
serverId,
|
|
8317
|
+
offerPushSpecFlag: false
|
|
8318
|
+
});
|
|
8319
|
+
if (drift)
|
|
8320
|
+
printWarn(` ! ${drift}`);
|
|
8321
|
+
printInfo(c.dim(dryRun ? ` → run \`mcp servers regenerate ${serverId}\` (without --dry-run) to apply, then \`mcp servers deploy ${serverId} --wait\`.` : ` → run \`mcp servers deploy ${serverId} --wait\` to ship the regenerated bundle.`));
|
|
8322
|
+
}
|
|
8323
|
+
|
|
8324
|
+
// src/commands/servers-lifecycle.ts
|
|
7848
8325
|
function registerServerLifecycleCommands(servers) {
|
|
7849
8326
|
servers.command("deploy <server>").description("Deploy a server's generated bundle to Cloudflare Workers (closes the spec → deploy loop)").option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").option("--target <target>", `Deploy target: ${DEPLOY_TARGETS.join(" | ")}`, "workersDev").option("--access-mode <mode>", `Access mode: ${ACCESS_MODES.join(" | ")} (default: keep the server's current mode). A server with per-member upstream keys needs privateOrg or unlisted — public has no caller identity.`).option("--env <environment>", "Deploy into a named environment (lane) — its own worker, hostname and credentials. Omit, or pass production, for the default lane.").option("--channel <channel>", "Deprecated alias for --env: beta means --env beta, stable means production. Mutually exclusive with --env.").option("--upstream-base-url <url>", "Upstream API base URL bound into the worker").option("--upstream-api-key <key>", "Upstream API key (encrypted at rest server-side)").option("--upstream-api-key-header <header>", "Header name to inject the upstream API key on (default: Authorization)").option("--upstream-api-key-prefix <prefix>", 'Prefix for the upstream API key value (e.g. "Bearer ")').option("--wait", "Tail deployment events until the deploy reaches a terminal state").option("--wait-timeout <seconds>", "Maximum seconds to wait for terminal state (default 300)", "300").addHelpText("after", [
|
|
7850
8327
|
"",
|
|
@@ -7863,389 +8340,91 @@ function registerServerLifecycleCommands(servers) {
|
|
|
7863
8340
|
" $ mcp servers deploy srv_123 --env staging --wait"
|
|
7864
8341
|
].join(`
|
|
7865
8342
|
`)).action(runAction(async (serverRef, opts) => {
|
|
7866
|
-
|
|
7867
|
-
const environment = resolveDeployEnvironment(opts);
|
|
7868
|
-
const accessMode = opts.accessMode ? validateChoice("access-mode", opts.accessMode, ACCESS_MODES) : undefined;
|
|
7869
|
-
const orgId = await resolveOrgId(opts.org);
|
|
7870
|
-
let projectId = await resolveOptionalProjectId(opts.project, orgId);
|
|
7871
|
-
const serverId = await resolveServerId(serverRef, orgId, projectId);
|
|
7872
|
-
if (!projectId) {
|
|
7873
|
-
const serverData = await api.get("/api/v1/server", {
|
|
7874
|
-
organizationId: orgId,
|
|
7875
|
-
serverId
|
|
7876
|
-
});
|
|
7877
|
-
projectId = serverData.server.projectId;
|
|
7878
|
-
}
|
|
7879
|
-
const runtimeConfig = {
|
|
7880
|
-
upstreamBaseUrl: opts.upstreamBaseUrl ?? null,
|
|
7881
|
-
upstreamApiKey: opts.upstreamApiKey ?? null,
|
|
7882
|
-
upstreamApiKeyHeader: opts.upstreamApiKeyHeader ?? null,
|
|
7883
|
-
upstreamApiKeyPrefix: opts.upstreamApiKeyPrefix ?? null,
|
|
7884
|
-
upstreamCustomHeaders: null,
|
|
7885
|
-
additionalPlainTextBindings: null,
|
|
7886
|
-
additionalSecretBindings: null
|
|
7887
|
-
};
|
|
7888
|
-
const body = {
|
|
7889
|
-
organizationId: orgId,
|
|
7890
|
-
projectId,
|
|
7891
|
-
serverId,
|
|
7892
|
-
target,
|
|
7893
|
-
...accessMode ? { accessMode } : {},
|
|
7894
|
-
...environment ? { environment } : {},
|
|
7895
|
-
runtimeConfig
|
|
7896
|
-
};
|
|
7897
|
-
if (!isJsonMode()) {
|
|
7898
|
-
printStep(`Deploying ${c.bold(serverId)} to ${c.cyan(target)}${environment ? ` in ${c.cyan(environment)}` : ""}…`);
|
|
7899
|
-
}
|
|
7900
|
-
const data = await api.post("/api/v1/server/deploy", body);
|
|
7901
|
-
const dep = data.deployment;
|
|
7902
|
-
let terminalEvent = null;
|
|
7903
|
-
let waitDone = !opts.wait;
|
|
7904
|
-
if (opts.wait) {
|
|
7905
|
-
if (isTerminalDeploymentStatus(dep.status)) {
|
|
7906
|
-
waitDone = true;
|
|
7907
|
-
} else {
|
|
7908
|
-
const totalTimeoutMs = Math.max(10, Number.parseInt(opts.waitTimeout, 10) || 300) * 1000;
|
|
7909
|
-
if (!isJsonMode()) {
|
|
7910
|
-
printStep(`Following events for ${c.bold(dep.deploymentId)} (timeout ${Math.round(totalTimeoutMs / 1000)}s)…`);
|
|
7911
|
-
}
|
|
7912
|
-
const result = await followDeploymentEvents({
|
|
7913
|
-
organizationId: orgId,
|
|
7914
|
-
deploymentId: dep.deploymentId,
|
|
7915
|
-
totalTimeoutMs,
|
|
7916
|
-
onEvent: (event) => {
|
|
7917
|
-
if (!isJsonMode()) {
|
|
7918
|
-
printInfo(formatEventLine(event));
|
|
7919
|
-
}
|
|
7920
|
-
}
|
|
7921
|
-
});
|
|
7922
|
-
terminalEvent = result.terminalEvent;
|
|
7923
|
-
waitDone = result.done;
|
|
7924
|
-
if (!waitDone && !isJsonMode()) {
|
|
7925
|
-
printError(`Deploy did not reach a terminal state within ${Math.round(totalTimeoutMs / 1000)}s. Check \`mcp deployments get ${dep.deploymentId}\` and \`mcp deployments health ${dep.deploymentId}\`, or tail \`mcp deployments logs ${dep.deploymentId} --follow\`.`);
|
|
7926
|
-
}
|
|
7927
|
-
}
|
|
7928
|
-
}
|
|
7929
|
-
const deployFailed = dep.status === "failed" || Boolean(terminalEvent && isFailureEvent(terminalEvent));
|
|
7930
|
-
let final = dep;
|
|
7931
|
-
if (opts.wait && waitDone) {
|
|
7932
|
-
const row = await refreshDeploymentRow({
|
|
7933
|
-
deploymentId: dep.deploymentId,
|
|
7934
|
-
orgId,
|
|
7935
|
-
serverId
|
|
7936
|
-
});
|
|
7937
|
-
if (row)
|
|
7938
|
-
final = { ...dep, ...row };
|
|
7939
|
-
}
|
|
7940
|
-
if (isJsonMode()) {
|
|
7941
|
-
printJson({ deployment: final, waitDone, terminalEvent });
|
|
7942
|
-
if (opts.wait && (!waitDone || deployFailed))
|
|
7943
|
-
throw new CliExitError(1);
|
|
7944
|
-
return;
|
|
7945
|
-
}
|
|
7946
|
-
printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
|
|
7947
|
-
printKeyValue({
|
|
7948
|
-
id: dep.deploymentId,
|
|
7949
|
-
...environment ? { environment } : {},
|
|
7950
|
-
...final.deploymentUrl ? { url: final.deploymentUrl } : {},
|
|
7951
|
-
exposure: formatDeploymentExposure(final),
|
|
7952
|
-
"access mode": dep.accessMode,
|
|
7953
|
-
status: final.status,
|
|
7954
|
-
health: final.healthStatus,
|
|
7955
|
-
...terminalEvent ? {
|
|
7956
|
-
"terminal event": `${terminalEvent.stage} (${terminalEvent.level}) — ${terminalEvent.message}`,
|
|
7957
|
-
"terminal at": formatDate(terminalEvent.timestamp)
|
|
7958
|
-
} : {}
|
|
7959
|
-
});
|
|
7960
|
-
if (!accessMode && dep.accessMode === "public" && await isOnlyDeployment({
|
|
7961
|
-
deploymentId: dep.deploymentId,
|
|
7962
|
-
orgId,
|
|
7963
|
-
serverId
|
|
7964
|
-
})) {
|
|
7965
|
-
printWarn("This first deployment is public — anyone with the URL can call it. Redeploy with --access-mode privateOrg or unlisted to require sign-in (recommended when the upstream API needs credentials).");
|
|
7966
|
-
}
|
|
7967
|
-
if (!opts.wait && dep.status === "queued") {
|
|
7968
|
-
printInfo(`Deploy is running in the background — follow with \`mcp deployments get ${dep.deploymentId}\` or rerun with --wait.`);
|
|
7969
|
-
}
|
|
7970
|
-
if (opts.wait && !waitDone)
|
|
7971
|
-
throw new CliExitError(1);
|
|
7972
|
-
if (deployFailed) {
|
|
7973
|
-
if (!reportDeployFailure(terminalEvent)) {
|
|
7974
|
-
printError("Deploy failed.");
|
|
7975
|
-
}
|
|
7976
|
-
throw new CliExitError(1);
|
|
7977
|
-
}
|
|
8343
|
+
await runDeploy(serverRef, opts);
|
|
7978
8344
|
}));
|
|
7979
|
-
servers
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
"",
|
|
7991
|
-
" # Idempotent retry-safe push in CI:",
|
|
7992
|
-
" $ mcp --ci --idempotency-key $GITHUB_RUN_ID servers push-spec srv_123 --deploy --wait",
|
|
7993
|
-
"",
|
|
7994
|
-
"Notes (--apply):",
|
|
7995
|
-
" Without --apply this regenerates the BUNDLE and reports the tool",
|
|
7996
|
-
" changes the spec implies — it does not touch the stored tools, so a",
|
|
7997
|
-
" deploy afterwards still ships the old ones.",
|
|
7998
|
-
" --apply writes the tools AND regenerates the stored bundle (what",
|
|
7999
|
-
" deploy ships), so the next deploy advertises the new spec. If the",
|
|
8000
|
-
" bundle step fails you are told, and --deploy refuses to ship the",
|
|
8001
|
-
" stale one; `mcp servers generate <server> --store` repairs it.",
|
|
8002
|
-
" --apply reconciles: tools match on their stable operation key so",
|
|
8003
|
-
" renames survive, spec-derived shape is refreshed, and curated fields",
|
|
8004
|
-
" (description, tags, riskClass, projections, enrichment) are kept.",
|
|
8005
|
-
" Operations missing from the spec are REPORTED, never deleted.",
|
|
8006
|
-
" It is lossy for hand-edited input schemas — the spec becomes the",
|
|
8007
|
-
" truth — which is why it is opt-in."
|
|
8008
|
-
].join(`
|
|
8009
|
-
`)).action(runAction(async (serverRef, opts) => {
|
|
8010
|
-
const cwd = process.cwd();
|
|
8011
|
-
const specPath = isAbsolute2(opts.spec) ? opts.spec : resolve4(cwd, opts.spec);
|
|
8012
|
-
if (!existsSync6(specPath)) {
|
|
8013
|
-
throw new Error(`Spec file not found: ${specPath}`);
|
|
8014
|
-
}
|
|
8015
|
-
const orgId = await resolveOrgId(opts.org);
|
|
8016
|
-
const serverId = await resolveServerId(serverRef, orgId);
|
|
8017
|
-
const target = validateChoice("target", opts.target, DEPLOY_TARGETS);
|
|
8018
|
-
const accessMode = opts.accessMode ? validateChoice("access-mode", opts.accessMode, ACCESS_MODES) : undefined;
|
|
8019
|
-
if (!isJsonMode()) {
|
|
8020
|
-
printStep(`Pushing spec ${c.bold(specPath)} to ${c.bold(serverId)}…`);
|
|
8021
|
-
}
|
|
8022
|
-
const { content } = readSpecFile(specPath);
|
|
8023
|
-
const result = await regenerateDevBundle({
|
|
8024
|
-
applySpec: true,
|
|
8025
|
-
dryRunApply: !opts.apply,
|
|
8026
|
-
organizationId: orgId,
|
|
8027
|
-
serverId,
|
|
8028
|
-
rawSpec: content,
|
|
8029
|
-
sourceType: "openapi",
|
|
8030
|
-
preserveEnrichment: Boolean(opts.enrichOnChange)
|
|
8031
|
-
});
|
|
8032
|
-
if (!result.ok) {
|
|
8033
|
-
const hint = result.code === "insufficient_scope" ? " push-spec --apply regenerates the server’s code, which needs the servers:build scope; mint the key with --preset ci-build or add --scope servers:build." : "";
|
|
8034
|
-
throw new Error(`Spec regen failed: ${result.message} (code: ${result.code})${hint}`);
|
|
8035
|
-
}
|
|
8036
|
-
const summary = summarizeDiff(result.diff);
|
|
8037
|
-
if (!isJsonMode()) {
|
|
8038
|
-
if (!summary.hasChanges) {
|
|
8039
|
-
printInfo(c.dim(" → no tool surface changes; bundle refreshed."));
|
|
8040
|
-
} else {
|
|
8041
|
-
printInfo(` → ${summary.total} tool change${summary.total === 1 ? "" : "s"} by name${result.applied?.dryRun === false ? "" : " (nothing written)"}:`);
|
|
8042
|
-
printInfo(summary.text);
|
|
8043
|
-
}
|
|
8044
|
-
if (result.applied) {
|
|
8045
|
-
const {
|
|
8046
|
-
dryRun,
|
|
8047
|
-
inserted,
|
|
8048
|
-
removedKeys,
|
|
8049
|
-
unchanged,
|
|
8050
|
-
updated,
|
|
8051
|
-
warnings
|
|
8052
|
-
} = result.applied;
|
|
8053
|
-
printInfo(` → ${dryRun ? "would apply" : "applied"}: ${updated.length} updated, ${inserted.length} added, ${unchanged} unchanged`);
|
|
8054
|
-
if (removedKeys.length > 0) {
|
|
8055
|
-
printWarn(` ! ${removedKeys.length} tool(s) no longer in the spec, left in place: ${removedKeys.join(", ")}`);
|
|
8056
|
-
}
|
|
8057
|
-
for (const warning of warnings)
|
|
8058
|
-
printWarn(` ! ${warning}`);
|
|
8059
|
-
if (!dryRun) {
|
|
8060
|
-
if (result.applied.storedBundle) {
|
|
8061
|
-
printInfo(c.dim(" → stored bundle regenerated; a deploy now ships this spec."));
|
|
8062
|
-
} else {
|
|
8063
|
-
printWarn(` ! stored bundle NOT regenerated${result.applied.storeError ? `: ${result.applied.storeError}` : ""}. ` + `Deploy ships the stored bundle — run \`mcp servers generate ${serverId} --store\` before deploying.`);
|
|
8064
|
-
}
|
|
8065
|
-
}
|
|
8066
|
-
}
|
|
8067
|
-
if (result.applied?.dryRun) {
|
|
8068
|
-
printInfo(c.dim(" → re-run with --apply to write this to the server’s tools."));
|
|
8069
|
-
}
|
|
8070
|
-
if (result.enrichmentApplied) {
|
|
8071
|
-
printInfo(c.dim(" → preserved per-tool enrichment."));
|
|
8072
|
-
}
|
|
8073
|
-
printSuccess(result.applied && !result.applied.dryRun ? "Spec applied." : "Bundle regenerated (preview).");
|
|
8074
|
-
}
|
|
8075
|
-
const appliedWithoutStore = result.applied !== null && !result.applied.dryRun && result.applied.storedBundle === false;
|
|
8076
|
-
if (!opts.deploy) {
|
|
8077
|
-
if (isJsonMode()) {
|
|
8078
|
-
printJson({
|
|
8079
|
-
applied: result.applied,
|
|
8080
|
-
diff: result.diff,
|
|
8081
|
-
enrichmentApplied: result.enrichmentApplied,
|
|
8082
|
-
pushed: Boolean(result.applied && !result.applied.dryRun)
|
|
8083
|
-
});
|
|
8084
|
-
} else if (appliedWithoutStore) {
|
|
8085
|
-
printInfo(c.dim(" → run `mcp servers generate " + serverId + " --store`, then `mcp servers deploy " + serverId + " --wait` to ship."));
|
|
8086
|
-
} else {
|
|
8087
|
-
printInfo(c.dim(" → run `mcp servers deploy " + serverId + " --wait` (or pass --deploy) to ship."));
|
|
8088
|
-
}
|
|
8089
|
-
return;
|
|
8090
|
-
}
|
|
8091
|
-
if (appliedWithoutStore) {
|
|
8092
|
-
throw new Error(`The spec was applied but the stored bundle was not regenerated${result.applied?.storeError ? ` (${result.applied.storeError})` : ""}. ` + `Run \`mcp servers generate ${serverId} --store\`, then deploy.`);
|
|
8093
|
-
}
|
|
8345
|
+
registerServerPushSpecCommand(servers);
|
|
8346
|
+
registerServerRegenerateCommand(servers);
|
|
8347
|
+
}
|
|
8348
|
+
async function runDeploy(serverRef, opts) {
|
|
8349
|
+
const target = validateChoice("target", opts.target, DEPLOY_TARGETS);
|
|
8350
|
+
const environment = resolveDeployEnvironment(opts);
|
|
8351
|
+
const accessMode = opts.accessMode ? validateChoice("access-mode", opts.accessMode, ACCESS_MODES) : undefined;
|
|
8352
|
+
const orgId = await resolveOrgId(opts.org);
|
|
8353
|
+
let projectId = await resolveOptionalProjectId(opts.project, orgId);
|
|
8354
|
+
const serverId = await resolveServerId(serverRef, orgId, projectId);
|
|
8355
|
+
if (!projectId) {
|
|
8094
8356
|
const serverData = await api.get("/api/v1/server", {
|
|
8095
8357
|
organizationId: orgId,
|
|
8096
8358
|
serverId
|
|
8097
8359
|
});
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
if (!isJsonMode())
|
|
8135
|
-
printInfo(formatEventLine(event));
|
|
8136
|
-
}
|
|
8137
|
-
});
|
|
8138
|
-
terminalEvent = r.terminalEvent;
|
|
8139
|
-
waitDone = r.done;
|
|
8140
|
-
if (!waitDone && !isJsonMode()) {
|
|
8141
|
-
printError(`Deploy did not reach a terminal state within ${Math.round(totalTimeoutMs / 1000)}s. Check \`mcp deployments get ${dep.deploymentId}\` and \`mcp deployments health ${dep.deploymentId}\`, or tail \`mcp deployments logs ${dep.deploymentId} --follow\`.`);
|
|
8142
|
-
}
|
|
8143
|
-
}
|
|
8144
|
-
}
|
|
8145
|
-
const deployFailed = dep.status === "failed" || Boolean(terminalEvent && isFailureEvent(terminalEvent));
|
|
8146
|
-
let final = dep;
|
|
8147
|
-
if (opts.wait && waitDone) {
|
|
8148
|
-
const row = await refreshDeploymentRow({
|
|
8149
|
-
deploymentId: dep.deploymentId,
|
|
8150
|
-
orgId,
|
|
8151
|
-
serverId
|
|
8152
|
-
});
|
|
8153
|
-
if (row)
|
|
8154
|
-
final = { ...dep, ...row };
|
|
8155
|
-
}
|
|
8156
|
-
if (isJsonMode()) {
|
|
8157
|
-
printJson({
|
|
8158
|
-
pushed: true,
|
|
8159
|
-
diff: result.diff,
|
|
8160
|
-
enrichmentApplied: result.enrichmentApplied,
|
|
8161
|
-
applied: result.applied,
|
|
8162
|
-
deployment: final,
|
|
8163
|
-
waitDone,
|
|
8164
|
-
terminalEvent
|
|
8165
|
-
});
|
|
8166
|
-
if (opts.wait && (!waitDone || deployFailed))
|
|
8167
|
-
throw new CliExitError(1);
|
|
8168
|
-
return;
|
|
8169
|
-
}
|
|
8170
|
-
printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
|
|
8171
|
-
printKeyValue({
|
|
8172
|
-
id: dep.deploymentId,
|
|
8173
|
-
...final.deploymentUrl ? { url: final.deploymentUrl } : {},
|
|
8174
|
-
target: final.target,
|
|
8175
|
-
"access mode": final.accessMode,
|
|
8176
|
-
status: final.status,
|
|
8177
|
-
health: final.healthStatus,
|
|
8178
|
-
...terminalEvent ? {
|
|
8179
|
-
"terminal event": `${terminalEvent.stage} (${terminalEvent.level}) — ${terminalEvent.message}`,
|
|
8180
|
-
"terminal at": formatDate(terminalEvent.timestamp)
|
|
8181
|
-
} : {}
|
|
8182
|
-
});
|
|
8183
|
-
if (!accessMode && dep.accessMode === "public" && await isOnlyDeployment({
|
|
8360
|
+
projectId = serverData.server.projectId;
|
|
8361
|
+
}
|
|
8362
|
+
const runtimeConfig = {
|
|
8363
|
+
upstreamBaseUrl: opts.upstreamBaseUrl ?? null,
|
|
8364
|
+
upstreamApiKey: opts.upstreamApiKey ?? null,
|
|
8365
|
+
upstreamApiKeyHeader: opts.upstreamApiKeyHeader ?? null,
|
|
8366
|
+
upstreamApiKeyPrefix: opts.upstreamApiKeyPrefix ?? null,
|
|
8367
|
+
upstreamCustomHeaders: null,
|
|
8368
|
+
additionalPlainTextBindings: null,
|
|
8369
|
+
additionalSecretBindings: null
|
|
8370
|
+
};
|
|
8371
|
+
const body = {
|
|
8372
|
+
organizationId: orgId,
|
|
8373
|
+
projectId,
|
|
8374
|
+
serverId,
|
|
8375
|
+
target,
|
|
8376
|
+
...accessMode ? { accessMode } : {},
|
|
8377
|
+
...environment ? { environment } : {},
|
|
8378
|
+
runtimeConfig
|
|
8379
|
+
};
|
|
8380
|
+
if (!isJsonMode()) {
|
|
8381
|
+
printStep(`Deploying ${c.bold(serverId)} to ${c.cyan(target)}${environment ? ` in ${c.cyan(environment)}` : ""}…`);
|
|
8382
|
+
}
|
|
8383
|
+
const data = await api.post("/api/v1/server/deploy", body);
|
|
8384
|
+
const dep = data.deployment;
|
|
8385
|
+
const outcome = await waitForDeploy({
|
|
8386
|
+
deployment: dep,
|
|
8387
|
+
orgId,
|
|
8388
|
+
wait: opts.wait,
|
|
8389
|
+
waitTimeout: opts.waitTimeout,
|
|
8390
|
+
announceTimeout: true
|
|
8391
|
+
});
|
|
8392
|
+
const { terminalEvent, waitDone, deployFailed } = outcome;
|
|
8393
|
+
let final = dep;
|
|
8394
|
+
if (opts.wait && waitDone) {
|
|
8395
|
+
const row = await refreshDeploymentRow({
|
|
8184
8396
|
deploymentId: dep.deploymentId,
|
|
8185
8397
|
orgId,
|
|
8186
8398
|
serverId
|
|
8187
|
-
})) {
|
|
8188
|
-
printWarn("This first deployment is public — anyone with the URL can call it. Redeploy with --access-mode privateOrg or unlisted to require sign-in (recommended when the upstream API needs credentials).");
|
|
8189
|
-
}
|
|
8190
|
-
if (!opts.wait && dep.status === "queued") {
|
|
8191
|
-
printInfo(`Deploy is running in the background — follow with \`mcp deployments get ${dep.deploymentId}\` or rerun with --wait.`);
|
|
8192
|
-
}
|
|
8193
|
-
if (opts.wait && !waitDone)
|
|
8194
|
-
throw new CliExitError(1);
|
|
8195
|
-
if (deployFailed) {
|
|
8196
|
-
if (!reportDeployFailure(terminalEvent)) {
|
|
8197
|
-
printError("Deploy failed.");
|
|
8198
|
-
}
|
|
8199
|
-
throw new CliExitError(1);
|
|
8200
|
-
}
|
|
8201
|
-
}));
|
|
8202
|
-
servers.command("regenerate <server>").description("Re-fetch a server's source spec and rebuild its tools with the current codegen (preserves customizations). Works for spec-URL, OpenAPI, and GraphQL sources; a pasted or manual server has nothing to re-fetch. Wraps POST /api/v1/server/regenerate.").option("--org <organizationId>", "Organization ID").option("--project <project>", "Project (id or name; defaults to the server's project)").option("--source-url <url>", "Override the stored spec source URL (needed for GraphQL servers whose introspection endpoint wasn't persisted)").addHelpText("after", [
|
|
8203
|
-
"",
|
|
8204
|
-
"Regenerate re-introspects/re-parses the source and rebuilds each tool with",
|
|
8205
|
-
"the current codegen, so codegen fixes reach existing servers. It does not",
|
|
8206
|
-
"deploy — run `mcp servers deploy` afterwards to ship the rebuilt bundle.",
|
|
8207
|
-
"",
|
|
8208
|
-
"Examples:",
|
|
8209
|
-
" # Propagate codegen fixes into an existing server, then deploy:",
|
|
8210
|
-
" $ mcp servers regenerate srv_123 && mcp servers deploy srv_123 --wait",
|
|
8211
|
-
"",
|
|
8212
|
-
" # GraphQL server whose introspection endpoint must be supplied:",
|
|
8213
|
-
" $ mcp servers regenerate srv_123 --source-url https://api.example.com/graphql"
|
|
8214
|
-
].join(`
|
|
8215
|
-
`)).action(runAction(async (serverRef, opts) => {
|
|
8216
|
-
const orgId = await resolveOrgId(opts.org);
|
|
8217
|
-
let projectId = await resolveOptionalProjectId(opts.project, orgId);
|
|
8218
|
-
const serverId = await resolveServerId(serverRef, orgId, projectId);
|
|
8219
|
-
if (!projectId) {
|
|
8220
|
-
const serverData = await api.get("/api/v1/server", { organizationId: orgId, serverId });
|
|
8221
|
-
projectId = serverData.server.projectId;
|
|
8222
|
-
}
|
|
8223
|
-
if (!isJsonMode()) {
|
|
8224
|
-
printStep(`Regenerating ${c.bold(serverId)} from its source spec…`);
|
|
8225
|
-
}
|
|
8226
|
-
const data = await api.post("/api/v1/server/regenerate", {
|
|
8227
|
-
organizationId: orgId,
|
|
8228
|
-
projectId,
|
|
8229
|
-
serverId,
|
|
8230
|
-
...opts.sourceUrl ? { sourceUrl: opts.sourceUrl } : {}
|
|
8231
|
-
});
|
|
8232
|
-
const r = data.regeneration;
|
|
8233
|
-
if (isJsonMode()) {
|
|
8234
|
-
printJson({ regeneration: r });
|
|
8235
|
-
return;
|
|
8236
|
-
}
|
|
8237
|
-
printSuccess(`Regenerated ${c.bold(serverId)} (${r.sourceType}).`);
|
|
8238
|
-
printKeyValue({
|
|
8239
|
-
"tools updated": String(r.updated),
|
|
8240
|
-
"tools added": String(r.inserted),
|
|
8241
|
-
"tools dropped from spec": String(r.removed),
|
|
8242
|
-
generation: r.generationStatus
|
|
8243
8399
|
});
|
|
8244
|
-
if (
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8400
|
+
if (row)
|
|
8401
|
+
final = { ...dep, ...row };
|
|
8402
|
+
}
|
|
8403
|
+
if (isJsonMode()) {
|
|
8404
|
+
printJson({ deployment: final, waitDone, terminalEvent });
|
|
8405
|
+
if (opts.wait && (!waitDone || deployFailed))
|
|
8406
|
+
throw new CliExitError(1);
|
|
8407
|
+
return;
|
|
8408
|
+
}
|
|
8409
|
+
printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
|
|
8410
|
+
printKeyValue({
|
|
8411
|
+
id: dep.deploymentId,
|
|
8412
|
+
...environment ? { environment } : {},
|
|
8413
|
+
...final.deploymentUrl ? { url: final.deploymentUrl } : {},
|
|
8414
|
+
exposure: formatDeploymentExposure(final),
|
|
8415
|
+
"access mode": dep.accessMode,
|
|
8416
|
+
status: final.status,
|
|
8417
|
+
health: final.healthStatus,
|
|
8418
|
+
...terminalEventFields(terminalEvent)
|
|
8419
|
+
});
|
|
8420
|
+
await finishDeployReport({
|
|
8421
|
+
deployment: dep,
|
|
8422
|
+
accessModeRequested: accessMode,
|
|
8423
|
+
orgId,
|
|
8424
|
+
serverId,
|
|
8425
|
+
wait: opts.wait,
|
|
8426
|
+
outcome
|
|
8427
|
+
});
|
|
8249
8428
|
}
|
|
8250
8429
|
|
|
8251
8430
|
// src/commands/servers-traces.ts
|
|
@@ -9506,20 +9685,9 @@ function registerServerCommands(program) {
|
|
|
9506
9685
|
}));
|
|
9507
9686
|
}
|
|
9508
9687
|
|
|
9509
|
-
// src/commands/tools-
|
|
9510
|
-
import { existsSync as
|
|
9511
|
-
import { relative } from "node:path";
|
|
9512
|
-
|
|
9513
|
-
// src/lib/dev/handlers-sync.ts
|
|
9514
|
-
import {
|
|
9515
|
-
existsSync as existsSync10,
|
|
9516
|
-
mkdirSync as mkdirSync7,
|
|
9517
|
-
readFileSync as readFileSync13,
|
|
9518
|
-
statSync,
|
|
9519
|
-
writeFileSync as writeFileSync6
|
|
9520
|
-
} from "node:fs";
|
|
9521
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
9522
|
-
import { dirname as dirname5, join as join8 } from "node:path";
|
|
9688
|
+
// src/commands/tools-delete.ts
|
|
9689
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
9690
|
+
import { join as join8, relative } from "node:path";
|
|
9523
9691
|
|
|
9524
9692
|
// src/lib/dev/state.ts
|
|
9525
9693
|
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "node:fs";
|
|
@@ -9921,6 +10089,9 @@ async function pushLocalToolEdit(args) {
|
|
|
9921
10089
|
return { kind: "missing-file" };
|
|
9922
10090
|
return { kind: "unknown-tool" };
|
|
9923
10091
|
}
|
|
10092
|
+
if (local.view.name !== args.toolName) {
|
|
10093
|
+
return { kind: "rename-requested", from: args.toolName, to: local.view.name };
|
|
10094
|
+
}
|
|
9924
10095
|
const newHash = hashLocalView(local.view);
|
|
9925
10096
|
if (newHash === local.storedHash) {
|
|
9926
10097
|
return { kind: "noop" };
|
|
@@ -9993,12 +10164,119 @@ async function fetchToolsForServer(args) {
|
|
|
9993
10164
|
}));
|
|
9994
10165
|
}
|
|
9995
10166
|
|
|
10167
|
+
// src/commands/tools-context.ts
|
|
10168
|
+
async function resolveToolsContext(opts = {}) {
|
|
10169
|
+
const cwd = process.cwd();
|
|
10170
|
+
const cached = readState(cwd);
|
|
10171
|
+
const serverRef = opts.serverOverride ?? cached?.serverId;
|
|
10172
|
+
if (!serverRef) {
|
|
10173
|
+
if (opts.requireServer === false)
|
|
10174
|
+
return null;
|
|
10175
|
+
printError("No server selected. Pass --server <server> or run from a directory with .mcpcloud/state.json.");
|
|
10176
|
+
throw new CliExitError(1);
|
|
10177
|
+
}
|
|
10178
|
+
const organizationId = opts.orgOverride ?? cached?.organizationId ?? await resolveOrgId(undefined);
|
|
10179
|
+
const serverId = opts.serverOverride ? await resolveServerId(opts.serverOverride, organizationId) : serverRef;
|
|
10180
|
+
let projectId = cached?.projectId;
|
|
10181
|
+
if (!projectId) {
|
|
10182
|
+
const data = await api.get("/api/v1/server", {
|
|
10183
|
+
organizationId,
|
|
10184
|
+
serverId
|
|
10185
|
+
});
|
|
10186
|
+
projectId = data.server.projectId;
|
|
10187
|
+
}
|
|
10188
|
+
return { organizationId, projectId, serverId };
|
|
10189
|
+
}
|
|
10190
|
+
|
|
10191
|
+
// src/commands/tools-delete.ts
|
|
10192
|
+
var HELP_AFTER2 = [
|
|
10193
|
+
"",
|
|
10194
|
+
"This is the remove-and-re-add route: the tool is deleted, and a later",
|
|
10195
|
+
"`mcp servers push-spec <server> --apply` (or `servers regenerate`) re-creates",
|
|
10196
|
+
"it from the spec as a NEW tool — new id, codegen’s current name. The deleted",
|
|
10197
|
+
"tool’s access grants are removed and its saved test scenarios archived.",
|
|
10198
|
+
"To change only the name and keep the id, grants and scenarios, use",
|
|
10199
|
+
"`mcp tools rename` instead.",
|
|
10200
|
+
"",
|
|
10201
|
+
"The stored bundle is regenerated without the tool; the live server keeps",
|
|
10202
|
+
"serving it until `mcp servers deploy <server> --wait`.",
|
|
10203
|
+
"",
|
|
10204
|
+
"Examples:",
|
|
10205
|
+
" $ mcp tools delete getrecording --server srv_123 --confirm getrecording"
|
|
10206
|
+
];
|
|
10207
|
+
function plural(count, noun) {
|
|
10208
|
+
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
10209
|
+
}
|
|
10210
|
+
function registerToolsDeleteCommand(tools) {
|
|
10211
|
+
tools.command("delete <name>").description('Delete one tool from a server (the remove-and-re-add route). Removes its access grants and archives its saved test scenarios. Requires --confirm "<name>".').option("--org <organizationId>", "Organization ID (defaults to .mcpcloud/state.json)").option("--server <server>", "Server (id, name, or slug; defaults to .mcpcloud/state.json)").option("--confirm <name>", "The exact tool name, typed to confirm the destructive action").addHelpText("after", HELP_AFTER2.join(`
|
|
10212
|
+
`)).action(runAction(async (name, opts) => {
|
|
10213
|
+
await runToolsDelete(name, opts);
|
|
10214
|
+
}));
|
|
10215
|
+
}
|
|
10216
|
+
async function runToolsDelete(toolName, opts) {
|
|
10217
|
+
assertConfirmMatches({
|
|
10218
|
+
entityKind: "tool",
|
|
10219
|
+
entityName: toolName,
|
|
10220
|
+
confirm: opts.confirm
|
|
10221
|
+
});
|
|
10222
|
+
const ctx = await resolveToolsContext({
|
|
10223
|
+
orgOverride: opts.org,
|
|
10224
|
+
serverOverride: opts.server
|
|
10225
|
+
});
|
|
10226
|
+
if (!ctx)
|
|
10227
|
+
return;
|
|
10228
|
+
if (!isJsonMode()) {
|
|
10229
|
+
printStep(`Deleting tool ${c.bold(toolName)} from ${c.bold(ctx.serverId)}…`);
|
|
10230
|
+
}
|
|
10231
|
+
const res = await api.delete("/api/v1/server/tool", {
|
|
10232
|
+
organizationId: ctx.organizationId,
|
|
10233
|
+
serverId: ctx.serverId,
|
|
10234
|
+
toolName
|
|
10235
|
+
});
|
|
10236
|
+
if (isJsonMode()) {
|
|
10237
|
+
printJson(res);
|
|
10238
|
+
return;
|
|
10239
|
+
}
|
|
10240
|
+
printSuccess(`Deleted tool ${c.bold(res.deleted.name)} (${res.deleted.toolId}) from ${c.bold(ctx.serverId)}.`);
|
|
10241
|
+
printInfo(` Removed ${plural(res.removed.accessGrants, "access grant")}; archived ${plural(res.removed.testScenarios, "saved scenario")}.`);
|
|
10242
|
+
if (res.references.length > 0) {
|
|
10243
|
+
printWarn(`${plural(res.references.length, "reference")} still ${res.references.length === 1 ? "points" : "point"} at ${res.deleted.name} — update ${res.references.length === 1 ? "it" : "them"} by hand:`);
|
|
10244
|
+
for (const ref of res.references) {
|
|
10245
|
+
printInfo(` ${ref.kind.padEnd(11)} ${ref.name} (${ref.id})${ref.detail ? ` — ${ref.detail}` : ""}`);
|
|
10246
|
+
}
|
|
10247
|
+
}
|
|
10248
|
+
if (res.storedBundle) {
|
|
10249
|
+
printInfo(c.dim(" → stored bundle regenerated without it."));
|
|
10250
|
+
} else {
|
|
10251
|
+
printWarn(`stored bundle NOT regenerated${res.storeError ? `: ${res.storeError}` : ""}. ` + `Run \`mcp servers generate ${ctx.serverId} --store\` before deploying.`);
|
|
10252
|
+
}
|
|
10253
|
+
printInfo(` → The live server still serves ${res.deleted.name}. Run ${c.bold(`mcp servers deploy ${ctx.serverId} --wait`)} to remove it there.`);
|
|
10254
|
+
const cwd = process.cwd();
|
|
10255
|
+
const localFile = join8(serverToolsDir(devRoot(cwd), ctx.serverId), `${res.deleted.name}.md`);
|
|
10256
|
+
if (existsSync10(localFile)) {
|
|
10257
|
+
printInfo(` → ${relative(cwd, localFile)} is now stale — run ${c.bold(`mcp tools pull --server ${ctx.serverId}`)} to remove it.`);
|
|
10258
|
+
}
|
|
10259
|
+
}
|
|
10260
|
+
|
|
10261
|
+
// src/commands/tools-diff.ts
|
|
10262
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "node:fs";
|
|
10263
|
+
import { relative as relative2 } from "node:path";
|
|
10264
|
+
|
|
9996
10265
|
// src/lib/dev/handlers-sync.ts
|
|
10266
|
+
import {
|
|
10267
|
+
existsSync as existsSync11,
|
|
10268
|
+
mkdirSync as mkdirSync7,
|
|
10269
|
+
readFileSync as readFileSync13,
|
|
10270
|
+
statSync,
|
|
10271
|
+
writeFileSync as writeFileSync6
|
|
10272
|
+
} from "node:fs";
|
|
10273
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
10274
|
+
import { dirname as dirname5, join as join9 } from "node:path";
|
|
9997
10275
|
function toKebabSlug(value) {
|
|
9998
10276
|
return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "generated-mcp-server";
|
|
9999
10277
|
}
|
|
10000
10278
|
function serverBundleHandlersDir(devRootDir) {
|
|
10001
|
-
return
|
|
10279
|
+
return join9(devRootDir, "server", "src", "tools");
|
|
10002
10280
|
}
|
|
10003
10281
|
function handlerSlugFromWatcherPath(rel) {
|
|
10004
10282
|
if (!rel)
|
|
@@ -10027,8 +10305,8 @@ function hashSource(source) {
|
|
|
10027
10305
|
}
|
|
10028
10306
|
var lastPushedHash = new Map;
|
|
10029
10307
|
async function pushLocalHandlerEdit(args) {
|
|
10030
|
-
const filePath =
|
|
10031
|
-
if (!
|
|
10308
|
+
const filePath = join9(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
|
|
10309
|
+
if (!existsSync11(filePath)) {
|
|
10032
10310
|
return { kind: "missing-file" };
|
|
10033
10311
|
}
|
|
10034
10312
|
const resolved = resolveToolFromSlug(args.devRootDir, args.serverId, args.slug);
|
|
@@ -10179,7 +10457,7 @@ async function resetCloudHandler(args) {
|
|
|
10179
10457
|
}
|
|
10180
10458
|
}
|
|
10181
10459
|
function writeLocalHandlerFromCloud(args) {
|
|
10182
|
-
const filePath =
|
|
10460
|
+
const filePath = join9(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
|
|
10183
10461
|
mkdirSync7(dirname5(filePath), { recursive: true });
|
|
10184
10462
|
writeFileSync6(filePath, args.content, "utf-8");
|
|
10185
10463
|
lastPushedHash.set(filePath, hashSource(args.content));
|
|
@@ -10190,8 +10468,8 @@ function handlerSlugFromToolName(toolName) {
|
|
|
10190
10468
|
}
|
|
10191
10469
|
var RECENT_EDIT_WINDOW_MS = 5 * 60 * 1000;
|
|
10192
10470
|
function inspectLocalHandler(args) {
|
|
10193
|
-
const filePath =
|
|
10194
|
-
if (!
|
|
10471
|
+
const filePath = join9(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
|
|
10472
|
+
if (!existsSync11(filePath))
|
|
10195
10473
|
return { kind: "no-local-file" };
|
|
10196
10474
|
const cached = lastPushedHash.get(filePath);
|
|
10197
10475
|
const onDisk = hashSource(readFileSync13(filePath, "utf-8"));
|
|
@@ -10223,30 +10501,6 @@ async function fetchHandlerOverrideStatuses(args) {
|
|
|
10223
10501
|
}));
|
|
10224
10502
|
}
|
|
10225
10503
|
|
|
10226
|
-
// src/commands/tools-context.ts
|
|
10227
|
-
async function resolveToolsContext(opts = {}) {
|
|
10228
|
-
const cwd = process.cwd();
|
|
10229
|
-
const cached = readState(cwd);
|
|
10230
|
-
const serverRef = opts.serverOverride ?? cached?.serverId;
|
|
10231
|
-
if (!serverRef) {
|
|
10232
|
-
if (opts.requireServer === false)
|
|
10233
|
-
return null;
|
|
10234
|
-
printError("No server selected. Pass --server <server> or run from a directory with .mcpcloud/state.json.");
|
|
10235
|
-
throw new CliExitError(1);
|
|
10236
|
-
}
|
|
10237
|
-
const organizationId = opts.orgOverride ?? cached?.organizationId ?? await resolveOrgId(undefined);
|
|
10238
|
-
const serverId = opts.serverOverride ? await resolveServerId(opts.serverOverride, organizationId) : serverRef;
|
|
10239
|
-
let projectId = cached?.projectId;
|
|
10240
|
-
if (!projectId) {
|
|
10241
|
-
const data = await api.get("/api/v1/server", {
|
|
10242
|
-
organizationId,
|
|
10243
|
-
serverId
|
|
10244
|
-
});
|
|
10245
|
-
projectId = data.server.projectId;
|
|
10246
|
-
}
|
|
10247
|
-
return { organizationId, projectId, serverId };
|
|
10248
|
-
}
|
|
10249
|
-
|
|
10250
10504
|
// src/commands/tools-diff.ts
|
|
10251
10505
|
function registerToolsDiffCommand(tools) {
|
|
10252
10506
|
tools.command("diff <name>").description("Compare a tool's local files (metadata .md + handler .ts) against the cloud version.").option("--org <organizationId>", "Organization ID (defaults to .mcpcloud/state.json)").option("--server <server>", "Server (id, name, or slug; defaults to .mcpcloud/state.json)").option("--metadata-only", "Skip the handler-source diff (faster — only one cloud round-trip).", false).option("--handler-only", "Skip the metadata diff (only compare the .ts handler).", false).addHelpText("after", [
|
|
@@ -10325,7 +10579,7 @@ async function runToolsDiff(args) {
|
|
|
10325
10579
|
}
|
|
10326
10580
|
function buildMetadataDiff(args) {
|
|
10327
10581
|
const filePath = `${serverToolsDir(args.devRootDir, args.serverId)}/${args.cloud.name}.md`;
|
|
10328
|
-
if (!
|
|
10582
|
+
if (!existsSync12(filePath)) {
|
|
10329
10583
|
return {
|
|
10330
10584
|
hasLocal: false,
|
|
10331
10585
|
localPath: null,
|
|
@@ -10335,7 +10589,7 @@ function buildMetadataDiff(args) {
|
|
|
10335
10589
|
diff: {}
|
|
10336
10590
|
};
|
|
10337
10591
|
}
|
|
10338
|
-
const rel =
|
|
10592
|
+
const rel = relative2(args.cwd, filePath) || filePath;
|
|
10339
10593
|
let local = null;
|
|
10340
10594
|
let parseError = null;
|
|
10341
10595
|
try {
|
|
@@ -10381,9 +10635,9 @@ async function buildHandlerDiff(args) {
|
|
|
10381
10635
|
}
|
|
10382
10636
|
const slug = handlerSlugFromToolName(args.toolName);
|
|
10383
10637
|
const filePath = `${serverBundleHandlersDir(args.devRootDir)}/${slug}.ts`;
|
|
10384
|
-
const hasLocal =
|
|
10638
|
+
const hasLocal = existsSync12(filePath);
|
|
10385
10639
|
const localContent = hasLocal ? readFileSync14(filePath, "utf-8") : null;
|
|
10386
|
-
const localPath = hasLocal ?
|
|
10640
|
+
const localPath = hasLocal ? relative2(args.cwd, filePath) || filePath : null;
|
|
10387
10641
|
const changed = hasLocal ? localContent !== cloudResult.snapshot.content : true;
|
|
10388
10642
|
return {
|
|
10389
10643
|
kind: "ok",
|
|
@@ -10492,18 +10746,18 @@ function renderBool(value) {
|
|
|
10492
10746
|
}
|
|
10493
10747
|
|
|
10494
10748
|
// src/commands/tools-edit.ts
|
|
10495
|
-
import { existsSync as
|
|
10496
|
-
import { join as
|
|
10749
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
10750
|
+
import { join as join11, relative as relative3 } from "node:path";
|
|
10497
10751
|
|
|
10498
10752
|
// src/lib/editor-shell.ts
|
|
10499
10753
|
import { spawn as spawn2 } from "node:child_process";
|
|
10500
|
-
import { existsSync as
|
|
10754
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "node:fs";
|
|
10501
10755
|
import { platform as platform2 } from "node:os";
|
|
10502
|
-
import { delimiter, join as
|
|
10756
|
+
import { delimiter, join as join10 } from "node:path";
|
|
10503
10757
|
var DEFAULT_FALLBACKS = ["vi"];
|
|
10504
10758
|
function isExecutableFile(path) {
|
|
10505
10759
|
try {
|
|
10506
|
-
return
|
|
10760
|
+
return existsSync13(path);
|
|
10507
10761
|
} catch {
|
|
10508
10762
|
return false;
|
|
10509
10763
|
}
|
|
@@ -10520,7 +10774,7 @@ function findOnPath(command) {
|
|
|
10520
10774
|
if (!dir)
|
|
10521
10775
|
continue;
|
|
10522
10776
|
for (const ext of exts) {
|
|
10523
|
-
const candidate =
|
|
10777
|
+
const candidate = join10(dir, command + ext);
|
|
10524
10778
|
if (isExecutableFile(candidate))
|
|
10525
10779
|
return candidate;
|
|
10526
10780
|
}
|
|
@@ -10548,7 +10802,7 @@ function defaultRun(cmd, args) {
|
|
|
10548
10802
|
});
|
|
10549
10803
|
}
|
|
10550
10804
|
async function runEditor(args) {
|
|
10551
|
-
if (!
|
|
10805
|
+
if (!existsSync13(args.filePath)) {
|
|
10552
10806
|
return {
|
|
10553
10807
|
ok: false,
|
|
10554
10808
|
reason: "file-missing",
|
|
@@ -10616,13 +10870,13 @@ async function runToolsEdit(args) {
|
|
|
10616
10870
|
if (!ctx)
|
|
10617
10871
|
return;
|
|
10618
10872
|
const cwd = process.cwd();
|
|
10619
|
-
const filePath =
|
|
10620
|
-
if (!
|
|
10621
|
-
printError(`Tool file not found: ${
|
|
10873
|
+
const filePath = join11(serverToolsDir(devRoot(cwd), ctx.serverId), `${args.toolName}.md`);
|
|
10874
|
+
if (!existsSync14(filePath)) {
|
|
10875
|
+
printError(`Tool file not found: ${relative3(cwd, filePath)}`);
|
|
10622
10876
|
printInfo(` ${c.dim("Run")} ${c.bold(`mcp tools pull ${args.toolName}`)} ${c.dim("first to materialize it, or check the name is spelled correctly.")}`);
|
|
10623
10877
|
throw new CliExitError(1);
|
|
10624
10878
|
}
|
|
10625
|
-
printStep(`Opening ${c.bold(
|
|
10879
|
+
printStep(`Opening ${c.bold(relative3(cwd, filePath))} in editor…`);
|
|
10626
10880
|
const editorResult = await runEditor({
|
|
10627
10881
|
filePath,
|
|
10628
10882
|
...args.editorOverride ? { command: args.editorOverride } : {}
|
|
@@ -10655,6 +10909,12 @@ async function runToolsEdit(args) {
|
|
|
10655
10909
|
printInfo(` ${c.dim("Re-open and fix the file, or run")} ${c.bold(`mcp tools pull ${args.toolName}`)} ${c.dim("to reset.")}`);
|
|
10656
10910
|
throw new CliExitError(1);
|
|
10657
10911
|
return;
|
|
10912
|
+
case "rename-requested":
|
|
10913
|
+
printError(`The name isn't changed by editing ${args.toolName}.md (you changed \`name:\` to ${c.bold(push.to)}) — nothing was pushed.`);
|
|
10914
|
+
printInfo(` ${c.dim("Rename it with")} ${c.bold(`mcp tools rename ${push.from} ${push.to} --server ${ctx.serverId}`)}${c.dim(", then")} ${c.bold(`mcp tools pull --server ${ctx.serverId}`)} ${c.dim("to refresh the local files.")}`);
|
|
10915
|
+
printInfo(` ${c.dim(`To push your other edits, set \`name:\` back to ${push.from} and save again.`)}`);
|
|
10916
|
+
throw new CliExitError(1);
|
|
10917
|
+
return;
|
|
10658
10918
|
case "patched":
|
|
10659
10919
|
if (isJsonMode()) {
|
|
10660
10920
|
printJson({ ok: true, updatedAt: push.updatedAt, changedFields: push.changedFields });
|
|
@@ -11204,7 +11464,7 @@ async function runToolsHandlersList(args) {
|
|
|
11204
11464
|
}
|
|
11205
11465
|
|
|
11206
11466
|
// src/commands/tools-pull.ts
|
|
11207
|
-
import { relative as
|
|
11467
|
+
import { relative as relative4 } from "node:path";
|
|
11208
11468
|
function registerToolsPullCommand(tools) {
|
|
11209
11469
|
tools.command("pull [name]").description("Refresh local tool files from the cloud, overwriting any unsaved local edits. Use after `409 conflict` to re-sync.").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", "Refresh every tool for the server (default when no name given)").addHelpText("after", [
|
|
11210
11470
|
"",
|
|
@@ -11268,7 +11528,7 @@ async function runToolsPull(args) {
|
|
|
11268
11528
|
});
|
|
11269
11529
|
return;
|
|
11270
11530
|
}
|
|
11271
|
-
const dirRel =
|
|
11531
|
+
const dirRel = relative4(cwd, serverToolsDir(dest, ctx.serverId)) || ".";
|
|
11272
11532
|
printSuccess(`Pulled ${result.filesWritten} tool${result.filesWritten === 1 ? "" : "s"} to ${c.bold(dirRel + "/")}.`);
|
|
11273
11533
|
if (args.toolName) {
|
|
11274
11534
|
printInfo(` ${c.dim("Edit the .md file and re-save to push your changes back.")}`);
|
|
@@ -11278,7 +11538,7 @@ async function runToolsPull(args) {
|
|
|
11278
11538
|
}
|
|
11279
11539
|
|
|
11280
11540
|
// src/commands/tools-pull-handler.ts
|
|
11281
|
-
import { relative as
|
|
11541
|
+
import { relative as relative5 } from "node:path";
|
|
11282
11542
|
function registerToolsPullHandlerCommand(tools) {
|
|
11283
11543
|
tools.command("pull-handler <name>").description("Refresh a tool's handler source from the cloud, overwriting any local edits. Use after `409 conflict` to re-sync.").option("--org <organizationId>", "Organization ID (defaults to .mcpcloud/state.json)").option("--server <server>", "Server (id, name, or slug; defaults to .mcpcloud/state.json)").option("--force", "Overwrite local file even if it has unsaved changes (default: refuse).", false).addHelpText("after", [
|
|
11284
11544
|
"",
|
|
@@ -11365,7 +11625,7 @@ async function runPullHandler(args) {
|
|
|
11365
11625
|
});
|
|
11366
11626
|
return;
|
|
11367
11627
|
}
|
|
11368
|
-
const fileRel =
|
|
11628
|
+
const fileRel = relative5(cwd, written) || written;
|
|
11369
11629
|
printSuccess(`Pulled handler for ${c.bold(result.snapshot.toolName)} (version ${result.snapshot.version}) to ${c.bold(fileRel)}.`);
|
|
11370
11630
|
if (result.snapshot.isDefault) {
|
|
11371
11631
|
printInfo(` ${c.dim("Status: default (matches codegen output).")}`);
|
|
@@ -11376,8 +11636,274 @@ async function runPullHandler(args) {
|
|
|
11376
11636
|
printInfo(` ${c.dim("Re-save the file to push your changes back.")}`);
|
|
11377
11637
|
}
|
|
11378
11638
|
|
|
11639
|
+
// src/commands/tools-rename.ts
|
|
11640
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
11641
|
+
import { relative as relative6 } from "node:path";
|
|
11642
|
+
|
|
11643
|
+
// src/commands/tools-rename-input.ts
|
|
11644
|
+
import { existsSync as existsSync15, readFileSync as readFileSync16 } from "node:fs";
|
|
11645
|
+
import { isAbsolute as isAbsolute3, resolve as resolve7 } from "node:path";
|
|
11646
|
+
var MODES_HINT = "Pass exactly one of: <old> <new>, --from-operation-ids, or --map @renames.json.";
|
|
11647
|
+
function resolveRenameSelection(args) {
|
|
11648
|
+
const positional = args.oldName !== undefined || args.newName !== undefined;
|
|
11649
|
+
const modes = [positional, Boolean(args.fromOperationIds), args.map !== undefined];
|
|
11650
|
+
const chosen = modes.filter(Boolean).length;
|
|
11651
|
+
if (chosen === 0)
|
|
11652
|
+
throw new CliExitError(1, `Nothing to rename. ${MODES_HINT}`);
|
|
11653
|
+
if (chosen > 1)
|
|
11654
|
+
throw new CliExitError(1, `Choose one way to rename. ${MODES_HINT}`);
|
|
11655
|
+
if (args.fromOperationIds)
|
|
11656
|
+
return { fromOperationIds: true };
|
|
11657
|
+
if (args.map !== undefined) {
|
|
11658
|
+
return { renames: readRenameMap(args.map, args.cwd) };
|
|
11659
|
+
}
|
|
11660
|
+
const from = args.oldName?.trim() ?? "";
|
|
11661
|
+
const to = args.newName?.trim() ?? "";
|
|
11662
|
+
if (!from || !to) {
|
|
11663
|
+
throw new CliExitError(1, "Pass both the current name and the new one: mcp tools rename <old> <new> --server <server>.");
|
|
11664
|
+
}
|
|
11665
|
+
if (from === to) {
|
|
11666
|
+
throw new CliExitError(1, `"${from}" is already the tool's name.`);
|
|
11667
|
+
}
|
|
11668
|
+
return { renames: [{ from, to }] };
|
|
11669
|
+
}
|
|
11670
|
+
function readRenameMap(ref, cwd) {
|
|
11671
|
+
const raw = ref.trim().replace(/^@/, "");
|
|
11672
|
+
if (!raw)
|
|
11673
|
+
throw new CliExitError(1, "--map needs a file path, e.g. --map @renames.json.");
|
|
11674
|
+
const path = isAbsolute3(raw) ? raw : resolve7(cwd, raw);
|
|
11675
|
+
if (!existsSync15(path))
|
|
11676
|
+
throw new CliExitError(1, `--map file not found: ${path}`);
|
|
11677
|
+
let parsed;
|
|
11678
|
+
try {
|
|
11679
|
+
parsed = JSON.parse(readFileSync16(path, "utf-8"));
|
|
11680
|
+
} catch (err) {
|
|
11681
|
+
throw new CliExitError(1, `--map file is not valid JSON (${path}): ${err instanceof Error ? err.message : String(err)}`);
|
|
11682
|
+
}
|
|
11683
|
+
const pairs = parseRenameMap(parsed);
|
|
11684
|
+
if (pairs.length === 0) {
|
|
11685
|
+
throw new CliExitError(1, `--map file lists no renames: ${path}`);
|
|
11686
|
+
}
|
|
11687
|
+
return pairs;
|
|
11688
|
+
}
|
|
11689
|
+
var MAP_SHAPE = 'Expected {"oldName": "newName", …} or [{"from": "oldName", "to": "newName"}, …].';
|
|
11690
|
+
function parseRenameMap(value) {
|
|
11691
|
+
const entries = Array.isArray(value) ? value.map((item) => {
|
|
11692
|
+
if (!item || typeof item !== "object") {
|
|
11693
|
+
throw new CliExitError(1, `--map entry is not an object. ${MAP_SHAPE}`);
|
|
11694
|
+
}
|
|
11695
|
+
const record = item;
|
|
11696
|
+
return [record["from"], record["to"]];
|
|
11697
|
+
}) : value && typeof value === "object" ? Object.entries(value) : (() => {
|
|
11698
|
+
throw new CliExitError(1, `--map file has the wrong shape. ${MAP_SHAPE}`);
|
|
11699
|
+
})();
|
|
11700
|
+
return entries.map(([from, to]) => {
|
|
11701
|
+
if (typeof from !== "string" || !from.trim()) {
|
|
11702
|
+
throw new CliExitError(1, `--map entry has no current name. ${MAP_SHAPE}`);
|
|
11703
|
+
}
|
|
11704
|
+
if (typeof to !== "string" || !to.trim()) {
|
|
11705
|
+
throw new CliExitError(1, `--map entry for "${from}" has no new name. ${MAP_SHAPE}`);
|
|
11706
|
+
}
|
|
11707
|
+
return { from: from.trim(), to: to.trim() };
|
|
11708
|
+
});
|
|
11709
|
+
}
|
|
11710
|
+
|
|
11711
|
+
// src/commands/tools-rename-report.ts
|
|
11712
|
+
var SKIP_LABELS = {
|
|
11713
|
+
already_named: "already named",
|
|
11714
|
+
custom_name: "custom name, kept",
|
|
11715
|
+
graphql: "GraphQL tool",
|
|
11716
|
+
ambiguous: "ambiguous"
|
|
11717
|
+
};
|
|
11718
|
+
function plural2(count, noun) {
|
|
11719
|
+
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
11720
|
+
}
|
|
11721
|
+
function printRenameReport(res, ctx) {
|
|
11722
|
+
const { serverId } = ctx;
|
|
11723
|
+
if (res.renamed.length === 0) {
|
|
11724
|
+
printSuccess(ctx.fromOperationIds ? `Nothing to rename on ${c.bold(serverId)} — no tool name differs from its operationId only by case.` : `Nothing to rename on ${c.bold(serverId)}.`);
|
|
11725
|
+
} else {
|
|
11726
|
+
const count = plural2(res.renamed.length, "tool");
|
|
11727
|
+
printSuccess(res.dryRun ? `Would rename ${count} on ${c.bold(serverId)} — dry run, nothing written.` : `Renamed ${count} on ${c.bold(serverId)}.`);
|
|
11728
|
+
for (const entry of res.renamed)
|
|
11729
|
+
printInfo(` ${entry.from} → ${entry.to}`);
|
|
11730
|
+
}
|
|
11731
|
+
printSkipped(res.skipped);
|
|
11732
|
+
if (res.renamed.length > 0)
|
|
11733
|
+
printCarried(res);
|
|
11734
|
+
printReferences(res.references, serverId);
|
|
11735
|
+
if (res.droppedHandlerOverrides.length > 0) {
|
|
11736
|
+
printWarn(`${res.dryRun ? "Would drop" : "Dropped"} ${plural2(res.droppedHandlerOverrides.length, "edited handler override")} (the handler is regenerated from the spec): ${res.droppedHandlerOverrides.map((entry) => `${entry.toolName} (${entry.path})`).join(", ")}`);
|
|
11737
|
+
}
|
|
11738
|
+
if (res.renamed.length === 0)
|
|
11739
|
+
return;
|
|
11740
|
+
if (res.dryRun) {
|
|
11741
|
+
printInfo(c.dim(" → re-run without --dry-run to apply."));
|
|
11742
|
+
return;
|
|
11743
|
+
}
|
|
11744
|
+
printNextSteps(res, ctx);
|
|
11745
|
+
}
|
|
11746
|
+
function printSkipped(skipped) {
|
|
11747
|
+
if (skipped.length === 0)
|
|
11748
|
+
return;
|
|
11749
|
+
printInfo(` Skipped ${plural2(skipped.length, "tool")}:`);
|
|
11750
|
+
const groups = new Map;
|
|
11751
|
+
for (const entry of skipped) {
|
|
11752
|
+
const bucket = groups.get(entry.reason);
|
|
11753
|
+
if (bucket)
|
|
11754
|
+
bucket.push(entry);
|
|
11755
|
+
else
|
|
11756
|
+
groups.set(entry.reason, [entry]);
|
|
11757
|
+
}
|
|
11758
|
+
for (const [reason, entries] of groups) {
|
|
11759
|
+
const label = SKIP_LABELS[reason] ?? reason;
|
|
11760
|
+
const names = entries.map((entry) => reason === "custom_name" && entry.codegenName ? `${entry.name} (codegen: ${entry.codegenName})` : entry.name);
|
|
11761
|
+
printInfo(` ${label} (${entries.length}): ${formatNameList(names)}`);
|
|
11762
|
+
if (reason === "ambiguous") {
|
|
11763
|
+
for (const entry of entries) {
|
|
11764
|
+
if (entry.detail)
|
|
11765
|
+
printInfo(c.dim(` ${entry.name}: ${entry.detail}`));
|
|
11766
|
+
}
|
|
11767
|
+
}
|
|
11768
|
+
}
|
|
11769
|
+
}
|
|
11770
|
+
function printCarried(res) {
|
|
11771
|
+
const { accessGrants, testScenarios, compositions } = res.carried;
|
|
11772
|
+
printInfo(` ${res.dryRun ? "Would carry" : "Carried"} to the new names: ` + `${plural2(accessGrants, "access grant")}, ` + `${plural2(testScenarios, "saved test scenario")}, ` + `${plural2(compositions, "composition reference")}.`);
|
|
11773
|
+
}
|
|
11774
|
+
function printReferences(references, serverId) {
|
|
11775
|
+
if (references.length === 0)
|
|
11776
|
+
return;
|
|
11777
|
+
const prompts = references.filter((ref) => ref.kind === "prompt").length;
|
|
11778
|
+
const skills = references.length - prompts;
|
|
11779
|
+
const subject = [
|
|
11780
|
+
prompts > 0 ? plural2(prompts, "prompt") : null,
|
|
11781
|
+
skills > 0 ? plural2(skills, "skill") : null
|
|
11782
|
+
].filter(Boolean).join(" and ");
|
|
11783
|
+
printWarn(`${subject} still ${references.length === 1 ? "mentions" : "mention"} an old tool name — that text is not rewritten; update it by hand:`);
|
|
11784
|
+
for (const ref of references) {
|
|
11785
|
+
printInfo(` ${ref.kind.padEnd(6)} ${ref.name} (${ref.id}): ${ref.toolNames.join(", ")}`);
|
|
11786
|
+
}
|
|
11787
|
+
printInfo(c.dim(` prompts: \`mcp servers prompts set ${serverId} <name>\` · skills: \`mcp skills set-content <skill>\``));
|
|
11788
|
+
}
|
|
11789
|
+
function printNextSteps(res, ctx) {
|
|
11790
|
+
if (res.storedBundle) {
|
|
11791
|
+
printInfo(c.dim(" → stored bundle regenerated with the new names."));
|
|
11792
|
+
} else {
|
|
11793
|
+
printWarn(`stored bundle NOT regenerated${res.storeError ? `: ${res.storeError}` : ""}. ` + `Run \`mcp servers generate ${ctx.serverId} --store\` before deploying.`);
|
|
11794
|
+
}
|
|
11795
|
+
printInfo(` → The live server still serves the old names. Run ${c.bold(`mcp servers deploy ${ctx.serverId} --wait`)} to ship the new ones — after that, agents calling an old name get unknown-tool.`);
|
|
11796
|
+
if (ctx.localToolsDir) {
|
|
11797
|
+
printInfo(` → Local tool files in ${ctx.localToolsDir}/ still use the old names — run ${c.bold(`mcp tools pull --server ${ctx.serverId}`)} to refresh them.`);
|
|
11798
|
+
}
|
|
11799
|
+
}
|
|
11800
|
+
|
|
11801
|
+
// src/commands/tools-rename.ts
|
|
11802
|
+
var HELP_AFTER3 = [
|
|
11803
|
+
"",
|
|
11804
|
+
"A rename keeps the tool id, so what points at the tool follows it: end-user",
|
|
11805
|
+
"and RBAC access grants, saved test scenarios, and parent-server composition",
|
|
11806
|
+
"allowlists are rewritten to the new name. Prompt and skill TEXT is not — the",
|
|
11807
|
+
"report lists what still mentions an old name.",
|
|
11808
|
+
"",
|
|
11809
|
+
"The stored bundle is regenerated with the new names; the LIVE server keeps",
|
|
11810
|
+
"the old ones until `mcp servers deploy <server> --wait`. After that deploy an",
|
|
11811
|
+
"agent calling an old name gets unknown-tool.",
|
|
11812
|
+
"",
|
|
11813
|
+
"--from-operation-ids adopts the current naming rule: codegen now keeps the",
|
|
11814
|
+
"operationId's case (getRecording), where servers created earlier were named",
|
|
11815
|
+
"getrecording. Only names that differ from codegen by case are renamed; a",
|
|
11816
|
+
"custom name is kept and reported. A tool with an edited handler override is",
|
|
11817
|
+
"refused unless --drop-handler-overrides discards the override.",
|
|
11818
|
+
"",
|
|
11819
|
+
"Examples:",
|
|
11820
|
+
" $ mcp tools rename getrecording getRecording --server srv_123",
|
|
11821
|
+
" $ mcp tools rename --from-operation-ids --server srv_123 --dry-run",
|
|
11822
|
+
" $ mcp tools rename --map @renames.json --server srv_123",
|
|
11823
|
+
' # renames.json: {"getrecording": "getRecording"} or [{"from": "…", "to": "…"}]'
|
|
11824
|
+
];
|
|
11825
|
+
function registerToolsRenameCommand(tools) {
|
|
11826
|
+
tools.command("rename [old] [new]").description("Rename tools on a server, keeping their ids — access grants, saved test scenarios and composition references follow. Regenerates the stored bundle; deploy to ship the new names.").option("--org <organizationId>", "Organization ID (defaults to .mcpcloud/state.json)").option("--server <server>", "Server (id, name, or slug; defaults to .mcpcloud/state.json)").option("--dry-run", "Report what would be renamed; nothing is written").option("--from-operation-ids", "Rename every OpenAPI tool whose name differs from its operationId only by case (getrecording → getRecording)").option("--map <file>", 'Rename several tools from a JSON file (@renames.json): {"old": "new", …} or [{"from", "to"}, …]').option("--drop-handler-overrides", "Allow renaming tools that carry an edited handler override by discarding the override (it is regenerated from the spec)").addHelpText("after", HELP_AFTER3.join(`
|
|
11827
|
+
`)).action(runAction(async (oldName, newName, opts) => {
|
|
11828
|
+
await runToolsRename(oldName, newName, opts);
|
|
11829
|
+
}));
|
|
11830
|
+
}
|
|
11831
|
+
async function runToolsRename(oldName, newName, opts) {
|
|
11832
|
+
const cwd = process.cwd();
|
|
11833
|
+
const selection = resolveRenameSelection({
|
|
11834
|
+
oldName,
|
|
11835
|
+
newName,
|
|
11836
|
+
fromOperationIds: opts.fromOperationIds,
|
|
11837
|
+
map: opts.map,
|
|
11838
|
+
cwd
|
|
11839
|
+
});
|
|
11840
|
+
const ctx = await resolveToolsContext({
|
|
11841
|
+
orgOverride: opts.org,
|
|
11842
|
+
serverOverride: opts.server
|
|
11843
|
+
});
|
|
11844
|
+
if (!ctx)
|
|
11845
|
+
return;
|
|
11846
|
+
if (!isJsonMode()) {
|
|
11847
|
+
printStep(`${opts.dryRun ? "Previewing renames" : "Renaming tools"} on ${c.bold(ctx.serverId)}…`);
|
|
11848
|
+
}
|
|
11849
|
+
let res;
|
|
11850
|
+
try {
|
|
11851
|
+
res = await api.post("/api/v1/server/tools/rename", {
|
|
11852
|
+
organizationId: ctx.organizationId,
|
|
11853
|
+
serverId: ctx.serverId,
|
|
11854
|
+
...selectionBody(selection),
|
|
11855
|
+
...opts.dryRun ? { dryRun: true } : {},
|
|
11856
|
+
...opts.dropHandlerOverrides ? { dropHandlerOverrides: true } : {}
|
|
11857
|
+
});
|
|
11858
|
+
} catch (err) {
|
|
11859
|
+
if (!isJsonMode())
|
|
11860
|
+
reportRenameRefusal(err, ctx.serverId);
|
|
11861
|
+
throw err;
|
|
11862
|
+
}
|
|
11863
|
+
if (isJsonMode()) {
|
|
11864
|
+
printJson(res);
|
|
11865
|
+
return;
|
|
11866
|
+
}
|
|
11867
|
+
const localDir = serverToolsDir(devRoot(cwd), ctx.serverId);
|
|
11868
|
+
printRenameReport(res, {
|
|
11869
|
+
serverId: ctx.serverId,
|
|
11870
|
+
fromOperationIds: "fromOperationIds" in selection,
|
|
11871
|
+
localToolsDir: existsSync16(localDir) ? relative6(cwd, localDir) || "." : null
|
|
11872
|
+
});
|
|
11873
|
+
}
|
|
11874
|
+
function selectionBody(selection) {
|
|
11875
|
+
return "fromOperationIds" in selection ? { fromOperationIds: true } : { renames: selection.renames };
|
|
11876
|
+
}
|
|
11877
|
+
var PROBLEM_HINTS = {
|
|
11878
|
+
tool_not_found: (serverId) => `\`mcp tools list --server ${serverId}\` shows the current names.`,
|
|
11879
|
+
handler_override: () => "Re-run with --drop-handler-overrides to discard the edited handler (it is regenerated from the spec), or pull it first with `mcp tools pull-handler`.",
|
|
11880
|
+
name_conflict: () => "Pick a name no other tool on the server uses.",
|
|
11881
|
+
codegen_name_taken: () => "Codegen gives that name to a different operation in the spec, so this tool would inherit that operation's schema — choose another name."
|
|
11882
|
+
};
|
|
11883
|
+
function reportRenameRefusal(err, serverId) {
|
|
11884
|
+
if (!(err instanceof McpCloudApiError))
|
|
11885
|
+
return;
|
|
11886
|
+
if (err.error.code !== "tool_rename_invalid")
|
|
11887
|
+
return;
|
|
11888
|
+
const problems = err.error.details?.["problems"];
|
|
11889
|
+
if (!Array.isArray(problems) || problems.length === 0)
|
|
11890
|
+
return;
|
|
11891
|
+
printError("Rename refused — nothing was changed:");
|
|
11892
|
+
const hints = new Set;
|
|
11893
|
+
for (const raw of problems) {
|
|
11894
|
+
const pair = raw.to ? `${raw.from} → ${raw.to}` : raw.from;
|
|
11895
|
+
printInfo(` ${pair}: ${raw.message} (${raw.code})`);
|
|
11896
|
+
const hint = PROBLEM_HINTS[raw.code];
|
|
11897
|
+
if (hint)
|
|
11898
|
+
hints.add(hint(serverId));
|
|
11899
|
+
}
|
|
11900
|
+
for (const hint of hints)
|
|
11901
|
+
printInfo(c.dim(` → ${hint}`));
|
|
11902
|
+
throw new CliExitError(1);
|
|
11903
|
+
}
|
|
11904
|
+
|
|
11379
11905
|
// src/commands/tools-reset-handler.ts
|
|
11380
|
-
import { relative as
|
|
11906
|
+
import { relative as relative7 } from "node:path";
|
|
11381
11907
|
function registerToolsResetHandlerCommand(tools) {
|
|
11382
11908
|
tools.command("reset-handler <name>").description("Drop the cloud handler override for a tool and re-materialize the codegen default. Prompts to confirm unless --yes is passed.").option("--org <organizationId>", "Organization ID (defaults to .mcpcloud/state.json)").option("--server <server>", "Server (id, name, or slug; defaults to .mcpcloud/state.json)").option("--yes", "Skip the confirmation prompt", false).option("--force", "Overwrite local file even if it has unsaved changes (default: refuse).", false).addHelpText("after", [
|
|
11383
11909
|
"",
|
|
@@ -11488,7 +12014,7 @@ async function runResetHandler(args) {
|
|
|
11488
12014
|
});
|
|
11489
12015
|
return;
|
|
11490
12016
|
}
|
|
11491
|
-
const fileRel =
|
|
12017
|
+
const fileRel = relative7(cwd, written) || written;
|
|
11492
12018
|
if (result.snapshot.alreadyDefault) {
|
|
11493
12019
|
printSuccess(`Handler for ${c.bold(result.snapshot.toolName)} was already at the codegen default (no content change). Local file refreshed at ${c.bold(fileRel)}.`);
|
|
11494
12020
|
} else {
|
|
@@ -11498,7 +12024,7 @@ async function runResetHandler(args) {
|
|
|
11498
12024
|
}
|
|
11499
12025
|
|
|
11500
12026
|
// src/commands/tools-show.ts
|
|
11501
|
-
import { existsSync as
|
|
12027
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
11502
12028
|
function registerToolsShowCommand(tools) {
|
|
11503
12029
|
tools.command("show <name>").description("Show a tool's full applied metadata (and any pending AI suggestion).").option("--org <organizationId>", "Organization ID (defaults to .mcpcloud/state.json)").option("--server <server>", "Server (id, name, or slug; defaults to .mcpcloud/state.json)").action(runAction(async (name, opts) => {
|
|
11504
12030
|
await runToolsShow({
|
|
@@ -11526,7 +12052,7 @@ async function runToolsShow(args) {
|
|
|
11526
12052
|
throw new CliExitError(1);
|
|
11527
12053
|
}
|
|
11528
12054
|
const devRootDir = devRoot(process.cwd());
|
|
11529
|
-
const inDevTree =
|
|
12055
|
+
const inDevTree = existsSync17(devRootDir);
|
|
11530
12056
|
const [detailed, handlerResult] = await Promise.all([
|
|
11531
12057
|
fetchEnrichedToolDetail({
|
|
11532
12058
|
organizationId: ctx.organizationId,
|
|
@@ -11669,6 +12195,8 @@ function registerToolCommands(program) {
|
|
|
11669
12195
|
registerToolsShowCommand(tools);
|
|
11670
12196
|
registerToolsDiffCommand(tools);
|
|
11671
12197
|
registerToolsEditCommand(tools);
|
|
12198
|
+
registerToolsRenameCommand(tools);
|
|
12199
|
+
registerToolsDeleteCommand(tools);
|
|
11672
12200
|
registerToolsEnrichCommand(tools);
|
|
11673
12201
|
registerToolsPullCommand(tools);
|
|
11674
12202
|
registerToolsPullHandlerCommand(tools);
|
|
@@ -11826,13 +12354,13 @@ function isAgentKey(value) {
|
|
|
11826
12354
|
}
|
|
11827
12355
|
|
|
11828
12356
|
// src/commands/skills-lifecycle.ts
|
|
11829
|
-
import { readFileSync as
|
|
12357
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
11830
12358
|
function readValueOption2(raw) {
|
|
11831
12359
|
if (raw === "@-") {
|
|
11832
|
-
return
|
|
12360
|
+
return readFileSync17(0, "utf8");
|
|
11833
12361
|
}
|
|
11834
12362
|
if (raw.startsWith("@")) {
|
|
11835
|
-
return
|
|
12363
|
+
return readFileSync17(raw.slice(1), "utf8");
|
|
11836
12364
|
}
|
|
11837
12365
|
return raw;
|
|
11838
12366
|
}
|
|
@@ -12182,7 +12710,7 @@ function registerSkillMutationCommands(skills) {
|
|
|
12182
12710
|
}
|
|
12183
12711
|
|
|
12184
12712
|
// src/commands/skills-test-authoring.ts
|
|
12185
|
-
import { readFileSync as
|
|
12713
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
12186
12714
|
var FIXTURE_MODES = [
|
|
12187
12715
|
"baseline",
|
|
12188
12716
|
"validationFailure",
|
|
@@ -12191,9 +12719,9 @@ var FIXTURE_MODES = [
|
|
|
12191
12719
|
];
|
|
12192
12720
|
function readValueOption3(raw) {
|
|
12193
12721
|
if (raw === "@-")
|
|
12194
|
-
return
|
|
12722
|
+
return readFileSync18(0, "utf8");
|
|
12195
12723
|
if (raw.startsWith("@"))
|
|
12196
|
-
return
|
|
12724
|
+
return readFileSync18(raw.slice(1), "utf8");
|
|
12197
12725
|
return raw;
|
|
12198
12726
|
}
|
|
12199
12727
|
function parseAssertionsOption(raw) {
|
|
@@ -12760,14 +13288,14 @@ function registerSkillInstallCommand(skills) {
|
|
|
12760
13288
|
|
|
12761
13289
|
// src/commands/skills-pull.ts
|
|
12762
13290
|
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
12763
|
-
import { dirname as dirname6, isAbsolute as
|
|
13291
|
+
import { dirname as dirname6, isAbsolute as isAbsolute4, join as join12, resolve as resolve8 } from "node:path";
|
|
12764
13292
|
function formatBytes2(bytes) {
|
|
12765
13293
|
if (bytes < 1024)
|
|
12766
13294
|
return `${bytes} B`;
|
|
12767
13295
|
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
12768
13296
|
}
|
|
12769
13297
|
function assertSafeRelativePath2(path) {
|
|
12770
|
-
if (
|
|
13298
|
+
if (isAbsolute4(path) || path.split(/[\\/]/).includes("..")) {
|
|
12771
13299
|
throw new Error(`Refusing to write unsafe package path: ${path}`);
|
|
12772
13300
|
}
|
|
12773
13301
|
}
|
|
@@ -12788,10 +13316,10 @@ function registerSkillPullCommand(skills) {
|
|
|
12788
13316
|
registryArtifactId,
|
|
12789
13317
|
...opts.skillVersion ? { version: opts.skillVersion } : {}
|
|
12790
13318
|
});
|
|
12791
|
-
const dir =
|
|
13319
|
+
const dir = resolve8(opts.output ?? `./${result.skill.slug}`);
|
|
12792
13320
|
for (const file of result.files) {
|
|
12793
13321
|
assertSafeRelativePath2(file.path);
|
|
12794
|
-
const full =
|
|
13322
|
+
const full = join12(dir, file.path);
|
|
12795
13323
|
mkdirSync8(dirname6(full), { recursive: true });
|
|
12796
13324
|
writeFileSync7(full, file.content);
|
|
12797
13325
|
}
|
|
@@ -12809,12 +13337,12 @@ function registerSkillPullCommand(skills) {
|
|
|
12809
13337
|
const missing = result.dependencies.filter((entry) => !entry.installed);
|
|
12810
13338
|
if (result.dependencies.length === 0) {
|
|
12811
13339
|
printKeyValue({
|
|
12812
|
-
Next: `Copy ${
|
|
13340
|
+
Next: `Copy ${join12(dir, "SKILL.md")} into the skills directory your agent reads.`
|
|
12813
13341
|
});
|
|
12814
13342
|
return;
|
|
12815
13343
|
}
|
|
12816
13344
|
printKeyValue({
|
|
12817
|
-
Next: `Copy ${
|
|
13345
|
+
Next: `Copy ${join12(dir, "SKILL.md")} into the skills directory your agent reads.`,
|
|
12818
13346
|
Servers: result.dependencies.map((entry) => `${entry.name}${entry.installed ? "" : " (not installed)"}`).join(", ")
|
|
12819
13347
|
});
|
|
12820
13348
|
if (missing.length > 0) {
|
|
@@ -13246,18 +13774,18 @@ function registerSkillCommands(program) {
|
|
|
13246
13774
|
}
|
|
13247
13775
|
|
|
13248
13776
|
// src/lib/dev/prepare.ts
|
|
13249
|
-
import { existsSync as
|
|
13250
|
-
import { join as
|
|
13777
|
+
import { existsSync as existsSync24, readFileSync as readFileSync22, writeFileSync as writeFileSync12 } from "node:fs";
|
|
13778
|
+
import { join as join19, relative as relative8, resolve as resolve9 } from "node:path";
|
|
13251
13779
|
|
|
13252
13780
|
// src/lib/dev/bundle-sync.ts
|
|
13253
13781
|
import {
|
|
13254
|
-
existsSync as
|
|
13782
|
+
existsSync as existsSync18,
|
|
13255
13783
|
mkdirSync as mkdirSync9,
|
|
13256
|
-
readFileSync as
|
|
13784
|
+
readFileSync as readFileSync19,
|
|
13257
13785
|
rmSync as rmSync2,
|
|
13258
13786
|
writeFileSync as writeFileSync8
|
|
13259
13787
|
} from "node:fs";
|
|
13260
|
-
import { dirname as dirname7, join as
|
|
13788
|
+
import { dirname as dirname7, join as join13 } from "node:path";
|
|
13261
13789
|
async function resolveServerProject(args) {
|
|
13262
13790
|
const data = await api.get("/api/v1/server", {
|
|
13263
13791
|
organizationId: args.organizationId,
|
|
@@ -13318,7 +13846,7 @@ async function fetchBundle(args) {
|
|
|
13318
13846
|
return parsed;
|
|
13319
13847
|
}
|
|
13320
13848
|
function materializeBundle(destDir, bundle, options = {}) {
|
|
13321
|
-
if (!
|
|
13849
|
+
if (!existsSync18(destDir)) {
|
|
13322
13850
|
mkdirSync9(destDir, { recursive: true });
|
|
13323
13851
|
}
|
|
13324
13852
|
const preserve = options.preservePaths;
|
|
@@ -13334,7 +13862,7 @@ function materializeBundle(destDir, bundle, options = {}) {
|
|
|
13334
13862
|
preservedCount += 1;
|
|
13335
13863
|
continue;
|
|
13336
13864
|
}
|
|
13337
|
-
const target =
|
|
13865
|
+
const target = join13(destDir, safePath);
|
|
13338
13866
|
mkdirSync9(dirname7(target), { recursive: true });
|
|
13339
13867
|
writeFileSync8(target, file.content, "utf-8");
|
|
13340
13868
|
writtenCount += 1;
|
|
@@ -13364,7 +13892,7 @@ function pruneStaleFiles(rootDir, currentDir, kept) {
|
|
|
13364
13892
|
const { relative } = __require("node:path");
|
|
13365
13893
|
let removed = 0;
|
|
13366
13894
|
for (const entry of readdirSync(currentDir)) {
|
|
13367
|
-
const abs =
|
|
13895
|
+
const abs = join13(currentDir, entry);
|
|
13368
13896
|
const stat = statSync(abs);
|
|
13369
13897
|
if (stat.isDirectory()) {
|
|
13370
13898
|
removed += pruneStaleFiles(rootDir, abs, kept);
|
|
@@ -13388,12 +13916,12 @@ function pruneStaleFiles(rootDir, currentDir, kept) {
|
|
|
13388
13916
|
// src/lib/dev/git-clone.ts
|
|
13389
13917
|
import { spawnSync } from "node:child_process";
|
|
13390
13918
|
import {
|
|
13391
|
-
existsSync as
|
|
13919
|
+
existsSync as existsSync19,
|
|
13392
13920
|
mkdirSync as mkdirSync10,
|
|
13393
|
-
readFileSync as
|
|
13921
|
+
readFileSync as readFileSync20,
|
|
13394
13922
|
writeFileSync as writeFileSync9
|
|
13395
13923
|
} from "node:fs";
|
|
13396
|
-
import { dirname as dirname8, join as
|
|
13924
|
+
import { dirname as dirname8, join as join14 } from "node:path";
|
|
13397
13925
|
|
|
13398
13926
|
class GitNotAvailableError extends Error {
|
|
13399
13927
|
constructor() {
|
|
@@ -13484,12 +14012,12 @@ function isGitAvailable() {
|
|
|
13484
14012
|
}
|
|
13485
14013
|
}
|
|
13486
14014
|
function applyLocalExcludes(repoDir) {
|
|
13487
|
-
const excludePath =
|
|
13488
|
-
const existing =
|
|
14015
|
+
const excludePath = join14(repoDir, ".git", "info", "exclude");
|
|
14016
|
+
const existing = existsSync19(excludePath) ? readFileSync20(excludePath, "utf-8") : "";
|
|
13489
14017
|
const next = mergeLocalExcludes(existing);
|
|
13490
14018
|
if (next === null)
|
|
13491
14019
|
return;
|
|
13492
|
-
if (!
|
|
14020
|
+
if (!existsSync19(dirname8(excludePath))) {
|
|
13493
14021
|
mkdirSync10(dirname8(excludePath), { recursive: true });
|
|
13494
14022
|
}
|
|
13495
14023
|
writeFileSync9(excludePath, next, "utf-8");
|
|
@@ -13498,7 +14026,7 @@ function ensureClone(opts) {
|
|
|
13498
14026
|
if (!isGitAvailable()) {
|
|
13499
14027
|
throw new GitNotAvailableError;
|
|
13500
14028
|
}
|
|
13501
|
-
if (
|
|
14029
|
+
if (existsSync19(join14(opts.repoDir, ".git"))) {
|
|
13502
14030
|
opts.onStep?.("Refreshing existing clone (git fetch)…");
|
|
13503
14031
|
runGit(gitFetchArgs(opts.branch), opts.repoDir);
|
|
13504
14032
|
applyLocalExcludes(opts.repoDir);
|
|
@@ -13514,12 +14042,12 @@ function ensureClone(opts) {
|
|
|
13514
14042
|
}
|
|
13515
14043
|
|
|
13516
14044
|
// src/lib/dev/host-runtime.ts
|
|
13517
|
-
import { existsSync as
|
|
13518
|
-
import { dirname as dirname9, join as
|
|
14045
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync12, writeFileSync as writeFileSync11 } from "node:fs";
|
|
14046
|
+
import { dirname as dirname9, join as join16 } from "node:path";
|
|
13519
14047
|
|
|
13520
14048
|
// src/lib/dev/inspector-module.ts
|
|
13521
|
-
import { existsSync as
|
|
13522
|
-
import { join as
|
|
14049
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "node:fs";
|
|
14050
|
+
import { join as join15 } from "node:path";
|
|
13523
14051
|
|
|
13524
14052
|
// src/lib/dev/inspector-assets.ts
|
|
13525
14053
|
var INSPECTOR_HTML = `<!doctype html>
|
|
@@ -28667,9 +29195,9 @@ function buildInspectorModule() {
|
|
|
28667
29195
|
`);
|
|
28668
29196
|
}
|
|
28669
29197
|
function writeInspectorModule(serverDir) {
|
|
28670
|
-
if (!
|
|
29198
|
+
if (!existsSync20(serverDir))
|
|
28671
29199
|
mkdirSync11(serverDir, { recursive: true });
|
|
28672
|
-
const target =
|
|
29200
|
+
const target = join15(serverDir, INSPECTOR_FILE_NAME);
|
|
28673
29201
|
writeFileSync10(target, buildInspectorModule(), "utf-8");
|
|
28674
29202
|
return target;
|
|
28675
29203
|
}
|
|
@@ -28855,9 +29383,9 @@ function buildHostScript(options) {
|
|
|
28855
29383
|
`);
|
|
28856
29384
|
}
|
|
28857
29385
|
function writeHostScript(serverDir, options) {
|
|
28858
|
-
if (!
|
|
29386
|
+
if (!existsSync21(serverDir))
|
|
28859
29387
|
mkdirSync12(serverDir, { recursive: true });
|
|
28860
|
-
const target =
|
|
29388
|
+
const target = join16(serverDir, HOST_FILE_NAME);
|
|
28861
29389
|
mkdirSync12(dirname9(target), { recursive: true });
|
|
28862
29390
|
writeFileSync11(target, buildHostScript(options), "utf-8");
|
|
28863
29391
|
if (options.inspector) {
|
|
@@ -28869,38 +29397,38 @@ function writeHostScript(serverDir, options) {
|
|
|
28869
29397
|
// src/lib/dev/inspector-store.ts
|
|
28870
29398
|
import {
|
|
28871
29399
|
appendFileSync as appendFileSync2,
|
|
28872
|
-
existsSync as
|
|
29400
|
+
existsSync as existsSync22,
|
|
28873
29401
|
mkdirSync as mkdirSync13,
|
|
28874
|
-
readFileSync as
|
|
29402
|
+
readFileSync as readFileSync21,
|
|
28875
29403
|
readdirSync as readdirSync2,
|
|
28876
29404
|
renameSync,
|
|
28877
29405
|
statSync as statSync2
|
|
28878
29406
|
} from "node:fs";
|
|
28879
|
-
import { join as
|
|
29407
|
+
import { join as join17 } from "node:path";
|
|
28880
29408
|
var INSPECTOR_DIRNAME = "inspector";
|
|
28881
29409
|
var ACTIVE_FILE = "calls.ndjson";
|
|
28882
29410
|
var ROTATED_PREFIX = "calls.";
|
|
28883
29411
|
var ROTATED_SUFFIX = ".ndjson";
|
|
28884
29412
|
var ROTATION_LIMIT_BYTES = 50 * 1024 * 1024;
|
|
28885
29413
|
function inspectorDir(cwd) {
|
|
28886
|
-
return
|
|
29414
|
+
return join17(devRoot(cwd), INSPECTOR_DIRNAME);
|
|
28887
29415
|
}
|
|
28888
29416
|
function ensureInspectorDir(dir) {
|
|
28889
|
-
if (!
|
|
29417
|
+
if (!existsSync22(dir))
|
|
28890
29418
|
mkdirSync13(dir, { recursive: true });
|
|
28891
29419
|
}
|
|
28892
29420
|
function activeFilePath(dir) {
|
|
28893
|
-
return
|
|
29421
|
+
return join17(dir, ACTIVE_FILE);
|
|
28894
29422
|
}
|
|
28895
29423
|
function isRotatedName(name) {
|
|
28896
29424
|
return name !== ACTIVE_FILE && name.startsWith(ROTATED_PREFIX) && name.endsWith(ROTATED_SUFFIX);
|
|
28897
29425
|
}
|
|
28898
29426
|
function listRotatedFiles(dir) {
|
|
28899
|
-
if (!
|
|
29427
|
+
if (!existsSync22(dir))
|
|
28900
29428
|
return [];
|
|
28901
29429
|
const rotated = readdirSync2(dir).filter(isRotatedName);
|
|
28902
29430
|
rotated.sort();
|
|
28903
|
-
return rotated.map((n) =>
|
|
29431
|
+
return rotated.map((n) => join17(dir, n));
|
|
28904
29432
|
}
|
|
28905
29433
|
function parseLines(text) {
|
|
28906
29434
|
if (!text)
|
|
@@ -28917,13 +29445,13 @@ function parseLines(text) {
|
|
|
28917
29445
|
return out;
|
|
28918
29446
|
}
|
|
28919
29447
|
function readAll(dir) {
|
|
28920
|
-
if (!
|
|
29448
|
+
if (!existsSync22(dir))
|
|
28921
29449
|
return [];
|
|
28922
29450
|
const out = [];
|
|
28923
29451
|
for (const path of [...listRotatedFiles(dir), activeFilePath(dir)]) {
|
|
28924
|
-
if (!
|
|
29452
|
+
if (!existsSync22(path))
|
|
28925
29453
|
continue;
|
|
28926
|
-
out.push(...parseLines(
|
|
29454
|
+
out.push(...parseLines(readFileSync21(path, "utf-8")));
|
|
28927
29455
|
}
|
|
28928
29456
|
return out;
|
|
28929
29457
|
}
|
|
@@ -28973,8 +29501,8 @@ function parseDuration(input) {
|
|
|
28973
29501
|
|
|
28974
29502
|
// src/lib/dev/local-runtime.ts
|
|
28975
29503
|
import { spawn as spawn4 } from "node:child_process";
|
|
28976
|
-
import { existsSync as
|
|
28977
|
-
import { join as
|
|
29504
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
29505
|
+
import { join as join18 } from "node:path";
|
|
28978
29506
|
function detectRuntime(preferred = "auto") {
|
|
28979
29507
|
if (preferred === "bun" || preferred === "node")
|
|
28980
29508
|
return preferred;
|
|
@@ -28985,7 +29513,7 @@ function detectRuntime(preferred = "auto") {
|
|
|
28985
29513
|
return "node";
|
|
28986
29514
|
}
|
|
28987
29515
|
function startRuntime(options) {
|
|
28988
|
-
if (!
|
|
29516
|
+
if (!existsSync23(options.hostScriptPath)) {
|
|
28989
29517
|
throw new Error(`Host script not found: ${options.hostScriptPath}`);
|
|
28990
29518
|
}
|
|
28991
29519
|
const runtime = detectRuntime(options.runtime);
|
|
@@ -29026,11 +29554,11 @@ function startRuntime(options) {
|
|
|
29026
29554
|
};
|
|
29027
29555
|
}
|
|
29028
29556
|
async function installDependencies(options) {
|
|
29029
|
-
const pkgPath =
|
|
29030
|
-
if (!
|
|
29557
|
+
const pkgPath = join18(options.serverDir, "package.json");
|
|
29558
|
+
if (!existsSync23(pkgPath))
|
|
29031
29559
|
return { ran: false, exitCode: null };
|
|
29032
|
-
const nodeModules =
|
|
29033
|
-
if (options.skipIfPresent !== false &&
|
|
29560
|
+
const nodeModules = join18(options.serverDir, "node_modules");
|
|
29561
|
+
if (options.skipIfPresent !== false && existsSync23(nodeModules)) {
|
|
29034
29562
|
return { ran: false, exitCode: 0 };
|
|
29035
29563
|
}
|
|
29036
29564
|
options.onStep?.("Installing dependencies (bun install)…");
|
|
@@ -29069,22 +29597,22 @@ function missingEnvBindingNames(envExample, envJson) {
|
|
|
29069
29597
|
return names;
|
|
29070
29598
|
}
|
|
29071
29599
|
function printEnvBindingHint(dest, envPath, cwd) {
|
|
29072
|
-
const examplePath =
|
|
29073
|
-
if (!
|
|
29600
|
+
const examplePath = join19(dest, ".env.example");
|
|
29601
|
+
if (!existsSync24(examplePath))
|
|
29074
29602
|
return;
|
|
29075
29603
|
let example = "";
|
|
29076
29604
|
let envJson = "{}";
|
|
29077
29605
|
try {
|
|
29078
|
-
example =
|
|
29079
|
-
if (
|
|
29080
|
-
envJson =
|
|
29606
|
+
example = readFileSync22(examplePath, "utf-8");
|
|
29607
|
+
if (existsSync24(envPath))
|
|
29608
|
+
envJson = readFileSync22(envPath, "utf-8");
|
|
29081
29609
|
} catch {
|
|
29082
29610
|
return;
|
|
29083
29611
|
}
|
|
29084
29612
|
const missing = missingEnvBindingNames(example, envJson);
|
|
29085
29613
|
if (missing.length === 0)
|
|
29086
29614
|
return;
|
|
29087
|
-
printInfo(` → this server expects ${missing.map((n) => c.bold(n)).join(", ")} — add value${missing.length === 1 ? "" : "s"} to ${
|
|
29615
|
+
printInfo(` → this server expects ${missing.map((n) => c.bold(n)).join(", ")} — add value${missing.length === 1 ? "" : "s"} to ${relative8(cwd, envPath)} (upstream calls 401 until then)`);
|
|
29088
29616
|
}
|
|
29089
29617
|
var ENTRY_CANDIDATES = ["src/worker.ts", "src/server.ts", "src/index.ts"];
|
|
29090
29618
|
async function prepareDev(opts) {
|
|
@@ -29111,9 +29639,9 @@ async function prepareDev(opts) {
|
|
|
29111
29639
|
projectId = projectId ?? gitLink.projectId;
|
|
29112
29640
|
serverName = `${gitLink.owner}/${gitLink.name}`;
|
|
29113
29641
|
const registered = readConfig().gitRepoDirs?.[opts.serverId];
|
|
29114
|
-
const chosen = opts.repoDirOverride ?
|
|
29642
|
+
const chosen = opts.repoDirOverride ? resolve9(cwd, opts.repoDirOverride) : registered;
|
|
29115
29643
|
repoDir = chosen ?? gitCloneDir(cwd, gitLink.owner, gitLink.name);
|
|
29116
|
-
if (chosen &&
|
|
29644
|
+
if (chosen && existsSync24(join19(repoDir, ".git"))) {
|
|
29117
29645
|
const origin = getOriginUrl(repoDir);
|
|
29118
29646
|
if (!origin || !originMatchesRepo(origin, gitLink.owner, gitLink.name)) {
|
|
29119
29647
|
printError(`${repoDir} is a checkout of ${origin ?? "an unknown remote"}, not ${gitLink.owner}/${gitLink.name}. Pass a checkout of the linked repo, or drop --repo to use the default clone.`);
|
|
@@ -29123,7 +29651,7 @@ async function prepareDev(opts) {
|
|
|
29123
29651
|
if (registered && !opts.repoDirOverride) {
|
|
29124
29652
|
printInfo(` → using registered checkout ${repoDir}`);
|
|
29125
29653
|
}
|
|
29126
|
-
dest = gitLink.pathPrefix ?
|
|
29654
|
+
dest = gitLink.pathPrefix ? join19(repoDir, gitLink.pathPrefix) : repoDir;
|
|
29127
29655
|
printStep(`Git-linked server — cloning ${gitLink.owner}/${gitLink.name}@${gitLink.branch}…`);
|
|
29128
29656
|
try {
|
|
29129
29657
|
const result = ensureClone({
|
|
@@ -29132,7 +29660,7 @@ async function prepareDev(opts) {
|
|
|
29132
29660
|
branch: gitLink.branch,
|
|
29133
29661
|
onStep: (m) => printStep(m)
|
|
29134
29662
|
});
|
|
29135
|
-
printSuccess(result.cloned ? `Cloned to ${
|
|
29663
|
+
printSuccess(result.cloned ? `Cloned to ${relative8(cwd, repoDir)}` : `Using existing clone at ${relative8(cwd, repoDir)}`);
|
|
29136
29664
|
const cfg = readConfig();
|
|
29137
29665
|
if (cfg.gitRepoDirs?.[opts.serverId] !== repoDir) {
|
|
29138
29666
|
cfg.gitRepoDirs = { ...cfg.gitRepoDirs, [opts.serverId]: repoDir };
|
|
@@ -29148,10 +29676,10 @@ async function prepareDev(opts) {
|
|
|
29148
29676
|
}
|
|
29149
29677
|
process.exit(1);
|
|
29150
29678
|
}
|
|
29151
|
-
if (!
|
|
29679
|
+
if (!existsSync24(env)) {
|
|
29152
29680
|
writeFileSync12(env, `{}
|
|
29153
29681
|
`, "utf-8");
|
|
29154
|
-
printInfo(` → seeded ${
|
|
29682
|
+
printInfo(` → seeded ${relative8(cwd, env)} (empty; fill secrets here)`);
|
|
29155
29683
|
}
|
|
29156
29684
|
printEnvBindingHint(dest, env, cwd);
|
|
29157
29685
|
printInfo(` ${"Open the clone in your editor; commit + push to sync (config under mcpcloud/ is read-only)."}`);
|
|
@@ -29174,10 +29702,10 @@ async function prepareDev(opts) {
|
|
|
29174
29702
|
});
|
|
29175
29703
|
const result = materializeBundle(dest, bundle, { prune: true });
|
|
29176
29704
|
printInfo(` → wrote ${result.filesWritten} files (pruned ${result.filesRemoved})`);
|
|
29177
|
-
if (!
|
|
29705
|
+
if (!existsSync24(env)) {
|
|
29178
29706
|
writeFileSync12(env, `{}
|
|
29179
29707
|
`, "utf-8");
|
|
29180
|
-
printInfo(` → seeded ${
|
|
29708
|
+
printInfo(` → seeded ${relative8(cwd, env)} (empty; fill secrets here)`);
|
|
29181
29709
|
}
|
|
29182
29710
|
printEnvBindingHint(dest, env, cwd);
|
|
29183
29711
|
printStep("Fetching tool metadata…");
|
|
@@ -29193,7 +29721,7 @@ async function prepareDev(opts) {
|
|
|
29193
29721
|
});
|
|
29194
29722
|
printInfo(` → wrote ${toolsResult.filesWritten} editable tool file${toolsResult.filesWritten === 1 ? "" : "s"}${toolsResult.filesRemoved > 0 ? ` (pruned ${toolsResult.filesRemoved})` : ""}`);
|
|
29195
29723
|
} else {
|
|
29196
|
-
if (!
|
|
29724
|
+
if (!existsSync24(dest)) {
|
|
29197
29725
|
printError("No cached bundle found. Run once without --offline first.");
|
|
29198
29726
|
process.exit(1);
|
|
29199
29727
|
}
|
|
@@ -29202,7 +29730,7 @@ async function prepareDev(opts) {
|
|
|
29202
29730
|
process.exit(1);
|
|
29203
29731
|
}
|
|
29204
29732
|
}
|
|
29205
|
-
const entryRel = ENTRY_CANDIDATES.find((c2) =>
|
|
29733
|
+
const entryRel = ENTRY_CANDIDATES.find((c2) => existsSync24(join19(dest, c2)));
|
|
29206
29734
|
if (!entryRel) {
|
|
29207
29735
|
printError("Could not locate an entry file (expected src/worker.ts).");
|
|
29208
29736
|
process.exit(1);
|
|
@@ -29250,8 +29778,8 @@ async function prepareDev(opts) {
|
|
|
29250
29778
|
|
|
29251
29779
|
// src/lib/dev/tree-sync.ts
|
|
29252
29780
|
import { createHash as createHash4 } from "node:crypto";
|
|
29253
|
-
import { existsSync as
|
|
29254
|
-
import { join as
|
|
29781
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync14, readdirSync as readdirSync3, readFileSync as readFileSync23, writeFileSync as writeFileSync13 } from "node:fs";
|
|
29782
|
+
import { join as join20 } from "node:path";
|
|
29255
29783
|
var AUTHORING_GUIDE_FILE = "AGENTS.md";
|
|
29256
29784
|
var STRUCTURAL_FRONTMATTER_KEYS = [
|
|
29257
29785
|
"inputSchema",
|
|
@@ -29266,13 +29794,13 @@ function isEmptyPlan(plan) {
|
|
|
29266
29794
|
}
|
|
29267
29795
|
function listToolFiles(devRootDir, serverId) {
|
|
29268
29796
|
const dir = serverToolsDir(devRootDir, serverId);
|
|
29269
|
-
if (!
|
|
29797
|
+
if (!existsSync25(dir))
|
|
29270
29798
|
return [];
|
|
29271
29799
|
return readdirSync3(dir).filter((entry) => entry.endsWith(".md")).filter((entry) => entry !== AUTHORING_GUIDE_FILE).map((entry) => entry.slice(0, -3)).sort();
|
|
29272
29800
|
}
|
|
29273
29801
|
function readToolFile(devRootDir, serverId, toolName) {
|
|
29274
|
-
const file =
|
|
29275
|
-
const raw =
|
|
29802
|
+
const file = join20(serverToolsDir(devRootDir, serverId), `${toolName}.md`);
|
|
29803
|
+
const raw = readFileSync23(file, "utf-8");
|
|
29276
29804
|
try {
|
|
29277
29805
|
return { raw, view: parseToolMarkdown(raw) };
|
|
29278
29806
|
} catch (err) {
|
|
@@ -29298,7 +29826,7 @@ async function writeAuthoringGuide(args) {
|
|
|
29298
29826
|
return null;
|
|
29299
29827
|
const dir = serverToolsDir(args.devRootDir, args.serverId);
|
|
29300
29828
|
mkdirSync14(dir, { recursive: true });
|
|
29301
|
-
const guidePath =
|
|
29829
|
+
const guidePath = join20(dir, AUTHORING_GUIDE_FILE);
|
|
29302
29830
|
writeFileSync13(guidePath, markdown, "utf8");
|
|
29303
29831
|
return guidePath;
|
|
29304
29832
|
} catch {
|
|
@@ -29350,6 +29878,14 @@ function planPush(args) {
|
|
|
29350
29878
|
changes.push({ kind: "new-tool", toolName });
|
|
29351
29879
|
continue;
|
|
29352
29880
|
}
|
|
29881
|
+
if (parsed.view.name !== toolName) {
|
|
29882
|
+
blockers.push({
|
|
29883
|
+
reason: `\`name:\` was changed to ${parsed.view.name}; editing the file does not rename a tool`,
|
|
29884
|
+
remedy: `mcp tools rename ${toolName} ${parsed.view.name}`,
|
|
29885
|
+
toolName
|
|
29886
|
+
});
|
|
29887
|
+
continue;
|
|
29888
|
+
}
|
|
29353
29889
|
if (hashLocalView(parsed.view) !== tracked.descriptionHash) {
|
|
29354
29890
|
changes.push({
|
|
29355
29891
|
changedFields: changedFieldNames(parsed.view, tracked.descriptionHash),
|
|
@@ -29381,18 +29917,18 @@ function changedFieldNames(view, storedHash) {
|
|
|
29381
29917
|
}
|
|
29382
29918
|
var HANDLER_MANIFEST_FILE = ".handlers-state.json";
|
|
29383
29919
|
function handlerManifestPath(devRootDir, serverId) {
|
|
29384
|
-
return
|
|
29920
|
+
return join20(serverToolsDir(devRootDir, serverId), HANDLER_MANIFEST_FILE);
|
|
29385
29921
|
}
|
|
29386
29922
|
function hashSource2(source) {
|
|
29387
29923
|
return createHash4("sha256").update(source).digest("hex");
|
|
29388
29924
|
}
|
|
29389
29925
|
function listHandlerFiles(devRootDir) {
|
|
29390
29926
|
const dir = serverBundleHandlersDir(devRootDir);
|
|
29391
|
-
if (!
|
|
29927
|
+
if (!existsSync25(dir))
|
|
29392
29928
|
return [];
|
|
29393
29929
|
return readdirSync3(dir).filter((entry) => entry.endsWith(".ts") && !entry.endsWith(".definition.ts")).sort().map((entry) => ({
|
|
29394
29930
|
slug: entry.slice(0, -3),
|
|
29395
|
-
source:
|
|
29931
|
+
source: readFileSync23(join20(dir, entry), "utf-8")
|
|
29396
29932
|
}));
|
|
29397
29933
|
}
|
|
29398
29934
|
function writeHandlerManifest(args) {
|
|
@@ -29401,7 +29937,7 @@ function writeHandlerManifest(args) {
|
|
|
29401
29937
|
handlers[file.slug] = hashSource2(file.source);
|
|
29402
29938
|
}
|
|
29403
29939
|
const dir = serverToolsDir(args.devRootDir, args.serverId);
|
|
29404
|
-
if (!
|
|
29940
|
+
if (!existsSync25(dir))
|
|
29405
29941
|
mkdirSync14(dir, { recursive: true });
|
|
29406
29942
|
const manifest = {
|
|
29407
29943
|
handlers,
|
|
@@ -29412,23 +29948,23 @@ function writeHandlerManifest(args) {
|
|
|
29412
29948
|
return Object.keys(handlers).length;
|
|
29413
29949
|
}
|
|
29414
29950
|
function recordHandlerPushed(args) {
|
|
29415
|
-
const file =
|
|
29416
|
-
if (!
|
|
29951
|
+
const file = join20(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
|
|
29952
|
+
if (!existsSync25(file))
|
|
29417
29953
|
return;
|
|
29418
29954
|
const manifest = readHandlerManifest(args.devRootDir, args.serverId) ?? {
|
|
29419
29955
|
handlers: {},
|
|
29420
29956
|
serverId: args.serverId,
|
|
29421
29957
|
version: 1
|
|
29422
29958
|
};
|
|
29423
|
-
manifest.handlers[args.slug] = hashSource2(
|
|
29959
|
+
manifest.handlers[args.slug] = hashSource2(readFileSync23(file, "utf-8"));
|
|
29424
29960
|
writeFileSync13(handlerManifestPath(args.devRootDir, args.serverId), JSON.stringify(manifest, null, 2), "utf-8");
|
|
29425
29961
|
}
|
|
29426
29962
|
function readHandlerManifest(devRootDir, serverId) {
|
|
29427
29963
|
const file = handlerManifestPath(devRootDir, serverId);
|
|
29428
|
-
if (!
|
|
29964
|
+
if (!existsSync25(file))
|
|
29429
29965
|
return null;
|
|
29430
29966
|
try {
|
|
29431
|
-
const parsed = JSON.parse(
|
|
29967
|
+
const parsed = JSON.parse(readFileSync23(file, "utf-8"));
|
|
29432
29968
|
return parsed.version === 1 && parsed.handlers ? parsed : null;
|
|
29433
29969
|
} catch {
|
|
29434
29970
|
return null;
|
|
@@ -29513,12 +30049,22 @@ function describeToolPush(toolName, result) {
|
|
|
29513
30049
|
ok: false,
|
|
29514
30050
|
target: toolName
|
|
29515
30051
|
};
|
|
29516
|
-
|
|
30052
|
+
case "rename-requested":
|
|
30053
|
+
return {
|
|
30054
|
+
detail: `editing \`name:\` does not rename — use \`mcp tools rename ${result.from} ${result.to}\``,
|
|
30055
|
+
ok: false,
|
|
30056
|
+
target: toolName
|
|
30057
|
+
};
|
|
30058
|
+
case "api-error":
|
|
29517
30059
|
return {
|
|
29518
30060
|
detail: `${result.message} (HTTP ${result.status})`,
|
|
29519
30061
|
ok: false,
|
|
29520
30062
|
target: toolName
|
|
29521
30063
|
};
|
|
30064
|
+
default: {
|
|
30065
|
+
const exhaustive = result;
|
|
30066
|
+
return exhaustive;
|
|
30067
|
+
}
|
|
29522
30068
|
}
|
|
29523
30069
|
}
|
|
29524
30070
|
function describeHandlerPush(toolName, result) {
|
|
@@ -30188,9 +30734,9 @@ function registerDomainCommands(program) {
|
|
|
30188
30734
|
|
|
30189
30735
|
// src/commands/config.ts
|
|
30190
30736
|
import { homedir as homedir2 } from "node:os";
|
|
30191
|
-
import { join as
|
|
30737
|
+
import { join as join21 } from "node:path";
|
|
30192
30738
|
function configFilePath() {
|
|
30193
|
-
return
|
|
30739
|
+
return join21(homedir2(), ".mcpcloud", "config.json");
|
|
30194
30740
|
}
|
|
30195
30741
|
function previewKey2(key) {
|
|
30196
30742
|
if (!key)
|
|
@@ -30435,30 +30981,30 @@ function registerConfigCommands(program) {
|
|
|
30435
30981
|
}
|
|
30436
30982
|
|
|
30437
30983
|
// src/commands/dev.ts
|
|
30438
|
-
import { existsSync as
|
|
30439
|
-
import { isAbsolute as
|
|
30984
|
+
import { existsSync as existsSync38 } from "node:fs";
|
|
30985
|
+
import { isAbsolute as isAbsolute5, relative as relative9, resolve as resolve11 } from "node:path";
|
|
30440
30986
|
|
|
30441
30987
|
// src/lib/dev/sessions.ts
|
|
30442
30988
|
import {
|
|
30443
|
-
existsSync as
|
|
30989
|
+
existsSync as existsSync26,
|
|
30444
30990
|
mkdirSync as mkdirSync15,
|
|
30445
|
-
readFileSync as
|
|
30991
|
+
readFileSync as readFileSync24,
|
|
30446
30992
|
readdirSync as readdirSync4,
|
|
30447
30993
|
unlinkSync,
|
|
30448
30994
|
writeFileSync as writeFileSync14
|
|
30449
30995
|
} from "node:fs";
|
|
30450
30996
|
import { homedir as homedir3 } from "node:os";
|
|
30451
|
-
import { join as
|
|
30997
|
+
import { join as join22 } from "node:path";
|
|
30452
30998
|
var SESSIONS_DIR_NAME = "dev-sessions";
|
|
30453
30999
|
function sessionsDir() {
|
|
30454
|
-
return
|
|
31000
|
+
return join22(homedir3(), ".mcpcloud", SESSIONS_DIR_NAME);
|
|
30455
31001
|
}
|
|
30456
31002
|
function sessionFile(pid) {
|
|
30457
|
-
return
|
|
31003
|
+
return join22(sessionsDir(), `${pid}.json`);
|
|
30458
31004
|
}
|
|
30459
31005
|
function ensureDir() {
|
|
30460
31006
|
const dir = sessionsDir();
|
|
30461
|
-
if (!
|
|
31007
|
+
if (!existsSync26(dir))
|
|
30462
31008
|
mkdirSync15(dir, { recursive: true, mode: 448 });
|
|
30463
31009
|
}
|
|
30464
31010
|
function recordSession(record) {
|
|
@@ -30470,7 +31016,7 @@ function recordSession(record) {
|
|
|
30470
31016
|
}
|
|
30471
31017
|
function removeSession(pid) {
|
|
30472
31018
|
const path = sessionFile(pid);
|
|
30473
|
-
if (
|
|
31019
|
+
if (existsSync26(path)) {
|
|
30474
31020
|
try {
|
|
30475
31021
|
unlinkSync(path);
|
|
30476
31022
|
} catch {}
|
|
@@ -30478,15 +31024,15 @@ function removeSession(pid) {
|
|
|
30478
31024
|
}
|
|
30479
31025
|
function listSessions() {
|
|
30480
31026
|
const dir = sessionsDir();
|
|
30481
|
-
if (!
|
|
31027
|
+
if (!existsSync26(dir))
|
|
30482
31028
|
return [];
|
|
30483
31029
|
const live = [];
|
|
30484
31030
|
for (const entry of readdirSync4(dir)) {
|
|
30485
31031
|
if (!entry.endsWith(".json"))
|
|
30486
31032
|
continue;
|
|
30487
|
-
const path =
|
|
31033
|
+
const path = join22(dir, entry);
|
|
30488
31034
|
try {
|
|
30489
|
-
const parsed = JSON.parse(
|
|
31035
|
+
const parsed = JSON.parse(readFileSync24(path, "utf-8"));
|
|
30490
31036
|
if (typeof parsed.pid !== "number") {
|
|
30491
31037
|
unlinkSync(path);
|
|
30492
31038
|
continue;
|
|
@@ -30823,13 +31369,13 @@ function formatSummary(record) {
|
|
|
30823
31369
|
|
|
30824
31370
|
// src/commands/dev-tail.ts
|
|
30825
31371
|
import {
|
|
30826
|
-
existsSync as
|
|
31372
|
+
existsSync as existsSync27,
|
|
30827
31373
|
openSync,
|
|
30828
31374
|
readSync,
|
|
30829
31375
|
closeSync,
|
|
30830
31376
|
statSync as statSync3
|
|
30831
31377
|
} from "node:fs";
|
|
30832
|
-
import { join as
|
|
31378
|
+
import { join as join23 } from "node:path";
|
|
30833
31379
|
var DEFAULT_INTERVAL_MS = 250;
|
|
30834
31380
|
var MAX_READ_CHUNK = 64 * 1024;
|
|
30835
31381
|
function registerDevTailCommand(dev) {
|
|
@@ -30838,14 +31384,14 @@ function registerDevTailCommand(dev) {
|
|
|
30838
31384
|
async function runDevTail(opts) {
|
|
30839
31385
|
const cwd = process.cwd();
|
|
30840
31386
|
const dir = inspectorDir(cwd);
|
|
30841
|
-
const activePath =
|
|
31387
|
+
const activePath = join23(dir, ACTIVE_FILE);
|
|
30842
31388
|
const intervalMs = parseInterval(opts.intervalMs);
|
|
30843
|
-
if (!
|
|
31389
|
+
if (!existsSync27(dir)) {
|
|
30844
31390
|
printInfo(`No inspector history yet at ${dir}. Run \`mcp dev\` to start capturing.`);
|
|
30845
31391
|
throw new CliExitError(0);
|
|
30846
31392
|
}
|
|
30847
31393
|
let cursor = 0;
|
|
30848
|
-
if (
|
|
31394
|
+
if (existsSync27(activePath)) {
|
|
30849
31395
|
try {
|
|
30850
31396
|
cursor = opts.fromStart ? 0 : statSync3(activePath).size;
|
|
30851
31397
|
} catch {
|
|
@@ -30861,7 +31407,7 @@ async function runDevTail(opts) {
|
|
|
30861
31407
|
process.on("SIGTERM", stop);
|
|
30862
31408
|
while (!stopped) {
|
|
30863
31409
|
let size = 0;
|
|
30864
|
-
if (
|
|
31410
|
+
if (existsSync27(activePath)) {
|
|
30865
31411
|
try {
|
|
30866
31412
|
size = statSync3(activePath).size;
|
|
30867
31413
|
} catch {
|
|
@@ -31106,8 +31652,8 @@ function startGitPullPoll(args) {
|
|
|
31106
31652
|
|
|
31107
31653
|
// src/lib/dev/editor.ts
|
|
31108
31654
|
import { spawn as spawn5 } from "node:child_process";
|
|
31109
|
-
import { existsSync as
|
|
31110
|
-
import { delimiter as delimiter2, join as
|
|
31655
|
+
import { existsSync as existsSync28, statSync as statSync4 } from "node:fs";
|
|
31656
|
+
import { delimiter as delimiter2, join as join24 } from "node:path";
|
|
31111
31657
|
import { platform as platform3 } from "node:os";
|
|
31112
31658
|
var TARGET_LABEL = {
|
|
31113
31659
|
tools: "tool metadata",
|
|
@@ -31143,8 +31689,8 @@ function findOnPath2(command) {
|
|
|
31143
31689
|
if (!dir)
|
|
31144
31690
|
continue;
|
|
31145
31691
|
for (const ext of exts) {
|
|
31146
|
-
const candidate =
|
|
31147
|
-
if (
|
|
31692
|
+
const candidate = join24(dir, command + ext);
|
|
31693
|
+
if (existsSync28(candidate) && isExecutable(candidate))
|
|
31148
31694
|
return candidate;
|
|
31149
31695
|
}
|
|
31150
31696
|
}
|
|
@@ -31433,11 +31979,11 @@ function parseOpenChoice(raw) {
|
|
|
31433
31979
|
}
|
|
31434
31980
|
|
|
31435
31981
|
// src/lib/dev/file-watcher.ts
|
|
31436
|
-
import { existsSync as
|
|
31437
|
-
import { join as
|
|
31982
|
+
import { existsSync as existsSync29, statSync as statSync5, watch as fsWatch2 } from "node:fs";
|
|
31983
|
+
import { join as join25 } from "node:path";
|
|
31438
31984
|
var DEFAULT_IGNORE = ["node_modules", ".git", "_mcpsh_host.mjs"];
|
|
31439
31985
|
function watchDir(options) {
|
|
31440
|
-
if (!
|
|
31986
|
+
if (!existsSync29(options.rootDir)) {
|
|
31441
31987
|
throw new Error(`watchDir: directory does not exist: ${options.rootDir}`);
|
|
31442
31988
|
}
|
|
31443
31989
|
const ignore = new Set([...DEFAULT_IGNORE, ...options.ignore ?? []]);
|
|
@@ -31473,7 +32019,7 @@ function watchDir(options) {
|
|
|
31473
32019
|
for (const entry of readdirSync(options.rootDir)) {
|
|
31474
32020
|
if (ignore.has(entry))
|
|
31475
32021
|
continue;
|
|
31476
|
-
const sub =
|
|
32022
|
+
const sub = join25(options.rootDir, entry);
|
|
31477
32023
|
try {
|
|
31478
32024
|
const stat = statSync5(sub);
|
|
31479
32025
|
if (!stat.isDirectory())
|
|
@@ -31521,10 +32067,10 @@ function isToolHandlerPath(rel) {
|
|
|
31521
32067
|
}
|
|
31522
32068
|
|
|
31523
32069
|
// src/lib/dev/tools-watcher.ts
|
|
31524
|
-
import { existsSync as
|
|
32070
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
31525
32071
|
function startToolsWatcher(args) {
|
|
31526
32072
|
const rootDir = serverToolsDir(devRoot(args.cwd), args.serverId);
|
|
31527
|
-
if (!
|
|
32073
|
+
if (!existsSync30(rootDir))
|
|
31528
32074
|
return null;
|
|
31529
32075
|
let inflight = false;
|
|
31530
32076
|
let pendingNames = new Set;
|
|
@@ -31589,6 +32135,10 @@ async function handleOne(args) {
|
|
|
31589
32135
|
printError(`Couldn't parse ${args.toolName}.md: ${result.message}`);
|
|
31590
32136
|
printInfo(c.dim(` → fix the file and save again, or run \`mcp tools pull ${args.toolName}\` to reset.`));
|
|
31591
32137
|
return;
|
|
32138
|
+
case "rename-requested":
|
|
32139
|
+
printWarn(`${args.toolName}.md: \`name:\` was changed to ${c.bold(result.to)}, but editing the file does not rename a tool — nothing was pushed.`);
|
|
32140
|
+
printInfo(c.dim(` → run \`mcp tools rename ${result.from} ${result.to} --server ${args.serverId}\`, then \`mcp tools pull\`; or set \`name:\` back to ${result.from} to push your other edits.`));
|
|
32141
|
+
return;
|
|
31592
32142
|
case "patched": {
|
|
31593
32143
|
printSuccess(`Saved ${c.bold(args.toolName)} ${c.dim(`(${result.changedFields.length} field${result.changedFields.length === 1 ? "" : "s"} patched)`)}`);
|
|
31594
32144
|
const hotPatched = await maybeHotReload({
|
|
@@ -31639,10 +32189,10 @@ async function maybeHotReload(args) {
|
|
|
31639
32189
|
}
|
|
31640
32190
|
|
|
31641
32191
|
// src/lib/dev/handlers-watcher.ts
|
|
31642
|
-
import { existsSync as
|
|
32192
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
31643
32193
|
function startHandlersWatcher(args) {
|
|
31644
32194
|
const rootDir = serverBundleHandlersDir(devRoot(args.cwd));
|
|
31645
|
-
if (!
|
|
32195
|
+
if (!existsSync31(rootDir))
|
|
31646
32196
|
return null;
|
|
31647
32197
|
let inflight = false;
|
|
31648
32198
|
let pendingSlugs = new Set;
|
|
@@ -31849,24 +32399,24 @@ function createBurstGuard() {
|
|
|
31849
32399
|
}
|
|
31850
32400
|
|
|
31851
32401
|
// src/lib/dev/agent-connectors/claude-code.ts
|
|
31852
|
-
import { existsSync as
|
|
32402
|
+
import { existsSync as existsSync33, unlinkSync as unlinkSync2, writeFileSync as writeFileSync16 } from "node:fs";
|
|
31853
32403
|
import { homedir as homedir4 } from "node:os";
|
|
31854
|
-
import { join as
|
|
32404
|
+
import { join as join27 } from "node:path";
|
|
31855
32405
|
|
|
31856
32406
|
// src/lib/dev/agent-connectors/json-config-utils.ts
|
|
31857
|
-
import { existsSync as
|
|
31858
|
-
import { dirname as dirname10, join as
|
|
32407
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync16, readFileSync as readFileSync25, writeFileSync as writeFileSync15 } from "node:fs";
|
|
32408
|
+
import { dirname as dirname10, join as join26, basename } from "node:path";
|
|
31859
32409
|
function readJsonFile(path) {
|
|
31860
|
-
if (!
|
|
32410
|
+
if (!existsSync32(path))
|
|
31861
32411
|
return { ok: true, value: {} };
|
|
31862
32412
|
try {
|
|
31863
|
-
const raw =
|
|
32413
|
+
const raw = readFileSync25(path, "utf-8");
|
|
31864
32414
|
if (!raw.trim())
|
|
31865
32415
|
return { ok: true, value: {} };
|
|
31866
32416
|
return { ok: true, value: JSON.parse(raw) };
|
|
31867
32417
|
} catch {
|
|
31868
32418
|
try {
|
|
31869
|
-
return { ok: false, raw:
|
|
32419
|
+
return { ok: false, raw: readFileSync25(path, "utf-8") };
|
|
31870
32420
|
} catch {
|
|
31871
32421
|
return { ok: false };
|
|
31872
32422
|
}
|
|
@@ -31881,25 +32431,25 @@ function atomicWriteJson(path, value) {
|
|
|
31881
32431
|
renameSync(tmp, path);
|
|
31882
32432
|
}
|
|
31883
32433
|
function backupConfig(args) {
|
|
31884
|
-
if (!
|
|
32434
|
+
if (!existsSync32(args.configPath)) {
|
|
31885
32435
|
return { backupPath: null, existed: false };
|
|
31886
32436
|
}
|
|
31887
32437
|
mkdirSync16(args.backupsDir, { recursive: true });
|
|
31888
32438
|
const filename = `${args.agentId}__${basename(args.configPath)}.bak`;
|
|
31889
|
-
const backupPath =
|
|
31890
|
-
if (!
|
|
31891
|
-
const raw =
|
|
32439
|
+
const backupPath = join26(args.backupsDir, filename);
|
|
32440
|
+
if (!existsSync32(backupPath)) {
|
|
32441
|
+
const raw = readFileSync25(args.configPath, "utf-8");
|
|
31892
32442
|
writeFileSync15(backupPath, raw, "utf-8");
|
|
31893
32443
|
}
|
|
31894
32444
|
return { backupPath, existed: true };
|
|
31895
32445
|
}
|
|
31896
32446
|
function restoreFromBackup(args) {
|
|
31897
32447
|
const filename = `${args.agentId}__${basename(args.configPath)}.bak`;
|
|
31898
|
-
const backupPath =
|
|
31899
|
-
if (!
|
|
32448
|
+
const backupPath = join26(args.backupsDir, filename);
|
|
32449
|
+
if (!existsSync32(backupPath)) {
|
|
31900
32450
|
return { restored: false };
|
|
31901
32451
|
}
|
|
31902
|
-
const raw =
|
|
32452
|
+
const raw = readFileSync25(backupPath, "utf-8");
|
|
31903
32453
|
mkdirSync16(dirname10(args.configPath), { recursive: true });
|
|
31904
32454
|
writeFileSync15(args.configPath, raw, "utf-8");
|
|
31905
32455
|
return { restored: true };
|
|
@@ -31907,15 +32457,15 @@ function restoreFromBackup(args) {
|
|
|
31907
32457
|
|
|
31908
32458
|
// src/lib/dev/agent-connectors/claude-code.ts
|
|
31909
32459
|
function configPath() {
|
|
31910
|
-
return
|
|
32460
|
+
return join27(homedir4(), ".claude.json");
|
|
31911
32461
|
}
|
|
31912
32462
|
function legacyConfigPath() {
|
|
31913
|
-
return
|
|
32463
|
+
return join27(homedir4(), ".claude", "mcp.json");
|
|
31914
32464
|
}
|
|
31915
32465
|
function resolveConfigPath() {
|
|
31916
|
-
if (
|
|
32466
|
+
if (existsSync33(configPath()))
|
|
31917
32467
|
return configPath();
|
|
31918
|
-
if (
|
|
32468
|
+
if (existsSync33(legacyConfigPath()))
|
|
31919
32469
|
return legacyConfigPath();
|
|
31920
32470
|
return configPath();
|
|
31921
32471
|
}
|
|
@@ -31925,8 +32475,8 @@ var claudeCodeConnector = {
|
|
|
31925
32475
|
hotkey: "c",
|
|
31926
32476
|
describeLocation: () => resolveConfigPath(),
|
|
31927
32477
|
async detect() {
|
|
31928
|
-
const claudeDir =
|
|
31929
|
-
if (
|
|
32478
|
+
const claudeDir = join27(homedir4(), ".claude");
|
|
32479
|
+
if (existsSync33(configPath()) || existsSync33(legacyConfigPath()) || existsSync33(claudeDir)) {
|
|
31930
32480
|
return { installed: true, note: "Found Claude Code config" };
|
|
31931
32481
|
}
|
|
31932
32482
|
return { installed: false };
|
|
@@ -31964,7 +32514,7 @@ var claudeCodeConnector = {
|
|
|
31964
32514
|
backupsDir: args.backupsDir,
|
|
31965
32515
|
agentId: this.id
|
|
31966
32516
|
});
|
|
31967
|
-
if (!restored.restored &&
|
|
32517
|
+
if (!restored.restored && existsSync33(path)) {
|
|
31968
32518
|
const after = readJsonFile(path);
|
|
31969
32519
|
if (after.ok && after.value && typeof after.value === "object") {
|
|
31970
32520
|
const root = after.value;
|
|
@@ -31986,16 +32536,16 @@ var claudeCodeConnector = {
|
|
|
31986
32536
|
|
|
31987
32537
|
// src/lib/dev/agent-connectors/codex.ts
|
|
31988
32538
|
import {
|
|
31989
|
-
existsSync as
|
|
32539
|
+
existsSync as existsSync34,
|
|
31990
32540
|
mkdirSync as mkdirSync17,
|
|
31991
|
-
readFileSync as
|
|
32541
|
+
readFileSync as readFileSync26,
|
|
31992
32542
|
unlinkSync as unlinkSync3,
|
|
31993
32543
|
writeFileSync as writeFileSync17
|
|
31994
32544
|
} from "node:fs";
|
|
31995
32545
|
import { homedir as homedir5 } from "node:os";
|
|
31996
|
-
import { dirname as dirname11, join as
|
|
32546
|
+
import { dirname as dirname11, join as join28 } from "node:path";
|
|
31997
32547
|
function configPath2() {
|
|
31998
|
-
return
|
|
32548
|
+
return join28(homedir5(), ".codex", "config.toml");
|
|
31999
32549
|
}
|
|
32000
32550
|
var SECTION_PREFIX = "mcp_servers.";
|
|
32001
32551
|
function buildSection(name, url) {
|
|
@@ -32033,7 +32583,7 @@ var codexConnector = {
|
|
|
32033
32583
|
hotkey: "x",
|
|
32034
32584
|
describeLocation: () => configPath2(),
|
|
32035
32585
|
async detect() {
|
|
32036
|
-
if (
|
|
32586
|
+
if (existsSync34(join28(homedir5(), ".codex")) || existsSync34(configPath2())) {
|
|
32037
32587
|
return { installed: true, note: "Found ~/.codex/" };
|
|
32038
32588
|
}
|
|
32039
32589
|
return { installed: false };
|
|
@@ -32046,8 +32596,8 @@ var codexConnector = {
|
|
|
32046
32596
|
agentId: this.id
|
|
32047
32597
|
});
|
|
32048
32598
|
let existing = "";
|
|
32049
|
-
if (
|
|
32050
|
-
existing =
|
|
32599
|
+
if (existsSync34(path)) {
|
|
32600
|
+
existing = readFileSync26(path, "utf-8");
|
|
32051
32601
|
}
|
|
32052
32602
|
const conflict = findSectionRange(existing, args.name) !== null;
|
|
32053
32603
|
let next = existing;
|
|
@@ -32068,8 +32618,8 @@ var codexConnector = {
|
|
|
32068
32618
|
},
|
|
32069
32619
|
async remove(args) {
|
|
32070
32620
|
const path = configPath2();
|
|
32071
|
-
if (
|
|
32072
|
-
const existing =
|
|
32621
|
+
if (existsSync34(path)) {
|
|
32622
|
+
const existing = readFileSync26(path, "utf-8");
|
|
32073
32623
|
const range = findSectionRange(existing, args.name);
|
|
32074
32624
|
if (range) {
|
|
32075
32625
|
const next = existing.slice(0, range.start) + existing.slice(range.end);
|
|
@@ -32081,8 +32631,8 @@ var codexConnector = {
|
|
|
32081
32631
|
backupsDir: args.backupsDir,
|
|
32082
32632
|
agentId: this.id
|
|
32083
32633
|
});
|
|
32084
|
-
if (!restored.restored &&
|
|
32085
|
-
const after =
|
|
32634
|
+
if (!restored.restored && existsSync34(path)) {
|
|
32635
|
+
const after = readFileSync26(path, "utf-8");
|
|
32086
32636
|
if (!after.trim()) {
|
|
32087
32637
|
try {
|
|
32088
32638
|
unlinkSync3(path);
|
|
@@ -32094,11 +32644,11 @@ var codexConnector = {
|
|
|
32094
32644
|
};
|
|
32095
32645
|
|
|
32096
32646
|
// src/lib/dev/agent-connectors/continue.ts
|
|
32097
|
-
import { existsSync as
|
|
32647
|
+
import { existsSync as existsSync35, unlinkSync as unlinkSync4, writeFileSync as writeFileSync18 } from "node:fs";
|
|
32098
32648
|
import { homedir as homedir6 } from "node:os";
|
|
32099
|
-
import { join as
|
|
32649
|
+
import { join as join29 } from "node:path";
|
|
32100
32650
|
function configPath3() {
|
|
32101
|
-
return
|
|
32651
|
+
return join29(homedir6(), ".continue", "config.json");
|
|
32102
32652
|
}
|
|
32103
32653
|
function isContinueServerEntry(value) {
|
|
32104
32654
|
return Boolean(value && typeof value === "object" && typeof value.name === "string");
|
|
@@ -32109,8 +32659,8 @@ var continueConnector = {
|
|
|
32109
32659
|
hotkey: "n",
|
|
32110
32660
|
describeLocation: () => configPath3(),
|
|
32111
32661
|
async detect() {
|
|
32112
|
-
const dir =
|
|
32113
|
-
if (
|
|
32662
|
+
const dir = join29(homedir6(), ".continue");
|
|
32663
|
+
if (existsSync35(dir) || existsSync35(configPath3())) {
|
|
32114
32664
|
return { installed: true, note: "Found ~/.continue/" };
|
|
32115
32665
|
}
|
|
32116
32666
|
return { installed: false };
|
|
@@ -32148,7 +32698,7 @@ var continueConnector = {
|
|
|
32148
32698
|
backupsDir: args.backupsDir,
|
|
32149
32699
|
agentId: this.id
|
|
32150
32700
|
});
|
|
32151
|
-
if (!restored.restored &&
|
|
32701
|
+
if (!restored.restored && existsSync35(path)) {
|
|
32152
32702
|
const after = readJsonFile(path);
|
|
32153
32703
|
if (after.ok && after.value && typeof after.value === "object") {
|
|
32154
32704
|
const root = after.value;
|
|
@@ -32169,11 +32719,11 @@ var continueConnector = {
|
|
|
32169
32719
|
};
|
|
32170
32720
|
|
|
32171
32721
|
// src/lib/dev/agent-connectors/cursor.ts
|
|
32172
|
-
import { existsSync as
|
|
32722
|
+
import { existsSync as existsSync36, unlinkSync as unlinkSync5, writeFileSync as writeFileSync19 } from "node:fs";
|
|
32173
32723
|
import { homedir as homedir7 } from "node:os";
|
|
32174
|
-
import { join as
|
|
32724
|
+
import { join as join30 } from "node:path";
|
|
32175
32725
|
function globalConfigPath() {
|
|
32176
|
-
return
|
|
32726
|
+
return join30(homedir7(), ".cursor", "mcp.json");
|
|
32177
32727
|
}
|
|
32178
32728
|
var cursorConnector = {
|
|
32179
32729
|
id: "cursor",
|
|
@@ -32181,9 +32731,9 @@ var cursorConnector = {
|
|
|
32181
32731
|
hotkey: "u",
|
|
32182
32732
|
describeLocation: () => globalConfigPath(),
|
|
32183
32733
|
async detect() {
|
|
32184
|
-
const cursorDir =
|
|
32185
|
-
const macAppSupport =
|
|
32186
|
-
if (
|
|
32734
|
+
const cursorDir = join30(homedir7(), ".cursor");
|
|
32735
|
+
const macAppSupport = join30(homedir7(), "Library", "Application Support", "Cursor");
|
|
32736
|
+
if (existsSync36(cursorDir) || existsSync36(macAppSupport)) {
|
|
32187
32737
|
return { installed: true, note: "Found Cursor config dir" };
|
|
32188
32738
|
}
|
|
32189
32739
|
return { installed: false };
|
|
@@ -32221,7 +32771,7 @@ var cursorConnector = {
|
|
|
32221
32771
|
backupsDir: args.backupsDir,
|
|
32222
32772
|
agentId: this.id
|
|
32223
32773
|
});
|
|
32224
|
-
if (!restored.restored &&
|
|
32774
|
+
if (!restored.restored && existsSync36(path)) {
|
|
32225
32775
|
const after = readJsonFile(path);
|
|
32226
32776
|
if (after.ok && after.value && typeof after.value === "object") {
|
|
32227
32777
|
const root = after.value;
|
|
@@ -32242,30 +32792,30 @@ var cursorConnector = {
|
|
|
32242
32792
|
};
|
|
32243
32793
|
|
|
32244
32794
|
// src/lib/dev/agent-connectors/vscode-copilot.ts
|
|
32245
|
-
import { existsSync as
|
|
32795
|
+
import { existsSync as existsSync37, unlinkSync as unlinkSync6, writeFileSync as writeFileSync20 } from "node:fs";
|
|
32246
32796
|
import { homedir as homedir8, platform as platform4 } from "node:os";
|
|
32247
|
-
import { join as
|
|
32797
|
+
import { join as join31, resolve as resolve10 } from "node:path";
|
|
32248
32798
|
function userLevelConfigPath() {
|
|
32249
32799
|
const home = homedir8();
|
|
32250
32800
|
const p = platform4();
|
|
32251
32801
|
if (p === "darwin")
|
|
32252
|
-
return
|
|
32802
|
+
return join31(home, "Library", "Application Support", "Code", "User", "mcp.json");
|
|
32253
32803
|
if (p === "win32") {
|
|
32254
|
-
const appData = process.env["APPDATA"] ??
|
|
32255
|
-
return
|
|
32804
|
+
const appData = process.env["APPDATA"] ?? join31(home, "AppData", "Roaming");
|
|
32805
|
+
return join31(appData, "Code", "User", "mcp.json");
|
|
32256
32806
|
}
|
|
32257
|
-
const xdg = process.env["XDG_CONFIG_HOME"] ??
|
|
32258
|
-
return
|
|
32807
|
+
const xdg = process.env["XDG_CONFIG_HOME"] ?? join31(home, ".config");
|
|
32808
|
+
return join31(xdg, "Code", "User", "mcp.json");
|
|
32259
32809
|
}
|
|
32260
32810
|
function isHomeDir(cwd) {
|
|
32261
|
-
return
|
|
32811
|
+
return resolve10(cwd) === resolve10(homedir8());
|
|
32262
32812
|
}
|
|
32263
32813
|
function hasWorkspaceVscode(cwd) {
|
|
32264
|
-
return
|
|
32814
|
+
return existsSync37(join31(cwd, ".vscode"));
|
|
32265
32815
|
}
|
|
32266
32816
|
function configPath4(cwd) {
|
|
32267
32817
|
if (!isHomeDir(cwd) && hasWorkspaceVscode(cwd)) {
|
|
32268
|
-
return
|
|
32818
|
+
return join31(cwd, ".vscode", "mcp.json");
|
|
32269
32819
|
}
|
|
32270
32820
|
return userLevelConfigPath();
|
|
32271
32821
|
}
|
|
@@ -32279,7 +32829,7 @@ var vscodeCopilotConnector = {
|
|
|
32279
32829
|
if (!isHomeDir(here) && hasWorkspaceVscode(here)) {
|
|
32280
32830
|
return { installed: true, note: "Found .vscode/ in current dir" };
|
|
32281
32831
|
}
|
|
32282
|
-
if (
|
|
32832
|
+
if (existsSync37(userLevelConfigPath().replace(/mcp\.json$/, ""))) {
|
|
32283
32833
|
return { installed: true, note: "Found VS Code user profile" };
|
|
32284
32834
|
}
|
|
32285
32835
|
return { installed: false };
|
|
@@ -32317,7 +32867,7 @@ var vscodeCopilotConnector = {
|
|
|
32317
32867
|
backupsDir: args.backupsDir,
|
|
32318
32868
|
agentId: this.id
|
|
32319
32869
|
});
|
|
32320
|
-
if (!restored.restored &&
|
|
32870
|
+
if (!restored.restored && existsSync37(path)) {
|
|
32321
32871
|
const after = readJsonFile(path);
|
|
32322
32872
|
if (after.ok && after.value && typeof after.value === "object") {
|
|
32323
32873
|
const root = after.value;
|
|
@@ -32471,7 +33021,7 @@ function parseGraceMs(value, fallbackMs) {
|
|
|
32471
33021
|
return Math.round(n * 1000);
|
|
32472
33022
|
}
|
|
32473
33023
|
function resolveSpecPath(cwd, raw) {
|
|
32474
|
-
return
|
|
33024
|
+
return isAbsolute5(raw) ? raw : resolve11(cwd, raw);
|
|
32475
33025
|
}
|
|
32476
33026
|
function buildConnectionName(serverId) {
|
|
32477
33027
|
const slug = serverId.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24) || "mcp-server";
|
|
@@ -32635,7 +33185,7 @@ async function runDev(opts) {
|
|
|
32635
33185
|
let specPath = null;
|
|
32636
33186
|
if (opts.spec) {
|
|
32637
33187
|
const resolved = resolveSpecPath(cwd, opts.spec);
|
|
32638
|
-
if (!
|
|
33188
|
+
if (!existsSync38(resolved)) {
|
|
32639
33189
|
printError(`Spec file not found: ${resolved}`);
|
|
32640
33190
|
process.exit(1);
|
|
32641
33191
|
}
|
|
@@ -32846,7 +33396,7 @@ async function runWithRuntime(args) {
|
|
|
32846
33396
|
return;
|
|
32847
33397
|
regenInFlight = true;
|
|
32848
33398
|
try {
|
|
32849
|
-
printStep(`Spec changed (${
|
|
33399
|
+
printStep(`Spec changed (${relative9(args.cwd, args.specPath)}) — regenerating…`);
|
|
32850
33400
|
const result = await regenerateDevBundle({
|
|
32851
33401
|
organizationId: args.organizationId,
|
|
32852
33402
|
serverId: args.serverId,
|
|
@@ -32912,9 +33462,9 @@ async function runWithRuntime(args) {
|
|
|
32912
33462
|
if (args.inspector) {
|
|
32913
33463
|
printInfo(` ${c.dim("Inspector: ")} ${c.cyan(`${baseUrl}/__inspect`)}`);
|
|
32914
33464
|
}
|
|
32915
|
-
printInfo(` ${c.dim(`Edit files in ${
|
|
33465
|
+
printInfo(` ${c.dim(`Edit files in ${relative9(args.cwd, args.prepared.destDir)}/ to hot-reload.`)}`);
|
|
32916
33466
|
if (args.gitNative && args.prepared.repoDir) {
|
|
32917
|
-
printInfo(` ${c.dim(`Git-native: edit + commit + push in ${
|
|
33467
|
+
printInfo(` ${c.dim(`Git-native: edit + commit + push in ${relative9(args.cwd, args.prepared.repoDir)}/ — the webhook reconciles src/**.`)}`);
|
|
32918
33468
|
printInfo(` ${c.dim("Config under mcpcloud/ is read-only; change it in the dashboard. Deploy when ready (mcp servers deploy).")}`);
|
|
32919
33469
|
}
|
|
32920
33470
|
if (toolsWatcher) {
|
|
@@ -32924,7 +33474,7 @@ async function runWithRuntime(args) {
|
|
|
32924
33474
|
printInfo(` ${c.dim(`Edit tool handlers in .mcpcloud/server/src/tools/<slug>.ts (changes push to cloud on save).`)}`);
|
|
32925
33475
|
}
|
|
32926
33476
|
if (specWatcher) {
|
|
32927
|
-
printInfo(` ${c.dim(`Spec watch: ${
|
|
33477
|
+
printInfo(` ${c.dim(`Spec watch: ${relative9(args.cwd, args.specPath)}`)}`);
|
|
32928
33478
|
}
|
|
32929
33479
|
const gitPullPoller = args.gitNative ? startGitPullPoll({
|
|
32930
33480
|
fetchStatus: () => resolveServerGitPullStatus({
|
|
@@ -33565,13 +34115,13 @@ async function confirmRollback(deploymentId) {
|
|
|
33565
34115
|
}
|
|
33566
34116
|
|
|
33567
34117
|
// src/commands/doctor.ts
|
|
33568
|
-
import { existsSync as
|
|
34118
|
+
import { existsSync as existsSync40, statSync as statSync6, readFileSync as readFileSync28 } from "node:fs";
|
|
33569
34119
|
import { homedir as homedir9, platform as platform6 } from "node:os";
|
|
33570
|
-
import { join as
|
|
34120
|
+
import { join as join33, delimiter as delimiter3 } from "node:path";
|
|
33571
34121
|
|
|
33572
34122
|
// src/lib/version-check.ts
|
|
33573
|
-
import { existsSync as
|
|
33574
|
-
import { join as
|
|
34123
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync18, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "node:fs";
|
|
34124
|
+
import { join as join32 } from "node:path";
|
|
33575
34125
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
33576
34126
|
var FETCH_TIMEOUT_MS2 = 2000;
|
|
33577
34127
|
var REGISTRY_URL = "https://registry.npmjs.org/@mcpcloud/cli/latest";
|
|
@@ -33579,13 +34129,13 @@ function cacheDir() {
|
|
|
33579
34129
|
return configDir();
|
|
33580
34130
|
}
|
|
33581
34131
|
function cacheFile() {
|
|
33582
|
-
return
|
|
34132
|
+
return join32(cacheDir(), "version-check.json");
|
|
33583
34133
|
}
|
|
33584
34134
|
function readCache() {
|
|
33585
|
-
if (!
|
|
34135
|
+
if (!existsSync39(cacheFile()))
|
|
33586
34136
|
return null;
|
|
33587
34137
|
try {
|
|
33588
|
-
const raw = JSON.parse(
|
|
34138
|
+
const raw = JSON.parse(readFileSync27(cacheFile(), "utf-8"));
|
|
33589
34139
|
if (typeof raw.latest !== "string" || typeof raw.fetchedAt !== "number")
|
|
33590
34140
|
return null;
|
|
33591
34141
|
return { latest: raw.latest, fetchedAt: raw.fetchedAt };
|
|
@@ -33595,7 +34145,7 @@ function readCache() {
|
|
|
33595
34145
|
}
|
|
33596
34146
|
function writeCache(entry) {
|
|
33597
34147
|
try {
|
|
33598
|
-
if (!
|
|
34148
|
+
if (!existsSync39(cacheDir()))
|
|
33599
34149
|
mkdirSync18(cacheDir(), { recursive: true, mode: 448 });
|
|
33600
34150
|
writeFileSync21(cacheFile(), JSON.stringify(entry, null, 2), { mode: 384 });
|
|
33601
34151
|
} catch {}
|
|
@@ -33708,8 +34258,8 @@ function checkNode() {
|
|
|
33708
34258
|
}
|
|
33709
34259
|
function checkConfigFile() {
|
|
33710
34260
|
const t0 = Date.now();
|
|
33711
|
-
const path =
|
|
33712
|
-
if (!
|
|
34261
|
+
const path = join33(homedir9(), ".mcpcloud", "config.json");
|
|
34262
|
+
if (!existsSync40(path)) {
|
|
33713
34263
|
return {
|
|
33714
34264
|
name: "Config file",
|
|
33715
34265
|
status: "warn",
|
|
@@ -33731,7 +34281,7 @@ function checkConfigFile() {
|
|
|
33731
34281
|
} catch {}
|
|
33732
34282
|
}
|
|
33733
34283
|
try {
|
|
33734
|
-
JSON.parse(
|
|
34284
|
+
JSON.parse(readFileSync28(path, "utf-8"));
|
|
33735
34285
|
} catch (err) {
|
|
33736
34286
|
return {
|
|
33737
34287
|
name: "Config file",
|
|
@@ -33862,8 +34412,8 @@ function checkClaudeCli() {
|
|
|
33862
34412
|
for (const dir of PATH.split(delimiter3)) {
|
|
33863
34413
|
if (!dir)
|
|
33864
34414
|
continue;
|
|
33865
|
-
const candidate =
|
|
33866
|
-
if (
|
|
34415
|
+
const candidate = join33(dir, exe);
|
|
34416
|
+
if (existsSync40(candidate)) {
|
|
33867
34417
|
return {
|
|
33868
34418
|
name: "claude CLI",
|
|
33869
34419
|
status: "pass",
|
|
@@ -34144,6 +34694,7 @@ var TOPICS = {
|
|
|
34144
34694
|
" authentication_required Run `mcp login` or set MCPCLOUD_API_KEY.",
|
|
34145
34695
|
" invalid_api_key The key is invalid or revoked. Run `mcp login`.",
|
|
34146
34696
|
" organization_not_selected Pass --org <id> or run `mcp config set-org <id>`.",
|
|
34697
|
+
" organization_access_denied Not a member of that org — pass --org <id>; `mcp orgs list` shows yours.",
|
|
34147
34698
|
" rate_limited Honor the Retry-After header. The CLI auto-retries once.",
|
|
34148
34699
|
" deployment_failed Check `error.details.findings` (--json) for release-gate context.",
|
|
34149
34700
|
" idempotency_key_conflict Same key reused with a different body. Generate a fresh one.",
|
|
@@ -34613,8 +35164,8 @@ function registerInstallationCommands(program) {
|
|
|
34613
35164
|
}
|
|
34614
35165
|
|
|
34615
35166
|
// src/commands/invoke.ts
|
|
34616
|
-
import { existsSync as
|
|
34617
|
-
import { join as
|
|
35167
|
+
import { existsSync as existsSync41, readFileSync as readFileSync29 } from "node:fs";
|
|
35168
|
+
import { join as join34 } from "node:path";
|
|
34618
35169
|
|
|
34619
35170
|
// src/lib/dev/local-invoke.ts
|
|
34620
35171
|
var READY_TIMEOUT_MS = 30000;
|
|
@@ -34706,9 +35257,9 @@ function selectCodeModeInvocation(args) {
|
|
|
34706
35257
|
}
|
|
34707
35258
|
function readCodeOption(raw) {
|
|
34708
35259
|
if (raw === "@-")
|
|
34709
|
-
return
|
|
35260
|
+
return readFileSync29(0, "utf-8");
|
|
34710
35261
|
if (raw.startsWith("@"))
|
|
34711
|
-
return
|
|
35262
|
+
return readFileSync29(raw.slice(1), "utf-8");
|
|
34712
35263
|
return raw;
|
|
34713
35264
|
}
|
|
34714
35265
|
function parseTimeoutMs4(value, fallbackMs) {
|
|
@@ -34990,14 +35541,14 @@ async function runLocalInvoke(args) {
|
|
|
34990
35541
|
throw new CliExitError(1);
|
|
34991
35542
|
}
|
|
34992
35543
|
const env = {
|
|
34993
|
-
...args.envFile ? parseEnvFile(
|
|
35544
|
+
...args.envFile ? parseEnvFile(readFileSync29(args.envFile, "utf-8")) : {},
|
|
34994
35545
|
...parseEnvPairs(args.envPairs)
|
|
34995
35546
|
};
|
|
34996
35547
|
const port = await findAvailablePort({ range: 40, start: 43117 });
|
|
34997
35548
|
const prepared = await prepareDev({
|
|
34998
35549
|
cwd,
|
|
34999
35550
|
inspector: false,
|
|
35000
|
-
offline:
|
|
35551
|
+
offline: existsSync41(join34(devRoot(cwd), "server")),
|
|
35001
35552
|
organizationId: target.organizationId,
|
|
35002
35553
|
port,
|
|
35003
35554
|
serverId: target.serverId,
|
|
@@ -35979,38 +36530,38 @@ function registerConnectionsCommands(program) {
|
|
|
35979
36530
|
// src/lib/plugins.ts
|
|
35980
36531
|
import { spawn as spawn7 } from "node:child_process";
|
|
35981
36532
|
import {
|
|
35982
|
-
existsSync as
|
|
36533
|
+
existsSync as existsSync42,
|
|
35983
36534
|
mkdirSync as mkdirSync19,
|
|
35984
36535
|
readdirSync as readdirSync5,
|
|
35985
|
-
readFileSync as
|
|
36536
|
+
readFileSync as readFileSync30,
|
|
35986
36537
|
rmSync as rmSync3,
|
|
35987
36538
|
statSync as statSync7
|
|
35988
36539
|
} from "node:fs";
|
|
35989
|
-
import { join as
|
|
36540
|
+
import { join as join35 } from "node:path";
|
|
35990
36541
|
import { pathToFileURL } from "node:url";
|
|
35991
36542
|
function pluginsDir() {
|
|
35992
|
-
return
|
|
36543
|
+
return join35(configDir(), "plugins");
|
|
35993
36544
|
}
|
|
35994
36545
|
function ensureDir2() {
|
|
35995
36546
|
const dir = pluginsDir();
|
|
35996
|
-
if (!
|
|
36547
|
+
if (!existsSync42(dir))
|
|
35997
36548
|
mkdirSync19(dir, { recursive: true, mode: 448 });
|
|
35998
36549
|
}
|
|
35999
36550
|
function readManifestFromPackageDir(packageDir) {
|
|
36000
|
-
const pkgPath =
|
|
36001
|
-
if (!
|
|
36551
|
+
const pkgPath = join35(packageDir, "package.json");
|
|
36552
|
+
if (!existsSync42(pkgPath))
|
|
36002
36553
|
return null;
|
|
36003
36554
|
let pkg;
|
|
36004
36555
|
try {
|
|
36005
|
-
pkg = JSON.parse(
|
|
36556
|
+
pkg = JSON.parse(readFileSync30(pkgPath, "utf-8"));
|
|
36006
36557
|
} catch {
|
|
36007
36558
|
return null;
|
|
36008
36559
|
}
|
|
36009
36560
|
if (!pkg.name)
|
|
36010
36561
|
return null;
|
|
36011
36562
|
const relEntry = pkg.mcpsh?.register ?? pkg.mcp?.register ?? pkg.main ?? "index.js";
|
|
36012
|
-
const entry =
|
|
36013
|
-
if (!
|
|
36563
|
+
const entry = join35(packageDir, relEntry);
|
|
36564
|
+
if (!existsSync42(entry))
|
|
36014
36565
|
return null;
|
|
36015
36566
|
return {
|
|
36016
36567
|
name: pkg.name,
|
|
@@ -36021,11 +36572,11 @@ function readManifestFromPackageDir(packageDir) {
|
|
|
36021
36572
|
}
|
|
36022
36573
|
function listInstalledPlugins() {
|
|
36023
36574
|
const dir = pluginsDir();
|
|
36024
|
-
if (!
|
|
36575
|
+
if (!existsSync42(dir))
|
|
36025
36576
|
return [];
|
|
36026
36577
|
const out = [];
|
|
36027
36578
|
for (const entry of readdirSync5(dir)) {
|
|
36028
|
-
const full =
|
|
36579
|
+
const full = join35(dir, entry);
|
|
36029
36580
|
let s;
|
|
36030
36581
|
try {
|
|
36031
36582
|
s = statSync7(full);
|
|
@@ -36036,7 +36587,7 @@ function listInstalledPlugins() {
|
|
|
36036
36587
|
continue;
|
|
36037
36588
|
if (entry.startsWith("@")) {
|
|
36038
36589
|
for (const child of readdirSync5(full)) {
|
|
36039
|
-
const m = readManifestFromPackageDir(
|
|
36590
|
+
const m = readManifestFromPackageDir(join35(full, child));
|
|
36040
36591
|
if (m)
|
|
36041
36592
|
out.push(m);
|
|
36042
36593
|
}
|
|
@@ -36100,12 +36651,12 @@ async function installPlugin(packageSpec) {
|
|
|
36100
36651
|
if (code !== 0) {
|
|
36101
36652
|
throw new Error(`npm install ${packageSpec} exited with code ${code}.`);
|
|
36102
36653
|
}
|
|
36103
|
-
const nm =
|
|
36104
|
-
if (!
|
|
36654
|
+
const nm = join35(dir, "node_modules");
|
|
36655
|
+
if (!existsSync42(nm)) {
|
|
36105
36656
|
throw new Error(`npm install ran but produced no node_modules under ${dir}.`);
|
|
36106
36657
|
}
|
|
36107
36658
|
const baseName = packageSpec.replace(/@[^@/]+$/, "");
|
|
36108
|
-
const candidatePath = baseName.startsWith("@") ?
|
|
36659
|
+
const candidatePath = baseName.startsWith("@") ? join35(nm, baseName.split("/")[0], baseName.split("/")[1] ?? "") : join35(nm, baseName);
|
|
36109
36660
|
const manifest = readManifestFromPackageDir(candidatePath);
|
|
36110
36661
|
if (!manifest) {
|
|
36111
36662
|
throw new Error(`Installed but could not read plugin manifest at ${candidatePath}.`);
|
|
@@ -36113,26 +36664,26 @@ async function installPlugin(packageSpec) {
|
|
|
36113
36664
|
return { name: manifest.name, entry: manifest.entry };
|
|
36114
36665
|
}
|
|
36115
36666
|
function removePlugin(name) {
|
|
36116
|
-
const nm =
|
|
36117
|
-
if (!
|
|
36667
|
+
const nm = join35(pluginsDir(), "node_modules");
|
|
36668
|
+
if (!existsSync42(nm))
|
|
36118
36669
|
return false;
|
|
36119
|
-
const target = name.startsWith("@") ?
|
|
36120
|
-
if (!
|
|
36670
|
+
const target = name.startsWith("@") ? join35(nm, name.split("/")[0], name.split("/")[1] ?? "") : join35(nm, name);
|
|
36671
|
+
if (!existsSync42(target))
|
|
36121
36672
|
return false;
|
|
36122
36673
|
rmSync3(target, { recursive: true, force: true });
|
|
36123
36674
|
return true;
|
|
36124
36675
|
}
|
|
36125
36676
|
function listInstalledPluginsCombined() {
|
|
36126
36677
|
const direct = listInstalledPlugins();
|
|
36127
|
-
const nm =
|
|
36128
|
-
if (!
|
|
36678
|
+
const nm = join35(pluginsDir(), "node_modules");
|
|
36679
|
+
if (!existsSync42(nm))
|
|
36129
36680
|
return direct;
|
|
36130
36681
|
const seen = new Set(direct.map((p) => p.name));
|
|
36131
36682
|
const out = [...direct];
|
|
36132
36683
|
for (const entry of readdirSync5(nm)) {
|
|
36133
36684
|
if (entry === ".bin" || entry === ".package-lock.json")
|
|
36134
36685
|
continue;
|
|
36135
|
-
const full =
|
|
36686
|
+
const full = join35(nm, entry);
|
|
36136
36687
|
let s;
|
|
36137
36688
|
try {
|
|
36138
36689
|
s = statSync7(full);
|
|
@@ -36143,7 +36694,7 @@ function listInstalledPluginsCombined() {
|
|
|
36143
36694
|
continue;
|
|
36144
36695
|
if (entry.startsWith("@")) {
|
|
36145
36696
|
for (const child of readdirSync5(full)) {
|
|
36146
|
-
const m = readManifestFromPackageDir(
|
|
36697
|
+
const m = readManifestFromPackageDir(join35(full, child));
|
|
36147
36698
|
if (m && !seen.has(m.name)) {
|
|
36148
36699
|
seen.add(m.name);
|
|
36149
36700
|
out.push(m);
|
|
@@ -36366,7 +36917,7 @@ async function runResume(selection, deps) {
|
|
|
36366
36917
|
return mapMutationError(err, `resume ${selection.name}`);
|
|
36367
36918
|
}
|
|
36368
36919
|
}
|
|
36369
|
-
async function
|
|
36920
|
+
async function runDeploy2(selection, deps) {
|
|
36370
36921
|
try {
|
|
36371
36922
|
await deps.api.post("/api/v1/server/deploy", {
|
|
36372
36923
|
organizationId: deps.organizationId,
|
|
@@ -36736,8 +37287,13 @@ var CLI_EXAMPLES = {
|
|
|
36736
37287
|
"servers pause": ["mcp servers pause srv_abc"],
|
|
36737
37288
|
"servers resume": ["mcp servers resume srv_abc"],
|
|
36738
37289
|
"servers push-spec": [
|
|
36739
|
-
"mcp servers push-spec ./openapi.json
|
|
36740
|
-
|
|
37290
|
+
"mcp servers push-spec srv_abc --spec ./openapi.json",
|
|
37291
|
+
"mcp servers push-spec srv_abc --spec ./openapi.json --apply --deploy --wait",
|
|
37292
|
+
"mcp servers push-spec srv_abc --spec ./openapi.json --apply --rename-to-operation-ids"
|
|
37293
|
+
],
|
|
37294
|
+
"servers regenerate": [
|
|
37295
|
+
"mcp servers regenerate srv_abc --dry-run",
|
|
37296
|
+
"mcp servers regenerate srv_abc && mcp servers deploy srv_abc --wait"
|
|
36741
37297
|
],
|
|
36742
37298
|
"servers env list": ["mcp servers env list srv_abc"],
|
|
36743
37299
|
"servers env set": [
|
|
@@ -36761,6 +37317,14 @@ var CLI_EXAMPLES = {
|
|
|
36761
37317
|
"tools pull": ["mcp tools pull srv_abc"],
|
|
36762
37318
|
"tools pull-handler": ["mcp tools pull-handler srv_abc query_users"],
|
|
36763
37319
|
"tools reset-handler": ["mcp tools reset-handler srv_abc query_users"],
|
|
37320
|
+
"tools rename": [
|
|
37321
|
+
"mcp tools rename getrecording getRecording --server srv_abc",
|
|
37322
|
+
"mcp tools rename --from-operation-ids --server srv_abc --dry-run",
|
|
37323
|
+
"mcp tools rename --map @renames.json --server srv_abc"
|
|
37324
|
+
],
|
|
37325
|
+
"tools delete": [
|
|
37326
|
+
"mcp tools delete query_users --server srv_abc --confirm query_users"
|
|
37327
|
+
],
|
|
36764
37328
|
invoke: ["mcp invoke srv_abc query_users @args.json"],
|
|
36765
37329
|
"skills list": ["mcp skills list"],
|
|
36766
37330
|
"skills connect": [
|
|
@@ -37735,12 +38299,12 @@ function displayValueFor(field, value) {
|
|
|
37735
38299
|
|
|
37736
38300
|
// src/lib/tui/state-store.ts
|
|
37737
38301
|
import {
|
|
37738
|
-
existsSync as
|
|
38302
|
+
existsSync as existsSync43,
|
|
37739
38303
|
mkdirSync as mkdirSync20,
|
|
37740
|
-
readFileSync as
|
|
38304
|
+
readFileSync as readFileSync31,
|
|
37741
38305
|
writeFileSync as writeFileSync22
|
|
37742
38306
|
} from "node:fs";
|
|
37743
|
-
import { join as
|
|
38307
|
+
import { join as join36 } from "node:path";
|
|
37744
38308
|
var ALL_TABS = [
|
|
37745
38309
|
"servers",
|
|
37746
38310
|
"projects",
|
|
@@ -37750,7 +38314,7 @@ var ALL_TABS = [
|
|
|
37750
38314
|
"devSessions"
|
|
37751
38315
|
];
|
|
37752
38316
|
function tuiStateFile() {
|
|
37753
|
-
return
|
|
38317
|
+
return join36(configDir(), "tui-state.json");
|
|
37754
38318
|
}
|
|
37755
38319
|
function isTab(v) {
|
|
37756
38320
|
return typeof v === "string" && ALL_TABS.includes(v);
|
|
@@ -37815,11 +38379,11 @@ function sanitizeTuiState(input) {
|
|
|
37815
38379
|
}
|
|
37816
38380
|
function readTuiState() {
|
|
37817
38381
|
const path = tuiStateFile();
|
|
37818
|
-
if (!
|
|
38382
|
+
if (!existsSync43(path))
|
|
37819
38383
|
return {};
|
|
37820
38384
|
let parsed;
|
|
37821
38385
|
try {
|
|
37822
|
-
parsed = JSON.parse(
|
|
38386
|
+
parsed = JSON.parse(readFileSync31(path, "utf-8"));
|
|
37823
38387
|
} catch {
|
|
37824
38388
|
return {};
|
|
37825
38389
|
}
|
|
@@ -37827,7 +38391,7 @@ function readTuiState() {
|
|
|
37827
38391
|
}
|
|
37828
38392
|
function writeTuiState(state) {
|
|
37829
38393
|
try {
|
|
37830
|
-
if (!
|
|
38394
|
+
if (!existsSync43(configDir())) {
|
|
37831
38395
|
mkdirSync20(configDir(), { recursive: true, mode: 448 });
|
|
37832
38396
|
}
|
|
37833
38397
|
writeFileSync22(tuiStateFile(), JSON.stringify(state, null, 2), {
|
|
@@ -39758,7 +40322,7 @@ function registerUiCommand(program) {
|
|
|
39758
40322
|
openConfirmAction({
|
|
39759
40323
|
prompt: `Deploy ${selection.name}?`,
|
|
39760
40324
|
detail: "Triggers a workersDev build from the current bundle. Takes ~10–30s.",
|
|
39761
|
-
run: () =>
|
|
40325
|
+
run: () => runDeploy2(selection, actionDeps)
|
|
39762
40326
|
});
|
|
39763
40327
|
return true;
|
|
39764
40328
|
}
|