Skip to content

Commit d0622d3

Browse files
authored
Merge branch 'main' into main
2 parents c4cc414 + 8f7680d commit d0622d3

22 files changed

+362
-139
lines changed

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@modelcontextprotocol/inspector-client",
3-
"version": "0.8.2",
3+
"version": "0.9.0",
44
"description": "Client-side application for the Model Context Protocol inspector",
55
"license": "MIT",
66
"author": "Anthropic, PBC (https://anthropic.com)",

client/src/components/DynamicJsonForm.tsx

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,9 @@ import { Button } from "@/components/ui/button";
33
import { Input } from "@/components/ui/input";
44
import { Label } from "@/components/ui/label";
55
import JsonEditor from "./JsonEditor";
6-
import { updateValueAtPath, JsonObject } from "@/utils/jsonPathUtils";
6+
import { updateValueAtPath } from "@/utils/jsonUtils";
77
import { generateDefaultValue, formatFieldLabel } from "@/utils/schemaUtils";
8-
9-
export type JsonValue =
10-
| string
11-
| number
12-
| boolean
13-
| null
14-
| undefined
15-
| JsonValue[]
16-
| { [key: string]: JsonValue };
17-
18-
export type JsonSchemaType = {
19-
type:
20-
| "string"
21-
| "number"
22-
| "integer"
23-
| "boolean"
24-
| "array"
25-
| "object"
26-
| "null";
27-
description?: string;
28-
required?: boolean;
29-
default?: JsonValue;
30-
properties?: Record<string, JsonSchemaType>;
31-
items?: JsonSchemaType;
32-
};
8+
import type { JsonValue, JsonSchemaType, JsonObject } from "@/utils/jsonUtils";
339

3410
interface DynamicJsonFormProps {
3511
schema: JsonSchemaType;

client/src/components/JsonView.tsx

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,18 @@
11
import { useState, memo, useMemo, useCallback, useEffect } from "react";
2-
import { JsonValue } from "./DynamicJsonForm";
2+
import type { JsonValue } from "@/utils/jsonUtils";
33
import clsx from "clsx";
44
import { Copy, CheckCheck } from "lucide-react";
55
import { Button } from "@/components/ui/button";
66
import { useToast } from "@/hooks/use-toast";
7+
import { getDataType, tryParseJson } from "@/utils/jsonUtils";
78

89
interface JsonViewProps {
910
data: unknown;
1011
name?: string;
1112
initialExpandDepth?: number;
1213
className?: string;
1314
withCopyButton?: boolean;
14-
}
15-
16-
function tryParseJson(str: string): { success: boolean; data: JsonValue } {
17-
const trimmed = str.trim();
18-
if (
19-
!(trimmed.startsWith("{") && trimmed.endsWith("}")) &&
20-
!(trimmed.startsWith("[") && trimmed.endsWith("]"))
21-
) {
22-
return { success: false, data: str };
23-
}
24-
try {
25-
return { success: true, data: JSON.parse(str) };
26-
} catch {
27-
return { success: false, data: str };
28-
}
15+
isError?: boolean;
2916
}
3017

3118
const JsonView = memo(
@@ -35,6 +22,7 @@ const JsonView = memo(
3522
initialExpandDepth = 3,
3623
className,
3724
withCopyButton = true,
25+
isError = false,
3826
}: JsonViewProps) => {
3927
const { toast } = useToast();
4028
const [copied, setCopied] = useState(false);
@@ -100,6 +88,7 @@ const JsonView = memo(
10088
name={name}
10189
depth={0}
10290
initialExpandDepth={initialExpandDepth}
91+
isError={isError}
10392
/>
10493
</div>
10594
</div>
@@ -114,28 +103,28 @@ interface JsonNodeProps {
114103
name?: string;
115104
depth: number;
116105
initialExpandDepth: number;
106+
isError?: boolean;
117107
}
118108

119109
const JsonNode = memo(
120-
({ data, name, depth = 0, initialExpandDepth }: JsonNodeProps) => {
110+
({
111+
data,
112+
name,
113+
depth = 0,
114+
initialExpandDepth,
115+
isError = false,
116+
}: JsonNodeProps) => {
121117
const [isExpanded, setIsExpanded] = useState(depth < initialExpandDepth);
122-
123-
const getDataType = (value: JsonValue): string => {
124-
if (Array.isArray(value)) return "array";
125-
if (value === null) return "null";
126-
return typeof value;
127-
};
128-
129-
const dataType = getDataType(data);
130-
131-
const typeStyleMap: Record<string, string> = {
118+
const [typeStyleMap] = useState<Record<string, string>>({
132119
number: "text-blue-600",
133120
boolean: "text-amber-600",
134121
null: "text-purple-600",
135122
undefined: "text-gray-600",
136-
string: "text-green-600 break-all whitespace-pre-wrap",
123+
string: "text-green-600 group-hover:text-green-500",
124+
error: "text-red-600 group-hover:text-red-500",
137125
default: "text-gray-700",
138-
};
126+
});
127+
const dataType = getDataType(data);
139128

140129
const renderCollapsible = (isArray: boolean) => {
141130
const items = isArray
@@ -236,7 +225,14 @@ const JsonNode = memo(
236225
{name}:
237226
</span>
238227
)}
239-
<pre className={typeStyleMap.string}>"{value}"</pre>
228+
<pre
229+
className={clsx(
230+
typeStyleMap.string,
231+
"break-all whitespace-pre-wrap",
232+
)}
233+
>
234+
"{value}"
235+
</pre>
240236
</div>
241237
);
242238
}
@@ -250,8 +246,8 @@ const JsonNode = memo(
250246
)}
251247
<pre
252248
className={clsx(
253-
typeStyleMap.string,
254-
"cursor-pointer group-hover:text-green-500",
249+
isError ? typeStyleMap.error : typeStyleMap.string,
250+
"cursor-pointer break-all whitespace-pre-wrap",
255251
)}
256252
onClick={() => setIsExpanded(!isExpanded)}
257253
title={isExpanded ? "Click to collapse" : "Click to expand"}

client/src/components/ListPane.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const ListPane = <T extends object>({
2222
isButtonDisabled,
2323
}: ListPaneProps<T>) => (
2424
<div className="bg-card rounded-lg shadow">
25-
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
25+
<div className="p-4 border-b border-gray-200 dark:border-gray-800">
2626
<h3 className="font-semibold dark:text-white">{title}</h3>
2727
</div>
2828
<div className="p-4">

client/src/components/PromptsTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ const PromptsTab = ({
108108
/>
109109

110110
<div className="bg-card rounded-lg shadow">
111-
<div className="p-4 border-b border-gray-200">
111+
<div className="p-4 border-b border-gray-200 dark:border-gray-800">
112112
<h3 className="font-semibold">
113113
{selectedPrompt ? selectedPrompt.name : "Select a prompt"}
114114
</h3>

client/src/components/ResourcesTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ const ResourcesTab = ({
162162
/>
163163

164164
<div className="bg-card rounded-lg shadow">
165-
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
165+
<div className="p-4 border-b border-gray-200 dark:border-gray-800 flex justify-between items-center">
166166
<h3
167167
className="font-semibold truncate"
168168
title={selectedResource?.name || selectedTemplate?.name}

client/src/components/Sidebar.tsx

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ const Sidebar = ({
9494

9595
return (
9696
<div className="w-80 bg-card border-r border-border flex flex-col h-full">
97-
<div className="flex items-center justify-between p-4 border-b border-gray-200">
97+
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800">
9898
<div className="flex items-center">
9999
<h1 className="ml-2 text-lg font-semibold">
100100
MCP Inspector v{version}
@@ -105,14 +105,19 @@ const Sidebar = ({
105105
<div className="p-4 flex-1 overflow-auto">
106106
<div className="space-y-4">
107107
<div className="space-y-2">
108-
<label className="text-sm font-medium">Transport Type</label>
108+
<label
109+
className="text-sm font-medium"
110+
htmlFor="transport-type-select"
111+
>
112+
Transport Type
113+
</label>
109114
<Select
110115
value={transportType}
111116
onValueChange={(value: "stdio" | "sse") =>
112117
setTransportType(value)
113118
}
114119
>
115-
<SelectTrigger>
120+
<SelectTrigger id="transport-type-select">
116121
<SelectValue placeholder="Select transport type" />
117122
</SelectTrigger>
118123
<SelectContent>
@@ -125,17 +130,26 @@ const Sidebar = ({
125130
{transportType === "stdio" ? (
126131
<>
127132
<div className="space-y-2">
128-
<label className="text-sm font-medium">Command</label>
133+
<label className="text-sm font-medium" htmlFor="command-input">
134+
Command
135+
</label>
129136
<Input
137+
id="command-input"
130138
placeholder="Command"
131139
value={command}
132140
onChange={(e) => setCommand(e.target.value)}
133141
className="font-mono"
134142
/>
135143
</div>
136144
<div className="space-y-2">
137-
<label className="text-sm font-medium">Arguments</label>
145+
<label
146+
className="text-sm font-medium"
147+
htmlFor="arguments-input"
148+
>
149+
Arguments
150+
</label>
138151
<Input
152+
id="arguments-input"
139153
placeholder="Arguments (space-separated)"
140154
value={args}
141155
onChange={(e) => setArgs(e.target.value)}
@@ -146,8 +160,11 @@ const Sidebar = ({
146160
) : (
147161
<>
148162
<div className="space-y-2">
149-
<label className="text-sm font-medium">URL</label>
163+
<label className="text-sm font-medium" htmlFor="sse-url-input">
164+
URL
165+
</label>
150166
<Input
167+
id="sse-url-input"
151168
placeholder="URL"
152169
value={sseUrl}
153170
onChange={(e) => setSseUrl(e.target.value)}
@@ -159,6 +176,7 @@ const Sidebar = ({
159176
variant="outline"
160177
onClick={() => setShowBearerToken(!showBearerToken)}
161178
className="flex items-center w-full"
179+
aria-expanded={showBearerToken}
162180
>
163181
{showBearerToken ? (
164182
<ChevronDown className="w-4 h-4 mr-2" />
@@ -169,8 +187,14 @@ const Sidebar = ({
169187
</Button>
170188
{showBearerToken && (
171189
<div className="space-y-2">
172-
<label className="text-sm font-medium">Bearer Token</label>
190+
<label
191+
className="text-sm font-medium"
192+
htmlFor="bearer-token-input"
193+
>
194+
Bearer Token
195+
</label>
173196
<Input
197+
id="bearer-token-input"
174198
placeholder="Bearer Token"
175199
value={bearerToken}
176200
onChange={(e) => setBearerToken(e.target.value)}
@@ -189,6 +213,7 @@ const Sidebar = ({
189213
onClick={() => setShowEnvVars(!showEnvVars)}
190214
className="flex items-center w-full"
191215
data-testid="env-vars-button"
216+
aria-expanded={showEnvVars}
192217
>
193218
{showEnvVars ? (
194219
<ChevronDown className="w-4 h-4 mr-2" />
@@ -203,6 +228,7 @@ const Sidebar = ({
203228
<div key={idx} className="space-y-2 pb-4">
204229
<div className="flex gap-2">
205230
<Input
231+
aria-label={`Environment variable key ${idx + 1}`}
206232
placeholder="Key"
207233
value={key}
208234
onChange={(e) => {
@@ -245,6 +271,7 @@ const Sidebar = ({
245271
</div>
246272
<div className="flex gap-2">
247273
<Input
274+
aria-label={`Environment variable value ${idx + 1}`}
248275
type={shownEnvVars.has(key) ? "text" : "password"}
249276
placeholder="Value"
250277
value={value}
@@ -311,6 +338,7 @@ const Sidebar = ({
311338
onClick={() => setShowConfig(!showConfig)}
312339
className="flex items-center w-full"
313340
data-testid="config-button"
341+
aria-expanded={showConfig}
314342
>
315343
{showConfig ? (
316344
<ChevronDown className="w-4 h-4 mr-2" />
@@ -327,7 +355,10 @@ const Sidebar = ({
327355
return (
328356
<div key={key} className="space-y-2">
329357
<div className="flex items-center gap-1">
330-
<label className="text-sm font-medium text-green-600 break-all">
358+
<label
359+
className="text-sm font-medium text-green-600 break-all"
360+
htmlFor={`${configKey}-input`}
361+
>
331362
{configItem.label}
332363
</label>
333364
<Tooltip>
@@ -341,6 +372,7 @@ const Sidebar = ({
341372
</div>
342373
{typeof configItem.value === "number" ? (
343374
<Input
375+
id={`${configKey}-input`}
344376
type="number"
345377
data-testid={`${configKey}-input`}
346378
value={configItem.value}
@@ -367,7 +399,7 @@ const Sidebar = ({
367399
setConfig(newConfig);
368400
}}
369401
>
370-
<SelectTrigger>
402+
<SelectTrigger id={`${configKey}-input`}>
371403
<SelectValue />
372404
</SelectTrigger>
373405
<SelectContent>
@@ -377,6 +409,7 @@ const Sidebar = ({
377409
</Select>
378410
) : (
379411
<Input
412+
id={`${configKey}-input`}
380413
data-testid={`${configKey}-input`}
381414
value={configItem.value}
382415
onChange={(e) => {
@@ -400,7 +433,13 @@ const Sidebar = ({
400433
<div className="space-y-2">
401434
{connectionStatus === "connected" && (
402435
<div className="grid grid-cols-2 gap-4">
403-
<Button data-testid="connect-button" onClick={onConnect}>
436+
<Button
437+
data-testid="connect-button"
438+
onClick={() => {
439+
onDisconnect();
440+
onConnect();
441+
}}
442+
>
404443
<RotateCcw className="w-4 h-4 mr-2" />
405444
{transportType === "stdio" ? "Restart" : "Reconnect"}
406445
</Button>
@@ -450,14 +489,19 @@ const Sidebar = ({
450489

451490
{loggingSupported && connectionStatus === "connected" && (
452491
<div className="space-y-2">
453-
<label className="text-sm font-medium">Logging Level</label>
492+
<label
493+
className="text-sm font-medium"
494+
htmlFor="logging-level-select"
495+
>
496+
Logging Level
497+
</label>
454498
<Select
455499
value={logLevel}
456500
onValueChange={(value: LoggingLevel) =>
457501
sendLogLevelRequest(value)
458502
}
459503
>
460-
<SelectTrigger>
504+
<SelectTrigger id="logging-level-select">
461505
<SelectValue placeholder="Select logging level" />
462506
</SelectTrigger>
463507
<SelectContent>

0 commit comments

Comments
 (0)