@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 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:
@@ -100,7 +100,9 @@ async function main() {
100
100
 
101
101
  if (serverOk) {
102
102
  try {
103
- if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") {
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-CfNcvwpX.js";
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();
@@ -1,4 +1,4 @@
1
- import { r as reactExports, S as SESSION_KEYS, j as jsxRuntimeExports } from "./index-CfNcvwpX.js";
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(() => {
@@ -989,9 +989,6 @@ video {
989
989
  .w-8 {
990
990
  width: 2rem;
991
991
  }
992
- .w-80 {
993
- width: 20rem;
994
- }
995
992
  .w-9 {
996
993
  width: 2.25rem;
997
994
  }
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/OAuthCallback-CJlw2xLt.js","assets/oauthUtils-DTcoXpSP.js","assets/OAuthDebugCallback-0U3Rpr19.js"])))=>i.map(i=>d[i]);
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.0";
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 savedConfig = localStorage.getItem(localStorageKey);
24222
- let baseConfig;
24223
- if (savedConfig) {
24224
- const mergedConfig = {
24225
- ...DEFAULT_INSPECTOR_CONFIG,
24226
- ...JSON.parse(savedConfig)
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 proxyHealthResponse = await fetch(proxyHealthUrl);
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, { ...init, headers })
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, { ...init, headers })
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, { ...init, headers })
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.0";
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: "w-80 bg-card border-r border-border flex flex-col h-full", children: [
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.jsx(
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
- return "Connection Error, is your MCP server running?";
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
- localStorage.setItem(CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config));
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
- fetch(`${getMCPProxyAddress(config)}/config`).then((response) => response.json()).then((data2) => {
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-CJlw2xLt.js"), true ? __vite__mapDeps([0,1]) : void 0)
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-0U3Rpr19.js"), true ? __vite__mapDeps([2,1]) : void 0)
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.jsx(
37393
- Sidebar,
37533
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
37534
+ "div",
37394
37535
  {
37395
- connectionStatus,
37396
- transportType,
37397
- setTransportType,
37398
- command,
37399
- setCommand,
37400
- args,
37401
- setArgs,
37402
- sseUrl,
37403
- setSseUrl,
37404
- env,
37405
- setEnv,
37406
- config,
37407
- setConfig,
37408
- bearerToken,
37409
- setBearerToken,
37410
- headerName,
37411
- setHeaderName,
37412
- onConnect: connectMcpServer,
37413
- onDisconnect: disconnectMcpServer,
37414
- stdErrNotifications,
37415
- logLevel,
37416
- sendLogLevelRequest,
37417
- loggingSupported: !!(serverCapabilities == null ? void 0 : serverCapabilities.logging) || false,
37418
- clearStdErrNotifications
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: [
@@ -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-CfNcvwpX.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-1IEtev7A.css">
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.0",
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.0",
45
- "@modelcontextprotocol/inspector-client": "^0.14.0",
46
- "@modelcontextprotocol/inspector-server": "^0.14.0",
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",
@@ -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(`Stdio transport: command=${cmd}, args=${args}`);
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=${Object.keys(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 streamable-http connection");
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("Connected MCP client to server transport");
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("Created streamable web app transport " + sessionId);
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:", error.message);
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
- webAppTransport.send({
241
- jsonrpc: "2.0",
242
- method: "notifications/stderr",
243
- params: {
244
- content: chunk.toString(),
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 streamable-http");
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:", error.message);
338
+ console.error("Received 401 Unauthorized from MCP server. Authentication failure.");
269
339
  res.status(401).json(error);
270
340
  return;
271
341
  }
272
- throw error;
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 server = app.listen(PORT);
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 port ${PORT}`);
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`)) {
@@ -3,11 +3,19 @@ function onClientError(error) {
3
3
  console.error("Error from inspector client:", error);
4
4
  }
5
5
  function onServerError(error) {
6
- console.error("Error from MCP server:", error);
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 = () => {