@modelcontextprotocol/inspector 0.14.0 → 0.14.2
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-UvVmnvtb.js} +1 -1
- package/client/dist/assets/{OAuthDebugCallback-0U3Rpr19.js → OAuthDebugCallback-CrRUPVLi.js} +1 -1
- package/client/dist/assets/{index-1IEtev7A.css → index-C1MLjTGf.css} +0 -3
- package/client/dist/assets/{index-CfNcvwpX.js → index-Daz_CV25.js} +237 -64
- package/client/dist/index.html +2 -2
- package/package.json +7 -5
- package/server/build/index.js +142 -49
- 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-Daz_CV25.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-CrRUPVLi.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-Daz_CV25.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-UvVmnvtb.js","assets/oauthUtils-DTcoXpSP.js","assets/OAuthDebugCallback-CrRUPVLi.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.2";
|
|
24150
24160
|
const packageJson = {
|
|
24151
24161
|
version: version$1
|
|
24152
24162
|
};
|
|
@@ -24166,6 +24176,12 @@ 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 {
|
|
24181
|
+
token: config.MCP_PROXY_AUTH_TOKEN.value,
|
|
24182
|
+
header: "X-MCP-Proxy-Auth"
|
|
24183
|
+
};
|
|
24184
|
+
};
|
|
24169
24185
|
const getSearchParam = (key) => {
|
|
24170
24186
|
try {
|
|
24171
24187
|
const url = new URL(window.location.href);
|
|
@@ -24218,26 +24234,46 @@ const getConfigOverridesFromQueryParams = (defaultConfig) => {
|
|
|
24218
24234
|
return overrides;
|
|
24219
24235
|
};
|
|
24220
24236
|
const initializeInspectorConfig = (localStorageKey) => {
|
|
24221
|
-
const
|
|
24222
|
-
|
|
24223
|
-
|
|
24224
|
-
|
|
24225
|
-
|
|
24226
|
-
|
|
24237
|
+
const savedPersistentConfig = localStorage.getItem(localStorageKey);
|
|
24238
|
+
const savedEphemeralConfig = sessionStorage.getItem(
|
|
24239
|
+
`${localStorageKey}_ephemeral`
|
|
24240
|
+
);
|
|
24241
|
+
let baseConfig = { ...DEFAULT_INSPECTOR_CONFIG };
|
|
24242
|
+
if (savedPersistentConfig) {
|
|
24243
|
+
const parsedPersistentConfig = JSON.parse(savedPersistentConfig);
|
|
24244
|
+
baseConfig = { ...baseConfig, ...parsedPersistentConfig };
|
|
24245
|
+
}
|
|
24246
|
+
if (savedEphemeralConfig) {
|
|
24247
|
+
const parsedEphemeralConfig = JSON.parse(savedEphemeralConfig);
|
|
24248
|
+
baseConfig = { ...baseConfig, ...parsedEphemeralConfig };
|
|
24249
|
+
}
|
|
24250
|
+
for (const [key, value] of Object.entries(baseConfig)) {
|
|
24251
|
+
baseConfig[key] = {
|
|
24252
|
+
...value,
|
|
24253
|
+
label: DEFAULT_INSPECTOR_CONFIG[key].label,
|
|
24254
|
+
description: DEFAULT_INSPECTOR_CONFIG[key].description,
|
|
24255
|
+
is_session_item: DEFAULT_INSPECTOR_CONFIG[key].is_session_item
|
|
24227
24256
|
};
|
|
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
24257
|
}
|
|
24238
24258
|
const overrides = getConfigOverridesFromQueryParams(DEFAULT_INSPECTOR_CONFIG);
|
|
24239
24259
|
return { ...baseConfig, ...overrides };
|
|
24240
24260
|
};
|
|
24261
|
+
const saveInspectorConfig = (localStorageKey, config) => {
|
|
24262
|
+
const persistentConfig = {};
|
|
24263
|
+
const ephemeralConfig = {};
|
|
24264
|
+
for (const [key, value] of Object.entries(config)) {
|
|
24265
|
+
if (value.is_session_item) {
|
|
24266
|
+
ephemeralConfig[key] = value;
|
|
24267
|
+
} else {
|
|
24268
|
+
persistentConfig[key] = value;
|
|
24269
|
+
}
|
|
24270
|
+
}
|
|
24271
|
+
localStorage.setItem(localStorageKey, JSON.stringify(persistentConfig));
|
|
24272
|
+
sessionStorage.setItem(
|
|
24273
|
+
`${localStorageKey}_ephemeral`,
|
|
24274
|
+
JSON.stringify(ephemeralConfig)
|
|
24275
|
+
);
|
|
24276
|
+
};
|
|
24241
24277
|
function useConnection({
|
|
24242
24278
|
transportType,
|
|
24243
24279
|
command,
|
|
@@ -24375,7 +24411,12 @@ function useConnection({
|
|
|
24375
24411
|
const checkProxyHealth = async () => {
|
|
24376
24412
|
try {
|
|
24377
24413
|
const proxyHealthUrl = new URL(`${getMCPProxyAddress(config)}/health`);
|
|
24378
|
-
const
|
|
24414
|
+
const { token: proxyAuthToken, header: proxyAuthTokenHeader } = getMCPProxyAuthToken(config);
|
|
24415
|
+
const headers = {};
|
|
24416
|
+
if (proxyAuthToken) {
|
|
24417
|
+
headers[proxyAuthTokenHeader] = `Bearer ${proxyAuthToken}`;
|
|
24418
|
+
}
|
|
24419
|
+
const proxyHealthResponse = await fetch(proxyHealthUrl, { headers });
|
|
24379
24420
|
const proxyHealth = await proxyHealthResponse.json();
|
|
24380
24421
|
if ((proxyHealth == null ? void 0 : proxyHealth.status) !== "ok") {
|
|
24381
24422
|
throw new Error("MCP Proxy Server is not healthy");
|
|
@@ -24388,6 +24429,9 @@ function useConnection({
|
|
|
24388
24429
|
const is401Error = (error) => {
|
|
24389
24430
|
return error instanceof SseError && error.code === 401 || error instanceof Error && error.message.includes("401") || error instanceof Error && error.message.includes("Unauthorized");
|
|
24390
24431
|
};
|
|
24432
|
+
const isProxyAuthError = (error) => {
|
|
24433
|
+
return error instanceof Error && error.message.includes("Authentication required. Use the session token");
|
|
24434
|
+
};
|
|
24391
24435
|
const handleAuthError = async (error) => {
|
|
24392
24436
|
if (is401Error(error)) {
|
|
24393
24437
|
const serverAuthProvider = new InspectorOAuthClientProvider(sseUrl);
|
|
@@ -24431,6 +24475,11 @@ function useConnection({
|
|
|
24431
24475
|
headers[authHeaderName] = `Bearer ${token}`;
|
|
24432
24476
|
}
|
|
24433
24477
|
}
|
|
24478
|
+
const { token: proxyAuthToken, header: proxyAuthTokenHeader } = getMCPProxyAuthToken(config);
|
|
24479
|
+
const proxyHeaders = {};
|
|
24480
|
+
if (proxyAuthToken) {
|
|
24481
|
+
proxyHeaders[proxyAuthTokenHeader] = `Bearer ${proxyAuthToken}`;
|
|
24482
|
+
}
|
|
24434
24483
|
let transportOptions;
|
|
24435
24484
|
let mcpProxyServerUrl;
|
|
24436
24485
|
switch (transportType) {
|
|
@@ -24442,10 +24491,13 @@ function useConnection({
|
|
|
24442
24491
|
transportOptions = {
|
|
24443
24492
|
authProvider: serverAuthProvider,
|
|
24444
24493
|
eventSourceInit: {
|
|
24445
|
-
fetch: (url, init) => fetch(url, {
|
|
24494
|
+
fetch: (url, init) => fetch(url, {
|
|
24495
|
+
...init,
|
|
24496
|
+
headers: { ...headers, ...proxyHeaders }
|
|
24497
|
+
})
|
|
24446
24498
|
},
|
|
24447
24499
|
requestInit: {
|
|
24448
|
-
headers
|
|
24500
|
+
headers: { ...headers, ...proxyHeaders }
|
|
24449
24501
|
}
|
|
24450
24502
|
};
|
|
24451
24503
|
break;
|
|
@@ -24454,10 +24506,13 @@ function useConnection({
|
|
|
24454
24506
|
mcpProxyServerUrl.searchParams.append("url", sseUrl);
|
|
24455
24507
|
transportOptions = {
|
|
24456
24508
|
eventSourceInit: {
|
|
24457
|
-
fetch: (url, init) => fetch(url, {
|
|
24509
|
+
fetch: (url, init) => fetch(url, {
|
|
24510
|
+
...init,
|
|
24511
|
+
headers: { ...headers, ...proxyHeaders }
|
|
24512
|
+
})
|
|
24458
24513
|
},
|
|
24459
24514
|
requestInit: {
|
|
24460
|
-
headers
|
|
24515
|
+
headers: { ...headers, ...proxyHeaders }
|
|
24461
24516
|
}
|
|
24462
24517
|
};
|
|
24463
24518
|
break;
|
|
@@ -24466,10 +24521,13 @@ function useConnection({
|
|
|
24466
24521
|
mcpProxyServerUrl.searchParams.append("url", sseUrl);
|
|
24467
24522
|
transportOptions = {
|
|
24468
24523
|
eventSourceInit: {
|
|
24469
|
-
fetch: (url, init) => fetch(url, {
|
|
24524
|
+
fetch: (url, init) => fetch(url, {
|
|
24525
|
+
...init,
|
|
24526
|
+
headers: { ...headers, ...proxyHeaders }
|
|
24527
|
+
})
|
|
24470
24528
|
},
|
|
24471
24529
|
requestInit: {
|
|
24472
|
-
headers
|
|
24530
|
+
headers: { ...headers, ...proxyHeaders }
|
|
24473
24531
|
},
|
|
24474
24532
|
// TODO these should be configurable...
|
|
24475
24533
|
reconnectionOptions: {
|
|
@@ -24532,6 +24590,15 @@ function useConnection({
|
|
|
24532
24590
|
`Failed to connect to MCP Server via the MCP Inspector Proxy: ${mcpProxyServerUrl}:`,
|
|
24533
24591
|
error
|
|
24534
24592
|
);
|
|
24593
|
+
if (isProxyAuthError(error)) {
|
|
24594
|
+
toast2({
|
|
24595
|
+
title: "Proxy Authentication Required",
|
|
24596
|
+
description: "Please enter the session token from the proxy server console in the Configuration settings.",
|
|
24597
|
+
variant: "destructive"
|
|
24598
|
+
});
|
|
24599
|
+
setConnectionStatus("error");
|
|
24600
|
+
return;
|
|
24601
|
+
}
|
|
24535
24602
|
const shouldRetry = await handleAuthError(error);
|
|
24536
24603
|
if (shouldRetry) {
|
|
24537
24604
|
return connect(void 0, retryCount + 1);
|
|
@@ -24633,6 +24700,52 @@ function useDraggablePane(initialHeight) {
|
|
|
24633
24700
|
handleDragStart
|
|
24634
24701
|
};
|
|
24635
24702
|
}
|
|
24703
|
+
function useDraggableSidebar(initialWidth) {
|
|
24704
|
+
const [width, setWidth] = reactExports.useState(initialWidth);
|
|
24705
|
+
const [isDragging, setIsDragging] = reactExports.useState(false);
|
|
24706
|
+
const dragStartX = reactExports.useRef(0);
|
|
24707
|
+
const dragStartWidth = reactExports.useRef(0);
|
|
24708
|
+
const handleDragStart = reactExports.useCallback(
|
|
24709
|
+
(e) => {
|
|
24710
|
+
setIsDragging(true);
|
|
24711
|
+
dragStartX.current = e.clientX;
|
|
24712
|
+
dragStartWidth.current = width;
|
|
24713
|
+
document.body.style.userSelect = "none";
|
|
24714
|
+
},
|
|
24715
|
+
[width]
|
|
24716
|
+
);
|
|
24717
|
+
const handleDragMove = reactExports.useCallback(
|
|
24718
|
+
(e) => {
|
|
24719
|
+
if (!isDragging) return;
|
|
24720
|
+
const deltaX = e.clientX - dragStartX.current;
|
|
24721
|
+
const newWidth = Math.max(
|
|
24722
|
+
200,
|
|
24723
|
+
Math.min(600, dragStartWidth.current + deltaX)
|
|
24724
|
+
);
|
|
24725
|
+
setWidth(newWidth);
|
|
24726
|
+
},
|
|
24727
|
+
[isDragging]
|
|
24728
|
+
);
|
|
24729
|
+
const handleDragEnd = reactExports.useCallback(() => {
|
|
24730
|
+
setIsDragging(false);
|
|
24731
|
+
document.body.style.userSelect = "";
|
|
24732
|
+
}, []);
|
|
24733
|
+
reactExports.useEffect(() => {
|
|
24734
|
+
if (isDragging) {
|
|
24735
|
+
window.addEventListener("mousemove", handleDragMove);
|
|
24736
|
+
window.addEventListener("mouseup", handleDragEnd);
|
|
24737
|
+
return () => {
|
|
24738
|
+
window.removeEventListener("mousemove", handleDragMove);
|
|
24739
|
+
window.removeEventListener("mouseup", handleDragEnd);
|
|
24740
|
+
};
|
|
24741
|
+
}
|
|
24742
|
+
}, [isDragging, handleDragMove, handleDragEnd]);
|
|
24743
|
+
return {
|
|
24744
|
+
width,
|
|
24745
|
+
isDragging,
|
|
24746
|
+
handleDragStart
|
|
24747
|
+
};
|
|
24748
|
+
}
|
|
24636
24749
|
var useReactId = React$1[" useId ".trim().toString()] || (() => void 0);
|
|
24637
24750
|
var count$1 = 0;
|
|
24638
24751
|
function useId(deterministicId) {
|
|
@@ -35185,7 +35298,7 @@ const useTheme = () => {
|
|
|
35185
35298
|
[theme, setThemeWithSideEffect]
|
|
35186
35299
|
);
|
|
35187
35300
|
};
|
|
35188
|
-
const version = "0.14.
|
|
35301
|
+
const version = "0.14.2";
|
|
35189
35302
|
var [createTooltipContext, createTooltipScope] = createContextScope("Tooltip", [
|
|
35190
35303
|
createPopperScope
|
|
35191
35304
|
]);
|
|
@@ -35788,7 +35901,7 @@ const Sidebar = ({
|
|
|
35788
35901
|
reportError2(error);
|
|
35789
35902
|
}
|
|
35790
35903
|
}, [generateMCPServerFile, toast2, reportError2]);
|
|
35791
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "
|
|
35904
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-card border-r border-border flex flex-col h-full", children: [
|
|
35792
35905
|
/* @__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
35906
|
"MCP Inspector v",
|
|
35794
35907
|
version
|
|
@@ -35856,7 +35969,19 @@ const Sidebar = ({
|
|
|
35856
35969
|
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
35857
35970
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
35858
35971
|
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-sm font-medium", htmlFor: "sse-url-input", children: "URL" }),
|
|
35859
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
35972
|
+
sseUrl ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
|
|
35973
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35974
|
+
Input,
|
|
35975
|
+
{
|
|
35976
|
+
id: "sse-url-input",
|
|
35977
|
+
placeholder: "URL",
|
|
35978
|
+
value: sseUrl,
|
|
35979
|
+
onChange: (e) => setSseUrl(e.target.value),
|
|
35980
|
+
className: "font-mono"
|
|
35981
|
+
}
|
|
35982
|
+
) }),
|
|
35983
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: sseUrl })
|
|
35984
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35860
35985
|
Input,
|
|
35861
35986
|
{
|
|
35862
35987
|
id: "sse-url-input",
|
|
@@ -36207,11 +36332,17 @@ const Sidebar = ({
|
|
|
36207
36332
|
}
|
|
36208
36333
|
),
|
|
36209
36334
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: (() => {
|
|
36335
|
+
var _a;
|
|
36210
36336
|
switch (connectionStatus) {
|
|
36211
36337
|
case "connected":
|
|
36212
36338
|
return "Connected";
|
|
36213
|
-
case "error":
|
|
36214
|
-
|
|
36339
|
+
case "error": {
|
|
36340
|
+
const hasProxyToken = (_a = config.MCP_PROXY_AUTH_TOKEN) == null ? void 0 : _a.value;
|
|
36341
|
+
if (!hasProxyToken) {
|
|
36342
|
+
return "Connection Error - Did you add the proxy session token in Configuration?";
|
|
36343
|
+
}
|
|
36344
|
+
return "Connection Error - Check if your MCP server is running and proxy token is correct";
|
|
36345
|
+
}
|
|
36215
36346
|
case "error-connecting-to-proxy":
|
|
36216
36347
|
return "Error Connecting to MCP Inspector Proxy - Check Console logs";
|
|
36217
36348
|
default:
|
|
@@ -37015,6 +37146,11 @@ const App = () => {
|
|
|
37015
37146
|
const [nextToolCursor, setNextToolCursor] = reactExports.useState();
|
|
37016
37147
|
const progressTokenRef = reactExports.useRef(0);
|
|
37017
37148
|
const { height: historyPaneHeight, handleDragStart } = useDraggablePane(300);
|
|
37149
|
+
const {
|
|
37150
|
+
width: sidebarWidth,
|
|
37151
|
+
isDragging: isSidebarDragging,
|
|
37152
|
+
handleDragStart: handleSidebarDragStart
|
|
37153
|
+
} = useDraggableSidebar(320);
|
|
37018
37154
|
const {
|
|
37019
37155
|
connectionStatus,
|
|
37020
37156
|
serverCapabilities,
|
|
@@ -37071,7 +37207,7 @@ const App = () => {
|
|
|
37071
37207
|
localStorage.setItem("lastHeaderName", headerName);
|
|
37072
37208
|
}, [headerName]);
|
|
37073
37209
|
reactExports.useEffect(() => {
|
|
37074
|
-
|
|
37210
|
+
saveInspectorConfig(CONFIG_LOCAL_STORAGE_KEY, config);
|
|
37075
37211
|
}, [config]);
|
|
37076
37212
|
const onOAuthConnect = reactExports.useCallback(
|
|
37077
37213
|
(serverUrl) => {
|
|
@@ -37163,7 +37299,12 @@ const App = () => {
|
|
|
37163
37299
|
loadOAuthTokens();
|
|
37164
37300
|
}, [sseUrl]);
|
|
37165
37301
|
reactExports.useEffect(() => {
|
|
37166
|
-
|
|
37302
|
+
const headers = {};
|
|
37303
|
+
const { token: proxyAuthToken, header: proxyAuthTokenHeader } = getMCPProxyAuthToken(config);
|
|
37304
|
+
if (proxyAuthToken) {
|
|
37305
|
+
headers[proxyAuthTokenHeader] = `Bearer ${proxyAuthToken}`;
|
|
37306
|
+
}
|
|
37307
|
+
fetch(`${getMCPProxyAddress(config)}/config`, { headers }).then((response) => response.json()).then((data2) => {
|
|
37167
37308
|
setEnv(data2.defaultEnvironment);
|
|
37168
37309
|
if (data2.defaultCommand) {
|
|
37169
37310
|
setCommand(data2.defaultCommand);
|
|
@@ -37174,7 +37315,7 @@ const App = () => {
|
|
|
37174
37315
|
}).catch(
|
|
37175
37316
|
(error) => console.error("Error fetching default environment:", error)
|
|
37176
37317
|
);
|
|
37177
|
-
}, []);
|
|
37318
|
+
}, [config]);
|
|
37178
37319
|
reactExports.useEffect(() => {
|
|
37179
37320
|
rootsRef.current = roots;
|
|
37180
37321
|
}, [roots]);
|
|
@@ -37378,44 +37519,76 @@ const App = () => {
|
|
|
37378
37519
|
) });
|
|
37379
37520
|
if (window.location.pathname === "/oauth/callback") {
|
|
37380
37521
|
const OAuthCallback = React.lazy(
|
|
37381
|
-
() => __vitePreload(() => import("./OAuthCallback-
|
|
37522
|
+
() => __vitePreload(() => import("./OAuthCallback-UvVmnvtb.js"), true ? __vite__mapDeps([0,1]) : void 0)
|
|
37382
37523
|
);
|
|
37383
37524
|
return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthCallback, { onConnect: onOAuthConnect }) });
|
|
37384
37525
|
}
|
|
37385
37526
|
if (window.location.pathname === "/oauth/callback/debug") {
|
|
37386
37527
|
const OAuthDebugCallback = React.lazy(
|
|
37387
|
-
() => __vitePreload(() => import("./OAuthDebugCallback-
|
|
37528
|
+
() => __vitePreload(() => import("./OAuthDebugCallback-CrRUPVLi.js"), true ? __vite__mapDeps([2,1]) : void 0)
|
|
37388
37529
|
);
|
|
37389
37530
|
return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthDebugCallback, { onConnect: onOAuthDebugConnect }) });
|
|
37390
37531
|
}
|
|
37391
37532
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex h-screen bg-background", children: [
|
|
37392
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
37393
|
-
|
|
37533
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
37534
|
+
"div",
|
|
37394
37535
|
{
|
|
37395
|
-
|
|
37396
|
-
|
|
37397
|
-
|
|
37398
|
-
|
|
37399
|
-
|
|
37400
|
-
|
|
37401
|
-
|
|
37402
|
-
|
|
37403
|
-
|
|
37404
|
-
|
|
37405
|
-
|
|
37406
|
-
|
|
37407
|
-
|
|
37408
|
-
|
|
37409
|
-
|
|
37410
|
-
|
|
37411
|
-
|
|
37412
|
-
|
|
37413
|
-
|
|
37414
|
-
|
|
37415
|
-
|
|
37416
|
-
|
|
37417
|
-
|
|
37418
|
-
|
|
37536
|
+
style: {
|
|
37537
|
+
width: sidebarWidth,
|
|
37538
|
+
minWidth: 200,
|
|
37539
|
+
maxWidth: 600,
|
|
37540
|
+
transition: isSidebarDragging ? "none" : "width 0.15s"
|
|
37541
|
+
},
|
|
37542
|
+
className: "bg-card border-r border-border flex flex-col h-full relative",
|
|
37543
|
+
children: [
|
|
37544
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
37545
|
+
Sidebar,
|
|
37546
|
+
{
|
|
37547
|
+
connectionStatus,
|
|
37548
|
+
transportType,
|
|
37549
|
+
setTransportType,
|
|
37550
|
+
command,
|
|
37551
|
+
setCommand,
|
|
37552
|
+
args,
|
|
37553
|
+
setArgs,
|
|
37554
|
+
sseUrl,
|
|
37555
|
+
setSseUrl,
|
|
37556
|
+
env,
|
|
37557
|
+
setEnv,
|
|
37558
|
+
config,
|
|
37559
|
+
setConfig,
|
|
37560
|
+
bearerToken,
|
|
37561
|
+
setBearerToken,
|
|
37562
|
+
headerName,
|
|
37563
|
+
setHeaderName,
|
|
37564
|
+
onConnect: connectMcpServer,
|
|
37565
|
+
onDisconnect: disconnectMcpServer,
|
|
37566
|
+
stdErrNotifications,
|
|
37567
|
+
logLevel,
|
|
37568
|
+
sendLogLevelRequest,
|
|
37569
|
+
loggingSupported: !!(serverCapabilities == null ? void 0 : serverCapabilities.logging) || false,
|
|
37570
|
+
clearStdErrNotifications
|
|
37571
|
+
}
|
|
37572
|
+
),
|
|
37573
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
37574
|
+
"div",
|
|
37575
|
+
{
|
|
37576
|
+
onMouseDown: handleSidebarDragStart,
|
|
37577
|
+
style: {
|
|
37578
|
+
cursor: "col-resize",
|
|
37579
|
+
position: "absolute",
|
|
37580
|
+
top: 0,
|
|
37581
|
+
right: 0,
|
|
37582
|
+
width: 6,
|
|
37583
|
+
height: "100%",
|
|
37584
|
+
zIndex: 10,
|
|
37585
|
+
background: isSidebarDragging ? "rgba(0,0,0,0.08)" : "transparent"
|
|
37586
|
+
},
|
|
37587
|
+
"aria-label": "Resize sidebar",
|
|
37588
|
+
"data-testid": "sidebar-drag-handle"
|
|
37589
|
+
}
|
|
37590
|
+
)
|
|
37591
|
+
]
|
|
37419
37592
|
}
|
|
37420
37593
|
),
|
|
37421
37594
|
/* @__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-Daz_CV25.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.2",
|
|
4
4
|
"description": "Model Context Protocol inspector",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
|
@@ -38,12 +38,14 @@
|
|
|
38
38
|
"prettier-check": "prettier --check .",
|
|
39
39
|
"lint": "prettier --check . && cd client && npm run lint",
|
|
40
40
|
"prepare": "npm run build",
|
|
41
|
-
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
|
41
|
+
"publish-all": "npm publish --workspaces --access public && npm publish --access public",
|
|
42
|
+
"update-version": "node scripts/update-version.js",
|
|
43
|
+
"check-version": "node scripts/check-version-consistency.js"
|
|
42
44
|
},
|
|
43
45
|
"dependencies": {
|
|
44
|
-
"@modelcontextprotocol/inspector-cli": "^0.14.
|
|
45
|
-
"@modelcontextprotocol/inspector-client": "^0.14.
|
|
46
|
-
"@modelcontextprotocol/inspector-server": "^0.14.
|
|
46
|
+
"@modelcontextprotocol/inspector-cli": "^0.14.2",
|
|
47
|
+
"@modelcontextprotocol/inspector-client": "^0.14.2",
|
|
48
|
+
"@modelcontextprotocol/inspector-server": "^0.14.2",
|
|
47
49
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
48
50
|
"concurrently": "^9.0.1",
|
|
49
51
|
"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,66 @@ 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["x-mcp-proxy-auth"];
|
|
101
|
+
const authHeaderValue = Array.isArray(authHeader)
|
|
102
|
+
? authHeader[0]
|
|
103
|
+
: authHeader;
|
|
104
|
+
if (!authHeaderValue || !authHeaderValue.startsWith("Bearer ")) {
|
|
105
|
+
sendUnauthorized();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const providedToken = authHeaderValue.substring(7); // Remove 'Bearer ' prefix
|
|
109
|
+
const expectedToken = sessionToken;
|
|
110
|
+
// Convert to buffers for timing-safe comparison
|
|
111
|
+
const providedBuffer = Buffer.from(providedToken);
|
|
112
|
+
const expectedBuffer = Buffer.from(expectedToken);
|
|
113
|
+
// Check length first to prevent timing attacks
|
|
114
|
+
if (providedBuffer.length !== expectedBuffer.length) {
|
|
115
|
+
sendUnauthorized();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// Perform timing-safe comparison
|
|
119
|
+
if (!timingSafeEqual(providedBuffer, expectedBuffer)) {
|
|
120
|
+
sendUnauthorized();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
next();
|
|
124
|
+
};
|
|
68
125
|
const createTransport = async (req) => {
|
|
69
126
|
const query = req.query;
|
|
70
|
-
console.log("Query parameters:", query);
|
|
127
|
+
console.log("Query parameters:", JSON.stringify(query));
|
|
71
128
|
const transportType = query.transportType;
|
|
72
129
|
if (transportType === "stdio") {
|
|
73
130
|
const command = query.command;
|
|
@@ -75,7 +132,7 @@ const createTransport = async (req) => {
|
|
|
75
132
|
const queryEnv = query.env ? JSON.parse(query.env) : {};
|
|
76
133
|
const env = { ...process.env, ...defaultEnvironment, ...queryEnv };
|
|
77
134
|
const { cmd, args } = findActualExecutable(command, origArgs);
|
|
78
|
-
console.log(`
|
|
135
|
+
console.log(`STDIO transport: command=${cmd}, args=${args}`);
|
|
79
136
|
const transport = new StdioClientTransport({
|
|
80
137
|
command: cmd,
|
|
81
138
|
args,
|
|
@@ -83,13 +140,12 @@ const createTransport = async (req) => {
|
|
|
83
140
|
stderr: "pipe",
|
|
84
141
|
});
|
|
85
142
|
await transport.start();
|
|
86
|
-
console.log("Spawned stdio transport");
|
|
87
143
|
return transport;
|
|
88
144
|
}
|
|
89
145
|
else if (transportType === "sse") {
|
|
90
146
|
const url = query.url;
|
|
91
147
|
const headers = getHttpHeaders(req, transportType);
|
|
92
|
-
console.log(`SSE transport: url=${url}, headers=${
|
|
148
|
+
console.log(`SSE transport: url=${url}, headers=${JSON.stringify(headers)}`);
|
|
93
149
|
const transport = new SSEClientTransport(new URL(url), {
|
|
94
150
|
eventSourceInit: {
|
|
95
151
|
fetch: (url, init) => fetch(url, { ...init, headers }),
|
|
@@ -99,7 +155,6 @@ const createTransport = async (req) => {
|
|
|
99
155
|
},
|
|
100
156
|
});
|
|
101
157
|
await transport.start();
|
|
102
|
-
console.log("Connected to SSE transport");
|
|
103
158
|
return transport;
|
|
104
159
|
}
|
|
105
160
|
else if (transportType === "streamable-http") {
|
|
@@ -110,7 +165,6 @@ const createTransport = async (req) => {
|
|
|
110
165
|
},
|
|
111
166
|
});
|
|
112
167
|
await transport.start();
|
|
113
|
-
console.log("Connected to Streamable HTTP transport");
|
|
114
168
|
return transport;
|
|
115
169
|
}
|
|
116
170
|
else {
|
|
@@ -118,7 +172,7 @@ const createTransport = async (req) => {
|
|
|
118
172
|
throw new Error("Invalid transport type specified");
|
|
119
173
|
}
|
|
120
174
|
};
|
|
121
|
-
app.get("/mcp", async (req, res) => {
|
|
175
|
+
app.get("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
122
176
|
const sessionId = req.headers["mcp-session-id"];
|
|
123
177
|
console.log(`Received GET message for sessionId ${sessionId}`);
|
|
124
178
|
try {
|
|
@@ -136,13 +190,12 @@ app.get("/mcp", async (req, res) => {
|
|
|
136
190
|
res.status(500).json(error);
|
|
137
191
|
}
|
|
138
192
|
});
|
|
139
|
-
app.post("/mcp", async (req, res) => {
|
|
193
|
+
app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
140
194
|
const sessionId = req.headers["mcp-session-id"];
|
|
141
|
-
console.log(`Received POST message for sessionId ${sessionId}`);
|
|
142
195
|
let serverTransport;
|
|
143
196
|
if (!sessionId) {
|
|
144
197
|
try {
|
|
145
|
-
console.log("New
|
|
198
|
+
console.log("New StreamableHttp connection request");
|
|
146
199
|
try {
|
|
147
200
|
serverTransport = await createTransport(req);
|
|
148
201
|
}
|
|
@@ -154,15 +207,16 @@ app.post("/mcp", async (req, res) => {
|
|
|
154
207
|
}
|
|
155
208
|
throw error;
|
|
156
209
|
}
|
|
157
|
-
console.log("
|
|
210
|
+
console.log("Created StreamableHttp server transport");
|
|
158
211
|
const webAppTransport = new StreamableHTTPServerTransport({
|
|
159
212
|
sessionIdGenerator: randomUUID,
|
|
160
213
|
onsessioninitialized: (sessionId) => {
|
|
161
214
|
webAppTransports.set(sessionId, webAppTransport);
|
|
162
215
|
serverTransports.set(sessionId, serverTransport);
|
|
163
|
-
console.log("
|
|
216
|
+
console.log("Client <-> Proxy sessionId: " + sessionId);
|
|
164
217
|
},
|
|
165
218
|
});
|
|
219
|
+
console.log("Created StreamableHttp client transport");
|
|
166
220
|
await webAppTransport.start();
|
|
167
221
|
mcpProxy({
|
|
168
222
|
transportToClient: webAppTransport,
|
|
@@ -176,6 +230,7 @@ app.post("/mcp", async (req, res) => {
|
|
|
176
230
|
}
|
|
177
231
|
}
|
|
178
232
|
else {
|
|
233
|
+
console.log(`Received POST message for sessionId ${sessionId}`);
|
|
179
234
|
try {
|
|
180
235
|
const transport = webAppTransports.get(sessionId);
|
|
181
236
|
if (!transport) {
|
|
@@ -191,7 +246,7 @@ app.post("/mcp", async (req, res) => {
|
|
|
191
246
|
}
|
|
192
247
|
}
|
|
193
248
|
});
|
|
194
|
-
app.delete("/mcp", async (req, res) => {
|
|
249
|
+
app.delete("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
195
250
|
const sessionId = req.headers["mcp-session-id"];
|
|
196
251
|
console.log(`Received DELETE message for sessionId ${sessionId}`);
|
|
197
252
|
let serverTransport;
|
|
@@ -215,84 +270,110 @@ app.delete("/mcp", async (req, res) => {
|
|
|
215
270
|
}
|
|
216
271
|
}
|
|
217
272
|
});
|
|
218
|
-
app.get("/stdio", async (req, res) => {
|
|
273
|
+
app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
219
274
|
try {
|
|
220
|
-
console.log("New connection");
|
|
275
|
+
console.log("New STDIO connection request");
|
|
221
276
|
let serverTransport;
|
|
222
277
|
try {
|
|
223
278
|
serverTransport = await createTransport(req);
|
|
279
|
+
console.log("Created server transport");
|
|
224
280
|
}
|
|
225
281
|
catch (error) {
|
|
226
282
|
if (error instanceof SseError && error.code === 401) {
|
|
227
|
-
console.error("Received 401 Unauthorized from MCP server
|
|
283
|
+
console.error("Received 401 Unauthorized from MCP server. Authentication failure.");
|
|
228
284
|
res.status(401).json(error);
|
|
229
285
|
return;
|
|
230
286
|
}
|
|
231
287
|
throw error;
|
|
232
288
|
}
|
|
233
|
-
console.log("Connected MCP client to backing server transport");
|
|
234
289
|
const webAppTransport = new SSEServerTransport("/message", res);
|
|
290
|
+
console.log("Created client transport");
|
|
235
291
|
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
|
236
292
|
serverTransports.set(webAppTransport.sessionId, serverTransport);
|
|
237
|
-
console.log("Created client/server transports");
|
|
238
293
|
await webAppTransport.start();
|
|
239
294
|
serverTransport.stderr.on("data", (chunk) => {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
295
|
+
if (chunk.toString().includes("MODULE_NOT_FOUND")) {
|
|
296
|
+
webAppTransport.send({
|
|
297
|
+
jsonrpc: "2.0",
|
|
298
|
+
method: "notifications/stderr",
|
|
299
|
+
params: {
|
|
300
|
+
content: "Command not found, transports removed",
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
webAppTransport.close();
|
|
304
|
+
serverTransport.close();
|
|
305
|
+
webAppTransports.delete(webAppTransport.sessionId);
|
|
306
|
+
serverTransports.delete(webAppTransport.sessionId);
|
|
307
|
+
console.error("Command not found, transports removed");
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
webAppTransport.send({
|
|
311
|
+
jsonrpc: "2.0",
|
|
312
|
+
method: "notifications/stderr",
|
|
313
|
+
params: {
|
|
314
|
+
content: chunk.toString(),
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
}
|
|
247
318
|
});
|
|
248
319
|
mcpProxy({
|
|
249
320
|
transportToClient: webAppTransport,
|
|
250
321
|
transportToServer: serverTransport,
|
|
251
322
|
});
|
|
252
|
-
console.log("Set up MCP proxy");
|
|
253
323
|
}
|
|
254
324
|
catch (error) {
|
|
255
325
|
console.error("Error in /stdio route:", error);
|
|
256
326
|
res.status(500).json(error);
|
|
257
327
|
}
|
|
258
328
|
});
|
|
259
|
-
app.get("/sse", async (req, res) => {
|
|
329
|
+
app.get("/sse", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
260
330
|
try {
|
|
261
|
-
console.log("New SSE connection. NOTE: The sse transport is deprecated and has been replaced by
|
|
331
|
+
console.log("New SSE connection request. NOTE: The sse transport is deprecated and has been replaced by StreamableHttp");
|
|
262
332
|
let serverTransport;
|
|
263
333
|
try {
|
|
264
334
|
serverTransport = await createTransport(req);
|
|
265
335
|
}
|
|
266
336
|
catch (error) {
|
|
267
337
|
if (error instanceof SseError && error.code === 401) {
|
|
268
|
-
console.error("Received 401 Unauthorized from MCP server
|
|
338
|
+
console.error("Received 401 Unauthorized from MCP server. Authentication failure.");
|
|
269
339
|
res.status(401).json(error);
|
|
270
340
|
return;
|
|
271
341
|
}
|
|
272
|
-
|
|
342
|
+
else if (error instanceof SseError && error.code === 404) {
|
|
343
|
+
console.error("Received 404 not found from MCP server. Does the MCP server support SSE?");
|
|
344
|
+
res.status(404).json(error);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
else if (JSON.stringify(error).includes("ECONNREFUSED")) {
|
|
348
|
+
console.error("Connection refused. Is the MCP server running?");
|
|
349
|
+
res.status(500).json(error);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (serverTransport) {
|
|
356
|
+
const webAppTransport = new SSEServerTransport("/message", res);
|
|
357
|
+
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
|
358
|
+
console.log("Created client transport");
|
|
359
|
+
serverTransports.set(webAppTransport.sessionId, serverTransport);
|
|
360
|
+
console.log("Created server transport");
|
|
361
|
+
await webAppTransport.start();
|
|
362
|
+
mcpProxy({
|
|
363
|
+
transportToClient: webAppTransport,
|
|
364
|
+
transportToServer: serverTransport,
|
|
365
|
+
});
|
|
273
366
|
}
|
|
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
367
|
}
|
|
287
368
|
catch (error) {
|
|
288
369
|
console.error("Error in /sse route:", error);
|
|
289
370
|
res.status(500).json(error);
|
|
290
371
|
}
|
|
291
372
|
});
|
|
292
|
-
app.post("/message", async (req, res) => {
|
|
373
|
+
app.post("/message", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
293
374
|
try {
|
|
294
375
|
const sessionId = req.query.sessionId;
|
|
295
|
-
console.log(`Received message for sessionId ${sessionId}`);
|
|
376
|
+
console.log(`Received POST message for sessionId ${sessionId}`);
|
|
296
377
|
const transport = webAppTransports.get(sessionId);
|
|
297
378
|
if (!transport) {
|
|
298
379
|
res.status(404).end("Session not found");
|
|
@@ -310,7 +391,7 @@ app.get("/health", (req, res) => {
|
|
|
310
391
|
status: "ok",
|
|
311
392
|
});
|
|
312
393
|
});
|
|
313
|
-
app.get("/config", (req, res) => {
|
|
394
|
+
app.get("/config", originValidationMiddleware, authMiddleware, (req, res) => {
|
|
314
395
|
try {
|
|
315
396
|
res.json({
|
|
316
397
|
defaultEnvironment,
|
|
@@ -323,10 +404,22 @@ app.get("/config", (req, res) => {
|
|
|
323
404
|
res.status(500).json(error);
|
|
324
405
|
}
|
|
325
406
|
});
|
|
326
|
-
const PORT = process.env.PORT || 6277;
|
|
327
|
-
const
|
|
407
|
+
const PORT = parseInt(process.env.PORT || "6277", 10);
|
|
408
|
+
const HOST = process.env.HOST || "127.0.0.1";
|
|
409
|
+
const server = app.listen(PORT, HOST);
|
|
328
410
|
server.on("listening", () => {
|
|
329
|
-
console.log(`⚙️ Proxy server listening on
|
|
411
|
+
console.log(`⚙️ Proxy server listening on ${HOST}:${PORT}`);
|
|
412
|
+
if (!authDisabled) {
|
|
413
|
+
console.log(`🔑 Session token: ${sessionToken}`);
|
|
414
|
+
console.log(`Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth`);
|
|
415
|
+
// Display clickable URL with pre-filled token
|
|
416
|
+
const clientPort = process.env.CLIENT_PORT || "6274";
|
|
417
|
+
const clientUrl = `http://localhost:${clientPort}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
|
|
418
|
+
console.log(`\n🔗 Open inspector with token pre-filled:\n ${clientUrl}\n (Auto-open is disabled when authentication is enabled)\n`);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
console.log(`⚠️ WARNING: Authentication is disabled. This is not recommended.`);
|
|
422
|
+
}
|
|
330
423
|
});
|
|
331
424
|
server.on("error", (err) => {
|
|
332
425
|
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 = () => {
|