@yawlabs/mcph 0.47.1 → 0.47.2
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/CHANGELOG.md +4 -0
- package/README.md +1 -1
- package/dist/index.js +547 -304
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -125,7 +125,8 @@ function stripJsoncComments(src) {
|
|
|
125
125
|
return out;
|
|
126
126
|
}
|
|
127
127
|
function parseJsonc(src) {
|
|
128
|
-
const
|
|
128
|
+
const debommed = src.charCodeAt(0) === 65279 ? src.slice(1) : src;
|
|
129
|
+
const stripped = stripJsoncComments(debommed);
|
|
129
130
|
return JSON.parse(stripped);
|
|
130
131
|
}
|
|
131
132
|
|
|
@@ -191,9 +192,9 @@ var LEGACY_PROJECT_FILENAME = ".mcph.json";
|
|
|
191
192
|
var LEGACY_LOCAL_FILENAME = ".mcph.local.json";
|
|
192
193
|
var NEW_CONFIG_FILENAME = "config.json";
|
|
193
194
|
var NEW_LOCAL_FILENAME = "config.local.json";
|
|
194
|
-
async function exists(
|
|
195
|
+
async function exists(path5) {
|
|
195
196
|
try {
|
|
196
|
-
await stat(
|
|
197
|
+
await stat(path5);
|
|
197
198
|
return true;
|
|
198
199
|
} catch {
|
|
199
200
|
return false;
|
|
@@ -244,7 +245,7 @@ async function migrateLegacyConfigPaths(opts) {
|
|
|
244
245
|
}
|
|
245
246
|
}
|
|
246
247
|
async function findLegacyProjectRoot(cwd, home) {
|
|
247
|
-
const { resolve: resolve4, dirname:
|
|
248
|
+
const { resolve: resolve4, dirname: dirname2 } = await import("path");
|
|
248
249
|
const homeResolved = resolve4(home);
|
|
249
250
|
let dir = resolve4(cwd);
|
|
250
251
|
let prev = "";
|
|
@@ -254,7 +255,7 @@ async function findLegacyProjectRoot(cwd, home) {
|
|
|
254
255
|
const legacyLocal = join(dir, LEGACY_LOCAL_FILENAME);
|
|
255
256
|
if (await exists(legacyProject) || await exists(legacyLocal)) return dir;
|
|
256
257
|
prev = dir;
|
|
257
|
-
dir =
|
|
258
|
+
dir = dirname2(dir);
|
|
258
259
|
}
|
|
259
260
|
return null;
|
|
260
261
|
}
|
|
@@ -264,10 +265,10 @@ var CONFIG_FILENAME = "config.json";
|
|
|
264
265
|
var LOCAL_CONFIG_FILENAME = "config.local.json";
|
|
265
266
|
var CURRENT_SCHEMA_VERSION = 1;
|
|
266
267
|
var DEFAULT_API_BASE = "https://mcp.hosting";
|
|
267
|
-
async function readConfigAt(
|
|
268
|
+
async function readConfigAt(path5, scope, warnings) {
|
|
268
269
|
let raw;
|
|
269
270
|
try {
|
|
270
|
-
raw = await readFile(
|
|
271
|
+
raw = await readFile(path5, "utf8");
|
|
271
272
|
} catch {
|
|
272
273
|
return null;
|
|
273
274
|
}
|
|
@@ -276,19 +277,19 @@ async function readConfigAt(path4, scope, warnings) {
|
|
|
276
277
|
parsed = parseJsonc(raw);
|
|
277
278
|
} catch (err) {
|
|
278
279
|
const msg = err instanceof Error ? err.message : String(err);
|
|
279
|
-
warnings.push(`${
|
|
280
|
-
log("warn", "Config file is not valid JSON; ignoring", { path:
|
|
280
|
+
warnings.push(`${path5}: invalid JSON (${msg}) \u2014 file ignored`);
|
|
281
|
+
log("warn", "Config file is not valid JSON; ignoring", { path: path5, error: msg });
|
|
281
282
|
return null;
|
|
282
283
|
}
|
|
283
284
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
284
|
-
warnings.push(`${
|
|
285
|
+
warnings.push(`${path5}: root must be a JSON object \u2014 file ignored`);
|
|
285
286
|
return null;
|
|
286
287
|
}
|
|
287
288
|
const obj = parsed;
|
|
288
289
|
const version = typeof obj.version === "number" ? obj.version : void 0;
|
|
289
290
|
if (version !== void 0 && version > CURRENT_SCHEMA_VERSION) {
|
|
290
291
|
warnings.push(
|
|
291
|
-
`${
|
|
292
|
+
`${path5}: schema version ${version} is newer than this mcph (${CURRENT_SCHEMA_VERSION}); upgrade with \`npm i -g @yawlabs/mcph@latest\`. Loading best-effort.`
|
|
292
293
|
);
|
|
293
294
|
}
|
|
294
295
|
const token6 = typeof obj.token === "string" && obj.token.length > 0 ? obj.token : void 0;
|
|
@@ -298,21 +299,21 @@ async function readConfigAt(path4, scope, warnings) {
|
|
|
298
299
|
if (token6) {
|
|
299
300
|
if (scope === "project") {
|
|
300
301
|
warnings.push(
|
|
301
|
-
`${
|
|
302
|
+
`${path5}: 'token' should not appear in a project-shared file. Move it to ${CONFIG_DIRNAME}/${LOCAL_CONFIG_FILENAME} (gitignored) or ~/${CONFIG_DIRNAME}/${CONFIG_FILENAME}.`
|
|
302
303
|
);
|
|
303
304
|
}
|
|
304
|
-
await checkPermissions(
|
|
305
|
+
await checkPermissions(path5, warnings);
|
|
305
306
|
}
|
|
306
|
-
return { path:
|
|
307
|
+
return { path: path5, scope, version, token: token6, apiBase, servers, blocked };
|
|
307
308
|
}
|
|
308
|
-
async function checkPermissions(
|
|
309
|
+
async function checkPermissions(path5, warnings) {
|
|
309
310
|
if (process.platform === "win32") return;
|
|
310
311
|
try {
|
|
311
|
-
const st = await stat2(
|
|
312
|
+
const st = await stat2(path5);
|
|
312
313
|
const mode = st.mode & 511;
|
|
313
314
|
if ((mode & 63) !== 0) {
|
|
314
315
|
warnings.push(
|
|
315
|
-
`${
|
|
316
|
+
`${path5}: contains a token but is readable by group/other (mode ${mode.toString(8)}). Run \`chmod 600 ${path5}\` to restrict.`
|
|
316
317
|
);
|
|
317
318
|
}
|
|
318
319
|
} catch {
|
|
@@ -994,6 +995,124 @@ import { readFile as readFile3 } from "fs/promises";
|
|
|
994
995
|
import { homedir as homedir4 } from "os";
|
|
995
996
|
import { join as join4 } from "path";
|
|
996
997
|
|
|
998
|
+
// src/analytics.ts
|
|
999
|
+
import { request as request3 } from "undici";
|
|
1000
|
+
var FLUSH_INTERVAL = 3e4;
|
|
1001
|
+
var FLUSH_SIZE = 50;
|
|
1002
|
+
var MAX_BUFFER = 5e3;
|
|
1003
|
+
var buffer = [];
|
|
1004
|
+
var dispatchBuffer = [];
|
|
1005
|
+
var flushTimer = null;
|
|
1006
|
+
var apiUrl = "";
|
|
1007
|
+
var token = "";
|
|
1008
|
+
var lastFailure = null;
|
|
1009
|
+
function getLastAnalyticsFailure() {
|
|
1010
|
+
return lastFailure;
|
|
1011
|
+
}
|
|
1012
|
+
function recordConnectEvent(event) {
|
|
1013
|
+
if (buffer.length >= MAX_BUFFER) return;
|
|
1014
|
+
buffer.push({ ...event, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1015
|
+
if (buffer.length >= FLUSH_SIZE) {
|
|
1016
|
+
flush().catch(() => {
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
function recordDispatchEvent(event) {
|
|
1021
|
+
if (dispatchBuffer.length >= MAX_BUFFER) return;
|
|
1022
|
+
dispatchBuffer.push(event);
|
|
1023
|
+
if (dispatchBuffer.length >= FLUSH_SIZE) {
|
|
1024
|
+
flushDispatch().catch(() => {
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
async function flush() {
|
|
1029
|
+
if (buffer.length === 0 || !apiUrl || !token) return;
|
|
1030
|
+
const events = buffer.splice(0, FLUSH_SIZE);
|
|
1031
|
+
const url = `${apiUrl.replace(/\/$/, "")}/api/connect/analytics`;
|
|
1032
|
+
try {
|
|
1033
|
+
const res = await request3(url, {
|
|
1034
|
+
method: "POST",
|
|
1035
|
+
headers: {
|
|
1036
|
+
Authorization: `Bearer ${token}`,
|
|
1037
|
+
"Content-Type": "application/json"
|
|
1038
|
+
},
|
|
1039
|
+
body: JSON.stringify({ events }),
|
|
1040
|
+
headersTimeout: 1e4,
|
|
1041
|
+
bodyTimeout: 1e4
|
|
1042
|
+
});
|
|
1043
|
+
if (res.statusCode >= 400) {
|
|
1044
|
+
const room = MAX_BUFFER - buffer.length;
|
|
1045
|
+
if (room > 0) buffer.push(...events.slice(0, room));
|
|
1046
|
+
log("warn", "Analytics flush failed", { status: res.statusCode });
|
|
1047
|
+
lastFailure = { statusCode: res.statusCode, url, at: Date.now() };
|
|
1048
|
+
} else {
|
|
1049
|
+
lastFailure = null;
|
|
1050
|
+
}
|
|
1051
|
+
await res.body.text().catch(() => {
|
|
1052
|
+
});
|
|
1053
|
+
} catch (err) {
|
|
1054
|
+
const room = MAX_BUFFER - buffer.length;
|
|
1055
|
+
if (room > 0) buffer.push(...events.slice(0, room));
|
|
1056
|
+
log("warn", "Analytics flush error", { error: err.message });
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
async function flushDispatch() {
|
|
1060
|
+
if (dispatchBuffer.length === 0 || !apiUrl || !token) return;
|
|
1061
|
+
const events = dispatchBuffer.splice(0, FLUSH_SIZE);
|
|
1062
|
+
const url = `${apiUrl.replace(/\/$/, "")}/api/connect/dispatch-events`;
|
|
1063
|
+
try {
|
|
1064
|
+
const res = await request3(url, {
|
|
1065
|
+
method: "POST",
|
|
1066
|
+
headers: {
|
|
1067
|
+
Authorization: `Bearer ${token}`,
|
|
1068
|
+
"Content-Type": "application/json"
|
|
1069
|
+
},
|
|
1070
|
+
body: JSON.stringify({ events }),
|
|
1071
|
+
headersTimeout: 1e4,
|
|
1072
|
+
bodyTimeout: 1e4
|
|
1073
|
+
});
|
|
1074
|
+
if (res.statusCode >= 400 && res.statusCode !== 204) {
|
|
1075
|
+
const room = MAX_BUFFER - dispatchBuffer.length;
|
|
1076
|
+
if (room > 0) dispatchBuffer.push(...events.slice(0, room));
|
|
1077
|
+
log("warn", "Dispatch-events flush failed", { status: res.statusCode });
|
|
1078
|
+
lastFailure = { statusCode: res.statusCode, url, at: Date.now() };
|
|
1079
|
+
} else if (res.statusCode < 400) {
|
|
1080
|
+
lastFailure = null;
|
|
1081
|
+
}
|
|
1082
|
+
await res.body.text().catch(() => {
|
|
1083
|
+
});
|
|
1084
|
+
} catch (err) {
|
|
1085
|
+
const room = MAX_BUFFER - dispatchBuffer.length;
|
|
1086
|
+
if (room > 0) dispatchBuffer.push(...events.slice(0, room));
|
|
1087
|
+
log("warn", "Dispatch-events flush error", { error: err.message });
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
function initAnalytics(url, tok) {
|
|
1091
|
+
apiUrl = url;
|
|
1092
|
+
token = tok;
|
|
1093
|
+
flushTimer = setInterval(() => {
|
|
1094
|
+
flush().catch(() => {
|
|
1095
|
+
});
|
|
1096
|
+
flushDispatch().catch(() => {
|
|
1097
|
+
});
|
|
1098
|
+
}, FLUSH_INTERVAL);
|
|
1099
|
+
if (flushTimer.unref) flushTimer.unref();
|
|
1100
|
+
}
|
|
1101
|
+
async function shutdownAnalytics() {
|
|
1102
|
+
if (flushTimer) {
|
|
1103
|
+
clearInterval(flushTimer);
|
|
1104
|
+
flushTimer = null;
|
|
1105
|
+
}
|
|
1106
|
+
for (let i = 0; i < 3 && buffer.length > 0; i++) {
|
|
1107
|
+
await flush();
|
|
1108
|
+
}
|
|
1109
|
+
for (let i = 0; i < 3 && dispatchBuffer.length > 0; i++) {
|
|
1110
|
+
await flushDispatch();
|
|
1111
|
+
}
|
|
1112
|
+
buffer.length = 0;
|
|
1113
|
+
dispatchBuffer.length = 0;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
997
1116
|
// src/cli-shadows.ts
|
|
998
1117
|
var EMPTY = [];
|
|
999
1118
|
var NAMESPACE_REGISTRY = {
|
|
@@ -1229,7 +1348,7 @@ var INSTALL_TARGETS = [
|
|
|
1229
1348
|
function resolveInstallPath(opts) {
|
|
1230
1349
|
const home = opts.home ?? homedir3();
|
|
1231
1350
|
const appData = opts.appData ?? process.env.APPDATA ?? join3(home, "AppData", "Roaming");
|
|
1232
|
-
const { clientId, scope, os, projectDir } = opts;
|
|
1351
|
+
const { clientId, scope, os, projectDir, claudeConfigDir } = opts;
|
|
1233
1352
|
const target = INSTALL_TARGETS.find((t) => t.clientId === clientId);
|
|
1234
1353
|
if (!target) throw new Error(`Unknown client: ${clientId}`);
|
|
1235
1354
|
const scopeSpec = target.scopes.find((s) => s.scope === scope);
|
|
@@ -1240,15 +1359,24 @@ function resolveInstallPath(opts) {
|
|
|
1240
1359
|
if (scopeSpec.requiresProjectDir && !projectDir) {
|
|
1241
1360
|
throw new Error(`Scope ${scope} for ${clientId} requires a project directory`);
|
|
1242
1361
|
}
|
|
1243
|
-
const p = pathFor(clientId, scope, os, {
|
|
1362
|
+
const p = pathFor(clientId, scope, os, {
|
|
1363
|
+
home,
|
|
1364
|
+
appData,
|
|
1365
|
+
projectDir: projectDir ?? "",
|
|
1366
|
+
claudeConfigDir: claudeConfigDir && claudeConfigDir.length > 0 ? claudeConfigDir : void 0
|
|
1367
|
+
});
|
|
1244
1368
|
return p;
|
|
1245
1369
|
}
|
|
1246
1370
|
function pathFor(client, scope, os, base) {
|
|
1247
|
-
const { home, appData, projectDir } = base;
|
|
1371
|
+
const { home, appData, projectDir, claudeConfigDir } = base;
|
|
1248
1372
|
const sep = os === "windows" ? "\\" : "/";
|
|
1249
1373
|
const joinPath = (...parts) => parts.join(sep);
|
|
1250
1374
|
if (client === "claude-code") {
|
|
1251
1375
|
if (scope === "user") {
|
|
1376
|
+
if (claudeConfigDir) {
|
|
1377
|
+
const absolute = join3(claudeConfigDir, ".claude.json");
|
|
1378
|
+
return { absolute, display: absolute, containerPath: ["mcpServers"] };
|
|
1379
|
+
}
|
|
1252
1380
|
const display = os === "windows" ? "%USERPROFILE%\\.claude.json" : "~/.claude.json";
|
|
1253
1381
|
return { absolute: join3(home, ".claude.json"), display, containerPath: ["mcpServers"] };
|
|
1254
1382
|
}
|
|
@@ -1259,6 +1387,10 @@ function pathFor(client, scope, os, base) {
|
|
|
1259
1387
|
containerPath: ["mcpServers"]
|
|
1260
1388
|
};
|
|
1261
1389
|
}
|
|
1390
|
+
if (claudeConfigDir) {
|
|
1391
|
+
const absolute = join3(claudeConfigDir, ".claude.json");
|
|
1392
|
+
return { absolute, display: absolute, containerPath: ["projects", projectDir, "mcpServers"] };
|
|
1393
|
+
}
|
|
1262
1394
|
return {
|
|
1263
1395
|
absolute: join3(home, ".claude.json"),
|
|
1264
1396
|
display: os === "windows" ? "%USERPROFILE%\\.claude.json" : "~/.claude.json",
|
|
@@ -1311,20 +1443,39 @@ function buildLaunchEntry(opts) {
|
|
|
1311
1443
|
var ENTRY_NAME = "mcp.hosting";
|
|
1312
1444
|
var CLAUDE_CODE_ALLOW_PATTERN = "mcp__mcp_hosting__*";
|
|
1313
1445
|
function resolveClaudeCodeSettingsPath(scope, opts) {
|
|
1314
|
-
const { home, projectDir } = opts;
|
|
1315
|
-
|
|
1446
|
+
const { home, projectDir, claudeConfigDir } = opts;
|
|
1447
|
+
const cfgDir = claudeConfigDir && claudeConfigDir.length > 0 ? claudeConfigDir : null;
|
|
1448
|
+
if (scope === "user") return cfgDir ? join3(cfgDir, "settings.json") : join3(home, ".claude", "settings.json");
|
|
1316
1449
|
if (scope === "project" && projectDir) return join3(projectDir, ".claude", "settings.json");
|
|
1317
1450
|
if (scope === "local" && projectDir) return join3(projectDir, ".claude", "settings.local.json");
|
|
1318
1451
|
return null;
|
|
1319
1452
|
}
|
|
1320
1453
|
|
|
1321
1454
|
// src/persistence.ts
|
|
1322
|
-
import {
|
|
1455
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1456
|
+
import path3 from "path";
|
|
1457
|
+
|
|
1458
|
+
// src/atomic-write.ts
|
|
1459
|
+
import { mkdir as mkdir2, rename as rename2, unlink, writeFile } from "fs/promises";
|
|
1323
1460
|
import path2 from "path";
|
|
1461
|
+
async function atomicWriteFile(filePath, contents, encoding = "utf8") {
|
|
1462
|
+
const dir = path2.dirname(filePath);
|
|
1463
|
+
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
1464
|
+
await mkdir2(dir, { recursive: true });
|
|
1465
|
+
try {
|
|
1466
|
+
await writeFile(tmp, contents, encoding);
|
|
1467
|
+
await rename2(tmp, filePath);
|
|
1468
|
+
} catch (err) {
|
|
1469
|
+
await unlink(tmp).catch(() => void 0);
|
|
1470
|
+
throw err;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// src/persistence.ts
|
|
1324
1475
|
var STATE_SCHEMA_VERSION = 1;
|
|
1325
1476
|
var STATE_FILENAME = "state.json";
|
|
1326
1477
|
function statePath(configDir = userConfigDir()) {
|
|
1327
|
-
return
|
|
1478
|
+
return path3.join(configDir, STATE_FILENAME);
|
|
1328
1479
|
}
|
|
1329
1480
|
function emptyState() {
|
|
1330
1481
|
return { version: STATE_SCHEMA_VERSION, savedAt: 0, learning: {}, packHistory: [] };
|
|
@@ -1348,19 +1499,21 @@ async function loadState(filePath = statePath()) {
|
|
|
1348
1499
|
return emptyState();
|
|
1349
1500
|
}
|
|
1350
1501
|
}
|
|
1351
|
-
|
|
1502
|
+
var saveChain = Promise.resolve();
|
|
1503
|
+
function saveState(state, filePath = statePath()) {
|
|
1504
|
+
const next = saveChain.then(() => doSaveState(state, filePath));
|
|
1505
|
+
saveChain = next.catch(() => void 0);
|
|
1506
|
+
return next;
|
|
1507
|
+
}
|
|
1508
|
+
async function doSaveState(state, filePath) {
|
|
1352
1509
|
const payload = {
|
|
1353
1510
|
version: STATE_SCHEMA_VERSION,
|
|
1354
1511
|
savedAt: Date.now(),
|
|
1355
1512
|
learning: state.learning,
|
|
1356
1513
|
packHistory: state.packHistory
|
|
1357
1514
|
};
|
|
1358
|
-
const dir = path2.dirname(filePath);
|
|
1359
|
-
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
1360
1515
|
try {
|
|
1361
|
-
await
|
|
1362
|
-
await writeFile(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
1363
|
-
await rename2(tmp, filePath);
|
|
1516
|
+
await atomicWriteFile(filePath, JSON.stringify(payload, null, 2));
|
|
1364
1517
|
} catch (err) {
|
|
1365
1518
|
log("warn", "Failed to save mcph state", { error: errorMessage(err) });
|
|
1366
1519
|
}
|
|
@@ -1399,6 +1552,45 @@ function errorMessage(err) {
|
|
|
1399
1552
|
return err instanceof Error ? err.message : String(err);
|
|
1400
1553
|
}
|
|
1401
1554
|
|
|
1555
|
+
// src/tool-report.ts
|
|
1556
|
+
import { request as request4 } from "undici";
|
|
1557
|
+
var apiUrl2 = "";
|
|
1558
|
+
var token2 = "";
|
|
1559
|
+
var lastFailure2 = null;
|
|
1560
|
+
function getLastReportFailure() {
|
|
1561
|
+
return lastFailure2;
|
|
1562
|
+
}
|
|
1563
|
+
function initToolReport(url, tok) {
|
|
1564
|
+
apiUrl2 = url;
|
|
1565
|
+
token2 = tok;
|
|
1566
|
+
}
|
|
1567
|
+
async function reportTools(serverId, tools) {
|
|
1568
|
+
if (!apiUrl2 || !token2 || !serverId) return;
|
|
1569
|
+
const url = `${apiUrl2.replace(/\/$/, "")}/api/connect/servers/${serverId}/tools`;
|
|
1570
|
+
try {
|
|
1571
|
+
const res = await request4(url, {
|
|
1572
|
+
method: "POST",
|
|
1573
|
+
headers: {
|
|
1574
|
+
Authorization: `Bearer ${token2}`,
|
|
1575
|
+
"Content-Type": "application/json"
|
|
1576
|
+
},
|
|
1577
|
+
body: JSON.stringify({ tools }),
|
|
1578
|
+
headersTimeout: 1e4,
|
|
1579
|
+
bodyTimeout: 1e4
|
|
1580
|
+
});
|
|
1581
|
+
await res.body.text().catch(() => {
|
|
1582
|
+
});
|
|
1583
|
+
if (res.statusCode >= 400 && res.statusCode !== 404) {
|
|
1584
|
+
log("warn", "Tool report failed", { serverId, status: res.statusCode });
|
|
1585
|
+
lastFailure2 = { statusCode: res.statusCode, url, at: Date.now() };
|
|
1586
|
+
} else if (res.statusCode < 400) {
|
|
1587
|
+
lastFailure2 = null;
|
|
1588
|
+
}
|
|
1589
|
+
} catch (err) {
|
|
1590
|
+
log("warn", "Tool report error", { serverId, error: err?.message });
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1402
1594
|
// src/usage-hints.ts
|
|
1403
1595
|
var MAX_PEERS = 3;
|
|
1404
1596
|
var MIN_SUCCESS_TO_SHOW = 1;
|
|
@@ -1458,7 +1650,7 @@ function selectFlakyNamespaces(entries, limit) {
|
|
|
1458
1650
|
}
|
|
1459
1651
|
|
|
1460
1652
|
// src/doctor-cmd.ts
|
|
1461
|
-
var VERSION = true ? "0.47.
|
|
1653
|
+
var VERSION = true ? "0.47.2" : "dev";
|
|
1462
1654
|
async function runDoctor(opts = {}) {
|
|
1463
1655
|
if (opts.json) return runDoctorJson(opts);
|
|
1464
1656
|
const lines = [];
|
|
@@ -1497,7 +1689,9 @@ async function runDoctor(opts = {}) {
|
|
|
1497
1689
|
renderEnvSection({ env, print });
|
|
1498
1690
|
await renderStateSection({ home, env, print });
|
|
1499
1691
|
await renderReliabilitySection({ home, env, print });
|
|
1500
|
-
|
|
1692
|
+
renderBackgroundPostersSection({ print });
|
|
1693
|
+
const claudeConfigDir = env.CLAUDE_CONFIG_DIR && env.CLAUDE_CONFIG_DIR.length > 0 ? env.CLAUDE_CONFIG_DIR : void 0;
|
|
1694
|
+
const clients = probeClients({ home, os, cwd, claudeConfigDir });
|
|
1501
1695
|
print("INSTALLED CLIENTS (probed config files)");
|
|
1502
1696
|
for (const c of clients) {
|
|
1503
1697
|
const status = c.unavailable ? "unavailable on this OS" : c.malformed ? "exists but JSON is malformed \u2014 fix or rerun `mcph install`" : c.hasMcphEntry ? `OK \u2014 has "${ENTRY_NAME}" entry` : c.exists ? `present, no "${ENTRY_NAME}" entry \u2014 run \`mcph install ${c.clientId}${c.scope === "user" ? "" : ` --scope ${c.scope}`}\`` : `not configured \u2014 run \`mcph install ${c.clientId}${c.scope === "user" ? "" : ` --scope ${c.scope}`}\``;
|
|
@@ -1557,7 +1751,8 @@ async function runDoctorJson(opts) {
|
|
|
1557
1751
|
const env = opts.env ?? process.env;
|
|
1558
1752
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1559
1753
|
const config = await loadMcphConfig({ cwd, home, env });
|
|
1560
|
-
const
|
|
1754
|
+
const claudeConfigDir = env.CLAUDE_CONFIG_DIR && env.CLAUDE_CONFIG_DIR.length > 0 ? env.CLAUDE_CONFIG_DIR : void 0;
|
|
1755
|
+
const clients = probeClients({ home, os, cwd, claudeConfigDir });
|
|
1561
1756
|
const envVarNames = [
|
|
1562
1757
|
"MCPH_POLL_INTERVAL",
|
|
1563
1758
|
"MCPH_SERVER_CAP",
|
|
@@ -1675,6 +1870,25 @@ async function renderStateSection(opts) {
|
|
|
1675
1870
|
}
|
|
1676
1871
|
const filePath = join4(userConfigDir(home), STATE_FILENAME);
|
|
1677
1872
|
print(` path: ${filePath}`);
|
|
1873
|
+
const peek = await peekStateFile(filePath);
|
|
1874
|
+
if (peek.kind === "malformed") {
|
|
1875
|
+
print(" status: corrupt -- file exists but JSON is unparseable");
|
|
1876
|
+
print(` fix: \`mcph reset-learning\` to clear, or open ${filePath} and fix by hand`);
|
|
1877
|
+
print(` detail: ${peek.message}`);
|
|
1878
|
+
print("");
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
if (peek.kind === "stale-version") {
|
|
1882
|
+
print(` status: schema mismatch (file is v${peek.version ?? "?"}, this mcph reads v${peek.expected})`);
|
|
1883
|
+
print(" fix: `mcph reset-learning` to drop the old file -- learning will rebuild on use");
|
|
1884
|
+
print("");
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
if (peek.kind === "unreadable") {
|
|
1888
|
+
print(` status: unreadable (${peek.message})`);
|
|
1889
|
+
print("");
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1678
1892
|
const persisted = await loadState(filePath);
|
|
1679
1893
|
if (persisted.savedAt === 0) {
|
|
1680
1894
|
print(" (no persisted state yet \u2014 will be created on the first tool call)");
|
|
@@ -1685,6 +1899,29 @@ async function renderStateSection(opts) {
|
|
|
1685
1899
|
}
|
|
1686
1900
|
print("");
|
|
1687
1901
|
}
|
|
1902
|
+
async function peekStateFile(filePath) {
|
|
1903
|
+
let raw;
|
|
1904
|
+
try {
|
|
1905
|
+
raw = await readFile3(filePath, "utf8");
|
|
1906
|
+
} catch (err) {
|
|
1907
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
|
|
1908
|
+
return { kind: "missing" };
|
|
1909
|
+
}
|
|
1910
|
+
return { kind: "unreadable", message: err instanceof Error ? err.message : String(err) };
|
|
1911
|
+
}
|
|
1912
|
+
let parsed;
|
|
1913
|
+
try {
|
|
1914
|
+
parsed = JSON.parse(raw);
|
|
1915
|
+
} catch (err) {
|
|
1916
|
+
return { kind: "malformed", message: err instanceof Error ? err.message : String(err) };
|
|
1917
|
+
}
|
|
1918
|
+
if (!parsed || typeof parsed !== "object") return { kind: "malformed", message: "top-level value is not an object" };
|
|
1919
|
+
const version = parsed.version;
|
|
1920
|
+
if (version !== STATE_SCHEMA_VERSION) {
|
|
1921
|
+
return { kind: "stale-version", version, expected: STATE_SCHEMA_VERSION };
|
|
1922
|
+
}
|
|
1923
|
+
return { kind: "ok" };
|
|
1924
|
+
}
|
|
1688
1925
|
async function renderReliabilitySection(opts) {
|
|
1689
1926
|
const { home, env, print } = opts;
|
|
1690
1927
|
const raw = env.MCPH_DISABLE_PERSISTENCE;
|
|
@@ -1705,6 +1942,18 @@ async function renderReliabilitySection(opts) {
|
|
|
1705
1942
|
}
|
|
1706
1943
|
print("");
|
|
1707
1944
|
}
|
|
1945
|
+
function renderBackgroundPostersSection(opts) {
|
|
1946
|
+
const { print } = opts;
|
|
1947
|
+
const analyticsFailure = getLastAnalyticsFailure();
|
|
1948
|
+
const reportFailure = getLastReportFailure();
|
|
1949
|
+
if (!analyticsFailure && !reportFailure) return;
|
|
1950
|
+
const now = Date.now();
|
|
1951
|
+
const fmt = (f) => `HTTP ${f.statusCode} from ${f.url}, ${formatRelativeAge(now - f.at)} ago`;
|
|
1952
|
+
print("BACKGROUND POSTERS (recent failures)");
|
|
1953
|
+
print(` analytics: ${analyticsFailure ? fmt(analyticsFailure) : "(no recent failure)"}`);
|
|
1954
|
+
print(` tool-report: ${reportFailure ? fmt(reportFailure) : "(no recent failure)"}`);
|
|
1955
|
+
print("");
|
|
1956
|
+
}
|
|
1708
1957
|
function formatRelativeAge(ms) {
|
|
1709
1958
|
const clamped = Math.max(0, ms);
|
|
1710
1959
|
const s = Math.floor(clamped / 1e3);
|
|
@@ -1746,7 +1995,8 @@ function probeClients(opts) {
|
|
|
1746
1995
|
scope: scope.scope,
|
|
1747
1996
|
os: opts.os,
|
|
1748
1997
|
home: opts.home,
|
|
1749
|
-
projectDir: scope.requiresProjectDir ? opts.cwd : void 0
|
|
1998
|
+
projectDir: scope.requiresProjectDir ? opts.cwd : void 0,
|
|
1999
|
+
claudeConfigDir: opts.claudeConfigDir
|
|
1750
2000
|
});
|
|
1751
2001
|
} catch {
|
|
1752
2002
|
continue;
|
|
@@ -1784,9 +2034,9 @@ function probeClients(opts) {
|
|
|
1784
2034
|
}
|
|
1785
2035
|
return out;
|
|
1786
2036
|
}
|
|
1787
|
-
function walkContainer(root,
|
|
2037
|
+
function walkContainer(root, path5) {
|
|
1788
2038
|
let cur = root;
|
|
1789
|
-
for (const key of
|
|
2039
|
+
for (const key of path5) {
|
|
1790
2040
|
if (typeof cur !== "object" || cur === null || Array.isArray(cur)) return null;
|
|
1791
2041
|
cur = cur[key];
|
|
1792
2042
|
}
|
|
@@ -1815,7 +2065,8 @@ async function probeClientsAsync(opts) {
|
|
|
1815
2065
|
scope: scope.scope,
|
|
1816
2066
|
os: opts.os,
|
|
1817
2067
|
home: opts.home,
|
|
1818
|
-
projectDir: scope.requiresProjectDir ? opts.cwd : void 0
|
|
2068
|
+
projectDir: scope.requiresProjectDir ? opts.cwd : void 0,
|
|
2069
|
+
claudeConfigDir: opts.claudeConfigDir
|
|
1819
2070
|
});
|
|
1820
2071
|
const exists3 = existsSync(resolved.absolute);
|
|
1821
2072
|
let hasMcphEntry = false;
|
|
@@ -1922,9 +2173,9 @@ function shellHistorySources(opts) {
|
|
|
1922
2173
|
}
|
|
1923
2174
|
return sources;
|
|
1924
2175
|
}
|
|
1925
|
-
function readTailLines(
|
|
2176
|
+
function readTailLines(path5, n) {
|
|
1926
2177
|
try {
|
|
1927
|
-
const raw = readFileSync(
|
|
2178
|
+
const raw = readFileSync(path5, "utf8");
|
|
1928
2179
|
const all = raw.split(/\r?\n/);
|
|
1929
2180
|
return all.length <= n ? all : all.slice(all.length - n);
|
|
1930
2181
|
} catch {
|
|
@@ -2025,9 +2276,8 @@ function closestNames(query, candidates, limit) {
|
|
|
2025
2276
|
|
|
2026
2277
|
// src/install-cmd.ts
|
|
2027
2278
|
import { existsSync as existsSync2 } from "fs";
|
|
2028
|
-
import { chmod,
|
|
2279
|
+
import { chmod, readFile as readFile4 } from "fs/promises";
|
|
2029
2280
|
import { homedir as homedir5 } from "os";
|
|
2030
|
-
import { dirname as dirname2 } from "path";
|
|
2031
2281
|
import { join as join5, resolve as resolve2 } from "path";
|
|
2032
2282
|
import { createInterface } from "readline/promises";
|
|
2033
2283
|
var USAGE = "Usage: mcph install <claude-code|claude-desktop|cursor|vscode> [--scope user|project|local]\n [--token <mcp_pat_\u2026>] [--project-dir <path>] [--os macos|linux|windows]\n [--force | --skip] [--dry-run] [--no-mcph-config]\n mcph install --list (detect clients; no writes)\n mcph install --all [--token <mcp_pat_\u2026>] (install into every detected client)";
|
|
@@ -2089,7 +2339,8 @@ ${USAGE}`);
|
|
|
2089
2339
|
scope,
|
|
2090
2340
|
os,
|
|
2091
2341
|
home: opts.home,
|
|
2092
|
-
projectDir
|
|
2342
|
+
projectDir,
|
|
2343
|
+
claudeConfigDir: opts.claudeConfigDir
|
|
2093
2344
|
});
|
|
2094
2345
|
} catch (e) {
|
|
2095
2346
|
err(`mcph install: ${e.message}`);
|
|
@@ -2172,40 +2423,39 @@ ${USAGE}`);
|
|
|
2172
2423
|
const writeMcphConfig = !opts.skipMcphConfig;
|
|
2173
2424
|
const home = opts.home ?? homedir5();
|
|
2174
2425
|
const mcphConfigPath = join5(home, CONFIG_DIRNAME, CONFIG_FILENAME);
|
|
2175
|
-
const
|
|
2426
|
+
const mcphConfigComposed = await composeMcphConfig(mcphConfigPath, token6);
|
|
2427
|
+
if (mcphConfigComposed.backupPath) {
|
|
2428
|
+
log2(
|
|
2429
|
+
`mcph install: existing ${mcphConfigPath} was malformed; original bytes backed up to ${mcphConfigComposed.backupPath} before overwriting.`
|
|
2430
|
+
);
|
|
2431
|
+
}
|
|
2432
|
+
const mcphConfigJson = mcphConfigComposed.json;
|
|
2176
2433
|
const settingsPatch = opts.clientId === "claude-code" ? await prepareClaudeCodeSettingsPatch({
|
|
2177
2434
|
scope,
|
|
2178
2435
|
home,
|
|
2179
2436
|
projectDir,
|
|
2180
|
-
os
|
|
2437
|
+
os,
|
|
2438
|
+
claudeConfigDir: opts.claudeConfigDir
|
|
2181
2439
|
}) : null;
|
|
2182
2440
|
if (opts.dryRun) {
|
|
2183
2441
|
log2("\n--- dry run: would write the following ---");
|
|
2442
|
+
if (writeMcphConfig) log2(`# ${mcphConfigPath}
|
|
2443
|
+
${mcphConfigJson}`);
|
|
2184
2444
|
log2(`
|
|
2185
2445
|
# ${resolved.absolute}
|
|
2186
2446
|
${clientJson}`);
|
|
2187
|
-
if (writeMcphConfig) log2(`# ${mcphConfigPath}
|
|
2188
|
-
${mcphConfigJson}`);
|
|
2189
2447
|
if (settingsPatch?.changed) log2(`# ${settingsPatch.path}
|
|
2190
2448
|
${settingsPatch.nextJson}`);
|
|
2191
|
-
const wouldWrite = [
|
|
2449
|
+
const wouldWrite = [];
|
|
2192
2450
|
if (writeMcphConfig) wouldWrite.push(mcphConfigPath);
|
|
2451
|
+
wouldWrite.push(resolved.absolute);
|
|
2193
2452
|
if (settingsPatch?.changed) wouldWrite.push(settingsPatch.path);
|
|
2194
2453
|
return { written: [], wouldWrite, messages, exitCode: 0 };
|
|
2195
2454
|
}
|
|
2196
|
-
|
|
2197
|
-
await mkdir3(dirname2(resolved.absolute), { recursive: true });
|
|
2198
|
-
await writeFile2(resolved.absolute, clientJson, "utf8");
|
|
2199
|
-
} catch (e) {
|
|
2200
|
-
err(`mcph install: failed to write ${resolved.absolute}: ${e.message}`);
|
|
2201
|
-
return { written: [], wouldWrite: [], messages, exitCode: 1 };
|
|
2202
|
-
}
|
|
2203
|
-
log2(`Wrote ${resolved.absolute}`);
|
|
2204
|
-
const written = [resolved.absolute];
|
|
2455
|
+
const written = [];
|
|
2205
2456
|
if (writeMcphConfig) {
|
|
2206
2457
|
try {
|
|
2207
|
-
await
|
|
2208
|
-
await writeFile2(mcphConfigPath, mcphConfigJson, "utf8");
|
|
2458
|
+
await atomicWriteFile(mcphConfigPath, mcphConfigJson);
|
|
2209
2459
|
if (process.platform !== "win32") {
|
|
2210
2460
|
try {
|
|
2211
2461
|
await chmod(mcphConfigPath, 384);
|
|
@@ -2214,15 +2464,22 @@ ${settingsPatch.nextJson}`);
|
|
|
2214
2464
|
}
|
|
2215
2465
|
} catch (e) {
|
|
2216
2466
|
err(`mcph install: failed to write ${mcphConfigPath}: ${e.message}`);
|
|
2217
|
-
return { written, wouldWrite: [], messages, exitCode: 1 };
|
|
2467
|
+
return { written: [], wouldWrite: [], messages, exitCode: 1 };
|
|
2218
2468
|
}
|
|
2219
2469
|
log2(`Wrote ${mcphConfigPath}`);
|
|
2220
2470
|
written.push(mcphConfigPath);
|
|
2221
2471
|
}
|
|
2472
|
+
try {
|
|
2473
|
+
await atomicWriteFile(resolved.absolute, clientJson);
|
|
2474
|
+
} catch (e) {
|
|
2475
|
+
err(`mcph install: failed to write ${resolved.absolute}: ${e.message}`);
|
|
2476
|
+
return { written, wouldWrite: [], messages, exitCode: 1 };
|
|
2477
|
+
}
|
|
2478
|
+
log2(`Wrote ${resolved.absolute}`);
|
|
2479
|
+
written.push(resolved.absolute);
|
|
2222
2480
|
if (settingsPatch?.changed) {
|
|
2223
2481
|
try {
|
|
2224
|
-
await
|
|
2225
|
-
await writeFile2(settingsPatch.path, settingsPatch.nextJson, "utf8");
|
|
2482
|
+
await atomicWriteFile(settingsPatch.path, settingsPatch.nextJson);
|
|
2226
2483
|
log2(`Wrote ${settingsPatch.path} (added ${CLAUDE_CODE_ALLOW_PATTERN} to permissions.allow)`);
|
|
2227
2484
|
written.push(settingsPatch.path);
|
|
2228
2485
|
} catch (e) {
|
|
@@ -2237,33 +2494,34 @@ ${settingsPatch.nextJson}`);
|
|
|
2237
2494
|
return { written, wouldWrite: [], messages, exitCode: 0 };
|
|
2238
2495
|
}
|
|
2239
2496
|
async function prepareClaudeCodeSettingsPatch(opts) {
|
|
2240
|
-
const
|
|
2497
|
+
const path5 = resolveClaudeCodeSettingsPath(opts.scope, {
|
|
2241
2498
|
home: opts.home,
|
|
2242
2499
|
projectDir: opts.projectDir,
|
|
2243
|
-
os: opts.os
|
|
2500
|
+
os: opts.os,
|
|
2501
|
+
claudeConfigDir: opts.claudeConfigDir
|
|
2244
2502
|
});
|
|
2245
|
-
if (!
|
|
2503
|
+
if (!path5) return null;
|
|
2246
2504
|
let existing = {};
|
|
2247
|
-
if (existsSync2(
|
|
2505
|
+
if (existsSync2(path5)) {
|
|
2248
2506
|
try {
|
|
2249
|
-
const raw = await readFile4(
|
|
2507
|
+
const raw = await readFile4(path5, "utf8");
|
|
2250
2508
|
if (raw.trim().length > 0) {
|
|
2251
2509
|
const parsed = parseJsonc(raw);
|
|
2252
2510
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
2253
2511
|
existing = parsed;
|
|
2254
2512
|
} else {
|
|
2255
|
-
return { path:
|
|
2513
|
+
return { path: path5, nextJson: "", changed: false };
|
|
2256
2514
|
}
|
|
2257
2515
|
}
|
|
2258
2516
|
} catch {
|
|
2259
|
-
return { path:
|
|
2517
|
+
return { path: path5, nextJson: "", changed: false };
|
|
2260
2518
|
}
|
|
2261
2519
|
}
|
|
2262
2520
|
const merged = mergePermissionsAllow(existing, [CLAUDE_CODE_ALLOW_PATTERN]);
|
|
2263
2521
|
const before = JSON.stringify(existing);
|
|
2264
2522
|
const after = JSON.stringify(merged);
|
|
2265
|
-
if (before === after) return { path:
|
|
2266
|
-
return { path:
|
|
2523
|
+
if (before === after) return { path: path5, nextJson: "", changed: false };
|
|
2524
|
+
return { path: path5, nextJson: `${JSON.stringify(merged, null, 2)}
|
|
2267
2525
|
`, changed: true };
|
|
2268
2526
|
}
|
|
2269
2527
|
function mergePermissionsAllow(existing, patterns) {
|
|
@@ -2279,13 +2537,13 @@ function mergePermissionsAllow(existing, patterns) {
|
|
|
2279
2537
|
out.permissions = perms;
|
|
2280
2538
|
return out;
|
|
2281
2539
|
}
|
|
2282
|
-
async function promptCollision(
|
|
2540
|
+
async function promptCollision(path5, io) {
|
|
2283
2541
|
const stdin = io?.stdin ?? process.stdin;
|
|
2284
2542
|
const stdout = io?.stdout ?? process.stdout;
|
|
2285
2543
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
2286
2544
|
try {
|
|
2287
2545
|
const answer = (await rl.question(
|
|
2288
|
-
`${
|
|
2546
|
+
`${path5} already has an "${ENTRY_NAME}" entry.
|
|
2289
2547
|
[o]verwrite, [s]kip, or [a]bort? (default: skip) `
|
|
2290
2548
|
)).trim().toLowerCase();
|
|
2291
2549
|
if (answer.startsWith("o")) return "overwrite";
|
|
@@ -2321,23 +2579,37 @@ function mergeClientConfig(existing, containerPath, entry) {
|
|
|
2321
2579
|
parent[leafKey] = container;
|
|
2322
2580
|
return out;
|
|
2323
2581
|
}
|
|
2324
|
-
async function composeMcphConfig(
|
|
2582
|
+
async function composeMcphConfig(path5, token6) {
|
|
2325
2583
|
let existing = {};
|
|
2326
|
-
|
|
2584
|
+
let backupPath;
|
|
2585
|
+
if (existsSync2(path5)) {
|
|
2586
|
+
let raw = "";
|
|
2327
2587
|
try {
|
|
2328
|
-
|
|
2329
|
-
const parsed = parseJsonc(raw);
|
|
2330
|
-
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
2331
|
-
existing = parsed;
|
|
2332
|
-
}
|
|
2588
|
+
raw = await readFile4(path5, "utf8");
|
|
2333
2589
|
} catch {
|
|
2590
|
+
raw = "";
|
|
2591
|
+
}
|
|
2592
|
+
if (raw) {
|
|
2593
|
+
try {
|
|
2594
|
+
const parsed = parseJsonc(raw);
|
|
2595
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
2596
|
+
existing = parsed;
|
|
2597
|
+
}
|
|
2598
|
+
} catch {
|
|
2599
|
+
const candidate = `${path5}.bak-${Date.now()}`;
|
|
2600
|
+
try {
|
|
2601
|
+
await atomicWriteFile(candidate, raw);
|
|
2602
|
+
backupPath = candidate;
|
|
2603
|
+
} catch {
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2334
2606
|
}
|
|
2335
2607
|
}
|
|
2336
2608
|
const next = { version: CURRENT_SCHEMA_VERSION, ...existing };
|
|
2337
2609
|
next.token = token6;
|
|
2338
2610
|
if (typeof next.version !== "number") next.version = CURRENT_SCHEMA_VERSION;
|
|
2339
|
-
return `${JSON.stringify(next, null, 2)}
|
|
2340
|
-
|
|
2611
|
+
return { json: `${JSON.stringify(next, null, 2)}
|
|
2612
|
+
`, backupPath };
|
|
2341
2613
|
}
|
|
2342
2614
|
function parseInstallArgs(argv) {
|
|
2343
2615
|
if (argv.length === 0) return { ok: false, error: USAGE };
|
|
@@ -2427,7 +2699,7 @@ async function runInstallList(opts, log2) {
|
|
|
2427
2699
|
const home = opts.home ?? homedir5();
|
|
2428
2700
|
const cwd = opts.cwd ?? process.cwd();
|
|
2429
2701
|
const os = opts.os ?? CURRENT_OS;
|
|
2430
|
-
const probes = await probeClientsAsync({ home, os, cwd });
|
|
2702
|
+
const probes = await probeClientsAsync({ home, os, cwd, claudeConfigDir: opts.claudeConfigDir });
|
|
2431
2703
|
const rows = probes.map((p) => ({
|
|
2432
2704
|
client: INSTALL_TARGETS.find((t) => t.clientId === p.clientId)?.label ?? p.clientId,
|
|
2433
2705
|
scope: p.scope,
|
|
@@ -2546,9 +2818,29 @@ async function runInstallAll(opts, log2, err) {
|
|
|
2546
2818
|
}
|
|
2547
2819
|
|
|
2548
2820
|
// src/reset-learning-cmd.ts
|
|
2549
|
-
import { unlink } from "fs/promises";
|
|
2821
|
+
import { unlink as unlink2 } from "fs/promises";
|
|
2550
2822
|
import { homedir as homedir6 } from "os";
|
|
2551
2823
|
import { join as join6 } from "path";
|
|
2824
|
+
var RESET_LEARNING_USAGE = `Usage: mcph reset-learning
|
|
2825
|
+
|
|
2826
|
+
Delete ~/.mcph/state.json so cross-session learning starts fresh.
|
|
2827
|
+
Use this after fixing the root cause of a flaky upstream (token
|
|
2828
|
+
rotated, account swapped, server replaced) so the routing penalty
|
|
2829
|
+
doesn't keep suppressing it.
|
|
2830
|
+
|
|
2831
|
+
-h, --help Show this help.`;
|
|
2832
|
+
function parseResetLearningArgs(argv) {
|
|
2833
|
+
for (const arg of argv) {
|
|
2834
|
+
if (arg === "-h" || arg === "--help") return { kind: "help" };
|
|
2835
|
+
return {
|
|
2836
|
+
kind: "error",
|
|
2837
|
+
error: `mcph reset-learning: unknown argument "${arg}"
|
|
2838
|
+
|
|
2839
|
+
${RESET_LEARNING_USAGE}`
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
return { kind: "ok", options: {} };
|
|
2843
|
+
}
|
|
2552
2844
|
async function runResetLearning(opts = {}) {
|
|
2553
2845
|
const home = opts.home ?? homedir6();
|
|
2554
2846
|
const env = opts.env ?? process.env;
|
|
@@ -2576,7 +2868,7 @@ async function runResetLearning(opts = {}) {
|
|
|
2576
2868
|
const learningCount = Object.keys(persisted.learning).length;
|
|
2577
2869
|
const packCount = persisted.packHistory.length;
|
|
2578
2870
|
try {
|
|
2579
|
-
await
|
|
2871
|
+
await unlink2(filePath);
|
|
2580
2872
|
} catch (err) {
|
|
2581
2873
|
if (isFileNotFound2(err)) {
|
|
2582
2874
|
print("mcph reset-learning: no persisted state to reset.");
|
|
@@ -2613,112 +2905,6 @@ import {
|
|
|
2613
2905
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
2614
2906
|
import { request as request9 } from "undici";
|
|
2615
2907
|
|
|
2616
|
-
// src/analytics.ts
|
|
2617
|
-
import { request as request3 } from "undici";
|
|
2618
|
-
var FLUSH_INTERVAL = 3e4;
|
|
2619
|
-
var FLUSH_SIZE = 50;
|
|
2620
|
-
var MAX_BUFFER = 5e3;
|
|
2621
|
-
var buffer = [];
|
|
2622
|
-
var dispatchBuffer = [];
|
|
2623
|
-
var flushTimer = null;
|
|
2624
|
-
var apiUrl = "";
|
|
2625
|
-
var token = "";
|
|
2626
|
-
function recordConnectEvent(event) {
|
|
2627
|
-
if (buffer.length >= MAX_BUFFER) return;
|
|
2628
|
-
buffer.push({ ...event, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2629
|
-
if (buffer.length >= FLUSH_SIZE) {
|
|
2630
|
-
flush().catch(() => {
|
|
2631
|
-
});
|
|
2632
|
-
}
|
|
2633
|
-
}
|
|
2634
|
-
function recordDispatchEvent(event) {
|
|
2635
|
-
if (dispatchBuffer.length >= MAX_BUFFER) return;
|
|
2636
|
-
dispatchBuffer.push(event);
|
|
2637
|
-
if (dispatchBuffer.length >= FLUSH_SIZE) {
|
|
2638
|
-
flushDispatch().catch(() => {
|
|
2639
|
-
});
|
|
2640
|
-
}
|
|
2641
|
-
}
|
|
2642
|
-
async function flush() {
|
|
2643
|
-
if (buffer.length === 0 || !apiUrl || !token) return;
|
|
2644
|
-
const events = buffer.splice(0, FLUSH_SIZE);
|
|
2645
|
-
try {
|
|
2646
|
-
const res = await request3(`${apiUrl.replace(/\/$/, "")}/api/connect/analytics`, {
|
|
2647
|
-
method: "POST",
|
|
2648
|
-
headers: {
|
|
2649
|
-
Authorization: `Bearer ${token}`,
|
|
2650
|
-
"Content-Type": "application/json"
|
|
2651
|
-
},
|
|
2652
|
-
body: JSON.stringify({ events }),
|
|
2653
|
-
headersTimeout: 1e4,
|
|
2654
|
-
bodyTimeout: 1e4
|
|
2655
|
-
});
|
|
2656
|
-
if (res.statusCode >= 400) {
|
|
2657
|
-
const room = MAX_BUFFER - buffer.length;
|
|
2658
|
-
if (room > 0) buffer.push(...events.slice(0, room));
|
|
2659
|
-
log("warn", "Analytics flush failed", { status: res.statusCode });
|
|
2660
|
-
}
|
|
2661
|
-
await res.body.text().catch(() => {
|
|
2662
|
-
});
|
|
2663
|
-
} catch (err) {
|
|
2664
|
-
const room = MAX_BUFFER - buffer.length;
|
|
2665
|
-
if (room > 0) buffer.push(...events.slice(0, room));
|
|
2666
|
-
log("warn", "Analytics flush error", { error: err.message });
|
|
2667
|
-
}
|
|
2668
|
-
}
|
|
2669
|
-
async function flushDispatch() {
|
|
2670
|
-
if (dispatchBuffer.length === 0 || !apiUrl || !token) return;
|
|
2671
|
-
const events = dispatchBuffer.splice(0, FLUSH_SIZE);
|
|
2672
|
-
try {
|
|
2673
|
-
const res = await request3(`${apiUrl.replace(/\/$/, "")}/api/connect/dispatch-events`, {
|
|
2674
|
-
method: "POST",
|
|
2675
|
-
headers: {
|
|
2676
|
-
Authorization: `Bearer ${token}`,
|
|
2677
|
-
"Content-Type": "application/json"
|
|
2678
|
-
},
|
|
2679
|
-
body: JSON.stringify({ events }),
|
|
2680
|
-
headersTimeout: 1e4,
|
|
2681
|
-
bodyTimeout: 1e4
|
|
2682
|
-
});
|
|
2683
|
-
if (res.statusCode >= 400 && res.statusCode !== 204) {
|
|
2684
|
-
const room = MAX_BUFFER - dispatchBuffer.length;
|
|
2685
|
-
if (room > 0) dispatchBuffer.push(...events.slice(0, room));
|
|
2686
|
-
log("warn", "Dispatch-events flush failed", { status: res.statusCode });
|
|
2687
|
-
}
|
|
2688
|
-
await res.body.text().catch(() => {
|
|
2689
|
-
});
|
|
2690
|
-
} catch (err) {
|
|
2691
|
-
const room = MAX_BUFFER - dispatchBuffer.length;
|
|
2692
|
-
if (room > 0) dispatchBuffer.push(...events.slice(0, room));
|
|
2693
|
-
log("warn", "Dispatch-events flush error", { error: err.message });
|
|
2694
|
-
}
|
|
2695
|
-
}
|
|
2696
|
-
function initAnalytics(url, tok) {
|
|
2697
|
-
apiUrl = url;
|
|
2698
|
-
token = tok;
|
|
2699
|
-
flushTimer = setInterval(() => {
|
|
2700
|
-
flush().catch(() => {
|
|
2701
|
-
});
|
|
2702
|
-
flushDispatch().catch(() => {
|
|
2703
|
-
});
|
|
2704
|
-
}, FLUSH_INTERVAL);
|
|
2705
|
-
if (flushTimer.unref) flushTimer.unref();
|
|
2706
|
-
}
|
|
2707
|
-
async function shutdownAnalytics() {
|
|
2708
|
-
if (flushTimer) {
|
|
2709
|
-
clearInterval(flushTimer);
|
|
2710
|
-
flushTimer = null;
|
|
2711
|
-
}
|
|
2712
|
-
for (let i = 0; i < 3 && buffer.length > 0; i++) {
|
|
2713
|
-
await flush();
|
|
2714
|
-
}
|
|
2715
|
-
for (let i = 0; i < 3 && dispatchBuffer.length > 0; i++) {
|
|
2716
|
-
await flushDispatch();
|
|
2717
|
-
}
|
|
2718
|
-
buffer.length = 0;
|
|
2719
|
-
dispatchBuffer.length = 0;
|
|
2720
|
-
}
|
|
2721
|
-
|
|
2722
2908
|
// src/compliance.ts
|
|
2723
2909
|
var GRADE_ORDER = {
|
|
2724
2910
|
A: 4,
|
|
@@ -2727,13 +2913,20 @@ var GRADE_ORDER = {
|
|
|
2727
2913
|
D: 1,
|
|
2728
2914
|
F: 0
|
|
2729
2915
|
};
|
|
2916
|
+
function classifyGrade(grade) {
|
|
2917
|
+
if (grade === void 0 || grade === null) return { kind: "ungraded" };
|
|
2918
|
+
const trimmed = grade.trim();
|
|
2919
|
+
if (trimmed === "") return { kind: "ungraded" };
|
|
2920
|
+
const up = trimmed.toUpperCase();
|
|
2921
|
+
if (up in GRADE_ORDER) return { kind: "graded", rank: GRADE_ORDER[up] };
|
|
2922
|
+
return { kind: "unrecognized", raw: grade };
|
|
2923
|
+
}
|
|
2730
2924
|
function gradeRank(grade) {
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
if (up in GRADE_ORDER) return GRADE_ORDER[up];
|
|
2734
|
-
return -1;
|
|
2925
|
+
const c = classifyGrade(grade);
|
|
2926
|
+
return c.kind === "graded" ? c.rank : -1;
|
|
2735
2927
|
}
|
|
2736
2928
|
var invalidWarned = false;
|
|
2929
|
+
var unrecognizedServerWarned = /* @__PURE__ */ new Set();
|
|
2737
2930
|
function parseMinCompliance(raw) {
|
|
2738
2931
|
if (raw === void 0) return null;
|
|
2739
2932
|
const trimmed = raw.trim();
|
|
@@ -2750,9 +2943,19 @@ function parseMinCompliance(raw) {
|
|
|
2750
2943
|
}
|
|
2751
2944
|
function passesMinCompliance(serverGrade, min) {
|
|
2752
2945
|
if (min === null) return true;
|
|
2753
|
-
const
|
|
2754
|
-
if (
|
|
2755
|
-
|
|
2946
|
+
const c = classifyGrade(serverGrade);
|
|
2947
|
+
if (c.kind === "ungraded") return true;
|
|
2948
|
+
if (c.kind === "unrecognized") {
|
|
2949
|
+
if (!unrecognizedServerWarned.has(c.raw)) {
|
|
2950
|
+
unrecognizedServerWarned.add(c.raw);
|
|
2951
|
+
log("warn", "Unrecognized server compliance grade; failing closed under MCPH_MIN_COMPLIANCE", {
|
|
2952
|
+
grade: c.raw,
|
|
2953
|
+
min
|
|
2954
|
+
});
|
|
2955
|
+
}
|
|
2956
|
+
return false;
|
|
2957
|
+
}
|
|
2958
|
+
return c.rank >= gradeRank(min);
|
|
2756
2959
|
}
|
|
2757
2960
|
|
|
2758
2961
|
// src/cost-estimate.ts
|
|
@@ -3023,18 +3226,18 @@ function stepBindingKey(step, index) {
|
|
|
3023
3226
|
// src/guide.ts
|
|
3024
3227
|
import { readFile as readFile5 } from "fs/promises";
|
|
3025
3228
|
var GUIDE_READ_TIMEOUT_MS = 1e3;
|
|
3026
|
-
async function readGuide(
|
|
3229
|
+
async function readGuide(path5, scope) {
|
|
3027
3230
|
let raw;
|
|
3028
3231
|
try {
|
|
3029
3232
|
raw = await Promise.race([
|
|
3030
|
-
readFile5(
|
|
3233
|
+
readFile5(path5, "utf8"),
|
|
3031
3234
|
new Promise(
|
|
3032
3235
|
(_, reject) => setTimeout(() => reject(new Error("guide read timeout")), GUIDE_READ_TIMEOUT_MS)
|
|
3033
3236
|
)
|
|
3034
3237
|
]);
|
|
3035
3238
|
} catch (err) {
|
|
3036
3239
|
if (err instanceof Error && err.message === "guide read timeout") {
|
|
3037
|
-
log("warn", "Guide read timed out", { path:
|
|
3240
|
+
log("warn", "Guide read timed out", { path: path5 });
|
|
3038
3241
|
}
|
|
3039
3242
|
return null;
|
|
3040
3243
|
}
|
|
@@ -3042,7 +3245,7 @@ async function readGuide(path4, scope) {
|
|
|
3042
3245
|
if (content.length === 0) {
|
|
3043
3246
|
return null;
|
|
3044
3247
|
}
|
|
3045
|
-
return { scope, path:
|
|
3248
|
+
return { scope, path: path5, content };
|
|
3046
3249
|
}
|
|
3047
3250
|
async function loadUserGuide(home) {
|
|
3048
3251
|
const p = guidePath(userConfigDir(home));
|
|
@@ -3822,6 +4025,14 @@ function buildToolRoutes(activeConnections, inactiveWithCache = []) {
|
|
|
3822
4025
|
const routes = /* @__PURE__ */ new Map();
|
|
3823
4026
|
for (const conn of activeConnections.values()) {
|
|
3824
4027
|
for (const tool of conn.tools) {
|
|
4028
|
+
const existing = routes.get(tool.namespacedName);
|
|
4029
|
+
if (existing && existing.namespace !== conn.config.namespace) {
|
|
4030
|
+
log("warn", "Tool route collision; later upstream shadows earlier", {
|
|
4031
|
+
tool: tool.namespacedName,
|
|
4032
|
+
shadowedNamespace: existing.namespace,
|
|
4033
|
+
winningNamespace: conn.config.namespace
|
|
4034
|
+
});
|
|
4035
|
+
}
|
|
3825
4036
|
routes.set(tool.namespacedName, {
|
|
3826
4037
|
namespace: conn.config.namespace,
|
|
3827
4038
|
originalName: tool.name
|
|
@@ -3969,9 +4180,12 @@ async function routeToolCall(toolName, args, toolRoutes, activeConnections) {
|
|
|
3969
4180
|
});
|
|
3970
4181
|
return result;
|
|
3971
4182
|
} catch (err) {
|
|
3972
|
-
|
|
4183
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4184
|
+
const code = err && typeof err === "object" && "code" in err && typeof err.code === "number" ? err.code : void 0;
|
|
4185
|
+
log("error", "Tool call failed", { tool: toolName, namespace: route.namespace, error: message, code });
|
|
4186
|
+
const codeTag = code !== void 0 ? ` [code=${code}]` : "";
|
|
3973
4187
|
return {
|
|
3974
|
-
content: [{ type: "text", text: `Error calling ${toolName}: ${
|
|
4188
|
+
content: [{ type: "text", text: `Error calling ${toolName}${codeTag}: ${message}` }],
|
|
3975
4189
|
isError: true
|
|
3976
4190
|
};
|
|
3977
4191
|
}
|
|
@@ -4194,26 +4408,26 @@ function rankServers(context, servers) {
|
|
|
4194
4408
|
}
|
|
4195
4409
|
|
|
4196
4410
|
// src/rerank.ts
|
|
4197
|
-
import { request as
|
|
4198
|
-
var
|
|
4199
|
-
var
|
|
4411
|
+
import { request as request5 } from "undici";
|
|
4412
|
+
var apiUrl3 = "";
|
|
4413
|
+
var token3 = "";
|
|
4200
4414
|
var RERANK_TIMEOUT_MS = 2e3;
|
|
4201
4415
|
function initRerank(url, tok) {
|
|
4202
|
-
|
|
4203
|
-
|
|
4416
|
+
apiUrl3 = url;
|
|
4417
|
+
token3 = tok;
|
|
4204
4418
|
}
|
|
4205
4419
|
async function rerank(intent, candidateIds, limit) {
|
|
4206
|
-
if (!
|
|
4420
|
+
if (!apiUrl3 || !token3) return null;
|
|
4207
4421
|
if (!intent?.trim()) return null;
|
|
4208
4422
|
if (candidateIds !== void 0 && candidateIds.length === 0) return null;
|
|
4209
4423
|
const payload = { intent: intent.trim() };
|
|
4210
4424
|
if (candidateIds && candidateIds.length > 0) payload.candidateIds = candidateIds;
|
|
4211
4425
|
if (typeof limit === "number" && limit > 0) payload.limit = limit;
|
|
4212
4426
|
try {
|
|
4213
|
-
const res = await
|
|
4427
|
+
const res = await request5(`${apiUrl3.replace(/\/$/, "")}/api/connect/rerank`, {
|
|
4214
4428
|
method: "POST",
|
|
4215
4429
|
headers: {
|
|
4216
|
-
Authorization: `Bearer ${
|
|
4430
|
+
Authorization: `Bearer ${token3}`,
|
|
4217
4431
|
"Content-Type": "application/json"
|
|
4218
4432
|
},
|
|
4219
4433
|
body: JSON.stringify(payload),
|
|
@@ -4243,14 +4457,14 @@ async function rerank(intent, candidateIds, limit) {
|
|
|
4243
4457
|
|
|
4244
4458
|
// src/runtime-detect.ts
|
|
4245
4459
|
import { spawn as spawn2 } from "child_process";
|
|
4246
|
-
import { request as
|
|
4460
|
+
import { request as request6 } from "undici";
|
|
4247
4461
|
var PROBE_TIMEOUT_MS = 3e3;
|
|
4248
4462
|
var RUNTIME_REPORT_PATH = "/api/connect/runtimes";
|
|
4249
|
-
var
|
|
4250
|
-
var
|
|
4463
|
+
var apiUrl4 = "";
|
|
4464
|
+
var token4 = "";
|
|
4251
4465
|
function initRuntimeDetect(url, tok) {
|
|
4252
|
-
|
|
4253
|
-
|
|
4466
|
+
apiUrl4 = url;
|
|
4467
|
+
token4 = tok;
|
|
4254
4468
|
}
|
|
4255
4469
|
var PROBES = {
|
|
4256
4470
|
node: {
|
|
@@ -4362,7 +4576,7 @@ async function detectRuntimes() {
|
|
|
4362
4576
|
return out;
|
|
4363
4577
|
}
|
|
4364
4578
|
async function reportRuntimes() {
|
|
4365
|
-
if (!
|
|
4579
|
+
if (!apiUrl4 || !token4) return;
|
|
4366
4580
|
let runtimes;
|
|
4367
4581
|
try {
|
|
4368
4582
|
runtimes = await detectRuntimes();
|
|
@@ -4371,10 +4585,10 @@ async function reportRuntimes() {
|
|
|
4371
4585
|
return;
|
|
4372
4586
|
}
|
|
4373
4587
|
try {
|
|
4374
|
-
const res = await
|
|
4588
|
+
const res = await request6(`${apiUrl4.replace(/\/$/, "")}${RUNTIME_REPORT_PATH}`, {
|
|
4375
4589
|
method: "POST",
|
|
4376
4590
|
headers: {
|
|
4377
|
-
Authorization: `Bearer ${
|
|
4591
|
+
Authorization: `Bearer ${token4}`,
|
|
4378
4592
|
"Content-Type": "application/json"
|
|
4379
4593
|
},
|
|
4380
4594
|
body: JSON.stringify({ runtimes }),
|
|
@@ -4419,15 +4633,23 @@ function buildTiebreakPrompt(intent, candidates) {
|
|
|
4419
4633
|
].join("\n");
|
|
4420
4634
|
}
|
|
4421
4635
|
function parseTiebreakResponse(response, candidates) {
|
|
4422
|
-
const namespaces =
|
|
4636
|
+
const namespaces = candidates.map((c) => c.namespace);
|
|
4637
|
+
const namespaceSet = new Set(namespaces);
|
|
4423
4638
|
for (const rawLine of response.split(/\r?\n/)) {
|
|
4424
4639
|
const line = rawLine.trim().replace(/^[`"'*>\-\s]+|[`"'*\s]+$/g, "");
|
|
4425
4640
|
if (!line) continue;
|
|
4426
|
-
if (
|
|
4641
|
+
if (namespaceSet.has(line)) return line;
|
|
4642
|
+
let bestNs = null;
|
|
4643
|
+
let bestPos = Number.POSITIVE_INFINITY;
|
|
4427
4644
|
for (const ns of namespaces) {
|
|
4428
4645
|
const re = new RegExp(`\\b${escapeRegex(ns)}\\b`);
|
|
4429
|
-
|
|
4646
|
+
const match = re.exec(line);
|
|
4647
|
+
if (match && match.index < bestPos) {
|
|
4648
|
+
bestPos = match.index;
|
|
4649
|
+
bestNs = ns;
|
|
4650
|
+
}
|
|
4430
4651
|
}
|
|
4652
|
+
if (bestNs) return bestNs;
|
|
4431
4653
|
}
|
|
4432
4654
|
return null;
|
|
4433
4655
|
}
|
|
@@ -4506,7 +4728,7 @@ function evaluateServerCap(namespace, loaded, cap) {
|
|
|
4506
4728
|
}
|
|
4507
4729
|
|
|
4508
4730
|
// src/test-runner.ts
|
|
4509
|
-
import { request as
|
|
4731
|
+
import { request as request8 } from "undici";
|
|
4510
4732
|
|
|
4511
4733
|
// src/upstream.ts
|
|
4512
4734
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -4524,9 +4746,9 @@ import { spawn as spawn3 } from "child_process";
|
|
|
4524
4746
|
import { createHash } from "crypto";
|
|
4525
4747
|
import { createWriteStream } from "fs";
|
|
4526
4748
|
import fs from "fs/promises";
|
|
4527
|
-
import
|
|
4749
|
+
import path4 from "path";
|
|
4528
4750
|
import { pipeline } from "stream/promises";
|
|
4529
|
-
import { request as
|
|
4751
|
+
import { request as request7 } from "undici";
|
|
4530
4752
|
var UV_VERSION = "0.11.7";
|
|
4531
4753
|
var RELEASE_BASE = `https://github.com/astral-sh/uv/releases/download/${UV_VERSION}`;
|
|
4532
4754
|
function uvTarget() {
|
|
@@ -4603,7 +4825,7 @@ async function onPath(cmd) {
|
|
|
4603
4825
|
async function fetchWithRedirects(url, maxHops = 5) {
|
|
4604
4826
|
let current = url;
|
|
4605
4827
|
for (let i = 0; i < maxHops; i++) {
|
|
4606
|
-
const res = await
|
|
4828
|
+
const res = await request7(current, { method: "GET" });
|
|
4607
4829
|
if (res.statusCode >= 300 && res.statusCode < 400) {
|
|
4608
4830
|
const loc = res.headers.location;
|
|
4609
4831
|
if (!loc) throw new Error(`Redirect without Location header from ${current}`);
|
|
@@ -4653,7 +4875,7 @@ function runCommand(cmd, args) {
|
|
|
4653
4875
|
async function findBinary(root, name) {
|
|
4654
4876
|
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
4655
4877
|
for (const e of entries) {
|
|
4656
|
-
const full =
|
|
4878
|
+
const full = path4.join(root, e.name);
|
|
4657
4879
|
if (e.isFile() && e.name === name) return full;
|
|
4658
4880
|
if (e.isDirectory()) {
|
|
4659
4881
|
const found = await findBinary(full, name);
|
|
@@ -4675,8 +4897,8 @@ async function resolveUv() {
|
|
|
4675
4897
|
`No prebuilt uv binary for ${process.platform}/${process.arch}. Install uv manually: https://docs.astral.sh/uv/`
|
|
4676
4898
|
);
|
|
4677
4899
|
}
|
|
4678
|
-
const installDir =
|
|
4679
|
-
const finalBin =
|
|
4900
|
+
const installDir = path4.join(cacheDir(), "uv", UV_VERSION);
|
|
4901
|
+
const finalBin = path4.join(installDir, binName());
|
|
4680
4902
|
if (await exists2(finalBin)) return finalBin;
|
|
4681
4903
|
await fs.mkdir(installDir, { recursive: true });
|
|
4682
4904
|
log("info", "Bootstrapping uv", { version: UV_VERSION, target, cache: installDir });
|
|
@@ -4689,11 +4911,11 @@ async function resolveUv() {
|
|
|
4689
4911
|
if (!expected || expected.toLowerCase() !== actual.toLowerCase()) {
|
|
4690
4912
|
throw new Error(`uv archive checksum mismatch (expected ${expected}, got ${actual})`);
|
|
4691
4913
|
}
|
|
4692
|
-
const archivePath =
|
|
4914
|
+
const archivePath = path4.join(installDir, archiveName);
|
|
4693
4915
|
await pipeline(async function* () {
|
|
4694
4916
|
yield archiveBuf;
|
|
4695
4917
|
}, createWriteStream(archivePath));
|
|
4696
|
-
const extractDir =
|
|
4918
|
+
const extractDir = path4.join(installDir, "extract");
|
|
4697
4919
|
await fs.rm(extractDir, { recursive: true, force: true });
|
|
4698
4920
|
await extractArchive(archivePath, extractDir);
|
|
4699
4921
|
const extracted = await findBinary(extractDir, binName());
|
|
@@ -4722,6 +4944,12 @@ var CONNECT_TIMEOUT = (() => {
|
|
|
4722
4944
|
const n = Number.parseInt(env, 10);
|
|
4723
4945
|
return Number.isFinite(n) && n > 0 ? n : 15e3;
|
|
4724
4946
|
})();
|
|
4947
|
+
var LIST_TIMEOUT = (() => {
|
|
4948
|
+
const env = process.env.MCP_LIST_TIMEOUT;
|
|
4949
|
+
if (!env) return 15e3;
|
|
4950
|
+
const n = Number.parseInt(env, 10);
|
|
4951
|
+
return Number.isFinite(n) && n > 0 ? n : 15e3;
|
|
4952
|
+
})();
|
|
4725
4953
|
var STDERR_RING_CAP = 8 * 1024;
|
|
4726
4954
|
var MAX_TOOLS_PER_SERVER = 1e3;
|
|
4727
4955
|
var MAX_RESOURCES_PER_SERVER = 1e3;
|
|
@@ -4746,7 +4974,7 @@ function categorizeSpawnError(err) {
|
|
|
4746
4974
|
}
|
|
4747
4975
|
async function connectToUpstream(config, onDisconnect, onListChanged) {
|
|
4748
4976
|
const client = new Client(
|
|
4749
|
-
{ name: "mcph", version: true ? "0.47.
|
|
4977
|
+
{ name: "mcph", version: true ? "0.47.2" : "dev" },
|
|
4750
4978
|
{ capabilities: {} }
|
|
4751
4979
|
);
|
|
4752
4980
|
let transport;
|
|
@@ -4911,7 +5139,7 @@ async function disconnectFromUpstream(connection) {
|
|
|
4911
5139
|
}
|
|
4912
5140
|
async function fetchResourcesFromUpstream(client, namespace) {
|
|
4913
5141
|
try {
|
|
4914
|
-
const result = await client.listResources();
|
|
5142
|
+
const result = await client.listResources({}, { timeout: LIST_TIMEOUT });
|
|
4915
5143
|
const raw = result.resources ?? [];
|
|
4916
5144
|
if (raw.length > MAX_RESOURCES_PER_SERVER) {
|
|
4917
5145
|
log("warn", "Upstream returned more resources than cap; truncating", {
|
|
@@ -4933,7 +5161,7 @@ async function fetchResourcesFromUpstream(client, namespace) {
|
|
|
4933
5161
|
}
|
|
4934
5162
|
async function fetchPromptsFromUpstream(client, namespace) {
|
|
4935
5163
|
try {
|
|
4936
|
-
const result = await client.listPrompts();
|
|
5164
|
+
const result = await client.listPrompts({}, { timeout: LIST_TIMEOUT });
|
|
4937
5165
|
const raw = result.prompts ?? [];
|
|
4938
5166
|
if (raw.length > MAX_PROMPTS_PER_SERVER) {
|
|
4939
5167
|
log("warn", "Upstream returned more prompts than cap; truncating", {
|
|
@@ -4953,7 +5181,7 @@ async function fetchPromptsFromUpstream(client, namespace) {
|
|
|
4953
5181
|
}
|
|
4954
5182
|
}
|
|
4955
5183
|
async function fetchToolsFromUpstream(client, namespace) {
|
|
4956
|
-
const result = await client.listTools();
|
|
5184
|
+
const result = await client.listTools({}, { timeout: LIST_TIMEOUT });
|
|
4957
5185
|
const raw = result.tools ?? [];
|
|
4958
5186
|
if (raw.length > MAX_TOOLS_PER_SERVER) {
|
|
4959
5187
|
log("warn", "Upstream returned more tools than cap; truncating", {
|
|
@@ -4974,23 +5202,27 @@ async function fetchToolsFromUpstream(client, namespace) {
|
|
|
4974
5202
|
// src/test-runner.ts
|
|
4975
5203
|
var POLL_INTERVAL_MS = 3e4;
|
|
4976
5204
|
var REQUEST_TIMEOUT_MS = 1e4;
|
|
4977
|
-
var
|
|
4978
|
-
var
|
|
5205
|
+
var CONSECUTIVE_404_LIMIT = 10;
|
|
5206
|
+
var apiUrl5 = "";
|
|
5207
|
+
var token5 = "";
|
|
4979
5208
|
var pollTimer = null;
|
|
4980
5209
|
var running = false;
|
|
5210
|
+
var consecutive404 = 0;
|
|
4981
5211
|
var configRef = () => null;
|
|
4982
5212
|
function initTestRunner(url, tok, getConfig) {
|
|
4983
|
-
|
|
4984
|
-
|
|
5213
|
+
apiUrl5 = url;
|
|
5214
|
+
token5 = tok;
|
|
4985
5215
|
configRef = getConfig;
|
|
4986
5216
|
}
|
|
4987
5217
|
function startTestRunner() {
|
|
4988
5218
|
if (running) return;
|
|
4989
5219
|
running = true;
|
|
5220
|
+
consecutive404 = 0;
|
|
4990
5221
|
schedule();
|
|
4991
5222
|
}
|
|
4992
5223
|
function stopTestRunner() {
|
|
4993
5224
|
running = false;
|
|
5225
|
+
consecutive404 = 0;
|
|
4994
5226
|
if (pollTimer) {
|
|
4995
5227
|
clearTimeout(pollTimer);
|
|
4996
5228
|
pollTimer = null;
|
|
@@ -5008,7 +5240,7 @@ function schedule() {
|
|
|
5008
5240
|
pollTimer.unref?.();
|
|
5009
5241
|
}
|
|
5010
5242
|
async function pollOnce() {
|
|
5011
|
-
if (!
|
|
5243
|
+
if (!apiUrl5 || !token5) return;
|
|
5012
5244
|
const list = await fetchPending();
|
|
5013
5245
|
if (list.length === 0) return;
|
|
5014
5246
|
for (const pending2 of list) {
|
|
@@ -5020,23 +5252,34 @@ async function pollOnce() {
|
|
|
5020
5252
|
}
|
|
5021
5253
|
async function fetchPending() {
|
|
5022
5254
|
try {
|
|
5023
|
-
const res = await
|
|
5255
|
+
const res = await request8(`${apiUrl5.replace(/\/$/, "")}/api/connect/test-requests`, {
|
|
5024
5256
|
method: "GET",
|
|
5025
|
-
headers: { Authorization: `Bearer ${
|
|
5257
|
+
headers: { Authorization: `Bearer ${token5}` },
|
|
5026
5258
|
headersTimeout: REQUEST_TIMEOUT_MS,
|
|
5027
5259
|
bodyTimeout: REQUEST_TIMEOUT_MS
|
|
5028
5260
|
});
|
|
5029
5261
|
if (res.statusCode === 404) {
|
|
5030
5262
|
await res.body.text().catch(() => {
|
|
5031
5263
|
});
|
|
5032
|
-
|
|
5264
|
+
consecutive404++;
|
|
5265
|
+
if (consecutive404 === 1) {
|
|
5266
|
+
log("warn", "Test runner endpoint returned 404; will retry", { url: `${apiUrl5}/api/connect/test-requests` });
|
|
5267
|
+
}
|
|
5268
|
+
if (consecutive404 >= CONSECUTIVE_404_LIMIT) {
|
|
5269
|
+
log("warn", "Test runner endpoint persistently 404; stopping poller", {
|
|
5270
|
+
consecutive: consecutive404
|
|
5271
|
+
});
|
|
5272
|
+
stopTestRunner();
|
|
5273
|
+
}
|
|
5033
5274
|
return [];
|
|
5034
5275
|
}
|
|
5035
5276
|
if (res.statusCode !== 200) {
|
|
5036
5277
|
await res.body.text().catch(() => {
|
|
5037
5278
|
});
|
|
5279
|
+
consecutive404 = 0;
|
|
5038
5280
|
return [];
|
|
5039
5281
|
}
|
|
5282
|
+
consecutive404 = 0;
|
|
5040
5283
|
const body = await res.body.json();
|
|
5041
5284
|
return Array.isArray(body?.requests) ? body.requests : [];
|
|
5042
5285
|
} catch {
|
|
@@ -5093,12 +5336,12 @@ async function runOne(pending2) {
|
|
|
5093
5336
|
}
|
|
5094
5337
|
async function postResult(requestId, result) {
|
|
5095
5338
|
try {
|
|
5096
|
-
const res = await
|
|
5097
|
-
`${
|
|
5339
|
+
const res = await request8(
|
|
5340
|
+
`${apiUrl5.replace(/\/$/, "")}/api/connect/test-requests/${encodeURIComponent(requestId)}/result`,
|
|
5098
5341
|
{
|
|
5099
5342
|
method: "POST",
|
|
5100
5343
|
headers: {
|
|
5101
|
-
Authorization: `Bearer ${
|
|
5344
|
+
Authorization: `Bearer ${token5}`,
|
|
5102
5345
|
"Content-Type": "application/json"
|
|
5103
5346
|
},
|
|
5104
5347
|
body: JSON.stringify(result),
|
|
@@ -5113,37 +5356,6 @@ async function postResult(requestId, result) {
|
|
|
5113
5356
|
}
|
|
5114
5357
|
}
|
|
5115
5358
|
|
|
5116
|
-
// src/tool-report.ts
|
|
5117
|
-
import { request as request8 } from "undici";
|
|
5118
|
-
var apiUrl5 = "";
|
|
5119
|
-
var token5 = "";
|
|
5120
|
-
function initToolReport(url, tok) {
|
|
5121
|
-
apiUrl5 = url;
|
|
5122
|
-
token5 = tok;
|
|
5123
|
-
}
|
|
5124
|
-
async function reportTools(serverId, tools) {
|
|
5125
|
-
if (!apiUrl5 || !token5 || !serverId) return;
|
|
5126
|
-
try {
|
|
5127
|
-
const res = await request8(`${apiUrl5.replace(/\/$/, "")}/api/connect/servers/${serverId}/tools`, {
|
|
5128
|
-
method: "POST",
|
|
5129
|
-
headers: {
|
|
5130
|
-
Authorization: `Bearer ${token5}`,
|
|
5131
|
-
"Content-Type": "application/json"
|
|
5132
|
-
},
|
|
5133
|
-
body: JSON.stringify({ tools }),
|
|
5134
|
-
headersTimeout: 1e4,
|
|
5135
|
-
bodyTimeout: 1e4
|
|
5136
|
-
});
|
|
5137
|
-
await res.body.text().catch(() => {
|
|
5138
|
-
});
|
|
5139
|
-
if (res.statusCode >= 400 && res.statusCode !== 404) {
|
|
5140
|
-
log("warn", "Tool report failed", { serverId, status: res.statusCode });
|
|
5141
|
-
}
|
|
5142
|
-
} catch (err) {
|
|
5143
|
-
log("warn", "Tool report error", { serverId, error: err?.message });
|
|
5144
|
-
}
|
|
5145
|
-
}
|
|
5146
|
-
|
|
5147
5359
|
// src/server.ts
|
|
5148
5360
|
var DEFAULT_POLL_INTERVAL_MS = 6e4;
|
|
5149
5361
|
function resolvePollIntervalMs() {
|
|
@@ -5227,7 +5439,7 @@ var ConnectServer = class _ConnectServer {
|
|
|
5227
5439
|
this.apiUrl = apiUrl6;
|
|
5228
5440
|
this.token = token6;
|
|
5229
5441
|
this.server = new Server(
|
|
5230
|
-
{ name: "mcph", version: true ? "0.47.
|
|
5442
|
+
{ name: "mcph", version: true ? "0.47.2" : "dev" },
|
|
5231
5443
|
{
|
|
5232
5444
|
capabilities: {
|
|
5233
5445
|
tools: { listChanged: true },
|
|
@@ -5740,7 +5952,10 @@ var ConnectServer = class _ConnectServer {
|
|
|
5740
5952
|
if (serverConfig) {
|
|
5741
5953
|
let reconnected = false;
|
|
5742
5954
|
let lastErr;
|
|
5743
|
-
|
|
5955
|
+
const RECONNECT_ATTEMPTS = 2;
|
|
5956
|
+
const RECONNECT_DELAY_MS = 1e3;
|
|
5957
|
+
for (let attempt = 0; attempt < RECONNECT_ATTEMPTS; attempt++) {
|
|
5958
|
+
if (attempt > 0) await new Promise((r) => setTimeout(r, RECONNECT_DELAY_MS));
|
|
5744
5959
|
try {
|
|
5745
5960
|
await disconnectFromUpstream(conn);
|
|
5746
5961
|
const newConn = await connectToUpstream(
|
|
@@ -5756,23 +5971,23 @@ var ConnectServer = class _ConnectServer {
|
|
|
5756
5971
|
break;
|
|
5757
5972
|
} catch (err) {
|
|
5758
5973
|
lastErr = err;
|
|
5759
|
-
if (attempt
|
|
5974
|
+
if (attempt < RECONNECT_ATTEMPTS - 1) {
|
|
5760
5975
|
log("warn", "Auto-reconnect attempt failed, retrying", {
|
|
5761
5976
|
namespace: route.namespace,
|
|
5762
|
-
error: err.message
|
|
5977
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5763
5978
|
});
|
|
5764
|
-
await new Promise((r) => setTimeout(r, 1e3 * 2 ** attempt));
|
|
5765
5979
|
}
|
|
5766
5980
|
}
|
|
5767
5981
|
}
|
|
5768
5982
|
if (!reconnected) {
|
|
5769
5983
|
conn.status = "error";
|
|
5770
|
-
|
|
5984
|
+
const lastErrMsg = lastErr instanceof Error ? lastErr.message : String(lastErr);
|
|
5985
|
+
log("error", "Auto-reconnect failed", { namespace: route.namespace, error: lastErrMsg });
|
|
5771
5986
|
return {
|
|
5772
5987
|
content: [
|
|
5773
5988
|
{
|
|
5774
5989
|
type: "text",
|
|
5775
|
-
text: `Server "${route.namespace}" disconnected and auto-reconnect failed: ${
|
|
5990
|
+
text: `Server "${route.namespace}" disconnected and auto-reconnect failed: ${lastErrMsg}. Use mcp_connect_activate with server "${route.namespace}" to reload it manually.`
|
|
5776
5991
|
}
|
|
5777
5992
|
],
|
|
5778
5993
|
isError: true
|
|
@@ -5800,8 +6015,7 @@ var ConnectServer = class _ConnectServer {
|
|
|
5800
6015
|
toolName: route.originalName,
|
|
5801
6016
|
action: "tool_call",
|
|
5802
6017
|
latencyMs,
|
|
5803
|
-
success: !result.isError
|
|
5804
|
-
error: result.isError ? result.content[0]?.text : void 0
|
|
6018
|
+
success: !result.isError
|
|
5805
6019
|
});
|
|
5806
6020
|
if (!result.isError && Array.isArray(result.content)) {
|
|
5807
6021
|
try {
|
|
@@ -5848,9 +6062,11 @@ var ConnectServer = class _ConnectServer {
|
|
|
5848
6062
|
} catch {
|
|
5849
6063
|
}
|
|
5850
6064
|
}
|
|
6065
|
+
this.learning.recordDispatch(route.namespace);
|
|
6066
|
+
if (!result.isError) this.learning.recordSuccess(route.namespace);
|
|
6067
|
+
this.scheduleStateSave();
|
|
5851
6068
|
if (!result.isError) {
|
|
5852
6069
|
this.packDetector.recordCall(route.namespace, route.originalName, Date.now());
|
|
5853
|
-
this.scheduleStateSave();
|
|
5854
6070
|
}
|
|
5855
6071
|
await this.trackUsageAndAutoDeactivate(route.namespace);
|
|
5856
6072
|
}
|
|
@@ -5909,9 +6125,9 @@ var ConnectServer = class _ConnectServer {
|
|
|
5909
6125
|
const shortlist = bm25.slice(0, _ConnectServer.BM25_TOP_K);
|
|
5910
6126
|
const idByNamespace = new Map(servers.map((s) => [s.namespace, s.id]));
|
|
5911
6127
|
const candidateIds = shortlist.map((r) => idByNamespace.get(r.namespace)).filter((id) => typeof id === "string" && id.length > 0);
|
|
5912
|
-
if (candidateIds.length === 0) return shortlist;
|
|
6128
|
+
if (candidateIds.length === 0) return shortlist.map((r) => ({ ...r, hasRerank: false }));
|
|
5913
6129
|
const rerankResults = await rerank(context, candidateIds);
|
|
5914
|
-
if (!rerankResults) return shortlist;
|
|
6130
|
+
if (!rerankResults) return shortlist.map((r) => ({ ...r, hasRerank: false }));
|
|
5915
6131
|
const namespaceById = new Map(servers.map((s) => [s.id, s.namespace]));
|
|
5916
6132
|
const rerankScoreByNamespace = /* @__PURE__ */ new Map();
|
|
5917
6133
|
for (const r of rerankResults) {
|
|
@@ -5931,7 +6147,7 @@ var ConnectServer = class _ConnectServer {
|
|
|
5931
6147
|
if (a.hasRerank !== b.hasRerank) return a.hasRerank ? -1 : 1;
|
|
5932
6148
|
return b.score - a.score;
|
|
5933
6149
|
});
|
|
5934
|
-
return reordered
|
|
6150
|
+
return reordered;
|
|
5935
6151
|
}
|
|
5936
6152
|
// Auto-warm confidence gate — applied to discover(context) so a single
|
|
5937
6153
|
// clearly-winning server gets activated without the LLM needing to
|
|
@@ -5942,10 +6158,15 @@ var ConnectServer = class _ConnectServer {
|
|
|
5942
6158
|
return raw === void 0 || raw === "" || raw === "1" || raw.toLowerCase() === "true";
|
|
5943
6159
|
})();
|
|
5944
6160
|
// Top score must clear this floor AND the gap over the runner-up must
|
|
5945
|
-
// be convincing before we auto-activate. Values
|
|
5946
|
-
//
|
|
5947
|
-
|
|
5948
|
-
|
|
6161
|
+
// be convincing before we auto-activate. Values are scale-dependent --
|
|
6162
|
+
// BM25 scores are unbounded positive numbers, rerank cosines are in
|
|
6163
|
+
// [0, 1] -- so we keep separate thresholds and pick based on whether
|
|
6164
|
+
// the top entry was reranked. Tuned by intuition; revisit when we
|
|
6165
|
+
// have real usage data.
|
|
6166
|
+
static AUTO_ACTIVATE_MIN_SCORE_BM25 = 1;
|
|
6167
|
+
static AUTO_ACTIVATE_MARGIN_BM25 = 1.3;
|
|
6168
|
+
static AUTO_ACTIVATE_MIN_SCORE_COSINE = 0.5;
|
|
6169
|
+
static AUTO_ACTIVATE_MARGIN_COSINE = 1.25;
|
|
5949
6170
|
// Below this installed-server count, discover() appends a one-line
|
|
5950
6171
|
// marketplace pointer so sparse-config users see where to add more.
|
|
5951
6172
|
// At or above the threshold we stay silent — power users already know
|
|
@@ -5966,7 +6187,9 @@ var ConnectServer = class _ConnectServer {
|
|
|
5966
6187
|
if (ranked.length === 0) return this.handleDiscover(context);
|
|
5967
6188
|
const top = ranked[0];
|
|
5968
6189
|
const second = ranked[1];
|
|
5969
|
-
const
|
|
6190
|
+
const minScore = top?.hasRerank ? _ConnectServer.AUTO_ACTIVATE_MIN_SCORE_COSINE : _ConnectServer.AUTO_ACTIVATE_MIN_SCORE_BM25;
|
|
6191
|
+
const margin = top?.hasRerank ? _ConnectServer.AUTO_ACTIVATE_MARGIN_COSINE : _ConnectServer.AUTO_ACTIVATE_MARGIN_BM25;
|
|
6192
|
+
const topWinsDecisively = top !== void 0 && top.score >= minScore && (second === void 0 || top.score / (second.score || 1e-6) >= margin);
|
|
5970
6193
|
if (!topWinsDecisively || !top) return this.handleDiscover(context);
|
|
5971
6194
|
const existing = this.connections.get(top.namespace);
|
|
5972
6195
|
if (existing && existing.status === "connected") return this.handleDiscover(context);
|
|
@@ -6487,9 +6710,6 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
|
|
|
6487
6710
|
results.push(`${winner.namespace} (score ${winner.score.toFixed(2)}): ${r.message}`);
|
|
6488
6711
|
if (r.isChanged) anyChanged = true;
|
|
6489
6712
|
if (!r.ok) anyError = true;
|
|
6490
|
-
this.learning.recordDispatch(winner.namespace);
|
|
6491
|
-
if (r.ok) this.learning.recordSuccess(winner.namespace);
|
|
6492
|
-
this.scheduleStateSave();
|
|
6493
6713
|
}
|
|
6494
6714
|
if (anyChanged) {
|
|
6495
6715
|
this.rebuildRoutes();
|
|
@@ -7551,13 +7771,24 @@ async function runUpgrade(opts = {}) {
|
|
|
7551
7771
|
print("No upgrade command available for this install method.");
|
|
7552
7772
|
return { exitCode: 0, lines };
|
|
7553
7773
|
}
|
|
7774
|
+
const autoRunnable = method === "global-npm";
|
|
7554
7775
|
if (!opts.run) {
|
|
7555
|
-
|
|
7776
|
+
if (autoRunnable) {
|
|
7777
|
+
print(`Run:
|
|
7778
|
+
${plan.command}
|
|
7779
|
+
|
|
7780
|
+
Or re-run with --run to upgrade in place.`);
|
|
7781
|
+
} else {
|
|
7782
|
+
print(`Suggested command (run it yourself; --run only works for global-npm installs):
|
|
7556
7783
|
${plan.command}`);
|
|
7784
|
+
}
|
|
7557
7785
|
return { exitCode: 1, lines };
|
|
7558
7786
|
}
|
|
7559
|
-
if (
|
|
7560
|
-
printErr(
|
|
7787
|
+
if (!autoRunnable) {
|
|
7788
|
+
printErr(
|
|
7789
|
+
`mcph upgrade --run: install method "${method}" can't be upgraded automatically. Run manually:
|
|
7790
|
+
${plan.command}`
|
|
7791
|
+
);
|
|
7561
7792
|
return { exitCode: 2, lines };
|
|
7562
7793
|
}
|
|
7563
7794
|
const runner = opts.spawnImpl ?? defaultSpawn;
|
|
@@ -7572,7 +7803,7 @@ async function runUpgrade(opts = {}) {
|
|
|
7572
7803
|
return { exitCode: 3, lines };
|
|
7573
7804
|
}
|
|
7574
7805
|
function readCurrentVersion() {
|
|
7575
|
-
return true ? "0.47.
|
|
7806
|
+
return true ? "0.47.2" : "dev";
|
|
7576
7807
|
}
|
|
7577
7808
|
|
|
7578
7809
|
// src/index.ts
|
|
@@ -7601,7 +7832,8 @@ if (subcommand === "compliance") {
|
|
|
7601
7832
|
`);
|
|
7602
7833
|
process.exit(2);
|
|
7603
7834
|
}
|
|
7604
|
-
|
|
7835
|
+
const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR && process.env.CLAUDE_CONFIG_DIR.length > 0 ? process.env.CLAUDE_CONFIG_DIR : void 0;
|
|
7836
|
+
runInstall({ ...parsed.options, claudeConfigDir }).then((r) => process.exit(r.exitCode));
|
|
7605
7837
|
} else if (subcommand === "doctor") {
|
|
7606
7838
|
const doctorArgs = process.argv.slice(3);
|
|
7607
7839
|
const doctorJson = doctorArgs.includes("--json");
|
|
@@ -7619,6 +7851,17 @@ if (subcommand === "compliance") {
|
|
|
7619
7851
|
}
|
|
7620
7852
|
runDoctor({ json: doctorJson }).then((r) => process.exit(r.exitCode));
|
|
7621
7853
|
} else if (subcommand === "reset-learning") {
|
|
7854
|
+
const parsed = parseResetLearningArgs(process.argv.slice(3));
|
|
7855
|
+
if (parsed.kind === "help") {
|
|
7856
|
+
process.stdout.write(`${RESET_LEARNING_USAGE}
|
|
7857
|
+
`);
|
|
7858
|
+
process.exit(0);
|
|
7859
|
+
}
|
|
7860
|
+
if (parsed.kind === "error") {
|
|
7861
|
+
process.stderr.write(`${parsed.error}
|
|
7862
|
+
`);
|
|
7863
|
+
process.exit(2);
|
|
7864
|
+
}
|
|
7622
7865
|
runResetLearning().then((r) => process.exit(r.exitCode));
|
|
7623
7866
|
} else if (subcommand === "servers") {
|
|
7624
7867
|
const parsed = parseServersArgs(process.argv.slice(3));
|
|
@@ -7728,7 +7971,7 @@ if (subcommand === "compliance") {
|
|
|
7728
7971
|
`);
|
|
7729
7972
|
process.exit(0);
|
|
7730
7973
|
} else if (subcommand === "--version" || subcommand === "-V") {
|
|
7731
|
-
process.stdout.write(`mcph ${true ? "0.47.
|
|
7974
|
+
process.stdout.write(`mcph ${true ? "0.47.2" : "dev"}
|
|
7732
7975
|
`);
|
|
7733
7976
|
process.exit(0);
|
|
7734
7977
|
} else if (subcommand && !subcommand.startsWith("-")) {
|