@modelcontextprotocol/inspector 2.7.0 → 2.8.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/LICENSE +216 -0
- package/README.md +7 -2
- package/clients/cli/build/index.js +353 -35
- package/clients/tui/build/index.js +360 -41
- package/clients/web/build/index.js +177 -14
- package/clients/web/dist/assets/{index-DZkZ6KYt.js → index-9ZWu1ACP.js} +114 -114
- package/clients/web/dist/assets/{prism-light-CoQfbOXL.js → prism-light-CGBvpchP.js} +1 -1
- package/clients/web/dist/index.html +1 -1
- package/package.json +2 -2
|
@@ -1041,6 +1041,176 @@ function checkSkillNameCollisions(entries) {
|
|
|
1041
1041
|
return issues;
|
|
1042
1042
|
}
|
|
1043
1043
|
|
|
1044
|
+
// ../../core/json/localRefs.ts
|
|
1045
|
+
var SUBSCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
|
|
1046
|
+
"items",
|
|
1047
|
+
"contains",
|
|
1048
|
+
"not",
|
|
1049
|
+
"propertyNames",
|
|
1050
|
+
"if",
|
|
1051
|
+
"then",
|
|
1052
|
+
"else",
|
|
1053
|
+
"additionalProperties",
|
|
1054
|
+
"unevaluatedProperties",
|
|
1055
|
+
"additionalItems",
|
|
1056
|
+
"unevaluatedItems",
|
|
1057
|
+
"contentSchema"
|
|
1058
|
+
]);
|
|
1059
|
+
var SUBSCHEMA_ARRAY_KEYWORDS = /* @__PURE__ */ new Set([
|
|
1060
|
+
"allOf",
|
|
1061
|
+
"anyOf",
|
|
1062
|
+
"oneOf",
|
|
1063
|
+
"prefixItems"
|
|
1064
|
+
]);
|
|
1065
|
+
var SUBSCHEMA_MAP_KEYWORDS = /* @__PURE__ */ new Set([
|
|
1066
|
+
"properties",
|
|
1067
|
+
"patternProperties",
|
|
1068
|
+
"dependentSchemas",
|
|
1069
|
+
// Pre-2019 spelling of `dependentSchemas`; its array values are property
|
|
1070
|
+
// name lists, which are not schemas and pass through untouched.
|
|
1071
|
+
"dependencies",
|
|
1072
|
+
"$defs",
|
|
1073
|
+
"definitions"
|
|
1074
|
+
]);
|
|
1075
|
+
var NON_CONSTRAINT_SIBLINGS = /* @__PURE__ */ new Set([
|
|
1076
|
+
"title",
|
|
1077
|
+
"description",
|
|
1078
|
+
"default",
|
|
1079
|
+
"examples",
|
|
1080
|
+
"deprecated",
|
|
1081
|
+
"readOnly",
|
|
1082
|
+
"writeOnly",
|
|
1083
|
+
// Non-standard labels for `enum` values, read by both form builders.
|
|
1084
|
+
"enumNames",
|
|
1085
|
+
"$comment",
|
|
1086
|
+
"$schema",
|
|
1087
|
+
"$id",
|
|
1088
|
+
"$defs",
|
|
1089
|
+
"definitions"
|
|
1090
|
+
]);
|
|
1091
|
+
var EXPANSION_BUDGET = 1e4;
|
|
1092
|
+
var MAX_DEPTH = 64;
|
|
1093
|
+
var Bail = class extends Error {
|
|
1094
|
+
};
|
|
1095
|
+
function spend(traversal) {
|
|
1096
|
+
traversal.remaining -= 1;
|
|
1097
|
+
if (traversal.remaining < 0) throw new Bail();
|
|
1098
|
+
}
|
|
1099
|
+
function mapSubschemas(node, visit) {
|
|
1100
|
+
const result = {};
|
|
1101
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1102
|
+
let next = value;
|
|
1103
|
+
if (SUBSCHEMA_KEYWORDS.has(key)) {
|
|
1104
|
+
next = Array.isArray(value) ? value.map(visit) : visit(value);
|
|
1105
|
+
} else if (SUBSCHEMA_ARRAY_KEYWORDS.has(key) && Array.isArray(value)) {
|
|
1106
|
+
next = value.map(visit);
|
|
1107
|
+
} else if (SUBSCHEMA_MAP_KEYWORDS.has(key) && isRecord(value)) {
|
|
1108
|
+
next = mapNames(value, visit);
|
|
1109
|
+
}
|
|
1110
|
+
define(result, key, next);
|
|
1111
|
+
}
|
|
1112
|
+
return result;
|
|
1113
|
+
}
|
|
1114
|
+
function mapNames(map, visit) {
|
|
1115
|
+
const result = {};
|
|
1116
|
+
for (const [name, value] of Object.entries(map)) {
|
|
1117
|
+
define(result, name, Array.isArray(value) ? value : visit(value));
|
|
1118
|
+
}
|
|
1119
|
+
return result;
|
|
1120
|
+
}
|
|
1121
|
+
function define(target, key, value) {
|
|
1122
|
+
Object.defineProperty(target, key, {
|
|
1123
|
+
value,
|
|
1124
|
+
writable: true,
|
|
1125
|
+
enumerable: true,
|
|
1126
|
+
configurable: true
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
function isRecord(value) {
|
|
1130
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1131
|
+
}
|
|
1132
|
+
function resolvePointer(root, ref) {
|
|
1133
|
+
if (!ref.startsWith("#")) return void 0;
|
|
1134
|
+
let pointer;
|
|
1135
|
+
try {
|
|
1136
|
+
pointer = decodeURIComponent(ref.slice(1));
|
|
1137
|
+
} catch {
|
|
1138
|
+
return void 0;
|
|
1139
|
+
}
|
|
1140
|
+
if (pointer === "") return root;
|
|
1141
|
+
if (!pointer.startsWith("/")) return void 0;
|
|
1142
|
+
let current = root;
|
|
1143
|
+
for (const token of pointer.slice(1).split("/")) {
|
|
1144
|
+
if (/~(?![01])/.test(token)) return void 0;
|
|
1145
|
+
const segment = token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
1146
|
+
if (Array.isArray(current)) {
|
|
1147
|
+
const index = Number(segment);
|
|
1148
|
+
if (!/^(0|[1-9]\d*)$/.test(segment) || index >= current.length) {
|
|
1149
|
+
return void 0;
|
|
1150
|
+
}
|
|
1151
|
+
current = current[index];
|
|
1152
|
+
} else if (isRecord(current) && Object.hasOwn(current, segment)) {
|
|
1153
|
+
current = current[segment];
|
|
1154
|
+
} else {
|
|
1155
|
+
return void 0;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
return current;
|
|
1159
|
+
}
|
|
1160
|
+
function containsRef(node, depth) {
|
|
1161
|
+
if (depth > MAX_DEPTH) throw new Bail();
|
|
1162
|
+
if (!isRecord(node)) return false;
|
|
1163
|
+
if (typeof node.$ref === "string") return true;
|
|
1164
|
+
let found = false;
|
|
1165
|
+
mapSubschemas(node, (child) => {
|
|
1166
|
+
found ||= containsRef(child, depth + 1);
|
|
1167
|
+
return child;
|
|
1168
|
+
});
|
|
1169
|
+
return found;
|
|
1170
|
+
}
|
|
1171
|
+
function inline(node, traversal, depth) {
|
|
1172
|
+
if (depth > MAX_DEPTH) throw new Bail();
|
|
1173
|
+
if (!isRecord(node)) return node;
|
|
1174
|
+
if (node !== traversal.root && typeof node.$id === "string") return node;
|
|
1175
|
+
spend(traversal);
|
|
1176
|
+
const visit = (child) => inline(child, traversal, depth + 1);
|
|
1177
|
+
const ref = node.$ref;
|
|
1178
|
+
const siblingsConstrain = Object.keys(node).some(
|
|
1179
|
+
(key) => key !== "$ref" && !NON_CONSTRAINT_SIBLINGS.has(key)
|
|
1180
|
+
);
|
|
1181
|
+
if (typeof ref === "string" && !siblingsConstrain && !traversal.active.has(ref)) {
|
|
1182
|
+
const target = resolvePointer(traversal.root, ref);
|
|
1183
|
+
if (isRecord(target)) {
|
|
1184
|
+
traversal.active.add(ref);
|
|
1185
|
+
const resolved2 = visit(target);
|
|
1186
|
+
traversal.active.delete(ref);
|
|
1187
|
+
const siblings = { ...node };
|
|
1188
|
+
delete siblings.$ref;
|
|
1189
|
+
return { ...resolved2, ...mapSubschemas(siblings, visit) };
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return mapSubschemas(node, visit);
|
|
1193
|
+
}
|
|
1194
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
1195
|
+
function inlineLocalRefs(schema) {
|
|
1196
|
+
if (!isRecord(schema)) return schema;
|
|
1197
|
+
const cached = cache.get(schema);
|
|
1198
|
+
if (cached !== void 0) return cached;
|
|
1199
|
+
let resolved2;
|
|
1200
|
+
try {
|
|
1201
|
+
resolved2 = containsRef(schema, 0) ? inline(
|
|
1202
|
+
schema,
|
|
1203
|
+
{ root: schema, active: /* @__PURE__ */ new Set(), remaining: EXPANSION_BUDGET },
|
|
1204
|
+
0
|
|
1205
|
+
) : schema;
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
if (!(error instanceof Bail)) throw error;
|
|
1208
|
+
resolved2 = schema;
|
|
1209
|
+
}
|
|
1210
|
+
cache.set(schema, resolved2);
|
|
1211
|
+
return resolved2;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1044
1214
|
// ../../core/json/nullableUnion.ts
|
|
1045
1215
|
var RENDERABLE_TYPES = [
|
|
1046
1216
|
"string",
|
|
@@ -1639,7 +1809,7 @@ function convertToolParameters(tool, params) {
|
|
|
1639
1809
|
function convertParametersForSchema(inputSchema, params) {
|
|
1640
1810
|
const result = {};
|
|
1641
1811
|
const { base, branches } = resolveRootUnion(
|
|
1642
|
-
inputSchema ?? {}
|
|
1812
|
+
inlineLocalRefs(inputSchema ?? {})
|
|
1643
1813
|
);
|
|
1644
1814
|
const properties = coercionProperties(base, branches, params);
|
|
1645
1815
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -1831,7 +2001,7 @@ function oauthEndpointOverridesFromSettings(settings) {
|
|
|
1831
2001
|
};
|
|
1832
2002
|
}
|
|
1833
2003
|
function storedFieldsToInspectorSettings(stored) {
|
|
1834
|
-
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.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;
|
|
2004
|
+
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;
|
|
1835
2005
|
if (!hasAny) return void 0;
|
|
1836
2006
|
const headersPairs = stored.headers ? Object.entries(stored.headers).map(([key, value]) => ({ key, value })) : [];
|
|
1837
2007
|
const settings = {
|
|
@@ -1863,6 +2033,9 @@ function storedFieldsToInspectorSettings(stored) {
|
|
|
1863
2033
|
if (isSkillCatalogLimit(stored.skillCatalogMaxBytes)) {
|
|
1864
2034
|
settings.skillCatalogMaxBytes = stored.skillCatalogMaxBytes;
|
|
1865
2035
|
}
|
|
2036
|
+
if (stored.suppressNotificationStream === true) {
|
|
2037
|
+
settings.suppressNotificationStream = true;
|
|
2038
|
+
}
|
|
1866
2039
|
if (isProtocolEra(stored.protocolEra)) {
|
|
1867
2040
|
settings.protocolEra = stored.protocolEra;
|
|
1868
2041
|
}
|
|
@@ -1914,6 +2087,7 @@ var INSPECTOR_FIELD_KEY_MAP = {
|
|
|
1914
2087
|
taskTtl: true,
|
|
1915
2088
|
autoRefreshOnListChanged: true,
|
|
1916
2089
|
paginatedLists: true,
|
|
2090
|
+
suppressNotificationStream: true,
|
|
1917
2091
|
advertisedExtensions: true,
|
|
1918
2092
|
maxFetchRequests: true,
|
|
1919
2093
|
skillCatalogMaxSkills: true,
|
|
@@ -2268,7 +2442,7 @@ function secretStorageCaveat(info) {
|
|
|
2268
2442
|
return `The secrets file could not be read (${info.encryptionUnknown}). Saving a secret will fail rather than overwrite it.`;
|
|
2269
2443
|
}
|
|
2270
2444
|
if (info.plaintext) {
|
|
2271
|
-
return info.pendingEncryption ? "Existing secrets in this file are still unencrypted (file mode 0600). They are re-encrypted the next time a secret is saved." : "Secrets are stored unencrypted (file mode 0600). Set MCP_INSPECTOR_SECRET_KEY to encrypt them.";
|
|
2445
|
+
return info.pendingEncryption ? "Existing secrets in this file are still unencrypted (file mode 0600). They are re-encrypted the next time a secret is saved." : "Secrets are stored unencrypted (file mode 0600). Set MCP_INSPECTOR_SECRET_KEY or MCP_INSPECTOR_SECRET_KEY_FILE to encrypt them.";
|
|
2272
2446
|
}
|
|
2273
2447
|
return void 0;
|
|
2274
2448
|
}
|
|
@@ -2283,6 +2457,7 @@ function secretStorageSummary(info) {
|
|
|
2283
2457
|
|
|
2284
2458
|
// ../../core/auth/node/file-secret-store.ts
|
|
2285
2459
|
import * as crypto2 from "crypto";
|
|
2460
|
+
import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
2286
2461
|
import * as fs3 from "fs/promises";
|
|
2287
2462
|
import * as path3 from "path";
|
|
2288
2463
|
|
|
@@ -2485,6 +2660,55 @@ var SCRYPT_N = 16384;
|
|
|
2485
2660
|
var SCRYPT_R = 8;
|
|
2486
2661
|
var SCRYPT_P = 1;
|
|
2487
2662
|
var SECRET_KEY_ENV = "MCP_INSPECTOR_SECRET_KEY";
|
|
2663
|
+
var SECRET_KEY_FILE_ENV = "MCP_INSPECTOR_SECRET_KEY_FILE";
|
|
2664
|
+
function isSameFile(a, b) {
|
|
2665
|
+
if (path3.resolve(a) === path3.resolve(b)) return true;
|
|
2666
|
+
try {
|
|
2667
|
+
const sa = statSync2(a);
|
|
2668
|
+
const sb = statSync2(b);
|
|
2669
|
+
return sa.dev === sb.dev && sa.ino === sb.ino;
|
|
2670
|
+
} catch {
|
|
2671
|
+
return false;
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
function resolveSecretPassphrase(env = process.env, secretFilePath) {
|
|
2675
|
+
const direct = env[SECRET_KEY_ENV];
|
|
2676
|
+
const hasDirect = direct !== void 0 && direct.trim() !== "";
|
|
2677
|
+
const rawKeyFile = env[SECRET_KEY_FILE_ENV];
|
|
2678
|
+
const wantsKeyFile = rawKeyFile !== void 0;
|
|
2679
|
+
const keyFile = rawKeyFile?.trim();
|
|
2680
|
+
if (hasDirect && wantsKeyFile) {
|
|
2681
|
+
return {
|
|
2682
|
+
problem: `both ${SECRET_KEY_ENV} and ${SECRET_KEY_FILE_ENV} are set; set only one`
|
|
2683
|
+
};
|
|
2684
|
+
}
|
|
2685
|
+
if (hasDirect) return { passphrase: direct };
|
|
2686
|
+
if (!wantsKeyFile) return {};
|
|
2687
|
+
if (!keyFile) {
|
|
2688
|
+
return { problem: `${SECRET_KEY_FILE_ENV} is set but empty` };
|
|
2689
|
+
}
|
|
2690
|
+
const resolved2 = path3.resolve(keyFile);
|
|
2691
|
+
if (secretFilePath !== void 0 && isSameFile(resolved2, secretFilePath)) {
|
|
2692
|
+
return {
|
|
2693
|
+
problem: `${SECRET_KEY_FILE_ENV} (${resolved2}) is the secrets file itself; point it at a separate key file`
|
|
2694
|
+
};
|
|
2695
|
+
}
|
|
2696
|
+
let contents;
|
|
2697
|
+
try {
|
|
2698
|
+
contents = readFileSync2(resolved2, "utf-8");
|
|
2699
|
+
} catch (err) {
|
|
2700
|
+
return {
|
|
2701
|
+
// `String(err)` rather than `.message`: `readFileSync` only ever throws
|
|
2702
|
+
// an `Error`, so an `instanceof` guard would be a branch no test can take.
|
|
2703
|
+
problem: `${SECRET_KEY_FILE_ENV} (${resolved2}) could not be read: ${String(err)}`
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
const passphrase = contents.replace(/[\r\n]+$/, "");
|
|
2707
|
+
if (passphrase.trim() === "") {
|
|
2708
|
+
return { problem: `${SECRET_KEY_FILE_ENV} (${resolved2}) is empty` };
|
|
2709
|
+
}
|
|
2710
|
+
return { passphrase };
|
|
2711
|
+
}
|
|
2488
2712
|
var buildAccount2 = (serverId, field) => `${serverId}:${field}`;
|
|
2489
2713
|
function asSecretMap(value, filePath) {
|
|
2490
2714
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
@@ -2504,7 +2728,7 @@ function asSecretMap(value, filePath) {
|
|
|
2504
2728
|
var SecretFileKeyMismatchError = class extends SecretStoreUnavailableError {
|
|
2505
2729
|
constructor(filePath, hasKey) {
|
|
2506
2730
|
super(
|
|
2507
|
-
hasKey ? `The secrets file at ${filePath} could not be decrypted with the current ${SECRET_KEY_ENV}. Refusing to write, which would overwrite the existing secrets. Restore the original passphrase, or delete the file to start over.` : `The secrets file at ${filePath} is encrypted but ${SECRET_KEY_ENV} is not set. Refusing to write, which would overwrite the existing secrets. Set the passphrase this file was written with, or delete the file to start over.`
|
|
2731
|
+
hasKey ? `The secrets file at ${filePath} could not be decrypted with the current ${SECRET_KEY_ENV} (or ${SECRET_KEY_FILE_ENV}). Refusing to write, which would overwrite the existing secrets. Restore the original passphrase, or delete the file to start over.` : `The secrets file at ${filePath} is encrypted but ${SECRET_KEY_ENV} is not set (nor ${SECRET_KEY_FILE_ENV}). Refusing to write, which would overwrite the existing secrets. Set the passphrase this file was written with, or delete the file to start over.`
|
|
2508
2732
|
);
|
|
2509
2733
|
this.name = "SecretFileKeyMismatchError";
|
|
2510
2734
|
}
|
|
@@ -2562,10 +2786,17 @@ function encryptedEnvelopeProblem(parsed) {
|
|
|
2562
2786
|
var FileSecretStore = class {
|
|
2563
2787
|
filePath;
|
|
2564
2788
|
passphrase;
|
|
2789
|
+
/**
|
|
2790
|
+
* Why a configured key could not be obtained. While set, every read throws
|
|
2791
|
+
* and every write refuses — see {@link SecretPassphrase}.
|
|
2792
|
+
*/
|
|
2793
|
+
keyProblem;
|
|
2565
2794
|
constructor(options) {
|
|
2566
2795
|
this.filePath = options.filePath;
|
|
2567
|
-
const
|
|
2796
|
+
const resolved2 = options.passphrase !== void 0 ? { passphrase: options.passphrase } : resolveSecretPassphrase(process.env, this.filePath);
|
|
2797
|
+
const raw = resolved2.passphrase;
|
|
2568
2798
|
this.passphrase = raw && raw.trim() ? raw : void 0;
|
|
2799
|
+
this.keyProblem = resolved2.problem;
|
|
2569
2800
|
}
|
|
2570
2801
|
/**
|
|
2571
2802
|
* True when the *next write* will encrypt. This is the configured policy,
|
|
@@ -2580,7 +2811,8 @@ var FileSecretStore = class {
|
|
|
2580
2811
|
* no file yet, or when it cannot be read or parsed.
|
|
2581
2812
|
*
|
|
2582
2813
|
* Separate from {@link encrypted} because the two genuinely disagree for a
|
|
2583
|
-
* whole session: adding `MCP_INSPECTOR_SECRET_KEY`
|
|
2814
|
+
* whole session: adding a passphrase (`MCP_INSPECTOR_SECRET_KEY` or
|
|
2815
|
+
* `MCP_INSPECTOR_SECRET_KEY_FILE`) to an install that
|
|
2584
2816
|
* already has a plaintext file flips `encrypted` to true immediately,
|
|
2585
2817
|
* while the bytes stay readable until the next `set`. A descriptor built
|
|
2586
2818
|
* from the policy would tell that user "File (encrypted)" while their
|
|
@@ -2604,6 +2836,9 @@ var FileSecretStore = class {
|
|
|
2604
2836
|
* `set` is in fact about to refuse.
|
|
2605
2837
|
*/
|
|
2606
2838
|
async readOnDiskEncryption() {
|
|
2839
|
+
if (this.keyProblem !== void 0) {
|
|
2840
|
+
return { state: "unreadable", detail: this.keyProblem };
|
|
2841
|
+
}
|
|
2607
2842
|
let raw;
|
|
2608
2843
|
try {
|
|
2609
2844
|
raw = await readStoreFile(this.filePath);
|
|
@@ -2641,7 +2876,7 @@ var FileSecretStore = class {
|
|
|
2641
2876
|
} catch (err) {
|
|
2642
2877
|
return {
|
|
2643
2878
|
state: "unreadable",
|
|
2644
|
-
detail: err instanceof SecretFileKeyMismatchError ? `it cannot be decrypted with the current ${SECRET_KEY_ENV}` : err instanceof Error ? err.message : String(err)
|
|
2879
|
+
detail: err instanceof SecretFileKeyMismatchError ? `it cannot be decrypted with the current ${SECRET_KEY_ENV} (or ${SECRET_KEY_FILE_ENV})` : err instanceof Error ? err.message : String(err)
|
|
2645
2880
|
};
|
|
2646
2881
|
}
|
|
2647
2882
|
return { state: "encrypted" };
|
|
@@ -2692,6 +2927,11 @@ var FileSecretStore = class {
|
|
|
2692
2927
|
return this.serialize(() => this.readMap());
|
|
2693
2928
|
}
|
|
2694
2929
|
async readMap() {
|
|
2930
|
+
if (this.keyProblem !== void 0) {
|
|
2931
|
+
throw new SecretStoreUnavailableError(
|
|
2932
|
+
`The secrets file at ${this.filePath} cannot be used: ${this.keyProblem}. Refusing to read or write it rather than store secrets unencrypted.`
|
|
2933
|
+
);
|
|
2934
|
+
}
|
|
2695
2935
|
const raw = await readStoreFile(this.filePath);
|
|
2696
2936
|
if (raw === null) return null;
|
|
2697
2937
|
let parsed;
|
|
@@ -3047,6 +3287,7 @@ async function readSecretFilePermissions(filePath) {
|
|
|
3047
3287
|
var SECRET_STORE_ENV = "MCP_INSPECTOR_SECRET_STORE";
|
|
3048
3288
|
var SECRET_FILE_ENV = "MCP_INSPECTOR_SECRET_FILE";
|
|
3049
3289
|
var STORAGE_DIR_ENV = "MCP_STORAGE_DIR";
|
|
3290
|
+
var SECRET_STORAGE_DOCS_URL = "https://github.com/modelcontextprotocol/inspector/blob/main/docs/secret-storage.md";
|
|
3050
3291
|
var KINDS = ["keyring", "file", "memory"];
|
|
3051
3292
|
function parseSecretStoreEnv(raw) {
|
|
3052
3293
|
const value = raw?.trim().toLowerCase();
|
|
@@ -3189,6 +3430,11 @@ function warnAboutSecretStorage(info) {
|
|
|
3189
3430
|
}
|
|
3190
3431
|
const caveat = secretStorageCaveat(info);
|
|
3191
3432
|
if (caveat) console.warn(`[mcp-inspector] ${caveat}`);
|
|
3433
|
+
if (info.reason === "fallback" || caveat) {
|
|
3434
|
+
console.warn(
|
|
3435
|
+
`[mcp-inspector] How the secret store is chosen, and how to secure it: ${SECRET_STORAGE_DOCS_URL}`
|
|
3436
|
+
);
|
|
3437
|
+
}
|
|
3192
3438
|
}
|
|
3193
3439
|
var resolved;
|
|
3194
3440
|
function resolveSecretStore() {
|
|
@@ -4351,6 +4597,73 @@ function createProxyFetch() {
|
|
|
4351
4597
|
};
|
|
4352
4598
|
}
|
|
4353
4599
|
|
|
4600
|
+
// ../../core/mcp/node/notificationHeadersFetch.ts
|
|
4601
|
+
import { PROTOCOL_VERSION_META_KEY } from "@modelcontextprotocol/client";
|
|
4602
|
+
function createNotificationHeadersFetch(baseFetch) {
|
|
4603
|
+
return (input, init) => {
|
|
4604
|
+
const method = modernNotificationMethod(init);
|
|
4605
|
+
if (method === void 0) return baseFetch(input, init);
|
|
4606
|
+
const headers = new Headers(init?.headers);
|
|
4607
|
+
headers.set("mcp-protocol-version", method.version);
|
|
4608
|
+
headers.set("mcp-method", method.method);
|
|
4609
|
+
return baseFetch(input, { ...init, headers });
|
|
4610
|
+
};
|
|
4611
|
+
}
|
|
4612
|
+
function modernNotificationMethod(init) {
|
|
4613
|
+
if (init?.method?.toUpperCase() !== "POST") return void 0;
|
|
4614
|
+
if (typeof init.body !== "string") return void 0;
|
|
4615
|
+
let message;
|
|
4616
|
+
try {
|
|
4617
|
+
message = JSON.parse(init.body);
|
|
4618
|
+
} catch {
|
|
4619
|
+
return void 0;
|
|
4620
|
+
}
|
|
4621
|
+
if (!isRecord2(message) || "id" in message) return void 0;
|
|
4622
|
+
if (typeof message.method !== "string") return void 0;
|
|
4623
|
+
const meta = isRecord2(message.params) ? message.params._meta : void 0;
|
|
4624
|
+
const version = isRecord2(meta) ? meta[PROTOCOL_VERSION_META_KEY] : void 0;
|
|
4625
|
+
if (typeof version !== "string" || version < MODERN_PROTOCOL_VERSION) {
|
|
4626
|
+
return void 0;
|
|
4627
|
+
}
|
|
4628
|
+
return { method: message.method, version };
|
|
4629
|
+
}
|
|
4630
|
+
function isRecord2(value) {
|
|
4631
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4632
|
+
}
|
|
4633
|
+
|
|
4634
|
+
// ../../core/mcp/node/suppressNotificationStreamFetch.ts
|
|
4635
|
+
function createSuppressNotificationStreamFetch(baseFetch, endpoint) {
|
|
4636
|
+
return async (input, init) => {
|
|
4637
|
+
if (isStandaloneStreamRequest(input, init, endpoint)) {
|
|
4638
|
+
return new Response(null, {
|
|
4639
|
+
status: 405,
|
|
4640
|
+
statusText: "Method Not Allowed"
|
|
4641
|
+
});
|
|
4642
|
+
}
|
|
4643
|
+
return baseFetch(input, init);
|
|
4644
|
+
};
|
|
4645
|
+
}
|
|
4646
|
+
function requestUrl(input) {
|
|
4647
|
+
const raw = input instanceof Request ? input.url : input instanceof URL ? input.href : input;
|
|
4648
|
+
try {
|
|
4649
|
+
return new URL(raw);
|
|
4650
|
+
} catch {
|
|
4651
|
+
return void 0;
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
function isStandaloneStreamRequest(input, init, endpoint) {
|
|
4655
|
+
const request = input instanceof Request ? input : void 0;
|
|
4656
|
+
const method = (init?.method ?? request?.method ?? "GET").toUpperCase();
|
|
4657
|
+
if (method !== "GET") return false;
|
|
4658
|
+
const url = requestUrl(input);
|
|
4659
|
+
if (!url || url.origin !== endpoint.origin || url.pathname !== endpoint.pathname || url.search !== endpoint.search) {
|
|
4660
|
+
return false;
|
|
4661
|
+
}
|
|
4662
|
+
const headers = new Headers(init?.headers ?? request?.headers);
|
|
4663
|
+
const accept = headers.get("accept")?.toLowerCase() ?? "";
|
|
4664
|
+
return accept.includes("text/event-stream") && !headers.has("last-event-id");
|
|
4665
|
+
}
|
|
4666
|
+
|
|
4354
4667
|
// ../../core/mcp/node/transport.ts
|
|
4355
4668
|
function headersFromSettings(settings) {
|
|
4356
4669
|
if (!settings || settings.headers.length === 0) return void 0;
|
|
@@ -4438,10 +4751,14 @@ function createTransportNode(config, options = {}) {
|
|
|
4438
4751
|
...httpConfig.requestInit,
|
|
4439
4752
|
...headers && { headers }
|
|
4440
4753
|
};
|
|
4754
|
+
const stampedFetch = createNotificationHeadersFetch(
|
|
4755
|
+
fetchWithOptionalAuthIntercept
|
|
4756
|
+
);
|
|
4757
|
+
const httpFetch = settings?.suppressNotificationStream === true ? createSuppressNotificationStreamFetch(stampedFetch, url) : stampedFetch;
|
|
4441
4758
|
const transport = new StreamableHTTPClientTransport(url, {
|
|
4442
4759
|
authProvider,
|
|
4443
4760
|
requestInit,
|
|
4444
|
-
fetch:
|
|
4761
|
+
fetch: httpFetch,
|
|
4445
4762
|
// SEP-2350: how the transport reacts to a `403 insufficient_scope`
|
|
4446
4763
|
// challenge. Defaults to the SDK's `reauthorize` when unset.
|
|
4447
4764
|
...settings?.oauthOnInsufficientScope && {
|
|
@@ -4981,7 +5298,7 @@ var OAuthMemoryStore = class {
|
|
|
4981
5298
|
};
|
|
4982
5299
|
|
|
4983
5300
|
// ../../core/auth/oauth-persist.ts
|
|
4984
|
-
function
|
|
5301
|
+
function isRecord3(value) {
|
|
4985
5302
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4986
5303
|
}
|
|
4987
5304
|
function snapshotFromPayload(payload) {
|
|
@@ -4995,10 +5312,10 @@ function parseOAuthPersistBlob(raw) {
|
|
|
4995
5312
|
return null;
|
|
4996
5313
|
}
|
|
4997
5314
|
const parsed = typeof raw === "string" ? raw ? parseStore(raw) : null : raw;
|
|
4998
|
-
if (!
|
|
5315
|
+
if (!isRecord3(parsed)) {
|
|
4999
5316
|
return null;
|
|
5000
5317
|
}
|
|
5001
|
-
if (
|
|
5318
|
+
if (isRecord3(parsed.state) && "version" in parsed) {
|
|
5002
5319
|
return snapshotFromPayload(parsed.state);
|
|
5003
5320
|
}
|
|
5004
5321
|
if ("servers" in parsed || "idpSessions" in parsed) {
|
|
@@ -5462,14 +5779,14 @@ var OBJECT_VALUED_SUBSCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
5462
5779
|
function pathName(path5) {
|
|
5463
5780
|
return path5.length === 0 ? "<root>" : path5.join(".");
|
|
5464
5781
|
}
|
|
5465
|
-
function
|
|
5782
|
+
function isRecord4(node) {
|
|
5466
5783
|
return node !== null && typeof node === "object";
|
|
5467
5784
|
}
|
|
5468
5785
|
function scanXMcpHeaderDeclarations(inputSchema) {
|
|
5469
5786
|
const declarations = [];
|
|
5470
5787
|
const seenLower = /* @__PURE__ */ new Map();
|
|
5471
5788
|
const visit = (node, path5, reachable) => {
|
|
5472
|
-
if (!
|
|
5789
|
+
if (!isRecord4(node)) return void 0;
|
|
5473
5790
|
const schema = node;
|
|
5474
5791
|
if (X_MCP_HEADER_KEY in schema) {
|
|
5475
5792
|
if (!reachable || path5.length === 0) {
|
|
@@ -5495,7 +5812,7 @@ function scanXMcpHeaderDeclarations(inputSchema) {
|
|
|
5495
5812
|
declarations.push({ path: path5, headerName: raw, type });
|
|
5496
5813
|
}
|
|
5497
5814
|
const properties = schema.properties;
|
|
5498
|
-
if (
|
|
5815
|
+
if (isRecord4(properties)) {
|
|
5499
5816
|
for (const [key, child] of Object.entries(properties)) {
|
|
5500
5817
|
const fault2 = visit(child, [...path5, key], reachable);
|
|
5501
5818
|
if (fault2 !== void 0) return fault2;
|
|
@@ -5504,7 +5821,7 @@ function scanXMcpHeaderDeclarations(inputSchema) {
|
|
|
5504
5821
|
for (const k of NON_REACHABLE_SUBSCHEMA_KEYWORDS) {
|
|
5505
5822
|
const sub = schema[k];
|
|
5506
5823
|
if (sub === void 0) continue;
|
|
5507
|
-
const branches = Array.isArray(sub) ? sub :
|
|
5824
|
+
const branches = Array.isArray(sub) ? sub : isRecord4(sub) && OBJECT_VALUED_SUBSCHEMA_KEYWORDS.has(k) ? Object.values(sub) : [sub];
|
|
5508
5825
|
for (const branch of branches) {
|
|
5509
5826
|
const fault2 = visit(branch, [...path5, `<${k}>`], false);
|
|
5510
5827
|
if (fault2 !== void 0) return fault2;
|
|
@@ -5736,7 +6053,7 @@ import {
|
|
|
5736
6053
|
LOG_LEVEL_META_KEY,
|
|
5737
6054
|
CLIENT_CAPABILITIES_META_KEY,
|
|
5738
6055
|
CLIENT_INFO_META_KEY,
|
|
5739
|
-
PROTOCOL_VERSION_META_KEY,
|
|
6056
|
+
PROTOCOL_VERSION_META_KEY as PROTOCOL_VERSION_META_KEY2,
|
|
5740
6057
|
RELATED_TASK_META_KEY as RELATED_TASK_META_KEY3
|
|
5741
6058
|
} from "@modelcontextprotocol/client";
|
|
5742
6059
|
|
|
@@ -6942,7 +7259,7 @@ function withRfc8414OidcCompat(fetchFn) {
|
|
|
6942
7259
|
return response;
|
|
6943
7260
|
}
|
|
6944
7261
|
if (!probe.ok) {
|
|
6945
|
-
|
|
7262
|
+
releaseBodyDetached(probe);
|
|
6946
7263
|
if (callerSignal?.aborted) {
|
|
6947
7264
|
releaseBodyDetached(response);
|
|
6948
7265
|
throw callerSignal.reason;
|
|
@@ -6966,7 +7283,7 @@ function withRfc8414OidcCompat(fetchFn) {
|
|
|
6966
7283
|
return response;
|
|
6967
7284
|
}
|
|
6968
7285
|
const source = probe.url || candidate;
|
|
6969
|
-
|
|
7286
|
+
releaseBodyDetached(response);
|
|
6970
7287
|
console.warn(
|
|
6971
7288
|
`[oauth] ${source} returned RFC 8414 OAuth 2.0 authorization server metadata, not an OpenID provider document. The MCP TypeScript SDK validates that path as OpenID Connect Discovery and would reject it (modelcontextprotocol/typescript-sdk#2733), so the Inspector is handing it to discovery as the RFC 8414 document it is.`
|
|
6972
7289
|
);
|
|
@@ -11521,7 +11838,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11521
11838
|
...params,
|
|
11522
11839
|
_meta: {
|
|
11523
11840
|
...existingMeta,
|
|
11524
|
-
[
|
|
11841
|
+
[PROTOCOL_VERSION_META_KEY2]: protocolVersion,
|
|
11525
11842
|
[CLIENT_INFO_META_KEY]: this.clientInfo,
|
|
11526
11843
|
[CLIENT_CAPABILITIES_META_KEY]: clientCapabilities
|
|
11527
11844
|
}
|
|
@@ -14998,7 +15315,7 @@ var BOOLEAN_VALUED_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
14998
15315
|
"additionalItems",
|
|
14999
15316
|
"unevaluatedItems"
|
|
15000
15317
|
]);
|
|
15001
|
-
var
|
|
15318
|
+
var SUBSCHEMA_KEYWORDS2 = [
|
|
15002
15319
|
"items",
|
|
15003
15320
|
"contains",
|
|
15004
15321
|
"not",
|
|
@@ -15015,13 +15332,13 @@ var SUBSCHEMA_KEYWORDS = [
|
|
|
15015
15332
|
// real finding.
|
|
15016
15333
|
"contentSchema"
|
|
15017
15334
|
];
|
|
15018
|
-
var
|
|
15335
|
+
var SUBSCHEMA_ARRAY_KEYWORDS2 = [
|
|
15019
15336
|
"allOf",
|
|
15020
15337
|
"anyOf",
|
|
15021
15338
|
"oneOf",
|
|
15022
15339
|
"prefixItems"
|
|
15023
15340
|
];
|
|
15024
|
-
var
|
|
15341
|
+
var SUBSCHEMA_MAP_KEYWORDS2 = [
|
|
15025
15342
|
"properties",
|
|
15026
15343
|
"patternProperties",
|
|
15027
15344
|
"dependentSchemas",
|
|
@@ -15103,8 +15420,8 @@ function isPortableTypeUnion(type) {
|
|
|
15103
15420
|
}
|
|
15104
15421
|
return new Set(type).size === type.length;
|
|
15105
15422
|
}
|
|
15106
|
-
var
|
|
15107
|
-
function
|
|
15423
|
+
var MAX_DEPTH2 = 64;
|
|
15424
|
+
function isRecord5(value) {
|
|
15108
15425
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15109
15426
|
}
|
|
15110
15427
|
function childPath(parent, key) {
|
|
@@ -15120,7 +15437,7 @@ function describeSchemaPath(schema, path5) {
|
|
|
15120
15437
|
return path5 === "" ? schema : `${schema}.${path5}`;
|
|
15121
15438
|
}
|
|
15122
15439
|
function declaresAnyId(node, depth = 0) {
|
|
15123
|
-
if (depth >
|
|
15440
|
+
if (depth > MAX_DEPTH2 || !isRecord5(node)) return false;
|
|
15124
15441
|
if (typeof node.$id === "string" && node.$id !== "") return true;
|
|
15125
15442
|
let found = false;
|
|
15126
15443
|
forEachSubschema(node, "", (child) => {
|
|
@@ -15139,21 +15456,21 @@ function add(ctx, rule, severity, path5, issue, suggestion) {
|
|
|
15139
15456
|
});
|
|
15140
15457
|
}
|
|
15141
15458
|
function forEachSubschema(node, path5, visit) {
|
|
15142
|
-
for (const key of
|
|
15459
|
+
for (const key of SUBSCHEMA_MAP_KEYWORDS2) {
|
|
15143
15460
|
const map = node[key];
|
|
15144
|
-
if (!
|
|
15461
|
+
if (!isRecord5(map)) continue;
|
|
15145
15462
|
for (const [name, sub] of Object.entries(map)) {
|
|
15146
15463
|
visit(sub, childPath(childPath(path5, key), name), key);
|
|
15147
15464
|
}
|
|
15148
15465
|
}
|
|
15149
|
-
for (const key of
|
|
15466
|
+
for (const key of SUBSCHEMA_ARRAY_KEYWORDS2) {
|
|
15150
15467
|
const arr = node[key];
|
|
15151
15468
|
if (!Array.isArray(arr)) continue;
|
|
15152
15469
|
arr.forEach((sub, i) => {
|
|
15153
15470
|
visit(sub, indexPath(childPath(path5, key), i), key);
|
|
15154
15471
|
});
|
|
15155
15472
|
}
|
|
15156
|
-
for (const key of
|
|
15473
|
+
for (const key of SUBSCHEMA_KEYWORDS2) {
|
|
15157
15474
|
if (!(key in node)) continue;
|
|
15158
15475
|
const value = node[key];
|
|
15159
15476
|
if (key === "items" && Array.isArray(value)) {
|
|
@@ -15172,7 +15489,7 @@ function booleanTrueSuggestion(keyword) {
|
|
|
15172
15489
|
return 'Declare what the value actually is \u2014 e.g. `{"type": "object", "additionalProperties": true}` for a free-form object. That is a deliberate change of contract, not an equivalent rewrite: `true` accepts any JSON value at all.';
|
|
15173
15490
|
}
|
|
15174
15491
|
function walk(node, path5, keyword, depth, ctx) {
|
|
15175
|
-
if (depth >
|
|
15492
|
+
if (depth > MAX_DEPTH2) return;
|
|
15176
15493
|
if (typeof node === "boolean") {
|
|
15177
15494
|
if (keyword !== void 0 && BOOLEAN_VALUED_KEYWORDS.has(keyword)) return;
|
|
15178
15495
|
add(
|
|
@@ -15191,12 +15508,13 @@ function walk(node, path5, keyword, depth, ctx) {
|
|
|
15191
15508
|
);
|
|
15192
15509
|
return;
|
|
15193
15510
|
}
|
|
15194
|
-
if (!
|
|
15511
|
+
if (!isRecord5(node)) return;
|
|
15195
15512
|
lintNode(node, path5, keyword, ctx);
|
|
15196
15513
|
forEachSubschema(node, path5, (child, childPathValue, childKeyword) => {
|
|
15197
15514
|
walk(child, childPathValue, childKeyword, depth + 1, ctx);
|
|
15198
15515
|
});
|
|
15199
15516
|
}
|
|
15517
|
+
var NULL_BRANCH_CAVEAT = " A dialect with no `null` type, such as OpenAPI 3.0, still cannot express the `null` branch directly; there nullability is written `nullable: true`, which is not JSON Schema, so no single spelling is portable to both.";
|
|
15200
15518
|
function lintNode(node, path5, keyword, ctx) {
|
|
15201
15519
|
const type = node.type;
|
|
15202
15520
|
if (Array.isArray(type) && isPortableTypeUnion(type)) {
|
|
@@ -15205,10 +15523,10 @@ function lintNode(node, path5, keyword, ctx) {
|
|
|
15205
15523
|
"type-union",
|
|
15206
15524
|
"warning",
|
|
15207
15525
|
path5,
|
|
15208
|
-
`\`type\` is an array (${JSON.stringify(type)}).
|
|
15526
|
+
`\`type\` is an array (${JSON.stringify(type)}). This is legal JSON Schema, and some model providers recommend it for nullable fields, but it is less portable: a client that maps tool schemas onto a single-\`type\` dialect (such as the OpenAPI subset used for Gemini function declarations) may reject the tool or drop the constraint.`,
|
|
15209
15527
|
`Split it into \`anyOf\` branches, each with a single \`type\` \u2014 \`{"anyOf": [${type.map((t) => `{"type": "${t}"}`).join(
|
|
15210
15528
|
", "
|
|
15211
|
-
)}]}\`. (Making the property optional instead is a different contract: absent is not the same as \`null\`.)`
|
|
15529
|
+
)}]}\`. (Making the property optional instead is a different contract: absent is not the same as \`null\`.)${type.includes("null") ? NULL_BRANCH_CAVEAT : ""}`
|
|
15212
15530
|
);
|
|
15213
15531
|
}
|
|
15214
15532
|
const ref = node.$ref;
|
|
@@ -17764,7 +18082,7 @@ async function withCliAuthRecoveryRetry(inspectorClient, serverConfig, redirectU
|
|
|
17764
18082
|
import { LoggingLevelSchema } from "@modelcontextprotocol/core";
|
|
17765
18083
|
|
|
17766
18084
|
// ../../core/node/version.ts
|
|
17767
|
-
import { readFileSync as
|
|
18085
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
17768
18086
|
import { dirname as dirname5, join as join3, parse } from "path";
|
|
17769
18087
|
import { fileURLToPath } from "url";
|
|
17770
18088
|
var ROOT_PACKAGE_NAME = "@modelcontextprotocol/inspector";
|
|
@@ -17776,7 +18094,7 @@ function readInspectorVersion(callerUrl) {
|
|
|
17776
18094
|
let manifest;
|
|
17777
18095
|
try {
|
|
17778
18096
|
manifest = JSON.parse(
|
|
17779
|
-
|
|
18097
|
+
readFileSync4(join3(dir, "package.json"), "utf-8")
|
|
17780
18098
|
);
|
|
17781
18099
|
} catch {
|
|
17782
18100
|
}
|