From 509ca909bb00768754008ebae9ce2b80d9e52414 Mon Sep 17 00:00:00 2001 From: evalstate <1936278+evalstate@users.noreply.github.com> Date: Wed, 11 Jun 2025 12:01:36 +0100 Subject: [PATCH 1/7] make prompt and tool listing display consistent --- client/src/components/PromptsTab.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/components/PromptsTab.tsx b/client/src/components/PromptsTab.tsx index 66c15d8f..466b3d52 100644 --- a/client/src/components/PromptsTab.tsx +++ b/client/src/components/PromptsTab.tsx @@ -98,12 +98,12 @@ const PromptsTab = ({ setPromptArgs({}); }} renderItem={(prompt) => ( - <> +
{prompt.name} - + {prompt.description} - +
)} title="Prompts" buttonText={nextCursor ? "List More Prompts" : "List Prompts"} From 6034ea36dc405a48d7bc05337683afa165cb72dc Mon Sep 17 00:00:00 2001 From: Richard Michael Date: Sat, 14 Jun 2025 22:31:30 -0700 Subject: [PATCH 2/7] Fix expansion state tracking in Server Notifications --- client/src/components/History.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/client/src/components/History.tsx b/client/src/components/History.tsx index 78394dea..34c035f5 100644 --- a/client/src/components/History.tsx +++ b/client/src/components/History.tsx @@ -110,15 +110,27 @@ const HistoryAndNotifications = ({ >
toggleNotificationExpansion(index)} + onClick={() => + toggleNotificationExpansion( + serverNotifications.length - 1 - index, + ) + } > {serverNotifications.length - index}.{" "} {notification.method} - {expandedNotifications[index] ? "▼" : "▶"} + + {expandedNotifications[ + serverNotifications.length - 1 - index + ] + ? "▼" + : "▶"} +
- {expandedNotifications[index] && ( + {expandedNotifications[ + serverNotifications.length - 1 - index + ] && (
From 7eaa948e9800230237d2b701482a943183e53561 Mon Sep 17 00:00:00 2001 From: Richard Michael Date: Sat, 14 Jun 2025 23:29:19 -0700 Subject: [PATCH 3/7] Add tests for HistoryAndNotifications component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tests cover basic rendering, numbering, expansion functionality, and expansion state persistence when new items are added. Includes regression test for notification expansion state tracking bug. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../src/components/__tests__/History.test.tsx | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 client/src/components/__tests__/History.test.tsx diff --git a/client/src/components/__tests__/History.test.tsx b/client/src/components/__tests__/History.test.tsx new file mode 100644 index 00000000..19cae083 --- /dev/null +++ b/client/src/components/__tests__/History.test.tsx @@ -0,0 +1,216 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import { describe, it, expect, jest } from "@jest/globals"; +import HistoryAndNotifications from "../History"; +import { ServerNotification } from "@modelcontextprotocol/sdk/types.js"; + +// Mock JsonView component +jest.mock("../JsonView", () => { + return function JsonView({ data }: { data: string }) { + return
{data}
; + }; +}); + +describe("HistoryAndNotifications", () => { + const mockRequestHistory = [ + { + request: JSON.stringify({ method: "test/method1", params: {} }), + response: JSON.stringify({ result: "success" }), + }, + { + request: JSON.stringify({ method: "test/method2", params: {} }), + response: JSON.stringify({ result: "success" }), + }, + ]; + + const mockNotifications: ServerNotification[] = [ + { + method: "notification/test1", + params: { message: "First notification" }, + }, + { + method: "notification/test2", + params: { message: "Second notification" }, + }, + ]; + + it("renders history and notifications sections", () => { + render( + , + ); + + expect(screen.getByText("History")).toBeInTheDocument(); + expect(screen.getByText("Server Notifications")).toBeInTheDocument(); + }); + + it("displays request history items with correct numbering", () => { + render( + , + ); + + // Items should be numbered in reverse order (newest first) + expect(screen.getByText("2. test/method2")).toBeInTheDocument(); + expect(screen.getByText("1. test/method1")).toBeInTheDocument(); + }); + + it("displays server notifications with correct numbering", () => { + render( + , + ); + + // Items should be numbered in reverse order (newest first) + expect(screen.getByText("2. notification/test2")).toBeInTheDocument(); + expect(screen.getByText("1. notification/test1")).toBeInTheDocument(); + }); + + it("expands and collapses request items when clicked", () => { + render( + , + ); + + const firstRequestHeader = screen.getByText("2. test/method2"); + + // Initially collapsed - should show ▶ arrows (there are multiple) + expect(screen.getAllByText("▶")).toHaveLength(2); + expect(screen.queryByText("Request:")).not.toBeInTheDocument(); + + // Click to expand + fireEvent.click(firstRequestHeader); + + // Should now be expanded - one ▼ and one ▶ + expect(screen.getByText("▼")).toBeInTheDocument(); + expect(screen.getAllByText("▶")).toHaveLength(1); + expect(screen.getByText("Request:")).toBeInTheDocument(); + expect(screen.getByText("Response:")).toBeInTheDocument(); + }); + + it("expands and collapses notification items when clicked", () => { + render( + , + ); + + const firstNotificationHeader = screen.getByText("2. notification/test2"); + + // Initially collapsed + expect(screen.getAllByText("▶")).toHaveLength(2); + expect(screen.queryByText("Details:")).not.toBeInTheDocument(); + + // Click to expand + fireEvent.click(firstNotificationHeader); + + // Should now be expanded + expect(screen.getByText("▼")).toBeInTheDocument(); + expect(screen.getAllByText("▶")).toHaveLength(1); + expect(screen.getByText("Details:")).toBeInTheDocument(); + }); + + it("maintains expanded state when new notifications are added", () => { + const { rerender } = render( + , + ); + + // Find and expand the older notification (should be "1. notification/test1") + const olderNotificationHeader = screen.getByText("1. notification/test1"); + fireEvent.click(olderNotificationHeader); + + // Verify it's expanded + expect(screen.getByText("Details:")).toBeInTheDocument(); + + // Add a new notification at the beginning (simulating real behavior) + const newNotifications: ServerNotification[] = [ + { + method: "notification/new", + params: { message: "New notification" }, + }, + ...mockNotifications, + ]; + + // Re-render with new notifications + rerender( + , + ); + + // The original notification should still be expanded + // It's now numbered as "2. notification/test1" due to the new item + expect(screen.getByText("3. notification/test2")).toBeInTheDocument(); + expect(screen.getByText("2. notification/test1")).toBeInTheDocument(); + expect(screen.getByText("1. notification/new")).toBeInTheDocument(); + + // The originally expanded notification should still show its details + expect(screen.getByText("Details:")).toBeInTheDocument(); + }); + + it("maintains expanded state when new requests are added", () => { + const { rerender } = render( + , + ); + + // Find and expand the older request (should be "1. test/method1") + const olderRequestHeader = screen.getByText("1. test/method1"); + fireEvent.click(olderRequestHeader); + + // Verify it's expanded + expect(screen.getByText("Request:")).toBeInTheDocument(); + expect(screen.getByText("Response:")).toBeInTheDocument(); + + // Add a new request at the beginning + const newRequestHistory = [ + { + request: JSON.stringify({ method: "test/new_method", params: {} }), + response: JSON.stringify({ result: "new success" }), + }, + ...mockRequestHistory, + ]; + + // Re-render with new request history + rerender( + , + ); + + // The original request should still be expanded + // It's now numbered as "2. test/method1" due to the new item + expect(screen.getByText("3. test/method2")).toBeInTheDocument(); + expect(screen.getByText("2. test/method1")).toBeInTheDocument(); + expect(screen.getByText("1. test/new_method")).toBeInTheDocument(); + + // The originally expanded request should still show its details + expect(screen.getByText("Request:")).toBeInTheDocument(); + expect(screen.getByText("Response:")).toBeInTheDocument(); + }); + + it("displays empty state messages when no data is available", () => { + render( + , + ); + + expect(screen.getByText("No history yet")).toBeInTheDocument(); + expect(screen.getByText("No notifications yet")).toBeInTheDocument(); + }); +}); From 78eaa177a49183cf1d95fe31055237aebf4aa196 Mon Sep 17 00:00:00 2001 From: Richard Michael Date: Sat, 14 Jun 2025 23:34:41 -0700 Subject: [PATCH 4/7] Rename History file to HistoryAndNotifications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename History.tsx to HistoryAndNotifications.tsx for consistency with component name and update imports. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- client/src/App.tsx | 2 +- ...istory.tsx => HistoryAndNotifications.tsx} | 0 ...t.tsx => HistoryAndNotifications.test.tsx} | 88 +++++++++++-------- 3 files changed, 50 insertions(+), 40 deletions(-) rename client/src/components/{History.tsx => HistoryAndNotifications.tsx} (100%) rename client/src/components/__tests__/{History.test.tsx => HistoryAndNotifications.test.tsx} (66%) diff --git a/client/src/App.tsx b/client/src/App.tsx index 26eb44c5..3ceafcae 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -52,7 +52,7 @@ import { z } from "zod"; import "./App.css"; import AuthDebugger from "./components/AuthDebugger"; import ConsoleTab from "./components/ConsoleTab"; -import HistoryAndNotifications from "./components/History"; +import HistoryAndNotifications from "./components/HistoryAndNotifications"; import PingTab from "./components/PingTab"; import PromptsTab, { Prompt } from "./components/PromptsTab"; import ResourcesTab from "./components/ResourcesTab"; diff --git a/client/src/components/History.tsx b/client/src/components/HistoryAndNotifications.tsx similarity index 100% rename from client/src/components/History.tsx rename to client/src/components/HistoryAndNotifications.tsx diff --git a/client/src/components/__tests__/History.test.tsx b/client/src/components/__tests__/HistoryAndNotifications.test.tsx similarity index 66% rename from client/src/components/__tests__/History.test.tsx rename to client/src/components/__tests__/HistoryAndNotifications.test.tsx index 19cae083..42c58519 100644 --- a/client/src/components/__tests__/History.test.tsx +++ b/client/src/components/__tests__/HistoryAndNotifications.test.tsx @@ -1,7 +1,6 @@ import { render, screen, fireEvent } from "@testing-library/react"; -import "@testing-library/jest-dom"; import { describe, it, expect, jest } from "@jest/globals"; -import HistoryAndNotifications from "../History"; +import HistoryAndNotifications from "../HistoryAndNotifications"; import { ServerNotification } from "@modelcontextprotocol/sdk/types.js"; // Mock JsonView component @@ -25,12 +24,19 @@ describe("HistoryAndNotifications", () => { const mockNotifications: ServerNotification[] = [ { - method: "notification/test1", - params: { message: "First notification" }, + method: "notifications/message", + params: { + level: "info" as const, + message: "First notification", + }, }, { - method: "notification/test2", - params: { message: "Second notification" }, + method: "notifications/progress", + params: { + progressToken: "test-token", + progress: 50, + message: "Second notification", + }, }, ]; @@ -42,8 +48,8 @@ describe("HistoryAndNotifications", () => { />, ); - expect(screen.getByText("History")).toBeInTheDocument(); - expect(screen.getByText("Server Notifications")).toBeInTheDocument(); + expect(screen.getByText("History")).toBeTruthy(); + expect(screen.getByText("Server Notifications")).toBeTruthy(); }); it("displays request history items with correct numbering", () => { @@ -55,8 +61,8 @@ describe("HistoryAndNotifications", () => { ); // Items should be numbered in reverse order (newest first) - expect(screen.getByText("2. test/method2")).toBeInTheDocument(); - expect(screen.getByText("1. test/method1")).toBeInTheDocument(); + expect(screen.getByText("2. test/method2")).toBeTruthy(); + expect(screen.getByText("1. test/method1")).toBeTruthy(); }); it("displays server notifications with correct numbering", () => { @@ -68,8 +74,8 @@ describe("HistoryAndNotifications", () => { ); // Items should be numbered in reverse order (newest first) - expect(screen.getByText("2. notification/test2")).toBeInTheDocument(); - expect(screen.getByText("1. notification/test1")).toBeInTheDocument(); + expect(screen.getByText("2. notifications/progress")).toBeTruthy(); + expect(screen.getByText("1. notifications/message")).toBeTruthy(); }); it("expands and collapses request items when clicked", () => { @@ -84,16 +90,16 @@ describe("HistoryAndNotifications", () => { // Initially collapsed - should show ▶ arrows (there are multiple) expect(screen.getAllByText("▶")).toHaveLength(2); - expect(screen.queryByText("Request:")).not.toBeInTheDocument(); + expect(screen.queryByText("Request:")).toBeNull(); // Click to expand fireEvent.click(firstRequestHeader); // Should now be expanded - one ▼ and one ▶ - expect(screen.getByText("▼")).toBeInTheDocument(); + expect(screen.getByText("▼")).toBeTruthy(); expect(screen.getAllByText("▶")).toHaveLength(1); - expect(screen.getByText("Request:")).toBeInTheDocument(); - expect(screen.getByText("Response:")).toBeInTheDocument(); + expect(screen.getByText("Request:")).toBeTruthy(); + expect(screen.getByText("Response:")).toBeTruthy(); }); it("expands and collapses notification items when clicked", () => { @@ -104,19 +110,21 @@ describe("HistoryAndNotifications", () => { />, ); - const firstNotificationHeader = screen.getByText("2. notification/test2"); + const firstNotificationHeader = screen.getByText( + "2. notifications/progress", + ); // Initially collapsed expect(screen.getAllByText("▶")).toHaveLength(2); - expect(screen.queryByText("Details:")).not.toBeInTheDocument(); + expect(screen.queryByText("Details:")).toBeNull(); // Click to expand fireEvent.click(firstNotificationHeader); // Should now be expanded - expect(screen.getByText("▼")).toBeInTheDocument(); + expect(screen.getByText("▼")).toBeTruthy(); expect(screen.getAllByText("▶")).toHaveLength(1); - expect(screen.getByText("Details:")).toBeInTheDocument(); + expect(screen.getByText("Details:")).toBeTruthy(); }); it("maintains expanded state when new notifications are added", () => { @@ -127,18 +135,20 @@ describe("HistoryAndNotifications", () => { />, ); - // Find and expand the older notification (should be "1. notification/test1") - const olderNotificationHeader = screen.getByText("1. notification/test1"); + // Find and expand the older notification (should be "1. notifications/message") + const olderNotificationHeader = screen.getByText( + "1. notifications/message", + ); fireEvent.click(olderNotificationHeader); // Verify it's expanded - expect(screen.getByText("Details:")).toBeInTheDocument(); + expect(screen.getByText("Details:")).toBeTruthy(); // Add a new notification at the beginning (simulating real behavior) const newNotifications: ServerNotification[] = [ { - method: "notification/new", - params: { message: "New notification" }, + method: "notifications/resources/updated", + params: { uri: "file://test.txt" }, }, ...mockNotifications, ]; @@ -152,13 +162,13 @@ describe("HistoryAndNotifications", () => { ); // The original notification should still be expanded - // It's now numbered as "2. notification/test1" due to the new item - expect(screen.getByText("3. notification/test2")).toBeInTheDocument(); - expect(screen.getByText("2. notification/test1")).toBeInTheDocument(); - expect(screen.getByText("1. notification/new")).toBeInTheDocument(); + // It's now numbered as "2. notifications/message" due to the new item + expect(screen.getByText("3. notifications/progress")).toBeTruthy(); + expect(screen.getByText("2. notifications/message")).toBeTruthy(); + expect(screen.getByText("1. notifications/resources/updated")).toBeTruthy(); // The originally expanded notification should still show its details - expect(screen.getByText("Details:")).toBeInTheDocument(); + expect(screen.getByText("Details:")).toBeTruthy(); }); it("maintains expanded state when new requests are added", () => { @@ -174,8 +184,8 @@ describe("HistoryAndNotifications", () => { fireEvent.click(olderRequestHeader); // Verify it's expanded - expect(screen.getByText("Request:")).toBeInTheDocument(); - expect(screen.getByText("Response:")).toBeInTheDocument(); + expect(screen.getByText("Request:")).toBeTruthy(); + expect(screen.getByText("Response:")).toBeTruthy(); // Add a new request at the beginning const newRequestHistory = [ @@ -196,13 +206,13 @@ describe("HistoryAndNotifications", () => { // The original request should still be expanded // It's now numbered as "2. test/method1" due to the new item - expect(screen.getByText("3. test/method2")).toBeInTheDocument(); - expect(screen.getByText("2. test/method1")).toBeInTheDocument(); - expect(screen.getByText("1. test/new_method")).toBeInTheDocument(); + expect(screen.getByText("3. test/method2")).toBeTruthy(); + expect(screen.getByText("2. test/method1")).toBeTruthy(); + expect(screen.getByText("1. test/new_method")).toBeTruthy(); // The originally expanded request should still show its details - expect(screen.getByText("Request:")).toBeInTheDocument(); - expect(screen.getByText("Response:")).toBeInTheDocument(); + expect(screen.getByText("Request:")).toBeTruthy(); + expect(screen.getByText("Response:")).toBeTruthy(); }); it("displays empty state messages when no data is available", () => { @@ -210,7 +220,7 @@ describe("HistoryAndNotifications", () => { , ); - expect(screen.getByText("No history yet")).toBeInTheDocument(); - expect(screen.getByText("No notifications yet")).toBeInTheDocument(); + expect(screen.getByText("No history yet")).toBeTruthy(); + expect(screen.getByText("No notifications yet")).toBeTruthy(); }); }); From c0e9899cd4716e231aaadc34dc893ba9022c4217 Mon Sep 17 00:00:00 2001 From: Manabu McCloskey Date: Mon, 23 Jun 2025 22:58:54 +0000 Subject: [PATCH 5/7] fix connection refused error message Signed-off-by: Manabu McCloskey --- server/src/mcpProxy.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/server/src/mcpProxy.ts b/server/src/mcpProxy.ts index 9dee9de4..d37e312f 100644 --- a/server/src/mcpProxy.ts +++ b/server/src/mcpProxy.ts @@ -6,11 +6,7 @@ function onClientError(error: Error) { } function onServerError(error: Error) { - if ( - (error?.message && - error.message.includes("Error POSTing to endpoint (HTTP 404)")) || - (error?.cause && JSON.stringify(error.cause).includes("ECONNREFUSED")) - ) { + if (error?.cause && JSON.stringify(error.cause).includes("ECONNREFUSED")) { console.error("Connection refused. Is the MCP server running?"); } else { console.error("Error from MCP server:", error); From 5868c6bad6075e7c9dd49c3eb9b4ef946ceed53f Mon Sep 17 00:00:00 2001 From: Manabu McCloskey Date: Thu, 3 Jul 2025 14:48:46 +0000 Subject: [PATCH 6/7] print explicit 404 message Signed-off-by: Manabu McCloskey --- server/src/mcpProxy.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/mcpProxy.ts b/server/src/mcpProxy.ts index d37e312f..664f1711 100644 --- a/server/src/mcpProxy.ts +++ b/server/src/mcpProxy.ts @@ -8,6 +8,8 @@ function onClientError(error: Error) { function onServerError(error: Error) { if (error?.cause && JSON.stringify(error.cause).includes("ECONNREFUSED")) { console.error("Connection refused. Is the MCP server running?"); + } else if (error.message && error.message.includes("404")) { + console.error("Error accessing endpoint (HTTP 404)"); } else { console.error("Error from MCP server:", error); } From d1e2498ad0e3fc0574eafd4288f6fe4572ae70e3 Mon Sep 17 00:00:00 2001 From: Richard Michael Date: Thu, 3 Jul 2025 21:28:00 -0700 Subject: [PATCH 7/7] Add engines to `package-lock.json` --- package-lock.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package-lock.json b/package-lock.json index ce3b7ba7..075b0a28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,9 @@ "prettier": "3.3.3", "rimraf": "^6.0.1", "typescript": "^5.4.2" + }, + "engines": { + "node": ">=22.7.5" } }, "cli": {