Add MCP proxy address config support, better error messages
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
EyeOff,
|
||||
RotateCcw,
|
||||
Settings,
|
||||
HelpCircle,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -26,12 +27,17 @@ import {
|
||||
LoggingLevelSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { InspectorConfig } from "@/lib/configurationTypes";
|
||||
|
||||
import { ConnectionStatus } from "@/lib/constants";
|
||||
import useTheme from "../lib/useTheme";
|
||||
import { version } from "../../../package.json";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipTrigger,
|
||||
TooltipContent,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
interface SidebarProps {
|
||||
connectionStatus: "disconnected" | "connected" | "error";
|
||||
connectionStatus: ConnectionStatus;
|
||||
transportType: "stdio" | "sse";
|
||||
setTransportType: (type: "stdio" | "sse") => void;
|
||||
command: string;
|
||||
@@ -177,6 +183,7 @@ const Sidebar = ({
|
||||
variant="outline"
|
||||
onClick={() => setShowEnvVars(!showEnvVars)}
|
||||
className="flex items-center w-full"
|
||||
data-testid="env-vars-button"
|
||||
>
|
||||
{showEnvVars ? (
|
||||
<ChevronDown className="w-4 h-4 mr-2" />
|
||||
@@ -298,6 +305,7 @@ const Sidebar = ({
|
||||
variant="outline"
|
||||
onClick={() => setShowConfig(!showConfig)}
|
||||
className="flex items-center w-full"
|
||||
data-testid="config-button"
|
||||
>
|
||||
{showConfig ? (
|
||||
<ChevronDown className="w-4 h-4 mr-2" />
|
||||
@@ -313,9 +321,19 @@ const Sidebar = ({
|
||||
const configKey = key as keyof InspectorConfig;
|
||||
return (
|
||||
<div key={key} className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
{configItem.description}
|
||||
</label>
|
||||
<div className="flex items-center gap-1">
|
||||
<label className="text-sm font-medium text-green-600">
|
||||
{configKey}
|
||||
</label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{configItem.description}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{typeof configItem.value === "number" ? (
|
||||
<Input
|
||||
type="number"
|
||||
@@ -375,7 +393,11 @@ const Sidebar = ({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Button className="w-full" onClick={onConnect}>
|
||||
<Button
|
||||
data-testid="connect-button"
|
||||
className="w-full"
|
||||
onClick={onConnect}
|
||||
>
|
||||
{connectionStatus === "connected" ? (
|
||||
<>
|
||||
<RotateCcw className="w-4 h-4 mr-2" />
|
||||
@@ -391,20 +413,32 @@ const Sidebar = ({
|
||||
|
||||
<div className="flex items-center justify-center space-x-2 mb-4">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
connectionStatus === "connected"
|
||||
? "bg-green-500"
|
||||
: connectionStatus === "error"
|
||||
? "bg-red-500"
|
||||
: "bg-gray-500"
|
||||
}`}
|
||||
className={`w-2 h-2 rounded-full ${(() => {
|
||||
switch (connectionStatus) {
|
||||
case "connected":
|
||||
return "bg-green-500";
|
||||
case "error":
|
||||
return "bg-red-500";
|
||||
case "error-connecting-to-proxy":
|
||||
return "bg-red-500";
|
||||
default:
|
||||
return "bg-gray-500";
|
||||
}
|
||||
})()}`}
|
||||
/>
|
||||
<span className="text-sm text-gray-600">
|
||||
{connectionStatus === "connected"
|
||||
? "Connected"
|
||||
: connectionStatus === "error"
|
||||
? "Connection Error"
|
||||
: "Disconnected"}
|
||||
{(() => {
|
||||
switch (connectionStatus) {
|
||||
case "connected":
|
||||
return "Connected";
|
||||
case "error":
|
||||
return "Connection Error, is your MCP server running?";
|
||||
case "error-connecting-to-proxy":
|
||||
return "Error Connecting to MCP Inspector Proxy - Check Console logs";
|
||||
default:
|
||||
return "Disconnected";
|
||||
}
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { describe, it, beforeEach, jest } from "@jest/globals";
|
||||
import Sidebar from "../Sidebar";
|
||||
import { DEFAULT_INSPECTOR_CONFIG } from "../../lib/constants";
|
||||
import { InspectorConfig } from "../../lib/configurationTypes";
|
||||
import { DEFAULT_INSPECTOR_CONFIG } from "@/lib/constants";
|
||||
import { InspectorConfig } from "@/lib/configurationTypes";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
|
||||
// Mock theme hook
|
||||
jest.mock("../../lib/useTheme", () => ({
|
||||
@@ -35,11 +36,15 @@ describe("Sidebar Environment Variables", () => {
|
||||
};
|
||||
|
||||
const renderSidebar = (props = {}) => {
|
||||
return render(<Sidebar {...defaultProps} {...props} />);
|
||||
return render(
|
||||
<TooltipProvider>
|
||||
<Sidebar {...defaultProps} {...props} />
|
||||
</TooltipProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
const openEnvVarsSection = () => {
|
||||
const button = screen.getByText("Environment Variables");
|
||||
const button = screen.getByTestId("env-vars-button");
|
||||
fireEvent.click(button);
|
||||
};
|
||||
|
||||
@@ -215,7 +220,11 @@ describe("Sidebar Environment Variables", () => {
|
||||
const updatedEnv = setEnv.mock.calls[0][0] as Record<string, string>;
|
||||
|
||||
// Rerender with the updated env
|
||||
rerender(<Sidebar {...defaultProps} env={updatedEnv} setEnv={setEnv} />);
|
||||
rerender(
|
||||
<TooltipProvider>
|
||||
<Sidebar {...defaultProps} env={updatedEnv} setEnv={setEnv} />
|
||||
</TooltipProvider>,
|
||||
);
|
||||
|
||||
// Second key edit
|
||||
const secondKeyInput = screen.getByDisplayValue("SECOND_KEY");
|
||||
@@ -246,7 +255,11 @@ describe("Sidebar Environment Variables", () => {
|
||||
fireEvent.change(keyInput, { target: { value: "NEW_KEY" } });
|
||||
|
||||
// Rerender with updated env
|
||||
rerender(<Sidebar {...defaultProps} env={{ NEW_KEY: "test_value" }} />);
|
||||
rerender(
|
||||
<TooltipProvider>
|
||||
<Sidebar {...defaultProps} env={{ NEW_KEY: "test_value" }} />
|
||||
</TooltipProvider>,
|
||||
);
|
||||
|
||||
// Value should still be visible
|
||||
const updatedValueInput = screen.getByDisplayValue("test_value");
|
||||
@@ -311,7 +324,7 @@ describe("Sidebar Environment Variables", () => {
|
||||
|
||||
describe("Configuration Operations", () => {
|
||||
const openConfigSection = () => {
|
||||
const button = screen.getByText("Configuration");
|
||||
const button = screen.getByTestId("config-button");
|
||||
fireEvent.click(button);
|
||||
};
|
||||
|
||||
@@ -326,12 +339,14 @@ describe("Sidebar Environment Variables", () => {
|
||||
);
|
||||
fireEvent.change(timeoutInput, { target: { value: "5000" } });
|
||||
|
||||
expect(setConfig).toHaveBeenCalledWith({
|
||||
MCP_SERVER_REQUEST_TIMEOUT: {
|
||||
description: "Timeout for requests to the MCP server (ms)",
|
||||
value: 5000,
|
||||
},
|
||||
});
|
||||
expect(setConfig).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
MCP_SERVER_REQUEST_TIMEOUT: {
|
||||
description: "Timeout for requests to the MCP server (ms)",
|
||||
value: 5000,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle invalid timeout values entered by user", () => {
|
||||
@@ -345,12 +360,14 @@ describe("Sidebar Environment Variables", () => {
|
||||
);
|
||||
fireEvent.change(timeoutInput, { target: { value: "abc1" } });
|
||||
|
||||
expect(setConfig).toHaveBeenCalledWith({
|
||||
MCP_SERVER_REQUEST_TIMEOUT: {
|
||||
description: "Timeout for requests to the MCP server (ms)",
|
||||
value: 0,
|
||||
},
|
||||
});
|
||||
expect(setConfig).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
MCP_SERVER_REQUEST_TIMEOUT: {
|
||||
description: "Timeout for requests to the MCP server (ms)",
|
||||
value: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should maintain configuration state after multiple updates", () => {
|
||||
@@ -361,7 +378,6 @@ describe("Sidebar Environment Variables", () => {
|
||||
});
|
||||
|
||||
openConfigSection();
|
||||
|
||||
// First update
|
||||
const timeoutInput = screen.getByTestId(
|
||||
"MCP_SERVER_REQUEST_TIMEOUT-input",
|
||||
@@ -373,11 +389,13 @@ describe("Sidebar Environment Variables", () => {
|
||||
|
||||
// Rerender with the updated config
|
||||
rerender(
|
||||
<Sidebar
|
||||
{...defaultProps}
|
||||
config={updatedConfig}
|
||||
setConfig={setConfig}
|
||||
/>,
|
||||
<TooltipProvider>
|
||||
<Sidebar
|
||||
{...defaultProps}
|
||||
config={updatedConfig}
|
||||
setConfig={setConfig}
|
||||
/>
|
||||
</TooltipProvider>,
|
||||
);
|
||||
|
||||
// Second update
|
||||
@@ -387,12 +405,14 @@ describe("Sidebar Environment Variables", () => {
|
||||
fireEvent.change(updatedTimeoutInput, { target: { value: "3000" } });
|
||||
|
||||
// Verify the final state matches what we expect
|
||||
expect(setConfig).toHaveBeenLastCalledWith({
|
||||
MCP_SERVER_REQUEST_TIMEOUT: {
|
||||
description: "Timeout for requests to the MCP server (ms)",
|
||||
value: 3000,
|
||||
},
|
||||
});
|
||||
expect(setConfig).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
MCP_SERVER_REQUEST_TIMEOUT: {
|
||||
description: "Timeout for requests to the MCP server (ms)",
|
||||
value: 3000,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
30
client/src/components/ui/tooltip.tsx
Normal file
30
client/src/components/ui/tooltip.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider;
|
||||
|
||||
const Tooltip = TooltipPrimitive.Root;
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"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]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||
Reference in New Issue
Block a user