Skip to content

Commit c19d4c0

Browse files
committed
fix: debounce MCP list changed notifications
1 parent db83829 commit c19d4c0

2 files changed

Lines changed: 44 additions & 1 deletion

File tree

packages/server/src/server/mcp.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ import { getCompleter, isCompletable } from './completable.js';
4747
import type { ServerOptions } from './server.js';
4848
import { Server } from './server.js';
4949

50+
const DEFAULT_DEBOUNCED_NOTIFICATION_METHODS = [
51+
'notifications/resources/list_changed',
52+
'notifications/tools/list_changed',
53+
'notifications/prompts/list_changed'
54+
];
55+
5056
/**
5157
* High-level MCP server that provides a simpler API for working with resources, tools, and prompts.
5258
* For advanced usage (like sending notifications or setting custom request handlers), use the underlying
@@ -75,7 +81,12 @@ export class McpServer {
7581
private _experimental?: { tasks: ExperimentalMcpServerTasks };
7682

7783
constructor(serverInfo: Implementation, options?: ServerOptions) {
78-
this.server = new Server(serverInfo, options);
84+
this.server = new Server(serverInfo, {
85+
...options,
86+
debouncedNotificationMethods: [
87+
...new Set([...DEFAULT_DEBOUNCED_NOTIFICATION_METHODS, ...(options?.debouncedNotificationMethods ?? [])])
88+
]
89+
});
7990
}
8091

8192
/**

test/integration/test/server/mcp.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,38 @@ describe('Zod v4', () => {
809809
]);
810810
});
811811

812+
test('should debounce synchronous tool list changed notifications', async () => {
813+
const mcpServer = new McpServer({
814+
name: 'test server',
815+
version: '1.0'
816+
});
817+
const notifications: Notification[] = [];
818+
const client = new Client({
819+
name: 'test client',
820+
version: '1.0'
821+
});
822+
client.fallbackNotificationHandler = async notification => {
823+
notifications.push(notification);
824+
};
825+
826+
mcpServer.registerTool('initial', {}, async () => ({
827+
content: [{ type: 'text', text: 'Initial response' }]
828+
}));
829+
830+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
831+
await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);
832+
833+
for (let i = 0; i < 20; i++) {
834+
mcpServer.registerTool(`tool-${i}`, {}, async () => ({
835+
content: [{ type: 'text', text: `Tool ${i} response` }]
836+
}));
837+
}
838+
839+
await new Promise(process.nextTick);
840+
841+
expect(notifications).toMatchObject([{ method: 'notifications/tools/list_changed' }]);
842+
});
843+
812844
/***
813845
* Test: listChanged capability should default to true when not specified
814846
*/

0 commit comments

Comments
 (0)