@modelcontextprotocol/inspector 0.14.0 → 0.14.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.
- package/README.md +47 -0
- package/client/bin/start.js +3 -1
- package/client/dist/assets/{OAuthCallback-CJlw2xLt.js → OAuthCallback-D7GXYYW8.js} +1 -1
- package/client/dist/assets/{OAuthDebugCallback-0U3Rpr19.js → OAuthDebugCallback-25cy6Vcm.js} +1 -1
- package/client/dist/assets/{index-1IEtev7A.css → index-C1MLjTGf.css} +0 -3
- package/client/dist/assets/{index-CfNcvwpX.js → index-TghXC4TH.js} +234 -64
- package/client/dist/index.html +2 -2
- package/package.json +4 -4
- package/server/build/index.js +138 -48
- package/server/build/mcpProxy.js +16 -1
package/README.md
CHANGED
|
@@ -137,6 +137,53 @@ The inspector supports bearer token authentication for SSE connections. Enter yo
|
|
|
137
137
|
|
|
138
138
|
The MCP Inspector includes a proxy server that can run and communicate with local MCP processes. The proxy server should not be exposed to untrusted networks as it has permissions to spawn local processes and can connect to any specified MCP server.
|
|
139
139
|
|
|
140
|
+
#### Authentication
|
|
141
|
+
|
|
142
|
+
The MCP Inspector proxy server requires authentication by default. When starting the server, a random session token is generated and printed to the console:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
🔑 Session token: 3a1c267fad21f7150b7d624c160b7f09b0b8c4f623c7107bbf13378f051538d4
|
|
146
|
+
|
|
147
|
+
🔗 Open inspector with token pre-filled:
|
|
148
|
+
http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=3a1c267fad21f7150b7d624c160b7f09b0b8c4f623c7107bbf13378f051538d4
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
This token must be included as a Bearer token in the Authorization header for all requests to the server. When authentication is enabled, auto-open is disabled by default to ensure you use the secure URL.
|
|
152
|
+
|
|
153
|
+
**Recommended: Use the pre-filled URL** - Click or copy the link shown in the console to open the inspector with the token already configured.
|
|
154
|
+
|
|
155
|
+
**Alternative: Manual configuration** - If you already have the inspector open:
|
|
156
|
+
|
|
157
|
+
1. Click the "Configuration" button in the sidebar
|
|
158
|
+
2. Find "Proxy Session Token" and enter the token displayed in the proxy console
|
|
159
|
+
3. Click "Save" to apply the configuration
|
|
160
|
+
|
|
161
|
+
The token will be saved in your browser's local storage for future use.
|
|
162
|
+
|
|
163
|
+
If you need to disable authentication (NOT RECOMMENDED), you can set the `DANGEROUSLY_OMIT_AUTH` environment variable:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
DANGEROUSLY_OMIT_AUTH=true npm start
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### Local-only Binding
|
|
170
|
+
|
|
171
|
+
By default, the MCP Inspector proxy server binds only to `127.0.0.1` (localhost) to prevent network access. This ensures the server is not accessible from other devices on the network. If you need to bind to all interfaces for development purposes, you can override this with the `HOST` environment variable:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
HOST=0.0.0.0 npm start
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Warning:** Only bind to all interfaces in trusted network environments, as this exposes the proxy server's ability to execute local processes.
|
|
178
|
+
|
|
179
|
+
#### DNS Rebinding Protection
|
|
180
|
+
|
|
181
|
+
To prevent DNS rebinding attacks, the MCP Inspector validates the `Origin` header on incoming requests. By default, only requests from the client origin are allowed (respects `CLIENT_PORT` if set, defaulting to port 6274). You can configure additional allowed origins by setting the `ALLOWED_ORIGINS` environment variable (comma-separated list):
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
ALLOWED_ORIGINS=http://localhost:6274,http://127.0.0.1:6274,http://localhost:8000 npm start
|
|
185
|
+
```
|
|
186
|
+
|
|
140
187
|
### Configuration
|
|
141
188
|
|
|
142
189
|
The MCP Inspector supports the following configuration settings. To change them, click on the `Configuration` button in the MCP Inspector UI:
|
package/client/bin/start.js
CHANGED
|
@@ -100,7 +100,9 @@ async function main() {
|
|
|
100
100
|
|
|
101
101
|
if (serverOk) {
|
|
102
102
|
try {
|
|
103
|
-
|
|
103
|
+
// Only auto-open when auth is disabled
|
|
104
|
+
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
|
|
105
|
+
if (process.env.MCP_AUTO_OPEN_ENABLED !== "false" && authDisabled) {
|
|
104
106
|
open(`http://127.0.0.1:${CLIENT_PORT}`);
|
|
105
107
|
}
|
|
106
108
|
await spawnPromise("node", [inspectorClientPath], {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { u as useToast, r as reactExports, j as jsxRuntimeExports, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-
|
|
1
|
+
import { u as useToast, r as reactExports, j as jsxRuntimeExports, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-TghXC4TH.js";
|
|
2
2
|
import { p as parseOAuthCallbackParams, g as generateOAuthErrorDescription } from "./oauthUtils-DTcoXpSP.js";
|
|
3
3
|
const OAuthCallback = ({ onConnect }) => {
|
|
4
4
|
const { toast } = useToast();
|
package/client/dist/assets/{OAuthDebugCallback-0U3Rpr19.js → OAuthDebugCallback-25cy6Vcm.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as reactExports, S as SESSION_KEYS, j as jsxRuntimeExports } from "./index-
|
|
1
|
+
import { r as reactExports, S as SESSION_KEYS, j as jsxRuntimeExports } from "./index-TghXC4TH.js";
|
|
2
2
|
import { p as parseOAuthCallbackParams, g as generateOAuthErrorDescription } from "./oauthUtils-DTcoXpSP.js";
|
|
3
3
|
const OAuthDebugCallback = ({ onConnect }) => {
|
|
4
4
|
reactExports.useEffect(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/OAuthCallback-
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/OAuthCallback-D7GXYYW8.js","assets/oauthUtils-DTcoXpSP.js","assets/OAuthDebugCallback-25cy6Vcm.js"])))=>i.map(i=>d[i]);
|
|
2
2
|
function _mergeNamespaces(n, m2) {
|
|
3
3
|
for (var i = 0; i < m2.length; i++) {
|
|
4
4
|
const e = m2[i];
|
|
@@ -16039,22 +16039,32 @@ const DEFAULT_INSPECTOR_CONFIG = {
|
|
|
16039
16039
|
MCP_SERVER_REQUEST_TIMEOUT: {
|
|
16040
16040
|
label: "Request Timeout",
|
|
16041
16041
|
description: "Timeout for requests to the MCP server (ms)",
|
|
16042
|
-
value: 1e4
|
|
16042
|
+
value: 1e4,
|
|
16043
|
+
is_session_item: false
|
|
16043
16044
|
},
|
|
16044
16045
|
MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS: {
|
|
16045
16046
|
label: "Reset Timeout on Progress",
|
|
16046
16047
|
description: "Reset timeout on progress notifications",
|
|
16047
|
-
value: true
|
|
16048
|
+
value: true,
|
|
16049
|
+
is_session_item: false
|
|
16048
16050
|
},
|
|
16049
16051
|
MCP_REQUEST_MAX_TOTAL_TIMEOUT: {
|
|
16050
16052
|
label: "Maximum Total Timeout",
|
|
16051
16053
|
description: "Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)",
|
|
16052
|
-
value: 6e4
|
|
16054
|
+
value: 6e4,
|
|
16055
|
+
is_session_item: false
|
|
16053
16056
|
},
|
|
16054
16057
|
MCP_PROXY_FULL_ADDRESS: {
|
|
16055
16058
|
label: "Inspector Proxy Address",
|
|
16056
16059
|
description: "Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577",
|
|
16057
|
-
value: ""
|
|
16060
|
+
value: "",
|
|
16061
|
+
is_session_item: false
|
|
16062
|
+
},
|
|
16063
|
+
MCP_PROXY_AUTH_TOKEN: {
|
|
16064
|
+
label: "Proxy Session Token",
|
|
16065
|
+
description: "Session token for authenticating with the MCP Proxy Server (displayed in proxy console on startup)",
|
|
16066
|
+
value: "",
|
|
16067
|
+
is_session_item: true
|
|
16058
16068
|
}
|
|
16059
16069
|
};
|
|
16060
16070
|
const EMPTY_DEBUGGER_STATE = {
|
|
@@ -24146,7 +24156,7 @@ const StdErrNotificationSchema = NotificationSchema.extend({
|
|
|
24146
24156
|
ClientNotificationSchema.or(
|
|
24147
24157
|
StdErrNotificationSchema
|
|
24148
24158
|
).or(ServerNotificationSchema).or(NotificationSchema);
|
|
24149
|
-
const version$1 = "0.14.
|
|
24159
|
+
const version$1 = "0.14.1";
|
|
24150
24160
|
const packageJson = {
|
|
24151
24161
|
version: version$1
|
|
24152
24162
|
};
|
|
@@ -24166,6 +24176,9 @@ const resetRequestTimeoutOnProgress = (config) => {
|
|
|
24166
24176
|
const getMCPServerRequestMaxTotalTimeout = (config) => {
|
|
24167
24177
|
return config.MCP_REQUEST_MAX_TOTAL_TIMEOUT.value;
|
|
24168
24178
|
};
|
|
24179
|
+
const getMCPProxyAuthToken = (config) => {
|
|
24180
|
+
return config.MCP_PROXY_AUTH_TOKEN.value;
|
|
24181
|
+
};
|
|
24169
24182
|
const getSearchParam = (key) => {
|
|
24170
24183
|
try {
|
|
24171
24184
|
const url = new URL(window.location.href);
|
|
@@ -24218,26 +24231,46 @@ const getConfigOverridesFromQueryParams = (defaultConfig) => {
|
|
|
24218
24231
|
return overrides;
|
|
24219
24232
|
};
|
|
24220
24233
|
const initializeInspectorConfig = (localStorageKey) => {
|
|
24221
|
-
const
|
|
24222
|
-
|
|
24223
|
-
|
|
24224
|
-
|
|
24225
|
-
|
|
24226
|
-
|
|
24234
|
+
const savedPersistentConfig = localStorage.getItem(localStorageKey);
|
|
24235
|
+
const savedEphemeralConfig = sessionStorage.getItem(
|
|
24236
|
+
`${localStorageKey}_ephemeral`
|
|
24237
|
+
);
|
|
24238
|
+
let baseConfig = { ...DEFAULT_INSPECTOR_CONFIG };
|
|
24239
|
+
if (savedPersistentConfig) {
|
|
24240
|
+
const parsedPersistentConfig = JSON.parse(savedPersistentConfig);
|
|
24241
|
+
baseConfig = { ...baseConfig, ...parsedPersistentConfig };
|
|
24242
|
+
}
|
|
24243
|
+
if (savedEphemeralConfig) {
|
|
24244
|
+
const parsedEphemeralConfig = JSON.parse(savedEphemeralConfig);
|
|
24245
|
+
baseConfig = { ...baseConfig, ...parsedEphemeralConfig };
|
|
24246
|
+
}
|
|
24247
|
+
for (const [key, value] of Object.entries(baseConfig)) {
|
|
24248
|
+
baseConfig[key] = {
|
|
24249
|
+
...value,
|
|
24250
|
+
label: DEFAULT_INSPECTOR_CONFIG[key].label,
|
|
24251
|
+
description: DEFAULT_INSPECTOR_CONFIG[key].description,
|
|
24252
|
+
is_session_item: DEFAULT_INSPECTOR_CONFIG[key].is_session_item
|
|
24227
24253
|
};
|
|
24228
|
-
for (const [key, value] of Object.entries(mergedConfig)) {
|
|
24229
|
-
mergedConfig[key] = {
|
|
24230
|
-
...value,
|
|
24231
|
-
label: DEFAULT_INSPECTOR_CONFIG[key].label
|
|
24232
|
-
};
|
|
24233
|
-
}
|
|
24234
|
-
baseConfig = mergedConfig;
|
|
24235
|
-
} else {
|
|
24236
|
-
baseConfig = DEFAULT_INSPECTOR_CONFIG;
|
|
24237
24254
|
}
|
|
24238
24255
|
const overrides = getConfigOverridesFromQueryParams(DEFAULT_INSPECTOR_CONFIG);
|
|
24239
24256
|
return { ...baseConfig, ...overrides };
|
|
24240
24257
|
};
|
|
24258
|
+
const saveInspectorConfig = (localStorageKey, config) => {
|
|
24259
|
+
const persistentConfig = {};
|
|
24260
|
+
const ephemeralConfig = {};
|
|
24261
|
+
for (const [key, value] of Object.entries(config)) {
|
|
24262
|
+
if (value.is_session_item) {
|
|
24263
|
+
ephemeralConfig[key] = value;
|
|
24264
|
+
} else {
|
|
24265
|
+
persistentConfig[key] = value;
|
|
24266
|
+
}
|
|
24267
|
+
}
|
|
24268
|
+
localStorage.setItem(localStorageKey, JSON.stringify(persistentConfig));
|
|
24269
|
+
sessionStorage.setItem(
|
|
24270
|
+
`${localStorageKey}_ephemeral`,
|
|
24271
|
+
JSON.stringify(ephemeralConfig)
|
|
24272
|
+
);
|
|
24273
|
+
};
|
|
24241
24274
|
function useConnection({
|
|
24242
24275
|
transportType,
|
|
24243
24276
|
command,
|
|
@@ -24375,7 +24408,12 @@ function useConnection({
|
|
|
24375
24408
|
const checkProxyHealth = async () => {
|
|
24376
24409
|
try {
|
|
24377
24410
|
const proxyHealthUrl = new URL(`${getMCPProxyAddress(config)}/health`);
|
|
24378
|
-
const
|
|
24411
|
+
const proxyAuthToken = getMCPProxyAuthToken(config);
|
|
24412
|
+
const headers = {};
|
|
24413
|
+
if (proxyAuthToken) {
|
|
24414
|
+
headers["Authorization"] = `Bearer ${proxyAuthToken}`;
|
|
24415
|
+
}
|
|
24416
|
+
const proxyHealthResponse = await fetch(proxyHealthUrl, { headers });
|
|
24379
24417
|
const proxyHealth = await proxyHealthResponse.json();
|
|
24380
24418
|
if ((proxyHealth == null ? void 0 : proxyHealth.status) !== "ok") {
|
|
24381
24419
|
throw new Error("MCP Proxy Server is not healthy");
|
|
@@ -24388,6 +24426,9 @@ function useConnection({
|
|
|
24388
24426
|
const is401Error = (error) => {
|
|
24389
24427
|
return error instanceof SseError && error.code === 401 || error instanceof Error && error.message.includes("401") || error instanceof Error && error.message.includes("Unauthorized");
|
|
24390
24428
|
};
|
|
24429
|
+
const isProxyAuthError = (error) => {
|
|
24430
|
+
return error instanceof Error && error.message.includes("Authentication required. Use the session token");
|
|
24431
|
+
};
|
|
24391
24432
|
const handleAuthError = async (error) => {
|
|
24392
24433
|
if (is401Error(error)) {
|
|
24393
24434
|
const serverAuthProvider = new InspectorOAuthClientProvider(sseUrl);
|
|
@@ -24431,6 +24472,11 @@ function useConnection({
|
|
|
24431
24472
|
headers[authHeaderName] = `Bearer ${token}`;
|
|
24432
24473
|
}
|
|
24433
24474
|
}
|
|
24475
|
+
const proxyAuthToken = getMCPProxyAuthToken(config);
|
|
24476
|
+
const proxyHeaders = {};
|
|
24477
|
+
if (proxyAuthToken) {
|
|
24478
|
+
proxyHeaders["Authorization"] = `Bearer ${proxyAuthToken}`;
|
|
24479
|
+
}
|
|
24434
24480
|
let transportOptions;
|
|
24435
24481
|
let mcpProxyServerUrl;
|
|
24436
24482
|
switch (transportType) {
|
|
@@ -24442,10 +24488,13 @@ function useConnection({
|
|
|
24442
24488
|
transportOptions = {
|
|
24443
24489
|
authProvider: serverAuthProvider,
|
|
24444
24490
|
eventSourceInit: {
|
|
24445
|
-
fetch: (url, init) => fetch(url, {
|
|
24491
|
+
fetch: (url, init) => fetch(url, {
|
|
24492
|
+
...init,
|
|
24493
|
+
headers: { ...headers, ...proxyHeaders }
|
|
24494
|
+
})
|
|
24446
24495
|
},
|
|
24447
24496
|
requestInit: {
|
|
24448
|
-
headers
|
|
24497
|
+
headers: { ...headers, ...proxyHeaders }
|
|
24449
24498
|
}
|
|
24450
24499
|
};
|
|
24451
24500
|
break;
|
|
@@ -24454,10 +24503,13 @@ function useConnection({
|
|
|
24454
24503
|
mcpProxyServerUrl.searchParams.append("url", sseUrl);
|
|
24455
24504
|
transportOptions = {
|
|
24456
24505
|
eventSourceInit: {
|
|
24457
|
-
fetch: (url, init) => fetch(url, {
|
|
24506
|
+
fetch: (url, init) => fetch(url, {
|
|
24507
|
+
...init,
|
|
24508
|
+
headers: { ...headers, ...proxyHeaders }
|
|
24509
|
+
})
|
|
24458
24510
|
},
|
|
24459
24511
|
requestInit: {
|
|
24460
|
-
headers
|
|
24512
|
+
headers: { ...headers, ...proxyHeaders }
|
|
24461
24513
|
}
|
|
24462
24514
|
};
|
|
24463
24515
|
break;
|
|
@@ -24466,10 +24518,13 @@ function useConnection({
|
|
|
24466
24518
|
mcpProxyServerUrl.searchParams.append("url", sseUrl);
|
|
24467
24519
|
transportOptions = {
|
|
24468
24520
|
eventSourceInit: {
|
|
24469
|
-
fetch: (url, init) => fetch(url, {
|
|
24521
|
+
fetch: (url, init) => fetch(url, {
|
|
24522
|
+
...init,
|
|
24523
|
+
headers: { ...headers, ...proxyHeaders }
|
|
24524
|
+
})
|
|
24470
24525
|
},
|
|
24471
24526
|
requestInit: {
|
|
24472
|
-
headers
|
|
24527
|
+
headers: { ...headers, ...proxyHeaders }
|
|
24473
24528
|
},
|
|
24474
24529
|
// TODO these should be configurable...
|
|
24475
24530
|
reconnectionOptions: {
|
|
@@ -24532,6 +24587,15 @@ function useConnection({
|
|
|
24532
24587
|
`Failed to connect to MCP Server via the MCP Inspector Proxy: ${mcpProxyServerUrl}:`,
|
|
24533
24588
|
error
|
|
24534
24589
|
);
|
|
24590
|
+
if (isProxyAuthError(error)) {
|
|
24591
|
+
toast2({
|
|
24592
|
+
title: "Proxy Authentication Required",
|
|
24593
|
+
description: "Please enter the session token from the proxy server console in the Configuration settings.",
|
|
24594
|
+
variant: "destructive"
|
|
24595
|
+
});
|
|
24596
|
+
setConnectionStatus("error");
|
|
24597
|
+
return;
|
|
24598
|
+
}
|
|
24535
24599
|
const shouldRetry = await handleAuthError(error);
|
|
24536
24600
|
if (shouldRetry) {
|
|
24537
24601
|
return connect(void 0, retryCount + 1);
|
|
@@ -24633,6 +24697,52 @@ function useDraggablePane(initialHeight) {
|
|
|
24633
24697
|
handleDragStart
|
|
24634
24698
|
};
|
|
24635
24699
|
}
|
|
24700
|
+
function useDraggableSidebar(initialWidth) {
|
|
24701
|
+
const [width, setWidth] = reactExports.useState(initialWidth);
|
|
24702
|
+
const [isDragging, setIsDragging] = reactExports.useState(false);
|
|
24703
|
+
const dragStartX = reactExports.useRef(0);
|
|
24704
|
+
const dragStartWidth = reactExports.useRef(0);
|
|
24705
|
+
const handleDragStart = reactExports.useCallback(
|
|
24706
|
+
(e) => {
|
|
24707
|
+
setIsDragging(true);
|
|
24708
|
+
dragStartX.current = e.clientX;
|
|
24709
|
+
dragStartWidth.current = width;
|
|
24710
|
+
document.body.style.userSelect = "none";
|
|
24711
|
+
},
|
|
24712
|
+
[width]
|
|
24713
|
+
);
|
|
24714
|
+
const handleDragMove = reactExports.useCallback(
|
|
24715
|
+
(e) => {
|
|
24716
|
+
if (!isDragging) return;
|
|
24717
|
+
const deltaX = e.clientX - dragStartX.current;
|
|
24718
|
+
const newWidth = Math.max(
|
|
24719
|
+
200,
|
|
24720
|
+
Math.min(600, dragStartWidth.current + deltaX)
|
|
24721
|
+
);
|
|
24722
|
+
setWidth(newWidth);
|
|
24723
|
+
},
|
|
24724
|
+
[isDragging]
|
|
24725
|
+
);
|
|
24726
|
+
const handleDragEnd = reactExports.useCallback(() => {
|
|
24727
|
+
setIsDragging(false);
|
|
24728
|
+
document.body.style.userSelect = "";
|
|
24729
|
+
}, []);
|
|
24730
|
+
reactExports.useEffect(() => {
|
|
24731
|
+
if (isDragging) {
|
|
24732
|
+
window.addEventListener("mousemove", handleDragMove);
|
|
24733
|
+
window.addEventListener("mouseup", handleDragEnd);
|
|
24734
|
+
return () => {
|
|
24735
|
+
window.removeEventListener("mousemove", handleDragMove);
|
|
24736
|
+
window.removeEventListener("mouseup", handleDragEnd);
|
|
24737
|
+
};
|
|
24738
|
+
}
|
|
24739
|
+
}, [isDragging, handleDragMove, handleDragEnd]);
|
|
24740
|
+
return {
|
|
24741
|
+
width,
|
|
24742
|
+
isDragging,
|
|
24743
|
+
handleDragStart
|
|
24744
|
+
};
|
|
24745
|
+
}
|
|
24636
24746
|
var useReactId = React$1[" useId ".trim().toString()] || (() => void 0);
|
|
24637
24747
|
var count$1 = 0;
|
|
24638
24748
|
function useId(deterministicId) {
|
|
@@ -35185,7 +35295,7 @@ const useTheme = () => {
|
|
|
35185
35295
|
[theme, setThemeWithSideEffect]
|
|
35186
35296
|
);
|
|
35187
35297
|
};
|
|
35188
|
-
const version = "0.14.
|
|
35298
|
+
const version = "0.14.1";
|
|
35189
35299
|
var [createTooltipContext, createTooltipScope] = createContextScope("Tooltip", [
|
|
35190
35300
|
createPopperScope
|
|
35191
35301
|
]);
|
|
@@ -35788,7 +35898,7 @@ const Sidebar = ({
|
|
|
35788
35898
|
reportError2(error);
|
|
35789
35899
|
}
|
|
35790
35900
|
}, [generateMCPServerFile, toast2, reportError2]);
|
|
35791
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "
|
|
35901
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-card border-r border-border flex flex-col h-full", children: [
|
|
35792
35902
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-between p-4 border-b border-gray-200 dark:border-border", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("h1", { className: "ml-2 text-lg font-semibold", children: [
|
|
35793
35903
|
"MCP Inspector v",
|
|
35794
35904
|
version
|
|
@@ -35856,7 +35966,19 @@ const Sidebar = ({
|
|
|
35856
35966
|
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
35857
35967
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
35858
35968
|
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-sm font-medium", htmlFor: "sse-url-input", children: "URL" }),
|
|
35859
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
35969
|
+
sseUrl ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
|
|
35970
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35971
|
+
Input,
|
|
35972
|
+
{
|
|
35973
|
+
id: "sse-url-input",
|
|
35974
|
+
placeholder: "URL",
|
|
35975
|
+
value: sseUrl,
|
|
35976
|
+
onChange: (e) => setSseUrl(e.target.value),
|
|
35977
|
+
className: "font-mono"
|
|
35978
|
+
}
|
|
35979
|
+
) }),
|
|
35980
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: sseUrl })
|
|
35981
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35860
35982
|
Input,
|
|
35861
35983
|
{
|
|
35862
35984
|
id: "sse-url-input",
|
|
@@ -36207,11 +36329,17 @@ const Sidebar = ({
|
|
|
36207
36329
|
}
|
|
36208
36330
|
),
|
|
36209
36331
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: (() => {
|
|
36332
|
+
var _a;
|
|
36210
36333
|
switch (connectionStatus) {
|
|
36211
36334
|
case "connected":
|
|
36212
36335
|
return "Connected";
|
|
36213
|
-
case "error":
|
|
36214
|
-
|
|
36336
|
+
case "error": {
|
|
36337
|
+
const hasProxyToken = (_a = config.MCP_PROXY_AUTH_TOKEN) == null ? void 0 : _a.value;
|
|
36338
|
+
if (!hasProxyToken) {
|
|
36339
|
+
return "Connection Error - Did you add the proxy session token in Configuration?";
|
|
36340
|
+
}
|
|
36341
|
+
return "Connection Error - Check if your MCP server is running and proxy token is correct";
|
|
36342
|
+
}
|
|
36215
36343
|
case "error-connecting-to-proxy":
|
|
36216
36344
|
return "Error Connecting to MCP Inspector Proxy - Check Console logs";
|
|
36217
36345
|
default:
|
|
@@ -37015,6 +37143,11 @@ const App = () => {
|
|
|
37015
37143
|
const [nextToolCursor, setNextToolCursor] = reactExports.useState();
|
|
37016
37144
|
const progressTokenRef = reactExports.useRef(0);
|
|
37017
37145
|
const { height: historyPaneHeight, handleDragStart } = useDraggablePane(300);
|
|
37146
|
+
const {
|
|
37147
|
+
width: sidebarWidth,
|
|
37148
|
+
isDragging: isSidebarDragging,
|
|
37149
|
+
handleDragStart: handleSidebarDragStart
|
|
37150
|
+
} = useDraggableSidebar(320);
|
|
37018
37151
|
const {
|
|
37019
37152
|
connectionStatus,
|
|
37020
37153
|
serverCapabilities,
|
|
@@ -37071,7 +37204,7 @@ const App = () => {
|
|
|
37071
37204
|
localStorage.setItem("lastHeaderName", headerName);
|
|
37072
37205
|
}, [headerName]);
|
|
37073
37206
|
reactExports.useEffect(() => {
|
|
37074
|
-
|
|
37207
|
+
saveInspectorConfig(CONFIG_LOCAL_STORAGE_KEY, config);
|
|
37075
37208
|
}, [config]);
|
|
37076
37209
|
const onOAuthConnect = reactExports.useCallback(
|
|
37077
37210
|
(serverUrl) => {
|
|
@@ -37163,7 +37296,12 @@ const App = () => {
|
|
|
37163
37296
|
loadOAuthTokens();
|
|
37164
37297
|
}, [sseUrl]);
|
|
37165
37298
|
reactExports.useEffect(() => {
|
|
37166
|
-
|
|
37299
|
+
const headers = {};
|
|
37300
|
+
const proxyAuthToken = getMCPProxyAuthToken(config);
|
|
37301
|
+
if (proxyAuthToken) {
|
|
37302
|
+
headers["Authorization"] = `Bearer ${proxyAuthToken}`;
|
|
37303
|
+
}
|
|
37304
|
+
fetch(`${getMCPProxyAddress(config)}/config`, { headers }).then((response) => response.json()).then((data2) => {
|
|
37167
37305
|
setEnv(data2.defaultEnvironment);
|
|
37168
37306
|
if (data2.defaultCommand) {
|
|
37169
37307
|
setCommand(data2.defaultCommand);
|
|
@@ -37174,7 +37312,7 @@ const App = () => {
|
|
|
37174
37312
|
}).catch(
|
|
37175
37313
|
(error) => console.error("Error fetching default environment:", error)
|
|
37176
37314
|
);
|
|
37177
|
-
}, []);
|
|
37315
|
+
}, [config]);
|
|
37178
37316
|
reactExports.useEffect(() => {
|
|
37179
37317
|
rootsRef.current = roots;
|
|
37180
37318
|
}, [roots]);
|
|
@@ -37378,44 +37516,76 @@ const App = () => {
|
|
|
37378
37516
|
) });
|
|
37379
37517
|
if (window.location.pathname === "/oauth/callback") {
|
|
37380
37518
|
const OAuthCallback = React.lazy(
|
|
37381
|
-
() => __vitePreload(() => import("./OAuthCallback-
|
|
37519
|
+
() => __vitePreload(() => import("./OAuthCallback-D7GXYYW8.js"), true ? __vite__mapDeps([0,1]) : void 0)
|
|
37382
37520
|
);
|
|
37383
37521
|
return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthCallback, { onConnect: onOAuthConnect }) });
|
|
37384
37522
|
}
|
|
37385
37523
|
if (window.location.pathname === "/oauth/callback/debug") {
|
|
37386
37524
|
const OAuthDebugCallback = React.lazy(
|
|
37387
|
-
() => __vitePreload(() => import("./OAuthDebugCallback-
|
|
37525
|
+
() => __vitePreload(() => import("./OAuthDebugCallback-25cy6Vcm.js"), true ? __vite__mapDeps([2,1]) : void 0)
|
|
37388
37526
|
);
|
|
37389
37527
|
return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthDebugCallback, { onConnect: onOAuthDebugConnect }) });
|
|
37390
37528
|
}
|
|
37391
37529
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex h-screen bg-background", children: [
|
|
37392
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
37393
|
-
|
|
37530
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
37531
|
+
"div",
|
|
37394
37532
|
{
|
|
37395
|
-
|
|
37396
|
-
|
|
37397
|
-
|
|
37398
|
-
|
|
37399
|
-
|
|
37400
|
-
|
|
37401
|
-
|
|
37402
|
-
|
|
37403
|
-
|
|
37404
|
-
|
|
37405
|
-
|
|
37406
|
-
|
|
37407
|
-
|
|
37408
|
-
|
|
37409
|
-
|
|
37410
|
-
|
|
37411
|
-
|
|
37412
|
-
|
|
37413
|
-
|
|
37414
|
-
|
|
37415
|
-
|
|
37416
|
-
|
|
37417
|
-
|
|
37418
|
-
|
|
37533
|
+
style: {
|
|
37534
|
+
width: sidebarWidth,
|
|
37535
|
+
minWidth: 200,
|
|
37536
|
+
maxWidth: 600,
|
|
37537
|
+
transition: isSidebarDragging ? "none" : "width 0.15s"
|
|
37538
|
+
},
|
|
37539
|
+
className: "bg-card border-r border-border flex flex-col h-full relative",
|
|
37540
|
+
children: [
|
|
37541
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
37542
|
+
Sidebar,
|
|
37543
|
+
{
|
|
37544
|
+
connectionStatus,
|
|
37545
|
+
transportType,
|
|
37546
|
+
setTransportType,
|
|
37547
|
+
command,
|
|
37548
|
+
setCommand,
|
|
37549
|
+
args,
|
|
37550
|
+
setArgs,
|
|
37551
|
+
sseUrl,
|
|
37552
|
+
setSseUrl,
|
|
37553
|
+
env,
|
|
37554
|
+
setEnv,
|
|
37555
|
+
config,
|
|
37556
|
+
setConfig,
|
|
37557
|
+
bearerToken,
|
|
37558
|
+
setBearerToken,
|
|
37559
|
+
headerName,
|
|
37560
|
+
setHeaderName,
|
|
37561
|
+
onConnect: connectMcpServer,
|
|
37562
|
+
onDisconnect: disconnectMcpServer,
|
|
37563
|
+
stdErrNotifications,
|
|
37564
|
+
logLevel,
|
|
37565
|
+
sendLogLevelRequest,
|
|
37566
|
+
loggingSupported: !!(serverCapabilities == null ? void 0 : serverCapabilities.logging) || false,
|
|
37567
|
+
clearStdErrNotifications
|
|
37568
|
+
}
|
|
37569
|
+
),
|
|
37570
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
37571
|
+
"div",
|
|
37572
|
+
{
|
|
37573
|
+
onMouseDown: handleSidebarDragStart,
|
|
37574
|
+
style: {
|
|
37575
|
+
cursor: "col-resize",
|
|
37576
|
+
position: "absolute",
|
|
37577
|
+
top: 0,
|
|
37578
|
+
right: 0,
|
|
37579
|
+
width: 6,
|
|
37580
|
+
height: "100%",
|
|
37581
|
+
zIndex: 10,
|
|
37582
|
+
background: isSidebarDragging ? "rgba(0,0,0,0.08)" : "transparent"
|
|
37583
|
+
},
|
|
37584
|
+
"aria-label": "Resize sidebar",
|
|
37585
|
+
"data-testid": "sidebar-drag-handle"
|
|
37586
|
+
}
|
|
37587
|
+
)
|
|
37588
|
+
]
|
|
37419
37589
|
}
|
|
37420
37590
|
),
|
|
37421
37591
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col overflow-hidden", children: [
|
package/client/dist/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/mcp.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>MCP Inspector</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-TghXC4TH.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C1MLjTGf.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelcontextprotocol/inspector",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Model Context Protocol inspector",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@modelcontextprotocol/inspector-cli": "^0.14.
|
|
45
|
-
"@modelcontextprotocol/inspector-client": "^0.14.
|
|
46
|
-
"@modelcontextprotocol/inspector-server": "^0.14.
|
|
44
|
+
"@modelcontextprotocol/inspector-cli": "^0.14.1",
|
|
45
|
+
"@modelcontextprotocol/inspector-client": "^0.14.1",
|
|
46
|
+
"@modelcontextprotocol/inspector-server": "^0.14.1",
|
|
47
47
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
48
48
|
"concurrently": "^9.0.1",
|
|
49
49
|
"open": "^10.1.0",
|
package/server/build/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
|
10
10
|
import express from "express";
|
|
11
11
|
import { findActualExecutable } from "spawn-rx";
|
|
12
12
|
import mcpProxy from "./mcpProxy.js";
|
|
13
|
-
import { randomUUID } from "node:crypto";
|
|
13
|
+
import { randomUUID, randomBytes, timingSafeEqual } from "node:crypto";
|
|
14
14
|
const SSE_HEADERS_PASSTHROUGH = ["authorization"];
|
|
15
15
|
const STREAMABLE_HTTP_HEADERS_PASSTHROUGH = [
|
|
16
16
|
"authorization",
|
|
@@ -65,9 +65,63 @@ app.use((req, res, next) => {
|
|
|
65
65
|
});
|
|
66
66
|
const webAppTransports = new Map(); // Web app transports by web app sessionId
|
|
67
67
|
const serverTransports = new Map(); // Server Transports by web app sessionId
|
|
68
|
+
const sessionToken = randomBytes(32).toString("hex");
|
|
69
|
+
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
|
|
70
|
+
// Origin validation middleware to prevent DNS rebinding attacks
|
|
71
|
+
const originValidationMiddleware = (req, res, next) => {
|
|
72
|
+
const origin = req.headers.origin;
|
|
73
|
+
// Default origins based on CLIENT_PORT or use environment variable
|
|
74
|
+
const clientPort = process.env.CLIENT_PORT || "6274";
|
|
75
|
+
const defaultOrigins = [
|
|
76
|
+
`http://localhost:${clientPort}`,
|
|
77
|
+
`http://127.0.0.1:${clientPort}`,
|
|
78
|
+
];
|
|
79
|
+
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",") || defaultOrigins;
|
|
80
|
+
if (origin && !allowedOrigins.includes(origin)) {
|
|
81
|
+
console.error(`Invalid origin: ${origin}`);
|
|
82
|
+
res.status(403).json({
|
|
83
|
+
error: "Forbidden - invalid origin",
|
|
84
|
+
message: "Request blocked to prevent DNS rebinding attacks. Configure allowed origins via environment variable.",
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
next();
|
|
89
|
+
};
|
|
90
|
+
const authMiddleware = (req, res, next) => {
|
|
91
|
+
if (authDisabled) {
|
|
92
|
+
return next();
|
|
93
|
+
}
|
|
94
|
+
const sendUnauthorized = () => {
|
|
95
|
+
res.status(401).json({
|
|
96
|
+
error: "Unauthorized",
|
|
97
|
+
message: "Authentication required. Use the session token shown in the console when starting the server.",
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
const authHeader = req.headers.authorization;
|
|
101
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
102
|
+
sendUnauthorized();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const providedToken = authHeader.substring(7); // Remove 'Bearer ' prefix
|
|
106
|
+
const expectedToken = sessionToken;
|
|
107
|
+
// Convert to buffers for timing-safe comparison
|
|
108
|
+
const providedBuffer = Buffer.from(providedToken);
|
|
109
|
+
const expectedBuffer = Buffer.from(expectedToken);
|
|
110
|
+
// Check length first to prevent timing attacks
|
|
111
|
+
if (providedBuffer.length !== expectedBuffer.length) {
|
|
112
|
+
sendUnauthorized();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Perform timing-safe comparison
|
|
116
|
+
if (!timingSafeEqual(providedBuffer, expectedBuffer)) {
|
|
117
|
+
sendUnauthorized();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
next();
|
|
121
|
+
};
|
|
68
122
|
const createTransport = async (req) => {
|
|
69
123
|
const query = req.query;
|
|
70
|
-
console.log("Query parameters:", query);
|
|
124
|
+
console.log("Query parameters:", JSON.stringify(query));
|
|
71
125
|
const transportType = query.transportType;
|
|
72
126
|
if (transportType === "stdio") {
|
|
73
127
|
const command = query.command;
|
|
@@ -75,7 +129,7 @@ const createTransport = async (req) => {
|
|
|
75
129
|
const queryEnv = query.env ? JSON.parse(query.env) : {};
|
|
76
130
|
const env = { ...process.env, ...defaultEnvironment, ...queryEnv };
|
|
77
131
|
const { cmd, args } = findActualExecutable(command, origArgs);
|
|
78
|
-
console.log(`
|
|
132
|
+
console.log(`STDIO transport: command=${cmd}, args=${args}`);
|
|
79
133
|
const transport = new StdioClientTransport({
|
|
80
134
|
command: cmd,
|
|
81
135
|
args,
|
|
@@ -83,7 +137,6 @@ const createTransport = async (req) => {
|
|
|
83
137
|
stderr: "pipe",
|
|
84
138
|
});
|
|
85
139
|
await transport.start();
|
|
86
|
-
console.log("Spawned stdio transport");
|
|
87
140
|
return transport;
|
|
88
141
|
}
|
|
89
142
|
else if (transportType === "sse") {
|
|
@@ -99,7 +152,6 @@ const createTransport = async (req) => {
|
|
|
99
152
|
},
|
|
100
153
|
});
|
|
101
154
|
await transport.start();
|
|
102
|
-
console.log("Connected to SSE transport");
|
|
103
155
|
return transport;
|
|
104
156
|
}
|
|
105
157
|
else if (transportType === "streamable-http") {
|
|
@@ -110,7 +162,6 @@ const createTransport = async (req) => {
|
|
|
110
162
|
},
|
|
111
163
|
});
|
|
112
164
|
await transport.start();
|
|
113
|
-
console.log("Connected to Streamable HTTP transport");
|
|
114
165
|
return transport;
|
|
115
166
|
}
|
|
116
167
|
else {
|
|
@@ -118,7 +169,7 @@ const createTransport = async (req) => {
|
|
|
118
169
|
throw new Error("Invalid transport type specified");
|
|
119
170
|
}
|
|
120
171
|
};
|
|
121
|
-
app.get("/mcp", async (req, res) => {
|
|
172
|
+
app.get("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
122
173
|
const sessionId = req.headers["mcp-session-id"];
|
|
123
174
|
console.log(`Received GET message for sessionId ${sessionId}`);
|
|
124
175
|
try {
|
|
@@ -136,13 +187,12 @@ app.get("/mcp", async (req, res) => {
|
|
|
136
187
|
res.status(500).json(error);
|
|
137
188
|
}
|
|
138
189
|
});
|
|
139
|
-
app.post("/mcp", async (req, res) => {
|
|
190
|
+
app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
140
191
|
const sessionId = req.headers["mcp-session-id"];
|
|
141
|
-
console.log(`Received POST message for sessionId ${sessionId}`);
|
|
142
192
|
let serverTransport;
|
|
143
193
|
if (!sessionId) {
|
|
144
194
|
try {
|
|
145
|
-
console.log("New
|
|
195
|
+
console.log("New StreamableHttp connection request");
|
|
146
196
|
try {
|
|
147
197
|
serverTransport = await createTransport(req);
|
|
148
198
|
}
|
|
@@ -154,15 +204,16 @@ app.post("/mcp", async (req, res) => {
|
|
|
154
204
|
}
|
|
155
205
|
throw error;
|
|
156
206
|
}
|
|
157
|
-
console.log("
|
|
207
|
+
console.log("Created StreamableHttp server transport");
|
|
158
208
|
const webAppTransport = new StreamableHTTPServerTransport({
|
|
159
209
|
sessionIdGenerator: randomUUID,
|
|
160
210
|
onsessioninitialized: (sessionId) => {
|
|
161
211
|
webAppTransports.set(sessionId, webAppTransport);
|
|
162
212
|
serverTransports.set(sessionId, serverTransport);
|
|
163
|
-
console.log("
|
|
213
|
+
console.log("Client <-> Proxy sessionId: " + sessionId);
|
|
164
214
|
},
|
|
165
215
|
});
|
|
216
|
+
console.log("Created StreamableHttp client transport");
|
|
166
217
|
await webAppTransport.start();
|
|
167
218
|
mcpProxy({
|
|
168
219
|
transportToClient: webAppTransport,
|
|
@@ -176,6 +227,7 @@ app.post("/mcp", async (req, res) => {
|
|
|
176
227
|
}
|
|
177
228
|
}
|
|
178
229
|
else {
|
|
230
|
+
console.log(`Received POST message for sessionId ${sessionId}`);
|
|
179
231
|
try {
|
|
180
232
|
const transport = webAppTransports.get(sessionId);
|
|
181
233
|
if (!transport) {
|
|
@@ -191,7 +243,7 @@ app.post("/mcp", async (req, res) => {
|
|
|
191
243
|
}
|
|
192
244
|
}
|
|
193
245
|
});
|
|
194
|
-
app.delete("/mcp", async (req, res) => {
|
|
246
|
+
app.delete("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
195
247
|
const sessionId = req.headers["mcp-session-id"];
|
|
196
248
|
console.log(`Received DELETE message for sessionId ${sessionId}`);
|
|
197
249
|
let serverTransport;
|
|
@@ -215,84 +267,110 @@ app.delete("/mcp", async (req, res) => {
|
|
|
215
267
|
}
|
|
216
268
|
}
|
|
217
269
|
});
|
|
218
|
-
app.get("/stdio", async (req, res) => {
|
|
270
|
+
app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
219
271
|
try {
|
|
220
|
-
console.log("New connection");
|
|
272
|
+
console.log("New STDIO connection request");
|
|
221
273
|
let serverTransport;
|
|
222
274
|
try {
|
|
223
275
|
serverTransport = await createTransport(req);
|
|
276
|
+
console.log("Created server transport");
|
|
224
277
|
}
|
|
225
278
|
catch (error) {
|
|
226
279
|
if (error instanceof SseError && error.code === 401) {
|
|
227
|
-
console.error("Received 401 Unauthorized from MCP server
|
|
280
|
+
console.error("Received 401 Unauthorized from MCP server. Authentication failure.");
|
|
228
281
|
res.status(401).json(error);
|
|
229
282
|
return;
|
|
230
283
|
}
|
|
231
284
|
throw error;
|
|
232
285
|
}
|
|
233
|
-
console.log("Connected MCP client to backing server transport");
|
|
234
286
|
const webAppTransport = new SSEServerTransport("/message", res);
|
|
287
|
+
console.log("Created client transport");
|
|
235
288
|
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
|
236
289
|
serverTransports.set(webAppTransport.sessionId, serverTransport);
|
|
237
|
-
console.log("Created client/server transports");
|
|
238
290
|
await webAppTransport.start();
|
|
239
291
|
serverTransport.stderr.on("data", (chunk) => {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
292
|
+
if (chunk.toString().includes("MODULE_NOT_FOUND")) {
|
|
293
|
+
webAppTransport.send({
|
|
294
|
+
jsonrpc: "2.0",
|
|
295
|
+
method: "notifications/stderr",
|
|
296
|
+
params: {
|
|
297
|
+
content: "Command not found, transports removed",
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
webAppTransport.close();
|
|
301
|
+
serverTransport.close();
|
|
302
|
+
webAppTransports.delete(webAppTransport.sessionId);
|
|
303
|
+
serverTransports.delete(webAppTransport.sessionId);
|
|
304
|
+
console.error("Command not found, transports removed");
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
webAppTransport.send({
|
|
308
|
+
jsonrpc: "2.0",
|
|
309
|
+
method: "notifications/stderr",
|
|
310
|
+
params: {
|
|
311
|
+
content: chunk.toString(),
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
247
315
|
});
|
|
248
316
|
mcpProxy({
|
|
249
317
|
transportToClient: webAppTransport,
|
|
250
318
|
transportToServer: serverTransport,
|
|
251
319
|
});
|
|
252
|
-
console.log("Set up MCP proxy");
|
|
253
320
|
}
|
|
254
321
|
catch (error) {
|
|
255
322
|
console.error("Error in /stdio route:", error);
|
|
256
323
|
res.status(500).json(error);
|
|
257
324
|
}
|
|
258
325
|
});
|
|
259
|
-
app.get("/sse", async (req, res) => {
|
|
326
|
+
app.get("/sse", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
260
327
|
try {
|
|
261
|
-
console.log("New SSE connection. NOTE: The sse transport is deprecated and has been replaced by
|
|
328
|
+
console.log("New SSE connection request. NOTE: The sse transport is deprecated and has been replaced by StreamableHttp");
|
|
262
329
|
let serverTransport;
|
|
263
330
|
try {
|
|
264
331
|
serverTransport = await createTransport(req);
|
|
265
332
|
}
|
|
266
333
|
catch (error) {
|
|
267
334
|
if (error instanceof SseError && error.code === 401) {
|
|
268
|
-
console.error("Received 401 Unauthorized from MCP server
|
|
335
|
+
console.error("Received 401 Unauthorized from MCP server. Authentication failure.");
|
|
269
336
|
res.status(401).json(error);
|
|
270
337
|
return;
|
|
271
338
|
}
|
|
272
|
-
|
|
339
|
+
else if (error instanceof SseError && error.code === 404) {
|
|
340
|
+
console.error("Received 404 not found from MCP server. Does the MCP server support SSE?");
|
|
341
|
+
res.status(404).json(error);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
else if (JSON.stringify(error).includes("ECONNREFUSED")) {
|
|
345
|
+
console.error("Connection refused. Is the MCP server running?");
|
|
346
|
+
res.status(500).json(error);
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (serverTransport) {
|
|
353
|
+
const webAppTransport = new SSEServerTransport("/message", res);
|
|
354
|
+
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
|
355
|
+
console.log("Created client transport");
|
|
356
|
+
serverTransports.set(webAppTransport.sessionId, serverTransport);
|
|
357
|
+
console.log("Created server transport");
|
|
358
|
+
await webAppTransport.start();
|
|
359
|
+
mcpProxy({
|
|
360
|
+
transportToClient: webAppTransport,
|
|
361
|
+
transportToServer: serverTransport,
|
|
362
|
+
});
|
|
273
363
|
}
|
|
274
|
-
console.log("Connected MCP client to backing server transport");
|
|
275
|
-
const webAppTransport = new SSEServerTransport("/message", res);
|
|
276
|
-
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
|
277
|
-
console.log("Created client transport");
|
|
278
|
-
serverTransports.set(webAppTransport.sessionId, serverTransport);
|
|
279
|
-
console.log("Created server transport");
|
|
280
|
-
await webAppTransport.start();
|
|
281
|
-
mcpProxy({
|
|
282
|
-
transportToClient: webAppTransport,
|
|
283
|
-
transportToServer: serverTransport,
|
|
284
|
-
});
|
|
285
|
-
console.log("Set up MCP proxy");
|
|
286
364
|
}
|
|
287
365
|
catch (error) {
|
|
288
366
|
console.error("Error in /sse route:", error);
|
|
289
367
|
res.status(500).json(error);
|
|
290
368
|
}
|
|
291
369
|
});
|
|
292
|
-
app.post("/message", async (req, res) => {
|
|
370
|
+
app.post("/message", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
293
371
|
try {
|
|
294
372
|
const sessionId = req.query.sessionId;
|
|
295
|
-
console.log(`Received message for sessionId ${sessionId}`);
|
|
373
|
+
console.log(`Received POST message for sessionId ${sessionId}`);
|
|
296
374
|
const transport = webAppTransports.get(sessionId);
|
|
297
375
|
if (!transport) {
|
|
298
376
|
res.status(404).end("Session not found");
|
|
@@ -310,7 +388,7 @@ app.get("/health", (req, res) => {
|
|
|
310
388
|
status: "ok",
|
|
311
389
|
});
|
|
312
390
|
});
|
|
313
|
-
app.get("/config", (req, res) => {
|
|
391
|
+
app.get("/config", originValidationMiddleware, authMiddleware, (req, res) => {
|
|
314
392
|
try {
|
|
315
393
|
res.json({
|
|
316
394
|
defaultEnvironment,
|
|
@@ -323,10 +401,22 @@ app.get("/config", (req, res) => {
|
|
|
323
401
|
res.status(500).json(error);
|
|
324
402
|
}
|
|
325
403
|
});
|
|
326
|
-
const PORT = process.env.PORT || 6277;
|
|
327
|
-
const
|
|
404
|
+
const PORT = parseInt(process.env.PORT || "6277", 10);
|
|
405
|
+
const HOST = process.env.HOST || "127.0.0.1";
|
|
406
|
+
const server = app.listen(PORT, HOST);
|
|
328
407
|
server.on("listening", () => {
|
|
329
|
-
console.log(`⚙️ Proxy server listening on
|
|
408
|
+
console.log(`⚙️ Proxy server listening on ${HOST}:${PORT}`);
|
|
409
|
+
if (!authDisabled) {
|
|
410
|
+
console.log(`🔑 Session token: ${sessionToken}`);
|
|
411
|
+
console.log(`Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth`);
|
|
412
|
+
// Display clickable URL with pre-filled token
|
|
413
|
+
const clientPort = process.env.CLIENT_PORT || "6274";
|
|
414
|
+
const clientUrl = `http://localhost:${clientPort}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
|
|
415
|
+
console.log(`\n🔗 Open inspector with token pre-filled:\n ${clientUrl}\n (Auto-open is disabled when authentication is enabled)\n`);
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
console.log(`⚠️ WARNING: Authentication is disabled. This is not recommended.`);
|
|
419
|
+
}
|
|
330
420
|
});
|
|
331
421
|
server.on("error", (err) => {
|
|
332
422
|
if (err.message.includes(`EADDRINUSE`)) {
|
package/server/build/mcpProxy.js
CHANGED
|
@@ -3,11 +3,19 @@ function onClientError(error) {
|
|
|
3
3
|
console.error("Error from inspector client:", error);
|
|
4
4
|
}
|
|
5
5
|
function onServerError(error) {
|
|
6
|
-
|
|
6
|
+
if ((error?.message &&
|
|
7
|
+
error.message.includes("Error POSTing to endpoint (HTTP 404)")) ||
|
|
8
|
+
(error?.cause && JSON.stringify(error.cause).includes("ECONNREFUSED"))) {
|
|
9
|
+
console.error("Connection refused. Is the MCP server running?");
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
console.error("Error from MCP server:", error);
|
|
13
|
+
}
|
|
7
14
|
}
|
|
8
15
|
export default function mcpProxy({ transportToClient, transportToServer, }) {
|
|
9
16
|
let transportToClientClosed = false;
|
|
10
17
|
let transportToServerClosed = false;
|
|
18
|
+
let reportedServerSession = false;
|
|
11
19
|
transportToClient.onmessage = (message) => {
|
|
12
20
|
transportToServer.send(message).catch((error) => {
|
|
13
21
|
// Send error response back to client if it was a request (has id) and connection is still open
|
|
@@ -26,6 +34,13 @@ export default function mcpProxy({ transportToClient, transportToServer, }) {
|
|
|
26
34
|
});
|
|
27
35
|
};
|
|
28
36
|
transportToServer.onmessage = (message) => {
|
|
37
|
+
if (!reportedServerSession) {
|
|
38
|
+
if (transportToServer.sessionId) {
|
|
39
|
+
// Can only report for StreamableHttp
|
|
40
|
+
console.error("Proxy <-> Server sessionId: " + transportToServer.sessionId);
|
|
41
|
+
}
|
|
42
|
+
reportedServerSession = true;
|
|
43
|
+
}
|
|
29
44
|
transportToClient.send(message).catch(onClientError);
|
|
30
45
|
};
|
|
31
46
|
transportToClient.onclose = () => {
|