@modelcontextprotocol/inspector 1.0.0 → 2.0.0-rc.1

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 (48) hide show
  1. package/README.md +199 -351
  2. package/clients/cli/build/index.js +13218 -0
  3. package/clients/launcher/build/index.js +90 -0
  4. package/clients/launcher/build/parse-launcher-argv.js +29 -0
  5. package/clients/tui/build/chunk-MCKGQKYU.js +15 -0
  6. package/clients/tui/build/index.js +14573 -0
  7. package/clients/tui/build/undici-HXPKCIY3.js +25789 -0
  8. package/clients/web/build/chunk-MCKGQKYU.js +15 -0
  9. package/clients/web/build/index.js +4322 -0
  10. package/clients/web/build/undici-HXPKCIY3.js +25789 -0
  11. package/clients/web/dist/assets/css-DfBGKsUM.js +1 -0
  12. package/clients/web/dist/assets/index-Cbtmmw0V.js +300 -0
  13. package/clients/web/dist/assets/index-CiUpWldH.css +1 -0
  14. package/clients/web/dist/assets/json-rfaH9Mx5.js +1 -0
  15. package/clients/web/dist/assets/markdown-PVXc4EJ0.js +1 -0
  16. package/clients/web/dist/assets/markup-Bk4keGDK.js +1 -0
  17. package/clients/web/dist/assets/markup-DPsXihXF.js +1 -0
  18. package/clients/web/dist/assets/prism-light-B8tIwqDr.js +6 -0
  19. package/clients/web/dist/assets/tomorrow-COrD2Qnh.js +1 -0
  20. package/clients/web/dist/assets/yaml-B78V9E0m.js +1 -0
  21. package/clients/web/dist/icons.svg +24 -0
  22. package/clients/web/dist/index.html +17 -0
  23. package/package.json +96 -80
  24. package/scripts/install-clients.mjs +65 -0
  25. package/LICENSE +0 -216
  26. package/cli/build/cli.js +0 -277
  27. package/cli/build/client/connection.js +0 -38
  28. package/cli/build/client/index.js +0 -6
  29. package/cli/build/client/prompts.js +0 -43
  30. package/cli/build/client/resources.js +0 -36
  31. package/cli/build/client/tools.js +0 -87
  32. package/cli/build/client/types.js +0 -1
  33. package/cli/build/error-handler.js +0 -18
  34. package/cli/build/index.js +0 -257
  35. package/cli/build/transport.js +0 -65
  36. package/cli/build/utils/awaitable-log.js +0 -7
  37. package/client/bin/client.js +0 -62
  38. package/client/bin/start.js +0 -350
  39. package/client/dist/assets/OAuthCallback-D56s1eS_.js +0 -55
  40. package/client/dist/assets/OAuthDebugCallback-CdGJkhXe.js +0 -64
  41. package/client/dist/assets/index-BlUSNvoM.css +0 -2738
  42. package/client/dist/assets/index-DjSLdS7f.js +0 -66698
  43. package/client/dist/index.html +0 -14
  44. package/server/build/index.js +0 -886
  45. package/server/build/mcpProxy.js +0 -121
  46. package/server/build/static/sandbox_proxy.html +0 -197
  47. package/server/static/sandbox_proxy.html +0 -197
  48. /package/{client/dist/mcp.svg → clients/web/dist/assets/MCP-CWGQJYyH.svg} +0 -0
@@ -1,121 +0,0 @@
1
- import { SseError } from "@modelcontextprotocol/sdk/client/sse.js";
2
- import { StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3
- import { isJSONRPCRequest } from "@modelcontextprotocol/sdk/types.js";
4
- /**
5
- * JSON-RPC error code for failed proxy → upstream transport sends.
6
- *
7
- * JSON-RPC 2.0 reserves **-32000 .. -32099** for implementation-defined *server* errors.
8
- * MCP uses that band for protocol/SDK errors (see `@modelcontextprotocol/sdk` `ErrorCode`, e.g.
9
- * `-32000` connection closed, `-32001` request timeout, `-32042` URL elicitation; other codes in
10
- * the band are assigned over time — e.g. **-32002** is used for **ResourceNotFound** in parts of
11
- * the MCP ecosystem). We use **-32099** so this inspector-only bridge error stays clear of those
12
- * registered meanings; the client still keys off `error.data` shape, not the number alone.
13
- *
14
- * Keep in sync with `client/src/lib/constants.ts`; drift is caught by
15
- * `client/src/lib/__tests__/mcpProxyTransportErrorCode.test.ts`.
16
- */
17
- export const MCP_PROXY_TRANSPORT_ERROR_CODE = -32099;
18
- function onClientError(error) {
19
- console.error("Error from inspector client:", error);
20
- }
21
- function onServerError(error) {
22
- if (error?.cause && JSON.stringify(error.cause).includes("ECONNREFUSED")) {
23
- console.error("Connection refused. Is the MCP server running?");
24
- }
25
- else if (error.message && error.message.includes("404")) {
26
- console.error("Error accessing endpoint (HTTP 404)");
27
- }
28
- else {
29
- console.error("Error from MCP server:", error);
30
- }
31
- }
32
- /** Exported for unit tests; used by the proxy when forwarding transport failures. */
33
- export function serializeProxyTransportError(error, headerHolder) {
34
- const data = {
35
- message: error.message,
36
- name: error.name,
37
- };
38
- if (error.cause !== undefined) {
39
- data.cause =
40
- error.cause instanceof Error ? error.cause.message : String(error.cause);
41
- }
42
- const attachHttpStatusIfValid = (code) => {
43
- if (Number.isInteger(code) && code >= 100 && code <= 599) {
44
- data.httpStatus = code;
45
- }
46
- };
47
- if (error instanceof StreamableHTTPError) {
48
- if (error.code !== undefined)
49
- attachHttpStatusIfValid(error.code);
50
- }
51
- else if (error instanceof SseError) {
52
- if (error.code !== undefined)
53
- attachHttpStatusIfValid(error.code);
54
- }
55
- if (headerHolder?.lastUpstream401) {
56
- const u = headerHolder.lastUpstream401;
57
- data.upstream401 = {
58
- wwwAuthenticate: u.wwwAuthenticate,
59
- body: u.body,
60
- contentType: u.contentType,
61
- };
62
- delete headerHolder.lastUpstream401;
63
- }
64
- return data;
65
- }
66
- export default function mcpProxy({ transportToClient, transportToServer, headerHolder, }) {
67
- let transportToClientClosed = false;
68
- let transportToServerClosed = false;
69
- let reportedServerSession = false;
70
- transportToClient.onmessage = (message) => {
71
- transportToServer.send(message).catch((err) => {
72
- const error = err instanceof Error ? err : new Error(String(err));
73
- // Send error response back to client if it was a request (has id) and connection is still open
74
- if (isJSONRPCRequest(message) && !transportToClientClosed) {
75
- const causeStr = error.cause !== undefined
76
- ? error.cause instanceof Error
77
- ? error.cause.message
78
- : String(error.cause)
79
- : null;
80
- const errorResponse = {
81
- jsonrpc: "2.0",
82
- id: message.id,
83
- error: {
84
- code: MCP_PROXY_TRANSPORT_ERROR_CODE,
85
- message: causeStr
86
- ? `${error.message} (cause: ${causeStr})`
87
- : error.message,
88
- data: serializeProxyTransportError(error, headerHolder),
89
- },
90
- };
91
- transportToClient.send(errorResponse).catch(onClientError);
92
- }
93
- });
94
- };
95
- transportToServer.onmessage = (message) => {
96
- if (!reportedServerSession) {
97
- if (transportToServer.sessionId) {
98
- // Can only report for StreamableHttp
99
- console.error("Proxy <-> Server sessionId: " + transportToServer.sessionId);
100
- }
101
- reportedServerSession = true;
102
- }
103
- transportToClient.send(message).catch(onClientError);
104
- };
105
- transportToClient.onclose = () => {
106
- if (transportToServerClosed) {
107
- return;
108
- }
109
- transportToClientClosed = true;
110
- transportToServer.close().catch(onServerError);
111
- };
112
- transportToServer.onclose = () => {
113
- if (transportToClientClosed) {
114
- return;
115
- }
116
- transportToServerClosed = true;
117
- transportToClient.close().catch(onClientError);
118
- };
119
- transportToClient.onerror = onClientError;
120
- transportToServer.onerror = onServerError;
121
- }
@@ -1,197 +0,0 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="color-scheme" content="light dark" />
6
- <!-- CSP is set via HTTP headers by serve.ts (based on ?csp= query param).
7
- The inner iframe inherits this CSP since we use document.write(). -->
8
- <title>MCP-UI Proxy</title>
9
- <style>
10
- html,
11
- body {
12
- margin: 0;
13
- height: 100vh;
14
- width: 100vw;
15
- /* Transparent background allows parent page to show through */
16
- background-color: transparent;
17
- }
18
- body {
19
- display: flex;
20
- flex-direction: column;
21
- }
22
- * {
23
- box-sizing: border-box;
24
- }
25
- iframe {
26
- background-color: transparent;
27
- border: 0px none transparent;
28
- padding: 0px;
29
- overflow: hidden;
30
- flex-grow: 1;
31
- /* Inherit color scheme from parent for consistent transparency */
32
- color-scheme: inherit;
33
- }
34
- </style>
35
- </head>
36
- <body>
37
- <script>
38
- function buildAllowAttribute(permissions) {
39
- if (!permissions) return "";
40
-
41
- const allowList = [];
42
- if (permissions["camera"]) allowList.push("camera");
43
- if (permissions["microphone"]) allowList.push("microphone");
44
- if (permissions["geolocation"]) allowList.push("geolocation");
45
- if (permissions["clipboardWrite"]) allowList.push("clipboard-write");
46
-
47
- return allowList.join("; ");
48
- }
49
-
50
- const ALLOWED_REFERRER_PATTERN =
51
- /^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/;
52
-
53
- if (window.self === window.top) {
54
- throw new Error("This file is only to be used in an iframe sandbox.");
55
- }
56
-
57
- if (!document.referrer) {
58
- throw new Error("No referrer, cannot validate embedding site.");
59
- }
60
-
61
- if (!document.referrer.match(ALLOWED_REFERRER_PATTERN)) {
62
- throw new Error(
63
- `Embedding domain not allowed in referrer ${document.referrer}. (Consider updating the validation logic to allow your domain.)`,
64
- );
65
- }
66
-
67
- // Extract the expected host origin from the referrer for origin validation.
68
- // This is the origin we expect all parent messages to come from.
69
- const EXPECTED_HOST_ORIGIN = new URL(document.referrer).origin;
70
-
71
- const OWN_ORIGIN = new URL(window.location.href).origin;
72
-
73
- // Security self-test: verify iframe isolation is working correctly.
74
- // This MUST throw a SecurityError -- if `window.top` is accessible, the sandbox
75
- // configuration is dangerously broken and untrusted content could escape.
76
- try {
77
- window.top?.alert(
78
- "If you see this, the sandbox is not setup securely.",
79
- );
80
- throw "FAIL";
81
- } catch (e) {
82
- if (e === "FAIL") {
83
- throw new Error("The sandbox is not setup securely.");
84
- }
85
-
86
- // Expected: SecurityError confirms proper sandboxing.
87
- }
88
-
89
- // Double-iframe sandbox architecture: THIS file is the outer sandbox proxy
90
- // iframe on a separate origin. It creates an inner iframe for untrusted HTML
91
- // content. Per the specification, the Host and the Sandbox MUST have different
92
- // origins.
93
- const inner = document.createElement("iframe");
94
- inner.style = "width:100%; height:100%; border:none;";
95
- inner.setAttribute(
96
- "sandbox",
97
- "allow-scripts allow-same-origin allow-forms",
98
- );
99
- // Note: allow attribute is set later when receiving sandbox-resource-ready notification
100
- // based on the permissions requested by the app
101
- document.body.appendChild(inner);
102
-
103
- const RESOURCE_READY_NOTIFICATION =
104
- "ui/notifications/sandbox-resource-ready";
105
- const PROXY_READY_NOTIFICATION = "ui/notifications/sandbox-proxy-ready";
106
-
107
- // Message relay: This Sandbox (outer iframe) acts as a bidirectional bridge,
108
- // forwarding messages between:
109
- //
110
- // Host (parent window) ↔ Sandbox (outer frame) ↔ View (inner iframe)
111
- //
112
- // Reason: the parent window and inner iframe have different origins and can't
113
- // communicate directly, so the outer iframe forwards messages in both
114
- // directions to connect them.
115
- //
116
- // Special case: The "ui/notifications/sandbox-proxy-ready" message is
117
- // intercepted here (not relayed) because the Sandbox uses it to configure and
118
- // load the inner iframe with the view HTML content.
119
- //
120
- // Security: CSP is enforced via HTTP headers on sandbox.html (set by serve.ts
121
- // based on ?csp= query param). This is tamper-proof unlike meta tags.
122
-
123
- window.addEventListener("message", async (event) => {
124
- if (event.source === window.parent) {
125
- // Validate that messages from parent come from the expected host origin.
126
- // This prevents malicious pages from sending messages to this sandbox.
127
- if (event.origin !== EXPECTED_HOST_ORIGIN) {
128
- console.error(
129
- "[Sandbox] Rejecting message from unexpected origin:",
130
- event.origin,
131
- "expected:",
132
- EXPECTED_HOST_ORIGIN,
133
- );
134
- return;
135
- }
136
-
137
- if (event.data && event.data.method === RESOURCE_READY_NOTIFICATION) {
138
- const { html, sandbox, permissions } = event.data.params;
139
- if (typeof sandbox === "string") {
140
- inner.setAttribute("sandbox", sandbox);
141
- }
142
- // Set Permission Policy allow attribute if permissions are requested
143
- const allowAttribute = buildAllowAttribute(permissions);
144
- if (allowAttribute) {
145
- console.log("[Sandbox] Setting allow attribute:", allowAttribute);
146
- inner.setAttribute("allow", allowAttribute);
147
- }
148
- if (typeof html === "string") {
149
- // Use document.write instead of srcdoc (which the CesiumJS Map won't work with)
150
- const doc =
151
- inner.contentDocument || inner.contentWindow?.document;
152
- if (doc) {
153
- doc.open();
154
- doc.write(html);
155
- doc.close();
156
- } else {
157
- // Fallback to srcdoc if document is not accessible
158
- console.warn(
159
- "[Sandbox] document.write not available, falling back to srcdoc",
160
- );
161
- inner.srcdoc = html;
162
- }
163
- }
164
- } else {
165
- if (inner && inner.contentWindow) {
166
- inner.contentWindow.postMessage(event.data, "*");
167
- }
168
- }
169
- } else if (event.source === inner.contentWindow) {
170
- if (event.origin !== OWN_ORIGIN) {
171
- console.error(
172
- "[Sandbox] Rejecting message from inner iframe with unexpected origin:",
173
- event.origin,
174
- "expected:",
175
- OWN_ORIGIN,
176
- );
177
- return;
178
- }
179
- // Relay messages from inner frame to parent window.
180
- // Use specific origin instead of "*" to prevent message interception.
181
- window.parent.postMessage(event.data, EXPECTED_HOST_ORIGIN);
182
- }
183
- });
184
-
185
- // Notify the Host that the Sandbox is ready to receive view HTML.
186
- // Use specific origin instead of "*" to ensure only the expected host receives this.
187
- window.parent.postMessage(
188
- {
189
- jsonrpc: "2.0",
190
- method: PROXY_READY_NOTIFICATION,
191
- params: {},
192
- },
193
- EXPECTED_HOST_ORIGIN,
194
- );
195
- </script>
196
- </body>
197
- </html>
@@ -1,197 +0,0 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="color-scheme" content="light dark" />
6
- <!-- CSP is set via HTTP headers by serve.ts (based on ?csp= query param).
7
- The inner iframe inherits this CSP since we use document.write(). -->
8
- <title>MCP-UI Proxy</title>
9
- <style>
10
- html,
11
- body {
12
- margin: 0;
13
- height: 100vh;
14
- width: 100vw;
15
- /* Transparent background allows parent page to show through */
16
- background-color: transparent;
17
- }
18
- body {
19
- display: flex;
20
- flex-direction: column;
21
- }
22
- * {
23
- box-sizing: border-box;
24
- }
25
- iframe {
26
- background-color: transparent;
27
- border: 0px none transparent;
28
- padding: 0px;
29
- overflow: hidden;
30
- flex-grow: 1;
31
- /* Inherit color scheme from parent for consistent transparency */
32
- color-scheme: inherit;
33
- }
34
- </style>
35
- </head>
36
- <body>
37
- <script>
38
- function buildAllowAttribute(permissions) {
39
- if (!permissions) return "";
40
-
41
- const allowList = [];
42
- if (permissions["camera"]) allowList.push("camera");
43
- if (permissions["microphone"]) allowList.push("microphone");
44
- if (permissions["geolocation"]) allowList.push("geolocation");
45
- if (permissions["clipboardWrite"]) allowList.push("clipboard-write");
46
-
47
- return allowList.join("; ");
48
- }
49
-
50
- const ALLOWED_REFERRER_PATTERN =
51
- /^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/;
52
-
53
- if (window.self === window.top) {
54
- throw new Error("This file is only to be used in an iframe sandbox.");
55
- }
56
-
57
- if (!document.referrer) {
58
- throw new Error("No referrer, cannot validate embedding site.");
59
- }
60
-
61
- if (!document.referrer.match(ALLOWED_REFERRER_PATTERN)) {
62
- throw new Error(
63
- `Embedding domain not allowed in referrer ${document.referrer}. (Consider updating the validation logic to allow your domain.)`,
64
- );
65
- }
66
-
67
- // Extract the expected host origin from the referrer for origin validation.
68
- // This is the origin we expect all parent messages to come from.
69
- const EXPECTED_HOST_ORIGIN = new URL(document.referrer).origin;
70
-
71
- const OWN_ORIGIN = new URL(window.location.href).origin;
72
-
73
- // Security self-test: verify iframe isolation is working correctly.
74
- // This MUST throw a SecurityError -- if `window.top` is accessible, the sandbox
75
- // configuration is dangerously broken and untrusted content could escape.
76
- try {
77
- window.top?.alert(
78
- "If you see this, the sandbox is not setup securely.",
79
- );
80
- throw "FAIL";
81
- } catch (e) {
82
- if (e === "FAIL") {
83
- throw new Error("The sandbox is not setup securely.");
84
- }
85
-
86
- // Expected: SecurityError confirms proper sandboxing.
87
- }
88
-
89
- // Double-iframe sandbox architecture: THIS file is the outer sandbox proxy
90
- // iframe on a separate origin. It creates an inner iframe for untrusted HTML
91
- // content. Per the specification, the Host and the Sandbox MUST have different
92
- // origins.
93
- const inner = document.createElement("iframe");
94
- inner.style = "width:100%; height:100%; border:none;";
95
- inner.setAttribute(
96
- "sandbox",
97
- "allow-scripts allow-same-origin allow-forms",
98
- );
99
- // Note: allow attribute is set later when receiving sandbox-resource-ready notification
100
- // based on the permissions requested by the app
101
- document.body.appendChild(inner);
102
-
103
- const RESOURCE_READY_NOTIFICATION =
104
- "ui/notifications/sandbox-resource-ready";
105
- const PROXY_READY_NOTIFICATION = "ui/notifications/sandbox-proxy-ready";
106
-
107
- // Message relay: This Sandbox (outer iframe) acts as a bidirectional bridge,
108
- // forwarding messages between:
109
- //
110
- // Host (parent window) ↔ Sandbox (outer frame) ↔ View (inner iframe)
111
- //
112
- // Reason: the parent window and inner iframe have different origins and can't
113
- // communicate directly, so the outer iframe forwards messages in both
114
- // directions to connect them.
115
- //
116
- // Special case: The "ui/notifications/sandbox-proxy-ready" message is
117
- // intercepted here (not relayed) because the Sandbox uses it to configure and
118
- // load the inner iframe with the view HTML content.
119
- //
120
- // Security: CSP is enforced via HTTP headers on sandbox.html (set by serve.ts
121
- // based on ?csp= query param). This is tamper-proof unlike meta tags.
122
-
123
- window.addEventListener("message", async (event) => {
124
- if (event.source === window.parent) {
125
- // Validate that messages from parent come from the expected host origin.
126
- // This prevents malicious pages from sending messages to this sandbox.
127
- if (event.origin !== EXPECTED_HOST_ORIGIN) {
128
- console.error(
129
- "[Sandbox] Rejecting message from unexpected origin:",
130
- event.origin,
131
- "expected:",
132
- EXPECTED_HOST_ORIGIN,
133
- );
134
- return;
135
- }
136
-
137
- if (event.data && event.data.method === RESOURCE_READY_NOTIFICATION) {
138
- const { html, sandbox, permissions } = event.data.params;
139
- if (typeof sandbox === "string") {
140
- inner.setAttribute("sandbox", sandbox);
141
- }
142
- // Set Permission Policy allow attribute if permissions are requested
143
- const allowAttribute = buildAllowAttribute(permissions);
144
- if (allowAttribute) {
145
- console.log("[Sandbox] Setting allow attribute:", allowAttribute);
146
- inner.setAttribute("allow", allowAttribute);
147
- }
148
- if (typeof html === "string") {
149
- // Use document.write instead of srcdoc (which the CesiumJS Map won't work with)
150
- const doc =
151
- inner.contentDocument || inner.contentWindow?.document;
152
- if (doc) {
153
- doc.open();
154
- doc.write(html);
155
- doc.close();
156
- } else {
157
- // Fallback to srcdoc if document is not accessible
158
- console.warn(
159
- "[Sandbox] document.write not available, falling back to srcdoc",
160
- );
161
- inner.srcdoc = html;
162
- }
163
- }
164
- } else {
165
- if (inner && inner.contentWindow) {
166
- inner.contentWindow.postMessage(event.data, "*");
167
- }
168
- }
169
- } else if (event.source === inner.contentWindow) {
170
- if (event.origin !== OWN_ORIGIN) {
171
- console.error(
172
- "[Sandbox] Rejecting message from inner iframe with unexpected origin:",
173
- event.origin,
174
- "expected:",
175
- OWN_ORIGIN,
176
- );
177
- return;
178
- }
179
- // Relay messages from inner frame to parent window.
180
- // Use specific origin instead of "*" to prevent message interception.
181
- window.parent.postMessage(event.data, EXPECTED_HOST_ORIGIN);
182
- }
183
- });
184
-
185
- // Notify the Host that the Sandbox is ready to receive view HTML.
186
- // Use specific origin instead of "*" to ensure only the expected host receives this.
187
- window.parent.postMessage(
188
- {
189
- jsonrpc: "2.0",
190
- method: PROXY_READY_NOTIFICATION,
191
- params: {},
192
- },
193
- EXPECTED_HOST_ORIGIN,
194
- );
195
- </script>
196
- </body>
197
- </html>