@modelcontextprotocol/inspector 2.0.0-rc.3 → 2.1.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 CHANGED
@@ -14,7 +14,7 @@ npx @modelcontextprotocol/inspector --cli # CLI
14
14
  npx @modelcontextprotocol/inspector --tui # TUI
15
15
  ```
16
16
 
17
- > **Repo status.** This is the **v2** line of the Inspector (branch `v2/main`). The `main` branch is the legacy v1 implementation (bug fixes only). v2 will eventually replace `main`. See [`AGENTS.md`](./AGENTS.md) for branch/board conventions.
17
+ > **Repo status.** This is the **v2** line of the Inspector. Active development happens on **`v2/main`** (the develop branch — all v2 PRs target it), which is merged into **`main`** at milestone releases; `main` is the default branch and holds the latest released v2, published to the npm `latest` tag. The legacy **v1** line lives on **`v1/main`** — security fixes only, published straight from that branch to the npm `v1-latest` tag (`npx @modelcontextprotocol/inspector@v1-latest`). See [`AGENTS.md`](./AGENTS.md) for branch/board conventions.
18
18
 
19
19
  ## Project layout
20
20
 
@@ -29,6 +29,7 @@ inspector/
29
29
  │ └── launcher/ # Shared launcher — provides the `mcp-inspector` bin, dispatches to web/cli/tui
30
30
  ├── core/ # Shared code consumed via the `@inspector/core` alias (no package.json)
31
31
  │ ├── auth/ # OAuth: providers, discovery, storage, mid-session recovery (browser/node/remote backends)
32
+ │ ├── client/ # Install-level client config (`client.json`): browser-safe parse/validate + Node load/save, remote backend, secrets
32
33
  │ ├── json/ # JSON + parameter/argument conversion utilities
33
34
  │ ├── logging/ # Silent pino logger singleton
34
35
  │ ├── mcp/ # InspectorClient runtime, state stores, transports, config import
@@ -37,6 +38,7 @@ inspector/
37
38
  │ └── storage/ # File I/O helpers for the OAuth persist backends
38
39
  ├── test-servers/ # Composable MCP test servers + fixtures used by integration tests
39
40
  ├── scripts/ # Root build/verify tooling (install cascade, smokes, verify-build-gate, verify-format-coverage, pack:verify)
41
+ ├── docs/ # Task-oriented guides (server configuration, MCP App review, launcher/config plan)
40
42
  ├── specification/ # Design/build specifications
41
43
  ├── AGENTS.md # Contribution rules for agents AND humans (see below)
42
44
  └── README.md # You are here
@@ -174,7 +176,7 @@ It also serves four `trigger_*` tools that the modern leg's spec-error injector
174
176
 
175
177
  Open the Network tab to see the mirrored `Mcp-*` headers highlighted, sentinel values decoded, and each error rendered distinctly.
176
178
 
177
- > **`Mcp-Param-*` mirroring is skipped by the SDK in the browser** (`detectProbeEnvironment() !== "browser"`). So calling `get_weather` from the **web** client omits `Mcp-Param-City` and the strict server answers `-32020`. The same tool is callable from the Node CLI/TUI, where mirroring is active.
179
+ > **`Mcp-Param-*` mirroring is built by the Inspector, not the SDK.** The SDK only mirrors inside `client.callTool()`, and skips it in the browser (`detectProbeEnvironment() !== "browser"`). The Inspector routes `tools/call` through `client.request()` to drive MRTR manually, so it builds the mirrored headers itself ([#1846](https://github.com/modelcontextprotocol/inspector/issues/1846)) — on **every** client, web included, since the web client's upstream request is issued by the Node backend rather than the browser. So `get_weather` is callable from web, CLI, and TUI alike, in both the plain and "Run as task" forms.
178
180
 
179
181
  #### `x-mcp-header` in the Tools tab
180
182
 
@@ -250,17 +252,17 @@ Individual clients: `build:web`, `build:cli`, `build:tui`, `build:launcher`. The
250
252
 
251
253
  Each client self-validates from its own folder; the root scripts chain them. There is **no** aggregate root `test` script — use `validate` (fast) or `coverage` (the gate).
252
254
 
253
- | Script | What it does |
254
- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
255
- | `npm run validate` | Runs `verify:format-coverage` (asserts every tracked source file is format-gated) first, then `validate:core` (the shared `core/` `format:check` + `lint` gate), then per client: `format:check` + `lint` + **`typecheck`** (cli/tui only) + `build` + fast unit tests. The quick inner-loop check. |
256
- | `npm run coverage` | The **per-file ≥90% gate** (lines/statements/functions/branches) under v8 instrumentation, per client. CI-enforced. For web this also runs the integration project and covers the shared `core/` runtime (including `core/json` and `core/client`). |
257
- | `npm run smoke` | End-to-end smokes through the built launcher (`--help` dispatch + prod cli/tui/web), plus a headless-Chromium boot smoke that runs the prod web bundle and asserts a clean first render (no uncaught error — sync exception or unhandled rejection, how a Node built-in reaching the browser bundle manifests). |
258
- | `npm run verify:build-gate` | Runs a real `vite build` with a Node built-in forced into the browser graph and asserts the build **fails** via the #1769 gate (which turns Vite's browser-externalization warning into a hard error). Guards against the warning phrasing drifting in a Vite bump and silently disabling the gate. Part of `npm run ci`. |
259
- | `npm run verify:format-coverage` | Parses the `format:check` globs out of every `package.json` (only those reachable from `validate`), enumerates all tracked source files, and **fails** listing any not covered by a glob — the durable guard for the "every first-party source file is format-gated" invariant (#1792). Runs first in `validate`. |
260
- | `npm run test:scripts` | Table-driven unit tests (`node --test`) for the guard's own pure parsers (`scripts/lib/npm-scripts.mjs` + the exported helpers of `verify-typecheck-coverage.mjs`), one case per rule they encode. Runs in `validate` — and `verify:typecheck-coverage` guards _this_ gate in turn (reachable from `validate`, non-empty test set, every test file matched by the `test:scripts` glob), since `node --test` silently skips a file its glob misses and still exits 0. |
261
- | `npm run verify:typecheck-coverage` | The typecheck-coverage analog of the above (#1791): for each Node client (auto-discovered from disk — enrolled via its `typecheck` script's projects, or for a `tsc -b` client like `clients/web` via its `tsconfig.json` `references`) it runs those projects with `tsc --listFilesOnly`, unions them, and **fails** listing any tracked `.ts`/`.tsx`/`.mts`/`.cts` under the client that lands in no project (so a new top-level config/helper can't silently go untypechecked). It also requires, deny-by-default, the first-party TS no client owns (`test-servers/src`, the root `vitest.shared.mts`, all of `core/`, and any new top-level location) to land in some client project's tsc pass — so a `core` `*.tsx` web's projects don't reach is caught too. Also asserts the gate is wired (each client's typecheck pass — its `typecheck` script, or web's `tsc -b` — is reachable from its `validate`, and the root chain runs each client's `validate`). Runs in `validate`. |
262
- | `npm run ci` | **Mandatory pre-push command.** `validate` → `coverage` → `verify:build-gate` → `smoke` → Storybook. A true superset of GitHub CI. |
263
- | `npm run pack:verify` | Publish smoke — see [Publishing](#publishing). |
255
+ | Script | What it does |
256
+ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
257
+ | `npm run validate` | Runs `verify:format-coverage` (asserts every tracked source file is format-gated) first, then `validate:core` (the shared `core/` `format:check` + `lint` gate), then per client: `format:check` + `lint` + **`typecheck`** (cli/tui only) + `build` + fast unit tests. The quick inner-loop check. |
258
+ | `npm run coverage` | The **per-file ≥90% gate** (lines/statements/functions/branches) under v8 instrumentation, per client. CI-enforced. For web this also runs the integration project and covers the shared `core/` runtime (including `core/json` and `core/client`). |
259
+ | `npm run smoke` | End-to-end smokes through the built launcher (`--help` dispatch + prod cli/tui/web), plus a headless-Chromium boot smoke that runs the prod web bundle and asserts a clean first render (no uncaught error — sync exception or unhandled rejection, how a Node built-in reaching the browser bundle manifests). |
260
+ | `npm run verify:build-gate` | Runs a real `vite build` with a Node built-in forced into the browser graph and asserts the build **fails** via the #1769 gate (which turns Vite's browser-externalization warning into a hard error). Guards against the warning phrasing drifting in a Vite bump and silently disabling the gate. Part of `npm run ci`. |
261
+ | `npm run verify:format-coverage` | Parses the `format:check` globs out of every `package.json` (only those reachable from `validate`), enumerates all tracked source files, and **fails** listing any not covered by a glob — the durable guard for the "every first-party source file is format-gated" invariant (#1792). Runs first in `validate`. |
262
+ | `npm run test:scripts` | Table-driven unit tests (`node --test`) for the guard's own pure parsers (`scripts/lib/npm-scripts.mjs` + the exported helpers of `verify-typecheck-coverage.mjs`), one case per rule they encode. Runs in `validate` — and `verify:typecheck-coverage` guards *this* gate in turn (reachable from `validate`, non-empty test set, every test file matched by the `test:scripts` glob), since `node --test` silently skips a file its glob misses and still exits 0. |
263
+ | `npm run verify:typecheck-coverage` | The typecheck-coverage analog of the above (#1791): for each Node client (auto-discovered from disk — enrolled via its `typecheck` script's projects, or for a `tsc -b` client like `clients/web` via its `tsconfig.json` `references`) it runs those projects with `tsc --listFilesOnly`, unions them, and **fails** listing any tracked `.ts`/`.tsx`/`.mts`/`.cts` under the client that lands in no project (so a new top-level config/helper can't silently go untypechecked). It also requires, deny-by-default, the first-party TS no client owns (`test-servers/src`, the root `vitest.shared.mts`, all of `core/`, and any new top-level location) to land in some client project's tsc pass — so a `core` `*.tsx` web's projects don't reach is caught too. Also asserts the gate is wired (each client's typecheck pass — its `typecheck` script, or web's `tsc -b` — is reachable from its `validate`, and the root chain runs each client's `validate`). Runs in `validate`. |
264
+ | `npm run ci` | **Mandatory pre-push command.** `validate` → `coverage` → `verify:build-gate` → `smoke` → Storybook. A true superset of GitHub CI. |
265
+ | `npm run pack:verify` | Publish smoke — see [Publishing](#publishing). |
264
266
 
265
267
  Per-client scripts exist too (`validate:web`, `coverage:cli`, `smoke:tui`, …), plus root `validate:core` / `format:core` for the shared `core/` package, `format:scripts` for the root `scripts/` tooling, and `format:shared` / `lint:shared` for the root "shared" surface (`test-servers/src/**`, `vitest.shared.mts`, the root `eslint.config.js`). Run `npm run format` before committing — the root `format` fixes `core/`, the root `scripts/`, the shared surface, and every client; `validate` runs the non-fixing `format:check` and fails CI on any unformatted file.
266
268
 
@@ -286,9 +288,11 @@ The `smoke:*` scripts run against the in-repo build tree, which is **not** the p
286
288
 
287
289
  Publishing is automated by two release-gated jobs in [`.github/workflows/main.yml`](.github/workflows/main.yml) (`github.event_name == 'release'`, both `needs: build`):
288
290
 
289
- - **`publish`** — the npm package. Runs `npm run pack:verify` as the pre-publish gate, asserts the release tag matches the root `package.json` version, then `npm publish --access public --provenance` — a single `npm publish` (v2 is not an npm workspace, so there is no v1-style `publish-all`/`--workspaces`), with a signed provenance attestation via GitHub OIDC (`id-token: write`, `environment: release`). Authentication is npm **trusted publishing** — there is no `NPM_TOKEN` secret, and adding one would break the publish (an empty `NODE_AUTH_TOKEN` makes npm fail `ENEEDAUTH` before OIDC is attempted). The job upgrades the npm CLI to `>=11.5.1` first, since trusted publishing requires it and Node 22 bundles npm 10.x.
291
+ - **`publish`** — the npm package. Runs `npm run pack:verify` as the pre-publish gate, asserts the release tag matches the root `package.json` version, then `npm publish --access public --provenance` — a single `npm publish` (v2 is not an npm workspace, so there is no v1-style `publish-all`/`--workspaces`), with a signed provenance attestation via GitHub OIDC (`id-token: write`, `environment: release`, `NPM_TOKEN`).
290
292
  - **`publish-github-container-registry`** — the container image (see [Docker](#docker)).
291
293
 
294
+ A v2 release is cut from **`main`**, after the milestone's work has been merged there from `v2/main` — not from `v2/main` itself. (The v1 line releases independently from `v1/main` to the `v1-latest` tag and never touches `main`; see [Repo status](#mcp-inspector).)
295
+
292
296
  Because there is **one version number** (only the root `package.json` has one — the clients carry none, so there is nothing to keep in sync and no `check-version` step), the release flow is just:
293
297
 
294
298
  ```bash
@@ -536,9 +536,13 @@ function loadMcpServersConfig(configPath, writable) {
536
536
  return { ...config, mcpServers: normalizedServers };
537
537
  } catch (error) {
538
538
  if (error instanceof Error) {
539
- throw new Error(`Error loading configuration: ${error.message}`);
539
+ throw new Error(`Error loading configuration: ${error.message}`, {
540
+ cause: error
541
+ });
540
542
  }
541
- throw new Error("Error loading configuration: Unknown error");
543
+ throw new Error("Error loading configuration: Unknown error", {
544
+ cause: error
545
+ });
542
546
  }
543
547
  }
544
548
  function loadServerFromConfig(configPath, serverName, writable) {
@@ -1067,7 +1071,9 @@ function parseHttpUrl(value, label) {
1067
1071
  return new URL(trimmed);
1068
1072
  } catch (err) {
1069
1073
  const detail = err instanceof Error ? err.message : String(err);
1070
- throw new Error(`Invalid ${label}: "${trimmed}" (${detail})`);
1074
+ throw new Error(`Invalid ${label}: "${trimmed}" (${detail})`, {
1075
+ cause: err
1076
+ });
1071
1077
  }
1072
1078
  }
1073
1079
  var parseOAuthCallbackParams = (location) => {
@@ -2100,6 +2106,7 @@ import { Client } from "@modelcontextprotocol/client";
2100
2106
 
2101
2107
  // ../../core/json/xMcpHeader.ts
2102
2108
  var X_MCP_HEADER_KEY = "x-mcp-header";
2109
+ var MCP_PARAM_HEADER_PREFIX = "Mcp-Param-";
2103
2110
  var RFC9110_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
2104
2111
  var PERMITTED_X_MCP_HEADER_TYPES = /* @__PURE__ */ new Set([
2105
2112
  "string",
@@ -2189,6 +2196,66 @@ function scanXMcpHeaderDeclarations(inputSchema) {
2189
2196
  const fault = visit(inputSchema, [], true);
2190
2197
  return fault === void 0 ? { valid: true, declarations } : { valid: false, reason: fault };
2191
2198
  }
2199
+ var BASE64_SENTINEL_PREFIX = "=?base64?";
2200
+ var BASE64_SENTINEL_SUFFIX = "?=";
2201
+ function mcpParamPrimitiveToString(value) {
2202
+ if (typeof value === "string") return value;
2203
+ if (typeof value === "boolean") return value ? "true" : "false";
2204
+ if (typeof value === "number") {
2205
+ if (!Number.isFinite(value)) return void 0;
2206
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
2207
+ return void 0;
2208
+ }
2209
+ return String(value);
2210
+ }
2211
+ return void 0;
2212
+ }
2213
+ function needsBase64(s2) {
2214
+ if (s2.length === 0) return true;
2215
+ if (s2.startsWith(BASE64_SENTINEL_PREFIX) && s2.endsWith(BASE64_SENTINEL_SUFFIX)) {
2216
+ return true;
2217
+ }
2218
+ if (s2 !== s2.trim()) return true;
2219
+ for (let i2 = 0; i2 < s2.length; i2++) {
2220
+ const c = s2.codePointAt(i2);
2221
+ if (c === 9 || c >= 32 && c <= 126) continue;
2222
+ return true;
2223
+ }
2224
+ return false;
2225
+ }
2226
+ function utf8ToBase64(s2) {
2227
+ const bytes = new TextEncoder().encode(s2);
2228
+ let bin = "";
2229
+ for (const b2 of bytes) bin += String.fromCodePoint(b2);
2230
+ return btoa(bin);
2231
+ }
2232
+ function encodeMcpParamValue(value) {
2233
+ return needsBase64(value) ? `${BASE64_SENTINEL_PREFIX}${utf8ToBase64(value)}${BASE64_SENTINEL_SUFFIX}` : value;
2234
+ }
2235
+ function valueAtPath(root, path2) {
2236
+ let node = root;
2237
+ for (const key of path2) {
2238
+ if (node === null || typeof node !== "object") return void 0;
2239
+ node = node[key];
2240
+ }
2241
+ return node;
2242
+ }
2243
+ function buildMcpParamHeaders(declarations, args) {
2244
+ const out = {};
2245
+ for (const decl of declarations) {
2246
+ const raw = valueAtPath(args, decl.path);
2247
+ if (raw === void 0 || raw === null) continue;
2248
+ const stringValue = mcpParamPrimitiveToString(raw);
2249
+ if (stringValue === void 0) continue;
2250
+ out[`${MCP_PARAM_HEADER_PREFIX}${decl.headerName}`] = encodeMcpParamValue(stringValue);
2251
+ }
2252
+ return out;
2253
+ }
2254
+ function mcpParamHeadersForTool(tool, args) {
2255
+ const scan = scanXMcpHeaderDeclarations(tool.inputSchema);
2256
+ if (!scan.valid || scan.declarations.length === 0) return {};
2257
+ return buildMcpParamHeaders(scan.declarations, args);
2258
+ }
2192
2259
 
2193
2260
  // ../../core/mcp/messageTrackingTransport.ts
2194
2261
  var MessageTrackingTransport = class {
@@ -3285,7 +3352,9 @@ function getAuthorizationServerUrl(serverUrl, resourceMetadata) {
3285
3352
  return new URL("/", serverUrl);
3286
3353
  } catch (err) {
3287
3354
  const detail = err instanceof Error ? err.message : String(err);
3288
- throw new Error(`Invalid MCP server URL: "${serverUrl}" (${detail})`);
3355
+ throw new Error(`Invalid MCP server URL: "${serverUrl}" (${detail})`, {
3356
+ cause: err
3357
+ });
3289
3358
  }
3290
3359
  }
3291
3360
 
@@ -4948,7 +5017,10 @@ var RemoteClientTransport = class {
4948
5017
  message,
4949
5018
  ...options?.relatedRequestId != null && {
4950
5019
  relatedRequestId: options.relatedRequestId
4951
- }
5020
+ },
5021
+ // Forward per-send `Mcp-Param-*` headers (SEP-2243) for the backend to
5022
+ // apply to the upstream send; the browser can't set them cross-origin.
5023
+ ...options?.headers != null && { headers: options.headers }
4952
5024
  };
4953
5025
  const res = await this.fetchFn(`${this.baseUrl}/api/mcp/send`, {
4954
5026
  method: "POST",
@@ -7198,6 +7270,43 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7198
7270
  }
7199
7271
  }
7200
7272
  }
7273
+ /**
7274
+ * Coerce the string-valued entries of a tool's arguments to the types its
7275
+ * `inputSchema` declares (the Tools form hands everything over as text).
7276
+ * Shared by the two `tools/call` entry points — {@link attemptToolCall} and
7277
+ * {@link callToolStream} — so both put the SAME arguments on the wire.
7278
+ */
7279
+ convertStringToolArgs(tool, args) {
7280
+ const stringArgs = {};
7281
+ for (const [key, value] of Object.entries(args)) {
7282
+ if (typeof value === "string") {
7283
+ stringArgs[key] = value;
7284
+ }
7285
+ }
7286
+ if (Object.keys(stringArgs).length === 0) return args;
7287
+ return { ...args, ...convertToolParameters(tool, stringArgs) };
7288
+ }
7289
+ /**
7290
+ * SEP-2243: mirror `x-mcp-header`-annotated arguments into `Mcp-Param-*`
7291
+ * headers on a modern connection. The SDK only does this inside
7292
+ * `client.callTool()` (and skips it in the browser), but we route
7293
+ * `tools/call` through `client.request()` for manual MRTR driving (#1704), so
7294
+ * we mirror ourselves. `Protocol.request` forwards `headers` (preserved
7295
+ * across MRTR retry legs) to the transport, and the remote transport relays
7296
+ * them to the backend's upstream send — issued server-side, where the browser
7297
+ * skip doesn't apply. No-op on legacy/stdio (no annotations).
7298
+ *
7299
+ * Applied by BOTH `tools/call` entry points: a plain call
7300
+ * ({@link attemptToolCall}) and a task-augmented one
7301
+ * ({@link callToolStream}) — a strict modern server rejects either with
7302
+ * `-32020` when the mirrored header is missing.
7303
+ */
7304
+ applyMirroredParamHeaders(tool, convertedArgs, requestOptions) {
7305
+ if (this.protocolEra !== "modern") return;
7306
+ const paramHeaders = mcpParamHeadersForTool(tool, convertedArgs);
7307
+ if (Object.keys(paramHeaders).length === 0) return;
7308
+ requestOptions.headers = { ...requestOptions.headers, ...paramHeaders };
7309
+ }
7201
7310
  /**
7202
7311
  * Run a single tools/call attempt: convert args, issue the request, validate,
7203
7312
  * and return a successful {@link ToolCallInvocation}. Throws on any error
@@ -7217,17 +7326,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7217
7326
  if (!client) {
7218
7327
  throw new Error("Client is not connected");
7219
7328
  }
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
- }
7329
+ const convertedArgs = this.convertStringToolArgs(tool, args);
7231
7330
  const callMetadata = generalMetadata || toolSpecificMetadata ? { ...generalMetadata || {}, ...toolSpecificMetadata || {} } : void 0;
7232
7331
  const timestamp = /* @__PURE__ */ new Date();
7233
7332
  const metadata = this.mergeMeta(callMetadata);
@@ -7243,6 +7342,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7243
7342
  metadata?.progressToken,
7244
7343
  signal
7245
7344
  );
7345
+ this.applyMirroredParamHeaders(tool, convertedArgs, requestOptions);
7246
7346
  const rawResult = await this.invokeMcpClient(
7247
7347
  () => this.requestWithInputRequired(
7248
7348
  "tools/call",
@@ -7646,17 +7746,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7646
7746
  throw new Error("Client is not connected");
7647
7747
  }
7648
7748
  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
- }
7749
+ const convertedArgs = this.convertStringToolArgs(tool, args);
7660
7750
  const callMetadata = generalMetadata || toolSpecificMetadata ? { ...generalMetadata || {}, ...toolSpecificMetadata || {} } : void 0;
7661
7751
  const timestamp = /* @__PURE__ */ new Date();
7662
7752
  const metadata = this.mergeMeta(callMetadata);
@@ -7674,6 +7764,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7674
7764
  let taskId;
7675
7765
  let error;
7676
7766
  const requestOptions = this.getRequestOptions(metadata?.progressToken);
7767
+ this.applyMirroredParamHeaders(tool, convertedArgs, requestOptions);
7677
7768
  if (this.progress) {
7678
7769
  const innerOnProgress = requestOptions.onprogress;
7679
7770
  requestOptions.onprogress = (progress) => {
@@ -7779,7 +7870,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7779
7870
  );
7780
7871
  } catch (resultError) {
7781
7872
  throw new Error(
7782
- `Tool call did not return a result: ${resultError instanceof Error ? resultError.message : String(resultError)}`
7873
+ `Tool call did not return a result: ${resultError instanceof Error ? resultError.message : String(resultError)}`,
7874
+ { cause: resultError }
7783
7875
  );
7784
7876
  }
7785
7877
  }
@@ -7922,7 +8014,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7922
8014
  expandedUri = uriTemplate2.expand(params);
7923
8015
  } catch (error) {
7924
8016
  throw new Error(
7925
- `Failed to expand URI template "${uriTemplate}": ${error instanceof Error ? error.message : String(error)}`
8017
+ `Failed to expand URI template "${uriTemplate}": ${error instanceof Error ? error.message : String(error)}`,
8018
+ { cause: error }
7926
8019
  );
7927
8020
  }
7928
8021
  const readInvocation = await this.readResource(expandedUri, metadata);
@@ -8120,7 +8213,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8120
8213
  return { values: [] };
8121
8214
  }
8122
8215
  throw new Error(
8123
- `Failed to get completions: ${error instanceof Error ? error.message : String(error)}`
8216
+ `Failed to get completions: ${error instanceof Error ? error.message : String(error)}`,
8217
+ { cause: error }
8124
8218
  );
8125
8219
  }
8126
8220
  }
@@ -8522,7 +8616,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8522
8616
  this.dispatchSubscriptionsChange();
8523
8617
  } catch (error) {
8524
8618
  throw new Error(
8525
- `Failed to subscribe to resource: ${error instanceof Error ? error.message : String(error)}`
8619
+ `Failed to subscribe to resource: ${error instanceof Error ? error.message : String(error)}`,
8620
+ { cause: error }
8526
8621
  );
8527
8622
  }
8528
8623
  }
@@ -8566,7 +8661,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8566
8661
  }
8567
8662
  } catch (error) {
8568
8663
  throw new Error(
8569
- `Failed to unsubscribe from resource: ${error instanceof Error ? error.message : String(error)}`
8664
+ `Failed to unsubscribe from resource: ${error instanceof Error ? error.message : String(error)}`,
8665
+ { cause: error }
8570
8666
  );
8571
8667
  }
8572
8668
  }
@@ -11567,7 +11663,8 @@ async function runMethod(inspectorClient, args) {
11567
11663
  roots = parsed;
11568
11664
  } catch (e2) {
11569
11665
  throw new Error(
11570
- `--roots-json is invalid: ${e2 instanceof Error ? e2.message : String(e2)}`
11666
+ `--roots-json is invalid: ${e2 instanceof Error ? e2.message : String(e2)}`,
11667
+ { cause: e2 }
11571
11668
  );
11572
11669
  }
11573
11670
  await inspectorClient.setRoots(roots);
@@ -11846,7 +11943,7 @@ function parseRunnerOAuthCallbackUrl(cliCallbackUrl) {
11846
11943
  url = new URL(raw);
11847
11944
  } catch (err) {
11848
11945
  const reason = err?.message ?? String(err);
11849
- throw new Error(`Invalid OAuth callback URL: ${reason}`);
11946
+ throw new Error(`Invalid OAuth callback URL: ${reason}`, { cause: err });
11850
11947
  }
11851
11948
  if (url.protocol !== "http:") {
11852
11949
  throw new Error("OAuth callback URL must use http scheme");
@@ -12810,7 +12907,7 @@ async function parseArgs(argv) {
12810
12907
  const scriptArgs = rawArgs.slice(2);
12811
12908
  const dashDashIndex = scriptArgs.indexOf("--");
12812
12909
  let targetArgs = [];
12813
- let optionArgs = [];
12910
+ let optionArgs;
12814
12911
  if (dashDashIndex >= 0) {
12815
12912
  targetArgs = scriptArgs.slice(0, dashDashIndex);
12816
12913
  optionArgs = scriptArgs.slice(dashDashIndex + 1);
@@ -13120,7 +13217,8 @@ async function parseArgs(argv) {
13120
13217
  parsed = JSON.parse(options.toolArgsJson);
13121
13218
  } catch (e2) {
13122
13219
  throw new Error(
13123
- `--tool-args-json is not valid JSON: ${e2.message}`
13220
+ `--tool-args-json is not valid JSON: ${e2 instanceof Error ? e2.message : String(e2)}`,
13221
+ { cause: e2 }
13124
13222
  );
13125
13223
  }
13126
13224
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {