@modelcontextprotocol/inspector 2.9.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/clients/cli/build/index.js +14165 -10590
- package/clients/mcpdo/build/chunk-2E5EZYNU.js +21371 -0
- package/clients/mcpdo/build/mcp-bin.js +3990 -0
- package/clients/mcpdo/build/mcpdod.js +38 -0
- package/clients/tui/build/index.js +4702 -2501
- package/clients/web/build/index.js +498 -86
- package/clients/web/dist/assets/index-DgDEc2gL.js +1589 -0
- package/clients/web/dist/assets/{prism-light-leP1dqtE.js → prism-light-B0A727v7.js} +1 -1
- package/clients/web/dist/index.html +1 -1
- package/package.json +40 -11
- package/scripts/install-clients.mjs +1 -1
- package/skills/mcpdo/SKILL.md +165 -0
- package/clients/web/dist/assets/index-DgXruQhz.js +0 -1598
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { resolve as
|
|
4
|
+
import { resolve as resolve9 } from "path";
|
|
5
5
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
6
6
|
|
|
7
7
|
// server/run-web.ts
|
|
8
|
-
import { resolve as
|
|
8
|
+
import { resolve as resolve8, join as join10, dirname as dirname9 } from "path";
|
|
9
9
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
10
10
|
import { Command } from "commander";
|
|
11
11
|
|
|
@@ -94,6 +94,7 @@ var DEFAULT_TASK_TTL_MS = 6e4;
|
|
|
94
94
|
var DEFAULT_CONNECTION_TIMEOUT_MS = 3e4;
|
|
95
95
|
var DEFAULT_MAX_FETCH_REQUESTS = 1e3;
|
|
96
96
|
var DEFAULT_PROTOCOL_ERA = "legacy";
|
|
97
|
+
var DEFAULT_ELICIT_CAPABILITY = "both";
|
|
97
98
|
var DEFAULT_MODERN_LOG_LEVEL = "debug";
|
|
98
99
|
var MODERN_LOG_LEVELS = [
|
|
99
100
|
"off",
|
|
@@ -285,6 +286,15 @@ var VALID_PROTOCOL_ERAS = /* @__PURE__ */ new Set([
|
|
|
285
286
|
"auto",
|
|
286
287
|
"modern"
|
|
287
288
|
]);
|
|
289
|
+
var VALID_ELICIT_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
290
|
+
"off",
|
|
291
|
+
"url",
|
|
292
|
+
"form",
|
|
293
|
+
"both"
|
|
294
|
+
]);
|
|
295
|
+
function isElicitCapability(value) {
|
|
296
|
+
return typeof value === "string" && VALID_ELICIT_CAPABILITIES.has(value);
|
|
297
|
+
}
|
|
288
298
|
function isProtocolEra(value) {
|
|
289
299
|
return typeof value === "string" && VALID_PROTOCOL_ERAS.has(value);
|
|
290
300
|
}
|
|
@@ -429,7 +439,7 @@ function cleanEndpointOverride(value, field) {
|
|
|
429
439
|
return value.trim() || void 0;
|
|
430
440
|
}
|
|
431
441
|
function storedFieldsToInspectorSettings(stored) {
|
|
432
|
-
const hasAny = stored.headers !== void 0 || stored.metadata !== void 0 || stored.connectionTimeout !== void 0 || stored.requestTimeout !== void 0 || stored.taskTtl !== void 0 || stored.autoRefreshOnListChanged !== void 0 || stored.paginatedLists !== void 0 || stored.suppressNotificationStream !== void 0 || stored.advertisedExtensions !== void 0 || stored.maxFetchRequests !== void 0 || stored.skillCatalogMaxSkills !== void 0 || stored.skillCatalogMaxBytes !== void 0 || stored.oauth !== void 0 || stored.roots !== void 0 || stored.protocolEra !== void 0 || stored.modernLogLevel !== void 0 || stored.env !== void 0 || stored.cwd !== void 0;
|
|
442
|
+
const hasAny = stored.headers !== void 0 || stored.metadata !== void 0 || stored.connectionTimeout !== void 0 || stored.requestTimeout !== void 0 || stored.taskTtl !== void 0 || stored.autoRefreshOnListChanged !== void 0 || stored.paginatedLists !== void 0 || stored.suppressNotificationStream !== void 0 || stored.advertisedExtensions !== void 0 || stored.maxFetchRequests !== void 0 || stored.skillCatalogMaxSkills !== void 0 || stored.skillCatalogMaxBytes !== void 0 || stored.oauth !== void 0 || stored.roots !== void 0 || stored.protocolEra !== void 0 || stored.elicitCapability !== void 0 || stored.modernLogLevel !== void 0 || stored.env !== void 0 || stored.cwd !== void 0;
|
|
433
443
|
if (!hasAny) return void 0;
|
|
434
444
|
const headersPairs = stored.headers ? Object.entries(stored.headers).map(([key, value]) => ({ key, value })) : [];
|
|
435
445
|
const settings = {
|
|
@@ -467,6 +477,9 @@ function storedFieldsToInspectorSettings(stored) {
|
|
|
467
477
|
if (isProtocolEra(stored.protocolEra)) {
|
|
468
478
|
settings.protocolEra = stored.protocolEra;
|
|
469
479
|
}
|
|
480
|
+
if (isElicitCapability(stored.elicitCapability)) {
|
|
481
|
+
settings.elicitCapability = stored.elicitCapability;
|
|
482
|
+
}
|
|
470
483
|
if (isModernLogLevel(stored.modernLogLevel)) {
|
|
471
484
|
settings.modernLogLevel = stored.modernLogLevel;
|
|
472
485
|
}
|
|
@@ -542,6 +555,9 @@ function inspectorSettingsToStoredFields(settings) {
|
|
|
542
555
|
if (settings.protocolEra !== void 0 && settings.protocolEra !== DEFAULT_PROTOCOL_ERA) {
|
|
543
556
|
out.protocolEra = settings.protocolEra;
|
|
544
557
|
}
|
|
558
|
+
if (settings.elicitCapability !== void 0 && settings.elicitCapability !== DEFAULT_ELICIT_CAPABILITY) {
|
|
559
|
+
out.elicitCapability = settings.elicitCapability;
|
|
560
|
+
}
|
|
545
561
|
if (settings.modernLogLevel !== void 0 && settings.modernLogLevel !== DEFAULT_MODERN_LOG_LEVEL) {
|
|
546
562
|
out.modernLogLevel = settings.modernLogLevel;
|
|
547
563
|
}
|
|
@@ -596,6 +612,7 @@ var INSPECTOR_FIELD_KEY_MAP = {
|
|
|
596
612
|
headers: true,
|
|
597
613
|
metadata: true,
|
|
598
614
|
protocolEra: true,
|
|
615
|
+
elicitCapability: true,
|
|
599
616
|
modernLogLevel: true,
|
|
600
617
|
connectionTimeout: true,
|
|
601
618
|
requestTimeout: true,
|
|
@@ -700,6 +717,19 @@ var DEFAULT_SEED_CONFIG = {
|
|
|
700
717
|
}
|
|
701
718
|
};
|
|
702
719
|
|
|
720
|
+
// ../../core/storage/own-entry.ts
|
|
721
|
+
function setOwnEntry(map, key, value) {
|
|
722
|
+
Object.defineProperty(map, key, {
|
|
723
|
+
value,
|
|
724
|
+
enumerable: true,
|
|
725
|
+
writable: true,
|
|
726
|
+
configurable: true
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
function getOwnEntry(map, key) {
|
|
730
|
+
return map !== void 0 && Object.hasOwn(map, key) ? map[key] : void 0;
|
|
731
|
+
}
|
|
732
|
+
|
|
703
733
|
// ../../core/mcp/node/config.ts
|
|
704
734
|
function parseKeyValuePair(value, previous = {}) {
|
|
705
735
|
const parts = value.split("=");
|
|
@@ -778,13 +808,14 @@ function loadMcpServersConfig(configPath, writable) {
|
|
|
778
808
|
}
|
|
779
809
|
function loadServerFromConfig(configPath, serverName, writable) {
|
|
780
810
|
const config = loadMcpServersConfig(configPath, writable);
|
|
781
|
-
|
|
811
|
+
const server = getOwnEntry(config.mcpServers, serverName);
|
|
812
|
+
if (!server) {
|
|
782
813
|
const available = Object.keys(config.mcpServers).join(", ");
|
|
783
814
|
throw new Error(
|
|
784
815
|
`Server '${serverName}' not found in config file. Available servers: ${available}`
|
|
785
816
|
);
|
|
786
817
|
}
|
|
787
|
-
return
|
|
818
|
+
return server;
|
|
788
819
|
}
|
|
789
820
|
function buildConfigFromOptions(options) {
|
|
790
821
|
const target = options.target ?? [];
|
|
@@ -922,7 +953,6 @@ function resolveServerConfigs(options, mode) {
|
|
|
922
953
|
}
|
|
923
954
|
|
|
924
955
|
// server/web-server-config.ts
|
|
925
|
-
import open from "open";
|
|
926
956
|
import pino from "pino";
|
|
927
957
|
|
|
928
958
|
// ../../core/mcp/remote/constants.ts
|
|
@@ -1014,6 +1044,32 @@ function readInspectorVersionSafe(callerUrl) {
|
|
|
1014
1044
|
}
|
|
1015
1045
|
}
|
|
1016
1046
|
|
|
1047
|
+
// ../../core/node/openUrl.ts
|
|
1048
|
+
import open from "open";
|
|
1049
|
+
var OPEN_URL_TIMEOUT_MS = 5e3;
|
|
1050
|
+
function launched(child) {
|
|
1051
|
+
return new Promise((resolve10, reject) => {
|
|
1052
|
+
child.on("error", reject);
|
|
1053
|
+
child.once("spawn", () => resolve10());
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
1057
|
+
const href = typeof url === "string" ? url : url.href;
|
|
1058
|
+
let timer;
|
|
1059
|
+
const timeout = new Promise((_, reject) => {
|
|
1060
|
+
timer = setTimeout(
|
|
1061
|
+
() => reject(new Error(`browser did not open within ${timeoutMs / 1e3}s`)),
|
|
1062
|
+
timeoutMs
|
|
1063
|
+
);
|
|
1064
|
+
});
|
|
1065
|
+
try {
|
|
1066
|
+
const opened = Promise.resolve().then(() => open(href)).then(launched);
|
|
1067
|
+
await Promise.race([opened, timeout]);
|
|
1068
|
+
} finally {
|
|
1069
|
+
clearTimeout(timer);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1017
1073
|
// server/sandbox-controller.ts
|
|
1018
1074
|
import { createServer } from "http";
|
|
1019
1075
|
import { readFileSync as readFileSync3 } from "fs";
|
|
@@ -1151,12 +1207,12 @@ function createSandboxController(options) {
|
|
|
1151
1207
|
if (server && sandboxUrl) {
|
|
1152
1208
|
return { port: boundPort, url: sandboxUrl };
|
|
1153
1209
|
}
|
|
1154
|
-
return new Promise((
|
|
1210
|
+
return new Promise((resolve10) => {
|
|
1155
1211
|
let settled = false;
|
|
1156
1212
|
const settle = (value) => {
|
|
1157
1213
|
if (settled) return;
|
|
1158
1214
|
settled = true;
|
|
1159
|
-
|
|
1215
|
+
resolve10(value);
|
|
1160
1216
|
};
|
|
1161
1217
|
server = createServer((req, res) => {
|
|
1162
1218
|
if (req.method !== "GET" || req.url !== "/sandbox" && req.url !== "/sandbox/") {
|
|
@@ -1218,11 +1274,11 @@ function createSandboxController(options) {
|
|
|
1218
1274
|
},
|
|
1219
1275
|
async close() {
|
|
1220
1276
|
if (!server) return;
|
|
1221
|
-
return new Promise((
|
|
1277
|
+
return new Promise((resolve10) => {
|
|
1222
1278
|
server.close(() => {
|
|
1223
1279
|
server = null;
|
|
1224
1280
|
sandboxUrl = null;
|
|
1225
|
-
|
|
1281
|
+
resolve10();
|
|
1226
1282
|
});
|
|
1227
1283
|
});
|
|
1228
1284
|
},
|
|
@@ -1300,12 +1356,12 @@ function createAppOriginController(options) {
|
|
|
1300
1356
|
if (server && origin) {
|
|
1301
1357
|
return { port: boundPort, url: origin };
|
|
1302
1358
|
}
|
|
1303
|
-
return new Promise((
|
|
1359
|
+
return new Promise((resolve10) => {
|
|
1304
1360
|
let settled = false;
|
|
1305
1361
|
const settle = (value) => {
|
|
1306
1362
|
if (settled) return;
|
|
1307
1363
|
settled = true;
|
|
1308
|
-
|
|
1364
|
+
resolve10(value);
|
|
1309
1365
|
};
|
|
1310
1366
|
server = createServer2((req, res) => {
|
|
1311
1367
|
const id = req.method === "GET" ? parseDocumentId(req.url) : null;
|
|
@@ -1389,11 +1445,11 @@ function createAppOriginController(options) {
|
|
|
1389
1445
|
documents.clear();
|
|
1390
1446
|
retainedChars = 0;
|
|
1391
1447
|
if (!server) return;
|
|
1392
|
-
return new Promise((
|
|
1448
|
+
return new Promise((resolve10) => {
|
|
1393
1449
|
server.close(() => {
|
|
1394
1450
|
server = null;
|
|
1395
1451
|
origin = null;
|
|
1396
|
-
|
|
1452
|
+
resolve10();
|
|
1397
1453
|
});
|
|
1398
1454
|
});
|
|
1399
1455
|
},
|
|
@@ -1626,7 +1682,7 @@ MCP Inspector Web is up and running at:
|
|
|
1626
1682
|
return url;
|
|
1627
1683
|
}
|
|
1628
1684
|
function openBrowser(url) {
|
|
1629
|
-
|
|
1685
|
+
openUrl(url).catch((err) => {
|
|
1630
1686
|
console.warn("Could not open a browser automatically:", err);
|
|
1631
1687
|
});
|
|
1632
1688
|
}
|
|
@@ -1743,7 +1799,7 @@ function resolveAutoOpen() {
|
|
|
1743
1799
|
}
|
|
1744
1800
|
|
|
1745
1801
|
// server/start-vite-dev-server.ts
|
|
1746
|
-
import { join as join7, dirname as dirname7, resolve as
|
|
1802
|
+
import { join as join7, dirname as dirname7, resolve as resolve7 } from "path";
|
|
1747
1803
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1748
1804
|
import { createServer as createServer3 } from "vite";
|
|
1749
1805
|
import react from "@vitejs/plugin-react";
|
|
@@ -1937,8 +1993,8 @@ async function readChallengeBody(response) {
|
|
|
1937
1993
|
let text = "";
|
|
1938
1994
|
let bytes = 0;
|
|
1939
1995
|
let timer;
|
|
1940
|
-
const deadline = new Promise((
|
|
1941
|
-
timer = setTimeout(() =>
|
|
1996
|
+
const deadline = new Promise((resolve10) => {
|
|
1997
|
+
timer = setTimeout(() => resolve10("timeout"), CHALLENGE_BODY_READ_MS);
|
|
1942
1998
|
});
|
|
1943
1999
|
try {
|
|
1944
2000
|
while (bytes < CHALLENGE_BODY_MAX_BYTES) {
|
|
@@ -2567,19 +2623,6 @@ function createTransportNode(config, options = {}) {
|
|
|
2567
2623
|
// ../../core/auth/requestTimeout.ts
|
|
2568
2624
|
var OAUTH_TIMEOUT_WIRE_CODE = "oauth_request_timeout";
|
|
2569
2625
|
|
|
2570
|
-
// ../../core/storage/own-entry.ts
|
|
2571
|
-
function setOwnEntry(map, key, value) {
|
|
2572
|
-
Object.defineProperty(map, key, {
|
|
2573
|
-
value,
|
|
2574
|
-
enumerable: true,
|
|
2575
|
-
writable: true,
|
|
2576
|
-
configurable: true
|
|
2577
|
-
});
|
|
2578
|
-
}
|
|
2579
|
-
function getOwnEntry(map, key) {
|
|
2580
|
-
return map !== void 0 && Object.hasOwn(map, key) ? map[key] : void 0;
|
|
2581
|
-
}
|
|
2582
|
-
|
|
2583
2626
|
// ../../core/auth/oauth-persist.ts
|
|
2584
2627
|
import { OAuthTokensSchema } from "@modelcontextprotocol/core";
|
|
2585
2628
|
var OAUTH_PERSIST_STORE_ID = "oauth";
|
|
@@ -3191,9 +3234,9 @@ async function openSecretFileLock(filePath) {
|
|
|
3191
3234
|
}
|
|
3192
3235
|
async function withSecretFileLock(filePath, fn) {
|
|
3193
3236
|
const release = await openSecretFileLock(filePath);
|
|
3194
|
-
if (release === null) return fn();
|
|
3237
|
+
if (release === null) return fn(false);
|
|
3195
3238
|
try {
|
|
3196
|
-
return await fn();
|
|
3239
|
+
return await fn(true);
|
|
3197
3240
|
} finally {
|
|
3198
3241
|
await release();
|
|
3199
3242
|
}
|
|
@@ -3210,10 +3253,10 @@ import * as crypto2 from "crypto";
|
|
|
3210
3253
|
import { readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
3211
3254
|
import * as fs3 from "fs/promises";
|
|
3212
3255
|
import * as path3 from "path";
|
|
3213
|
-
var scrypt2 = (passphrase, salt, keylen, options) => new Promise((
|
|
3256
|
+
var scrypt2 = (passphrase, salt, keylen, options) => new Promise((resolve10, reject) => {
|
|
3214
3257
|
crypto2.scrypt(passphrase, salt, keylen, options, (err, key) => {
|
|
3215
3258
|
if (err) reject(err);
|
|
3216
|
-
else
|
|
3259
|
+
else resolve10(key);
|
|
3217
3260
|
});
|
|
3218
3261
|
});
|
|
3219
3262
|
var FORMAT_VERSION = 1;
|
|
@@ -4031,9 +4074,13 @@ async function describeFileStore(store, filePath, kind, reason, detail) {
|
|
|
4031
4074
|
};
|
|
4032
4075
|
}
|
|
4033
4076
|
function chooseFallbackKind(opts) {
|
|
4077
|
+
if (opts.allowMemory === false) return "file";
|
|
4034
4078
|
return opts.container && !opts.mounted ? "memory" : "file";
|
|
4035
4079
|
}
|
|
4036
|
-
|
|
4080
|
+
var memoryFallbackAllowed = true;
|
|
4081
|
+
var secretStorageWarningsQuiet = false;
|
|
4082
|
+
function warnAboutSecretStorage(info, opts) {
|
|
4083
|
+
if (secretStorageWarningsQuiet && opts?.force !== true) return;
|
|
4037
4084
|
if (info.reason === "fallback") {
|
|
4038
4085
|
console.warn(
|
|
4039
4086
|
`
|
|
@@ -4070,7 +4117,8 @@ function resolveSecretStore() {
|
|
|
4070
4117
|
}
|
|
4071
4118
|
const kind = chooseFallbackKind({
|
|
4072
4119
|
container: isContainer(),
|
|
4073
|
-
mounted: isOnMountPoint(path4.dirname(defaultSecretFilePath()))
|
|
4120
|
+
mounted: isOnMountPoint(path4.dirname(defaultSecretFilePath())),
|
|
4121
|
+
allowMemory: memoryFallbackAllowed
|
|
4074
4122
|
});
|
|
4075
4123
|
const result = await buildStore(kind, "fallback", probe.detail);
|
|
4076
4124
|
warnAboutSecretStorage(result.info);
|
|
@@ -4277,8 +4325,15 @@ var DeferredSecretStore = class {
|
|
|
4277
4325
|
function defaultSecretStore() {
|
|
4278
4326
|
return new DeferredSecretStore();
|
|
4279
4327
|
}
|
|
4328
|
+
async function resolveConcreteSecretStore(store) {
|
|
4329
|
+
return store instanceof DeferredSecretStore ? (await resolveSecretStore()).store : store;
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4332
|
+
// ../../core/auth/node/oauth-namespace-ledger.ts
|
|
4333
|
+
import { resolve as resolve6 } from "path";
|
|
4280
4334
|
|
|
4281
4335
|
// ../../core/auth/node/oauth-secrets.ts
|
|
4336
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4282
4337
|
import { OAuthTokensSchema as OAuthTokensSchema2 } from "@modelcontextprotocol/core";
|
|
4283
4338
|
var PERSIST_TOKENS_ENV = "MCP_INSPECTOR_PERSIST_TOKENS";
|
|
4284
4339
|
var warnedPolicyValues = /* @__PURE__ */ new Set();
|
|
@@ -4294,8 +4349,14 @@ function getPersistTokensPolicy(env = process.env) {
|
|
|
4294
4349
|
}
|
|
4295
4350
|
return "all";
|
|
4296
4351
|
}
|
|
4297
|
-
var oauthSecretServerId = (serverUrl) => `oauth+${encodeURIComponent(serverUrl)}`;
|
|
4298
|
-
var oauthIdpSecretServerId = (issuer) => `oauth-idp+${encodeURIComponent(issuer)}`;
|
|
4352
|
+
var oauthSecretServerId = (serverUrl, namespace) => namespace === void 0 ? `oauth+${encodeURIComponent(serverUrl)}` : `oauth+${namespace}+${encodeURIComponent(serverUrl)}`;
|
|
4353
|
+
var oauthIdpSecretServerId = (issuer, namespace) => namespace === void 0 ? `oauth-idp+${encodeURIComponent(issuer)}` : `oauth-idp+${namespace}+${encodeURIComponent(issuer)}`;
|
|
4354
|
+
function isValidSecretsNamespace(value) {
|
|
4355
|
+
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value);
|
|
4356
|
+
}
|
|
4357
|
+
function newSecretsNamespace() {
|
|
4358
|
+
return randomUUID2();
|
|
4359
|
+
}
|
|
4299
4360
|
var issuerTokensField = (issuer) => `tokens:${issuer}`;
|
|
4300
4361
|
var issuerClientSecretField = (issuer) => `client-secret:${issuer}`;
|
|
4301
4362
|
var issuerRegistrationTokenField = (issuer) => `registration-token:${issuer}`;
|
|
@@ -4552,6 +4613,175 @@ function snapshotHasPlaintextSecrets(snapshot) {
|
|
|
4552
4613
|
return false;
|
|
4553
4614
|
}
|
|
4554
4615
|
|
|
4616
|
+
// ../../core/auth/node/oauth-namespace-ledger.ts
|
|
4617
|
+
var namespaceLedgerPath = (stateFilePath) => `${stateFilePath}.namespaces.json`;
|
|
4618
|
+
var KEYRING_LOCATION = "keyring";
|
|
4619
|
+
var FILE_LOCATION_PREFIX = "file:";
|
|
4620
|
+
async function secretStoreLocation(store) {
|
|
4621
|
+
const concrete = await resolveConcreteSecretStore(store);
|
|
4622
|
+
if (concrete instanceof KeyringSecretStore) return KEYRING_LOCATION;
|
|
4623
|
+
if (concrete instanceof FileSecretStore) {
|
|
4624
|
+
return `${FILE_LOCATION_PREFIX}${resolve6(concrete.filePath)}`;
|
|
4625
|
+
}
|
|
4626
|
+
return "memory";
|
|
4627
|
+
}
|
|
4628
|
+
function coveredLocations(location, byLocation) {
|
|
4629
|
+
return [...byLocation].filter(
|
|
4630
|
+
([recorded]) => recorded === location || location === KEYRING_LOCATION && recorded.startsWith(FILE_LOCATION_PREFIX)
|
|
4631
|
+
).map(([recorded, keys]) => ({ location: recorded, keys }));
|
|
4632
|
+
}
|
|
4633
|
+
var warnedLedgerFailures = /* @__PURE__ */ new Set();
|
|
4634
|
+
function warnLedgerFailure(what, error) {
|
|
4635
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
4636
|
+
const key = `${what}:${reason}`;
|
|
4637
|
+
if (warnedLedgerFailures.has(key)) return;
|
|
4638
|
+
warnedLedgerFailures.add(key);
|
|
4639
|
+
console.warn(
|
|
4640
|
+
`[mcp-inspector] Could not ${what} the OAuth secrets-namespace ledger (${reason}). OAuth state is unaffected, but secret-store entries left behind if this state file's namespace is ever lost (for example, after a save by an older Inspector) may not be cleaned up automatically.`
|
|
4641
|
+
);
|
|
4642
|
+
}
|
|
4643
|
+
function isRecord3(value) {
|
|
4644
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4645
|
+
}
|
|
4646
|
+
function stringsOf(value) {
|
|
4647
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
4648
|
+
}
|
|
4649
|
+
function parseLedger(raw) {
|
|
4650
|
+
const ledger = /* @__PURE__ */ new Map();
|
|
4651
|
+
if (raw === null) return ledger;
|
|
4652
|
+
let parsed;
|
|
4653
|
+
try {
|
|
4654
|
+
parsed = JSON.parse(raw);
|
|
4655
|
+
} catch {
|
|
4656
|
+
return ledger;
|
|
4657
|
+
}
|
|
4658
|
+
const namespaces = isRecord3(parsed) ? parsed.namespaces : void 0;
|
|
4659
|
+
if (!isRecord3(namespaces)) return ledger;
|
|
4660
|
+
for (const [namespace, locations] of Object.entries(namespaces)) {
|
|
4661
|
+
if (!isValidSecretsNamespace(namespace) || !isRecord3(locations)) continue;
|
|
4662
|
+
const byLocation = /* @__PURE__ */ new Map();
|
|
4663
|
+
for (const [location, keys] of Object.entries(locations)) {
|
|
4664
|
+
if (!isRecord3(keys)) continue;
|
|
4665
|
+
byLocation.set(location, {
|
|
4666
|
+
servers: new Set(stringsOf(keys.servers)),
|
|
4667
|
+
idpSessions: new Set(stringsOf(keys.idpSessions))
|
|
4668
|
+
});
|
|
4669
|
+
}
|
|
4670
|
+
if (byLocation.size > 0) ledger.set(namespace, byLocation);
|
|
4671
|
+
}
|
|
4672
|
+
return ledger;
|
|
4673
|
+
}
|
|
4674
|
+
function serializeLedger(ledger) {
|
|
4675
|
+
const namespaces = Object.fromEntries(
|
|
4676
|
+
[...ledger].map(([namespace, byLocation]) => [
|
|
4677
|
+
namespace,
|
|
4678
|
+
Object.fromEntries(
|
|
4679
|
+
[...byLocation].map(([location, keys]) => [
|
|
4680
|
+
location,
|
|
4681
|
+
{ servers: [...keys.servers], idpSessions: [...keys.idpSessions] }
|
|
4682
|
+
])
|
|
4683
|
+
)
|
|
4684
|
+
])
|
|
4685
|
+
);
|
|
4686
|
+
return serializeStore({ namespaces });
|
|
4687
|
+
}
|
|
4688
|
+
async function writeLedger(stateFilePath, ledger) {
|
|
4689
|
+
const path6 = namespaceLedgerPath(stateFilePath);
|
|
4690
|
+
if (ledger.size === 0) await deleteStoreFile(path6);
|
|
4691
|
+
else await writeStoreFile(path6, serializeLedger(ledger));
|
|
4692
|
+
}
|
|
4693
|
+
async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions) {
|
|
4694
|
+
try {
|
|
4695
|
+
const ledger = parseLedger(
|
|
4696
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
4697
|
+
);
|
|
4698
|
+
const location = await secretStoreLocation(secretStore);
|
|
4699
|
+
let byLocation = ledger.get(namespace);
|
|
4700
|
+
if (!byLocation) {
|
|
4701
|
+
byLocation = /* @__PURE__ */ new Map();
|
|
4702
|
+
ledger.set(namespace, byLocation);
|
|
4703
|
+
}
|
|
4704
|
+
let keys = byLocation.get(location);
|
|
4705
|
+
let changed = false;
|
|
4706
|
+
if (!keys) {
|
|
4707
|
+
keys = { servers: /* @__PURE__ */ new Set(), idpSessions: /* @__PURE__ */ new Set() };
|
|
4708
|
+
byLocation.set(location, keys);
|
|
4709
|
+
changed = true;
|
|
4710
|
+
}
|
|
4711
|
+
for (const url of servers) {
|
|
4712
|
+
if (keys.servers.has(url)) continue;
|
|
4713
|
+
keys.servers.add(url);
|
|
4714
|
+
changed = true;
|
|
4715
|
+
}
|
|
4716
|
+
for (const issuer of idpSessions) {
|
|
4717
|
+
if (keys.idpSessions.has(issuer)) continue;
|
|
4718
|
+
keys.idpSessions.add(issuer);
|
|
4719
|
+
changed = true;
|
|
4720
|
+
}
|
|
4721
|
+
if (changed) await writeLedger(stateFilePath, ledger);
|
|
4722
|
+
} catch (error) {
|
|
4723
|
+
warnLedgerFailure("update", error);
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
4727
|
+
let ledger;
|
|
4728
|
+
let location;
|
|
4729
|
+
try {
|
|
4730
|
+
ledger = parseLedger(
|
|
4731
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
4732
|
+
);
|
|
4733
|
+
location = await secretStoreLocation(secretStore);
|
|
4734
|
+
} catch (error) {
|
|
4735
|
+
warnLedgerFailure("read", error);
|
|
4736
|
+
return;
|
|
4737
|
+
}
|
|
4738
|
+
let changed = false;
|
|
4739
|
+
for (const [namespace, byLocation] of ledger) {
|
|
4740
|
+
if (namespace === current) continue;
|
|
4741
|
+
for (const { location: recorded, keys } of coveredLocations(
|
|
4742
|
+
location,
|
|
4743
|
+
byLocation
|
|
4744
|
+
)) {
|
|
4745
|
+
const purges = [
|
|
4746
|
+
...[...keys.servers].map(
|
|
4747
|
+
(url) => () => oauthSecretServerId(url, namespace)
|
|
4748
|
+
),
|
|
4749
|
+
...[...keys.idpSessions].map(
|
|
4750
|
+
(issuer) => () => oauthIdpSecretServerId(issuer, namespace)
|
|
4751
|
+
)
|
|
4752
|
+
];
|
|
4753
|
+
let purged = true;
|
|
4754
|
+
for (const idOf of purges) {
|
|
4755
|
+
try {
|
|
4756
|
+
await secretStore.deleteAllForServer(idOf());
|
|
4757
|
+
} catch (error) {
|
|
4758
|
+
purged = false;
|
|
4759
|
+
warnLedgerFailure("purge an orphaned namespace recorded in", error);
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
if (!purged || recorded !== location) continue;
|
|
4763
|
+
byLocation.delete(recorded);
|
|
4764
|
+
if (recorded.startsWith(FILE_LOCATION_PREFIX)) {
|
|
4765
|
+
const keyring = byLocation.get(KEYRING_LOCATION) ?? {
|
|
4766
|
+
servers: /* @__PURE__ */ new Set(),
|
|
4767
|
+
idpSessions: /* @__PURE__ */ new Set()
|
|
4768
|
+
};
|
|
4769
|
+
for (const url of keys.servers) keyring.servers.add(url);
|
|
4770
|
+
for (const issuer of keys.idpSessions) keyring.idpSessions.add(issuer);
|
|
4771
|
+
byLocation.set(KEYRING_LOCATION, keyring);
|
|
4772
|
+
}
|
|
4773
|
+
changed = true;
|
|
4774
|
+
}
|
|
4775
|
+
if (byLocation.size === 0) ledger.delete(namespace);
|
|
4776
|
+
}
|
|
4777
|
+
if (!changed) return;
|
|
4778
|
+
try {
|
|
4779
|
+
await writeLedger(stateFilePath, ledger);
|
|
4780
|
+
} catch (error) {
|
|
4781
|
+
warnLedgerFailure("update", error);
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
|
|
4555
4785
|
// ../../core/auth/node/oauth-persist-file.ts
|
|
4556
4786
|
var warnedStoreFailures = /* @__PURE__ */ new Set();
|
|
4557
4787
|
function warnStoreWriteFailure(error) {
|
|
@@ -4601,9 +4831,9 @@ function rethrowLockError(filePath, error, action = "save") {
|
|
|
4601
4831
|
async function withOAuthStateLock(filePath, action, body) {
|
|
4602
4832
|
let entered = false;
|
|
4603
4833
|
try {
|
|
4604
|
-
return await withSecretFileLock(filePath, async () => {
|
|
4834
|
+
return await withSecretFileLock(filePath, async (locked) => {
|
|
4605
4835
|
entered = true;
|
|
4606
|
-
return body();
|
|
4836
|
+
return body(locked);
|
|
4607
4837
|
});
|
|
4608
4838
|
} catch (error) {
|
|
4609
4839
|
if (!entered) rethrowLockError(filePath, error, action);
|
|
@@ -4618,13 +4848,97 @@ var OAuthStateFileUnrecognizedError = class extends Error {
|
|
|
4618
4848
|
this.name = "OAuthStateFileUnrecognizedError";
|
|
4619
4849
|
}
|
|
4620
4850
|
};
|
|
4621
|
-
|
|
4851
|
+
var SECRETS_NAMESPACE_KEY = "secretsNamespace";
|
|
4852
|
+
function parseSecretsNamespace(raw) {
|
|
4853
|
+
if (raw === null) return void 0;
|
|
4854
|
+
try {
|
|
4855
|
+
const parsed = JSON.parse(raw);
|
|
4856
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
4857
|
+
const value = parsed[SECRETS_NAMESPACE_KEY];
|
|
4858
|
+
return isValidSecretsNamespace(value) ? value : void 0;
|
|
4859
|
+
} catch {
|
|
4860
|
+
return void 0;
|
|
4861
|
+
}
|
|
4862
|
+
}
|
|
4863
|
+
function serializeOAuthFileBlob(snapshot, namespace) {
|
|
4864
|
+
if (namespace === void 0) return serializeOAuthPersistBlob(snapshot);
|
|
4865
|
+
return serializeStore({ [SECRETS_NAMESPACE_KEY]: namespace, ...snapshot });
|
|
4866
|
+
}
|
|
4867
|
+
function warnNamespaceCleanupFailure(error) {
|
|
4868
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
4869
|
+
const key = `namespace-cleanup:${reason}`;
|
|
4870
|
+
if (warnedStoreFailures.has(key)) return;
|
|
4871
|
+
warnedStoreFailures.add(key);
|
|
4872
|
+
console.warn(
|
|
4873
|
+
`[mcp-inspector] Could not remove legacy un-namespaced secret-store entries after scoping them to this state file (${reason}). This file uses the scoped copies, but another pre-namespace state file could still read the stale leftovers until it adopts or re-authorizes \u2014 and they will not be cleaned up automatically.`
|
|
4874
|
+
);
|
|
4875
|
+
}
|
|
4876
|
+
async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
4622
4877
|
const raw = await readStoreFile(filePath);
|
|
4623
|
-
const
|
|
4624
|
-
if (
|
|
4625
|
-
|
|
4878
|
+
const existing = parseSecretsNamespace(raw);
|
|
4879
|
+
if (existing !== void 0) {
|
|
4880
|
+
if (locked) {
|
|
4881
|
+
await purgeSupersededNamespaces(filePath, secretStore, existing);
|
|
4882
|
+
}
|
|
4883
|
+
return existing;
|
|
4884
|
+
}
|
|
4885
|
+
const snapshot = parseOAuthPersistBlob(raw);
|
|
4886
|
+
if (raw !== null && snapshot === null) {
|
|
4887
|
+
throw new OAuthStateFileUnrecognizedError(filePath, "save");
|
|
4888
|
+
}
|
|
4889
|
+
if (locked) {
|
|
4890
|
+
await purgeSupersededNamespaces(filePath, secretStore, void 0);
|
|
4891
|
+
}
|
|
4892
|
+
const namespace = newSecretsNamespace();
|
|
4893
|
+
if (snapshot === null) return namespace;
|
|
4894
|
+
const moves = [
|
|
4895
|
+
...Object.entries(snapshot.servers).map(([url, state]) => ({
|
|
4896
|
+
legacyId: oauthSecretServerId(url),
|
|
4897
|
+
scopedId: oauthSecretServerId(url, namespace),
|
|
4898
|
+
fields: serverSecretFields(state)
|
|
4899
|
+
})),
|
|
4900
|
+
...Object.keys(snapshot.idpSessions).map((issuer) => ({
|
|
4901
|
+
legacyId: oauthIdpSecretServerId(issuer),
|
|
4902
|
+
scopedId: oauthIdpSecretServerId(issuer, namespace),
|
|
4903
|
+
fields: [IDP_SESSION_FIELD]
|
|
4904
|
+
}))
|
|
4905
|
+
];
|
|
4906
|
+
if (moves.length === 0) return namespace;
|
|
4907
|
+
if (!locked) {
|
|
4908
|
+
throw new SecretStoreUnavailableError(
|
|
4909
|
+
`Could not save OAuth state: ${filePath} predates per-state-file secret namespaces, and migrating its secret-store entries needs the file lock, which is unavailable here (see the lock warning above). Migrating without it could lose credentials if two processes migrate at once. Nothing was changed; make the lock directory writable and retry \u2014 or, if you cannot, clear this file's stored OAuth state and re-authorize (clearing does not migrate, and the fresh state file mints its namespace without the lock).`
|
|
4910
|
+
);
|
|
4626
4911
|
}
|
|
4627
|
-
|
|
4912
|
+
await recordNamespaceKeys(
|
|
4913
|
+
filePath,
|
|
4914
|
+
secretStore,
|
|
4915
|
+
namespace,
|
|
4916
|
+
Object.keys(snapshot.servers),
|
|
4917
|
+
Object.keys(snapshot.idpSessions)
|
|
4918
|
+
);
|
|
4919
|
+
const touched = [];
|
|
4920
|
+
try {
|
|
4921
|
+
for (const { legacyId, scopedId, fields } of moves) {
|
|
4922
|
+
for (const field of fields) {
|
|
4923
|
+
const value = await secretStoreGetStrict(secretStore, legacyId, field);
|
|
4924
|
+
if (value === null) continue;
|
|
4925
|
+
touched.push({ serverId: scopedId, field, value: null });
|
|
4926
|
+
await secretStore.set(scopedId, field, value);
|
|
4927
|
+
}
|
|
4928
|
+
}
|
|
4929
|
+
await writeStoreFile(filePath, serializeOAuthFileBlob(snapshot, namespace));
|
|
4930
|
+
} catch (error) {
|
|
4931
|
+
await restoreSecretFields(secretStore, touched, warnRestoreFailure);
|
|
4932
|
+
throw error;
|
|
4933
|
+
}
|
|
4934
|
+
for (const { legacyId } of moves) {
|
|
4935
|
+
try {
|
|
4936
|
+
await secretStore.deleteAllForServer(legacyId);
|
|
4937
|
+
} catch (error) {
|
|
4938
|
+
warnNamespaceCleanupFailure(error);
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
return namespace;
|
|
4628
4942
|
}
|
|
4629
4943
|
async function persistEntrySecrets(store, serverId, candidates, secrets, prior, allowDegrade) {
|
|
4630
4944
|
const priorValue = new Map(prior.map(({ field, value }) => [field, value]));
|
|
@@ -4672,7 +4986,8 @@ function preserveNonDurableSecrets(diskSecrets, secrets) {
|
|
|
4672
4986
|
async function writeOAuthSections(filePath, snapshot, sections, secretStore = defaultSecretStore()) {
|
|
4673
4987
|
const policy = getPersistTokensPolicy();
|
|
4674
4988
|
const durable = await secretStoreIsDurable(secretStore);
|
|
4675
|
-
await withOAuthStateLock(filePath, "save", async () => {
|
|
4989
|
+
await withOAuthStateLock(filePath, "save", async (locked) => {
|
|
4990
|
+
let namespace = await adoptSecretsNamespace(filePath, secretStore, locked);
|
|
4676
4991
|
const restoreBaseline = /* @__PURE__ */ new Map();
|
|
4677
4992
|
const ourWrites = /* @__PURE__ */ new Map();
|
|
4678
4993
|
const priorKey = (p) => `${p.serverId}\0${p.field}`;
|
|
@@ -4731,7 +5046,27 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
4731
5046
|
const attemptWrites = /* @__PURE__ */ new Map();
|
|
4732
5047
|
let written;
|
|
4733
5048
|
try {
|
|
4734
|
-
const
|
|
5049
|
+
const rawDisk = await readStoreFile(filePath);
|
|
5050
|
+
const disk = parseOAuthPersistBlob(rawDisk);
|
|
5051
|
+
if (rawDisk !== null && disk === null) {
|
|
5052
|
+
throw new OAuthStateFileUnrecognizedError(filePath, "save");
|
|
5053
|
+
}
|
|
5054
|
+
const diskNamespace = parseSecretsNamespace(rawDisk);
|
|
5055
|
+
if (diskNamespace !== void 0 && diskNamespace !== namespace) {
|
|
5056
|
+
let restoreFailure;
|
|
5057
|
+
await restoreSecretFields(
|
|
5058
|
+
secretStore,
|
|
5059
|
+
[...restoreBaseline.values()],
|
|
5060
|
+
(error) => {
|
|
5061
|
+
restoreFailure ??= error;
|
|
5062
|
+
}
|
|
5063
|
+
);
|
|
5064
|
+
if (restoreFailure !== void 0) throw restoreFailure;
|
|
5065
|
+
restoreBaseline.clear();
|
|
5066
|
+
ourWrites.clear();
|
|
5067
|
+
unconfirmed = null;
|
|
5068
|
+
namespace = diskNamespace;
|
|
5069
|
+
}
|
|
4735
5070
|
const effective = sections ? {
|
|
4736
5071
|
servers: sections.servers && [...new Set(sections.servers)],
|
|
4737
5072
|
idpSessions: sections.idpSessions && [
|
|
@@ -4752,8 +5087,15 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
4752
5087
|
]
|
|
4753
5088
|
};
|
|
4754
5089
|
const merged = mergeOAuthSections(disk, snapshot, effective);
|
|
5090
|
+
await recordNamespaceKeys(
|
|
5091
|
+
filePath,
|
|
5092
|
+
secretStore,
|
|
5093
|
+
namespace,
|
|
5094
|
+
Object.keys(merged.servers),
|
|
5095
|
+
Object.keys(merged.idpSessions)
|
|
5096
|
+
);
|
|
4755
5097
|
for (const url of effective.servers ?? []) {
|
|
4756
|
-
const serverId = oauthSecretServerId(url);
|
|
5098
|
+
const serverId = oauthSecretServerId(url, namespace);
|
|
4757
5099
|
const next = getOwnEntry(snapshot.servers, url);
|
|
4758
5100
|
const candidates = [
|
|
4759
5101
|
.../* @__PURE__ */ new Set([
|
|
@@ -4828,7 +5170,7 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
4828
5170
|
}
|
|
4829
5171
|
}
|
|
4830
5172
|
for (const issuer of effective.idpSessions ?? []) {
|
|
4831
|
-
const serverId = oauthIdpSecretServerId(issuer);
|
|
5173
|
+
const serverId = oauthIdpSecretServerId(issuer, namespace);
|
|
4832
5174
|
const next = getOwnEntry(snapshot.idpSessions, issuer);
|
|
4833
5175
|
const entryPrior = await snapshotSecretFields(secretStore, serverId, [
|
|
4834
5176
|
IDP_SESSION_FIELD
|
|
@@ -4889,7 +5231,7 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
4889
5231
|
value: secrets[IDP_SESSION_FIELD] ?? null
|
|
4890
5232
|
});
|
|
4891
5233
|
}
|
|
4892
|
-
written =
|
|
5234
|
+
written = serializeOAuthFileBlob(merged, namespace);
|
|
4893
5235
|
await writeStoreFile(filePath, written);
|
|
4894
5236
|
unconfirmed = { written, writes: attemptWrites };
|
|
4895
5237
|
} catch (error) {
|
|
@@ -4913,36 +5255,42 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
4913
5255
|
);
|
|
4914
5256
|
});
|
|
4915
5257
|
}
|
|
4916
|
-
function secretRequestsFor(snapshot) {
|
|
5258
|
+
function secretRequestsFor(snapshot, namespace) {
|
|
4917
5259
|
const requests = [];
|
|
4918
5260
|
for (const [url, state] of Object.entries(snapshot.servers)) {
|
|
4919
5261
|
requests.push({
|
|
4920
|
-
serverId: oauthSecretServerId(url),
|
|
5262
|
+
serverId: oauthSecretServerId(url, namespace),
|
|
4921
5263
|
fields: serverSecretFields(state)
|
|
4922
5264
|
});
|
|
4923
5265
|
}
|
|
4924
5266
|
for (const issuer of Object.keys(snapshot.idpSessions)) {
|
|
4925
5267
|
requests.push({
|
|
4926
|
-
serverId: oauthIdpSecretServerId(issuer),
|
|
5268
|
+
serverId: oauthIdpSecretServerId(issuer, namespace),
|
|
4927
5269
|
fields: [IDP_SESSION_FIELD]
|
|
4928
5270
|
});
|
|
4929
5271
|
}
|
|
4930
5272
|
return requests;
|
|
4931
5273
|
}
|
|
4932
|
-
async function joinSnapshot(snapshot, secretStore) {
|
|
4933
|
-
const requests = secretRequestsFor(snapshot);
|
|
5274
|
+
async function joinSnapshot(snapshot, secretStore, namespace) {
|
|
5275
|
+
const requests = secretRequestsFor(snapshot, namespace);
|
|
4934
5276
|
const values = requests.length > 0 ? await secretStoreGetManyStrict(secretStore, requests) : {};
|
|
4935
5277
|
return {
|
|
4936
5278
|
servers: Object.fromEntries(
|
|
4937
5279
|
Object.entries(snapshot.servers).map(([url, state]) => [
|
|
4938
5280
|
url,
|
|
4939
|
-
joinServerOAuthState(
|
|
5281
|
+
joinServerOAuthState(
|
|
5282
|
+
state,
|
|
5283
|
+
values[oauthSecretServerId(url, namespace)] ?? {}
|
|
5284
|
+
)
|
|
4940
5285
|
])
|
|
4941
5286
|
),
|
|
4942
5287
|
idpSessions: Object.fromEntries(
|
|
4943
5288
|
Object.entries(snapshot.idpSessions).map(([issuer, session]) => [
|
|
4944
5289
|
issuer,
|
|
4945
|
-
joinIdpSession(
|
|
5290
|
+
joinIdpSession(
|
|
5291
|
+
session,
|
|
5292
|
+
values[oauthIdpSecretServerId(issuer, namespace)] ?? {}
|
|
5293
|
+
)
|
|
4946
5294
|
])
|
|
4947
5295
|
)
|
|
4948
5296
|
};
|
|
@@ -4951,6 +5299,16 @@ async function migratePlaintextSecrets(filePath, secretStore) {
|
|
|
4951
5299
|
const raw = await readStoreFile(filePath);
|
|
4952
5300
|
const fresh = parseOAuthPersistBlob(raw);
|
|
4953
5301
|
if (!fresh || !snapshotHasPlaintextSecrets(fresh)) return;
|
|
5302
|
+
const namespace = parseSecretsNamespace(raw);
|
|
5303
|
+
if (namespace !== void 0) {
|
|
5304
|
+
await recordNamespaceKeys(
|
|
5305
|
+
filePath,
|
|
5306
|
+
secretStore,
|
|
5307
|
+
namespace,
|
|
5308
|
+
Object.keys(fresh.servers),
|
|
5309
|
+
Object.keys(fresh.idpSessions)
|
|
5310
|
+
);
|
|
5311
|
+
}
|
|
4954
5312
|
const migrateEntrySecrets = async (serverId, secrets) => {
|
|
4955
5313
|
const absent = {};
|
|
4956
5314
|
for (const [field, value] of Object.entries(secrets)) {
|
|
@@ -4967,44 +5325,57 @@ async function migratePlaintextSecrets(filePath, secretStore) {
|
|
|
4967
5325
|
for (const [url, state] of Object.entries(fresh.servers)) {
|
|
4968
5326
|
const split = splitServerOAuthState(state, "all");
|
|
4969
5327
|
setOwnEntry(residue.servers, url, split.residue);
|
|
4970
|
-
await migrateEntrySecrets(
|
|
5328
|
+
await migrateEntrySecrets(
|
|
5329
|
+
oauthSecretServerId(url, namespace),
|
|
5330
|
+
split.secrets
|
|
5331
|
+
);
|
|
4971
5332
|
}
|
|
4972
5333
|
for (const [issuer, session] of Object.entries(fresh.idpSessions)) {
|
|
4973
5334
|
const split = splitIdpSession(session, "all");
|
|
4974
5335
|
setOwnEntry(residue.idpSessions, issuer, split.residue);
|
|
4975
|
-
await migrateEntrySecrets(
|
|
5336
|
+
await migrateEntrySecrets(
|
|
5337
|
+
oauthIdpSecretServerId(issuer, namespace),
|
|
5338
|
+
split.secrets
|
|
5339
|
+
);
|
|
4976
5340
|
}
|
|
4977
|
-
const stripped =
|
|
5341
|
+
const stripped = serializeOAuthFileBlob(residue, namespace);
|
|
4978
5342
|
if (stripped !== raw) {
|
|
4979
5343
|
await writeStoreFile(filePath, stripped);
|
|
4980
5344
|
}
|
|
4981
5345
|
}
|
|
4982
5346
|
async function readOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
4983
5347
|
return withOAuthStateLock(filePath, "read", async () => {
|
|
4984
|
-
let
|
|
5348
|
+
let raw = await readStoreFile(filePath);
|
|
5349
|
+
let snapshot = parseOAuthPersistBlob(raw);
|
|
4985
5350
|
if (snapshot === null) return null;
|
|
4986
5351
|
if (snapshotHasPlaintextSecrets(snapshot) && await secretStoreIsDurable(secretStore)) {
|
|
4987
5352
|
try {
|
|
4988
5353
|
await migratePlaintextSecrets(filePath, secretStore);
|
|
4989
|
-
|
|
5354
|
+
raw = await readStoreFile(filePath);
|
|
5355
|
+
snapshot = parseOAuthPersistBlob(raw) ?? snapshot;
|
|
4990
5356
|
} catch (error) {
|
|
4991
5357
|
warnMigrationFailure(error);
|
|
4992
5358
|
}
|
|
4993
5359
|
}
|
|
4994
|
-
return joinSnapshot(snapshot, secretStore);
|
|
5360
|
+
return joinSnapshot(snapshot, secretStore, parseSecretsNamespace(raw));
|
|
4995
5361
|
});
|
|
4996
5362
|
}
|
|
4997
5363
|
async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
4998
|
-
await withOAuthStateLock(filePath, "remove", async () => {
|
|
4999
|
-
const
|
|
5364
|
+
await withOAuthStateLock(filePath, "remove", async (locked) => {
|
|
5365
|
+
const rawBlob = await readStoreFile(filePath);
|
|
5366
|
+
const snapshot = parseOAuthPersistBlob(rawBlob);
|
|
5367
|
+
if (rawBlob !== null && snapshot === null) {
|
|
5368
|
+
throw new OAuthStateFileUnrecognizedError(filePath, "remove");
|
|
5369
|
+
}
|
|
5000
5370
|
if (snapshot) {
|
|
5371
|
+
const namespace = parseSecretsNamespace(rawBlob);
|
|
5001
5372
|
const targets = [
|
|
5002
5373
|
...Object.entries(snapshot.servers).map(([url, state]) => ({
|
|
5003
|
-
id: oauthSecretServerId(url),
|
|
5374
|
+
id: oauthSecretServerId(url, namespace),
|
|
5004
5375
|
fields: serverSecretFields(state)
|
|
5005
5376
|
})),
|
|
5006
5377
|
...Object.keys(snapshot.idpSessions).map((issuer) => ({
|
|
5007
|
-
id: oauthIdpSecretServerId(issuer),
|
|
5378
|
+
id: oauthIdpSecretServerId(issuer, namespace),
|
|
5008
5379
|
fields: [IDP_SESSION_FIELD]
|
|
5009
5380
|
}))
|
|
5010
5381
|
];
|
|
@@ -5028,6 +5399,9 @@ async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
|
5028
5399
|
} else {
|
|
5029
5400
|
await deleteStoreFile(filePath);
|
|
5030
5401
|
}
|
|
5402
|
+
if (locked) {
|
|
5403
|
+
await purgeSupersededNamespaces(filePath, secretStore, void 0);
|
|
5404
|
+
}
|
|
5031
5405
|
});
|
|
5032
5406
|
}
|
|
5033
5407
|
|
|
@@ -5323,7 +5697,7 @@ var RemoteSession = class _RemoteSession {
|
|
|
5323
5697
|
* `auth_challenge` or `ok: true` only after the MCP round-trip (or auth error).
|
|
5324
5698
|
*/
|
|
5325
5699
|
waitForRequestResponse(requestId, timeoutMs = 6e4) {
|
|
5326
|
-
return new Promise((
|
|
5700
|
+
return new Promise((resolve10, reject) => {
|
|
5327
5701
|
let timer;
|
|
5328
5702
|
const arm = () => {
|
|
5329
5703
|
if (timeoutMs <= 0) return;
|
|
@@ -5346,7 +5720,7 @@ var RemoteSession = class _RemoteSession {
|
|
|
5346
5720
|
this.requestWaits.set(requestId, {
|
|
5347
5721
|
resolve: () => {
|
|
5348
5722
|
clear();
|
|
5349
|
-
|
|
5723
|
+
resolve10();
|
|
5350
5724
|
},
|
|
5351
5725
|
reject: (error) => {
|
|
5352
5726
|
clear();
|
|
@@ -5869,13 +6243,13 @@ function closeAbortedStream(stream) {
|
|
|
5869
6243
|
}
|
|
5870
6244
|
async function primeAndHoldSseStream(stream, cleanup) {
|
|
5871
6245
|
let done = false;
|
|
5872
|
-
const aborted = new Promise((
|
|
6246
|
+
const aborted = new Promise((resolve10) => {
|
|
5873
6247
|
stream.onAbort(() => {
|
|
5874
6248
|
if (done) return;
|
|
5875
6249
|
done = true;
|
|
5876
6250
|
cleanup();
|
|
5877
6251
|
closeAbortedStream(stream);
|
|
5878
|
-
|
|
6252
|
+
resolve10();
|
|
5879
6253
|
});
|
|
5880
6254
|
});
|
|
5881
6255
|
await stream.write(SSE_PRIMING_COMMENT);
|
|
@@ -6364,7 +6738,7 @@ function createRemoteApp(options) {
|
|
|
6364
6738
|
});
|
|
6365
6739
|
});
|
|
6366
6740
|
if (session.isTransportDead()) {
|
|
6367
|
-
await new Promise((
|
|
6741
|
+
await new Promise((resolve10) => setTimeout(resolve10, 0));
|
|
6368
6742
|
session.clearEventConsumer();
|
|
6369
6743
|
sessions.delete(sessionId);
|
|
6370
6744
|
return;
|
|
@@ -7664,6 +8038,40 @@ function injectAuthToken(html, token) {
|
|
|
7664
8038
|
return script + html;
|
|
7665
8039
|
}
|
|
7666
8040
|
|
|
8041
|
+
// server/health.ts
|
|
8042
|
+
var HEALTH_PATH = "/healthz";
|
|
8043
|
+
var HEALTH_BODY = Object.freeze({
|
|
8044
|
+
status: "ok"
|
|
8045
|
+
});
|
|
8046
|
+
var HEALTH_HEADERS = Object.freeze({
|
|
8047
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
8048
|
+
"Cache-Control": "no-store"
|
|
8049
|
+
});
|
|
8050
|
+
var HEALTH_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
8051
|
+
function isHealthRequest(method, url) {
|
|
8052
|
+
if (!method || !HEALTH_METHODS.has(method.toUpperCase())) return false;
|
|
8053
|
+
const path6 = (url ?? "").split(/[?#]/, 1)[0];
|
|
8054
|
+
return path6 === HEALTH_PATH;
|
|
8055
|
+
}
|
|
8056
|
+
function healthBodyFor(method) {
|
|
8057
|
+
return method.toUpperCase() === "HEAD" ? null : JSON.stringify(HEALTH_BODY);
|
|
8058
|
+
}
|
|
8059
|
+
function healthResponse(method = "GET") {
|
|
8060
|
+
return new Response(healthBodyFor(method), {
|
|
8061
|
+
status: 200,
|
|
8062
|
+
headers: { ...HEALTH_HEADERS }
|
|
8063
|
+
});
|
|
8064
|
+
}
|
|
8065
|
+
function handleNodeHealthRequest(req, res) {
|
|
8066
|
+
const { method } = req;
|
|
8067
|
+
if (method === void 0 || !isHealthRequest(method, req.url)) return false;
|
|
8068
|
+
res.writeHead(200, { ...HEALTH_HEADERS });
|
|
8069
|
+
const body = healthBodyFor(method);
|
|
8070
|
+
if (body === null) res.end();
|
|
8071
|
+
else res.end(body);
|
|
8072
|
+
return true;
|
|
8073
|
+
}
|
|
8074
|
+
|
|
7667
8075
|
// server/vite-hono-plugin.ts
|
|
7668
8076
|
function honoMiddlewarePlugin(config) {
|
|
7669
8077
|
let resolvedAuthToken = "";
|
|
@@ -7765,6 +8173,9 @@ function honoMiddlewarePlugin(config) {
|
|
|
7765
8173
|
const honoMiddleware = async (req, res, next) => {
|
|
7766
8174
|
try {
|
|
7767
8175
|
const pathname = req.url || "";
|
|
8176
|
+
if (handleNodeHealthRequest(req, res)) {
|
|
8177
|
+
return;
|
|
8178
|
+
}
|
|
7768
8179
|
if (!pathname.startsWith("/api")) {
|
|
7769
8180
|
return next();
|
|
7770
8181
|
}
|
|
@@ -7784,10 +8195,10 @@ function honoMiddlewarePlugin(config) {
|
|
|
7784
8195
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
7785
8196
|
const chunks = [];
|
|
7786
8197
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
7787
|
-
await new Promise((
|
|
7788
|
-
req.once("end", () =>
|
|
7789
|
-
req.once("error", () =>
|
|
7790
|
-
req.once("close", () =>
|
|
8198
|
+
await new Promise((resolve10) => {
|
|
8199
|
+
req.once("end", () => resolve10());
|
|
8200
|
+
req.once("error", () => resolve10());
|
|
8201
|
+
req.once("close", () => resolve10());
|
|
7791
8202
|
});
|
|
7792
8203
|
if (chunks.length > 0) {
|
|
7793
8204
|
init.body = Buffer.concat(chunks);
|
|
@@ -7829,10 +8240,10 @@ function honoMiddlewarePlugin(config) {
|
|
|
7829
8240
|
while (true) {
|
|
7830
8241
|
const { done, value } = await reader.read();
|
|
7831
8242
|
if (done || clientGone || res.destroyed) break;
|
|
7832
|
-
await new Promise((
|
|
8243
|
+
await new Promise((resolve10, reject) => {
|
|
7833
8244
|
res.write(
|
|
7834
8245
|
Buffer.from(value),
|
|
7835
|
-
(err) => err ? reject(err) :
|
|
8246
|
+
(err) => err ? reject(err) : resolve10()
|
|
7836
8247
|
);
|
|
7837
8248
|
});
|
|
7838
8249
|
}
|
|
@@ -8129,7 +8540,7 @@ function vitestSharedPaths(clientDir) {
|
|
|
8129
8540
|
// server/start-vite-dev-server.ts
|
|
8130
8541
|
var __dirname2 = dirname7(fileURLToPath4(import.meta.url));
|
|
8131
8542
|
async function startViteDevServer(config) {
|
|
8132
|
-
const root =
|
|
8543
|
+
const root = resolve7(join7(__dirname2, ".."));
|
|
8133
8544
|
clearViteDepsCache(root);
|
|
8134
8545
|
const { repoRoot, sharedAliases, sharedDedupe, nodeModulesAliases } = vitestSharedPaths(root);
|
|
8135
8546
|
const inlineConfig = {
|
|
@@ -8222,6 +8633,7 @@ async function startHonoServer(config) {
|
|
|
8222
8633
|
secretStorageResolver: getSecretStorageInfo
|
|
8223
8634
|
});
|
|
8224
8635
|
const app = new Hono2();
|
|
8636
|
+
app.get(HEALTH_PATH, (c) => healthResponse(c.req.method));
|
|
8225
8637
|
app.use("/api/*", async (c) => {
|
|
8226
8638
|
return apiApp.fetch(c.req.raw);
|
|
8227
8639
|
});
|
|
@@ -8289,8 +8701,8 @@ async function startHonoServer(config) {
|
|
|
8289
8701
|
if ("closeAllConnections" in httpServer) {
|
|
8290
8702
|
httpServer.closeAllConnections();
|
|
8291
8703
|
}
|
|
8292
|
-
await new Promise((
|
|
8293
|
-
httpServer.close((err) => err ? reject(err) :
|
|
8704
|
+
await new Promise((resolve10, reject) => {
|
|
8705
|
+
httpServer.close((err) => err ? reject(err) : resolve10());
|
|
8294
8706
|
});
|
|
8295
8707
|
}
|
|
8296
8708
|
};
|
|
@@ -8333,7 +8745,7 @@ function ensureWebBuild(webRoot, distRoot, deps = {}) {
|
|
|
8333
8745
|
var __dirname4 = dirname9(fileURLToPath6(import.meta.url));
|
|
8334
8746
|
function ensureStdioCwd(config) {
|
|
8335
8747
|
if ((config.type === "stdio" || config.type === void 0) && config.command && !config.cwd) {
|
|
8336
|
-
return { ...config, cwd:
|
|
8748
|
+
return { ...config, cwd: resolve8(process.cwd()) };
|
|
8337
8749
|
}
|
|
8338
8750
|
return config;
|
|
8339
8751
|
}
|
|
@@ -8396,7 +8808,7 @@ async function runWeb(argv) {
|
|
|
8396
8808
|
"Note: --server has no effect on the web UI yet; it lists every server in the catalog."
|
|
8397
8809
|
);
|
|
8398
8810
|
}
|
|
8399
|
-
mcpConfigPath =
|
|
8811
|
+
mcpConfigPath = resolve8(process.cwd(), catalogPath);
|
|
8400
8812
|
writable = true;
|
|
8401
8813
|
} else if (hasConfig) {
|
|
8402
8814
|
if (opts.server) {
|
|
@@ -8404,7 +8816,7 @@ async function runWeb(argv) {
|
|
|
8404
8816
|
"Note: --server has no effect on the web UI yet; it lists every server in the file."
|
|
8405
8817
|
);
|
|
8406
8818
|
}
|
|
8407
|
-
const resolvedConfigPath =
|
|
8819
|
+
const resolvedConfigPath = resolve8(process.cwd(), opts.config);
|
|
8408
8820
|
try {
|
|
8409
8821
|
resolveServerConfigs({ configPath: resolvedConfigPath }, "multi");
|
|
8410
8822
|
} catch (err) {
|
|
@@ -8512,7 +8924,7 @@ async function runWeb(argv) {
|
|
|
8512
8924
|
|
|
8513
8925
|
// src/index.ts
|
|
8514
8926
|
var __filename2 = fileURLToPath7(import.meta.url);
|
|
8515
|
-
var isMain = process.argv[1] !== void 0 &&
|
|
8927
|
+
var isMain = process.argv[1] !== void 0 && resolve9(process.argv[1]) === resolve9(__filename2);
|
|
8516
8928
|
if (isMain) {
|
|
8517
8929
|
runWeb(process.argv).then((code) => process.exit(code ?? 0)).catch((e) => {
|
|
8518
8930
|
console.error(e);
|