Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ const App = () => {
return localStorage.getItem("lastOauthScope") || "";
});

const [workingDir, setWorkingDir] = useState<string>(() => {
return localStorage.getItem("lastWorkingDir") || "";
});

const [oauthClientSecret, setOauthClientSecret] = useState<string>(() => {
return localStorage.getItem("lastOauthClientSecret") || "";
});
Expand Down Expand Up @@ -264,6 +268,7 @@ const App = () => {
args,
sseUrl,
env,
workingDir,
customHeaders,
oauthClientId,
oauthClientSecret,
Expand Down Expand Up @@ -399,6 +404,10 @@ const App = () => {
localStorage.setItem("lastOauthScope", oauthScope);
}, [oauthScope]);

useEffect(() => {
localStorage.setItem("lastWorkingDir", workingDir);
}, [workingDir]);

useEffect(() => {
localStorage.setItem("lastOauthClientSecret", oauthClientSecret);
}, [oauthClientSecret]);
Expand Down Expand Up @@ -918,6 +927,8 @@ const App = () => {
logLevel={logLevel}
sendLogLevelRequest={sendLogLevelRequest}
loggingSupported={!!serverCapabilities?.logging || false}
workingDir={workingDir}
setWorkingDir={setWorkingDir}
connectionType={connectionType}
setConnectionType={setConnectionType}
/>
Expand Down
85 changes: 73 additions & 12 deletions client/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { useState, useCallback } from "react";
import { useCallback, useState } from "react";
import {
Play,
Bug,
CheckCheck,
ChevronDown,
ChevronRight,
CircleHelp,
Bug,
Github,
Copy,
Eye,
EyeOff,
RotateCcw,
Settings,
Github,
HelpCircle,
Play,
RefreshCwOff,
Copy,
CheckCheck,
RotateCcw,
Settings,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
Expand All @@ -34,12 +34,13 @@ import useTheme from "../lib/hooks/useTheme";
import { version } from "../../../package.json";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import CustomHeaders from "./CustomHeaders";
import { CustomHeaders as CustomHeadersType } from "@/lib/types/customHeaders";
import { useToast } from "../lib/hooks/useToast";
import { useWorkingDirValidation } from "@/lib/hooks/useWorkingDirValidation";

interface SidebarProps {
connectionStatus: ConnectionStatus;
Expand Down Expand Up @@ -69,6 +70,8 @@ interface SidebarProps {
loggingSupported: boolean;
config: InspectorConfig;
setConfig: (config: InspectorConfig) => void;
workingDir: string;
setWorkingDir: (workingDir: string) => void;
connectionType: "direct" | "proxy";
setConnectionType: (type: "direct" | "proxy") => void;
}
Expand Down Expand Up @@ -100,6 +103,8 @@ const Sidebar = ({
loggingSupported,
config,
setConfig,
workingDir,
setWorkingDir,
connectionType,
setConnectionType,
}: SidebarProps) => {
Expand All @@ -113,6 +118,15 @@ const Sidebar = ({
const [copiedServerFile, setCopiedServerFile] = useState(false);
const { toast } = useToast();

// Server-side validation on blur
const {
workingDirError,
setWorkingDirError,
validateOnBlur,
validateNow,
isValidating,
} = useWorkingDirValidation(workingDir, config);

const connectionTypeTip =
"Connect to server directly (requires CORS config on server) or via MCP Inspector Proxy";
// Reusable error reporter for copy actions
Expand All @@ -130,11 +144,15 @@ const Sidebar = ({
// Shared utility function to generate server config
const generateServerConfig = useCallback(() => {
if (transportType === "stdio") {
return {
const config = {
command,
args: args.trim() ? args.split(/\s+/) : [],
env: { ...env },
};
if (workingDir) {
return { ...config, workingDir: workingDir };
}
return config;
}
if (transportType === "sse") {
return {
Expand All @@ -151,7 +169,7 @@ const Sidebar = ({
};
}
return {};
}, [transportType, command, args, env, sseUrl]);
}, [transportType, command, args, env, sseUrl, workingDir]);

// Memoized config entry generator
const generateMCPServerEntry = useCallback(() => {
Expand Down Expand Up @@ -294,6 +312,31 @@ const Sidebar = ({
className="font-mono"
/>
</div>
<div className="space-y-2">
<label
className="text-sm font-medium"
htmlFor="working-dir-input"
>
Working Directory (optional)
</label>
<Input
id="working-dir-input"
placeholder="Working Directory (optional)"
value={workingDir}
onChange={(e) => {
setWorkingDir(e.target.value);
// Clear prior validation error while editing; will re-validate on blur
if (workingDirError) setWorkingDirError(null);
}}
onBlur={validateOnBlur}
className="font-mono"
/>
{workingDirError && (
<div className="text-sm text-red-600 dark:text-red-400">
{workingDirError}
</div>
)}
</div>
</>
) : (
<>
Expand Down Expand Up @@ -734,7 +777,25 @@ const Sidebar = ({
</div>
)}
{connectionStatus !== "connected" && (
<Button className="w-full" onClick={onConnect}>
<Button
className="w-full"
onClick={async () => {
if (transportType === "stdio") {
const ok = await validateNow();
if (!ok) return;
}
onConnect();
}}
disabled={
transportType === "stdio" &&
(isValidating || !!workingDirError)
}
title={
transportType === "stdio" && workingDirError
? workingDirError
: undefined
}
>
<Play className="w-4 h-4 mr-2" />
Connect
</Button>
Expand Down
Loading