Skip to content

Commit 3adddfb

Browse files
committed
merge
2 parents 6dd62d3 + fc86dad commit 3adddfb

File tree

13 files changed

+312
-48
lines changed

13 files changed

+312
-48
lines changed

AGENTS.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# MCP Inspector Development Guide
2+
3+
## Build Commands
4+
5+
- Build all: `npm run build`
6+
- Build client: `npm run build-client`
7+
- Build server: `npm run build-server`
8+
- Development mode: `npm run dev` (use `npm run dev:windows` on Windows)
9+
- Format code: `npm run prettier-fix`
10+
- Client lint: `cd client && npm run lint`
11+
12+
## Code Style Guidelines
13+
14+
- Use TypeScript with proper type annotations
15+
- Follow React functional component patterns with hooks
16+
- Use ES modules (import/export) not CommonJS
17+
- Use Prettier for formatting (auto-formatted on commit)
18+
- Follow existing naming conventions:
19+
- camelCase for variables and functions
20+
- PascalCase for component names and types
21+
- kebab-case for file names
22+
- Use async/await for asynchronous operations
23+
- Implement proper error handling with try/catch blocks
24+
- Use Tailwind CSS for styling in the client
25+
- Keep components small and focused on a single responsibility
26+
27+
## Project Organization
28+
29+
The project is organized as a monorepo with workspaces:
30+
31+
- `client/`: React frontend with Vite, TypeScript and Tailwind
32+
- `server/`: Express backend with TypeScript
33+
- `cli/`: Command-line interface for testing and invoking MCP server methods directly

CLAUDE.md

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1 @@
1-
# MCP Inspector Development Guide
2-
3-
## Build Commands
4-
5-
- Build all: `npm run build`
6-
- Build client: `npm run build-client`
7-
- Build server: `npm run build-server`
8-
- Development mode: `npm run dev` (use `npm run dev:windows` on Windows)
9-
- Format code: `npm run prettier-fix`
10-
- Client lint: `cd client && npm run lint`
11-
12-
## Code Style Guidelines
13-
14-
- Use TypeScript with proper type annotations
15-
- Follow React functional component patterns with hooks
16-
- Use ES modules (import/export) not CommonJS
17-
- Use Prettier for formatting (auto-formatted on commit)
18-
- Follow existing naming conventions:
19-
- camelCase for variables and functions
20-
- PascalCase for component names and types
21-
- kebab-case for file names
22-
- Use async/await for asynchronous operations
23-
- Implement proper error handling with try/catch blocks
24-
- Use Tailwind CSS for styling in the client
25-
- Keep components small and focused on a single responsibility
26-
27-
## Project Organization
28-
29-
The project is organized as a monorepo with workspaces:
30-
31-
- `client/`: React frontend with Vite, TypeScript and Tailwind
32-
- `server/`: Express backend with TypeScript
33-
- `cli/`: Command-line interface for testing and invoking MCP server methods directly
1+
@./AGENTS.md

client/src/App.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ const App = () => {
141141
return localStorage.getItem("lastOauthScope") || "";
142142
});
143143

144+
const [oauthClientSecret, setOauthClientSecret] = useState<string>(() => {
145+
return localStorage.getItem("lastOauthClientSecret") || "";
146+
});
147+
144148
// Custom headers state with migration from legacy auth
145149
const [customHeaders, setCustomHeaders] = useState<CustomHeaders>(() => {
146150
const savedHeaders = localStorage.getItem("lastCustomHeaders");
@@ -262,6 +266,7 @@ const App = () => {
262266
env,
263267
customHeaders,
264268
oauthClientId,
269+
oauthClientSecret,
265270
oauthScope,
266271
config,
267272
connectionType,
@@ -394,6 +399,10 @@ const App = () => {
394399
localStorage.setItem("lastOauthScope", oauthScope);
395400
}, [oauthScope]);
396401

402+
useEffect(() => {
403+
localStorage.setItem("lastOauthClientSecret", oauthClientSecret);
404+
}, [oauthClientSecret]);
405+
397406
useEffect(() => {
398407
saveInspectorConfig(CONFIG_LOCAL_STORAGE_KEY, config);
399408
}, [config]);
@@ -900,6 +909,8 @@ const App = () => {
900909
setCustomHeaders={setCustomHeaders}
901910
oauthClientId={oauthClientId}
902911
setOauthClientId={setOauthClientId}
912+
oauthClientSecret={oauthClientSecret}
913+
setOauthClientSecret={setOauthClientSecret}
903914
oauthScope={oauthScope}
904915
setOauthScope={setOauthScope}
905916
onConnect={connectMcpServer}

client/src/components/JsonView.tsx

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { useState, memo, useMemo, useCallback } from "react";
1+
import { useState, memo, useMemo, useCallback, useEffect } from "react";
2+
import type React from "react";
23
import type { JsonValue } from "@/utils/jsonUtils";
34
import clsx from "clsx";
45
import { Copy, CheckCheck } from "lucide-react";
@@ -101,6 +102,7 @@ const JsonNode = memo(
101102
initialExpandDepth,
102103
isError = false,
103104
}: JsonNodeProps) => {
105+
const { toast } = useToast();
104106
const [isExpanded, setIsExpanded] = useState(depth < initialExpandDepth);
105107
const [typeStyleMap] = useState<Record<string, string>>({
106108
number: "text-blue-600",
@@ -113,6 +115,52 @@ const JsonNode = memo(
113115
});
114116
const dataType = getDataType(data);
115117

118+
const [copied, setCopied] = useState(false);
119+
useEffect(() => {
120+
let timeoutId: NodeJS.Timeout;
121+
if (copied) {
122+
timeoutId = setTimeout(() => setCopied(false), 500);
123+
}
124+
return () => {
125+
if (timeoutId) clearTimeout(timeoutId);
126+
};
127+
}, [copied]);
128+
129+
const handleCopyValue = useCallback(
130+
(value: JsonValue) => {
131+
try {
132+
let text: string;
133+
const valueType = getDataType(value);
134+
switch (valueType) {
135+
case "string":
136+
text = value as unknown as string;
137+
break;
138+
case "number":
139+
case "boolean":
140+
text = String(value);
141+
break;
142+
case "null":
143+
text = "null";
144+
break;
145+
case "undefined":
146+
text = "undefined";
147+
break;
148+
default:
149+
text = JSON.stringify(value);
150+
}
151+
navigator.clipboard.writeText(text);
152+
setCopied(true);
153+
} catch (error) {
154+
toast({
155+
title: "Error",
156+
description: `There was an error coping result into the clipboard: ${error instanceof Error ? error.message : String(error)}`,
157+
variant: "destructive",
158+
});
159+
}
160+
},
161+
[toast],
162+
);
163+
116164
const renderCollapsible = (isArray: boolean) => {
117165
const items = isArray
118166
? (data as JsonValue[])
@@ -206,7 +254,7 @@ const JsonNode = memo(
206254

207255
if (!isTooLong) {
208256
return (
209-
<div className="flex mr-1 rounded hover:bg-gray-800/20">
257+
<div className="flex mr-1 rounded hover:bg-gray-800/20 group items-start">
210258
{name && (
211259
<span className="mr-1 text-gray-600 dark:text-gray-400">
212260
{name}:
@@ -220,12 +268,28 @@ const JsonNode = memo(
220268
>
221269
"{value}"
222270
</pre>
271+
<Button
272+
variant="ghost"
273+
className="ml-1 h-6 w-6 p-0 opacity-0 group-hover:opacity-100"
274+
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
275+
e.stopPropagation();
276+
handleCopyValue(value as unknown as JsonValue);
277+
}}
278+
aria-label={name ? `Copy value of ${name}` : "Copy value"}
279+
title={name ? `Copy value of ${name}` : "Copy value"}
280+
>
281+
{copied ? (
282+
<CheckCheck className="size-4 dark:text-green-700 text-green-600" />
283+
) : (
284+
<Copy className="size-4 text-foreground" />
285+
)}
286+
</Button>
223287
</div>
224288
);
225289
}
226290

227291
return (
228-
<div className="flex mr-1 rounded group hover:bg-gray-800/20">
292+
<div className="flex mr-1 rounded group hover:bg-gray-800/20 items-start">
229293
{name && (
230294
<span className="mr-1 text-gray-600 dark:text-gray-400 dark:group-hover:text-gray-100 group-hover:text-gray-400">
231295
{name}:
@@ -241,6 +305,22 @@ const JsonNode = memo(
241305
>
242306
{isExpanded ? `"${value}"` : `"${value.slice(0, maxLength)}..."`}
243307
</pre>
308+
<Button
309+
variant="ghost"
310+
className="ml-1 h-6 w-6 p-0 opacity-0 group-hover:opacity-100"
311+
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
312+
e.stopPropagation();
313+
handleCopyValue(value as unknown as JsonValue);
314+
}}
315+
aria-label={name ? `Copy value of ${name}` : "Copy value"}
316+
title={name ? `Copy value of ${name}` : "Copy value"}
317+
>
318+
{copied ? (
319+
<CheckCheck className="size-4 dark:text-green-700 text-green-600" />
320+
) : (
321+
<Copy className="size-4 text-foreground" />
322+
)}
323+
</Button>
244324
</div>
245325
);
246326
};
@@ -253,7 +333,7 @@ const JsonNode = memo(
253333
return renderString(data as string);
254334
default:
255335
return (
256-
<div className="flex items-center mr-1 rounded hover:bg-gray-800/20">
336+
<div className="flex items-center mr-1 rounded hover:bg-gray-800/20 group">
257337
{name && (
258338
<span className="mr-1 text-gray-600 dark:text-gray-400">
259339
{name}:
@@ -262,6 +342,22 @@ const JsonNode = memo(
262342
<span className={typeStyleMap[dataType] || typeStyleMap.default}>
263343
{data === null ? "null" : String(data)}
264344
</span>
345+
<Button
346+
variant="ghost"
347+
className="ml-1 h-6 w-6 p-0 opacity-0 group-hover:opacity-100"
348+
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
349+
e.stopPropagation();
350+
handleCopyValue(data as JsonValue);
351+
}}
352+
aria-label={name ? `Copy value of ${name}` : "Copy value"}
353+
title={name ? `Copy value of ${name}` : "Copy value"}
354+
>
355+
{copied ? (
356+
<CheckCheck className="size-4 dark:text-green-700 text-green-600" />
357+
) : (
358+
<Copy className="size-4 text-foreground" />
359+
)}
360+
</Button>
265361
</div>
266362
);
267363
}

client/src/components/PromptsTab.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ const PromptsTab = ({
131131
<Alert variant="destructive">
132132
<AlertCircle className="h-4 w-4" />
133133
<AlertTitle>Error</AlertTitle>
134-
<AlertDescription>{error}</AlertDescription>
134+
<AlertDescription className="break-all">
135+
{error}
136+
</AlertDescription>
135137
</Alert>
136138
) : selectedPrompt ? (
137139
<div className="space-y-4">

client/src/components/ResourcesTab.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,9 @@ const ResourcesTab = ({
225225
<Alert variant="destructive">
226226
<AlertCircle className="h-4 w-4" />
227227
<AlertTitle>Error</AlertTitle>
228-
<AlertDescription>{error}</AlertDescription>
228+
<AlertDescription className="break-all">
229+
{error}
230+
</AlertDescription>
229231
</Alert>
230232
) : selectedResource ? (
231233
<JsonView

client/src/components/SamplingRequest.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const SamplingRequest = ({
5656
},
5757
role: {
5858
type: "string",
59-
default: "endTurn",
59+
default: "assistant",
6060
description: "Role of the model",
6161
},
6262
content: {

client/src/components/Sidebar.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ interface SidebarProps {
5858
setCustomHeaders: (headers: CustomHeadersType) => void;
5959
oauthClientId: string;
6060
setOauthClientId: (id: string) => void;
61+
oauthClientSecret: string;
62+
setOauthClientSecret: (secret: string) => void;
6163
oauthScope: string;
6264
setOauthScope: (scope: string) => void;
6365
onConnect: () => void;
@@ -87,6 +89,8 @@ const Sidebar = ({
8789
setCustomHeaders,
8890
oauthClientId,
8991
setOauthClientId,
92+
oauthClientSecret,
93+
setOauthClientSecret,
9094
oauthScope,
9195
setOauthScope,
9296
onConnect,
@@ -104,6 +108,7 @@ const Sidebar = ({
104108
const [showAuthConfig, setShowAuthConfig] = useState(false);
105109
const [showConfig, setShowConfig] = useState(false);
106110
const [shownEnvVars, setShownEnvVars] = useState<Set<string>>(new Set());
111+
const [showClientSecret, setShowClientSecret] = useState(false);
107112
const [copiedServerEntry, setCopiedServerEntry] = useState(false);
108113
const [copiedServerFile, setCopiedServerFile] = useState(false);
109114
const { toast } = useToast();
@@ -555,6 +560,38 @@ const Sidebar = ({
555560
data-testid="oauth-client-id-input"
556561
className="font-mono"
557562
/>
563+
<label className="text-sm font-medium">
564+
Client Secret
565+
</label>
566+
<div className="flex gap-2">
567+
<Input
568+
type={showClientSecret ? "text" : "password"}
569+
placeholder="Client Secret (optional)"
570+
onChange={(e) => setOauthClientSecret(e.target.value)}
571+
value={oauthClientSecret}
572+
data-testid="oauth-client-secret-input"
573+
className="font-mono"
574+
/>
575+
<Button
576+
variant="outline"
577+
size="icon"
578+
className="h-9 w-9 p-0 shrink-0"
579+
onClick={() => setShowClientSecret(!showClientSecret)}
580+
aria-label={
581+
showClientSecret ? "Hide secret" : "Show secret"
582+
}
583+
aria-pressed={showClientSecret}
584+
title={
585+
showClientSecret ? "Hide secret" : "Show secret"
586+
}
587+
>
588+
{showClientSecret ? (
589+
<Eye className="h-4 w-4" aria-hidden="true" />
590+
) : (
591+
<EyeOff className="h-4 w-4" aria-hidden="true" />
592+
)}
593+
</Button>
594+
</div>
558595
<label className="text-sm font-medium">
559596
Redirect URL
560597
</label>

0 commit comments

Comments
 (0)