Skip to content

Commit 1d629d7

Browse files
committed
Merge branch 'main' into dockerfile
2 parents 6ee7627 + 4053aa1 commit 1d629d7

27 files changed

+2758
-4249
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
The MCP inspector is a developer tool for testing and debugging MCP servers.
44

5-
![MCP Inspector Screenshot](mcp-inspector.png)
5+
![MCP Inspector Screenshot](https://raw.githubusercontent.com/modelcontextprotocol/inspector/main/mcp-inspector.png)
66

77
## Running the Inspector
88

@@ -53,6 +53,7 @@ The MCP Inspector supports the following configuration settings. To change them
5353
| Name | Purpose | Default Value |
5454
| -------------------------- | ----------------------------------------------------------------------------------------- | ------------- |
5555
| MCP_SERVER_REQUEST_TIMEOUT | Maximum time in milliseconds to wait for a response from the MCP server before timing out | 10000 |
56+
| MCP_PROXY_FULL_ADDRESS | The full URL of the MCP Inspector proxy server (e.g. `http://10.2.1.14:2277`) | `null` |
5657

5758
### From this repository
5859

client/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@modelcontextprotocol/inspector-client",
3-
"version": "0.7.0",
3+
"version": "0.8.2",
44
"description": "Client-side application for the Model Context Protocol inspector",
55
"license": "MIT",
66
"author": "Anthropic, PBC (https://anthropic.com)",
@@ -23,7 +23,7 @@
2323
"test:watch": "jest --config jest.config.cjs --watch"
2424
},
2525
"dependencies": {
26-
"@modelcontextprotocol/sdk": "^1.6.1",
26+
"@modelcontextprotocol/sdk": "^1.9.0",
2727
"@radix-ui/react-checkbox": "^1.1.4",
2828
"@radix-ui/react-dialog": "^1.1.3",
2929
"@radix-ui/react-icons": "^1.3.0",
@@ -32,7 +32,8 @@
3232
"@radix-ui/react-select": "^2.1.2",
3333
"@radix-ui/react-slot": "^1.1.0",
3434
"@radix-ui/react-tabs": "^1.1.1",
35-
"@types/prismjs": "^1.26.5",
35+
"@radix-ui/react-toast": "^1.2.6",
36+
"@radix-ui/react-tooltip": "^1.1.8",
3637
"class-variance-authority": "^0.7.0",
3738
"clsx": "^2.1.1",
3839
"cmdk": "^1.0.4",
@@ -42,7 +43,6 @@
4243
"react": "^18.3.1",
4344
"react-dom": "^18.3.1",
4445
"react-simple-code-editor": "^0.14.1",
45-
"react-toastify": "^10.0.6",
4646
"serve-handler": "^6.1.6",
4747
"tailwind-merge": "^2.5.3",
4848
"tailwindcss-animate": "^1.0.7",
@@ -54,6 +54,7 @@
5454
"@testing-library/react": "^16.2.0",
5555
"@types/jest": "^29.5.14",
5656
"@types/node": "^22.7.5",
57+
"@types/prismjs": "^1.26.5",
5758
"@types/react": "^18.3.10",
5859
"@types/react-dom": "^18.3.0",
5960
"@types/serve-handler": "^6.1.4",

client/src/App.tsx

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
import React, { Suspense, useEffect, useRef, useState } from "react";
2121
import { useConnection } from "./lib/hooks/useConnection";
2222
import { useDraggablePane } from "./lib/hooks/useDraggablePane";
23-
2423
import { StdErrNotification } from "./lib/notificationTypes";
2524

2625
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -33,7 +32,6 @@ import {
3332
MessageSquare,
3433
} from "lucide-react";
3534

36-
import { toast } from "react-toastify";
3735
import { z } from "zod";
3836
import "./App.css";
3937
import ConsoleTab from "./components/ConsoleTab";
@@ -47,13 +45,17 @@ import Sidebar from "./components/Sidebar";
4745
import ToolsTab from "./components/ToolsTab";
4846
import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants";
4947
import { InspectorConfig } from "./lib/configurationTypes";
48+
import {
49+
getMCPProxyAddress,
50+
getMCPServerRequestTimeout,
51+
} from "./utils/configUtils";
52+
import { useToast } from "@/hooks/use-toast";
5053

5154
const params = new URLSearchParams(window.location.search);
52-
const PROXY_PORT = params.get("proxyPort") ?? "6277";
53-
const PROXY_SERVER_URL = `http://${window.location.hostname}:${PROXY_PORT}`;
5455
const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1";
5556

5657
const App = () => {
58+
const { toast } = useToast();
5759
// Handle OAuth callback route
5860
const [resources, setResources] = useState<Resource[]>([]);
5961
const [resourceTemplates, setResourceTemplates] = useState<
@@ -95,7 +97,13 @@ const App = () => {
9597

9698
const [config, setConfig] = useState<InspectorConfig>(() => {
9799
const savedConfig = localStorage.getItem(CONFIG_LOCAL_STORAGE_KEY);
98-
return savedConfig ? JSON.parse(savedConfig) : DEFAULT_INSPECTOR_CONFIG;
100+
if (savedConfig) {
101+
return {
102+
...DEFAULT_INSPECTOR_CONFIG,
103+
...JSON.parse(savedConfig),
104+
} as InspectorConfig;
105+
}
106+
return DEFAULT_INSPECTOR_CONFIG;
99107
});
100108
const [bearerToken, setBearerToken] = useState<string>(() => {
101109
return localStorage.getItem("lastBearerToken") || "";
@@ -145,15 +153,16 @@ const App = () => {
145153
handleCompletion,
146154
completionsSupported,
147155
connect: connectMcpServer,
156+
disconnect: disconnectMcpServer,
148157
} = useConnection({
149158
transportType,
150159
command,
151160
args,
152161
sseUrl,
153162
env,
154163
bearerToken,
155-
proxyServerUrl: PROXY_SERVER_URL,
156-
requestTimeout: config.MCP_SERVER_REQUEST_TIMEOUT.value as number,
164+
proxyServerUrl: getMCPProxyAddress(config),
165+
requestTimeout: getMCPServerRequestTimeout(config),
157166
onNotification: (notification) => {
158167
setNotifications((prev) => [...prev, notification as ServerNotification]);
159168
},
@@ -196,8 +205,13 @@ const App = () => {
196205
localStorage.setItem(CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config));
197206
}, [config]);
198207

208+
const hasProcessedRef = useRef(false);
199209
// Auto-connect if serverUrl is provided in URL params (e.g. after OAuth callback)
200210
useEffect(() => {
211+
if (hasProcessedRef.current) {
212+
// Only try to connect once
213+
return;
214+
}
201215
const serverUrl = params.get("serverUrl");
202216
if (serverUrl) {
203217
setSseUrl(serverUrl);
@@ -207,14 +221,18 @@ const App = () => {
207221
newUrl.searchParams.delete("serverUrl");
208222
window.history.replaceState({}, "", newUrl.toString());
209223
// Show success toast for OAuth
210-
toast.success("Successfully authenticated with OAuth");
224+
toast({
225+
title: "Success",
226+
description: "Successfully authenticated with OAuth",
227+
});
228+
hasProcessedRef.current = true;
211229
// Connect to the server
212230
connectMcpServer();
213231
}
214-
}, [connectMcpServer]);
232+
}, [connectMcpServer, toast]);
215233

216234
useEffect(() => {
217-
fetch(`${PROXY_SERVER_URL}/config`)
235+
fetch(`${getMCPProxyAddress(config)}/config`)
218236
.then((response) => response.json())
219237
.then((data) => {
220238
setEnv(data.defaultEnvironment);
@@ -228,6 +246,7 @@ const App = () => {
228246
.catch((error) =>
229247
console.error("Error fetching default environment:", error),
230248
);
249+
// eslint-disable-next-line react-hooks/exhaustive-deps
231250
}, []);
232251

233252
useEffect(() => {
@@ -458,6 +477,7 @@ const App = () => {
458477
bearerToken={bearerToken}
459478
setBearerToken={setBearerToken}
460479
onConnect={connectMcpServer}
480+
onDisconnect={disconnectMcpServer}
461481
stdErrNotifications={stdErrNotifications}
462482
logLevel={logLevel}
463483
sendLogLevelRequest={sendLogLevelRequest}

client/src/components/History.tsx

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
2-
import { Copy } from "lucide-react";
32
import { useState } from "react";
43
import JsonView from "./JsonView";
54

@@ -25,10 +24,6 @@ const HistoryAndNotifications = ({
2524
setExpandedNotifications((prev) => ({ ...prev, [index]: !prev[index] }));
2625
};
2726

28-
const copyToClipboard = (text: string) => {
29-
navigator.clipboard.writeText(text);
30-
};
31-
3227
return (
3328
<div className="bg-card overflow-hidden flex h-full">
3429
<div className="flex-1 overflow-y-auto p-4 border-r">
@@ -68,33 +63,24 @@ const HistoryAndNotifications = ({
6863
<span className="font-semibold text-blue-600">
6964
Request:
7065
</span>
71-
<button
72-
onClick={() => copyToClipboard(request.request)}
73-
className="text-blue-500 hover:text-blue-700"
74-
>
75-
<Copy size={16} />
76-
</button>
77-
</div>
78-
<div className="bg-background p-2 rounded">
79-
<JsonView data={request.request} />
8066
</div>
67+
68+
<JsonView
69+
data={request.request}
70+
className="bg-background"
71+
/>
8172
</div>
8273
{request.response && (
8374
<div className="mt-2">
8475
<div className="flex justify-between items-center mb-1">
8576
<span className="font-semibold text-green-600">
8677
Response:
8778
</span>
88-
<button
89-
onClick={() => copyToClipboard(request.response!)}
90-
className="text-blue-500 hover:text-blue-700"
91-
>
92-
<Copy size={16} />
93-
</button>
94-
</div>
95-
<div className="bg-background p-2 rounded">
96-
<JsonView data={request.response} />
9779
</div>
80+
<JsonView
81+
data={request.response}
82+
className="bg-background"
83+
/>
9884
</div>
9985
)}
10086
</>
@@ -134,20 +120,11 @@ const HistoryAndNotifications = ({
134120
<span className="font-semibold text-purple-600">
135121
Details:
136122
</span>
137-
<button
138-
onClick={() =>
139-
copyToClipboard(JSON.stringify(notification))
140-
}
141-
className="text-blue-500 hover:text-blue-700"
142-
>
143-
<Copy size={16} />
144-
</button>
145-
</div>
146-
<div className="bg-background p-2 rounded">
147-
<JsonView
148-
data={JSON.stringify(notification, null, 2)}
149-
/>
150123
</div>
124+
<JsonView
125+
data={JSON.stringify(notification, null, 2)}
126+
className="bg-background"
127+
/>
151128
</div>
152129
)}
153130
</li>

client/src/components/JsonView.tsx

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
import { useState, memo } from "react";
1+
import { useState, memo, useMemo, useCallback, useEffect } from "react";
22
import { JsonValue } from "./DynamicJsonForm";
33
import clsx from "clsx";
4+
import { Copy, CheckCheck } from "lucide-react";
5+
import { Button } from "@/components/ui/button";
6+
import { useToast } from "@/hooks/use-toast";
47

58
interface JsonViewProps {
69
data: unknown;
710
name?: string;
811
initialExpandDepth?: number;
12+
className?: string;
13+
withCopyButton?: boolean;
914
}
1015

1116
function tryParseJson(str: string): { success: boolean; data: JsonValue } {
@@ -24,22 +29,79 @@ function tryParseJson(str: string): { success: boolean; data: JsonValue } {
2429
}
2530

2631
const JsonView = memo(
27-
({ data, name, initialExpandDepth = 3 }: JsonViewProps) => {
28-
const normalizedData =
29-
typeof data === "string"
32+
({
33+
data,
34+
name,
35+
initialExpandDepth = 3,
36+
className,
37+
withCopyButton = true,
38+
}: JsonViewProps) => {
39+
const { toast } = useToast();
40+
const [copied, setCopied] = useState(false);
41+
42+
useEffect(() => {
43+
let timeoutId: NodeJS.Timeout;
44+
if (copied) {
45+
timeoutId = setTimeout(() => {
46+
setCopied(false);
47+
}, 500);
48+
}
49+
return () => {
50+
if (timeoutId) {
51+
clearTimeout(timeoutId);
52+
}
53+
};
54+
}, [copied]);
55+
56+
const normalizedData = useMemo(() => {
57+
return typeof data === "string"
3058
? tryParseJson(data).success
3159
? tryParseJson(data).data
3260
: data
3361
: data;
62+
}, [data]);
63+
64+
const handleCopy = useCallback(() => {
65+
try {
66+
navigator.clipboard.writeText(
67+
typeof normalizedData === "string"
68+
? normalizedData
69+
: JSON.stringify(normalizedData, null, 2),
70+
);
71+
setCopied(true);
72+
} catch (error) {
73+
toast({
74+
title: "Error",
75+
description: `There was an error coping result into the clipboard: ${error instanceof Error ? error.message : String(error)}`,
76+
variant: "destructive",
77+
});
78+
}
79+
}, [toast, normalizedData]);
3480

3581
return (
36-
<div className="font-mono text-sm transition-all duration-300 ">
37-
<JsonNode
38-
data={normalizedData as JsonValue}
39-
name={name}
40-
depth={0}
41-
initialExpandDepth={initialExpandDepth}
42-
/>
82+
<div className={clsx("p-4 border rounded relative", className)}>
83+
{withCopyButton && (
84+
<Button
85+
size="icon"
86+
variant="ghost"
87+
className="absolute top-2 right-2"
88+
onClick={handleCopy}
89+
>
90+
{copied ? (
91+
<CheckCheck className="size-4 dark:text-green-700 text-green-600" />
92+
) : (
93+
<Copy className="size-4 text-foreground" />
94+
)}
95+
</Button>
96+
)}
97+
<div className="font-mono text-sm transition-all duration-300">
98+
<JsonNode
99+
data={normalizedData as JsonValue}
100+
name={name}
101+
depth={0}
102+
initialExpandDepth={initialExpandDepth}
103+
/>
104+
</div>
43105
</div>
44106
);
45107
},

client/src/components/PromptsTab.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,7 @@ const PromptsTab = ({
152152
Get Prompt
153153
</Button>
154154
{promptContent && (
155-
<div className="p-4 border rounded">
156-
<JsonView data={promptContent} />
157-
</div>
155+
<JsonView data={promptContent} withCopyButton={false} />
158156
)}
159157
</div>
160158
) : (

client/src/components/ResourcesTab.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,10 @@ const ResourcesTab = ({
215215
<AlertDescription>{error}</AlertDescription>
216216
</Alert>
217217
) : selectedResource ? (
218-
<div className="bg-gray-50 dark:bg-gray-800 p-4 rounded text-sm overflow-auto max-h-96 text-gray-900 dark:text-gray-100">
219-
<JsonView data={resourceContent} />
220-
</div>
218+
<JsonView
219+
data={resourceContent}
220+
className="bg-gray-50 dark:bg-gray-800 p-4 rounded text-sm overflow-auto max-h-96 text-gray-900 dark:text-gray-100"
221+
/>
221222
) : selectedTemplate ? (
222223
<div className="space-y-4">
223224
<p className="text-sm text-gray-600">

0 commit comments

Comments
 (0)