@modelcontextprotocol/inspector 2.13.0 → 2.15.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.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/clients/cli/build/index.js +5481 -1847
  3. package/clients/mcpdo/build/mcp-bin.js +925 -186
  4. package/clients/mcpdo/build/mcpdod.js +2 -3
  5. package/clients/mcpdo/build/{server-BHuKGkMj.js → server-B-CUVIBj.js} +3544 -995
  6. package/clients/tui/build/index.js +2126 -570
  7. package/clients/web/build/index.js +459 -280
  8. package/clients/web/dist/assets/fredoka-normal-hebrew-CjvktpUN.woff2 +0 -0
  9. package/clients/web/dist/assets/fredoka-normal-latin-DOQG_Jwn.woff2 +0 -0
  10. package/clients/web/dist/assets/fredoka-normal-latin-ext-tYNUQHO6.woff2 +0 -0
  11. package/clients/web/dist/assets/index-BZ94R6fh.js +1599 -0
  12. package/clients/web/dist/assets/{index-dqp8Sd8W.css → index-tBehH5iF.css} +1 -1
  13. package/clients/web/dist/assets/{prism-light-BIhJtJkv.js → prism-light-Crps--E8.js} +1 -1
  14. package/clients/web/dist/assets/roboto-mono-italic-cyrillic-C1E63de8.woff2 +0 -0
  15. package/clients/web/dist/assets/roboto-mono-italic-cyrillic-ext-DR9ovEqT.woff2 +0 -0
  16. package/clients/web/dist/assets/roboto-mono-italic-greek-Cs2u2nVh.woff2 +0 -0
  17. package/clients/web/dist/assets/roboto-mono-italic-latin-D_P-kX1t.woff2 +0 -0
  18. package/clients/web/dist/assets/roboto-mono-italic-latin-ext-CErTcBzw.woff2 +0 -0
  19. package/clients/web/dist/assets/roboto-mono-italic-vietnamese-D4UvzJMM.woff2 +0 -0
  20. package/clients/web/dist/assets/roboto-mono-normal-cyrillic-DfzWIUPd.woff2 +0 -0
  21. package/clients/web/dist/assets/roboto-mono-normal-cyrillic-ext-D8QBEkUe.woff2 +0 -0
  22. package/clients/web/dist/assets/roboto-mono-normal-greek-DlC4X1lQ.woff2 +0 -0
  23. package/clients/web/dist/assets/roboto-mono-normal-latin-BAKpfWyj.woff2 +0 -0
  24. package/clients/web/dist/assets/roboto-mono-normal-latin-ext-CSSVNFqV.woff2 +0 -0
  25. package/clients/web/dist/assets/roboto-mono-normal-vietnamese-DFkHeOHB.woff2 +0 -0
  26. package/clients/web/dist/assets/worker-json-DEGsizKa.js +7859 -0
  27. package/clients/web/dist/fonts/Fredoka-OFL.txt +93 -0
  28. package/clients/web/dist/fonts/RobotoMono-OFL.txt +93 -0
  29. package/clients/web/dist/index.html +2 -5
  30. package/clients/web/static/sandbox_proxy.html +177 -6
  31. package/package.json +8 -4
  32. package/skills/mcpdo/SKILL.md +63 -2
  33. package/clients/web/dist/assets/index-BqEbhCDX.js +0 -1587
  34. package/clients/web/dist/assets/worker-json-8CDXFfik.js +0 -1631
@@ -0,0 +1,93 @@
1
+ Copyright 2016 The Fredoka Project Authors (https://github.com/hafontia/Fredoka-One)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,93 @@
1
+ Copyright 2015 The Roboto Mono Project Authors (https://github.com/googlefonts/robotomono)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ https://openfontlicense.org
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -4,12 +4,9 @@
4
4
  <meta charset="UTF-8" />
5
5
  <link rel="icon" type="image/svg+xml" href="/assets/MCP-CWGQJYyH.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <link rel="preconnect" href="https://fonts.googleapis.com" />
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
- <link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@300..700&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap" rel="stylesheet" />
10
7
  <title>MCP Inspector</title>
11
- <script type="module" crossorigin src="/assets/index-BqEbhCDX.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-dqp8Sd8W.css">
8
+ <script type="module" crossorigin src="/assets/index-BZ94R6fh.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-tBehH5iF.css">
13
10
  </head>
14
11
  <body>
15
12
  <div id="root"></div>
@@ -33,7 +33,31 @@
33
33
  src/utils/sandbox-csp.ts). The proxy assigns that pre-wrapped document
34
34
  to `srcdoc` verbatim — it never parses or injects into the untrusted
35
35
  bytes — so the CSP `<meta>` is guaranteed to be the first `<head>`
36
- child of the inner document. -->
36
+ child of the inner document.
37
+
38
+ Self-navigation: a sandboxed frame may always navigate ITSELF —
39
+ `allow-top-navigation` governs other frames, not its own — and the
40
+ sandbox flags follow it, so a page the app navigates to is still
41
+ opaque ("null") and still `inner.contentWindow`. Nothing in the app's
42
+ own CSP can stop that (CSP has no shipped navigation directive), so
43
+ two controls here do instead:
44
+ 1. A `frame-src` / `child-src` policy on THIS document, installed
45
+ per render before the inner frame is loaded (see
46
+ `installFramePolicy`). A parent's `frame-src` governs every
47
+ navigation of its child frame, including one the child starts,
48
+ so the inner frame can load only the srcdoc (not a fetch, so not
49
+ subject to it), the dedicated app origin, or the app's approved
50
+ `frameDomains`. It is a <meta> rather than a header because the
51
+ sources are only known once the resource-ready message arrives.
52
+ 2. Navigation detection (see `closeNavigatedFrame` and its
53
+ callers): a navigation 1. refuses raises a policy violation on
54
+ this document, and on the srcdoc path the frame leaving its
55
+ document anyway (a navigation 1. allowed, such as to a declared
56
+ frame domain) is reported by the host wrapper's departure
57
+ script, and also shows as a second `load`. Any of them closes
58
+ the relay, removes the frame and tells the host. This half is
59
+ defense in depth, not the boundary: a navigated page's scripts
60
+ can run before either signal arrives. -->
37
61
  <title>MCP-UI Proxy</title>
38
62
  <style>
39
63
  html,
@@ -76,6 +100,38 @@
76
100
  return allowList.join("; ");
77
101
  }
78
102
 
103
+ // The approved `frameDomains` from the resource-ready message's `csp`
104
+ // (the host has already filtered them; see approveCspSources in
105
+ // src/utils/sandbox-csp.ts). Re-screened here because they become CSP
106
+ // source expressions on THIS document: anything carrying whitespace, a
107
+ // directive or list separator, or a quote (a keyword such as 'self',
108
+ // which would name this proxy's own origin) is dropped.
109
+ function approvedFrameSources(csp) {
110
+ const list = csp && Array.isArray(csp.frameDomains) ? csp.frameDomains : [];
111
+ return list.filter(
112
+ (s) => typeof s === "string" && s !== "" && !/[\s;,'"]/.test(s),
113
+ );
114
+ }
115
+
116
+ // Restrict what the inner frame may be navigated to, by installing a
117
+ // `frame-src` / `child-src` policy on this document (see the head
118
+ // comment for why a parent's policy is the control that works). Must run
119
+ // BEFORE the inner frame starts its navigation. Policies only ever
120
+ // intersect, so a second call can narrow but never widen what an
121
+ // earlier one allowed — a re-render in the same proxy document fails
122
+ // closed. `child-src` is set alongside so the restriction holds even in
123
+ // a browser that falls back to it for frames; the srcdoc document
124
+ // inherits this policy too, which is harmless because its own policy
125
+ // already limits frames to the same `frameDomains` and workers to
126
+ // 'none'.
127
+ function installFramePolicy(sources) {
128
+ const list = sources.length > 0 ? sources.join(" ") : "'none'";
129
+ const meta = document.createElement("meta");
130
+ meta.httpEquiv = "Content-Security-Policy";
131
+ meta.content = `frame-src ${list}; child-src ${list}`;
132
+ document.head.appendChild(meta);
133
+ }
134
+
79
135
  // The exact embedder origins this proxy accepts, injected by the sandbox
80
136
  // controller when it serves this page (see `embedderOriginsLiteral` in
81
137
  // server/sandbox-controller.ts). It is the same list the served
@@ -166,6 +222,95 @@
166
222
  // based on the permissions requested by the app
167
223
  document.body.appendChild(inner);
168
224
 
225
+ // Set once the inner frame is seen leaving the document the host loaded.
226
+ // From then on the relay is closed in both directions and every message
227
+ // is dropped: whatever the frame holds is no longer the app, even though
228
+ // it still satisfies `event.source === inner.contentWindow` and still
229
+ // serialises its origin as "null".
230
+ let navigatedAway = false;
231
+
232
+ // Fail closed on a frame that tried to leave its document: stop
233
+ // relaying, remove the frame (which destroys whatever document it
234
+ // holds), and tell the host through the app's own logging channel,
235
+ // which it already surfaces. Idempotent, because one attempt can be
236
+ // seen twice (a blocked navigation is both a policy violation and, in
237
+ // Chromium, a second `load` onto an error page).
238
+ function closeNavigatedFrame(what) {
239
+ if (navigatedAway) return;
240
+ navigatedAway = true;
241
+ inner.remove();
242
+ console.error(
243
+ `[Sandbox] the app ${what}; the frame was removed and the relay closed.`,
244
+ );
245
+ window.parent.postMessage(
246
+ {
247
+ jsonrpc: "2.0",
248
+ method: "notifications/message",
249
+ params: {
250
+ level: "error",
251
+ logger: "mcp-inspector-sandbox",
252
+ data: `The MCP App ${what}. The Inspector removed the frame and stopped relaying messages; reload the app to try again.`,
253
+ },
254
+ },
255
+ EXPECTED_HOST_ORIGIN,
256
+ );
257
+ }
258
+
259
+ // A navigation the frame policy (installFramePolicy) refuses is
260
+ // reported HERE, on the policy's own document. Engines differ in what
261
+ // follows: Chromium commits an error page (a second `load`), while
262
+ // Firefox leaves the original document in place and fires nothing
263
+ // further. The violation is the one signal both give, so it is what
264
+ // makes the attempt visible to the host everywhere. The app's own
265
+ // nested-frame violations are reported to the app's document, not
266
+ // this one, so they do not reach this listener.
267
+ document.addEventListener("securitypolicyviolation", (e) => {
268
+ if (
269
+ e.effectiveDirective === "frame-src" ||
270
+ e.effectiveDirective === "child-src"
271
+ ) {
272
+ closeNavigatedFrame(
273
+ "tried to navigate its frame to an origin it did not declare",
274
+ );
275
+ }
276
+ });
277
+
278
+ // Watch a srcdoc load for the frame navigating itself elsewhere. The
279
+ // srcdoc document fires at most one `load`, so any later one is a
280
+ // different document — another page (one the frame policy allowed,
281
+ // such as a declared frame domain), a blocked navigation's error page,
282
+ // or a reload. All of them close the frame. A navigation started while
283
+ // the srcdoc is still loading aborts it before its `load`, so the next
284
+ // document's `load` is the FIRST one; that case is caught by the
285
+ // departure message instead (see DEPARTURE_MESSAGE_KEY).
286
+ //
287
+ // The frame is detached while it is configured and re-attached after
288
+ // the listener is on, so the only `load` it can fire first is the
289
+ // srcdoc's: inserting a frame that already carries `srcdoc` skips the
290
+ // initial about:blank `load` that a bare frame would fire, which is what
291
+ // makes "the second load" unambiguous across engines.
292
+ function loadSrcdocWatchingForNavigation(html) {
293
+ inner.remove();
294
+ inner.srcdoc = html;
295
+ let loads = 0;
296
+ inner.addEventListener("load", function onLoad() {
297
+ loads += 1;
298
+ if (loads === 1) return;
299
+ inner.removeEventListener("load", onLoad);
300
+ closeNavigatedFrame(
301
+ "navigated its frame away from the document the host loaded",
302
+ );
303
+ });
304
+ document.body.appendChild(inner);
305
+ }
306
+
307
+ // The host wrapper (wrapSandboxedHtml in src/utils/sandbox-csp.ts)
308
+ // runs a script ahead of the app's code that posts
309
+ // `{ mcpInspectorSandbox: "departing" }` when the srcdoc document starts
310
+ // to leave (`beforeunload` / `pagehide`), whether or not it ever
311
+ // finished loading. It is consumed here and never relayed.
312
+ const DEPARTURE_MESSAGE_KEY = "mcpInspectorSandbox";
313
+
169
314
  const RESOURCE_READY_NOTIFICATION =
170
315
  "ui/notifications/sandbox-resource-ready";
171
316
  const PROXY_READY_NOTIFICATION = "ui/notifications/sandbox-proxy-ready";
@@ -190,6 +335,9 @@
190
335
  // never parses or injects into the untrusted bytes.
191
336
 
192
337
  window.addEventListener("message", async (event) => {
338
+ // Closed for good once the frame navigated away (see
339
+ // loadSrcdocWatchingForNavigation): nothing is relayed either way.
340
+ if (navigatedAway) return;
193
341
  if (event.source === window.parent) {
194
342
  // Validate that messages from parent come from the expected host origin.
195
343
  // This prevents malicious pages from sending messages to this sandbox.
@@ -204,7 +352,7 @@
204
352
  }
205
353
 
206
354
  if (event.data && event.data.method === RESOURCE_READY_NOTIFICATION) {
207
- const { html, sandbox, permissions, src } = event.data.params;
355
+ const { html, sandbox, permissions, src, csp } = event.data.params;
208
356
  // The host asked for the dedicated-origin path: it published the
209
357
  // wrapped document to its own app-origin listener and is handing
210
358
  // us the URL. Only an absolute http(s) URL is honored — anything
@@ -299,6 +447,10 @@
299
447
  // an app silently rendering the wrong bytes.
300
448
  inner.removeAttribute("srcdoc");
301
449
  innerOrigin = srcUrl.origin;
450
+ // The frame may hold only the app-origin document. That
451
+ // document's own nested frames are governed by its own header
452
+ // (it is not srcdoc, so it does not inherit this policy).
453
+ installFramePolicy([srcUrl.origin]);
302
454
  inner.src = srcUrl.href;
303
455
  } else if (typeof html === "string") {
304
456
  // The host has already wrapped the app's HTML in a fixed shell
@@ -323,16 +475,25 @@
323
475
  .filter((t) => t && t.toLowerCase() !== "allow-same-origin");
324
476
  inner.setAttribute("sandbox", fallbackTokens.join(" "));
325
477
  innerOrigin = "null";
326
- inner.srcdoc = html;
478
+ // The srcdoc load itself is not a fetch, so `frame-src` does not
479
+ // apply to it; what it does stop is the frame being navigated to
480
+ // anything but the app's approved `frameDomains` afterwards.
481
+ installFramePolicy(approvedFrameSources(csp));
482
+ loadSrcdocWatchingForNavigation(html);
327
483
  }
328
484
  } else {
329
485
  if (inner && inner.contentWindow) {
330
486
  // Target the inner frame's real origin on the dedicated-origin
331
487
  // path, so a frame that navigated itself elsewhere stops
332
488
  // receiving host messages. The opaque default has no targetable
333
- // origin ("null" is not a valid targetOrigin), so it keeps "*" —
334
- // safe there because the frame is `srcdoc` and cannot navigate
335
- // to another origin's document without allow-top-navigation.
489
+ // origin ("null" is not a valid targetOrigin), so it has to use
490
+ // "*" — which on its own would deliver to WHATEVER document the
491
+ // frame holds: a sandboxed frame can navigate itself to another
492
+ // origin without allow-top-navigation, and the new page keeps the
493
+ // opaque origin and the same contentWindow. What keeps "*" from
494
+ // reaching such a page is the frame policy (installFramePolicy),
495
+ // which blocks the navigation, and the navigation watch, which
496
+ // closes this relay if the frame leaves its document anyway.
336
497
  inner.contentWindow.postMessage(
337
498
  event.data,
338
499
  innerOrigin === "null" ? "*" : innerOrigin,
@@ -354,6 +515,16 @@
354
515
  );
355
516
  return;
356
517
  }
518
+ if (event.data && event.data[DEPARTURE_MESSAGE_KEY] === "departing") {
519
+ // Only the opaque srcdoc path watches for departure: a
520
+ // dedicated-origin app is targeted by its real origin instead.
521
+ if (innerOrigin === "null") {
522
+ closeNavigatedFrame(
523
+ "navigated its frame away from the document the host loaded",
524
+ );
525
+ }
526
+ return;
527
+ }
357
528
  // Relay messages from inner frame to parent window.
358
529
  // Use specific origin instead of "*" to prevent message interception.
359
530
  window.parent.postMessage(event.data, EXPECTED_HOST_ORIGIN);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelcontextprotocol/inspector",
3
- "version": "2.13.0",
3
+ "version": "2.15.0",
4
4
  "description": "The Model Context Protocol Inspector",
5
5
  "keywords": [
6
6
  "MCP",
@@ -111,6 +111,7 @@
111
111
  "smoke:web:browser": "node scripts/install-smoke-browser.mjs && node scripts/smoke-web-browser.mjs",
112
112
  "smoke:web:app": "node scripts/install-smoke-browser.mjs && node scripts/smoke-web-app.mjs",
113
113
  "smoke:web:elicit": "node scripts/install-smoke-browser.mjs && node scripts/smoke-web-elicitation.mjs",
114
+ "smoke:web:sandbox": "node scripts/install-smoke-browser.mjs && node scripts/smoke-web-sandbox-navigation.mjs",
114
115
  "smoke:web:tabs": "node scripts/install-smoke-browser.mjs chromium && node scripts/smoke-web-tabs.mjs chromium",
115
116
  "smoke:web:engine": "node scripts/run-engine-smokes.mjs",
116
117
  "smoke:web:chromium": "node scripts/run-engine-smokes.mjs chromium",
@@ -148,7 +149,7 @@
148
149
  "zod": "^4.4.3"
149
150
  },
150
151
  "engines": {
151
- "node": ">=22.19.0"
152
+ "node": "^22.23.4 || ^24.22.1 || >=26.12.1"
152
153
  },
153
154
  "allowScripts": {
154
155
  "fsevents": false
@@ -158,7 +159,7 @@
158
159
  "@modelcontextprotocol/server-legacy": "2.4.0",
159
160
  "@types/node": "^24.12.4",
160
161
  "@types/proper-lockfile": "^4.1.4",
161
- "@vitest/coverage-v8": "4.1.11",
162
+ "@vitest/coverage-v8": "4.1.12",
162
163
  "eslint": "^10.8.0",
163
164
  "eslint-plugin-react-hooks": "^7.1.1",
164
165
  "express": "^5.2.1",
@@ -167,6 +168,9 @@
167
168
  "semver": "^7.8.5",
168
169
  "typescript": "~5.9.3",
169
170
  "typescript-eslint": "^8.65.0",
170
- "vitest": "4.1.11"
171
+ "vitest": "4.1.12"
172
+ },
173
+ "overrides": {
174
+ "minimatch": "^11.0.4"
171
175
  }
172
176
  }
@@ -79,7 +79,10 @@ more).
79
79
  - Connections persist across separate `mcpdo` invocations and **self-heal**: a
80
80
  dropped transport (expired session, exited stdio child) transparently
81
81
  re-dials on next use with stored credentials. Don't monitor or reconnect
82
- manually; only an `auth_required` error needs action (re-run `connect`).
82
+ manually; only an exit-`3` auth error needs action (re-run `connect`):
83
+ `auth_required`, `oauth_error` (the authorization server refused — its
84
+ `oauthError` / `oauthErrorDescription` say why), or `oauth_timeout` (the
85
+ sign-in was not completed in time).
83
86
  `mcpdo disconnect` ends one connection; `mcpdo daemon stop` resets
84
87
  everything.
85
88
  - The `[legacy]` / `[modern]` era tag on `connections/list` is the negotiated
@@ -97,7 +100,10 @@ more).
97
100
  stdio server) or to more than one URL is rejected with guidance. EMA IdP
98
101
  login records lead with their bare issuer URL and carry a trailing
99
102
  `enterprise IdP login` marker, and clear by that bare issuer URL
100
- (`mcpdo auth/clear https://idp.example.com`). Run `connect` as its **own
103
+ (`mcpdo auth/clear https://idp.example.com`). To diagnose a 401/403 —
104
+ wrong audience, missing scope, expired token — run `mcpdo auth/show <url or
105
+ name>`: it prints the stored access token's decoded claims (unverified) and
106
+ expiry relative to now, never the token itself. Run `connect` as its **own
101
107
  command** — never chained with `&&`, `;`, or a follow-on
102
108
  `tools/list`/`connections/show`. A connect that needs sign-in exits 0 while
103
109
  the connection is still unusable, so a chained command runs against a
@@ -163,3 +169,58 @@ more).
163
169
  - Parked calls expire after 10 minutes; one parked call per connection. Pass
164
170
  `--elicit off` on `connect` to have well-behaved servers fall back to their
165
171
  own defaults instead of asking.
172
+
173
+ ## Load runs (`tools/call --load`)
174
+
175
+ - The summary's `latencyMs` and `okThroughput` cover **successful** calls
176
+ only; failures are timed in `errorLatencyMs`, and `isError` results' text
177
+ is tallied in `toolErrorMessages` (thrown ones in `errorMessages`). Judge a
178
+ run on these, not on `throughput`, which counts failures too.
179
+ - With `--load-rate`, compare `achievedRate` to `rate` and read
180
+ `droppedSlots`: slots nobody was free to take are dropped, so a server too
181
+ slow for the target can still report healthy latency. A shortfall under 90%
182
+ also prints a warning on stderr.
183
+ - `--load-scenario <file>` (no tool name) loads a weighted mix of
184
+ `tools/call` / `resources/read` / `prompts/get` steps; the top-level counts
185
+ are totals and `steps[]` breaks them down per step — find the slow or
186
+ failing step there.
187
+ - Identical calls flatter a caching server. Vary them with placeholders in
188
+ argument values (`id:=user-{{seq}}`, `{{uuid}}`, `{{rand:1-50}}`,
189
+ `{{worker}}`) or `--load-args-file rows.ndjson` (one argument object per
190
+ line, cycled; `--load-args-random` to shuffle).
191
+ - To find a server's knee, step the load: `--load-steps 10,25,50` or
192
+ `--load-ramp 10:100:60` (concurrency; `--load-step-by rate` for rate), each
193
+ stage `--load-step-duration` seconds. Read `stages[]` top to bottom for
194
+ where `latencyMs.p99` or `errorRate` jumps. `--load-stop-on-breach` with a
195
+ `--load-max-*` threshold stops at the first breaching stage.
196
+ - Latency has `p50`/`p90`/`p95`/`p99`/`p999`. Gate a run with
197
+ `--load-max-p50`/`-p95`/`-p99`, `--load-max-error-rate` or
198
+ `--load-min-throughput` (ok calls/s); a breach exits `10` after the summary.
199
+ To gate on "no worse than last time", save a `--format json` summary and
200
+ pass `--load-baseline prev.json --load-max-regression 10%` on the same run
201
+ shape (`load_regression` on a regression, `load_baseline_mismatch` when the
202
+ runs differ in tool, concurrency, bounds, rate or stages).
203
+ - `--load-interval <sec>` adds an `intervals[]` time series (spot a GC pause
204
+ or a slow leak the totals hide); `--load-warmup <sec>` leaves a warm-up out
205
+ of the statistics (reported under `warmup`). Progress lines on stderr appear
206
+ only with `--format text`.
207
+ - If the generator rather than the server is the bottleneck (throughput stops
208
+ rising while the server is idle), mcpdo cannot help: use the one-shot CLI's
209
+ `mcp-inspector --cli <url> … --load --load-workers 4`, which spreads the
210
+ run over processes and merges one summary.
211
+ - To measure notification fan-out, `--load-subscribe <uri>
212
+ --load-subscribers 50` subscribes 50 sessions; a tool name makes each
213
+ successful call a trigger (latency from its start), and no tool listens for
214
+ `--load-duration` to the server's own updates. Read `fanout.missed` and
215
+ `fanout.deliveryMs`.
216
+ - To load session setup and teardown too (servers behind gateways see many
217
+ short sessions), add `--load-churn` (HTTP/SSE only): each worker connects,
218
+ makes `--load-churn-calls` calls (default `1`), disconnects, repeats.
219
+ Connect cost is in `churn.connectMs`, not in `latencyMs`.
220
+ - `--load-rate` is closed-loop: busy workers drop slots, which hides
221
+ queueing. Add `--load-open-loop` to keep every slot and time calls from
222
+ their scheduled start (`openLoop: true` in the summary).
223
+ - `stoppedEarly` (`reason` + `detail`) means the run ended before its bounds:
224
+ `consecutive_errors` (the server stopped answering), `connection_lost`,
225
+ `threshold_breached` (a stepped run's `--load-stop-on-breach`), or
226
+ `interrupted`. The counts cover only what completed.