@modelcontextprotocol/inspector 2.0.0 → 2.2.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.
@@ -83,9 +83,6 @@ function withConnectTimeout(settings, connectionTimeout) {
83
83
  };
84
84
  }
85
85
 
86
- // ../../core/auth/node/secret-store.ts
87
- import { AsyncEntry, findCredentialsAsync } from "@napi-rs/keyring";
88
-
89
86
  // ../../core/auth/secret-fields.ts
90
87
  var SECRET_FIELD_OAUTH_CLIENT_SECRET = "oauth-client-secret";
91
88
  var SECRET_FIELD_IDP_CLIENT_SECRET = "idp-client-secret";
@@ -93,6 +90,41 @@ var envSecretField = (envKey) => `env:${envKey}`;
93
90
 
94
91
  // ../../core/auth/node/secret-store.ts
95
92
  var SERVICE_NAME = "mcp-inspector";
93
+ var keyringLoad;
94
+ var KeyringModuleShapeError = class extends Error {
95
+ constructor(detail, options) {
96
+ super(`@napi-rs/keyring loaded but ${detail}`, options);
97
+ this.name = "KeyringModuleShapeError";
98
+ }
99
+ };
100
+ var checkKeyringShape = (mod) => {
101
+ try {
102
+ if (typeof mod.AsyncEntry === "function" && typeof mod.findCredentialsAsync === "function") {
103
+ return { ok: true, mod };
104
+ }
105
+ return {
106
+ ok: false,
107
+ err: new KeyringModuleShapeError(
108
+ "did not expose AsyncEntry / findCredentialsAsync"
109
+ )
110
+ };
111
+ } catch (err) {
112
+ return {
113
+ ok: false,
114
+ err: new KeyringModuleShapeError(
115
+ `its exports could not be read: ${err instanceof Error ? err.message : String(err)}`,
116
+ { cause: err }
117
+ )
118
+ };
119
+ }
120
+ };
121
+ var loadKeyring = () => {
122
+ keyringLoad ??= import("@napi-rs/keyring").then(
123
+ checkKeyringShape,
124
+ (err) => ({ ok: false, err })
125
+ );
126
+ return keyringLoad;
127
+ };
96
128
  function parseAccount(account) {
97
129
  const idx = account.indexOf(":");
98
130
  if (idx <= 0 || idx === account.length - 1) return null;
@@ -104,16 +136,31 @@ function parseAccount(account) {
104
136
  var buildAccount = (serverId, field) => `${serverId}:${field}`;
105
137
  var KeychainUnavailableError = class extends Error {
106
138
  constructor(cause) {
139
+ const message = cause instanceof Error ? cause.message : String(cause);
107
140
  super(
108
- `OS keychain is not available. On Linux, install libsecret / gnome-keyring. Underlying error: ${cause instanceof Error ? cause.message : String(cause)}`
141
+ `OS keychain is not available. ${hintFor(cause, message)} Underlying error: ${message}`
109
142
  );
110
143
  this.name = "KeychainUnavailableError";
111
144
  }
112
145
  };
146
+ var hintFor = (cause, message) => {
147
+ if (cause instanceof KeyringModuleShapeError) {
148
+ return `The @napi-rs/keyring package loaded but does not expose the API this build expects \u2014 most likely a version or packaging mismatch; reinstall the Inspector, and report this if it persists.`;
149
+ }
150
+ if (message.includes("Cannot find native binding")) {
151
+ return `The @napi-rs/keyring platform package for this OS is missing or unavailable \u2014 reinstall the Inspector (for npx, clear the npx cache under your npm cache directory first).`;
152
+ }
153
+ return `On Linux, install libsecret / gnome-keyring.`;
154
+ };
113
155
  var KeyringSecretStore = class {
114
156
  async get(serverId, field) {
115
- const entry = new AsyncEntry(SERVICE_NAME, buildAccount(serverId, field));
116
157
  try {
158
+ const keyring = await loadKeyring();
159
+ if (!keyring.ok) return null;
160
+ const entry = new keyring.mod.AsyncEntry(
161
+ SERVICE_NAME,
162
+ buildAccount(serverId, field)
163
+ );
117
164
  const v2 = await entry.getPassword();
118
165
  return v2 ?? null;
119
166
  } catch {
@@ -121,16 +168,27 @@ var KeyringSecretStore = class {
121
168
  }
122
169
  }
123
170
  async set(serverId, field, value) {
124
- const entry = new AsyncEntry(SERVICE_NAME, buildAccount(serverId, field));
125
171
  try {
172
+ const keyring = await loadKeyring();
173
+ if (!keyring.ok) throw new KeychainUnavailableError(keyring.err);
174
+ const entry = new keyring.mod.AsyncEntry(
175
+ SERVICE_NAME,
176
+ buildAccount(serverId, field)
177
+ );
126
178
  await entry.setPassword(value);
127
179
  } catch (err) {
180
+ if (err instanceof KeychainUnavailableError) throw err;
128
181
  throw new KeychainUnavailableError(err);
129
182
  }
130
183
  }
131
184
  async delete(serverId, field) {
132
- const entry = new AsyncEntry(SERVICE_NAME, buildAccount(serverId, field));
133
185
  try {
186
+ const keyring = await loadKeyring();
187
+ if (!keyring.ok) return;
188
+ const entry = new keyring.mod.AsyncEntry(
189
+ SERVICE_NAME,
190
+ buildAccount(serverId, field)
191
+ );
134
192
  await entry.deleteCredential();
135
193
  } catch {
136
194
  }
@@ -138,7 +196,9 @@ var KeyringSecretStore = class {
138
196
  async deleteAllForServer(serverId) {
139
197
  let creds;
140
198
  try {
141
- creds = await findCredentialsAsync(SERVICE_NAME);
199
+ const keyring = await loadKeyring();
200
+ if (!keyring.ok) return;
201
+ creds = await keyring.mod.findCredentialsAsync(SERVICE_NAME);
142
202
  } catch {
143
203
  return;
144
204
  }
@@ -536,9 +596,13 @@ function loadMcpServersConfig(configPath, writable) {
536
596
  return { ...config, mcpServers: normalizedServers };
537
597
  } catch (error) {
538
598
  if (error instanceof Error) {
539
- throw new Error(`Error loading configuration: ${error.message}`);
599
+ throw new Error(`Error loading configuration: ${error.message}`, {
600
+ cause: error
601
+ });
540
602
  }
541
- throw new Error("Error loading configuration: Unknown error");
603
+ throw new Error("Error loading configuration: Unknown error", {
604
+ cause: error
605
+ });
542
606
  }
543
607
  }
544
608
  function loadServerFromConfig(configPath, serverName, writable) {
@@ -1067,7 +1131,9 @@ function parseHttpUrl(value, label) {
1067
1131
  return new URL(trimmed);
1068
1132
  } catch (err) {
1069
1133
  const detail = err instanceof Error ? err.message : String(err);
1070
- throw new Error(`Invalid ${label}: "${trimmed}" (${detail})`);
1134
+ throw new Error(`Invalid ${label}: "${trimmed}" (${detail})`, {
1135
+ cause: err
1136
+ });
1071
1137
  }
1072
1138
  }
1073
1139
  var parseOAuthCallbackParams = (location) => {
@@ -2100,6 +2166,7 @@ import { Client } from "@modelcontextprotocol/client";
2100
2166
 
2101
2167
  // ../../core/json/xMcpHeader.ts
2102
2168
  var X_MCP_HEADER_KEY = "x-mcp-header";
2169
+ var MCP_PARAM_HEADER_PREFIX = "Mcp-Param-";
2103
2170
  var RFC9110_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
2104
2171
  var PERMITTED_X_MCP_HEADER_TYPES = /* @__PURE__ */ new Set([
2105
2172
  "string",
@@ -2189,6 +2256,66 @@ function scanXMcpHeaderDeclarations(inputSchema) {
2189
2256
  const fault = visit(inputSchema, [], true);
2190
2257
  return fault === void 0 ? { valid: true, declarations } : { valid: false, reason: fault };
2191
2258
  }
2259
+ var BASE64_SENTINEL_PREFIX = "=?base64?";
2260
+ var BASE64_SENTINEL_SUFFIX = "?=";
2261
+ function mcpParamPrimitiveToString(value) {
2262
+ if (typeof value === "string") return value;
2263
+ if (typeof value === "boolean") return value ? "true" : "false";
2264
+ if (typeof value === "number") {
2265
+ if (!Number.isFinite(value)) return void 0;
2266
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
2267
+ return void 0;
2268
+ }
2269
+ return String(value);
2270
+ }
2271
+ return void 0;
2272
+ }
2273
+ function needsBase64(s2) {
2274
+ if (s2.length === 0) return true;
2275
+ if (s2.startsWith(BASE64_SENTINEL_PREFIX) && s2.endsWith(BASE64_SENTINEL_SUFFIX)) {
2276
+ return true;
2277
+ }
2278
+ if (s2 !== s2.trim()) return true;
2279
+ for (let i2 = 0; i2 < s2.length; i2++) {
2280
+ const c = s2.codePointAt(i2);
2281
+ if (c === 9 || c >= 32 && c <= 126) continue;
2282
+ return true;
2283
+ }
2284
+ return false;
2285
+ }
2286
+ function utf8ToBase64(s2) {
2287
+ const bytes = new TextEncoder().encode(s2);
2288
+ let bin = "";
2289
+ for (const b2 of bytes) bin += String.fromCodePoint(b2);
2290
+ return btoa(bin);
2291
+ }
2292
+ function encodeMcpParamValue(value) {
2293
+ return needsBase64(value) ? `${BASE64_SENTINEL_PREFIX}${utf8ToBase64(value)}${BASE64_SENTINEL_SUFFIX}` : value;
2294
+ }
2295
+ function valueAtPath(root, path2) {
2296
+ let node = root;
2297
+ for (const key of path2) {
2298
+ if (node === null || typeof node !== "object") return void 0;
2299
+ node = node[key];
2300
+ }
2301
+ return node;
2302
+ }
2303
+ function buildMcpParamHeaders(declarations, args) {
2304
+ const out = {};
2305
+ for (const decl of declarations) {
2306
+ const raw = valueAtPath(args, decl.path);
2307
+ if (raw === void 0 || raw === null) continue;
2308
+ const stringValue = mcpParamPrimitiveToString(raw);
2309
+ if (stringValue === void 0) continue;
2310
+ out[`${MCP_PARAM_HEADER_PREFIX}${decl.headerName}`] = encodeMcpParamValue(stringValue);
2311
+ }
2312
+ return out;
2313
+ }
2314
+ function mcpParamHeadersForTool(tool, args) {
2315
+ const scan = scanXMcpHeaderDeclarations(tool.inputSchema);
2316
+ if (!scan.valid || scan.declarations.length === 0) return {};
2317
+ return buildMcpParamHeaders(scan.declarations, args);
2318
+ }
2192
2319
 
2193
2320
  // ../../core/mcp/messageTrackingTransport.ts
2194
2321
  var MessageTrackingTransport = class {
@@ -3285,7 +3412,9 @@ function getAuthorizationServerUrl(serverUrl, resourceMetadata) {
3285
3412
  return new URL("/", serverUrl);
3286
3413
  } catch (err) {
3287
3414
  const detail = err instanceof Error ? err.message : String(err);
3288
- throw new Error(`Invalid MCP server URL: "${serverUrl}" (${detail})`);
3415
+ throw new Error(`Invalid MCP server URL: "${serverUrl}" (${detail})`, {
3416
+ cause: err
3417
+ });
3289
3418
  }
3290
3419
  }
3291
3420
 
@@ -4560,6 +4689,7 @@ async function* parseSSE(reader) {
4560
4689
  }
4561
4690
  var RemoteClientTransport = class {
4562
4691
  _sessionId = void 0;
4692
+ _protocolVersion = void 0;
4563
4693
  eventStreamReader = null;
4564
4694
  eventStreamAbort = null;
4565
4695
  eventStreamConsumeTask = null;
@@ -4582,6 +4712,21 @@ var RemoteClientTransport = class {
4582
4712
  getRemoteBackendSessionId() {
4583
4713
  return this._sessionId;
4584
4714
  }
4715
+ /**
4716
+ * The SDK Client calls this with the negotiated protocol version as soon as
4717
+ * `initialize` resolves — before it sends `notifications/initialized` — so an
4718
+ * HTTP transport can stamp `Mcp-Protocol-Version` on every later request.
4719
+ *
4720
+ * Here the real upstream transport lives on the backend, so we can only
4721
+ * record the version and forward it on the next `/api/mcp/send`, which
4722
+ * applies it to the upstream transport *before* sending. Because
4723
+ * `notifications/initialized` is that next send, it and everything after it
4724
+ * (including the standalone SSE GET and the session DELETE, both issued by
4725
+ * the upstream transport itself) carry the header (#1935).
4726
+ */
4727
+ setProtocolVersion(version) {
4728
+ this._protocolVersion = version;
4729
+ }
4585
4730
  /**
4586
4731
  * Reattach to an existing remote backend session after a full-page OAuth
4587
4732
  * redirect. Opens the SSE event stream without POST /connect.
@@ -4948,6 +5093,14 @@ var RemoteClientTransport = class {
4948
5093
  message,
4949
5094
  ...options?.relatedRequestId != null && {
4950
5095
  relatedRequestId: options.relatedRequestId
5096
+ },
5097
+ // Forward per-send `Mcp-Param-*` headers (SEP-2243) for the backend to
5098
+ // apply to the upstream send; the browser can't set them cross-origin.
5099
+ ...options?.headers != null && { headers: options.headers },
5100
+ // Forward the negotiated protocol version so the backend's transport can
5101
+ // stamp `Mcp-Protocol-Version` on this and every later request (#1935).
5102
+ ...this._protocolVersion !== void 0 && {
5103
+ protocolVersion: this._protocolVersion
4951
5104
  }
4952
5105
  };
4953
5106
  const res = await this.fetchFn(`${this.baseUrl}/api/mcp/send`, {
@@ -5029,6 +5182,13 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
5029
5182
  outputValidator = null;
5030
5183
  transport = null;
5031
5184
  baseTransport = null;
5185
+ // Correlation for `markResponseRejected` (#1953): the method of each
5186
+ // outbound request still awaiting a response, and — once one is answered —
5187
+ // the id of the most recently answered request per method. Entries are
5188
+ // dropped as responses arrive, so this holds at most one id per method
5189
+ // rather than growing with the session.
5190
+ outboundRequestMethods = /* @__PURE__ */ new Map();
5191
+ lastAnsweredRequestByMethod = /* @__PURE__ */ new Map();
5032
5192
  /** True when the cached transport was built with an OAuth authProvider attached. */
5033
5193
  transportHasAuthProvider = false;
5034
5194
  /** Dedupes concurrent ambient auth challenges (reason + scopes). */
@@ -5139,7 +5299,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
5139
5299
  // `McpSubscription` whose filter's `resourceSubscriptions` mirrors
5140
5300
  // `subscribedResources`; mutating the set re-lists (close old, open new), and
5141
5301
  // an unexpected `"remote"` close re-lists (reconnect-by-re-listen — the stream
5142
- // is not resumable). `null` when no URI is subscribed or on the legacy era.
5302
+ // is not resumable). `null` on the legacy era, and on the modern era whenever
5303
+ // the filter is empty — which is *not* the same as "no URI subscribed": the
5304
+ // stream also carries the list-change opt-ins, so a tools-only server opens it
5305
+ // with no subscriptions at all (#1920).
5143
5306
  modernSubscription = null;
5144
5307
  // Monotonic guard so a stale re-list/reconnect (whose `listen()` or `closed`
5145
5308
  // resolves after a newer refresh already started) can detect it lost the race
@@ -5362,6 +5525,9 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
5362
5525
  createMessageTrackingCallbacks() {
5363
5526
  return {
5364
5527
  trackRequest: (message, origin) => {
5528
+ if (origin === "client") {
5529
+ this.outboundRequestMethods.set(message.id, message.method);
5530
+ }
5365
5531
  const entry = {
5366
5532
  id: crypto.randomUUID(),
5367
5533
  timestamp: /* @__PURE__ */ new Date(),
@@ -5372,6 +5538,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
5372
5538
  this.dispatchTypedEvent("message", entry);
5373
5539
  },
5374
5540
  trackResponse: (message, origin) => {
5541
+ const responseId = message.id;
5542
+ if (origin === "server" && responseId !== void 0) {
5543
+ const method = this.outboundRequestMethods.get(responseId);
5544
+ this.outboundRequestMethods.delete(responseId);
5545
+ if (method !== void 0) {
5546
+ this.lastAnsweredRequestByMethod.set(method, responseId);
5547
+ }
5548
+ }
5375
5549
  const entry = {
5376
5550
  id: crypto.randomUUID(),
5377
5551
  timestamp: /* @__PURE__ */ new Date(),
@@ -5929,6 +6103,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
5929
6103
  this.clearReceiverTasks();
5930
6104
  this.resetSubscriptionStream();
5931
6105
  this.cancelledTaskIds.clear();
6106
+ this.outboundRequestMethods.clear();
6107
+ this.lastAnsweredRequestByMethod.clear();
5932
6108
  for (const [, controller] of this.taskInputAbortControllers) {
5933
6109
  controller.abort(new Error("Connection ended"));
5934
6110
  }
@@ -6152,7 +6328,6 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
6152
6328
  this.status = "connected";
6153
6329
  this.dispatchTypedEvent("statusChange", this.status);
6154
6330
  await this.fetchServerInfo();
6155
- this.dispatchTypedEvent("connect");
6156
6331
  if (this.initialLoggingLevel && this.capabilities?.logging) {
6157
6332
  await this.client.setLoggingLevel(
6158
6333
  this.initialLoggingLevel,
@@ -6231,6 +6406,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
6231
6406
  );
6232
6407
  }
6233
6408
  }
6409
+ await this.openModernListenStreamOnConnect();
6410
+ if (this.status === "connected" && !this.disconnecting) {
6411
+ this.dispatchTypedEvent("connect");
6412
+ }
6234
6413
  } catch (error) {
6235
6414
  if (!isConnectAuthRecoveryError(error)) {
6236
6415
  this.status = "error";
@@ -7042,7 +7221,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7042
7221
  this.getCacheableRequestOptions(options?.cacheMode)
7043
7222
  )
7044
7223
  );
7045
- await this.refreshExcludedTools(options?.metadata).catch(() => {
7224
+ await this.refreshExcludedTools(options?.metadata).catch((err) => {
7225
+ this.logger.warn(
7226
+ { err },
7227
+ "Excluded-tools walk failed; the SEP-2243 excluded list may be incomplete"
7228
+ );
7046
7229
  });
7047
7230
  return { tools: [...response.tools] };
7048
7231
  }
@@ -7055,6 +7238,26 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7055
7238
  excludesInvalidXMcpHeaderTools() {
7056
7239
  return this.isModernEra() && this.getServerType() !== "stdio";
7057
7240
  }
7241
+ /**
7242
+ * Mark the response that most recently answered `method` as rejected by the
7243
+ * client, so its Protocol entry shows why instead of rendering as a clean
7244
+ * success (#1953).
7245
+ *
7246
+ * The SDK gives no request id with a decode failure — `SdkError` carries the
7247
+ * method and nothing else — so the id is recovered by correlation: the last
7248
+ * response received for that method. That is exact rather than approximate,
7249
+ * because the SDK rejects synchronously while decoding the response (inside
7250
+ * the transport's `onmessage`) and the caller's `catch` runs in the very next
7251
+ * microtask. Delivering another response for the same method in that window
7252
+ * would take a macrotask (a socket read), which cannot interleave there.
7253
+ *
7254
+ * A no-op when nothing has answered `method` this session.
7255
+ */
7256
+ markResponseRejected(method, reason) {
7257
+ const id = this.lastAnsweredRequestByMethod.get(method);
7258
+ if (id === void 0) return;
7259
+ this.dispatchTypedEvent("responseRejected", { id, reason });
7260
+ }
7058
7261
  /** The current SEP-2243 excluded-tools set (empty on legacy/stdio). */
7059
7262
  getExcludedTools() {
7060
7263
  return this.excludedTools;
@@ -7198,6 +7401,43 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7198
7401
  }
7199
7402
  }
7200
7403
  }
7404
+ /**
7405
+ * Coerce the string-valued entries of a tool's arguments to the types its
7406
+ * `inputSchema` declares (the Tools form hands everything over as text).
7407
+ * Shared by the two `tools/call` entry points — {@link attemptToolCall} and
7408
+ * {@link callToolStream} — so both put the SAME arguments on the wire.
7409
+ */
7410
+ convertStringToolArgs(tool, args) {
7411
+ const stringArgs = {};
7412
+ for (const [key, value] of Object.entries(args)) {
7413
+ if (typeof value === "string") {
7414
+ stringArgs[key] = value;
7415
+ }
7416
+ }
7417
+ if (Object.keys(stringArgs).length === 0) return args;
7418
+ return { ...args, ...convertToolParameters(tool, stringArgs) };
7419
+ }
7420
+ /**
7421
+ * SEP-2243: mirror `x-mcp-header`-annotated arguments into `Mcp-Param-*`
7422
+ * headers on a modern connection. The SDK only does this inside
7423
+ * `client.callTool()` (and skips it in the browser), but we route
7424
+ * `tools/call` through `client.request()` for manual MRTR driving (#1704), so
7425
+ * we mirror ourselves. `Protocol.request` forwards `headers` (preserved
7426
+ * across MRTR retry legs) to the transport, and the remote transport relays
7427
+ * them to the backend's upstream send — issued server-side, where the browser
7428
+ * skip doesn't apply. No-op on legacy/stdio (no annotations).
7429
+ *
7430
+ * Applied by BOTH `tools/call` entry points: a plain call
7431
+ * ({@link attemptToolCall}) and a task-augmented one
7432
+ * ({@link callToolStream}) — a strict modern server rejects either with
7433
+ * `-32020` when the mirrored header is missing.
7434
+ */
7435
+ applyMirroredParamHeaders(tool, convertedArgs, requestOptions) {
7436
+ if (this.protocolEra !== "modern") return;
7437
+ const paramHeaders = mcpParamHeadersForTool(tool, convertedArgs);
7438
+ if (Object.keys(paramHeaders).length === 0) return;
7439
+ requestOptions.headers = { ...requestOptions.headers, ...paramHeaders };
7440
+ }
7201
7441
  /**
7202
7442
  * Run a single tools/call attempt: convert args, issue the request, validate,
7203
7443
  * and return a successful {@link ToolCallInvocation}. Throws on any error
@@ -7217,17 +7457,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7217
7457
  if (!client) {
7218
7458
  throw new Error("Client is not connected");
7219
7459
  }
7220
- let convertedArgs = args;
7221
- const stringArgs = {};
7222
- for (const [key, value] of Object.entries(args)) {
7223
- if (typeof value === "string") {
7224
- stringArgs[key] = value;
7225
- }
7226
- }
7227
- if (Object.keys(stringArgs).length > 0) {
7228
- const convertedStringArgs = convertToolParameters(tool, stringArgs);
7229
- convertedArgs = { ...args, ...convertedStringArgs };
7230
- }
7460
+ const convertedArgs = this.convertStringToolArgs(tool, args);
7231
7461
  const callMetadata = generalMetadata || toolSpecificMetadata ? { ...generalMetadata || {}, ...toolSpecificMetadata || {} } : void 0;
7232
7462
  const timestamp = /* @__PURE__ */ new Date();
7233
7463
  const metadata = this.mergeMeta(callMetadata);
@@ -7243,6 +7473,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7243
7473
  metadata?.progressToken,
7244
7474
  signal
7245
7475
  );
7476
+ this.applyMirroredParamHeaders(tool, convertedArgs, requestOptions);
7246
7477
  const rawResult = await this.invokeMcpClient(
7247
7478
  () => this.requestWithInputRequired(
7248
7479
  "tools/call",
@@ -7646,17 +7877,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7646
7877
  throw new Error("Client is not connected");
7647
7878
  }
7648
7879
  try {
7649
- let convertedArgs = args;
7650
- const stringArgs = {};
7651
- for (const [key, value] of Object.entries(args)) {
7652
- if (typeof value === "string") {
7653
- stringArgs[key] = value;
7654
- }
7655
- }
7656
- if (Object.keys(stringArgs).length > 0) {
7657
- const convertedStringArgs = convertToolParameters(tool, stringArgs);
7658
- convertedArgs = { ...args, ...convertedStringArgs };
7659
- }
7880
+ const convertedArgs = this.convertStringToolArgs(tool, args);
7660
7881
  const callMetadata = generalMetadata || toolSpecificMetadata ? { ...generalMetadata || {}, ...toolSpecificMetadata || {} } : void 0;
7661
7882
  const timestamp = /* @__PURE__ */ new Date();
7662
7883
  const metadata = this.mergeMeta(callMetadata);
@@ -7674,6 +7895,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7674
7895
  let taskId;
7675
7896
  let error;
7676
7897
  const requestOptions = this.getRequestOptions(metadata?.progressToken);
7898
+ this.applyMirroredParamHeaders(tool, convertedArgs, requestOptions);
7677
7899
  if (this.progress) {
7678
7900
  const innerOnProgress = requestOptions.onprogress;
7679
7901
  requestOptions.onprogress = (progress) => {
@@ -7779,7 +8001,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7779
8001
  );
7780
8002
  } catch (resultError) {
7781
8003
  throw new Error(
7782
- `Tool call did not return a result: ${resultError instanceof Error ? resultError.message : String(resultError)}`
8004
+ `Tool call did not return a result: ${resultError instanceof Error ? resultError.message : String(resultError)}`,
8005
+ { cause: resultError }
7783
8006
  );
7784
8007
  }
7785
8008
  }
@@ -7922,7 +8145,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7922
8145
  expandedUri = uriTemplate2.expand(params);
7923
8146
  } catch (error) {
7924
8147
  throw new Error(
7925
- `Failed to expand URI template "${uriTemplate}": ${error instanceof Error ? error.message : String(error)}`
8148
+ `Failed to expand URI template "${uriTemplate}": ${error instanceof Error ? error.message : String(error)}`,
8149
+ { cause: error }
7926
8150
  );
7927
8151
  }
7928
8152
  const readInvocation = await this.readResource(expandedUri, metadata);
@@ -8120,7 +8344,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8120
8344
  return { values: [] };
8121
8345
  }
8122
8346
  throw new Error(
8123
- `Failed to get completions: ${error instanceof Error ? error.message : String(error)}`
8347
+ `Failed to get completions: ${error instanceof Error ? error.message : String(error)}`,
8348
+ { cause: error }
8124
8349
  );
8125
8350
  }
8126
8351
  }
@@ -8300,9 +8525,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8300
8525
  * opted-in notification type (SEP §7.4).
8301
8526
  */
8302
8527
  buildSubscriptionFilter() {
8303
- const filter = {
8304
- resourceSubscriptions: Array.from(this.subscribedResources)
8305
- };
8528
+ const filter = {};
8529
+ if (this.subscribedResources.size > 0) {
8530
+ filter.resourceSubscriptions = Array.from(this.subscribedResources);
8531
+ }
8306
8532
  if (this.listChangedNotifications.tools && this.capabilities?.tools?.listChanged) {
8307
8533
  filter.toolsListChanged = true;
8308
8534
  }
@@ -8314,6 +8540,74 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8314
8540
  }
8315
8541
  return filter;
8316
8542
  }
8543
+ /**
8544
+ * Whether the modern listen stream should be open: the built filter carries
8545
+ * something to listen for (#1920). Before #1920 this was "at least one URI is
8546
+ * subscribed", which made the stream unreachable on a server with no resources
8547
+ * — a tools-only server advertising `tools.listChanged` had no way to open it,
8548
+ * so `notifications/tools/list_changed` could never arrive. The filter already
8549
+ * modelled the list-change opt-ins; only the trigger was narrower than the
8550
+ * filter it built. This matches the SDK's own `ClientOptions.listChanged`
8551
+ * auto-open, which opens whenever the effective (config ∩ capability)
8552
+ * intersection is non-empty.
8553
+ *
8554
+ * Note this is deliberately *not* the same predicate as the stream state's
8555
+ * `active` — see `modernStreamActive()`.
8556
+ */
8557
+ wantsModernStream() {
8558
+ const filter = this.buildSubscriptionFilter();
8559
+ return (filter.resourceSubscriptions?.length ?? 0) > 0 || filter.toolsListChanged === true || filter.resourcesListChanged === true || filter.promptsListChanged === true;
8560
+ }
8561
+ /**
8562
+ * Whether the stream state reports `active` — i.e. whether the *Subscriptions*
8563
+ * UI has a stream to describe. That is a narrower question than
8564
+ * `wantsModernStream()`: `ResourceSubscriptionStreamState` drives the
8565
+ * Subscriptions section's badge, so a stream open purely for list-change
8566
+ * notifications (no subscribed URI) has nothing to report there and stays
8567
+ * `active: false` (#1920). Keeping the two apart also preserves the invariant
8568
+ * the rest of this file is written against — an empty subscribed set is never
8569
+ * announced alongside an `active` stream.
8570
+ */
8571
+ modernStreamActive() {
8572
+ return this.subscribedResources.size > 0;
8573
+ }
8574
+ /**
8575
+ * Open the modern listen stream at the end of a successful connect, when the
8576
+ * filter is non-empty (#1920). Only the list-change opt-ins can make it
8577
+ * non-empty here — the subscribed set is emptied by `resetSessionState()` on
8578
+ * the way in — so this is exactly the "server advertises a listChanged the
8579
+ * Inspector wants" case that had no trigger before.
8580
+ *
8581
+ * A failure is not allowed to fail the connect: the handshake succeeded and
8582
+ * every request-scoped feature works without this stream. It is reported the
8583
+ * way a lost stream is — hand it to the reconnect machinery, which retries
8584
+ * with backoff and settles on `"ended"` past the cap.
8585
+ *
8586
+ * Gated on the same generation test as `subscribeToResource` — see the long
8587
+ * comment there. The `connect` event has deliberately *not* been dispatched
8588
+ * yet (that is the point of running here), so a list-state consumer is not the
8589
+ * risk; what is, is anything else that bumps the generation while this
8590
+ * `listen()` is in flight. `statusChange` has already fired, and any
8591
+ * concurrent call on this instance qualifies: a `subscribeToResource` from a
8592
+ * caller restoring subscriptions, or a `disconnect()` — whose
8593
+ * `resetSubscriptionStream` bumps the generation too, making a reconcile here
8594
+ * arm a reconnect for a session that is already gone.
8595
+ */
8596
+ async openModernListenStreamOnConnect() {
8597
+ if (!this.isModernEra() || !this.wantsModernStream()) return;
8598
+ const generationBefore = this.modernListenGeneration;
8599
+ try {
8600
+ await this.refreshModernSubscription();
8601
+ } catch (error) {
8602
+ this.logger.error(
8603
+ { error },
8604
+ "Failed to open the modern subscriptions/listen stream on connect"
8605
+ );
8606
+ if (this.modernListenGeneration === generationBefore + 1) {
8607
+ this.reconcileModernStreamStateAfterFailedRefresh();
8608
+ }
8609
+ }
8610
+ }
8317
8611
  /** Cancel a pending reconnect re-listen, if any (#1630). */
8318
8612
  clearModernReconnectTimer() {
8319
8613
  if (this.modernReconnectTimer !== void 0) {
@@ -8323,9 +8617,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8323
8617
  }
8324
8618
  /**
8325
8619
  * (Re-)establish the modern `subscriptions/listen` stream to match the current
8326
- * `subscribedResources` set (#1630). Because the stream is not resumable,
8327
- * every filter change re-lists: the existing stream is closed and a fresh
8328
- * `listen()` opened. With no subscribed URIs the stream is left closed.
8620
+ * filter (#1630). Because the stream is not resumable, every filter change
8621
+ * re-lists: the existing stream is closed and a fresh `listen()` opened. With
8622
+ * an empty filter — no subscribed URIs *and* no enabled list-change opt-in the
8623
+ * server advertises — the stream is left closed (#1920).
8329
8624
  *
8330
8625
  * `modernListenGeneration` guards against races — if a newer refresh starts
8331
8626
  * while this one awaits its acknowledgement, the just-opened stream is
@@ -8343,7 +8638,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8343
8638
  if (previous) {
8344
8639
  await closeSubscriptionBestEffort(previous);
8345
8640
  }
8346
- if (this.subscribedResources.size === 0) {
8641
+ if (!this.wantsModernStream()) {
8347
8642
  this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
8348
8643
  return;
8349
8644
  }
@@ -8358,7 +8653,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8358
8653
  this.modernSubscription = subscription;
8359
8654
  this.modernReconnectAttempts = 0;
8360
8655
  this.setModernStreamState({
8361
- active: true,
8656
+ active: this.modernStreamActive(),
8362
8657
  status: "acknowledged",
8363
8658
  honoredUris: subscription.honoredFilter.resourceSubscriptions ?? []
8364
8659
  });
@@ -8389,10 +8684,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8389
8684
  return;
8390
8685
  }
8391
8686
  this.modernSubscription = null;
8392
- const shouldReconnect = reason === "remote" && !isTerminalStatus(this.status) && this.subscribedResources.size > 0;
8687
+ const shouldReconnect = reason === "remote" && !isTerminalStatus(this.status) && this.wantsModernStream();
8393
8688
  if (!shouldReconnect) {
8394
8689
  this.setModernStreamState({
8395
- active: this.subscribedResources.size > 0,
8690
+ active: this.modernStreamActive(),
8396
8691
  status: "ended",
8397
8692
  honoredUris: []
8398
8693
  });
@@ -8406,10 +8701,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8406
8701
  * refresh owns the state as well as the filter — so both call sites gate on
8407
8702
  * the generation first.
8408
8703
  *
8409
- * The empty case is the ordinary one: nothing subscribed, no stream, inactive.
8410
- * The non-empty one exists because a failed re-listen leaves
8411
- * `modernSubscription` null with URIs still subscribed, and nothing else will
8412
- * notice: the reconnect machinery is reachable only from a stream that closed
8704
+ * The two branches are the empty and non-empty *filter* (#1920) — which is
8705
+ * "nothing subscribed" only when no list-change opt-in is live. The empty case
8706
+ * is the ordinary one: nothing to listen for, no stream, inactive. The
8707
+ * non-empty one exists because a failed re-listen leaves `modernSubscription`
8708
+ * null with the filter still wanting a stream, and nothing else will notice: the reconnect machinery is reachable only from a stream that closed
8413
8709
  * or a reconnect that failed, and neither happened here. Left alone, the state
8414
8710
  * keeps whatever the last success (or the optimistic `"connecting"`) wrote — a
8415
8711
  * badge that will never change over subscriptions the server may never have
@@ -8417,17 +8713,17 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8417
8713
  * Subscribe early-returns on the URI already being in the set).
8418
8714
  *
8419
8715
  * So it reconnects rather than settling for an honest-but-dead `"ended"`:
8420
- * every other route to "stream gone, URIs live" either expects the close or
8716
+ * every other route to "stream gone, filter live" either expects the close or
8421
8717
  * has exhausted the retry cap, and this is the one that has made no attempt
8422
8718
  * at all. `scheduleModernReconnect` fits as-is — a user-initiated refresh
8423
8719
  * already reset `modernReconnectAttempts`, so it starts at the base delay; the
8424
- * timer bails on a terminal status or an emptied set; and past the cap
8720
+ * timer bails on a terminal status or an emptied filter; and past the cap
8425
8721
  * `onModernReconnectFailed` lands on the same `"ended"` badge. The state
8426
8722
  * therefore becomes true or ends after a real attempt. The caller still sees
8427
8723
  * its error either way — the retry is about the subscriptions, not the call.
8428
8724
  */
8429
8725
  reconcileModernStreamStateAfterFailedRefresh() {
8430
- if (this.subscribedResources.size === 0) {
8726
+ if (!this.wantsModernStream()) {
8431
8727
  this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
8432
8728
  return;
8433
8729
  }
@@ -8441,7 +8737,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8441
8737
  */
8442
8738
  scheduleModernReconnect() {
8443
8739
  this.setModernStreamState({
8444
- active: true,
8740
+ active: this.modernStreamActive(),
8445
8741
  status: "reconnecting",
8446
8742
  honoredUris: []
8447
8743
  });
@@ -8452,7 +8748,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8452
8748
  this.clearModernReconnectTimer();
8453
8749
  this.modernReconnectTimer = setTimeout(() => {
8454
8750
  this.modernReconnectTimer = void 0;
8455
- if (isTerminalStatus(this.status) || this.subscribedResources.size === 0) {
8751
+ if (isTerminalStatus(this.status) || !this.wantsModernStream()) {
8456
8752
  return;
8457
8753
  }
8458
8754
  this.refreshModernSubscription(true).catch(
@@ -8467,9 +8763,9 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8467
8763
  */
8468
8764
  onModernReconnectFailed() {
8469
8765
  this.modernReconnectAttempts += 1;
8470
- if (this.modernReconnectAttempts > MODERN_RECONNECT_MAX_ATTEMPTS || isTerminalStatus(this.status) || this.subscribedResources.size === 0) {
8766
+ if (this.modernReconnectAttempts > MODERN_RECONNECT_MAX_ATTEMPTS || isTerminalStatus(this.status) || !this.wantsModernStream()) {
8471
8767
  this.setModernStreamState({
8472
- active: this.subscribedResources.size > 0,
8768
+ active: this.modernStreamActive(),
8473
8769
  status: "ended",
8474
8770
  honoredUris: []
8475
8771
  });
@@ -8522,7 +8818,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8522
8818
  this.dispatchSubscriptionsChange();
8523
8819
  } catch (error) {
8524
8820
  throw new Error(
8525
- `Failed to subscribe to resource: ${error instanceof Error ? error.message : String(error)}`
8821
+ `Failed to subscribe to resource: ${error instanceof Error ? error.message : String(error)}`,
8822
+ { cause: error }
8526
8823
  );
8527
8824
  }
8528
8825
  }
@@ -8566,7 +8863,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8566
8863
  }
8567
8864
  } catch (error) {
8568
8865
  throw new Error(
8569
- `Failed to unsubscribe from resource: ${error instanceof Error ? error.message : String(error)}`
8866
+ `Failed to unsubscribe from resource: ${error instanceof Error ? error.message : String(error)}`,
8867
+ { cause: error }
8570
8868
  );
8571
8869
  }
8572
8870
  }
@@ -9113,13 +9411,22 @@ async function consumeMethodOutcome(outcome, args) {
9113
9411
  }
9114
9412
 
9115
9413
  // ../../core/mcp/state/managedListState.ts
9414
+ import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
9116
9415
  var DEFAULT_LIST_CHANGED_DEBOUNCE_MS = 250;
9416
+ function isClientDecodeRejection(err) {
9417
+ return SdkError.isInstance(err) && (err.code === SdkErrorCode.InvalidResult || err.code === SdkErrorCode.UnsupportedResultType);
9418
+ }
9117
9419
  var ManagedListState = class extends TypedEventTarget {
9118
9420
  items = [];
9119
9421
  client = null;
9120
9422
  unsubscribe = null;
9121
9423
  _metadata = void 0;
9122
9424
  listChanged = false;
9425
+ // The last fetch's failure, kept as observable state so a load that fails
9426
+ // (a transport error, or a result the SDK codec rejects as invalid) is
9427
+ // rendered by the list panel instead of vanishing into an unhandled
9428
+ // rejection — which read as "this server has no tools" (#1953).
9429
+ error = null;
9123
9430
  config;
9124
9431
  // Debounce a burst of `list_changed` notifications into a single
9125
9432
  // refresh (or one indicator light) once it settles.
@@ -9138,7 +9445,8 @@ var ManagedListState = class extends TypedEventTarget {
9138
9445
  if (this.config.deferWhenPaginated && this.client?.getServerSettings()?.paginatedLists) {
9139
9446
  return;
9140
9447
  }
9141
- void this.refresh();
9448
+ void this.refresh().catch(() => {
9449
+ });
9142
9450
  };
9143
9451
  const onListChanged = () => {
9144
9452
  if (this.listChangedTimer !== null) clearTimeout(this.listChangedTimer);
@@ -9156,6 +9464,7 @@ var ManagedListState = class extends TypedEventTarget {
9156
9464
  this.items = [];
9157
9465
  this.dispatchChange();
9158
9466
  this.setListChanged(false);
9467
+ this.setError(null);
9159
9468
  }
9160
9469
  };
9161
9470
  this.client.addEventListener("connect", onConnect);
@@ -9197,7 +9506,8 @@ var ManagedListState = class extends TypedEventTarget {
9197
9506
  const settings = this.client?.getServerSettings();
9198
9507
  const skipAggregate = this.config.deferWhenPaginated && settings?.paginatedLists;
9199
9508
  if (!skipAggregate && settings?.autoRefreshOnListChanged) {
9200
- await this.refresh(void 0, "refresh");
9509
+ await this.refresh(void 0, "refresh").catch(() => {
9510
+ });
9201
9511
  } else if (this.config.supportsIndicator) {
9202
9512
  this.setListChanged(true);
9203
9513
  }
@@ -9239,6 +9549,18 @@ var ManagedListState = class extends TypedEventTarget {
9239
9549
  this.listChanged = value;
9240
9550
  this.emit("listChangedChange", value);
9241
9551
  }
9552
+ /** The last fetch's failure, or `null` when the last fetch succeeded. */
9553
+ getError() {
9554
+ return this.error;
9555
+ }
9556
+ // Compared by identity rather than message: two distinct failures with the
9557
+ // same text are still two events, and a re-render on a repeat failure is
9558
+ // cheap next to silently coalescing them.
9559
+ setError(value) {
9560
+ if (this.error === value) return;
9561
+ this.error = value;
9562
+ this.emit("errorChange", value);
9563
+ }
9242
9564
  setMetadata(metadata) {
9243
9565
  this._metadata = metadata;
9244
9566
  }
@@ -9247,9 +9569,29 @@ var ManagedListState = class extends TypedEventTarget {
9247
9569
  * this fetch: `undefined` (the connect-time load) uses the default `'use'`;
9248
9570
  * a user-initiated or auto refresh passes `'refresh'` to force a
9249
9571
  * cache-bypassing round trip and re-store the fresh aggregate.
9572
+ *
9573
+ * A failure is recorded as observable state (`getError`) AND re-thrown: the
9574
+ * state drives the panel's error rendering, while the rejection is what the
9575
+ * caller's auth-recovery wrapper keys off to detect a 401 and start a
9576
+ * re-authorization. Callers with nobody to await them (the connect-time load,
9577
+ * the `list_changed` auto-refresh) catch it explicitly (#1953).
9250
9578
  */
9251
9579
  async refresh(metadata, cacheMode) {
9252
- const next = await this.fetchItems(metadata, cacheMode);
9580
+ let next;
9581
+ try {
9582
+ next = await this.fetchItems(metadata, cacheMode);
9583
+ } catch (err) {
9584
+ const error = err instanceof Error ? err : new Error(String(err));
9585
+ this.setError(error);
9586
+ if (isClientDecodeRejection(err)) {
9587
+ this.client?.markResponseRejected?.(
9588
+ this.config.listMethod,
9589
+ error.message
9590
+ );
9591
+ }
9592
+ throw err;
9593
+ }
9594
+ this.setError(null);
9253
9595
  if (next === null) return this.getItems();
9254
9596
  this.applyItems(next);
9255
9597
  return this.getItems();
@@ -9288,6 +9630,7 @@ var ManagedListState = class extends TypedEventTarget {
9288
9630
  var ManagedToolsState = class extends ManagedListState {
9289
9631
  constructor(client, debounceMs = DEFAULT_LIST_CHANGED_DEBOUNCE_MS) {
9290
9632
  super(client, {
9633
+ listMethod: "tools/list",
9291
9634
  changeEvent: "toolsChange",
9292
9635
  listChangedEvent: "toolsListChanged",
9293
9636
  capabilityKey: "tools",
@@ -9349,6 +9692,20 @@ var MessageLogState = class extends TypedEventTarget {
9349
9692
  }
9350
9693
  pushEntry(entry);
9351
9694
  };
9695
+ const onResponseRejected = (event) => {
9696
+ const { id, reason } = event.detail;
9697
+ for (let i2 = this.messages.length - 1; i2 >= 0; i2--) {
9698
+ const entry = this.messages[i2];
9699
+ const entryId = entry.message.id;
9700
+ if (entryId !== id) continue;
9701
+ if (entry.direction === "notification") continue;
9702
+ if (entry.direction === "request" && !entry.response) continue;
9703
+ entry.clientError = reason;
9704
+ this.dispatchTypedEvent("message", entry);
9705
+ this.dispatchTypedEvent("messagesChange", this.getMessages());
9706
+ return;
9707
+ }
9708
+ };
9352
9709
  const onStatusChange = () => {
9353
9710
  if (isTerminalStatus(this.client?.getStatus())) {
9354
9711
  this.messages = [];
@@ -9357,10 +9714,12 @@ var MessageLogState = class extends TypedEventTarget {
9357
9714
  }
9358
9715
  };
9359
9716
  this.client.addEventListener("message", onMessage);
9717
+ this.client.addEventListener("responseRejected", onResponseRejected);
9360
9718
  this.client.addEventListener("statusChange", onStatusChange);
9361
9719
  this.unsubscribe = () => {
9362
9720
  if (this.client) {
9363
9721
  this.client.removeEventListener("message", onMessage);
9722
+ this.client.removeEventListener("responseRejected", onResponseRejected);
9364
9723
  this.client.removeEventListener("statusChange", onStatusChange);
9365
9724
  }
9366
9725
  this.client = null;
@@ -9406,6 +9765,7 @@ var MessageLogState = class extends TypedEventTarget {
9406
9765
  var ManagedResourcesState = class extends ManagedListState {
9407
9766
  constructor(client, debounceMs = DEFAULT_LIST_CHANGED_DEBOUNCE_MS) {
9408
9767
  super(client, {
9768
+ listMethod: "resources/list",
9409
9769
  changeEvent: "resourcesChange",
9410
9770
  listChangedEvent: "resourcesListChanged",
9411
9771
  capabilityKey: "resources",
@@ -9427,6 +9787,7 @@ var ManagedResourcesState = class extends ManagedListState {
9427
9787
  var ManagedResourceTemplatesState = class extends ManagedListState {
9428
9788
  constructor(client, debounceMs = DEFAULT_LIST_CHANGED_DEBOUNCE_MS) {
9429
9789
  super(client, {
9790
+ listMethod: "resources/templates/list",
9430
9791
  changeEvent: "resourceTemplatesChange",
9431
9792
  listChangedEvent: "resourceTemplatesListChanged",
9432
9793
  // Templates are gated on the broader `resources` capability.
@@ -9454,6 +9815,7 @@ var ManagedResourceTemplatesState = class extends ManagedListState {
9454
9815
  var ManagedPromptsState = class extends ManagedListState {
9455
9816
  constructor(client, debounceMs = DEFAULT_LIST_CHANGED_DEBOUNCE_MS) {
9456
9817
  super(client, {
9818
+ listMethod: "prompts/list",
9457
9819
  changeEvent: "promptsChange",
9458
9820
  listChangedEvent: "promptsListChanged",
9459
9821
  capabilityKey: "prompts",
@@ -11212,8 +11574,8 @@ var C = Q.object({ method: Q.literal("ui/resource-teardown"), params: Q.object({
11212
11574
  var S = Q.record(Q.string(), Q.unknown());
11213
11575
  var F = Q.object({ text: Q.object({}).optional().describe("Host supports text content blocks."), image: Q.object({}).optional().describe("Host supports image content blocks."), audio: Q.object({}).optional().describe("Host supports audio content blocks."), resource: Q.object({}).optional().describe("Host supports resource content blocks."), resourceLink: Q.object({}).optional().describe("Host supports resource link content blocks."), structuredContent: Q.object({}).optional().describe("Host supports structured content.") });
11214
11576
  var q = Q.object({ method: Q.literal("ui/notifications/request-teardown"), params: Q.object({}).optional() });
11215
- var t = Q.object({ experimental: Q.object({}).optional().describe("Experimental features (structure TBD)."), openLinks: Q.object({}).optional().describe("Host supports opening external URLs."), downloadFile: Q.object({}).optional().describe("Host supports file downloads via ui/download-file."), serverTools: Q.object({ listChanged: Q.boolean().optional().describe("Host supports tools/list_changed notifications.") }).optional().describe("Host can proxy tool calls to the MCP server."), serverResources: Q.object({ listChanged: Q.boolean().optional().describe("Host supports resources/list_changed notifications.") }).optional().describe("Host can proxy resource reads to the MCP server."), logging: Q.object({}).optional().describe("Host accepts log messages."), sandbox: Q.object({ permissions: z4.optional().describe("Permissions granted by the host (camera, microphone, geolocation)."), csp: O.optional().describe("CSP domains approved by the host.") }).optional().describe("Sandbox configuration applied by the host."), updateModelContext: F.optional().describe("Host accepts context updates (ui/update-model-context) to be included in the model's context for future turns."), message: F.optional().describe("Host supports receiving content messages (ui/message) from the view."), sampling: Q.object({ tools: Q.object({}).optional().describe("Host supports tool use via `tools` and `toolChoice` parameters.") }).optional().describe("Host supports LLM sampling (sampling/createMessage) from the view.\nMirrors the MCP `ClientCapabilities.sampling` shape so hosts can pass it through.") });
11216
- var a = Q.object({ experimental: Q.object({}).optional().describe("Experimental features (structure TBD)."), tools: Q.object({ listChanged: Q.boolean().optional().describe("App supports tools/list_changed notifications.") }).optional().describe("App exposes MCP-style tools that the host can call."), availableDisplayModes: Q.array(B).optional().describe("Display modes the app supports.") });
11577
+ var t = Q.object({ experimental: Q.record(Q.string(), Q.record(Q.string(), Q.any()).describe("Experimental features keyed by identifier.")).optional().describe("Experimental features keyed by identifier."), openLinks: Q.object({}).optional().describe("Host supports opening external URLs."), downloadFile: Q.object({}).optional().describe("Host supports file downloads via ui/download-file."), serverTools: Q.object({ listChanged: Q.boolean().optional().describe("Host supports tools/list_changed notifications.") }).optional().describe("Host can proxy tool calls to the MCP server."), serverResources: Q.object({ listChanged: Q.boolean().optional().describe("Host supports resources/list_changed notifications.") }).optional().describe("Host can proxy resource reads to the MCP server."), logging: Q.object({}).optional().describe("Host accepts log messages."), sandbox: Q.object({ permissions: z4.optional().describe("Permissions granted by the host (camera, microphone, geolocation)."), csp: O.optional().describe("CSP domains approved by the host.") }).optional().describe("Sandbox configuration applied by the host."), updateModelContext: F.optional().describe("Host accepts context updates (ui/update-model-context) to be included in the model's context for future turns."), message: F.optional().describe("Host supports receiving content messages (ui/message) from the view."), sampling: Q.object({ tools: Q.object({}).optional().describe("Host supports tool use via `tools` and `toolChoice` parameters.") }).optional().describe("Host supports LLM sampling (sampling/createMessage) from the view.\nMirrors the MCP `ClientCapabilities.sampling` shape so hosts can pass it through.") });
11578
+ var a = Q.object({ experimental: Q.record(Q.string(), Q.record(Q.string(), Q.any()).describe("Experimental features keyed by identifier.")).optional().describe("Experimental features keyed by identifier."), tools: Q.object({ listChanged: Q.boolean().optional().describe("App supports tools/list_changed notifications.") }).optional().describe("App exposes MCP-style tools that the host can call."), availableDisplayModes: Q.array(B).optional().describe("Display modes the app supports.") });
11217
11579
  var y = Q.object({ method: Q.literal("ui/notifications/initialized"), params: Q.object({}).optional() });
11218
11580
  var TQ = Q.object({ csp: O.optional().describe("Content Security Policy configuration for UI resources."), permissions: z4.optional().describe("Sandbox permissions requested by the UI resource."), domain: Q.string().optional().describe(`Dedicated origin for view sandbox.
11219
11581
 
@@ -11567,7 +11929,8 @@ async function runMethod(inspectorClient, args) {
11567
11929
  roots = parsed;
11568
11930
  } catch (e2) {
11569
11931
  throw new Error(
11570
- `--roots-json is invalid: ${e2 instanceof Error ? e2.message : String(e2)}`
11932
+ `--roots-json is invalid: ${e2 instanceof Error ? e2.message : String(e2)}`,
11933
+ { cause: e2 }
11571
11934
  );
11572
11935
  }
11573
11936
  await inspectorClient.setRoots(roots);
@@ -11846,7 +12209,7 @@ function parseRunnerOAuthCallbackUrl(cliCallbackUrl) {
11846
12209
  url = new URL(raw);
11847
12210
  } catch (err) {
11848
12211
  const reason = err?.message ?? String(err);
11849
- throw new Error(`Invalid OAuth callback URL: ${reason}`);
12212
+ throw new Error(`Invalid OAuth callback URL: ${reason}`, { cause: err });
11850
12213
  }
11851
12214
  if (url.protocol !== "http:") {
11852
12215
  throw new Error("OAuth callback URL must use http scheme");
@@ -12810,7 +13173,7 @@ async function parseArgs(argv) {
12810
13173
  const scriptArgs = rawArgs.slice(2);
12811
13174
  const dashDashIndex = scriptArgs.indexOf("--");
12812
13175
  let targetArgs = [];
12813
- let optionArgs = [];
13176
+ let optionArgs;
12814
13177
  if (dashDashIndex >= 0) {
12815
13178
  targetArgs = scriptArgs.slice(0, dashDashIndex);
12816
13179
  optionArgs = scriptArgs.slice(dashDashIndex + 1);
@@ -13120,7 +13483,8 @@ async function parseArgs(argv) {
13120
13483
  parsed = JSON.parse(options.toolArgsJson);
13121
13484
  } catch (e2) {
13122
13485
  throw new Error(
13123
- `--tool-args-json is not valid JSON: ${e2.message}`
13486
+ `--tool-args-json is not valid JSON: ${e2 instanceof Error ? e2.message : String(e2)}`,
13487
+ { cause: e2 }
13124
13488
  );
13125
13489
  }
13126
13490
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {