@modelcontextprotocol/inspector 2.10.0 → 2.11.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 +7 -5
- package/clients/cli/build/index.js +3087 -2771
- package/clients/launcher/build/index.js +4 -1
- package/clients/mcpdo/build/{chunk-2E5EZYNU.js → chunk-O6RN6WMD.js} +362 -78
- package/clients/mcpdo/build/mcp-bin.js +7 -21
- package/clients/mcpdo/build/mcpdod.js +1 -1
- package/clients/tui/build/index.js +421 -119
- package/clients/web/build/index.js +148 -43
- package/clients/web/dist/assets/index-B79AqubU.js +1587 -0
- package/clients/web/dist/assets/{markdown-CZCm46w1.js → markdown-BlYcOtJg.js} +1 -1
- package/clients/web/dist/assets/{prism-light-B0A727v7.js → prism-light-J_CKjs7N.js} +2 -2
- package/clients/web/dist/index.html +1 -1
- package/package.json +15 -9
- package/scripts/install-clients.mjs +68 -28
- package/clients/web/dist/assets/index-DgDEc2gL.js +0 -1589
|
@@ -2044,16 +2044,30 @@ function buildConfigFromOptions(options) {
|
|
|
2044
2044
|
const target = options.target ?? [];
|
|
2045
2045
|
const first = target[0];
|
|
2046
2046
|
const rest = target.slice(1);
|
|
2047
|
+
if (first && options.serverUrl) {
|
|
2048
|
+
throw new Error(
|
|
2049
|
+
`Cannot combine a positional target (${first}) with --server-url (${options.serverUrl}). Pass one or the other.`
|
|
2050
|
+
);
|
|
2051
|
+
}
|
|
2047
2052
|
const urlFromTarget = first && (first.startsWith("http://") || first.startsWith("https://")) ? first : null;
|
|
2048
2053
|
const url = urlFromTarget ?? options.serverUrl ?? null;
|
|
2049
2054
|
if (url) {
|
|
2050
|
-
if (rest.length > 0
|
|
2055
|
+
if (rest.length > 0) {
|
|
2051
2056
|
throw new Error("Arguments cannot be passed to a URL-based MCP server.");
|
|
2052
2057
|
}
|
|
2058
|
+
if (options.transport === "stdio") {
|
|
2059
|
+
throw new Error(
|
|
2060
|
+
`stdio transport cannot be used with a URL (${url}). Use --transport http or --transport sse, or pass a local command instead.`
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2053
2063
|
let transportType;
|
|
2054
2064
|
const t = options.transport === "http" ? "streamable-http" : options.transport;
|
|
2055
2065
|
if (t === "sse" || t === "streamable-http") {
|
|
2056
2066
|
transportType = t;
|
|
2067
|
+
} else if (t) {
|
|
2068
|
+
throw new Error(
|
|
2069
|
+
`Invalid transport type: ${t}. Valid types are: sse, http, stdio`
|
|
2070
|
+
);
|
|
2057
2071
|
} else {
|
|
2058
2072
|
const u = new URL(url);
|
|
2059
2073
|
if (u.pathname.endsWith("/mcp")) {
|
|
@@ -4552,7 +4566,10 @@ function parseAuthChallengeFromResponse(response, context) {
|
|
|
4552
4566
|
if (status !== 401 && status !== 403) {
|
|
4553
4567
|
return void 0;
|
|
4554
4568
|
}
|
|
4555
|
-
const wwwAuthenticate = response.headers.get("WWW-Authenticate")
|
|
4569
|
+
const wwwAuthenticate = response.headers.get("WWW-Authenticate") || void 0;
|
|
4570
|
+
if (status === 403 && !wwwAuthenticate) {
|
|
4571
|
+
return void 0;
|
|
4572
|
+
}
|
|
4556
4573
|
const bearer = wwwAuthenticate ? parseWwwAuthenticateBearer(wwwAuthenticate) : {};
|
|
4557
4574
|
const requiredScopes = parseScopeString(bearer.scope);
|
|
4558
4575
|
return {
|
|
@@ -5249,6 +5266,34 @@ function buildRunnerClientAuthOptions(clientConfig, savedSettings, cliOverrides)
|
|
|
5249
5266
|
};
|
|
5250
5267
|
}
|
|
5251
5268
|
|
|
5269
|
+
// ../../core/node/version.ts
|
|
5270
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5271
|
+
import { dirname as dirname5, join as join3, parse } from "path";
|
|
5272
|
+
import { fileURLToPath } from "url";
|
|
5273
|
+
var ROOT_PACKAGE_NAME = "@modelcontextprotocol/inspector";
|
|
5274
|
+
function readInspectorVersion(callerUrl) {
|
|
5275
|
+
const startDir = dirname5(fileURLToPath(callerUrl));
|
|
5276
|
+
const { root } = parse(startDir);
|
|
5277
|
+
let dir = startDir;
|
|
5278
|
+
for (; ; ) {
|
|
5279
|
+
let manifest;
|
|
5280
|
+
try {
|
|
5281
|
+
manifest = JSON.parse(
|
|
5282
|
+
readFileSync4(join3(dir, "package.json"), "utf-8")
|
|
5283
|
+
);
|
|
5284
|
+
} catch {
|
|
5285
|
+
}
|
|
5286
|
+
if (manifest?.name === ROOT_PACKAGE_NAME && manifest.version) {
|
|
5287
|
+
return manifest.version;
|
|
5288
|
+
}
|
|
5289
|
+
if (dir === root) break;
|
|
5290
|
+
dir = dirname5(dir);
|
|
5291
|
+
}
|
|
5292
|
+
throw new Error(
|
|
5293
|
+
`Could not locate the ${ROOT_PACKAGE_NAME} root package.json above ${startDir}`
|
|
5294
|
+
);
|
|
5295
|
+
}
|
|
5296
|
+
|
|
5252
5297
|
// ../../core/node/hostUrl.ts
|
|
5253
5298
|
import { isIPv6 } from "net";
|
|
5254
5299
|
function stripBrackets(host) {
|
|
@@ -6923,6 +6968,84 @@ function isJsonDocumentResponse(contentType) {
|
|
|
6923
6968
|
const mediaType = contentType.split(";")[0].trim().toLowerCase();
|
|
6924
6969
|
return mediaType === "application/json" || mediaType.endsWith("+json");
|
|
6925
6970
|
}
|
|
6971
|
+
var SYNTHESIZED_METADATA_HEADER = "x-inspector-oauth-metadata-synthesized";
|
|
6972
|
+
var synthesizedResponses = /* @__PURE__ */ new WeakSet();
|
|
6973
|
+
function isSynthesizedMetadataResponse(response) {
|
|
6974
|
+
return synthesizedResponses.has(response);
|
|
6975
|
+
}
|
|
6976
|
+
var OIDC_WELL_KNOWN = "/.well-known/openid-configuration";
|
|
6977
|
+
var RFC8414_WELL_KNOWN = "/.well-known/oauth-authorization-server";
|
|
6978
|
+
function continuesDiscovery(status) {
|
|
6979
|
+
return status < 500 || status === 502;
|
|
6980
|
+
}
|
|
6981
|
+
function authorizationServerForFinalCandidate(url) {
|
|
6982
|
+
let parsed;
|
|
6983
|
+
try {
|
|
6984
|
+
parsed = new URL(url);
|
|
6985
|
+
} catch {
|
|
6986
|
+
return void 0;
|
|
6987
|
+
}
|
|
6988
|
+
const { pathname } = parsed;
|
|
6989
|
+
if (pathname === OIDC_WELL_KNOWN) return parsed.origin;
|
|
6990
|
+
if (!pathname.endsWith(OIDC_WELL_KNOWN)) return void 0;
|
|
6991
|
+
if (pathname.startsWith(`${RFC8414_WELL_KNOWN}/`) || pathname.startsWith(`${OIDC_WELL_KNOWN}/`)) {
|
|
6992
|
+
return void 0;
|
|
6993
|
+
}
|
|
6994
|
+
return `${parsed.origin}${pathname.slice(0, -OIDC_WELL_KNOWN.length)}`;
|
|
6995
|
+
}
|
|
6996
|
+
function synthesizeAuthorizationServerMetadata(issuer, overrides) {
|
|
6997
|
+
return {
|
|
6998
|
+
issuer,
|
|
6999
|
+
authorization_endpoint: overrides.authorizationUrl ?? new URL("/authorize", issuer).href,
|
|
7000
|
+
token_endpoint: overrides.tokenUrl ?? new URL("/token", issuer).href,
|
|
7001
|
+
registration_endpoint: new URL("/register", issuer).href,
|
|
7002
|
+
response_types_supported: ["code"],
|
|
7003
|
+
code_challenge_methods_supported: ["S256"]
|
|
7004
|
+
};
|
|
7005
|
+
}
|
|
7006
|
+
function requestMethodOf(input, init) {
|
|
7007
|
+
if (init?.method) return init.method.toUpperCase();
|
|
7008
|
+
if (typeof input !== "string" && !(input instanceof URL)) {
|
|
7009
|
+
return input.method.toUpperCase();
|
|
7010
|
+
}
|
|
7011
|
+
return "GET";
|
|
7012
|
+
}
|
|
7013
|
+
function acceptsJson(input, init) {
|
|
7014
|
+
let accept = null;
|
|
7015
|
+
if (init?.headers) {
|
|
7016
|
+
accept = new Headers(init.headers).get("accept");
|
|
7017
|
+
} else if (typeof input !== "string" && !(input instanceof URL)) {
|
|
7018
|
+
accept = input.headers.get("accept");
|
|
7019
|
+
}
|
|
7020
|
+
if (!accept) return false;
|
|
7021
|
+
const mediaTypes = accept.split(",").map((part) => part.split(";")[0].trim().toLowerCase());
|
|
7022
|
+
return mediaTypes.includes("application/json") && !mediaTypes.includes("text/event-stream");
|
|
7023
|
+
}
|
|
7024
|
+
function synthesizedMetadataResponse(input, init, response, overrides) {
|
|
7025
|
+
if (!continuesDiscovery(response.status)) return void 0;
|
|
7026
|
+
if (requestMethodOf(input, init) !== "GET") return void 0;
|
|
7027
|
+
if (!acceptsJson(input, init)) return void 0;
|
|
7028
|
+
const issuer = authorizationServerForFinalCandidate(requestUrlOf(input));
|
|
7029
|
+
if (!issuer) return void 0;
|
|
7030
|
+
void response.body?.cancel().catch(() => {
|
|
7031
|
+
});
|
|
7032
|
+
console.warn(
|
|
7033
|
+
`[oauth] ${issuer} publishes no authorization server metadata; using the configured endpoint override(s) in place of the SDK's /authorize and /token fallbacks.`
|
|
7034
|
+
);
|
|
7035
|
+
const synthesized = new Response(
|
|
7036
|
+
JSON.stringify(synthesizeAuthorizationServerMetadata(issuer, overrides)),
|
|
7037
|
+
{
|
|
7038
|
+
status: 200,
|
|
7039
|
+
statusText: "OK",
|
|
7040
|
+
headers: {
|
|
7041
|
+
"content-type": "application/json",
|
|
7042
|
+
[SYNTHESIZED_METADATA_HEADER]: "endpoint-overrides"
|
|
7043
|
+
}
|
|
7044
|
+
}
|
|
7045
|
+
);
|
|
7046
|
+
synthesizedResponses.add(synthesized);
|
|
7047
|
+
return synthesized;
|
|
7048
|
+
}
|
|
6926
7049
|
function responseWithBody(response, body) {
|
|
6927
7050
|
const headers = new Headers(response.headers);
|
|
6928
7051
|
headers.delete("content-length");
|
|
@@ -6950,7 +7073,9 @@ function withOAuthEndpointOverrides(fetchFn, getOverrides) {
|
|
|
6950
7073
|
const overrides = resolveOverrides();
|
|
6951
7074
|
if (!overrides) return response;
|
|
6952
7075
|
if (!isAuthorizationServerMetadataRequest(input)) return response;
|
|
6953
|
-
if (!response.ok)
|
|
7076
|
+
if (!response.ok) {
|
|
7077
|
+
return synthesizedMetadataResponse(input, init, response, overrides) ?? response;
|
|
7078
|
+
}
|
|
6954
7079
|
if (!isJsonDocumentResponse(response.headers.get("content-type")))
|
|
6955
7080
|
return response;
|
|
6956
7081
|
let parsed;
|
|
@@ -7126,8 +7251,8 @@ import {
|
|
|
7126
7251
|
OAuthMetadataSchema,
|
|
7127
7252
|
OpenIdProviderDiscoveryMetadataSchema
|
|
7128
7253
|
} from "@modelcontextprotocol/core";
|
|
7129
|
-
var
|
|
7130
|
-
var
|
|
7254
|
+
var RFC8414_WELL_KNOWN2 = "/.well-known/oauth-authorization-server";
|
|
7255
|
+
var OIDC_WELL_KNOWN2 = "/.well-known/openid-configuration";
|
|
7131
7256
|
var COMPAT_SOURCE_HEADER = "x-inspector-oauth-metadata-source";
|
|
7132
7257
|
function oidcDiscoveryCandidates(rfc8414Url) {
|
|
7133
7258
|
let url;
|
|
@@ -7136,16 +7261,16 @@ function oidcDiscoveryCandidates(rfc8414Url) {
|
|
|
7136
7261
|
} catch {
|
|
7137
7262
|
return [];
|
|
7138
7263
|
}
|
|
7139
|
-
if (!url.pathname.startsWith(
|
|
7140
|
-
const path7 = url.pathname.slice(
|
|
7264
|
+
if (!url.pathname.startsWith(RFC8414_WELL_KNOWN2)) return [];
|
|
7265
|
+
const path7 = url.pathname.slice(RFC8414_WELL_KNOWN2.length);
|
|
7141
7266
|
if (path7 !== "" && !path7.startsWith("/")) return [];
|
|
7142
|
-
if (path7 === "") return [new URL(
|
|
7267
|
+
if (path7 === "") return [new URL(OIDC_WELL_KNOWN2, url.origin).href];
|
|
7143
7268
|
return [
|
|
7144
|
-
new URL(`${
|
|
7145
|
-
new URL(`${path7}${
|
|
7269
|
+
new URL(`${OIDC_WELL_KNOWN2}${path7}`, url.origin).href,
|
|
7270
|
+
new URL(`${path7}${OIDC_WELL_KNOWN2}`, url.origin).href
|
|
7146
7271
|
];
|
|
7147
7272
|
}
|
|
7148
|
-
function
|
|
7273
|
+
function continuesDiscovery2(status) {
|
|
7149
7274
|
return status >= 400 && status < 500 || status === 502;
|
|
7150
7275
|
}
|
|
7151
7276
|
async function releaseBody(response) {
|
|
@@ -7171,7 +7296,7 @@ function discoveryHeaders(input, init) {
|
|
|
7171
7296
|
}
|
|
7172
7297
|
return new Headers();
|
|
7173
7298
|
}
|
|
7174
|
-
function
|
|
7299
|
+
function requestMethodOf2(input, init) {
|
|
7175
7300
|
if (init?.method) return init.method.toUpperCase();
|
|
7176
7301
|
if (typeof input !== "string" && !(input instanceof URL)) {
|
|
7177
7302
|
return input.method.toUpperCase();
|
|
@@ -7182,8 +7307,8 @@ function withRfc8414OidcCompat(fetchFn) {
|
|
|
7182
7307
|
return async (input, init) => {
|
|
7183
7308
|
const response = await fetchFn(input, init);
|
|
7184
7309
|
if (response.ok) return response;
|
|
7185
|
-
if (!
|
|
7186
|
-
if (
|
|
7310
|
+
if (!continuesDiscovery2(response.status)) return response;
|
|
7311
|
+
if (requestMethodOf2(input, init) !== "GET") return response;
|
|
7187
7312
|
const candidates = oidcDiscoveryCandidates(requestUrlOf3(input));
|
|
7188
7313
|
if (candidates.length === 0) return response;
|
|
7189
7314
|
const headers = discoveryHeaders(input, init);
|
|
@@ -7213,7 +7338,7 @@ function withRfc8414OidcCompat(fetchFn) {
|
|
|
7213
7338
|
releaseBodyDetached(response);
|
|
7214
7339
|
throw callerSignal.reason;
|
|
7215
7340
|
}
|
|
7216
|
-
if (
|
|
7341
|
+
if (continuesDiscovery2(probe.status)) continue;
|
|
7217
7342
|
return response;
|
|
7218
7343
|
}
|
|
7219
7344
|
let parsed;
|
|
@@ -7233,6 +7358,16 @@ function withRfc8414OidcCompat(fetchFn) {
|
|
|
7233
7358
|
}
|
|
7234
7359
|
const source = probe.url || candidate;
|
|
7235
7360
|
releaseBodyDetached(response);
|
|
7361
|
+
if (isSynthesizedMetadataResponse(probe)) {
|
|
7362
|
+
return new Response(body, {
|
|
7363
|
+
status: 200,
|
|
7364
|
+
statusText: "OK",
|
|
7365
|
+
headers: {
|
|
7366
|
+
"content-type": "application/json",
|
|
7367
|
+
[SYNTHESIZED_METADATA_HEADER]: "endpoint-overrides"
|
|
7368
|
+
}
|
|
7369
|
+
});
|
|
7370
|
+
}
|
|
7236
7371
|
console.warn(
|
|
7237
7372
|
`[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.`
|
|
7238
7373
|
);
|
|
@@ -8213,10 +8348,10 @@ async function resolveIdpMetadata(issuer, storage, fetchFn) {
|
|
|
8213
8348
|
}
|
|
8214
8349
|
return discoverIdpMetadata(issuer, fetchFn);
|
|
8215
8350
|
}
|
|
8216
|
-
var
|
|
8351
|
+
var OIDC_WELL_KNOWN3 = "/.well-known/openid-configuration";
|
|
8217
8352
|
async function fetchOpenIdConfiguration(issuer, issuerUrl, fetchFn) {
|
|
8218
8353
|
const url = new URL(
|
|
8219
|
-
`${issuerUrl.pathname.replace(/\/$/, "")}${
|
|
8354
|
+
`${issuerUrl.pathname.replace(/\/$/, "")}${OIDC_WELL_KNOWN3}`,
|
|
8220
8355
|
issuerUrl.origin
|
|
8221
8356
|
);
|
|
8222
8357
|
try {
|
|
@@ -9799,7 +9934,11 @@ var OAuthManager = class {
|
|
|
9799
9934
|
};
|
|
9800
9935
|
|
|
9801
9936
|
// ../../core/mcp/remote/remoteClientTransport.ts
|
|
9802
|
-
import {
|
|
9937
|
+
import {
|
|
9938
|
+
SdkError as SdkError5,
|
|
9939
|
+
SdkErrorCode as SdkErrorCode5,
|
|
9940
|
+
SdkHttpError
|
|
9941
|
+
} from "@modelcontextprotocol/client";
|
|
9803
9942
|
|
|
9804
9943
|
// ../../core/mcp/remote/types.ts
|
|
9805
9944
|
function oauthTokensToRemoteAuthState(tokens) {
|
|
@@ -9854,6 +9993,17 @@ function isConnectAuthChallenge(json) {
|
|
|
9854
9993
|
function isConnectTransportError(json) {
|
|
9855
9994
|
return typeof json === "object" && json !== null && "ok" in json && json.ok === false && "kind" in json && json.kind === "transport_error";
|
|
9856
9995
|
}
|
|
9996
|
+
var SDK_ERROR_CODES = new Set(
|
|
9997
|
+
Object.values(SdkErrorCode5)
|
|
9998
|
+
);
|
|
9999
|
+
function toSdkHttpError(message, http) {
|
|
10000
|
+
const code = SDK_ERROR_CODES.has(http.code) ? http.code : SdkErrorCode5.ClientHttpNotImplemented;
|
|
10001
|
+
return new SdkHttpError(code, message, {
|
|
10002
|
+
status: http.status,
|
|
10003
|
+
...http.statusText !== void 0 && { statusText: http.statusText },
|
|
10004
|
+
...http.text !== void 0 && { text: http.text }
|
|
10005
|
+
});
|
|
10006
|
+
}
|
|
9857
10007
|
function legacySessionId(json) {
|
|
9858
10008
|
if (typeof json === "object" && json !== null && "sessionId" in json && typeof json.sessionId === "string" && !("ok" in json)) {
|
|
9859
10009
|
return json.sessionId;
|
|
@@ -10354,6 +10504,9 @@ var RemoteClientTransport = class {
|
|
|
10354
10504
|
});
|
|
10355
10505
|
return;
|
|
10356
10506
|
}
|
|
10507
|
+
if (json.http) {
|
|
10508
|
+
throw toSdkHttpError(json.error, json.http);
|
|
10509
|
+
}
|
|
10357
10510
|
throw new Error(json.error);
|
|
10358
10511
|
}
|
|
10359
10512
|
async requestSend(message, options) {
|
|
@@ -10426,8 +10579,9 @@ var corePackageJson = {
|
|
|
10426
10579
|
version: "0.0.0"
|
|
10427
10580
|
};
|
|
10428
10581
|
var MAX_URL_ELICITATION_RETRIES = 5;
|
|
10429
|
-
|
|
10430
|
-
|
|
10582
|
+
var WEB_CONNECTION_TIMEOUT_REMEDY = "To accommodate a slower server, you may increase the timeout value in Server Settings.";
|
|
10583
|
+
function connectionTimeoutMessage(timeoutMs, remedy = WEB_CONNECTION_TIMEOUT_REMEDY) {
|
|
10584
|
+
return `Connection timed out after ${timeoutMs} ms. ${remedy}`;
|
|
10431
10585
|
}
|
|
10432
10586
|
function createPendingAbortError() {
|
|
10433
10587
|
return new Error("Pending request aborted");
|
|
@@ -10518,6 +10672,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10518
10672
|
/** Dedupes concurrent ambient auth challenges (reason + scopes). */
|
|
10519
10673
|
ambientAuthChallengeInFlight = /* @__PURE__ */ new Map();
|
|
10520
10674
|
pipeStderr;
|
|
10675
|
+
connectionTimeoutRemedy;
|
|
10521
10676
|
initialLoggingLevel;
|
|
10522
10677
|
// Modern-era per-request log level (#1629). On 2026-07-28 servers
|
|
10523
10678
|
// `logging/setLevel` is gone; the client opts into logs per request via the
|
|
@@ -10755,6 +10910,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10755
10910
|
this.fetchFn = options.environment.fetch;
|
|
10756
10911
|
this.logger = options.environment.logger ?? silentLogger;
|
|
10757
10912
|
this.pipeStderr = options.pipeStderr ?? false;
|
|
10913
|
+
this.connectionTimeoutRemedy = options.connectionTimeoutRemedy ?? WEB_CONNECTION_TIMEOUT_REMEDY;
|
|
10758
10914
|
this.initialLoggingLevel = options.initialLoggingLevel;
|
|
10759
10915
|
this.sample = options.sample ?? true;
|
|
10760
10916
|
this.elicit = options.elicit ?? true;
|
|
@@ -11496,9 +11652,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11496
11652
|
// for that upstream gap and should be deleted with it.
|
|
11497
11653
|
//
|
|
11498
11654
|
// Known, accepted side effect of turning intercept on with no stored
|
|
11499
|
-
// tokens: `parseAuthChallengeFromResponse` treats 403
|
|
11500
|
-
// too, so a probe answered
|
|
11501
|
-
//
|
|
11655
|
+
// tokens: `parseAuthChallengeFromResponse` treats a 403 carrying
|
|
11656
|
+
// `WWW-Authenticate` as a challenge too, so a probe answered that way
|
|
11657
|
+
// for a *non-auth* reason (a gateway rejecting the unknown
|
|
11658
|
+
// `server/discover` method, say) now starts OAuth
|
|
11502
11659
|
// discovery instead of letting "auto" fall back to the legacy
|
|
11503
11660
|
// `initialize`. The outcome is a surfaced `oauthError`, not a hang, and
|
|
11504
11661
|
// it goes away with this clause.
|
|
@@ -11565,7 +11722,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11565
11722
|
const timeoutPromise = new Promise((_, reject) => {
|
|
11566
11723
|
timer = setTimeout(() => {
|
|
11567
11724
|
connectTimedOut = true;
|
|
11568
|
-
reject(
|
|
11725
|
+
reject(
|
|
11726
|
+
new Error(
|
|
11727
|
+
connectionTimeoutMessage(
|
|
11728
|
+
connectTimeoutMs,
|
|
11729
|
+
this.connectionTimeoutRemedy
|
|
11730
|
+
)
|
|
11731
|
+
)
|
|
11732
|
+
);
|
|
11569
11733
|
}, connectTimeoutMs);
|
|
11570
11734
|
});
|
|
11571
11735
|
try {
|
|
@@ -14843,6 +15007,11 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
14843
15007
|
// rendered by the list panel instead of vanishing into an unhandled
|
|
14844
15008
|
// rejection — which read as "this server has no tools" (#1953).
|
|
14845
15009
|
error = null;
|
|
15010
|
+
// Whether a fetch has committed a list this session. Until it has, the empty
|
|
15011
|
+
// `items` mean "not loaded yet", not "the server has none" — a distinction a
|
|
15012
|
+
// count of the list cannot make from `items` alone (#2607). Set and cleared
|
|
15013
|
+
// just before the `*Change` dispatch, so a reader keyed on that event sees it.
|
|
15014
|
+
loaded = false;
|
|
14846
15015
|
config;
|
|
14847
15016
|
// Debounce a burst of `list_changed` notifications into a single
|
|
14848
15017
|
// refresh (or one indicator light) once it settles.
|
|
@@ -14878,6 +15047,7 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
14878
15047
|
this.listChangedTimer = null;
|
|
14879
15048
|
}
|
|
14880
15049
|
this.items = [];
|
|
15050
|
+
this.loaded = false;
|
|
14881
15051
|
this.dispatchChange();
|
|
14882
15052
|
this.setListChanged(false);
|
|
14883
15053
|
this.setError(null);
|
|
@@ -14936,6 +15106,14 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
14936
15106
|
getItems() {
|
|
14937
15107
|
return [...this.items];
|
|
14938
15108
|
}
|
|
15109
|
+
/**
|
|
15110
|
+
* Whether a fetch has committed a list since the last connect. False while
|
|
15111
|
+
* the connect-time load is in flight, when it was deferred (paginated mode),
|
|
15112
|
+
* and after a disconnect. Changes only alongside the `*Change` event (#2607).
|
|
15113
|
+
*/
|
|
15114
|
+
isLoaded() {
|
|
15115
|
+
return this.loaded;
|
|
15116
|
+
}
|
|
14939
15117
|
/** Whether a `list_changed` arrived since the last refresh (indicator on). */
|
|
14940
15118
|
getListChanged() {
|
|
14941
15119
|
return this.listChanged;
|
|
@@ -15029,6 +15207,7 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
15029
15207
|
/** Commit a fetched list as the current one and notify subscribers. */
|
|
15030
15208
|
applyItems(items) {
|
|
15031
15209
|
this.items = items;
|
|
15210
|
+
this.loaded = true;
|
|
15032
15211
|
this.dispatchChange();
|
|
15033
15212
|
}
|
|
15034
15213
|
dispatchChange() {
|
|
@@ -15039,6 +15218,7 @@ var ManagedListState = class extends TypedEventTarget {
|
|
|
15039
15218
|
this.unsubscribe?.();
|
|
15040
15219
|
this.unsubscribe = null;
|
|
15041
15220
|
this.items = [];
|
|
15221
|
+
this.loaded = false;
|
|
15042
15222
|
}
|
|
15043
15223
|
};
|
|
15044
15224
|
|
|
@@ -16087,6 +16267,7 @@ function useListError(state) {
|
|
|
16087
16267
|
var NO_TOOLS = [];
|
|
16088
16268
|
var readTools = (state) => state.getTools();
|
|
16089
16269
|
var readListChanged = (state) => state.getListChanged();
|
|
16270
|
+
var readLoaded = (state) => state.isLoaded();
|
|
16090
16271
|
function useManagedTools(client, managedToolsState) {
|
|
16091
16272
|
const tools = useStoreSnapshot(
|
|
16092
16273
|
managedToolsState,
|
|
@@ -16100,6 +16281,12 @@ function useManagedTools(client, managedToolsState) {
|
|
|
16100
16281
|
readListChanged,
|
|
16101
16282
|
false
|
|
16102
16283
|
);
|
|
16284
|
+
const loaded = useStoreSnapshot(
|
|
16285
|
+
managedToolsState,
|
|
16286
|
+
"toolsChange",
|
|
16287
|
+
readLoaded,
|
|
16288
|
+
false
|
|
16289
|
+
);
|
|
16103
16290
|
const error = useListError(managedToolsState);
|
|
16104
16291
|
const refresh = useCallback4(async () => {
|
|
16105
16292
|
if (!managedToolsState || !client) return NO_TOOLS;
|
|
@@ -16109,7 +16296,7 @@ function useManagedTools(client, managedToolsState) {
|
|
|
16109
16296
|
const clearListChanged = useCallback4(() => {
|
|
16110
16297
|
managedToolsState?.clearListChanged();
|
|
16111
16298
|
}, [managedToolsState]);
|
|
16112
|
-
return { tools, error, listChanged, refresh, clearListChanged };
|
|
16299
|
+
return { tools, error, listChanged, loaded, refresh, clearListChanged };
|
|
16113
16300
|
}
|
|
16114
16301
|
|
|
16115
16302
|
// ../../core/react/useManagedResources.ts
|
|
@@ -16117,6 +16304,7 @@ import { useCallback as useCallback5 } from "react";
|
|
|
16117
16304
|
var NO_RESOURCES = [];
|
|
16118
16305
|
var readResources = (state) => state.getResources();
|
|
16119
16306
|
var readListChanged2 = (state) => state.getListChanged();
|
|
16307
|
+
var readLoaded2 = (state) => state.isLoaded();
|
|
16120
16308
|
function useManagedResources(client, managedResourcesState) {
|
|
16121
16309
|
const resources = useStoreSnapshot(
|
|
16122
16310
|
managedResourcesState,
|
|
@@ -16130,6 +16318,12 @@ function useManagedResources(client, managedResourcesState) {
|
|
|
16130
16318
|
readListChanged2,
|
|
16131
16319
|
false
|
|
16132
16320
|
);
|
|
16321
|
+
const loaded = useStoreSnapshot(
|
|
16322
|
+
managedResourcesState,
|
|
16323
|
+
"resourcesChange",
|
|
16324
|
+
readLoaded2,
|
|
16325
|
+
false
|
|
16326
|
+
);
|
|
16133
16327
|
const error = useListError(managedResourcesState);
|
|
16134
16328
|
const refresh = useCallback5(async () => {
|
|
16135
16329
|
if (!managedResourcesState || !client) return NO_RESOURCES;
|
|
@@ -16139,7 +16333,7 @@ function useManagedResources(client, managedResourcesState) {
|
|
|
16139
16333
|
const clearListChanged = useCallback5(() => {
|
|
16140
16334
|
managedResourcesState?.clearListChanged();
|
|
16141
16335
|
}, [managedResourcesState]);
|
|
16142
|
-
return { resources, error, listChanged, refresh, clearListChanged };
|
|
16336
|
+
return { resources, error, listChanged, loaded, refresh, clearListChanged };
|
|
16143
16337
|
}
|
|
16144
16338
|
|
|
16145
16339
|
// ../../core/react/useManagedResourceTemplates.ts
|
|
@@ -16168,6 +16362,7 @@ import { useCallback as useCallback7 } from "react";
|
|
|
16168
16362
|
var NO_PROMPTS = [];
|
|
16169
16363
|
var readPrompts = (state) => state.getPrompts();
|
|
16170
16364
|
var readListChanged3 = (state) => state.getListChanged();
|
|
16365
|
+
var readLoaded3 = (state) => state.isLoaded();
|
|
16171
16366
|
function useManagedPrompts(client, managedPromptsState) {
|
|
16172
16367
|
const prompts = useStoreSnapshot(
|
|
16173
16368
|
managedPromptsState,
|
|
@@ -16181,6 +16376,12 @@ function useManagedPrompts(client, managedPromptsState) {
|
|
|
16181
16376
|
readListChanged3,
|
|
16182
16377
|
false
|
|
16183
16378
|
);
|
|
16379
|
+
const loaded = useStoreSnapshot(
|
|
16380
|
+
managedPromptsState,
|
|
16381
|
+
"promptsChange",
|
|
16382
|
+
readLoaded3,
|
|
16383
|
+
false
|
|
16384
|
+
);
|
|
16184
16385
|
const error = useListError(managedPromptsState);
|
|
16185
16386
|
const refresh = useCallback7(async () => {
|
|
16186
16387
|
if (!managedPromptsState || !client) return NO_PROMPTS;
|
|
@@ -16190,7 +16391,7 @@ function useManagedPrompts(client, managedPromptsState) {
|
|
|
16190
16391
|
const clearListChanged = useCallback7(() => {
|
|
16191
16392
|
managedPromptsState?.clearListChanged();
|
|
16192
16393
|
}, [managedPromptsState]);
|
|
16193
|
-
return { prompts, error, listChanged, refresh, clearListChanged };
|
|
16394
|
+
return { prompts, error, listChanged, loaded, refresh, clearListChanged };
|
|
16194
16395
|
}
|
|
16195
16396
|
|
|
16196
16397
|
// ../../core/react/useManagedSkills.ts
|
|
@@ -16367,34 +16568,6 @@ function classifyAuthorizationServerMismatch(err) {
|
|
|
16367
16568
|
};
|
|
16368
16569
|
}
|
|
16369
16570
|
|
|
16370
|
-
// ../../core/node/version.ts
|
|
16371
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
16372
|
-
import { dirname as dirname5, join as join3, parse } from "path";
|
|
16373
|
-
import { fileURLToPath } from "url";
|
|
16374
|
-
var ROOT_PACKAGE_NAME = "@modelcontextprotocol/inspector";
|
|
16375
|
-
function readInspectorVersion(callerUrl) {
|
|
16376
|
-
const startDir = dirname5(fileURLToPath(callerUrl));
|
|
16377
|
-
const { root } = parse(startDir);
|
|
16378
|
-
let dir = startDir;
|
|
16379
|
-
for (; ; ) {
|
|
16380
|
-
let manifest;
|
|
16381
|
-
try {
|
|
16382
|
-
manifest = JSON.parse(
|
|
16383
|
-
readFileSync4(join3(dir, "package.json"), "utf-8")
|
|
16384
|
-
);
|
|
16385
|
-
} catch {
|
|
16386
|
-
}
|
|
16387
|
-
if (manifest?.name === ROOT_PACKAGE_NAME && manifest.version) {
|
|
16388
|
-
return manifest.version;
|
|
16389
|
-
}
|
|
16390
|
-
if (dir === root) break;
|
|
16391
|
-
dir = dirname5(dir);
|
|
16392
|
-
}
|
|
16393
|
-
throw new Error(
|
|
16394
|
-
`Could not locate the ${ROOT_PACKAGE_NAME} root package.json above ${startDir}`
|
|
16395
|
-
);
|
|
16396
|
-
}
|
|
16397
|
-
|
|
16398
16571
|
// ../../core/auth/oauth-storage.ts
|
|
16399
16572
|
import {
|
|
16400
16573
|
OAuthClientInformationSchema as OAuthClientInformationSchema2,
|
|
@@ -17300,6 +17473,17 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
|
|
|
17300
17473
|
warnLedgerFailure("update", error);
|
|
17301
17474
|
}
|
|
17302
17475
|
}
|
|
17476
|
+
async function recordedNamespaces(stateFilePath) {
|
|
17477
|
+
try {
|
|
17478
|
+
const ledger = parseLedger(
|
|
17479
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
17480
|
+
);
|
|
17481
|
+
return [...ledger.keys()].reverse();
|
|
17482
|
+
} catch (error) {
|
|
17483
|
+
warnLedgerFailure("read", error);
|
|
17484
|
+
return [];
|
|
17485
|
+
}
|
|
17486
|
+
}
|
|
17303
17487
|
async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
17304
17488
|
let ledger;
|
|
17305
17489
|
let location;
|
|
@@ -17463,24 +17647,13 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
17463
17647
|
if (raw !== null && snapshot === null) {
|
|
17464
17648
|
throw new OAuthStateFileUnrecognizedError(filePath, "save");
|
|
17465
17649
|
}
|
|
17466
|
-
if (locked) {
|
|
17467
|
-
await purgeSupersededNamespaces(filePath, secretStore, void 0);
|
|
17468
|
-
}
|
|
17469
17650
|
const namespace = newSecretsNamespace();
|
|
17470
|
-
if (snapshot === null)
|
|
17471
|
-
|
|
17472
|
-
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
|
|
17476
|
-
})),
|
|
17477
|
-
...Object.keys(snapshot.idpSessions).map((issuer) => ({
|
|
17478
|
-
legacyId: oauthIdpSecretServerId(issuer),
|
|
17479
|
-
scopedId: oauthIdpSecretServerId(issuer, namespace),
|
|
17480
|
-
fields: [IDP_SESSION_FIELD]
|
|
17481
|
-
}))
|
|
17482
|
-
];
|
|
17483
|
-
if (moves.length === 0) return namespace;
|
|
17651
|
+
if (snapshot === null || Object.keys(snapshot.servers).length === 0 && Object.keys(snapshot.idpSessions).length === 0) {
|
|
17652
|
+
if (locked) {
|
|
17653
|
+
await purgeSupersededNamespaces(filePath, secretStore, void 0);
|
|
17654
|
+
}
|
|
17655
|
+
return namespace;
|
|
17656
|
+
}
|
|
17484
17657
|
if (!locked) {
|
|
17485
17658
|
throw new SecretStoreUnavailableError(
|
|
17486
17659
|
`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).`
|
|
@@ -17495,10 +17668,24 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
17495
17668
|
);
|
|
17496
17669
|
const touched = [];
|
|
17497
17670
|
try {
|
|
17498
|
-
|
|
17499
|
-
|
|
17500
|
-
|
|
17501
|
-
|
|
17671
|
+
const values = await readEntrySecrets(
|
|
17672
|
+
snapshot,
|
|
17673
|
+
secretStore,
|
|
17674
|
+
void 0,
|
|
17675
|
+
await recordedNamespaces(filePath)
|
|
17676
|
+
);
|
|
17677
|
+
const copies = [
|
|
17678
|
+
...[...values.servers].map(([url, secrets]) => ({
|
|
17679
|
+
scopedId: oauthSecretServerId(url, namespace),
|
|
17680
|
+
secrets
|
|
17681
|
+
})),
|
|
17682
|
+
...[...values.idpSessions].map(([issuer, secrets]) => ({
|
|
17683
|
+
scopedId: oauthIdpSecretServerId(issuer, namespace),
|
|
17684
|
+
secrets
|
|
17685
|
+
}))
|
|
17686
|
+
];
|
|
17687
|
+
for (const { scopedId, secrets } of copies) {
|
|
17688
|
+
for (const [field, value] of Object.entries(secrets)) {
|
|
17502
17689
|
touched.push({ serverId: scopedId, field, value: null });
|
|
17503
17690
|
await secretStore.set(scopedId, field, value);
|
|
17504
17691
|
}
|
|
@@ -17508,7 +17695,14 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
17508
17695
|
await restoreSecretFields(secretStore, touched, warnRestoreFailure);
|
|
17509
17696
|
throw error;
|
|
17510
17697
|
}
|
|
17511
|
-
|
|
17698
|
+
await purgeSupersededNamespaces(filePath, secretStore, namespace);
|
|
17699
|
+
const legacyIds = [
|
|
17700
|
+
...Object.keys(snapshot.servers).map((url) => oauthSecretServerId(url)),
|
|
17701
|
+
...Object.keys(snapshot.idpSessions).map(
|
|
17702
|
+
(issuer) => oauthIdpSecretServerId(issuer)
|
|
17703
|
+
)
|
|
17704
|
+
];
|
|
17705
|
+
for (const legacyId of legacyIds) {
|
|
17512
17706
|
try {
|
|
17513
17707
|
await secretStore.deleteAllForServer(legacyId);
|
|
17514
17708
|
} catch (error) {
|
|
@@ -17561,6 +17755,14 @@ function preserveNonDurableSecrets(diskSecrets, secrets) {
|
|
|
17561
17755
|
return keep;
|
|
17562
17756
|
}
|
|
17563
17757
|
async function writeOAuthSections(filePath, snapshot, sections, secretStore = defaultSecretStore()) {
|
|
17758
|
+
await writeOAuthSectionsFrom(
|
|
17759
|
+
filePath,
|
|
17760
|
+
async () => snapshot,
|
|
17761
|
+
sections,
|
|
17762
|
+
secretStore
|
|
17763
|
+
);
|
|
17764
|
+
}
|
|
17765
|
+
async function writeOAuthSectionsFrom(filePath, resolveSnapshot, sections, secretStore) {
|
|
17564
17766
|
const policy = getPersistTokensPolicy();
|
|
17565
17767
|
const durable = await secretStoreIsDurable(secretStore);
|
|
17566
17768
|
await withOAuthStateLock(filePath, "save", async (locked) => {
|
|
@@ -17644,6 +17846,7 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
17644
17846
|
unconfirmed = null;
|
|
17645
17847
|
namespace = diskNamespace;
|
|
17646
17848
|
}
|
|
17849
|
+
const snapshot = await resolveSnapshot(disk, namespace);
|
|
17647
17850
|
const effective = sections ? {
|
|
17648
17851
|
servers: sections.servers && [...new Set(sections.servers)],
|
|
17649
17852
|
idpSessions: sections.idpSessions && [
|
|
@@ -17677,7 +17880,8 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
17677
17880
|
const candidates = [
|
|
17678
17881
|
.../* @__PURE__ */ new Set([
|
|
17679
17882
|
...serverSecretFields(getOwnEntry(disk?.servers, url)),
|
|
17680
|
-
...serverSecretFields(next)
|
|
17883
|
+
...serverSecretFields(next),
|
|
17884
|
+
...[...restoreBaseline.values()].filter((prior) => prior.serverId === serverId).map(({ field }) => field)
|
|
17681
17885
|
])
|
|
17682
17886
|
];
|
|
17683
17887
|
const entryPrior = await snapshotSecretFields(
|
|
@@ -17686,8 +17890,12 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
17686
17890
|
candidates
|
|
17687
17891
|
);
|
|
17688
17892
|
priorSecrets.push(...entryPrior);
|
|
17893
|
+
const entryPriorValue = new Map(
|
|
17894
|
+
entryPrior.map(({ field, value }) => [field, value])
|
|
17895
|
+
);
|
|
17689
17896
|
if (next === void 0) {
|
|
17690
17897
|
for (const field of candidates) {
|
|
17898
|
+
if (entryPriorValue.get(field) === null) continue;
|
|
17691
17899
|
ourWrites.set(priorKey({ serverId, field }), null);
|
|
17692
17900
|
}
|
|
17693
17901
|
await secretStore.deleteAllForServer(serverId);
|
|
@@ -17702,10 +17910,9 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
|
|
|
17702
17910
|
}
|
|
17703
17911
|
const { residue, secrets } = splitServerOAuthState(next, policy);
|
|
17704
17912
|
for (const field of candidates) {
|
|
17705
|
-
|
|
17706
|
-
|
|
17707
|
-
|
|
17708
|
-
);
|
|
17913
|
+
const value = secrets[field] ?? null;
|
|
17914
|
+
if (value === entryPriorValue.get(field)) continue;
|
|
17915
|
+
ourWrites.set(priorKey({ serverId, field }), value);
|
|
17709
17916
|
}
|
|
17710
17917
|
setOwnEntry(merged.servers, url, residue);
|
|
17711
17918
|
if (!durable) {
|
|
@@ -17848,26 +18055,52 @@ function secretRequestsFor(snapshot, namespace) {
|
|
|
17848
18055
|
}
|
|
17849
18056
|
return requests;
|
|
17850
18057
|
}
|
|
17851
|
-
|
|
17852
|
-
const
|
|
17853
|
-
|
|
18058
|
+
function fillMissingSecrets(target, source) {
|
|
18059
|
+
for (const [field, value] of Object.entries(source ?? {})) {
|
|
18060
|
+
if (!Object.hasOwn(target, field)) setOwnEntry(target, field, value);
|
|
18061
|
+
}
|
|
18062
|
+
}
|
|
18063
|
+
async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks) {
|
|
18064
|
+
const servers = /* @__PURE__ */ new Map();
|
|
18065
|
+
const idpSessions = /* @__PURE__ */ new Map();
|
|
18066
|
+
for (const url of Object.keys(snapshot.servers)) servers.set(url, {});
|
|
18067
|
+
for (const issuer of Object.keys(snapshot.idpSessions)) {
|
|
18068
|
+
idpSessions.set(issuer, {});
|
|
18069
|
+
}
|
|
18070
|
+
for (const source of [namespace, ...fallbacks]) {
|
|
18071
|
+
const requests = secretRequestsFor(snapshot, source);
|
|
18072
|
+
if (requests.length === 0) break;
|
|
18073
|
+
const values = await secretStoreGetManyStrict(secretStore, requests);
|
|
18074
|
+
for (const [url, target] of servers) {
|
|
18075
|
+
fillMissingSecrets(target, values[oauthSecretServerId(url, source)]);
|
|
18076
|
+
}
|
|
18077
|
+
for (const [issuer, target] of idpSessions) {
|
|
18078
|
+
fillMissingSecrets(
|
|
18079
|
+
target,
|
|
18080
|
+
values[oauthIdpSecretServerId(issuer, source)]
|
|
18081
|
+
);
|
|
18082
|
+
}
|
|
18083
|
+
}
|
|
18084
|
+
return { servers, idpSessions };
|
|
18085
|
+
}
|
|
18086
|
+
async function joinSnapshot(snapshot, secretStore, namespace, fallbacks) {
|
|
18087
|
+
const values = await readEntrySecrets(
|
|
18088
|
+
snapshot,
|
|
18089
|
+
secretStore,
|
|
18090
|
+
namespace,
|
|
18091
|
+
fallbacks
|
|
18092
|
+
);
|
|
17854
18093
|
return {
|
|
17855
18094
|
servers: Object.fromEntries(
|
|
17856
18095
|
Object.entries(snapshot.servers).map(([url, state]) => [
|
|
17857
18096
|
url,
|
|
17858
|
-
joinServerOAuthState(
|
|
17859
|
-
state,
|
|
17860
|
-
values[oauthSecretServerId(url, namespace)] ?? {}
|
|
17861
|
-
)
|
|
18097
|
+
joinServerOAuthState(state, values.servers.get(url) ?? {})
|
|
17862
18098
|
])
|
|
17863
18099
|
),
|
|
17864
18100
|
idpSessions: Object.fromEntries(
|
|
17865
18101
|
Object.entries(snapshot.idpSessions).map(([issuer, session]) => [
|
|
17866
18102
|
issuer,
|
|
17867
|
-
joinIdpSession(
|
|
17868
|
-
session,
|
|
17869
|
-
values[oauthIdpSecretServerId(issuer, namespace)] ?? {}
|
|
17870
|
-
)
|
|
18103
|
+
joinIdpSession(session, values.idpSessions.get(issuer) ?? {})
|
|
17871
18104
|
])
|
|
17872
18105
|
)
|
|
17873
18106
|
};
|
|
@@ -17934,7 +18167,9 @@ async function readOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
|
17934
18167
|
warnMigrationFailure(error);
|
|
17935
18168
|
}
|
|
17936
18169
|
}
|
|
17937
|
-
|
|
18170
|
+
const namespace = parseSecretsNamespace(raw);
|
|
18171
|
+
const fallbacks = namespace === void 0 ? await recordedNamespaces(filePath) : [];
|
|
18172
|
+
return joinSnapshot(snapshot, secretStore, namespace, fallbacks);
|
|
17938
18173
|
});
|
|
17939
18174
|
}
|
|
17940
18175
|
async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
@@ -19069,8 +19304,24 @@ function copyViaOsc52(text, sink) {
|
|
|
19069
19304
|
large: payloadBytes > OSC52_LARGE_PAYLOAD_BYTES
|
|
19070
19305
|
};
|
|
19071
19306
|
}
|
|
19307
|
+
var copyDirs = /* @__PURE__ */ new Set();
|
|
19308
|
+
var exitCleanupRegistered = false;
|
|
19309
|
+
function removeCopyFiles() {
|
|
19310
|
+
for (const dir of copyDirs) {
|
|
19311
|
+
try {
|
|
19312
|
+
fs5.rmSync(dir, { recursive: true, force: true });
|
|
19313
|
+
} catch {
|
|
19314
|
+
}
|
|
19315
|
+
copyDirs.delete(dir);
|
|
19316
|
+
}
|
|
19317
|
+
}
|
|
19072
19318
|
function writeCopyFile(text, baseDir = os.tmpdir()) {
|
|
19073
19319
|
const dir = fs5.mkdtempSync(path6.join(baseDir, "mcp-inspector-copy-"));
|
|
19320
|
+
copyDirs.add(dir);
|
|
19321
|
+
if (!exitCleanupRegistered) {
|
|
19322
|
+
process.on("exit", removeCopyFiles);
|
|
19323
|
+
exitCleanupRegistered = true;
|
|
19324
|
+
}
|
|
19074
19325
|
const file = path6.join(dir, "value.txt");
|
|
19075
19326
|
fs5.writeFileSync(file, text, { encoding: "utf8", mode: 384 });
|
|
19076
19327
|
return file;
|
|
@@ -19107,7 +19358,10 @@ function useCopyKeys(value, label) {
|
|
|
19107
19358
|
if (key === SAVE_KEY) {
|
|
19108
19359
|
try {
|
|
19109
19360
|
const file = writeCopyFile(value);
|
|
19110
|
-
setStatus({
|
|
19361
|
+
setStatus({
|
|
19362
|
+
tone: "success",
|
|
19363
|
+
message: `Saved ${label} to ${file} (deleted when the TUI exits)`
|
|
19364
|
+
});
|
|
19111
19365
|
} catch (err) {
|
|
19112
19366
|
setStatus({
|
|
19113
19367
|
tone: "error",
|
|
@@ -19516,7 +19770,7 @@ import { useCallback as useCallback14, useEffect as useEffect4, useMemo, useStat
|
|
|
19516
19770
|
function printable(input) {
|
|
19517
19771
|
return input.replace(/[\u0000-\u001f\u007f]/g, "");
|
|
19518
19772
|
}
|
|
19519
|
-
function useListFilter(items, fields, { enabled, onEditingChange }) {
|
|
19773
|
+
function useListFilter(items, fields, { enabled, onEditingChange, onEscapeClaimChange }) {
|
|
19520
19774
|
const [query, setQuery] = useState7("");
|
|
19521
19775
|
const [editingRequested, setEditingRequested] = useState7(false);
|
|
19522
19776
|
const editing = editingRequested && enabled;
|
|
@@ -19525,6 +19779,13 @@ function useListFilter(items, fields, { enabled, onEditingChange }) {
|
|
|
19525
19779
|
onEditingChange?.(true);
|
|
19526
19780
|
return () => onEditingChange?.(false);
|
|
19527
19781
|
}, [editing, onEditingChange]);
|
|
19782
|
+
const active = query.trim() !== "";
|
|
19783
|
+
const claimsEscape = enabled && active && !editingRequested;
|
|
19784
|
+
useEffect4(() => {
|
|
19785
|
+
if (!claimsEscape) return;
|
|
19786
|
+
onEscapeClaimChange?.(true);
|
|
19787
|
+
return () => onEscapeClaimChange?.(false);
|
|
19788
|
+
}, [claimsEscape, onEscapeClaimChange]);
|
|
19528
19789
|
const { filtered, indices } = useMemo(() => {
|
|
19529
19790
|
const needle = query.trim().toLowerCase();
|
|
19530
19791
|
const filtered2 = [];
|
|
@@ -19545,6 +19806,10 @@ function useListFilter(items, fields, { enabled, onEditingChange }) {
|
|
|
19545
19806
|
setEditingRequested(true);
|
|
19546
19807
|
return true;
|
|
19547
19808
|
}
|
|
19809
|
+
if (key.escape && query.trim() !== "") {
|
|
19810
|
+
setQuery("");
|
|
19811
|
+
return true;
|
|
19812
|
+
}
|
|
19548
19813
|
return false;
|
|
19549
19814
|
}
|
|
19550
19815
|
if (key.escape) {
|
|
@@ -19568,12 +19833,12 @@ function useListFilter(items, fields, { enabled, onEditingChange }) {
|
|
|
19568
19833
|
if (text) setQuery((prev) => prev + text);
|
|
19569
19834
|
return true;
|
|
19570
19835
|
},
|
|
19571
|
-
[enabled, editingRequested]
|
|
19836
|
+
[enabled, editingRequested, query]
|
|
19572
19837
|
);
|
|
19573
19838
|
return {
|
|
19574
19839
|
query,
|
|
19575
19840
|
editing,
|
|
19576
|
-
active
|
|
19841
|
+
active,
|
|
19577
19842
|
items: filtered,
|
|
19578
19843
|
indices,
|
|
19579
19844
|
handleInput
|
|
@@ -19593,7 +19858,7 @@ function ListFilterBar({ query, editing, focused }) {
|
|
|
19593
19858
|
] }) : query.trim() !== "" ? /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate-end", children: [
|
|
19594
19859
|
/* @__PURE__ */ jsx6(Text5, { color: "cyan", children: "/" }),
|
|
19595
19860
|
query,
|
|
19596
|
-
/* @__PURE__ */ jsx6(Text5, { dimColor: true, children: " (/ to edit)" })
|
|
19861
|
+
/* @__PURE__ */ jsx6(Text5, { dimColor: true, children: focused ? " (/ edit \xB7 Esc clear)" : " (/ to edit)" })
|
|
19597
19862
|
] }) : focused ? /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: "/ to filter" }) : /* @__PURE__ */ jsx6(Text5, { children: " " }) });
|
|
19598
19863
|
}
|
|
19599
19864
|
function filterCount(active, shown, total) {
|
|
@@ -19618,7 +19883,8 @@ function ResourcesTab({
|
|
|
19618
19883
|
onFetchTemplate,
|
|
19619
19884
|
onAuthRecoveryRequired,
|
|
19620
19885
|
modalOpen = false,
|
|
19621
|
-
onFilterEditingChange
|
|
19886
|
+
onFilterEditingChange,
|
|
19887
|
+
onFilterEscapeClaimChange
|
|
19622
19888
|
}) {
|
|
19623
19889
|
const [error, setError] = useState8(null);
|
|
19624
19890
|
const [resourceContent, setResourceContent] = useState8(null);
|
|
@@ -19641,7 +19907,8 @@ function ResourcesTab({
|
|
|
19641
19907
|
const visibleCount = Math.max(1, height - 7 - LIST_FILTER_ROWS);
|
|
19642
19908
|
const filter = useListFilter(allItems, resourceFilterFields, {
|
|
19643
19909
|
enabled: !modalOpen && focusedPane === "list",
|
|
19644
|
-
onEditingChange: onFilterEditingChange
|
|
19910
|
+
onEditingChange: onFilterEditingChange,
|
|
19911
|
+
onEscapeClaimChange: onFilterEscapeClaimChange
|
|
19645
19912
|
});
|
|
19646
19913
|
const shownItems = filter.items;
|
|
19647
19914
|
const { selectedIndex, firstVisible, setSelection } = useSelectableList(
|
|
@@ -19914,12 +20181,14 @@ function PromptsTab({
|
|
|
19914
20181
|
onFetchPrompt,
|
|
19915
20182
|
onAuthRecoveryRequired,
|
|
19916
20183
|
modalOpen = false,
|
|
19917
|
-
onFilterEditingChange
|
|
20184
|
+
onFilterEditingChange,
|
|
20185
|
+
onFilterEscapeClaimChange
|
|
19918
20186
|
}) {
|
|
19919
20187
|
const visibleCount = Math.max(1, height - 7 - LIST_FILTER_ROWS);
|
|
19920
20188
|
const filter = useListFilter(prompts, promptFilterFields, {
|
|
19921
20189
|
enabled: !modalOpen && focusedPane === "list",
|
|
19922
|
-
onEditingChange: onFilterEditingChange
|
|
20190
|
+
onEditingChange: onFilterEditingChange,
|
|
20191
|
+
onEscapeClaimChange: onFilterEscapeClaimChange
|
|
19923
20192
|
});
|
|
19924
20193
|
const shownPrompts = filter.items;
|
|
19925
20194
|
const { selectedIndex, firstVisible, setSelection } = useSelectableList(
|
|
@@ -20137,6 +20406,10 @@ import {
|
|
|
20137
20406
|
import { Box as Box9, Text as Text8, useInput as useInput5 } from "ink";
|
|
20138
20407
|
|
|
20139
20408
|
// ../../core/mcp/skillsVerification.ts
|
|
20409
|
+
var DEFAULT_SKILL_BUDGET_HINT = {
|
|
20410
|
+
raise: "with the server's skillCatalogMaxSkills / skillCatalogMaxBytes settings",
|
|
20411
|
+
verifyOne: "by verifying this skill alone"
|
|
20412
|
+
};
|
|
20140
20413
|
function reasonOf(err) {
|
|
20141
20414
|
return err instanceof Error ? err.message : String(err);
|
|
20142
20415
|
}
|
|
@@ -20198,7 +20471,7 @@ function responseBytes(result) {
|
|
|
20198
20471
|
}
|
|
20199
20472
|
return total;
|
|
20200
20473
|
}
|
|
20201
|
-
async function verifySkills(client, entries, metadata) {
|
|
20474
|
+
async function verifySkills(client, entries, metadata, budgetHint = DEFAULT_SKILL_BUDGET_HINT) {
|
|
20202
20475
|
const collisions = checkSkillNameCollisions(entries);
|
|
20203
20476
|
const reports = [];
|
|
20204
20477
|
const budget = resolveSkillCatalogBudget(client.getServerSettings?.());
|
|
@@ -20210,7 +20483,7 @@ async function verifySkills(client, entries, metadata) {
|
|
|
20210
20483
|
const files = [];
|
|
20211
20484
|
const declared = entry.resources === DYNAMIC_RESOURCES ? [] : entry.resources;
|
|
20212
20485
|
const manifest = withinBudget ? boundedManifest(declared) : [];
|
|
20213
|
-
let incomplete = !withinBudget ? `Not read: this run already reached its catalog budget of ${budget.maxSkills} skills / ${budget.maxBytes} bytes (raise it
|
|
20486
|
+
let incomplete = !withinBudget ? `Not read: this run already reached its catalog budget of ${budget.maxSkills} skills / ${budget.maxBytes} bytes (raise it ${budgetHint.raise}). Nothing about this skill's files has been checked \u2014 verify it on its own ${budgetHint.verifyOne} to get a verdict.` : manifest.length < declared.length ? `Only ${manifest.length} of ${declared.length} manifest entries were read: the skill exceeds the ${SKILL_MAX_RESOURCE_ENTRIES}-entry / ${SKILL_MAX_TOTAL_BYTES}-byte interoperability limits, so the rest were not fetched and cannot be reported on.` : void 0;
|
|
20214
20487
|
let receivedBytes = 0;
|
|
20215
20488
|
const entryIdentity = skillUriIdentity(entry.uri);
|
|
20216
20489
|
let selfAttempted = false;
|
|
@@ -20365,6 +20638,10 @@ var FILE_COLOR = {
|
|
|
20365
20638
|
error: "red",
|
|
20366
20639
|
"read-error": "red"
|
|
20367
20640
|
};
|
|
20641
|
+
var TUI_SKILL_BUDGET_HINT = {
|
|
20642
|
+
raise: "with --skill-catalog-max-skills / --skill-catalog-max-bytes at launch",
|
|
20643
|
+
verifyOne: "by selecting it and pressing Enter"
|
|
20644
|
+
};
|
|
20368
20645
|
var VERIFY_STATUS = {
|
|
20369
20646
|
verified: "[Verified \u2014 Enter to re-verify]",
|
|
20370
20647
|
unverifiable: "[UNVERIFIABLE \u2014 Enter to re-verify]",
|
|
@@ -20425,12 +20702,14 @@ function SkillsTab({
|
|
|
20425
20702
|
focusedPane = null,
|
|
20426
20703
|
onAuthRecoveryRequired,
|
|
20427
20704
|
modalOpen = false,
|
|
20428
|
-
onFilterEditingChange
|
|
20705
|
+
onFilterEditingChange,
|
|
20706
|
+
onFilterEscapeClaimChange
|
|
20429
20707
|
}) {
|
|
20430
20708
|
const visibleCount = Math.max(1, height - 8 - LIST_FILTER_ROWS);
|
|
20431
20709
|
const filter = useListFilter(skills, skillFilterFields, {
|
|
20432
20710
|
enabled: !modalOpen && focusedPane === "list",
|
|
20433
|
-
onEditingChange: onFilterEditingChange
|
|
20711
|
+
onEditingChange: onFilterEditingChange,
|
|
20712
|
+
onEscapeClaimChange: onFilterEscapeClaimChange
|
|
20434
20713
|
});
|
|
20435
20714
|
const shownSkills = filter.items;
|
|
20436
20715
|
const { selectedIndex, firstVisible, setSelection } = useSelectableList(
|
|
@@ -20454,7 +20733,12 @@ function SkillsTab({
|
|
|
20454
20733
|
setError(null);
|
|
20455
20734
|
void (async () => {
|
|
20456
20735
|
try {
|
|
20457
|
-
const results = await verifySkills(
|
|
20736
|
+
const results = await verifySkills(
|
|
20737
|
+
inspectorClient,
|
|
20738
|
+
entries,
|
|
20739
|
+
void 0,
|
|
20740
|
+
TUI_SKILL_BUDGET_HINT
|
|
20741
|
+
);
|
|
20458
20742
|
const live = new Set(skillsRef.current.map(skillEntryKey));
|
|
20459
20743
|
setReports(
|
|
20460
20744
|
(previous) => mergeReports(previous, entries, results, live)
|
|
@@ -21022,12 +21306,14 @@ function ToolsTab({
|
|
|
21022
21306
|
onTestTool,
|
|
21023
21307
|
onViewDetails,
|
|
21024
21308
|
modalOpen = false,
|
|
21025
|
-
onFilterEditingChange
|
|
21309
|
+
onFilterEditingChange,
|
|
21310
|
+
onFilterEscapeClaimChange
|
|
21026
21311
|
}) {
|
|
21027
21312
|
const visibleCount = Math.max(1, height - 7 - LIST_FILTER_ROWS);
|
|
21028
21313
|
const filter = useListFilter(tools, toolFilterFields, {
|
|
21029
21314
|
enabled: !modalOpen && focusedPane === "list",
|
|
21030
|
-
onEditingChange: onFilterEditingChange
|
|
21315
|
+
onEditingChange: onFilterEditingChange,
|
|
21316
|
+
onEscapeClaimChange: onFilterEscapeClaimChange
|
|
21031
21317
|
});
|
|
21032
21318
|
const shownTools = filter.items;
|
|
21033
21319
|
const { selectedIndex, firstVisible, setSelection } = useSelectableList(
|
|
@@ -24574,7 +24860,10 @@ function tabAcceleratorBinding(visible) {
|
|
|
24574
24860
|
function globalBindings(visible = tabs) {
|
|
24575
24861
|
return [
|
|
24576
24862
|
{ keys: "?", action: "Show or hide this help" },
|
|
24577
|
-
{
|
|
24863
|
+
{
|
|
24864
|
+
keys: "Esc / Ctrl+C",
|
|
24865
|
+
action: "Exit (Esc closes a dialog or clears a kept filter first)"
|
|
24866
|
+
},
|
|
24578
24867
|
{
|
|
24579
24868
|
keys: "Tab / Shift+Tab",
|
|
24580
24869
|
action: "Move focus: servers \u2192 tabs \u2192 list \u2192 details"
|
|
@@ -25451,6 +25740,7 @@ var APP_NAME = "MCP Inspector TUI";
|
|
|
25451
25740
|
var APP_DESCRIPTION = "Terminal User Interface for the Model Context Protocol Inspector";
|
|
25452
25741
|
var APP_VERSION = readInspectorVersion(import.meta.url);
|
|
25453
25742
|
var TUI_CLIENT_NAME = "inspector-tui";
|
|
25743
|
+
var TUI_CONNECTION_TIMEOUT_REMEDY = "To accommodate a slower server, set connectionTimeout on the server's entry in a catalog (--catalog) or config (--config) file.";
|
|
25454
25744
|
var NO_ROOTS = [];
|
|
25455
25745
|
var readRoots = (client) => client.getRoots();
|
|
25456
25746
|
function App({
|
|
@@ -25474,6 +25764,7 @@ function App({
|
|
|
25474
25764
|
const [helpOpen, setHelpOpen] = useState22(false);
|
|
25475
25765
|
const focus = helpOpen ? "help" : paneFocus;
|
|
25476
25766
|
const [listFilterEditing, setListFilterEditing] = useState22(false);
|
|
25767
|
+
const [listFilterClaimsEscape, setListFilterClaimsEscape] = useState22(false);
|
|
25477
25768
|
const [tabCounts, setTabCounts] = useState22({});
|
|
25478
25769
|
const [oauthStatus, setOauthStatus] = useState22("idle");
|
|
25479
25770
|
const [oauthMessage, setOauthMessage] = useState22(null);
|
|
@@ -25569,6 +25860,7 @@ function App({
|
|
|
25569
25860
|
environment,
|
|
25570
25861
|
clientIdentity: { name: TUI_CLIENT_NAME, version: APP_VERSION },
|
|
25571
25862
|
pipeStderr: true,
|
|
25863
|
+
connectionTimeoutRemedy: TUI_CONNECTION_TIMEOUT_REMEDY,
|
|
25572
25864
|
...savedSettings && savedSettings.requestTimeout > 0 && {
|
|
25573
25865
|
timeout: savedSettings.requestTimeout
|
|
25574
25866
|
},
|
|
@@ -26452,6 +26744,7 @@ function App({
|
|
|
26452
26744
|
return;
|
|
26453
26745
|
}
|
|
26454
26746
|
if (key.escape) {
|
|
26747
|
+
if (listFilterClaimsEscape) return;
|
|
26455
26748
|
exit();
|
|
26456
26749
|
}
|
|
26457
26750
|
const showAuthTab = !!selectedServer && !!selectedServerConfig && isOAuthCapableServerConfig(selectedServerConfig);
|
|
@@ -26553,7 +26846,7 @@ function App({
|
|
|
26553
26846
|
}
|
|
26554
26847
|
}
|
|
26555
26848
|
});
|
|
26556
|
-
const headerHeight =
|
|
26849
|
+
const headerHeight = 2;
|
|
26557
26850
|
const serverListWidth = Math.floor(dimensions.width * 0.3);
|
|
26558
26851
|
const contentWidth = dimensions.width - serverListWidth;
|
|
26559
26852
|
const shownTabs = visibleTabs({
|
|
@@ -26612,17 +26905,18 @@ function App({
|
|
|
26612
26905
|
justifyContent: "space-between",
|
|
26613
26906
|
alignItems: "center",
|
|
26614
26907
|
children: [
|
|
26615
|
-
/* @__PURE__ */ jsxs23(Box35, { children: [
|
|
26616
|
-
/* @__PURE__ */ jsx25(Text32, { bold: true, color: "cyan", children: APP_NAME }),
|
|
26617
|
-
/* @__PURE__ */ jsxs23(Text32, { dimColor: true, children: [
|
|
26618
|
-
"
|
|
26908
|
+
/* @__PURE__ */ jsxs23(Box35, { flexShrink: 1, overflow: "hidden", children: [
|
|
26909
|
+
/* @__PURE__ */ jsx25(Box35, { flexShrink: 0, children: /* @__PURE__ */ jsx25(Text32, { bold: true, color: "cyan", children: APP_NAME }) }),
|
|
26910
|
+
/* @__PURE__ */ jsxs23(Text32, { dimColor: true, wrap: "truncate-end", children: [
|
|
26911
|
+
" ",
|
|
26912
|
+
"- ",
|
|
26619
26913
|
APP_DESCRIPTION
|
|
26620
26914
|
] })
|
|
26621
26915
|
] }),
|
|
26622
|
-
/* @__PURE__ */ jsxs23(Text32, { dimColor: true, children: [
|
|
26916
|
+
/* @__PURE__ */ jsx25(Box35, { flexShrink: 0, marginLeft: 1, children: /* @__PURE__ */ jsxs23(Text32, { dimColor: true, children: [
|
|
26623
26917
|
"v",
|
|
26624
26918
|
APP_VERSION
|
|
26625
|
-
] })
|
|
26919
|
+
] }) })
|
|
26626
26920
|
]
|
|
26627
26921
|
}
|
|
26628
26922
|
),
|
|
@@ -26884,6 +27178,7 @@ function App({
|
|
|
26884
27178
|
ResourcesTab,
|
|
26885
27179
|
{
|
|
26886
27180
|
onFilterEditingChange: setListFilterEditing,
|
|
27181
|
+
onFilterEscapeClaimChange: setListFilterClaimsEscape,
|
|
26887
27182
|
resources: currentServerState.resources,
|
|
26888
27183
|
resourceTemplates: currentServerState.resourceTemplates,
|
|
26889
27184
|
inspectorClient: selectedInspectorClient,
|
|
@@ -26948,6 +27243,7 @@ function App({
|
|
|
26948
27243
|
SkillsTab,
|
|
26949
27244
|
{
|
|
26950
27245
|
onFilterEditingChange: setListFilterEditing,
|
|
27246
|
+
onFilterEscapeClaimChange: setListFilterClaimsEscape,
|
|
26951
27247
|
skills: managedSkills,
|
|
26952
27248
|
pageCount: managedSkillsPageCount,
|
|
26953
27249
|
loadError: managedSkillsError,
|
|
@@ -26963,6 +27259,7 @@ function App({
|
|
|
26963
27259
|
PromptsTab,
|
|
26964
27260
|
{
|
|
26965
27261
|
onFilterEditingChange: setListFilterEditing,
|
|
27262
|
+
onFilterEscapeClaimChange: setListFilterClaimsEscape,
|
|
26966
27263
|
prompts: currentServerState.prompts,
|
|
26967
27264
|
inspectorClient: selectedInspectorClient,
|
|
26968
27265
|
width: contentWidth,
|
|
@@ -26988,6 +27285,7 @@ function App({
|
|
|
26988
27285
|
ToolsTab,
|
|
26989
27286
|
{
|
|
26990
27287
|
onFilterEditingChange: setListFilterEditing,
|
|
27288
|
+
onFilterEscapeClaimChange: setListFilterClaimsEscape,
|
|
26991
27289
|
tools: currentServerState.tools,
|
|
26992
27290
|
isConnected: inspectorStatus === "connected",
|
|
26993
27291
|
width: contentWidth,
|
|
@@ -27145,7 +27443,11 @@ var App_default = App;
|
|
|
27145
27443
|
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
27146
27444
|
async function runTui(args) {
|
|
27147
27445
|
const program = new Command();
|
|
27148
|
-
program.name("mcp-inspector-tui").description("Terminal UI for MCP Inspector").
|
|
27446
|
+
program.name("mcp-inspector-tui").description("Terminal UI for MCP Inspector").version(
|
|
27447
|
+
readInspectorVersion(import.meta.url),
|
|
27448
|
+
"-V, --version",
|
|
27449
|
+
"Print the Inspector version"
|
|
27450
|
+
).option(
|
|
27149
27451
|
"--catalog <path>",
|
|
27150
27452
|
"Writable catalog file (created if missing; default: ~/.mcp-inspector/mcp.json, or MCP_CATALOG_PATH)"
|
|
27151
27453
|
).option(
|