Skip to content

Commit 81c0ef2

Browse files
Merge branch 'main' into main
2 parents 8486696 + 75537c7 commit 81c0ef2

26 files changed

+1156
-176
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Thanks for your interest in contributing! This guide explains how to get involve
77
1. Fork the repository and clone it locally
88
2. Install dependencies with `npm install`
99
3. Run `npm run dev` to start both client and server in development mode
10-
4. Use the web UI at http://localhost:5173 to interact with the inspector
10+
4. Use the web UI at http://127.0.0.1:5173 to interact with the inspector
1111

1212
## Development Process & Pull Requests
1313

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ For more details on ways to use the inspector, see the [Inspector section of the
4242

4343
The inspector supports bearer token authentication for SSE connections. Enter your token in the UI when connecting to an MCP server, and it will be sent in the Authorization header.
4444

45+
### Security Considerations
46+
47+
The MCP Inspector includes a proxy server that can run and communicate with local MCP processes. The proxy server should not be exposed to untrusted networks as it has permissions to spawn local processes and can connect to any specified MCP server.
48+
4549
### From this repository
4650

4751
If you're working on the inspector itself:

bin/cli.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ async function main() {
102102
await Promise.any([server, client, delay(2 * 1000)]);
103103
const portParam = SERVER_PORT === "3000" ? "" : `?proxyPort=${SERVER_PORT}`;
104104
console.log(
105-
`\n🔍 MCP Inspector is up and running at http://localhost:${CLIENT_PORT}${portParam} 🚀`,
105+
`\n🔍 MCP Inspector is up and running at http://127.0.0.1:${CLIENT_PORT}${portParam} 🚀`,
106106
);
107107

108108
try {

client/jest.config.cjs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,12 @@ module.exports = {
33
testEnvironment: "jsdom",
44
moduleNameMapper: {
55
"^@/(.*)$": "<rootDir>/src/$1",
6-
"^../components/DynamicJsonForm$":
7-
"<rootDir>/src/utils/__mocks__/DynamicJsonForm.ts",
8-
"^../../components/DynamicJsonForm$":
9-
"<rootDir>/src/utils/__mocks__/DynamicJsonForm.ts",
6+
"\\.css$": "<rootDir>/src/__mocks__/styleMock.js",
107
},
118
transform: {
129
"^.+\\.tsx?$": [
1310
"ts-jest",
1411
{
15-
useESM: true,
1612
jsx: "react-jsx",
1713
tsconfig: "tsconfig.jest.json",
1814
},

client/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@modelcontextprotocol/inspector-client",
3-
"version": "0.6.0",
3+
"version": "0.7.0",
44
"description": "Client-side application for the Model Context Protocol inspector",
55
"license": "MIT",
66
"author": "Anthropic, PBC (https://anthropic.com)",
@@ -24,8 +24,8 @@
2424
},
2525
"dependencies": {
2626
"@modelcontextprotocol/sdk": "^1.6.1",
27-
"@radix-ui/react-dialog": "^1.1.3",
2827
"@radix-ui/react-checkbox": "^1.1.4",
28+
"@radix-ui/react-dialog": "^1.1.3",
2929
"@radix-ui/react-icons": "^1.3.0",
3030
"@radix-ui/react-label": "^2.1.0",
3131
"@radix-ui/react-popover": "^1.1.3",
@@ -38,7 +38,7 @@
3838
"cmdk": "^1.0.4",
3939
"lucide-react": "^0.447.0",
4040
"pkce-challenge": "^4.1.0",
41-
"prismjs": "^1.29.0",
41+
"prismjs": "^1.30.0",
4242
"react": "^18.3.1",
4343
"react-dom": "^18.3.1",
4444
"react-simple-code-editor": "^0.14.1",
@@ -50,6 +50,8 @@
5050
},
5151
"devDependencies": {
5252
"@eslint/js": "^9.11.1",
53+
"@testing-library/jest-dom": "^6.6.3",
54+
"@testing-library/react": "^16.2.0",
5355
"@types/jest": "^29.5.14",
5456
"@types/node": "^22.7.5",
5557
"@types/react": "^18.3.10",

client/src/App.tsx

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,6 @@ const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1";
5555

5656
const App = () => {
5757
// Handle OAuth callback route
58-
if (window.location.pathname === "/oauth/callback") {
59-
const OAuthCallback = React.lazy(
60-
() => import("./components/OAuthCallback"),
61-
);
62-
return (
63-
<Suspense fallback={<div>Loading...</div>}>
64-
<OAuthCallback />
65-
</Suspense>
66-
);
67-
}
6858
const [resources, setResources] = useState<Resource[]>([]);
6959
const [resourceTemplates, setResourceTemplates] = useState<
7060
ResourceTemplate[]
@@ -122,22 +112,6 @@ const App = () => {
122112
const nextRequestId = useRef(0);
123113
const rootsRef = useRef<Root[]>([]);
124114

125-
const handleApproveSampling = (id: number, result: CreateMessageResult) => {
126-
setPendingSampleRequests((prev) => {
127-
const request = prev.find((r) => r.id === id);
128-
request?.resolve(result);
129-
return prev.filter((r) => r.id !== id);
130-
});
131-
};
132-
133-
const handleRejectSampling = (id: number) => {
134-
setPendingSampleRequests((prev) => {
135-
const request = prev.find((r) => r.id === id);
136-
request?.reject(new Error("Sampling request rejected"));
137-
return prev.filter((r) => r.id !== id);
138-
});
139-
};
140-
141115
const [selectedResource, setSelectedResource] = useState<Resource | null>(
142116
null,
143117
);
@@ -237,7 +211,7 @@ const App = () => {
237211
// Connect to the server
238212
connectMcpServer();
239213
}
240-
}, []);
214+
}, [connectMcpServer]);
241215

242216
useEffect(() => {
243217
fetch(`${PROXY_SERVER_URL}/config`)
@@ -266,6 +240,22 @@ const App = () => {
266240
}
267241
}, []);
268242

243+
const handleApproveSampling = (id: number, result: CreateMessageResult) => {
244+
setPendingSampleRequests((prev) => {
245+
const request = prev.find((r) => r.id === id);
246+
request?.resolve(result);
247+
return prev.filter((r) => r.id !== id);
248+
});
249+
};
250+
251+
const handleRejectSampling = (id: number) => {
252+
setPendingSampleRequests((prev) => {
253+
const request = prev.find((r) => r.id === id);
254+
request?.reject(new Error("Sampling request rejected"));
255+
return prev.filter((r) => r.id !== id);
256+
});
257+
};
258+
269259
const clearError = (tabKey: keyof typeof errors) => {
270260
setErrors((prev) => ({ ...prev, [tabKey]: null }));
271261
};
@@ -438,6 +428,17 @@ const App = () => {
438428
setLogLevel(level);
439429
};
440430

431+
if (window.location.pathname === "/oauth/callback") {
432+
const OAuthCallback = React.lazy(
433+
() => import("./components/OAuthCallback"),
434+
);
435+
return (
436+
<Suspense fallback={<div>Loading...</div>}>
437+
<OAuthCallback />
438+
</Suspense>
439+
);
440+
}
441+
441442
return (
442443
<div className="flex h-screen bg-background">
443444
<Sidebar

client/src/__mocks__/styleMock.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = {};

client/src/components/DynamicJsonForm.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,21 @@ const DynamicJsonForm = ({
108108
}
109109
};
110110

111+
const formatJson = () => {
112+
try {
113+
const jsonStr = rawJsonValue.trim();
114+
if (!jsonStr) {
115+
return;
116+
}
117+
const formatted = JSON.stringify(JSON.parse(jsonStr), null, 2);
118+
setRawJsonValue(formatted);
119+
debouncedUpdateParent(formatted);
120+
setJsonError(undefined);
121+
} catch (err) {
122+
setJsonError(err instanceof Error ? err.message : "Invalid JSON");
123+
}
124+
};
125+
111126
const renderFormFields = (
112127
propSchema: JsonSchemaType,
113128
currentValue: JsonValue,
@@ -353,7 +368,12 @@ const DynamicJsonForm = ({
353368

354369
return (
355370
<div className="space-y-4">
356-
<div className="flex justify-end">
371+
<div className="flex justify-end space-x-2">
372+
{isJsonMode && (
373+
<Button variant="outline" size="sm" onClick={formatJson}>
374+
Format JSON
375+
</Button>
376+
)}
357377
<Button variant="outline" size="sm" onClick={handleSwitchToFormMode}>
358378
{isJsonMode ? "Switch to Form" : "Switch to JSON"}
359379
</Button>

client/src/components/History.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
22
import { Copy } from "lucide-react";
33
import { useState } from "react";
4+
import JsonView from "./JsonView";
45

56
const HistoryAndNotifications = ({
67
requestHistory,
@@ -74,9 +75,9 @@ const HistoryAndNotifications = ({
7475
<Copy size={16} />
7576
</button>
7677
</div>
77-
<pre className="whitespace-pre-wrap break-words bg-background p-2 rounded">
78-
{JSON.stringify(JSON.parse(request.request), null, 2)}
79-
</pre>
78+
<div className="bg-background p-2 rounded">
79+
<JsonView data={request.request} />
80+
</div>
8081
</div>
8182
{request.response && (
8283
<div className="mt-2">
@@ -91,13 +92,9 @@ const HistoryAndNotifications = ({
9192
<Copy size={16} />
9293
</button>
9394
</div>
94-
<pre className="whitespace-pre-wrap break-words bg-background p-2 rounded">
95-
{JSON.stringify(
96-
JSON.parse(request.response),
97-
null,
98-
2,
99-
)}
100-
</pre>
95+
<div className="bg-background p-2 rounded">
96+
<JsonView data={request.response} />
97+
</div>
10198
</div>
10299
)}
103100
</>
@@ -146,9 +143,11 @@ const HistoryAndNotifications = ({
146143
<Copy size={16} />
147144
</button>
148145
</div>
149-
<pre className="whitespace-pre-wrap break-words bg-background p-2 rounded">
150-
{JSON.stringify(notification, null, 2)}
151-
</pre>
146+
<div className="bg-background p-2 rounded">
147+
<JsonView
148+
data={JSON.stringify(notification, null, 2)}
149+
/>
150+
</div>
152151
</div>
153152
)}
154153
</li>

client/src/components/JsonEditor.tsx

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import Editor from "react-simple-code-editor";
33
import Prism from "prismjs";
44
import "prismjs/components/prism-json";
55
import "prismjs/themes/prism.css";
6-
import { Button } from "@/components/ui/button";
76

87
interface JsonEditorProps {
98
value: string;
@@ -16,49 +15,25 @@ const JsonEditor = ({
1615
onChange,
1716
error: externalError,
1817
}: JsonEditorProps) => {
19-
const [editorContent, setEditorContent] = useState(value);
18+
const [editorContent, setEditorContent] = useState(value || "");
2019
const [internalError, setInternalError] = useState<string | undefined>(
2120
undefined,
2221
);
2322

2423
useEffect(() => {
25-
setEditorContent(value);
24+
setEditorContent(value || "");
2625
}, [value]);
2726

28-
const formatJson = (json: string): string => {
29-
try {
30-
return JSON.stringify(JSON.parse(json), null, 2);
31-
} catch {
32-
return json;
33-
}
34-
};
35-
3627
const handleEditorChange = (newContent: string) => {
3728
setEditorContent(newContent);
3829
setInternalError(undefined);
3930
onChange(newContent);
4031
};
4132

42-
const handleFormatJson = () => {
43-
try {
44-
const formatted = formatJson(editorContent);
45-
setEditorContent(formatted);
46-
onChange(formatted);
47-
setInternalError(undefined);
48-
} catch (err) {
49-
setInternalError(err instanceof Error ? err.message : "Invalid JSON");
50-
}
51-
};
52-
5333
const displayError = internalError || externalError;
5434

5535
return (
56-
<div className="relative space-y-2">
57-
<div className="flex justify-end">
58-
<Button variant="outline" size="sm" onClick={handleFormatJson}>
59-
Format JSON
60-
</Button>
61-
</div>
36+
<div className="relative">
6237
<div
6338
className={`border rounded-md ${
6439
displayError

0 commit comments

Comments
 (0)