Skip to content

Commit 5063880

Browse files
authored
Merge branch 'main' into feature/completions
2 parents d9df5ff + 2788097 commit 5063880

File tree

10 files changed

+612
-68
lines changed

10 files changed

+612
-68
lines changed

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The MCP inspector is a developer tool for testing and debugging MCP servers.
1111
To inspect an MCP server implementation, there's no need to clone this repo. Instead, use `npx`. For example, if your server is built at `build/index.js`:
1212

1313
```bash
14-
npx @modelcontextprotocol/inspector build/index.js
14+
npx @modelcontextprotocol/inspector node build/index.js
1515
```
1616

1717
You can pass both arguments and environment variables to your MCP server. Arguments are passed directly to your server, while environment variables can be set using the `-e` flag:
@@ -21,19 +21,19 @@ You can pass both arguments and environment variables to your MCP server. Argume
2121
npx @modelcontextprotocol/inspector build/index.js arg1 arg2
2222

2323
# Pass environment variables only
24-
npx @modelcontextprotocol/inspector -e KEY=value -e KEY2=$VALUE2 build/index.js
24+
npx @modelcontextprotocol/inspector -e KEY=value -e KEY2=$VALUE2 node build/index.js
2525

2626
# Pass both environment variables and arguments
27-
npx @modelcontextprotocol/inspector -e KEY=value -e KEY2=$VALUE2 build/index.js arg1 arg2
27+
npx @modelcontextprotocol/inspector -e KEY=value -e KEY2=$VALUE2 node build/index.js arg1 arg2
2828

2929
# Use -- to separate inspector flags from server arguments
30-
npx @modelcontextprotocol/inspector -e KEY=$VALUE -- build/index.js -e server-flag
30+
npx @modelcontextprotocol/inspector -e KEY=$VALUE -- node build/index.js -e server-flag
3131
```
3232

3333
The inspector runs both a client UI (default port 5173) and an MCP proxy server (default port 3000). Open the client UI in your browser to use the inspector. You can customize the ports if needed:
3434

3535
```bash
36-
CLIENT_PORT=8080 SERVER_PORT=9000 npx @modelcontextprotocol/inspector build/index.js
36+
CLIENT_PORT=8080 SERVER_PORT=9000 npx @modelcontextprotocol/inspector node build/index.js
3737
```
3838

3939
For more details on ways to use the inspector, see the [Inspector section of the MCP docs site](https://modelcontextprotocol.io/docs/tools/inspector). For help with debugging, see the [Debugging guide](https://modelcontextprotocol.io/docs/tools/debugging).
@@ -48,6 +48,13 @@ Development mode:
4848
npm run dev
4949
```
5050

51+
> **Note for Windows users:**
52+
> On Windows, use the following command instead:
53+
>
54+
> ```bash
55+
> npm run dev:windows
56+
> ```
57+
5158
Production mode:
5259
5360
```bash

client/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,23 @@
2323
"dependencies": {
2424
"@modelcontextprotocol/sdk": "^1.4.1",
2525
"@radix-ui/react-dialog": "^1.1.3",
26+
"@radix-ui/react-checkbox": "^1.1.4",
2627
"@radix-ui/react-icons": "^1.3.0",
2728
"@radix-ui/react-label": "^2.1.0",
2829
"@radix-ui/react-popover": "^1.1.3",
2930
"@radix-ui/react-select": "^2.1.2",
3031
"@radix-ui/react-slot": "^1.1.0",
3132
"@radix-ui/react-tabs": "^1.1.1",
33+
"@types/prismjs": "^1.26.5",
3234
"class-variance-authority": "^0.7.0",
3335
"clsx": "^2.1.1",
3436
"cmdk": "^1.0.4",
3537
"lucide-react": "^0.447.0",
38+
"prismjs": "^1.29.0",
3639
"pkce-challenge": "^4.1.0",
3740
"react": "^18.3.1",
3841
"react-dom": "^18.3.1",
42+
"react-simple-code-editor": "^0.14.1",
3943
"react-toastify": "^10.0.6",
4044
"serve-handler": "^6.1.6",
4145
"tailwind-merge": "^2.5.3",
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
import { useState } from "react";
2+
import { Button } from "@/components/ui/button";
3+
import { Input } from "@/components/ui/input";
4+
import { Label } from "@/components/ui/label";
5+
import JsonEditor from "./JsonEditor";
6+
7+
export type JsonValue =
8+
| string
9+
| number
10+
| boolean
11+
| null
12+
| JsonValue[]
13+
| { [key: string]: JsonValue };
14+
15+
export type JsonSchemaType = {
16+
type: "string" | "number" | "integer" | "boolean" | "array" | "object";
17+
description?: string;
18+
properties?: Record<string, JsonSchemaType>;
19+
items?: JsonSchemaType;
20+
};
21+
22+
type JsonObject = { [key: string]: JsonValue };
23+
24+
interface DynamicJsonFormProps {
25+
schema: JsonSchemaType;
26+
value: JsonValue;
27+
onChange: (value: JsonValue) => void;
28+
maxDepth?: number;
29+
}
30+
31+
const formatFieldLabel = (key: string): string => {
32+
return key
33+
.replace(/([A-Z])/g, " $1") // Insert space before capital letters
34+
.replace(/_/g, " ") // Replace underscores with spaces
35+
.replace(/^\w/, (c) => c.toUpperCase()); // Capitalize first letter
36+
};
37+
38+
const DynamicJsonForm = ({
39+
schema,
40+
value,
41+
onChange,
42+
maxDepth = 3,
43+
}: DynamicJsonFormProps) => {
44+
const [isJsonMode, setIsJsonMode] = useState(false);
45+
const [jsonError, setJsonError] = useState<string>();
46+
47+
const generateDefaultValue = (propSchema: JsonSchemaType): JsonValue => {
48+
switch (propSchema.type) {
49+
case "string":
50+
return "";
51+
case "number":
52+
case "integer":
53+
return 0;
54+
case "boolean":
55+
return false;
56+
case "array":
57+
return [];
58+
case "object": {
59+
const obj: JsonObject = {};
60+
if (propSchema.properties) {
61+
Object.entries(propSchema.properties).forEach(([key, prop]) => {
62+
obj[key] = generateDefaultValue(prop);
63+
});
64+
}
65+
return obj;
66+
}
67+
default:
68+
return null;
69+
}
70+
};
71+
72+
const renderFormFields = (
73+
propSchema: JsonSchemaType,
74+
currentValue: JsonValue,
75+
path: string[] = [],
76+
depth: number = 0,
77+
) => {
78+
if (
79+
depth >= maxDepth &&
80+
(propSchema.type === "object" || propSchema.type === "array")
81+
) {
82+
// Render as JSON editor when max depth is reached
83+
return (
84+
<JsonEditor
85+
value={JSON.stringify(
86+
currentValue ?? generateDefaultValue(propSchema),
87+
null,
88+
2,
89+
)}
90+
onChange={(newValue) => {
91+
try {
92+
const parsed = JSON.parse(newValue);
93+
handleFieldChange(path, parsed);
94+
setJsonError(undefined);
95+
} catch (err) {
96+
setJsonError(err instanceof Error ? err.message : "Invalid JSON");
97+
}
98+
}}
99+
error={jsonError}
100+
/>
101+
);
102+
}
103+
104+
switch (propSchema.type) {
105+
case "string":
106+
case "number":
107+
case "integer":
108+
return (
109+
<Input
110+
type={propSchema.type === "string" ? "text" : "number"}
111+
value={(currentValue as string | number) ?? ""}
112+
onChange={(e) =>
113+
handleFieldChange(
114+
path,
115+
propSchema.type === "string"
116+
? e.target.value
117+
: Number(e.target.value),
118+
)
119+
}
120+
placeholder={propSchema.description}
121+
/>
122+
);
123+
case "boolean":
124+
return (
125+
<Input
126+
type="checkbox"
127+
checked={(currentValue as boolean) ?? false}
128+
onChange={(e) => handleFieldChange(path, e.target.checked)}
129+
className="w-4 h-4"
130+
/>
131+
);
132+
case "object":
133+
if (!propSchema.properties) return null;
134+
return (
135+
<div className="space-y-4 border rounded-md p-4">
136+
{Object.entries(propSchema.properties).map(([key, prop]) => (
137+
<div key={key} className="space-y-2">
138+
<Label>{formatFieldLabel(key)}</Label>
139+
{renderFormFields(
140+
prop,
141+
(currentValue as JsonObject)?.[key],
142+
[...path, key],
143+
depth + 1,
144+
)}
145+
</div>
146+
))}
147+
</div>
148+
);
149+
case "array": {
150+
const arrayValue = Array.isArray(currentValue) ? currentValue : [];
151+
if (!propSchema.items) return null;
152+
return (
153+
<div className="space-y-4">
154+
{propSchema.description && (
155+
<p className="text-sm text-gray-600">{propSchema.description}</p>
156+
)}
157+
158+
{propSchema.items?.description && (
159+
<p className="text-sm text-gray-500">
160+
Items: {propSchema.items.description}
161+
</p>
162+
)}
163+
164+
<div className="space-y-2">
165+
{arrayValue.map((item, index) => (
166+
<div key={index} className="flex items-center gap-2">
167+
{renderFormFields(
168+
propSchema.items as JsonSchemaType,
169+
item,
170+
[...path, index.toString()],
171+
depth + 1,
172+
)}
173+
<Button
174+
variant="outline"
175+
size="sm"
176+
onClick={() => {
177+
const newArray = [...arrayValue];
178+
newArray.splice(index, 1);
179+
handleFieldChange(path, newArray);
180+
}}
181+
>
182+
Remove
183+
</Button>
184+
</div>
185+
))}
186+
<Button
187+
variant="outline"
188+
size="sm"
189+
onClick={() => {
190+
handleFieldChange(path, [
191+
...arrayValue,
192+
generateDefaultValue(propSchema.items as JsonSchemaType),
193+
]);
194+
}}
195+
title={
196+
propSchema.items?.description
197+
? `Add new ${propSchema.items.description}`
198+
: "Add new item"
199+
}
200+
>
201+
Add Item
202+
</Button>
203+
</div>
204+
</div>
205+
);
206+
}
207+
default:
208+
return null;
209+
}
210+
};
211+
212+
const handleFieldChange = (path: string[], fieldValue: JsonValue) => {
213+
if (path.length === 0) {
214+
onChange(fieldValue);
215+
return;
216+
}
217+
218+
const newValue = {
219+
...(typeof value === "object" && value !== null && !Array.isArray(value)
220+
? value
221+
: {}),
222+
} as JsonObject;
223+
let current: JsonObject = newValue;
224+
225+
for (let i = 0; i < path.length - 1; i++) {
226+
const key = path[i];
227+
if (!(key in current)) {
228+
current[key] = {};
229+
}
230+
current = current[key] as JsonObject;
231+
}
232+
233+
current[path[path.length - 1]] = fieldValue;
234+
onChange(newValue);
235+
};
236+
237+
return (
238+
<div className="space-y-4">
239+
<div className="flex justify-end">
240+
<Button
241+
variant="outline"
242+
size="sm"
243+
onClick={() => setIsJsonMode(!isJsonMode)}
244+
>
245+
{isJsonMode ? "Switch to Form" : "Switch to JSON"}
246+
</Button>
247+
</div>
248+
249+
{isJsonMode ? (
250+
<JsonEditor
251+
value={JSON.stringify(value ?? generateDefaultValue(schema), null, 2)}
252+
onChange={(newValue) => {
253+
try {
254+
onChange(JSON.parse(newValue));
255+
setJsonError(undefined);
256+
} catch (err) {
257+
setJsonError(err instanceof Error ? err.message : "Invalid JSON");
258+
}
259+
}}
260+
error={jsonError}
261+
/>
262+
) : (
263+
renderFormFields(schema, value)
264+
)}
265+
</div>
266+
);
267+
};
268+
269+
export default DynamicJsonForm;

0 commit comments

Comments
 (0)