Skip to content

Commit 51c7eda

Browse files
Add MCP proxy address config support, better error messages
1 parent fa7f9c8 commit 51c7eda

File tree

12 files changed

+483
-87
lines changed

12 files changed

+483
-87
lines changed

client/package.json

Lines changed: 1 addition & 0 deletions
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
"@types/prismjs": "^1.26.5",
3637
"class-variance-authority": "^0.7.0",
3738
"clsx": "^2.1.1",

client/src/App.tsx

Lines changed: 15 additions & 7 deletions
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";
@@ -47,10 +46,12 @@ import Sidebar from "./components/Sidebar";
4746
import ToolsTab from "./components/ToolsTab";
4847
import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants";
4948
import { InspectorConfig } from "./lib/configurationTypes";
49+
import {
50+
getMCPProxyAddress,
51+
getMCPServerRequestTimeout,
52+
} from "./utils/configUtils";
5053

5154
const params = new URLSearchParams(window.location.search);
52-
const PROXY_PORT = params.get("proxyPort") ?? "6277";
53-
const PROXY_SERVER_URL = `http://${window.location.hostname}:${PROXY_PORT}`;
5455
const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1";
5556

5657
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") || "";
@@ -152,8 +159,8 @@ const App = () => {
152159
sseUrl,
153160
env,
154161
bearerToken,
155-
proxyServerUrl: PROXY_SERVER_URL,
156-
requestTimeout: config.MCP_SERVER_REQUEST_TIMEOUT.value as number,
162+
proxyServerUrl: getMCPProxyAddress(config),
163+
requestTimeout: getMCPServerRequestTimeout(config),
157164
onNotification: (notification) => {
158165
setNotifications((prev) => [...prev, notification as ServerNotification]);
159166
},
@@ -214,7 +221,7 @@ const App = () => {
214221
}, [connectMcpServer]);
215222

216223
useEffect(() => {
217-
fetch(`${PROXY_SERVER_URL}/config`)
224+
fetch(`${getMCPProxyAddress(config)}/config`)
218225
.then((response) => response.json())
219226
.then((data) => {
220227
setEnv(data.defaultEnvironment);
@@ -228,6 +235,7 @@ const App = () => {
228235
.catch((error) =>
229236
console.error("Error fetching default environment:", error),
230237
);
238+
// eslint-disable-next-line react-hooks/exhaustive-deps
231239
}, []);
232240

233241
useEffect(() => {

client/src/components/Sidebar.tsx

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
EyeOff,
1111
RotateCcw,
1212
Settings,
13+
HelpCircle,
1314
} from "lucide-react";
1415
import { Button } from "@/components/ui/button";
1516
import { Input } from "@/components/ui/input";
@@ -26,12 +27,17 @@ import {
2627
LoggingLevelSchema,
2728
} from "@modelcontextprotocol/sdk/types.js";
2829
import { InspectorConfig } from "@/lib/configurationTypes";
29-
30+
import { ConnectionStatus } from "@/lib/constants";
3031
import useTheme from "../lib/useTheme";
3132
import { version } from "../../../package.json";
33+
import {
34+
Tooltip,
35+
TooltipTrigger,
36+
TooltipContent,
37+
} from "@/components/ui/tooltip";
3238

3339
interface SidebarProps {
34-
connectionStatus: "disconnected" | "connected" | "error";
40+
connectionStatus: ConnectionStatus;
3541
transportType: "stdio" | "sse";
3642
setTransportType: (type: "stdio" | "sse") => void;
3743
command: string;
@@ -177,6 +183,7 @@ const Sidebar = ({
177183
variant="outline"
178184
onClick={() => setShowEnvVars(!showEnvVars)}
179185
className="flex items-center w-full"
186+
data-testid="env-vars-button"
180187
>
181188
{showEnvVars ? (
182189
<ChevronDown className="w-4 h-4 mr-2" />
@@ -298,6 +305,7 @@ const Sidebar = ({
298305
variant="outline"
299306
onClick={() => setShowConfig(!showConfig)}
300307
className="flex items-center w-full"
308+
data-testid="config-button"
301309
>
302310
{showConfig ? (
303311
<ChevronDown className="w-4 h-4 mr-2" />
@@ -313,9 +321,19 @@ const Sidebar = ({
313321
const configKey = key as keyof InspectorConfig;
314322
return (
315323
<div key={key} className="space-y-2">
316-
<label className="text-sm font-medium">
317-
{configItem.description}
318-
</label>
324+
<div className="flex items-center gap-1">
325+
<label className="text-sm font-medium text-green-600">
326+
{configKey}
327+
</label>
328+
<Tooltip>
329+
<TooltipTrigger asChild>
330+
<HelpCircle className="h-4 w-4 text-muted-foreground" />
331+
</TooltipTrigger>
332+
<TooltipContent>
333+
{configItem.description}
334+
</TooltipContent>
335+
</Tooltip>
336+
</div>
319337
{typeof configItem.value === "number" ? (
320338
<Input
321339
type="number"
@@ -375,7 +393,11 @@ const Sidebar = ({
375393
</div>
376394

377395
<div className="space-y-2">
378-
<Button className="w-full" onClick={onConnect}>
396+
<Button
397+
data-testid="connect-button"
398+
className="w-full"
399+
onClick={onConnect}
400+
>
379401
{connectionStatus === "connected" ? (
380402
<>
381403
<RotateCcw className="w-4 h-4 mr-2" />
@@ -391,20 +413,32 @@ const Sidebar = ({
391413

392414
<div className="flex items-center justify-center space-x-2 mb-4">
393415
<div
394-
className={`w-2 h-2 rounded-full ${
395-
connectionStatus === "connected"
396-
? "bg-green-500"
397-
: connectionStatus === "error"
398-
? "bg-red-500"
399-
: "bg-gray-500"
400-
}`}
416+
className={`w-2 h-2 rounded-full ${(() => {
417+
switch (connectionStatus) {
418+
case "connected":
419+
return "bg-green-500";
420+
case "error":
421+
return "bg-red-500";
422+
case "error-connecting-to-proxy":
423+
return "bg-red-500";
424+
default:
425+
return "bg-gray-500";
426+
}
427+
})()}`}
401428
/>
402429
<span className="text-sm text-gray-600">
403-
{connectionStatus === "connected"
404-
? "Connected"
405-
: connectionStatus === "error"
406-
? "Connection Error"
407-
: "Disconnected"}
430+
{(() => {
431+
switch (connectionStatus) {
432+
case "connected":
433+
return "Connected";
434+
case "error":
435+
return "Connection Error, is your MCP server running?";
436+
case "error-connecting-to-proxy":
437+
return "Error Connecting to MCP Inspector Proxy - Check Console logs";
438+
default:
439+
return "Disconnected";
440+
}
441+
})()}
408442
</span>
409443
</div>
410444

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

Lines changed: 51 additions & 31 deletions
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", () => ({
@@ -35,11 +36,15 @@ describe("Sidebar Environment Variables", () => {
3536
};
3637

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

4146
const openEnvVarsSection = () => {
42-
const button = screen.getByText("Environment Variables");
47+
const button = screen.getByTestId("env-vars-button");
4348
fireEvent.click(button);
4449
};
4550

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

217222
// Rerender with the updated env
218-
rerender(<Sidebar {...defaultProps} env={updatedEnv} setEnv={setEnv} />);
223+
rerender(
224+
<TooltipProvider>
225+
<Sidebar {...defaultProps} env={updatedEnv} setEnv={setEnv} />
226+
</TooltipProvider>,
227+
);
219228

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

248257
// Rerender with updated env
249-
rerender(<Sidebar {...defaultProps} env={{ NEW_KEY: "test_value" }} />);
258+
rerender(
259+
<TooltipProvider>
260+
<Sidebar {...defaultProps} env={{ NEW_KEY: "test_value" }} />
261+
</TooltipProvider>,
262+
);
250263

251264
// Value should still be visible
252265
const updatedValueInput = screen.getByDisplayValue("test_value");
@@ -311,7 +324,7 @@ describe("Sidebar Environment Variables", () => {
311324

312325
describe("Configuration Operations", () => {
313326
const openConfigSection = () => {
314-
const button = screen.getByText("Configuration");
327+
const button = screen.getByTestId("config-button");
315328
fireEvent.click(button);
316329
};
317330

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

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

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

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

356373
it("should maintain configuration state after multiple updates", () => {
@@ -361,7 +378,6 @@ describe("Sidebar Environment Variables", () => {
361378
});
362379

363380
openConfigSection();
364-
365381
// First update
366382
const timeoutInput = screen.getByTestId(
367383
"MCP_SERVER_REQUEST_TIMEOUT-input",
@@ -373,11 +389,13 @@ describe("Sidebar Environment Variables", () => {
373389

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

383401
// Second update
@@ -387,12 +405,14 @@ describe("Sidebar Environment Variables", () => {
387405
fireEvent.change(updatedTimeoutInput, { target: { value: "3000" } });
388406

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

client/src/components/ui/tooltip.tsx

Lines changed: 30 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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)