Skip to content

Commit ce1a9d3

Browse files
authored
Merge pull request #243 from pulkitsharma07/add_proxy_config
Add MCP proxy address config support, better error messages, redesigned Config panel
2 parents 897e637 + 2a54429 commit ce1a9d3

File tree

13 files changed

+1356
-3498
lines changed

13 files changed

+1356
-3498
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ The MCP Inspector supports the following configuration settings. To change them
5353
| Name | Purpose | Default Value |
5454
| -------------------------- | ----------------------------------------------------------------------------------------- | ------------- |
5555
| MCP_SERVER_REQUEST_TIMEOUT | Maximum time in milliseconds to wait for a response from the MCP server before timing out | 10000 |
56+
| MCP_PROXY_FULL_ADDRESS | The full URL of the MCP Inspector proxy server (e.g. `http://10.2.1.14:2277`) | `null` |
5657

5758
### From this repository
5859

client/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@radix-ui/react-select": "^2.1.2",
3333
"@radix-ui/react-slot": "^1.1.0",
3434
"@radix-ui/react-tabs": "^1.1.1",
35+
"@radix-ui/react-tooltip": "^1.1.8",
3536
"@radix-ui/react-toast": "^1.2.6",
3637
"@types/prismjs": "^1.26.5",
3738
"class-variance-authority": "^0.7.0",

client/src/App.tsx

+15-7
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
import React, { Suspense, useEffect, useRef, useState } from "react";
2121
import { useConnection } from "./lib/hooks/useConnection";
2222
import { useDraggablePane } from "./lib/hooks/useDraggablePane";
23-
2423
import { StdErrNotification } from "./lib/notificationTypes";
2524

2625
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -46,10 +45,12 @@ import Sidebar from "./components/Sidebar";
4645
import ToolsTab from "./components/ToolsTab";
4746
import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants";
4847
import { InspectorConfig } from "./lib/configurationTypes";
48+
import {
49+
getMCPProxyAddress,
50+
getMCPServerRequestTimeout,
51+
} from "./utils/configUtils";
4952
import { useToast } from "@/hooks/use-toast";
5053
const params = new URLSearchParams(window.location.search);
51-
const PROXY_PORT = params.get("proxyPort") ?? "6277";
52-
const PROXY_SERVER_URL = `http://${window.location.hostname}:${PROXY_PORT}`;
5354
const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1";
5455

5556
const App = () => {
@@ -95,7 +96,13 @@ const App = () => {
9596

9697
const [config, setConfig] = useState<InspectorConfig>(() => {
9798
const savedConfig = localStorage.getItem(CONFIG_LOCAL_STORAGE_KEY);
98-
return savedConfig ? JSON.parse(savedConfig) : DEFAULT_INSPECTOR_CONFIG;
99+
if (savedConfig) {
100+
return {
101+
...DEFAULT_INSPECTOR_CONFIG,
102+
...JSON.parse(savedConfig),
103+
} as InspectorConfig;
104+
}
105+
return DEFAULT_INSPECTOR_CONFIG;
99106
});
100107
const [bearerToken, setBearerToken] = useState<string>(() => {
101108
return localStorage.getItem("lastBearerToken") || "";
@@ -153,8 +160,8 @@ const App = () => {
153160
sseUrl,
154161
env,
155162
bearerToken,
156-
proxyServerUrl: PROXY_SERVER_URL,
157-
requestTimeout: config.MCP_SERVER_REQUEST_TIMEOUT.value as number,
163+
proxyServerUrl: getMCPProxyAddress(config),
164+
requestTimeout: getMCPServerRequestTimeout(config),
158165
onNotification: (notification) => {
159166
setNotifications((prev) => [...prev, notification as ServerNotification]);
160167
},
@@ -218,7 +225,7 @@ const App = () => {
218225
}, [connectMcpServer, toast]);
219226

220227
useEffect(() => {
221-
fetch(`${PROXY_SERVER_URL}/config`)
228+
fetch(`${getMCPProxyAddress(config)}/config`)
222229
.then((response) => response.json())
223230
.then((data) => {
224231
setEnv(data.defaultEnvironment);
@@ -232,6 +239,7 @@ const App = () => {
232239
.catch((error) =>
233240
console.error("Error fetching default environment:", error),
234241
);
242+
// eslint-disable-next-line react-hooks/exhaustive-deps
235243
}, []);
236244

237245
useEffect(() => {

client/src/components/Sidebar.tsx

+48-18
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
EyeOff,
1111
RotateCcw,
1212
Settings,
13+
HelpCircle,
1314
RefreshCwOff,
1415
} from "lucide-react";
1516
import { Button } from "@/components/ui/button";
@@ -27,12 +28,17 @@ import {
2728
LoggingLevelSchema,
2829
} from "@modelcontextprotocol/sdk/types.js";
2930
import { InspectorConfig } from "@/lib/configurationTypes";
30-
31+
import { ConnectionStatus } from "@/lib/constants";
3132
import useTheme from "../lib/useTheme";
3233
import { version } from "../../../package.json";
34+
import {
35+
Tooltip,
36+
TooltipTrigger,
37+
TooltipContent,
38+
} from "@/components/ui/tooltip";
3339

3440
interface SidebarProps {
35-
connectionStatus: "disconnected" | "connected" | "error";
41+
connectionStatus: ConnectionStatus;
3642
transportType: "stdio" | "sse";
3743
setTransportType: (type: "stdio" | "sse") => void;
3844
command: string;
@@ -180,6 +186,7 @@ const Sidebar = ({
180186
variant="outline"
181187
onClick={() => setShowEnvVars(!showEnvVars)}
182188
className="flex items-center w-full"
189+
data-testid="env-vars-button"
183190
>
184191
{showEnvVars ? (
185192
<ChevronDown className="w-4 h-4 mr-2" />
@@ -301,6 +308,7 @@ const Sidebar = ({
301308
variant="outline"
302309
onClick={() => setShowConfig(!showConfig)}
303310
className="flex items-center w-full"
311+
data-testid="config-button"
304312
>
305313
{showConfig ? (
306314
<ChevronDown className="w-4 h-4 mr-2" />
@@ -316,9 +324,19 @@ const Sidebar = ({
316324
const configKey = key as keyof InspectorConfig;
317325
return (
318326
<div key={key} className="space-y-2">
319-
<label className="text-sm font-medium">
320-
{configItem.description}
321-
</label>
327+
<div className="flex items-center gap-1">
328+
<label className="text-sm font-medium text-green-600">
329+
{configKey}
330+
</label>
331+
<Tooltip>
332+
<TooltipTrigger asChild>
333+
<HelpCircle className="h-4 w-4 text-muted-foreground" />
334+
</TooltipTrigger>
335+
<TooltipContent>
336+
{configItem.description}
337+
</TooltipContent>
338+
</Tooltip>
339+
</div>
322340
{typeof configItem.value === "number" ? (
323341
<Input
324342
type="number"
@@ -380,7 +398,7 @@ const Sidebar = ({
380398
<div className="space-y-2">
381399
{connectionStatus === "connected" && (
382400
<div className="grid grid-cols-2 gap-4">
383-
<Button onClick={onConnect}>
401+
<Button data-testid="connect-button" onClick={onConnect}>
384402
<RotateCcw className="w-4 h-4 mr-2" />
385403
{transportType === "stdio" ? "Restart" : "Reconnect"}
386404
</Button>
@@ -399,20 +417,32 @@ const Sidebar = ({
399417

400418
<div className="flex items-center justify-center space-x-2 mb-4">
401419
<div
402-
className={`w-2 h-2 rounded-full ${
403-
connectionStatus === "connected"
404-
? "bg-green-500"
405-
: connectionStatus === "error"
406-
? "bg-red-500"
407-
: "bg-gray-500"
408-
}`}
420+
className={`w-2 h-2 rounded-full ${(() => {
421+
switch (connectionStatus) {
422+
case "connected":
423+
return "bg-green-500";
424+
case "error":
425+
return "bg-red-500";
426+
case "error-connecting-to-proxy":
427+
return "bg-red-500";
428+
default:
429+
return "bg-gray-500";
430+
}
431+
})()}`}
409432
/>
410433
<span className="text-sm text-gray-600">
411-
{connectionStatus === "connected"
412-
? "Connected"
413-
: connectionStatus === "error"
414-
? "Connection Error"
415-
: "Disconnected"}
434+
{(() => {
435+
switch (connectionStatus) {
436+
case "connected":
437+
return "Connected";
438+
case "error":
439+
return "Connection Error, is your MCP server running?";
440+
case "error-connecting-to-proxy":
441+
return "Error Connecting to MCP Inspector Proxy - Check Console logs";
442+
default:
443+
return "Disconnected";
444+
}
445+
})()}
416446
</span>
417447
</div>
418448

client/src/components/__tests__/Sidebar.test.tsx

+51-31
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { render, screen, fireEvent } from "@testing-library/react";
22
import { describe, it, beforeEach, jest } from "@jest/globals";
33
import Sidebar from "../Sidebar";
4-
import { DEFAULT_INSPECTOR_CONFIG } from "../../lib/constants";
5-
import { InspectorConfig } from "../../lib/configurationTypes";
4+
import { DEFAULT_INSPECTOR_CONFIG } from "@/lib/constants";
5+
import { InspectorConfig } from "@/lib/configurationTypes";
6+
import { TooltipProvider } from "@/components/ui/tooltip";
67

78
// Mock theme hook
89
jest.mock("../../lib/useTheme", () => ({
@@ -36,11 +37,15 @@ describe("Sidebar Environment Variables", () => {
3637
};
3738

3839
const renderSidebar = (props = {}) => {
39-
return render(<Sidebar {...defaultProps} {...props} />);
40+
return render(
41+
<TooltipProvider>
42+
<Sidebar {...defaultProps} {...props} />
43+
</TooltipProvider>,
44+
);
4045
};
4146

4247
const openEnvVarsSection = () => {
43-
const button = screen.getByText("Environment Variables");
48+
const button = screen.getByTestId("env-vars-button");
4449
fireEvent.click(button);
4550
};
4651

@@ -216,7 +221,11 @@ describe("Sidebar Environment Variables", () => {
216221
const updatedEnv = setEnv.mock.calls[0][0] as Record<string, string>;
217222

218223
// Rerender with the updated env
219-
rerender(<Sidebar {...defaultProps} env={updatedEnv} setEnv={setEnv} />);
224+
rerender(
225+
<TooltipProvider>
226+
<Sidebar {...defaultProps} env={updatedEnv} setEnv={setEnv} />
227+
</TooltipProvider>,
228+
);
220229

221230
// Second key edit
222231
const secondKeyInput = screen.getByDisplayValue("SECOND_KEY");
@@ -247,7 +256,11 @@ describe("Sidebar Environment Variables", () => {
247256
fireEvent.change(keyInput, { target: { value: "NEW_KEY" } });
248257

249258
// Rerender with updated env
250-
rerender(<Sidebar {...defaultProps} env={{ NEW_KEY: "test_value" }} />);
259+
rerender(
260+
<TooltipProvider>
261+
<Sidebar {...defaultProps} env={{ NEW_KEY: "test_value" }} />
262+
</TooltipProvider>,
263+
);
251264

252265
// Value should still be visible
253266
const updatedValueInput = screen.getByDisplayValue("test_value");
@@ -312,7 +325,7 @@ describe("Sidebar Environment Variables", () => {
312325

313326
describe("Configuration Operations", () => {
314327
const openConfigSection = () => {
315-
const button = screen.getByText("Configuration");
328+
const button = screen.getByTestId("config-button");
316329
fireEvent.click(button);
317330
};
318331

@@ -327,12 +340,14 @@ describe("Sidebar Environment Variables", () => {
327340
);
328341
fireEvent.change(timeoutInput, { target: { value: "5000" } });
329342

330-
expect(setConfig).toHaveBeenCalledWith({
331-
MCP_SERVER_REQUEST_TIMEOUT: {
332-
description: "Timeout for requests to the MCP server (ms)",
333-
value: 5000,
334-
},
335-
});
343+
expect(setConfig).toHaveBeenCalledWith(
344+
expect.objectContaining({
345+
MCP_SERVER_REQUEST_TIMEOUT: {
346+
description: "Timeout for requests to the MCP server (ms)",
347+
value: 5000,
348+
},
349+
}),
350+
);
336351
});
337352

338353
it("should handle invalid timeout values entered by user", () => {
@@ -346,12 +361,14 @@ describe("Sidebar Environment Variables", () => {
346361
);
347362
fireEvent.change(timeoutInput, { target: { value: "abc1" } });
348363

349-
expect(setConfig).toHaveBeenCalledWith({
350-
MCP_SERVER_REQUEST_TIMEOUT: {
351-
description: "Timeout for requests to the MCP server (ms)",
352-
value: 0,
353-
},
354-
});
364+
expect(setConfig).toHaveBeenCalledWith(
365+
expect.objectContaining({
366+
MCP_SERVER_REQUEST_TIMEOUT: {
367+
description: "Timeout for requests to the MCP server (ms)",
368+
value: 0,
369+
},
370+
}),
371+
);
355372
});
356373

357374
it("should maintain configuration state after multiple updates", () => {
@@ -362,7 +379,6 @@ describe("Sidebar Environment Variables", () => {
362379
});
363380

364381
openConfigSection();
365-
366382
// First update
367383
const timeoutInput = screen.getByTestId(
368384
"MCP_SERVER_REQUEST_TIMEOUT-input",
@@ -374,11 +390,13 @@ describe("Sidebar Environment Variables", () => {
374390

375391
// Rerender with the updated config
376392
rerender(
377-
<Sidebar
378-
{...defaultProps}
379-
config={updatedConfig}
380-
setConfig={setConfig}
381-
/>,
393+
<TooltipProvider>
394+
<Sidebar
395+
{...defaultProps}
396+
config={updatedConfig}
397+
setConfig={setConfig}
398+
/>
399+
</TooltipProvider>,
382400
);
383401

384402
// Second update
@@ -388,12 +406,14 @@ describe("Sidebar Environment Variables", () => {
388406
fireEvent.change(updatedTimeoutInput, { target: { value: "3000" } });
389407

390408
// Verify the final state matches what we expect
391-
expect(setConfig).toHaveBeenLastCalledWith({
392-
MCP_SERVER_REQUEST_TIMEOUT: {
393-
description: "Timeout for requests to the MCP server (ms)",
394-
value: 3000,
395-
},
396-
});
409+
expect(setConfig).toHaveBeenLastCalledWith(
410+
expect.objectContaining({
411+
MCP_SERVER_REQUEST_TIMEOUT: {
412+
description: "Timeout for requests to the MCP server (ms)",
413+
value: 3000,
414+
},
415+
}),
416+
);
397417
});
398418
});
399419
});

client/src/components/ui/tooltip.tsx

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
5+
6+
import { cn } from "@/lib/utils";
7+
8+
const TooltipProvider = TooltipPrimitive.Provider;
9+
10+
const Tooltip = TooltipPrimitive.Root;
11+
12+
const TooltipTrigger = TooltipPrimitive.Trigger;
13+
14+
const TooltipContent = React.forwardRef<
15+
React.ElementRef<typeof TooltipPrimitive.Content>,
16+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
17+
>(({ className, sideOffset = 4, ...props }, ref) => (
18+
<TooltipPrimitive.Content
19+
ref={ref}
20+
sideOffset={sideOffset}
21+
className={cn(
22+
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]",
23+
className,
24+
)}
25+
{...props}
26+
/>
27+
));
28+
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
29+
30+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

client/src/lib/configurationTypes.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ export type InspectorConfig = {
1515
* Maximum time in milliseconds to wait for a response from the MCP server before timing out.
1616
*/
1717
MCP_SERVER_REQUEST_TIMEOUT: ConfigItem;
18+
MCP_PROXY_FULL_ADDRESS: ConfigItem;
1819
};

0 commit comments

Comments
 (0)