Skip to content

Commit 4a23585

Browse files
Add support in UI to configure request timeout
1 parent 043f604 commit 4a23585

File tree

5 files changed

+115
-5
lines changed

5 files changed

+115
-5
lines changed

client/eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default tseslint.config(
1919
},
2020
rules: {
2121
...reactHooks.configs.recommended.rules,
22+
"react-hooks/rules-of-hooks": "off", // Disable hooks dependency order checking
2223
"react-refresh/only-export-components": [
2324
"warn",
2425
{ allowConstantExport: true },

client/src/App.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,13 @@ import RootsTab from "./components/RootsTab";
4545
import SamplingTab, { PendingRequest } from "./components/SamplingTab";
4646
import Sidebar from "./components/Sidebar";
4747
import ToolsTab from "./components/ToolsTab";
48+
import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants";
49+
import { InspectorConfig } from "./lib/configurationTypes";
4850

4951
const params = new URLSearchParams(window.location.search);
5052
const PROXY_PORT = params.get("proxyPort") ?? "3000";
5153
const PROXY_SERVER_URL = `http://localhost:${PROXY_PORT}`;
54+
const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1";
5255

5356
const App = () => {
5457
// Handle OAuth callback route
@@ -99,6 +102,11 @@ const App = () => {
99102
>([]);
100103
const [roots, setRoots] = useState<Root[]>([]);
101104
const [env, setEnv] = useState<Record<string, string>>({});
105+
106+
const [config, setConfig] = useState<InspectorConfig>(() => {
107+
const savedConfig = localStorage.getItem(CONFIG_LOCAL_STORAGE_KEY);
108+
return savedConfig ? JSON.parse(savedConfig) : DEFAULT_INSPECTOR_CONFIG;
109+
});
102110
const [bearerToken, setBearerToken] = useState<string>(() => {
103111
return localStorage.getItem("lastBearerToken") || "";
104112
});
@@ -171,6 +179,7 @@ const App = () => {
171179
env,
172180
bearerToken,
173181
proxyServerUrl: PROXY_SERVER_URL,
182+
requestTimeout: config.MCP_SERVER_REQUEST_TIMEOUT.value as number,
174183
onNotification: (notification) => {
175184
setNotifications((prev) => [...prev, notification as ServerNotification]);
176185
},
@@ -209,6 +218,10 @@ const App = () => {
209218
localStorage.setItem("lastBearerToken", bearerToken);
210219
}, [bearerToken]);
211220

221+
useEffect(() => {
222+
localStorage.setItem(CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config));
223+
}, [config]);
224+
212225
// Auto-connect if serverUrl is provided in URL params (e.g. after OAuth callback)
213226
useEffect(() => {
214227
const serverUrl = params.get("serverUrl");
@@ -439,6 +452,8 @@ const App = () => {
439452
setSseUrl={setSseUrl}
440453
env={env}
441454
setEnv={setEnv}
455+
config={config}
456+
setConfig={setConfig}
442457
bearerToken={bearerToken}
443458
setBearerToken={setBearerToken}
444459
onConnect={connectMcpServer}

client/src/components/Sidebar.tsx

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Github,
99
Eye,
1010
EyeOff,
11+
Settings,
1112
} from "lucide-react";
1213
import { Button } from "@/components/ui/button";
1314
import { Input } from "@/components/ui/input";
@@ -23,6 +24,7 @@ import {
2324
LoggingLevel,
2425
LoggingLevelSchema,
2526
} from "@modelcontextprotocol/sdk/types.js";
27+
import { InspectorConfig } from "@/lib/configurationTypes";
2628

2729
import useTheme from "../lib/useTheme";
2830
import { version } from "../../../package.json";
@@ -46,6 +48,8 @@ interface SidebarProps {
4648
logLevel: LoggingLevel;
4749
sendLogLevelRequest: (level: LoggingLevel) => void;
4850
loggingSupported: boolean;
51+
config: InspectorConfig;
52+
setConfig: (config: InspectorConfig) => void;
4953
}
5054

5155
const Sidebar = ({
@@ -67,10 +71,13 @@ const Sidebar = ({
6771
logLevel,
6872
sendLogLevelRequest,
6973
loggingSupported,
74+
config,
75+
setConfig,
7076
}: SidebarProps) => {
7177
const [theme, setTheme] = useTheme();
7278
const [showEnvVars, setShowEnvVars] = useState(false);
7379
const [showBearerToken, setShowBearerToken] = useState(false);
80+
const [showConfig, setShowConfig] = useState(false);
7481
const [shownEnvVars, setShownEnvVars] = useState<Set<string>>(new Set());
7582

7683
return (
@@ -276,6 +283,85 @@ const Sidebar = ({
276283
</div>
277284
)}
278285

286+
{/* Configuration */}
287+
<div className="space-y-2">
288+
<Button
289+
variant="secondary"
290+
onClick={() => setShowConfig(!showConfig)}
291+
className="flex items-center w-full"
292+
>
293+
{showConfig ? (
294+
<ChevronDown className="w-4 h-4 mr-2" />
295+
) : (
296+
<ChevronRight className="w-4 h-4 mr-2" />
297+
)}
298+
<Settings className="w-4 h-4 mr-2" />
299+
Configuration
300+
</Button>
301+
{showConfig && (
302+
<div className="space-y-2">
303+
{Object.entries(config).map(([key, configItem]) => {
304+
const configKey = key as keyof InspectorConfig;
305+
return (
306+
<div key={key} className="space-y-2">
307+
<label className="text-sm font-medium">
308+
{configItem.description}
309+
</label>
310+
{typeof configItem.value === "number" ? (
311+
<Input
312+
type="number"
313+
value={configItem.value}
314+
onChange={(e) => {
315+
const newConfig = { ...config };
316+
newConfig[configKey] = {
317+
...configItem,
318+
value: Number(e.target.value),
319+
};
320+
setConfig(newConfig);
321+
}}
322+
className="font-mono"
323+
/>
324+
) : typeof configItem.value === "boolean" ? (
325+
<Select
326+
value={configItem.value.toString()}
327+
onValueChange={(val) => {
328+
const newConfig = { ...config };
329+
newConfig[configKey] = {
330+
...configItem,
331+
value: val === "true",
332+
};
333+
setConfig(newConfig);
334+
}}
335+
>
336+
<SelectTrigger>
337+
<SelectValue />
338+
</SelectTrigger>
339+
<SelectContent>
340+
<SelectItem value="true">True</SelectItem>
341+
<SelectItem value="false">False</SelectItem>
342+
</SelectContent>
343+
</Select>
344+
) : (
345+
<Input
346+
value={configItem.value}
347+
onChange={(e) => {
348+
const newConfig = { ...config };
349+
newConfig[configKey] = {
350+
...configItem,
351+
value: e.target.value,
352+
};
353+
setConfig(newConfig);
354+
}}
355+
className="font-mono"
356+
/>
357+
)}
358+
</div>
359+
);
360+
})}
361+
</div>
362+
)}
363+
</div>
364+
279365
<div className="space-y-2">
280366
<Button className="w-full" onClick={onConnect}>
281367
<Play className="w-4 h-4 mr-2" />

client/src/lib/constants.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1+
import { InspectorConfig } from "./configurationTypes";
2+
3+
14
// OAuth-related session storage keys
25
export const SESSION_KEYS = {
36
CODE_VERIFIER: "mcp_code_verifier",
47
SERVER_URL: "mcp_server_url",
58
TOKENS: "mcp_tokens",
69
CLIENT_INFORMATION: "mcp_client_information",
710
} as const;
11+
12+
export const DEFAULT_INSPECTOR_CONFIG: InspectorConfig = {
13+
MCP_SERVER_REQUEST_TIMEOUT: {
14+
description: "Timeout for requests to the MCP server (ms)",
15+
value: 10000,
16+
},
17+
} as const;

client/src/lib/hooks/useConnection.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,6 @@ import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
2929
import { authProvider } from "../auth";
3030
import packageJson from "../../../package.json";
3131

32-
const params = new URLSearchParams(window.location.search);
33-
const DEFAULT_REQUEST_TIMEOUT_MSEC =
34-
parseInt(params.get("timeout") ?? "") || 10000;
35-
3632
interface UseConnectionOptions {
3733
transportType: "stdio" | "sse";
3834
command: string;
@@ -44,7 +40,9 @@ interface UseConnectionOptions {
4440
requestTimeout?: number;
4541
onNotification?: (notification: Notification) => void;
4642
onStdErrNotification?: (notification: Notification) => void;
43+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4744
onPendingRequest?: (request: any, resolve: any, reject: any) => void;
45+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4846
getRoots?: () => any[];
4947
}
5048

@@ -62,7 +60,7 @@ export function useConnection({
6260
env,
6361
proxyServerUrl,
6462
bearerToken,
65-
requestTimeout = DEFAULT_REQUEST_TIMEOUT_MSEC,
63+
requestTimeout,
6664
onNotification,
6765
onStdErrNotification,
6866
onPendingRequest,

0 commit comments

Comments
 (0)