From 7b055b6b9a80e8dc2eda4b1b0a73f48353eb97b2 Mon Sep 17 00:00:00 2001 From: katopz Date: Mon, 31 Mar 2025 18:29:09 +0900 Subject: [PATCH 01/35] fix: prop.type not accept integer --- client/src/components/ToolsTab.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index eb91c98c..26f5b3bf 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -226,7 +226,7 @@ const ToolsTab = ({ ) : ( Date: Mon, 31 Mar 2025 07:32:55 -0700 Subject: [PATCH 02/35] Add failing test for integer input --- .../components/__tests__/ToolsTab.test.tsx | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index 2a45065f..f75c8f92 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -16,6 +16,16 @@ describe("ToolsTab", () => { }, }, }, + { + name: "tool3", + description: "Integer tool", + inputSchema: { + type: "object" as const, + properties: { + count: { type: "integer" as const }, + }, + }, + }, { name: "tool2", description: "Second tool", @@ -69,4 +79,28 @@ describe("ToolsTab", () => { const newInput = screen.getByRole("spinbutton") as HTMLInputElement; expect(newInput.value).toBe(""); }); + + it("should handle integer type inputs", () => { + renderToolsTab({ + selectedTool: mockTools[2], + }); + + // Verify input is rendered as a number input + const input = screen.getByRole("spinbutton") as HTMLInputElement; + expect(input.type).toBe("text"); // This will fail - should be "number" + + // Enter an integer value + fireEvent.change(input, { target: { value: "42" } }); + expect(input.value).toBe("42"); + + // Verify the callTool function receives the value as a number + const submitButton = screen.getByRole("button", { name: /submit/i }); + fireEvent.click(submitButton); + + expect(defaultProps.callTool).toHaveBeenCalledWith( + mockTools[2].name, + { count: 42 }, // Should be number 42, not string "42" + expect.any(Function) + ); + }); }); From 7ac1e40c9dcd1866445baede30a8fe459d9104b9 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 31 Mar 2025 07:51:28 -0700 Subject: [PATCH 03/35] Update tests --- client/src/components/__tests__/ToolsTab.test.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index f75c8f92..b36085ea 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -71,7 +71,7 @@ describe("ToolsTab", () => { // Switch to second tool rerender( - + , ); @@ -82,25 +82,24 @@ describe("ToolsTab", () => { it("should handle integer type inputs", () => { renderToolsTab({ - selectedTool: mockTools[2], + selectedTool: mockTools[1], // Use the tool with integer type }); // Verify input is rendered as a number input const input = screen.getByRole("spinbutton") as HTMLInputElement; - expect(input.type).toBe("text"); // This will fail - should be "number" + expect(input.type).toBe("number"); // Integer type should be treated as number // Enter an integer value fireEvent.change(input, { target: { value: "42" } }); expect(input.value).toBe("42"); // Verify the callTool function receives the value as a number - const submitButton = screen.getByRole("button", { name: /submit/i }); + const submitButton = screen.getByRole("button", { name: /run tool/i }); fireEvent.click(submitButton); expect(defaultProps.callTool).toHaveBeenCalledWith( - mockTools[2].name, - { count: 42 }, // Should be number 42, not string "42" - expect.any(Function) + mockTools[1].name, + { count: 42 } ); }); }); From 7753b275e57987b62b4fe1b791b49e8848f2986b Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 31 Mar 2025 08:04:53 -0700 Subject: [PATCH 04/35] Update test to fail more explicitly --- client/src/components/__tests__/ToolsTab.test.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index b36085ea..624a72e3 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -85,15 +85,11 @@ describe("ToolsTab", () => { selectedTool: mockTools[1], // Use the tool with integer type }); - // Verify input is rendered as a number input - const input = screen.getByRole("spinbutton") as HTMLInputElement; - expect(input.type).toBe("number"); // Integer type should be treated as number - - // Enter an integer value + const input = screen.getByRole("textbox", { name: /count/i }) as HTMLInputElement; + expect(input).toHaveProperty("type", "number"); fireEvent.change(input, { target: { value: "42" } }); expect(input.value).toBe("42"); - // Verify the callTool function receives the value as a number const submitButton = screen.getByRole("button", { name: /run tool/i }); fireEvent.click(submitButton); From 180760c4db647b5c4ad8dc53af5f95d297b5c1f9 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 31 Mar 2025 09:16:25 -0700 Subject: [PATCH 05/35] Fix formatting --- client/src/components/__tests__/ToolsTab.test.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index 624a72e3..27e273cc 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -85,17 +85,18 @@ describe("ToolsTab", () => { selectedTool: mockTools[1], // Use the tool with integer type }); - const input = screen.getByRole("textbox", { name: /count/i }) as HTMLInputElement; + const input = screen.getByRole("textbox", { + name: /count/i, + }) as HTMLInputElement; expect(input).toHaveProperty("type", "number"); fireEvent.change(input, { target: { value: "42" } }); expect(input.value).toBe("42"); const submitButton = screen.getByRole("button", { name: /run tool/i }); fireEvent.click(submitButton); - - expect(defaultProps.callTool).toHaveBeenCalledWith( - mockTools[1].name, - { count: 42 } - ); + + expect(defaultProps.callTool).toHaveBeenCalledWith(mockTools[1].name, { + count: 42, + }); }); }); From 2ee0a53e360ec25c487dd2d952c7d98ed44429a6 Mon Sep 17 00:00:00 2001 From: katopz Date: Tue, 1 Apr 2025 09:27:24 +0900 Subject: [PATCH 06/35] fix: prettier --- client/src/components/ToolsTab.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 26f5b3bf..d9074c34 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -226,7 +226,11 @@ const ToolsTab = ({ ) : ( Date: Mon, 31 Mar 2025 18:54:44 -0700 Subject: [PATCH 07/35] feat: Add lightweight Disconnect button --- client/src/App.tsx | 2 ++ client/src/components/Sidebar.tsx | 30 ++++++++++++------- .../src/components/__tests__/Sidebar.test.tsx | 1 + client/src/lib/hooks/useConnection.ts | 9 ++++++ 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 23c508f2..e560d55f 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -145,6 +145,7 @@ const App = () => { handleCompletion, completionsSupported, connect: connectMcpServer, + disconnect: disconnectMcpServer, } = useConnection({ transportType, command, @@ -458,6 +459,7 @@ const App = () => { bearerToken={bearerToken} setBearerToken={setBearerToken} onConnect={connectMcpServer} + onDisconnect={disconnectMcpServer} stdErrNotifications={stdErrNotifications} logLevel={logLevel} sendLogLevelRequest={sendLogLevelRequest} diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index 33e88a75..568518de 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -10,6 +10,7 @@ import { EyeOff, RotateCcw, Settings, + RefreshCwOff, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -45,6 +46,7 @@ interface SidebarProps { bearerToken: string; setBearerToken: (token: string) => void; onConnect: () => void; + onDisconnect: () => void; stdErrNotifications: StdErrNotification[]; logLevel: LoggingLevel; sendLogLevelRequest: (level: LoggingLevel) => void; @@ -68,6 +70,7 @@ const Sidebar = ({ bearerToken, setBearerToken, onConnect, + onDisconnect, stdErrNotifications, logLevel, sendLogLevelRequest, @@ -375,19 +378,24 @@ const Sidebar = ({
- + + +
+ )} + {connectionStatus !== "connected" && ( + + )}
{ bearerToken: "", setBearerToken: jest.fn(), onConnect: jest.fn(), + onDisconnect: jest.fn(), stdErrNotifications: [], logLevel: "info" as const, sendLogLevelRequest: jest.fn(), diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 180240f6..25e55846 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -321,6 +321,14 @@ export function useConnection({ } }; + const disconnect = async () => { + await mcpClient?.close(); + setMcpClient(null); + setConnectionStatus("disconnected"); + setCompletionsSupported(false); + setServerCapabilities(null); + }; + return { connectionStatus, serverCapabilities, @@ -331,5 +339,6 @@ export function useConnection({ handleCompletion, completionsSupported, connect, + disconnect, }; } From b82c74458357ada6f927ecf1d7385f61cd6e3be0 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Tue, 1 Apr 2025 06:50:23 -0700 Subject: [PATCH 08/35] Use actual rendered element spinbutton in test --- client/src/components/__tests__/ToolsTab.test.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index 27e273cc..07aa52d5 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -85,9 +85,7 @@ describe("ToolsTab", () => { selectedTool: mockTools[1], // Use the tool with integer type }); - const input = screen.getByRole("textbox", { - name: /count/i, - }) as HTMLInputElement; + const input = screen.getByRole("spinbutton", { name: /count/i }) as HTMLInputElement; expect(input).toHaveProperty("type", "number"); fireEvent.change(input, { target: { value: "42" } }); expect(input.value).toBe("42"); From 51c7eda6a6397ef50b9c5aa4ef59e696df3f3584 Mon Sep 17 00:00:00 2001 From: Pulkit Sharma Date: Tue, 1 Apr 2025 17:35:25 +0530 Subject: [PATCH 09/35] Add MCP proxy address config support, better error messages --- client/package.json | 1 + client/src/App.tsx | 22 +- client/src/components/Sidebar.tsx | 70 +++-- .../src/components/__tests__/Sidebar.test.tsx | 82 +++--- client/src/components/ui/tooltip.tsx | 30 +++ client/src/lib/configurationTypes.ts | 1 + client/src/lib/constants.ts | 17 ++ client/src/lib/hooks/useConnection.ts | 78 ++++-- client/src/main.tsx | 7 +- client/src/utils/configUtils.ts | 14 + package-lock.json | 239 ++++++++++++++++++ server/src/index.ts | 9 +- 12 files changed, 483 insertions(+), 87 deletions(-) create mode 100644 client/src/components/ui/tooltip.tsx create mode 100644 client/src/utils/configUtils.ts diff --git a/client/package.json b/client/package.json index eb0091fb..3a2f51c9 100644 --- a/client/package.json +++ b/client/package.json @@ -32,6 +32,7 @@ "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.1", + "@radix-ui/react-tooltip": "^1.1.8", "@types/prismjs": "^1.26.5", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", diff --git a/client/src/App.tsx b/client/src/App.tsx index 23c508f2..e652263b 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -20,7 +20,6 @@ import { import React, { Suspense, useEffect, useRef, useState } from "react"; import { useConnection } from "./lib/hooks/useConnection"; import { useDraggablePane } from "./lib/hooks/useDraggablePane"; - import { StdErrNotification } from "./lib/notificationTypes"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; @@ -47,10 +46,12 @@ import Sidebar from "./components/Sidebar"; import ToolsTab from "./components/ToolsTab"; import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants"; import { InspectorConfig } from "./lib/configurationTypes"; +import { + getMCPProxyAddress, + getMCPServerRequestTimeout, +} from "./utils/configUtils"; const params = new URLSearchParams(window.location.search); -const PROXY_PORT = params.get("proxyPort") ?? "6277"; -const PROXY_SERVER_URL = `http://${window.location.hostname}:${PROXY_PORT}`; const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1"; const App = () => { @@ -95,7 +96,13 @@ const App = () => { const [config, setConfig] = useState(() => { const savedConfig = localStorage.getItem(CONFIG_LOCAL_STORAGE_KEY); - return savedConfig ? JSON.parse(savedConfig) : DEFAULT_INSPECTOR_CONFIG; + if (savedConfig) { + return { + ...DEFAULT_INSPECTOR_CONFIG, + ...JSON.parse(savedConfig), + } as InspectorConfig; + } + return DEFAULT_INSPECTOR_CONFIG; }); const [bearerToken, setBearerToken] = useState(() => { return localStorage.getItem("lastBearerToken") || ""; @@ -152,8 +159,8 @@ const App = () => { sseUrl, env, bearerToken, - proxyServerUrl: PROXY_SERVER_URL, - requestTimeout: config.MCP_SERVER_REQUEST_TIMEOUT.value as number, + proxyServerUrl: getMCPProxyAddress(config), + requestTimeout: getMCPServerRequestTimeout(config), onNotification: (notification) => { setNotifications((prev) => [...prev, notification as ServerNotification]); }, @@ -214,7 +221,7 @@ const App = () => { }, [connectMcpServer]); useEffect(() => { - fetch(`${PROXY_SERVER_URL}/config`) + fetch(`${getMCPProxyAddress(config)}/config`) .then((response) => response.json()) .then((data) => { setEnv(data.defaultEnvironment); @@ -228,6 +235,7 @@ const App = () => { .catch((error) => console.error("Error fetching default environment:", error), ); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index 33e88a75..69aec994 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -10,6 +10,7 @@ import { EyeOff, RotateCcw, Settings, + HelpCircle, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -26,12 +27,17 @@ import { LoggingLevelSchema, } from "@modelcontextprotocol/sdk/types.js"; import { InspectorConfig } from "@/lib/configurationTypes"; - +import { ConnectionStatus } from "@/lib/constants"; import useTheme from "../lib/useTheme"; import { version } from "../../../package.json"; +import { + Tooltip, + TooltipTrigger, + TooltipContent, +} from "@/components/ui/tooltip"; interface SidebarProps { - connectionStatus: "disconnected" | "connected" | "error"; + connectionStatus: ConnectionStatus; transportType: "stdio" | "sse"; setTransportType: (type: "stdio" | "sse") => void; command: string; @@ -177,6 +183,7 @@ const Sidebar = ({ variant="outline" onClick={() => setShowEnvVars(!showEnvVars)} className="flex items-center w-full" + data-testid="env-vars-button" > {showEnvVars ? ( @@ -298,6 +305,7 @@ const Sidebar = ({ variant="outline" onClick={() => setShowConfig(!showConfig)} className="flex items-center w-full" + data-testid="config-button" > {showConfig ? ( @@ -313,9 +321,19 @@ const Sidebar = ({ const configKey = key as keyof InspectorConfig; return (
- +
+ + + + + + + {configItem.description} + + +
{typeof configItem.value === "number" ? (
- {toolResult && renderToolResult()} + {error && ( + + + Error + {error} + + )}
) : ( From 93c9c74dc95b859f18360f3efcaae82a08e647ea Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Tue, 1 Apr 2025 06:59:50 -0700 Subject: [PATCH 11/35] Fix formatting --- client/src/components/__tests__/ToolsTab.test.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index 07aa52d5..89ad6037 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -85,7 +85,9 @@ describe("ToolsTab", () => { selectedTool: mockTools[1], // Use the tool with integer type }); - const input = screen.getByRole("spinbutton", { name: /count/i }) as HTMLInputElement; + const input = screen.getByRole("spinbutton", { + name: /count/i, + }) as HTMLInputElement; expect(input).toHaveProperty("type", "number"); fireEvent.change(input, { target: { value: "42" } }); expect(input.value).toBe("42"); From d2db697d894706500a4eaf562e168c1060d420fd Mon Sep 17 00:00:00 2001 From: NicolasMontone Date: Tue, 1 Apr 2025 11:00:13 -0300 Subject: [PATCH 12/35] fix: prettier. --- client/src/components/ToolsTab.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 3bd3da9a..6a6f9f55 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -149,9 +149,9 @@ const ToolsTab = ({
{selectedTool ? (
-

- {selectedTool.description} -

+

+ {selectedTool.description} +

{Object.entries(selectedTool.inputSchema.properties ?? []).map( ([key, value]) => { const prop = value as JsonSchemaType; From 51f2f726779253a07928bf1a323628a8d1166ac5 Mon Sep 17 00:00:00 2001 From: NicolasMontone Date: Tue, 1 Apr 2025 11:07:43 -0300 Subject: [PATCH 13/35] fix: add tests --- client/src/components/__tests__/ToolsTab.test.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index 2a45065f..41501db4 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -1,5 +1,6 @@ import { render, screen, fireEvent } from "@testing-library/react"; import { describe, it, expect, jest } from "@jest/globals"; +import "@testing-library/jest-dom"; import ToolsTab from "../ToolsTab"; import { Tool } from "@modelcontextprotocol/sdk/types.js"; import { Tabs } from "@/components/ui/tabs"; @@ -69,4 +70,16 @@ describe("ToolsTab", () => { const newInput = screen.getByRole("spinbutton") as HTMLInputElement; expect(newInput.value).toBe(""); }); + + it("should display error message when error prop is provided", () => { + const errorMessage = "Test error message"; + renderToolsTab({ + selectedTool: mockTools[0], + error: errorMessage, + }); + + // Verify error message is displayed + expect(screen.getByText("Error")).toBeTruthy(); + expect(screen.getByText(errorMessage)).toBeTruthy(); + }); }); From 65f38a482722ba658fd9a1c223bc8d98aab56235 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Tue, 1 Apr 2025 07:07:57 -0700 Subject: [PATCH 14/35] Bump version to 0.8.0 --- client/package.json | 2 +- package-lock.json | 12 ++++++------ package.json | 6 +++--- server/package.json | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client/package.json b/client/package.json index eb0091fb..8dc5a90e 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-client", - "version": "0.7.0", + "version": "0.8.0", "description": "Client-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/package-lock.json b/package-lock.json index feb893f4..e9254db4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.7.0", + "version": "0.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector", - "version": "0.7.0", + "version": "0.8.0", "license": "MIT", "workspaces": [ "client", "server" ], "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.7.0", - "@modelcontextprotocol/inspector-server": "^0.7.0", + "@modelcontextprotocol/inspector-client": "^0.8.0", + "@modelcontextprotocol/inspector-server": "^0.8.0", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", @@ -32,7 +32,7 @@ }, "client": { "name": "@modelcontextprotocol/inspector-client", - "version": "0.7.0", + "version": "0.8.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", @@ -11571,7 +11571,7 @@ }, "server": { "name": "@modelcontextprotocol/inspector-server", - "version": "0.7.0", + "version": "0.8.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", diff --git a/package.json b/package.json index 28b93d81..d323dda2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.7.0", + "version": "0.8.0", "description": "Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -36,8 +36,8 @@ "publish-all": "npm publish --workspaces --access public && npm publish --access public" }, "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.7.0", - "@modelcontextprotocol/inspector-server": "^0.7.0", + "@modelcontextprotocol/inspector-client": "^0.8.0", + "@modelcontextprotocol/inspector-server": "^0.8.0", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", diff --git a/server/package.json b/server/package.json index 732993f9..9072f51d 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-server", - "version": "0.7.0", + "version": "0.8.0", "description": "Server-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", From 0dcd10c1dd12565d6b1020bea367de6f398c8c60 Mon Sep 17 00:00:00 2001 From: Pulkit Sharma Date: Tue, 1 Apr 2025 19:46:55 +0530 Subject: [PATCH 15/35] Update readme --- README.md | 1 + client/src/lib/hooks/useConnection.ts | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b0e18deb..c43f9432 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ The MCP Inspector supports the following configuration settings. To change them | Name | Purpose | Default Value | | -------------------------- | ----------------------------------------------------------------------------------------- | ------------- | | MCP_SERVER_REQUEST_TIMEOUT | Maximum time in milliseconds to wait for a response from the MCP server before timing out | 10000 | +| MCP_PROXY_FULL_ADDRESS | The full URL of the MCP Inspector proxy server (e.g. `http://10.2.1.14:2277`) | `null` | ### From this repository diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 0bb81242..404cd985 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -233,14 +233,13 @@ export function useConnection({ }, ); - const mcpProxyServerUrl = new URL(`${proxyServerUrl}/sse`); try { await checkProxyHealth(); } catch { setConnectionStatus("error-connecting-to-proxy"); return; } - + const mcpProxyServerUrl = new URL(`${proxyServerUrl}/sse`); mcpProxyServerUrl.searchParams.append("transportType", transportType); if (transportType === "stdio") { mcpProxyServerUrl.searchParams.append("command", command); From c48670f426cd7443c85a01c90fd782140958e5e6 Mon Sep 17 00:00:00 2001 From: NicolasMontone Date: Tue, 1 Apr 2025 16:43:23 -0300 Subject: [PATCH 16/35] Add copy button to JSON & fix button styles override. --- client/src/components/JsonView.tsx | 2 +- client/src/components/ToolsTab.tsx | 31 ++++++++++++++++--- .../components/__tests__/ToolsTab.test.tsx | 1 - client/src/index.css | 26 ---------------- 4 files changed, 27 insertions(+), 33 deletions(-) diff --git a/client/src/components/JsonView.tsx b/client/src/components/JsonView.tsx index e2922f0e..3b9ec25a 100644 --- a/client/src/components/JsonView.tsx +++ b/client/src/components/JsonView.tsx @@ -33,7 +33,7 @@ const JsonView = memo( : data; return ( -
+
{ + try { + navigator.clipboard.writeText(JSON.stringify(toolResult)) + setCopied(true) + setTimeout(() => { + setCopied(false) + }, 500) + } catch (error) { + toast.error(`There was an error coping result into the clipboard: ${error instanceof Error ? error.message : String(error)}`) + } + }, [toolResult]) + const renderToolResult = () => { if (!toolResult) return null; @@ -53,7 +68,8 @@ const ToolsTab = ({ return ( <>

Invalid Tool Result:

-
+
+

Errors:

@@ -76,7 +92,12 @@ const ToolsTab = ({ {structuredResult.content.map((item, index) => (
{item.type === "text" && ( -
+
+
)} @@ -234,7 +255,7 @@ const ToolsTab = ({ ...params, [key]: prop.type === "number" || - prop.type === "integer" + prop.type === "integer" ? Number(e.target.value) : e.target.value, }) diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index e1b306d7..1c24a6c5 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -81,7 +81,6 @@ describe("ToolsTab", () => { expect(newInput.value).toBe(""); }); - it("should display error message when error prop is provided", () => { const errorMessage = "Test error message"; renderToolsTab({ diff --git a/client/src/index.css b/client/src/index.css index 1795f584..11c6f23d 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -38,29 +38,6 @@ h1 { line-height: 1.1; } -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -button[role="checkbox"] { - padding: 0; -} - @media (prefers-color-scheme: light) { :root { color: #213547; @@ -69,9 +46,6 @@ button[role="checkbox"] { a:hover { color: #747bff; } - button { - background-color: #f9f9f9; - } } @layer base { From d1e155f984b44699378d309d651b07e68a46bab4 Mon Sep 17 00:00:00 2001 From: NicolasMontone Date: Tue, 1 Apr 2025 16:48:01 -0300 Subject: [PATCH 17/35] Revert "Add copy button to JSON & fix button styles override." This reverts commit c48670f426cd7443c85a01c90fd782140958e5e6. --- client/src/components/JsonView.tsx | 2 +- client/src/components/ToolsTab.tsx | 31 +++---------------- .../components/__tests__/ToolsTab.test.tsx | 1 + client/src/index.css | 26 ++++++++++++++++ 4 files changed, 33 insertions(+), 27 deletions(-) diff --git a/client/src/components/JsonView.tsx b/client/src/components/JsonView.tsx index 3b9ec25a..e2922f0e 100644 --- a/client/src/components/JsonView.tsx +++ b/client/src/components/JsonView.tsx @@ -33,7 +33,7 @@ const JsonView = memo( : data; return ( -
+
{ - try { - navigator.clipboard.writeText(JSON.stringify(toolResult)) - setCopied(true) - setTimeout(() => { - setCopied(false) - }, 500) - } catch (error) { - toast.error(`There was an error coping result into the clipboard: ${error instanceof Error ? error.message : String(error)}`) - } - }, [toolResult]) - const renderToolResult = () => { if (!toolResult) return null; @@ -68,8 +53,7 @@ const ToolsTab = ({ return ( <>

Invalid Tool Result:

-
- +

Errors:

@@ -92,12 +76,7 @@ const ToolsTab = ({ {structuredResult.content.map((item, index) => (
{item.type === "text" && ( -
- +
)} @@ -255,7 +234,7 @@ const ToolsTab = ({ ...params, [key]: prop.type === "number" || - prop.type === "integer" + prop.type === "integer" ? Number(e.target.value) : e.target.value, }) diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index 1c24a6c5..e1b306d7 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -81,6 +81,7 @@ describe("ToolsTab", () => { expect(newInput.value).toBe(""); }); + it("should display error message when error prop is provided", () => { const errorMessage = "Test error message"; renderToolsTab({ diff --git a/client/src/index.css b/client/src/index.css index 11c6f23d..1795f584 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -38,6 +38,29 @@ h1 { line-height: 1.1; } +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +button[role="checkbox"] { + padding: 0; +} + @media (prefers-color-scheme: light) { :root { color: #213547; @@ -46,6 +69,9 @@ h1 { a:hover { color: #747bff; } + button { + background-color: #f9f9f9; + } } @layer base { From 1504d1307e2dc30752d38f2c90aa3c319ee22027 Mon Sep 17 00:00:00 2001 From: NicolasMontone Date: Tue, 1 Apr 2025 16:49:10 -0300 Subject: [PATCH 18/35] Remove error --- client/src/components/ToolsTab.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 79586649..6e855317 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -1,4 +1,4 @@ -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Alert, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; @@ -13,7 +13,7 @@ import { ListToolsResult, Tool, } from "@modelcontextprotocol/sdk/types.js"; -import { AlertCircle, Send } from "lucide-react"; +import { Send } from "lucide-react"; import { useEffect, useState } from "react"; import ListPane from "./ListPane"; import JsonView from "./JsonView"; @@ -27,7 +27,6 @@ const ToolsTab = ({ setSelectedTool, toolResult, nextCursor, - error, }: { tools: Tool[]; listTools: () => void; @@ -251,13 +250,6 @@ const ToolsTab = ({ Run Tool {toolResult && renderToolResult()} - {error && ( - - - Error - {error} - - )}
) : ( From 80f2986fd6dbc0803249d7cd08d08a782f107c66 Mon Sep 17 00:00:00 2001 From: NicolasMontone Date: Tue, 1 Apr 2025 16:58:32 -0300 Subject: [PATCH 19/35] remove unneeded test --- client/src/components/__tests__/ToolsTab.test.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index e1b306d7..349977ae 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -80,20 +80,6 @@ describe("ToolsTab", () => { const newInput = screen.getByRole("spinbutton") as HTMLInputElement; expect(newInput.value).toBe(""); }); - - - it("should display error message when error prop is provided", () => { - const errorMessage = "Test error message"; - renderToolsTab({ - selectedTool: mockTools[0], - error: errorMessage, - }); - - // Verify error message is displayed - expect(screen.getByText("Error")).toBeTruthy(); - expect(screen.getByText(errorMessage)).toBeTruthy(); - }); - it("should handle integer type inputs", () => { renderToolsTab({ selectedTool: mockTools[1], // Use the tool with integer type From 539de0fd851612ab9392b0dd81edb3ac252249be Mon Sep 17 00:00:00 2001 From: NicolasMontone Date: Tue, 1 Apr 2025 17:18:40 -0300 Subject: [PATCH 20/35] add copy button --- client/src/components/JsonView.tsx | 2 +- client/src/components/ToolsTab.tsx | 38 ++++++++++++++++++++++++++---- client/src/index.css | 26 -------------------- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/client/src/components/JsonView.tsx b/client/src/components/JsonView.tsx index e2922f0e..3b9ec25a 100644 --- a/client/src/components/JsonView.tsx +++ b/client/src/components/JsonView.tsx @@ -33,7 +33,7 @@ const JsonView = memo( : data; return ( -
+
{ + try { + navigator.clipboard.writeText(JSON.stringify(toolResult)); + setCopied(true); + setTimeout(() => { + setCopied(false); + }, 500); + } catch (error) { + toast.error( + `There was an error coping result into the clipboard: ${error instanceof Error ? error.message : String(error)}`, + ); + } + }, [toolResult]); + const renderToolResult = () => { if (!toolResult) return null; @@ -52,7 +69,8 @@ const ToolsTab = ({ return ( <>

Invalid Tool Result:

-
+
+

Errors:

@@ -75,7 +93,19 @@ const ToolsTab = ({ {structuredResult.content.map((item, index) => (
{item.type === "text" && ( -
+
+
)} diff --git a/client/src/index.css b/client/src/index.css index 1795f584..11c6f23d 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -38,29 +38,6 @@ h1 { line-height: 1.1; } -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -button[role="checkbox"] { - padding: 0; -} - @media (prefers-color-scheme: light) { :root { color: #213547; @@ -69,9 +46,6 @@ button[role="checkbox"] { a:hover { color: #747bff; } - button { - background-color: #f9f9f9; - } } @layer base { From 8586d63e6d1dd586095f88eb4d1f4be54d8672ee Mon Sep 17 00:00:00 2001 From: NicolasMontone Date: Tue, 1 Apr 2025 17:38:25 -0300 Subject: [PATCH 21/35] Add shadcn alert to match project design --- client/package.json | 2 +- client/src/App.tsx | 11 +- client/src/components/ToolsTab.tsx | 13 +- client/src/components/ui/toast.tsx | 126 ++++++++++++++ client/src/components/ui/toaster.tsx | 33 ++++ client/src/hooks/use-toast.ts | 191 +++++++++++++++++++++ client/src/lib/hooks/useConnection.ts | 27 ++- client/src/main.tsx | 5 +- package-lock.json | 235 ++++++++++++++++++++++++-- 9 files changed, 611 insertions(+), 32 deletions(-) create mode 100644 client/src/components/ui/toast.tsx create mode 100644 client/src/components/ui/toaster.tsx create mode 100644 client/src/hooks/use-toast.ts diff --git a/client/package.json b/client/package.json index 8dc5a90e..a415b189 100644 --- a/client/package.json +++ b/client/package.json @@ -32,6 +32,7 @@ "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.1", + "@radix-ui/react-toast": "^1.2.6", "@types/prismjs": "^1.26.5", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", @@ -42,7 +43,6 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-simple-code-editor": "^0.14.1", - "react-toastify": "^10.0.6", "serve-handler": "^6.1.6", "tailwind-merge": "^2.5.3", "tailwindcss-animate": "^1.0.7", diff --git a/client/src/App.tsx b/client/src/App.tsx index e560d55f..5c09e8e8 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -33,7 +33,6 @@ import { MessageSquare, } from "lucide-react"; -import { toast } from "react-toastify"; import { z } from "zod"; import "./App.css"; import ConsoleTab from "./components/ConsoleTab"; @@ -47,13 +46,14 @@ import Sidebar from "./components/Sidebar"; import ToolsTab from "./components/ToolsTab"; import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants"; import { InspectorConfig } from "./lib/configurationTypes"; - +import { useToast } from "@/hooks/use-toast"; const params = new URLSearchParams(window.location.search); const PROXY_PORT = params.get("proxyPort") ?? "6277"; const PROXY_SERVER_URL = `http://${window.location.hostname}:${PROXY_PORT}`; const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1"; const App = () => { + const { toast } = useToast(); // Handle OAuth callback route const [resources, setResources] = useState([]); const [resourceTemplates, setResourceTemplates] = useState< @@ -208,11 +208,14 @@ const App = () => { newUrl.searchParams.delete("serverUrl"); window.history.replaceState({}, "", newUrl.toString()); // Show success toast for OAuth - toast.success("Successfully authenticated with OAuth"); + toast({ + title: "Success", + description: "Successfully authenticated with OAuth", + }); // Connect to the server connectMcpServer(); } - }, [connectMcpServer]); + }, [connectMcpServer, toast]); useEffect(() => { fetch(`${PROXY_SERVER_URL}/config`) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 694377eb..bd30f96f 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -17,7 +17,7 @@ import { Copy, Send, CheckCheck } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import ListPane from "./ListPane"; import JsonView from "./JsonView"; -import { toast } from "react-toastify"; +import { useToast } from "@/hooks/use-toast"; const ToolsTab = ({ tools, @@ -39,6 +39,7 @@ const ToolsTab = ({ nextCursor: ListToolsResult["nextCursor"]; error: string | null; }) => { + const { toast } = useToast(); const [params, setParams] = useState>({}); useEffect(() => { setParams({}); @@ -54,11 +55,13 @@ const ToolsTab = ({ setCopied(false); }, 500); } catch (error) { - toast.error( - `There was an error coping result into the clipboard: ${error instanceof Error ? error.message : String(error)}`, - ); + toast({ + title: "Error", + description: `There was an error coping result into the clipboard: ${error instanceof Error ? error.message : String(error)}`, + variant: "destructive", + }); } - }, [toolResult]); + }, [toast, toolResult]); const renderToolResult = () => { if (!toolResult) return null; diff --git a/client/src/components/ui/toast.tsx b/client/src/components/ui/toast.tsx new file mode 100644 index 00000000..9cea3970 --- /dev/null +++ b/client/src/components/ui/toast.tsx @@ -0,0 +1,126 @@ +import * as React from "react"; +import * as ToastPrimitives from "@radix-ui/react-toast"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/lib/utils"; +import { Cross2Icon } from "@radix-ui/react-icons"; + +const ToastProvider = ToastPrimitives.Provider; + +const ToastViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastViewport.displayName = ToastPrimitives.Viewport.displayName; + +const toastVariants = cva( + "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", + { + variants: { + variant: { + default: "border bg-background text-foreground", + destructive: + "destructive group border-destructive bg-destructive text-destructive-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +const Toast = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, ...props }, ref) => { + return ( + + ); +}); +Toast.displayName = ToastPrimitives.Root.displayName; + +const ToastAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastAction.displayName = ToastPrimitives.Action.displayName; + +const ToastClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +ToastClose.displayName = ToastPrimitives.Close.displayName; + +const ToastTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastTitle.displayName = ToastPrimitives.Title.displayName; + +const ToastDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastDescription.displayName = ToastPrimitives.Description.displayName; + +type ToastProps = React.ComponentPropsWithoutRef; + +type ToastActionElement = React.ReactElement; + +export { + type ToastProps, + type ToastActionElement, + ToastProvider, + ToastViewport, + Toast, + ToastTitle, + ToastDescription, + ToastClose, + ToastAction, +}; diff --git a/client/src/components/ui/toaster.tsx b/client/src/components/ui/toaster.tsx new file mode 100644 index 00000000..5887f080 --- /dev/null +++ b/client/src/components/ui/toaster.tsx @@ -0,0 +1,33 @@ +import { useToast } from "@/hooks/use-toast"; +import { + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, +} from "@/components/ui/toast"; + +export function Toaster() { + const { toasts } = useToast(); + + return ( + + {toasts.map(function ({ id, title, description, action, ...props }) { + return ( + +
+ {title && {title}} + {description && ( + {description} + )} +
+ {action} + +
+ ); + })} + +
+ ); +} diff --git a/client/src/hooks/use-toast.ts b/client/src/hooks/use-toast.ts new file mode 100644 index 00000000..6555e795 --- /dev/null +++ b/client/src/hooks/use-toast.ts @@ -0,0 +1,191 @@ +"use client"; + +// Inspired by react-hot-toast library +import * as React from "react"; + +import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; + +const TOAST_LIMIT = 1; +const TOAST_REMOVE_DELAY = 1000000; + +type ToasterToast = ToastProps & { + id: string; + title?: React.ReactNode; + description?: React.ReactNode; + action?: ToastActionElement; +}; + +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const; + +let count = 0; + +function genId() { + count = (count + 1) % Number.MAX_SAFE_INTEGER; + return count.toString(); +} + +type ActionType = typeof actionTypes; + +type Action = + | { + type: ActionType["ADD_TOAST"]; + toast: ToasterToast; + } + | { + type: ActionType["UPDATE_TOAST"]; + toast: Partial; + } + | { + type: ActionType["DISMISS_TOAST"]; + toastId?: ToasterToast["id"]; + } + | { + type: ActionType["REMOVE_TOAST"]; + toastId?: ToasterToast["id"]; + }; + +interface State { + toasts: ToasterToast[]; +} + +const toastTimeouts = new Map>(); + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return; + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId); + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }); + }, TOAST_REMOVE_DELAY); + + toastTimeouts.set(toastId, timeout); +}; + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + }; + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t, + ), + }; + + case "DISMISS_TOAST": { + const { toastId } = action; + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId); + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id); + }); + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t, + ), + }; + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + }; + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + }; + } +}; + +const listeners: Array<(state: State) => void> = []; + +let memoryState: State = { toasts: [] }; + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action); + listeners.forEach((listener) => { + listener(memoryState); + }); +} + +type Toast = Omit; + +function toast({ ...props }: Toast) { + const id = genId(); + + const update = (props: ToasterToast) => + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }); + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss(); + }, + }, + }); + + return { + id: id, + dismiss, + update, + }; +} + +function useToast() { + const [state, setState] = React.useState(memoryState); + + React.useEffect(() => { + listeners.push(setState); + return () => { + const index = listeners.indexOf(setState); + if (index > -1) { + listeners.splice(index, 1); + } + }; + }, [state]); + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), + }; +} + +export { useToast, toast }; diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 25e55846..56c91d00 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -25,7 +25,7 @@ import { PromptListChangedNotificationSchema, } from "@modelcontextprotocol/sdk/types.js"; import { useState } from "react"; -import { toast } from "react-toastify"; +import { useToast } from "@/hooks/use-toast"; import { z } from "zod"; import { SESSION_KEYS } from "../constants"; import { Notification, StdErrNotificationSchema } from "../notificationTypes"; @@ -70,6 +70,7 @@ export function useConnection({ onPendingRequest, getRoots, }: UseConnectionOptions) { + const { toast } = useToast(); const [connectionStatus, setConnectionStatus] = useState< "disconnected" | "connected" | "error" >("disconnected"); @@ -125,7 +126,11 @@ export function useConnection({ } catch (e: unknown) { if (!options?.suppressToast) { const errorString = (e as Error).message ?? String(e); - toast.error(errorString); + toast({ + title: "Error", + description: errorString, + variant: "destructive", + }); } throw e; } @@ -167,7 +172,11 @@ export function useConnection({ } // Unexpected errors - show toast and rethrow - toast.error(e instanceof Error ? e.message : String(e)); + toast({ + title: "Error", + description: e instanceof Error ? e.message : String(e), + variant: "destructive", + }); throw e; } }; @@ -175,7 +184,11 @@ export function useConnection({ const sendNotification = async (notification: ClientNotification) => { if (!mcpClient) { const error = new Error("MCP client not connected"); - toast.error(error.message); + toast({ + title: "Error", + description: error.message, + variant: "destructive", + }); throw error; } @@ -188,7 +201,11 @@ export function useConnection({ // Log MCP protocol errors pushHistory(notification, { error: e.message }); } - toast.error(e instanceof Error ? e.message : String(e)); + toast({ + title: "Error", + description: e instanceof Error ? e.message : String(e), + variant: "destructive", + }); throw e; } }; diff --git a/client/src/main.tsx b/client/src/main.tsx index 450213d4..7379a14a 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,13 +1,12 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import { ToastContainer } from "react-toastify"; -import "react-toastify/dist/ReactToastify.css"; +import { Toaster } from "@/components/ui/toaster.tsx"; import App from "./App.tsx"; import "./index.css"; createRoot(document.getElementById("root")!).render( - + , ); diff --git a/package-lock.json b/package-lock.json index e9254db4..b018e162 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.1", + "@radix-ui/react-toast": "^1.2.6", "@types/prismjs": "^1.26.5", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", @@ -54,7 +55,6 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-simple-code-editor": "^0.14.1", - "react-toastify": "^10.0.6", "serve-handler": "^6.1.6", "tailwind-merge": "^2.5.3", "tailwindcss-animate": "^1.0.7", @@ -3383,6 +3383,226 @@ } } }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.6.tgz", + "integrity": "sha512-gN4dpuIVKEgpLn1z5FhzT9mYRUitbfZq9XqN/7kkBMUgFTzTG8x/KszWJugJXHcwxckY8xcKDZPz7kG3o6DsUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-collection": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.2.tgz", + "integrity": "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz", + "integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-portal": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz", + "integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz", + "integrity": "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", @@ -9355,19 +9575,6 @@ } } }, - "node_modules/react-toastify": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.6.tgz", - "integrity": "sha512-yYjp+omCDf9lhZcrZHKbSq7YMuK0zcYkDFTzfRFgTXkTFHZ1ToxwAonzA4JI5CxA91JpjFLmwEsZEgfYfOqI1A==", - "license": "MIT", - "dependencies": { - "clsx": "^2.1.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", From cc70fbd0f51e11b2d2f28631e3aff65e79e99cf3 Mon Sep 17 00:00:00 2001 From: NicolasMontone Date: Tue, 1 Apr 2025 19:14:32 -0300 Subject: [PATCH 22/35] add ring color --- client/src/index.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/index.css b/client/src/index.css index 11c6f23d..14c94704 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -26,6 +26,11 @@ a:hover { color: #535bf2; } +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + body { margin: 0; place-items: center; From c9ee22b7815f3f4edcfe54301100f5c791fe24fd Mon Sep 17 00:00:00 2001 From: NicolasMontone Date: Tue, 1 Apr 2025 19:23:57 -0300 Subject: [PATCH 23/35] fix(Select): add missing style. --- client/src/components/ui/select.tsx | 2 +- client/src/index.css | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/client/src/components/ui/select.tsx b/client/src/components/ui/select.tsx index 939dce12..9d99d926 100644 --- a/client/src/components/ui/select.tsx +++ b/client/src/components/ui/select.tsx @@ -22,7 +22,7 @@ const SelectTrigger = React.forwardRef< span]:line-clamp-1", + "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 hover:border-[#646cff] hover:border-1", className, )} {...props} diff --git a/client/src/index.css b/client/src/index.css index 14c94704..11c6f23d 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -26,11 +26,6 @@ a:hover { color: #535bf2; } -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - body { margin: 0; place-items: center; From e69bfc58bc949ffb004803c364afd29a904402af Mon Sep 17 00:00:00 2001 From: Pulkit Sharma Date: Wed, 2 Apr 2025 13:45:09 +0530 Subject: [PATCH 24/35] prettier-fix --- client/src/components/Sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index c0369ecb..19f8020b 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -398,7 +398,7 @@ const Sidebar = ({
{connectionStatus === "connected" && (
- From c964ff5cfe464f6980c73bb02cf10f289f3684af Mon Sep 17 00:00:00 2001 From: NicolasMontone Date: Wed, 2 Apr 2025 10:39:39 -0300 Subject: [PATCH 25/35] Use copy button insde JSON view component --- client/src/components/History.tsx | 49 +++++----------- client/src/components/JsonView.tsx | 80 ++++++++++++++++++++++---- client/src/components/PromptsTab.tsx | 4 +- client/src/components/ResourcesTab.tsx | 7 ++- client/src/components/SamplingTab.tsx | 8 ++- client/src/components/ToolsTab.tsx | 60 +++---------------- 6 files changed, 100 insertions(+), 108 deletions(-) diff --git a/client/src/components/History.tsx b/client/src/components/History.tsx index b03d1f44..0b05b55d 100644 --- a/client/src/components/History.tsx +++ b/client/src/components/History.tsx @@ -1,5 +1,4 @@ import { ServerNotification } from "@modelcontextprotocol/sdk/types.js"; -import { Copy } from "lucide-react"; import { useState } from "react"; import JsonView from "./JsonView"; @@ -25,10 +24,6 @@ const HistoryAndNotifications = ({ setExpandedNotifications((prev) => ({ ...prev, [index]: !prev[index] })); }; - const copyToClipboard = (text: string) => { - navigator.clipboard.writeText(text); - }; - return (
@@ -68,16 +63,12 @@ const HistoryAndNotifications = ({ Request: - -
-
-
+ +
{request.response && (
@@ -85,16 +76,11 @@ const HistoryAndNotifications = ({ Response: - -
-
-
+
)} @@ -134,20 +120,11 @@ const HistoryAndNotifications = ({ Details: - -
-
-
+
)} diff --git a/client/src/components/JsonView.tsx b/client/src/components/JsonView.tsx index 3b9ec25a..a98a11cf 100644 --- a/client/src/components/JsonView.tsx +++ b/client/src/components/JsonView.tsx @@ -1,11 +1,16 @@ -import { useState, memo } from "react"; +import { useState, memo, useMemo, useCallback, useEffect } from "react"; import { JsonValue } from "./DynamicJsonForm"; import clsx from "clsx"; +import { Copy, CheckCheck } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { useToast } from "@/hooks/use-toast"; interface JsonViewProps { data: unknown; name?: string; initialExpandDepth?: number; + className?: string; + withCopyButton?: boolean; } function tryParseJson(str: string): { success: boolean; data: JsonValue } { @@ -24,22 +29,75 @@ function tryParseJson(str: string): { success: boolean; data: JsonValue } { } const JsonView = memo( - ({ data, name, initialExpandDepth = 3 }: JsonViewProps) => { - const normalizedData = - typeof data === "string" + ({ + data, + name, + initialExpandDepth = 3, + className, + withCopyButton = true, + }: JsonViewProps) => { + const { toast } = useToast(); + const [copied, setCopied] = useState(false); + + useEffect(() => { + let timeoutId: NodeJS.Timeout; + if (copied) { + timeoutId = setTimeout(() => { + setCopied(false); + }, 500); + } + return () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }; + }, [copied]); + + const normalizedData = useMemo(() => { + return typeof data === "string" ? tryParseJson(data).success ? tryParseJson(data).data : data : data; + }, [data]); + + const handleCopy = useCallback(() => { + try { + navigator.clipboard.writeText(JSON.stringify(normalizedData, null, 2)); + setCopied(true); + } catch (error) { + toast({ + title: "Error", + description: `There was an error coping result into the clipboard: ${error instanceof Error ? error.message : String(error)}`, + variant: "destructive", + }); + } + }, [toast, normalizedData]); return ( -
- +
+ {withCopyButton && ( + + )} +
+ +
); }, diff --git a/client/src/components/PromptsTab.tsx b/client/src/components/PromptsTab.tsx index b42cf77e..48c847d3 100644 --- a/client/src/components/PromptsTab.tsx +++ b/client/src/components/PromptsTab.tsx @@ -152,9 +152,7 @@ const PromptsTab = ({ Get Prompt {promptContent && ( -
- -
+ )}
) : ( diff --git a/client/src/components/ResourcesTab.tsx b/client/src/components/ResourcesTab.tsx index 443a902c..2a108249 100644 --- a/client/src/components/ResourcesTab.tsx +++ b/client/src/components/ResourcesTab.tsx @@ -215,9 +215,10 @@ const ResourcesTab = ({ {error} ) : selectedResource ? ( -
- -
+ ) : selectedTemplate ? (

diff --git a/client/src/components/SamplingTab.tsx b/client/src/components/SamplingTab.tsx index 21fc7dd8..a72ea7d7 100644 --- a/client/src/components/SamplingTab.tsx +++ b/client/src/components/SamplingTab.tsx @@ -44,9 +44,11 @@ const SamplingTab = ({ pendingRequests, onApprove, onReject }: Props) => {

Recent Requests

{pendingRequests.map((request) => (
-
- -
+ +
- -
- )} + {item.type === "text" && } {item.type === "image" && ( Your browser does not support audio playback

) : ( -
- -
+ ))}
))} @@ -141,9 +98,8 @@ const ToolsTab = ({ return ( <>

Tool Result (Legacy):

-
- -
+ + ); } From 8b31f495ba8eafdb16ac48766e10d045f2abb733 Mon Sep 17 00:00:00 2001 From: NicolasMontone Date: Wed, 2 Apr 2025 10:41:33 -0300 Subject: [PATCH 26/35] fix unkown type. --- client/src/components/JsonView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/JsonView.tsx b/client/src/components/JsonView.tsx index a98a11cf..56796be7 100644 --- a/client/src/components/JsonView.tsx +++ b/client/src/components/JsonView.tsx @@ -63,7 +63,7 @@ const JsonView = memo( const handleCopy = useCallback(() => { try { - navigator.clipboard.writeText(JSON.stringify(normalizedData, null, 2)); + navigator.clipboard.writeText(typeof normalizedData === "string" ? normalizedData : JSON.stringify(normalizedData, null, 2)); setCopied(true); } catch (error) { toast({ From 5db5fc26c79d45f5190b0d4dbfbce26e117271fa Mon Sep 17 00:00:00 2001 From: NicolasMontone Date: Wed, 2 Apr 2025 10:42:52 -0300 Subject: [PATCH 27/35] fix prettier --- client/src/components/JsonView.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/components/JsonView.tsx b/client/src/components/JsonView.tsx index 56796be7..b59f3cc5 100644 --- a/client/src/components/JsonView.tsx +++ b/client/src/components/JsonView.tsx @@ -63,7 +63,11 @@ const JsonView = memo( const handleCopy = useCallback(() => { try { - navigator.clipboard.writeText(typeof normalizedData === "string" ? normalizedData : JSON.stringify(normalizedData, null, 2)); + navigator.clipboard.writeText( + typeof normalizedData === "string" + ? normalizedData + : JSON.stringify(normalizedData, null, 2), + ); setCopied(true); } catch (error) { toast({ From 0bd51fa84a2ba166469b06bccd058e20a029eafc Mon Sep 17 00:00:00 2001 From: Maxwell Gerber Date: Wed, 2 Apr 2025 15:18:33 -0700 Subject: [PATCH 28/35] fix: Do not reconnect on rerender --- client/src/App.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/src/App.tsx b/client/src/App.tsx index 5c09e8e8..c7569035 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -47,6 +47,7 @@ import ToolsTab from "./components/ToolsTab"; import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants"; import { InspectorConfig } from "./lib/configurationTypes"; import { useToast } from "@/hooks/use-toast"; + const params = new URLSearchParams(window.location.search); const PROXY_PORT = params.get("proxyPort") ?? "6277"; const PROXY_SERVER_URL = `http://${window.location.hostname}:${PROXY_PORT}`; @@ -197,8 +198,13 @@ const App = () => { localStorage.setItem(CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config)); }, [config]); + const hasProcessedRef = useRef(false); // Auto-connect if serverUrl is provided in URL params (e.g. after OAuth callback) useEffect(() => { + if (hasProcessedRef.current) { + // Only try to connect once + return; + } const serverUrl = params.get("serverUrl"); if (serverUrl) { setSseUrl(serverUrl); @@ -212,6 +218,7 @@ const App = () => { title: "Success", description: "Successfully authenticated with OAuth", }); + hasProcessedRef.current = true; // Connect to the server connectMcpServer(); } From a4140333543c854efb7be24939238a37d5903d3b Mon Sep 17 00:00:00 2001 From: Maxwell Gerber Date: Tue, 1 Apr 2025 20:09:36 -0700 Subject: [PATCH 29/35] fix: Use same protocol for proxy server URL --- client/src/utils/configUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/utils/configUtils.ts b/client/src/utils/configUtils.ts index a6f2dd21..dcd4c97d 100644 --- a/client/src/utils/configUtils.ts +++ b/client/src/utils/configUtils.ts @@ -6,7 +6,7 @@ export const getMCPProxyAddress = (config: InspectorConfig): string => { if (proxyFullAddress) { return proxyFullAddress; } - return `http://${window.location.hostname}:${DEFAULT_MCP_PROXY_LISTEN_PORT}`; + return `${window.location.protocol}//${window.location.hostname}:${DEFAULT_MCP_PROXY_LISTEN_PORT}`; }; export const getMCPServerRequestTimeout = (config: InspectorConfig): number => { From 9092c780f7b85b61b6e68f6d3ea51c7d0e9a1d48 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Thu, 3 Apr 2025 14:30:59 -0400 Subject: [PATCH 30/35] Bump version to 0.8.1 * In client/package.json - version bumped to 0.8.1 - typescript SDK version 0.8.0 (latest) * In server/package.json - version bumped to 0.8.1 - typescript SDK version 0.8.0 (latest) * In client/package.json - version bumped to 0.8.1 - inspector-client bumped to 0.8.1 - inspector-server bumped to 0.8.1 --- client/package.json | 4 ++-- package.json | 6 +++--- server/package.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/package.json b/client/package.json index d4789a3b..f43ab239 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-client", - "version": "0.8.0", + "version": "0.8.1", "description": "Client-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -23,7 +23,7 @@ "test:watch": "jest --config jest.config.cjs --watch" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.6.1", + "@modelcontextprotocol/sdk": "^1.8.0", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", diff --git a/package.json b/package.json index d323dda2..453fa71c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.8.0", + "version": "0.8.1", "description": "Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -36,8 +36,8 @@ "publish-all": "npm publish --workspaces --access public && npm publish --access public" }, "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.8.0", - "@modelcontextprotocol/inspector-server": "^0.8.0", + "@modelcontextprotocol/inspector-client": "^0.8.1", + "@modelcontextprotocol/inspector-server": "^0.8.1", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", diff --git a/server/package.json b/server/package.json index 9072f51d..9da3ca8e 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-server", - "version": "0.8.0", + "version": "0.8.1", "description": "Server-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -27,7 +27,7 @@ "typescript": "^5.6.2" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.6.1", + "@modelcontextprotocol/sdk": "^1.8.0", "cors": "^2.8.5", "express": "^4.21.0", "ws": "^8.18.0", From 3f73ec83a2fa5bec6fa478944374a358f0e342a6 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Thu, 3 Apr 2025 16:32:59 -0400 Subject: [PATCH 31/35] Bump version to 0.8.1 * package-lock.json --- package-lock.json | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index e626474a..42fec875 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.8.0", + "version": "0.8.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector", - "version": "0.8.0", + "version": "0.8.1", "license": "MIT", "workspaces": [ "client", "server" ], "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.8.0", - "@modelcontextprotocol/inspector-server": "^0.8.0", + "@modelcontextprotocol/inspector-client": "^0.8.1", + "@modelcontextprotocol/inspector-server": "^0.8.1", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", @@ -32,10 +32,10 @@ }, "client": { "name": "@modelcontextprotocol/inspector-client", - "version": "0.8.0", + "version": "0.8.1", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.6.1", + "@modelcontextprotocol/sdk": "^1.8.0", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", @@ -1329,11 +1329,14 @@ "link": true }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.6.1", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz", + "integrity": "sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", + "cross-spawn": "^7.0.3", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", @@ -9483,10 +9486,10 @@ }, "server": { "name": "@modelcontextprotocol/inspector-server", - "version": "0.8.0", + "version": "0.8.1", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.6.1", + "@modelcontextprotocol/sdk": "^1.8.0", "cors": "^2.8.5", "express": "^4.21.0", "ws": "^8.18.0", From b8ab30fdf3ae0fc7ce20565ebb5ee5ac954fbd5a Mon Sep 17 00:00:00 2001 From: yusheng chen Date: Fri, 4 Apr 2025 19:26:11 +0800 Subject: [PATCH 32/35] chore: move `@types/prismjs` to `devDependencies` --- client/package.json | 4 ++-- package-lock.json | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/client/package.json b/client/package.json index f43ab239..d1112109 100644 --- a/client/package.json +++ b/client/package.json @@ -32,9 +32,8 @@ "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.1", - "@radix-ui/react-tooltip": "^1.1.8", "@radix-ui/react-toast": "^1.2.6", - "@types/prismjs": "^1.26.5", + "@radix-ui/react-tooltip": "^1.1.8", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.4", @@ -55,6 +54,7 @@ "@testing-library/react": "^16.2.0", "@types/jest": "^29.5.14", "@types/node": "^22.7.5", + "@types/prismjs": "^1.26.5", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", "@types/serve-handler": "^6.1.4", diff --git a/package-lock.json b/package-lock.json index 42fec875..1dcc8d1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,6 @@ "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-toast": "^1.2.6", "@radix-ui/react-tooltip": "^1.1.8", - "@types/prismjs": "^1.26.5", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.4", @@ -70,6 +69,7 @@ "@testing-library/react": "^16.2.0", "@types/jest": "^29.5.14", "@types/node": "^22.7.5", + "@types/prismjs": "^1.26.5", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", "@types/serve-handler": "^6.1.4", @@ -3566,6 +3566,9 @@ }, "node_modules/@types/prismjs": { "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/prop-types": { From a506e4c4194fa8e69a93462880119519b87808e3 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Mon, 7 Apr 2025 12:19:34 -0400 Subject: [PATCH 33/35] Bump version to 0.8.2 * In package.json - Use inspector-[client|server] version 0.8.2 * In [client|server]/package.json - New version to 0.8.2 - Use typescript SDK version 1.9.0 * In package-lock.json - above changes - sdk dependency's version increased: node_modules/@modelcontextprotocol/sdk/node_modules/pkce-challenge --- client/package.json | 4 ++-- package-lock.json | 33 +++++++++++++++++++++------------ package.json | 6 +++--- server/package.json | 4 ++-- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/client/package.json b/client/package.json index f43ab239..30955a95 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-client", - "version": "0.8.1", + "version": "0.8.2", "description": "Client-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -23,7 +23,7 @@ "test:watch": "jest --config jest.config.cjs --watch" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", + "@modelcontextprotocol/sdk": "^1.9.0", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", diff --git a/package-lock.json b/package-lock.json index 42fec875..534c05d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.8.1", + "version": "0.8.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector", - "version": "0.8.1", + "version": "0.8.2", "license": "MIT", "workspaces": [ "client", "server" ], "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.8.1", - "@modelcontextprotocol/inspector-server": "^0.8.1", + "@modelcontextprotocol/inspector-client": "^0.8.2", + "@modelcontextprotocol/inspector-server": "^0.8.2", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", @@ -32,10 +32,10 @@ }, "client": { "name": "@modelcontextprotocol/inspector-client", - "version": "0.8.1", + "version": "0.8.2", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", + "@modelcontextprotocol/sdk": "^1.9.0", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", @@ -1329,9 +1329,9 @@ "link": true }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz", - "integrity": "sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.9.0.tgz", + "integrity": "sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -1340,7 +1340,7 @@ "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", - "pkce-challenge": "^4.1.0", + "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" @@ -1585,6 +1585,15 @@ "node": ">= 0.6" } }, + "node_modules/@modelcontextprotocol/sdk/node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/@modelcontextprotocol/sdk/node_modules/send": { "version": "1.1.0", "license": "MIT", @@ -9486,10 +9495,10 @@ }, "server": { "name": "@modelcontextprotocol/inspector-server", - "version": "0.8.1", + "version": "0.8.2", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", + "@modelcontextprotocol/sdk": "^1.9.0", "cors": "^2.8.5", "express": "^4.21.0", "ws": "^8.18.0", diff --git a/package.json b/package.json index 453fa71c..f9d22176 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.8.1", + "version": "0.8.2", "description": "Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -36,8 +36,8 @@ "publish-all": "npm publish --workspaces --access public && npm publish --access public" }, "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.8.1", - "@modelcontextprotocol/inspector-server": "^0.8.1", + "@modelcontextprotocol/inspector-client": "^0.8.2", + "@modelcontextprotocol/inspector-server": "^0.8.2", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", diff --git a/server/package.json b/server/package.json index 9da3ca8e..248db61b 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-server", - "version": "0.8.1", + "version": "0.8.2", "description": "Server-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -27,7 +27,7 @@ "typescript": "^5.6.2" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", + "@modelcontextprotocol/sdk": "^1.9.0", "cors": "^2.8.5", "express": "^4.21.0", "ws": "^8.18.0", From 6bcf1531c3c7ec8baad5ed54e25171a52172e212 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Mon, 7 Apr 2025 12:32:06 -0400 Subject: [PATCH 34/35] Bump version to 0.8.2 * In README - Point to inspector screenshot's rawcontent address so it doesn't appear as a broken image in the NPM package view. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c43f9432..d307b01c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ The MCP inspector is a developer tool for testing and debugging MCP servers. -![MCP Inspector Screenshot](mcp-inspector.png) +![MCP Inspector Screenshot](https://raw.githubusercontent.com/modelcontextprotocol/inspector/main/mcp-inspector.png) ## Running the Inspector From c5dc4ded5c9ac260cb12845b43245260e9c27ac9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 17:30:21 +0000 Subject: [PATCH 35/35] Bump vite in the npm_and_yarn group across 1 directory Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite). Updates `vite` from 5.4.16 to 5.4.17 - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.4.17/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.4.17/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 5.4.17 dependency-type: direct:development dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] --- package-lock.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 534c05d9..dacfab88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9133,7 +9133,9 @@ } }, "node_modules/vite": { - "version": "5.4.16", + "version": "5.4.17", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.17.tgz", + "integrity": "sha512-5+VqZryDj4wgCs55o9Lp+p8GE78TLVg0lasCH5xFZ4jacZjtqZa6JUw9/p0WeAojaOfncSM6v77InkFPGnvPvg==", "dev": true, "license": "MIT", "dependencies": {