From 46178ee85d3632f8a7033947c2b095dfb97133c5 Mon Sep 17 00:00:00 2001 From: haosenwang1018 Date: Thu, 2 Apr 2026 05:32:34 +0000 Subject: [PATCH] fix: paginate listTools responses --- src/client.ts | 12 ++++++++++-- tests/client.test.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/client.ts b/src/client.ts index 50731cf..23fb78e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -332,8 +332,16 @@ function createStdioTransport(config: StdioServerConfig): StdioClientTransport { */ export async function listTools(client: Client): Promise { return withRetry(async () => { - const result = await client.listTools(); - return result.tools.map((tool: Tool) => ({ + const tools: Tool[] = []; + let cursor: string | undefined; + + do { + const result = await client.listTools(cursor ? { cursor } : undefined); + tools.push(...result.tools); + cursor = result.nextCursor; + } while (cursor); + + return tools.map((tool: Tool) => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema as Record, diff --git a/tests/client.test.ts b/tests/client.test.ts index 9c53200..87900ab 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -15,6 +15,7 @@ import { isTransientError, getTimeoutMs, getConcurrencyLimit, + listTools, } from '../src/client'; describe('client', () => { @@ -139,6 +140,51 @@ describe('client', () => { }); }); + describe('listTools', () => { + test('collects all paginated tool pages', async () => { + const client = { + listTools: async (params?: { cursor?: string }) => { + if (!params?.cursor) { + return { + tools: [ + { + name: 'first-tool', + description: 'first page', + inputSchema: { type: 'object' }, + }, + ], + nextCursor: 'page-2', + }; + } + + expect(params.cursor).toBe('page-2'); + return { + tools: [ + { + name: 'second-tool', + description: 'second page', + inputSchema: { type: 'object', properties: { q: { type: 'string' } } }, + }, + ], + }; + }, + }; + + await expect(listTools(client as never)).resolves.toEqual([ + { + name: 'first-tool', + description: 'first page', + inputSchema: { type: 'object' }, + }, + { + name: 'second-tool', + description: 'second page', + inputSchema: { type: 'object', properties: { q: { type: 'string' } } }, + }, + ]); + }); + }); + describe('getConcurrencyLimit', () => { const originalEnv = process.env.MCP_CONCURRENCY;