Skip to content

Commit 61129a0

Browse files
committed
Fixed list management (including broken "clear" behavior in web ux)
1 parent 03199d2 commit 61129a0

3 files changed

Lines changed: 112 additions & 20 deletions

File tree

core/__tests__/inspectorClient.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,73 @@ describe("InspectorClient", () => {
317317

318318
expect(changeCount).toBeGreaterThan(0);
319319
});
320+
321+
it("getMessages(predicate) returns only matching entries", async () => {
322+
client = new InspectorClient(
323+
{
324+
type: "stdio",
325+
command: serverCommand.command,
326+
args: serverCommand.args,
327+
},
328+
{
329+
environment: { transport: createTransportNode },
330+
autoSyncLists: false,
331+
},
332+
);
333+
334+
await client.connect();
335+
await client.listAllTools();
336+
337+
const all = client.getMessages();
338+
expect(all.length).toBeGreaterThan(0);
339+
340+
const requests = client.getMessages((m) => m.direction === "request");
341+
expect(requests.length).toBeLessThanOrEqual(all.length);
342+
expect(requests.every((m) => m.direction === "request")).toBe(true);
343+
344+
const notifications = client.getMessages(
345+
(m) => m.direction === "notification",
346+
);
347+
expect(notifications.every((m) => m.direction === "notification")).toBe(
348+
true,
349+
);
350+
});
351+
352+
it("clearMessages(predicate) removes only matching entries and dispatches messagesChange", async () => {
353+
client = new InspectorClient(
354+
{
355+
type: "stdio",
356+
command: serverCommand.command,
357+
args: serverCommand.args,
358+
},
359+
{
360+
environment: { transport: createTransportNode },
361+
autoSyncLists: false,
362+
},
363+
);
364+
365+
await client.connect();
366+
await client.listAllTools();
367+
368+
const before = client.getMessages().length;
369+
expect(before).toBeGreaterThan(0);
370+
371+
let changeCount = 0;
372+
client.addEventListener("messagesChange", () => {
373+
changeCount++;
374+
});
375+
376+
client.clearMessages((m) => m.direction === "request");
377+
const after = client.getMessages();
378+
expect(after.length).toBeLessThan(before);
379+
expect(after.every((m) => m.direction !== "request")).toBe(true);
380+
expect(changeCount).toBe(1);
381+
382+
client.clearMessages();
383+
expect(client.getMessages().length).toBe(0);
384+
// messagesChange fires again only if the list actually changed (e.g. if any non-request entries remained)
385+
expect(changeCount).toBeGreaterThanOrEqual(1);
386+
});
320387
});
321388

322389
describe("Fetch Request Tracking", () => {

core/mcp/inspectorClient.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,9 +1223,13 @@ export class InspectorClient extends InspectorClientEventTarget {
12231223
}
12241224

12251225
/**
1226-
* Get all messages
1226+
* Get messages. When `predicate` is provided, returns only entries for which
1227+
* predicate returns true. When omitted, returns all messages.
12271228
*/
1228-
getMessages(): MessageEntry[] {
1229+
getMessages(predicate?: (entry: MessageEntry) => boolean): MessageEntry[] {
1230+
if (predicate) {
1231+
return this.messages.filter(predicate);
1232+
}
12291233
return [...this.messages];
12301234
}
12311235

@@ -1318,6 +1322,18 @@ export class InspectorClient extends InspectorClientEventTarget {
13181322
this.dispatchTypedEvent("promptsChange", this.prompts);
13191323
}
13201324

1325+
/**
1326+
* Remove messages from history. When `predicate` is provided, removes only entries
1327+
* for which predicate returns true. When omitted, clears all messages.
1328+
*/
1329+
clearMessages(predicate?: (entry: MessageEntry) => boolean): void {
1330+
const before = this.messages.length;
1331+
this.messages = predicate ? this.messages.filter((m) => !predicate(m)) : [];
1332+
if (this.messages.length !== before) {
1333+
this.dispatchTypedEvent("messagesChange");
1334+
}
1335+
}
1336+
13211337
/**
13221338
* Get task capabilities from server
13231339
* @returns Task capabilities or undefined if not supported

web/src/App.tsx

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { useInspectorClient } from "@modelcontextprotocol/inspector-core/react/u
3030
import {
3131
InspectorClient,
3232
type InspectorClientOptions,
33+
type MessageEntry,
3334
} from "@modelcontextprotocol/inspector-core/mcp/index.js";
3435
import {
3536
createWebEnvironment,
@@ -121,6 +122,11 @@ const filterReservedMetadata = (
121122
);
122123
};
123124

125+
// Shared predicates for history/notifications views; used for both getMessages (display) and clearMessages (clear)
126+
const notificationsMessagePredicate = (m: MessageEntry) =>
127+
m.direction === "notification";
128+
const historyMessagePredicate = (m: MessageEntry) => m.direction === "request";
129+
124130
const App = () => {
125131
const [resourceContent, setResourceContent] = useState<string>("");
126132
const [resourceContentMap, setResourceContentMap] = useState<
@@ -553,13 +559,13 @@ const App = () => {
553559
}
554560
}, [ensureInspectorClient, toast]);
555561

556-
// Extract server notifications from messages
557-
// Use useMemo to stabilize the array reference and prevent infinite loops
562+
// Extract server notifications from messages (same predicate as clearMessages for this view)
558563
const extractedNotifications = useMemo(() => {
559-
return inspectorMessages
560-
.filter((msg) => msg.direction === "notification" && msg.message)
564+
if (!inspectorClient) return [];
565+
return inspectorClient
566+
.getMessages(notificationsMessagePredicate)
561567
.map((msg) => msg.message as ServerNotification);
562-
}, [inspectorMessages]);
568+
}, [inspectorClient, inspectorMessages]);
563569

564570
// Use ref to track previous serialized value to prevent infinite loops
565571
const previousNotificationsRef = useRef<string>("[]");
@@ -731,21 +737,20 @@ const App = () => {
731737
serverCapabilities?.completions !== undefined &&
732738
serverCapabilities.completions !== null;
733739

734-
// Map MCP protocol messages (requests/responses) to requestHistory format
735-
// Filter out notifications - those go in the Notifications tab
740+
// Request history (same predicate as clearMessages for this view)
736741
const requestHistory = useMemo(() => {
737-
return inspectorMessages
738-
.filter((msg) => msg.direction === "request")
739-
.map((msg) => ({
740-
request: JSON.stringify(msg.message),
741-
response: msg.response ? JSON.stringify(msg.response) : undefined,
742-
}));
743-
}, [inspectorMessages]);
742+
if (!inspectorClient) return [];
743+
return inspectorClient.getMessages(historyMessagePredicate).map((msg) => ({
744+
request: JSON.stringify(msg.message),
745+
response: msg.response ? JSON.stringify(msg.response) : undefined,
746+
}));
747+
}, [inspectorClient, inspectorMessages]);
744748

745749
const clearRequestHistory = useCallback(() => {
746-
// InspectorClient doesn't have a clear method, so this is a no-op
747-
// The history is managed internally by InspectorClient
748-
}, []);
750+
if (inspectorClient) {
751+
inspectorClient.clearMessages(historyMessagePredicate);
752+
}
753+
}, [inspectorClient]);
749754

750755
useEffect(() => {
751756
if (serverCapabilities) {
@@ -1498,7 +1503,11 @@ const App = () => {
14981503
};
14991504

15001505
const handleClearNotifications = () => {
1501-
setNotifications([]);
1506+
if (inspectorClient) {
1507+
inspectorClient.clearMessages(notificationsMessagePredicate);
1508+
} else {
1509+
setNotifications([]);
1510+
}
15021511
};
15031512

15041513
const sendLogLevelRequest = async (level: LoggingLevel) => {

0 commit comments

Comments
 (0)