import { useState, useCallback } from "react"; import { Play, ChevronDown, ChevronRight, CircleHelp, Bug, Github, Eye, EyeOff, RotateCcw, Settings, HelpCircle, RefreshCwOff, Copy, CheckCheck, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { StdErrNotification } from "@/lib/notificationTypes"; import { LoggingLevel, LoggingLevelSchema, } from "@modelcontextprotocol/sdk/types.js"; import { InspectorConfig } from "@/lib/configurationTypes"; import { ConnectionStatus } from "@/lib/constants"; import useTheme from "../lib/hooks/useTheme"; import { version } from "../../../package.json"; import { Tooltip, TooltipTrigger, TooltipContent, } from "@/components/ui/tooltip"; import { useToast } from "../lib/hooks/useToast"; export interface SidebarProps { connectionStatus: ConnectionStatus; transportType: "stdio" | "sse" | "streamable-http"; setTransportType: (type: "stdio" | "sse" | "streamable-http") => void; command: string; setCommand: (command: string) => void; args: string; setArgs: (args: string) => void; sseUrl: string; setSseUrl: (url: string) => void; env: Record; setEnv: (env: Record) => void; bearerToken: string; setBearerToken: (token: string) => void; headerName?: string; setHeaderName?: (name: string) => void; customHeaders: [string, string][]; setCustomHeaders: (headers: [string, string][]) => void; onConnect: () => void; onDisconnect: () => void; stdErrNotifications: StdErrNotification[]; clearStdErrNotifications: () => void; logLevel: LoggingLevel; sendLogLevelRequest: (level: LoggingLevel) => void; loggingSupported: boolean; config: InspectorConfig; setConfig: (config: InspectorConfig) => void; } const Sidebar = ({ connectionStatus, transportType, setTransportType, command, setCommand, args, setArgs, sseUrl, setSseUrl, env, setEnv, bearerToken, setBearerToken, headerName, setHeaderName, customHeaders, setCustomHeaders, onConnect, onDisconnect, stdErrNotifications, clearStdErrNotifications, logLevel, sendLogLevelRequest, loggingSupported, config, setConfig, }: SidebarProps) => { const [theme, setTheme] = useTheme(); const [showEnvVars, setShowEnvVars] = useState(false); const [showBearerToken, setShowBearerToken] = useState(false); const [showConfig, setShowConfig] = useState(false); const [shownEnvVars, setShownEnvVars] = useState>(new Set()); const [copiedServerEntry, setCopiedServerEntry] = useState(false); const [copiedServerFile, setCopiedServerFile] = useState(false); const { toast } = useToast(); const [showCustomHeaders, setShowCustomHeaders] = useState(false); // Reusable error reporter for copy actions const reportError = useCallback( (error: unknown) => { toast({ title: "Error", description: `Failed to copy config: ${error instanceof Error ? error.message : String(error)}`, variant: "destructive", }); }, [toast], ); // Shared utility function to generate server config const generateServerConfig = useCallback(() => { if (transportType === "stdio") { return { command, args: args.trim() ? args.split(/\s+/) : [], env: { ...env }, }; } if (transportType === "sse") { return { type: "sse", url: sseUrl, note: "For SSE connections, add this URL directly in Client", }; } if (transportType === "streamable-http") { return { type: "streamable-http", url: sseUrl, note: "For Streamable HTTP connections, add this URL directly in Client", }; } return {}; }, [transportType, command, args, env, sseUrl]); // Memoized config entry generator const generateMCPServerEntry = useCallback(() => { return JSON.stringify(generateServerConfig(), null, 4); }, [generateServerConfig]); // Memoized config file generator const generateMCPServerFile = useCallback(() => { return JSON.stringify( { mcpServers: { "default-server": generateServerConfig(), }, }, null, 4, ); }, [generateServerConfig]); // Memoized copy handlers const handleCopyServerEntry = useCallback(() => { try { const configJson = generateMCPServerEntry(); navigator.clipboard .writeText(configJson) .then(() => { setCopiedServerEntry(true); toast({ title: "Config entry copied", description: transportType === "stdio" ? "Server configuration has been copied to clipboard. Add this to your mcp.json inside the 'mcpServers' object with your preferred server name." : "SSE URL has been copied. Use this URL in Cursor directly.", }); setTimeout(() => { setCopiedServerEntry(false); }, 2000); }) .catch((error) => { reportError(error); }); } catch (error) { reportError(error); } }, [generateMCPServerEntry, transportType, toast, reportError]); const handleCopyServerFile = useCallback(() => { try { const configJson = generateMCPServerFile(); navigator.clipboard .writeText(configJson) .then(() => { setCopiedServerFile(true); toast({ title: "Servers file copied", description: "Servers configuration has been copied to clipboard. Add this to your mcp.json file. Current testing server will be added as 'default-server'", }); setTimeout(() => { setCopiedServerFile(false); }, 2000); }) .catch((error) => { reportError(error); }); } catch (error) { reportError(error); } }, [generateMCPServerFile, toast, reportError]); const removeCustomHeader = (index: number) => { const newHeaders = [...customHeaders]; newHeaders.splice(index, 1); setCustomHeaders(newHeaders); }; const updateCustomHeader = (index: number, field: 'key' | 'value', value: string) => { const newArr = [...customHeaders]; const [oldKey, oldValue] = newArr[index]; const newTuple: [string, string] = field === 'key' ? [value, oldValue] : [oldKey, value]; newArr[index] = newTuple; setCustomHeaders(newArr); }; return (

MCP Inspector v{version}

{transportType === "stdio" ? ( <>
setCommand(e.target.value)} className="font-mono" />
setArgs(e.target.value)} className="font-mono" />
) : ( <>
setSseUrl(e.target.value)} className="font-mono" />
{showBearerToken && (
setHeaderName && setHeaderName(e.target.value) } data-testid="header-input" className="font-mono" value={headerName} /> setBearerToken(e.target.value)} data-testid="bearer-token-input" className="font-mono" type="password" />
)}
)} {transportType === "sse" && (
{showCustomHeaders && (
{customHeaders.map((header, index) => (
updateCustomHeader(index, 'key', e.target.value)} className="font-mono" /> updateCustomHeader(index, 'value', e.target.value)} className="font-mono" />
))}
)}
)} {transportType === "stdio" && (
{showEnvVars && (
{Object.entries(env).map(([key, value], idx) => (
{ const newKey = e.target.value; const newEnv = Object.entries(env).reduce( (acc, [k, v]) => { if (k === key) { acc[newKey] = value; } else { acc[k] = v; } return acc; }, {} as Record, ); setEnv(newEnv); setShownEnvVars((prev) => { const next = new Set(prev); if (next.has(key)) { next.delete(key); next.add(newKey); } return next; }); }} className="font-mono" />
{ const newEnv = { ...env }; newEnv[key] = e.target.value; setEnv(newEnv); }} className="font-mono" />
))}
)}
)} {/* Always show both copy buttons for all transport types */}
Copy Server Entry Copy Servers File
{/* Configuration */}
{showConfig && (
{Object.entries(config).map(([key, configItem]) => { const configKey = key as keyof InspectorConfig; return (
{configItem.description}
{typeof configItem.value === "number" ? ( { const newConfig = { ...config }; newConfig[configKey] = { ...configItem, value: Number(e.target.value), }; setConfig(newConfig); }} className="font-mono" /> ) : typeof configItem.value === "boolean" ? ( ) : ( { const newConfig = { ...config }; newConfig[configKey] = { ...configItem, value: e.target.value, }; setConfig(newConfig); }} className="font-mono" /> )}
); })}
)}
{connectionStatus === "connected" && (
)} {connectionStatus !== "connected" && ( )}
{ 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"; } })()}`} /> {(() => { 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"; } })()}
{loggingSupported && connectionStatus === "connected" && (
)} {stdErrNotifications.length > 0 && ( <>

Error output from MCP server

{stdErrNotifications.map((notification, index) => (
{notification.params.content}
))}
)}
); }; export default Sidebar;