Skip to content

Commit 801ae28

Browse files
committed
feat: add clear buttons for request history and server notifications
fixes modelcontextprotocol#780
1 parent 3772110 commit 801ae28

File tree

6 files changed

+105
-3
lines changed

6 files changed

+105
-3
lines changed

client/src/App.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ const App = () => {
200200
serverCapabilities,
201201
mcpClient,
202202
requestHistory,
203+
clearRequestHistory,
203204
makeRequest,
204205
sendNotification,
205206
handleCompletion,
@@ -736,6 +737,10 @@ const App = () => {
736737
await sendNotification({ method: "notifications/roots/list_changed" });
737738
};
738739

740+
const handleClearNotifications = () => {
741+
setNotifications([]);
742+
};
743+
739744
const sendLogLevelRequest = async (level: LoggingLevel) => {
740745
await sendMCPRequest(
741746
{
@@ -1102,6 +1107,8 @@ const App = () => {
11021107
<HistoryAndNotifications
11031108
requestHistory={requestHistory}
11041109
serverNotifications={notifications}
1110+
onClearHistory={clearRequestHistory}
1111+
onClearNotifications={handleClearNotifications}
11051112
/>
11061113
</div>
11071114
</div>

client/src/__tests__/App.config.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ jest.mock("../lib/hooks/useConnection", () => ({
4949
serverCapabilities: null,
5050
mcpClient: null,
5151
requestHistory: [],
52+
clearRequestHistory: jest.fn(),
5253
makeRequest: jest.fn(),
5354
sendNotification: jest.fn(),
5455
handleCompletion: jest.fn(),

client/src/__tests__/App.routing.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const disconnectedConnectionState = {
4242
serverCapabilities: null,
4343
mcpClient: null,
4444
requestHistory: [],
45+
clearRequestHistory: jest.fn(),
4546
makeRequest: jest.fn(),
4647
sendNotification: jest.fn(),
4748
handleCompletion: jest.fn(),

client/src/components/HistoryAndNotifications.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
22
import { useState } from "react";
33
import JsonView from "./JsonView";
4+
import { Button } from "@/components/ui/button";
45

56
const HistoryAndNotifications = ({
67
requestHistory,
78
serverNotifications,
9+
onClearHistory,
10+
onClearNotifications,
811
}: {
912
requestHistory: Array<{ request: string; response?: string }>;
1013
serverNotifications: ServerNotification[];
14+
onClearHistory?: () => void;
15+
onClearNotifications?: () => void;
1116
}) => {
1217
const [expandedRequests, setExpandedRequests] = useState<{
1318
[key: number]: boolean;
@@ -27,7 +32,17 @@ const HistoryAndNotifications = ({
2732
return (
2833
<div className="bg-card overflow-hidden flex h-full">
2934
<div className="flex-1 overflow-y-auto p-4 border-r">
30-
<h2 className="text-lg font-semibold mb-4">History</h2>
35+
<div className="flex items-center justify-between mb-4">
36+
<h2 className="text-lg font-semibold">History</h2>
37+
<Button
38+
variant="outline"
39+
size="sm"
40+
onClick={onClearHistory}
41+
disabled={requestHistory.length === 0}
42+
>
43+
Clear
44+
</Button>
45+
</div>
3146
{requestHistory.length === 0 ? (
3247
<p className="text-sm text-gray-500 dark:text-gray-400 italic">
3348
No history yet
@@ -93,7 +108,17 @@ const HistoryAndNotifications = ({
93108
)}
94109
</div>
95110
<div className="flex-1 overflow-y-auto p-4">
96-
<h2 className="text-lg font-semibold mb-4">Server Notifications</h2>
111+
<div className="flex items-center justify-between mb-4">
112+
<h2 className="text-lg font-semibold">Server Notifications</h2>
113+
<Button
114+
variant="outline"
115+
size="sm"
116+
onClick={onClearNotifications}
117+
disabled={serverNotifications.length === 0}
118+
>
119+
Clear
120+
</Button>
121+
</div>
97122
{serverNotifications.length === 0 ? (
98123
<p className="text-sm text-gray-500 dark:text-gray-400 italic">
99124
No notifications yet

client/src/components/__tests__/HistoryAndNotifications.test.tsx

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { render, screen, fireEvent } from "@testing-library/react";
1+
import { render, screen, fireEvent, within } from "@testing-library/react";
2+
import { useState } from "react";
23
import { describe, it, expect, jest } from "@jest/globals";
34
import HistoryAndNotifications from "../HistoryAndNotifications";
45
import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
@@ -223,4 +224,66 @@ describe("HistoryAndNotifications", () => {
223224
expect(screen.getByText("No history yet")).toBeTruthy();
224225
expect(screen.getByText("No notifications yet")).toBeTruthy();
225226
});
227+
228+
it("clears request history when Clear is clicked", () => {
229+
const Wrapper = () => {
230+
const [history, setHistory] = useState(mockRequestHistory);
231+
return (
232+
<HistoryAndNotifications
233+
requestHistory={history}
234+
serverNotifications={[]}
235+
onClearHistory={() => setHistory([])}
236+
/>
237+
);
238+
};
239+
240+
render(<Wrapper />);
241+
242+
// Verify items are present initially
243+
expect(screen.getByText("2. test/method2")).toBeTruthy();
244+
expect(screen.getByText("1. test/method1")).toBeTruthy();
245+
246+
// Click Clear in History header (scoped by the History heading's container)
247+
const historyHeader = screen.getByText("History");
248+
const historyHeaderContainer = historyHeader.parentElement as HTMLElement;
249+
const historyClearButton = within(historyHeaderContainer).getByRole(
250+
"button",
251+
{ name: "Clear" },
252+
);
253+
fireEvent.click(historyClearButton);
254+
255+
// History should now be empty
256+
expect(screen.getByText("No history yet")).toBeTruthy();
257+
});
258+
259+
it("clears server notifications when Clear is clicked", () => {
260+
const Wrapper = () => {
261+
const [notifications, setNotifications] =
262+
useState<ServerNotification[]>(mockNotifications);
263+
return (
264+
<HistoryAndNotifications
265+
requestHistory={[]}
266+
serverNotifications={notifications}
267+
onClearNotifications={() => setNotifications([])}
268+
/>
269+
);
270+
};
271+
272+
render(<Wrapper />);
273+
274+
// Verify items are present initially
275+
expect(screen.getByText("2. notifications/progress")).toBeTruthy();
276+
expect(screen.getByText("1. notifications/message")).toBeTruthy();
277+
278+
// Click Clear in Server Notifications header (scoped by its heading's container)
279+
const notifHeader = screen.getByText("Server Notifications");
280+
const notifHeaderContainer = notifHeader.parentElement as HTMLElement;
281+
const notifClearButton = within(notifHeaderContainer).getByRole("button", {
282+
name: "Clear",
283+
});
284+
fireEvent.click(notifClearButton);
285+
286+
// Notifications should now be empty
287+
expect(screen.getByText("No notifications yet")).toBeTruthy();
288+
});
226289
});

client/src/lib/hooks/useConnection.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,11 +653,16 @@ export function useConnection({
653653
setServerCapabilities(null);
654654
};
655655

656+
const clearRequestHistory = () => {
657+
setRequestHistory([]);
658+
};
659+
656660
return {
657661
connectionStatus,
658662
serverCapabilities,
659663
mcpClient,
660664
requestHistory,
665+
clearRequestHistory,
661666
makeRequest,
662667
sendNotification,
663668
handleCompletion,

0 commit comments

Comments
 (0)