diff --git a/packages/server/src/server/mcp.ts b/packages/server/src/server/mcp.ts index fb45fd5db6..ea02d3d030 100644 --- a/packages/server/src/server/mcp.ts +++ b/packages/server/src/server/mcp.ts @@ -138,6 +138,8 @@ export class McpServer { (): ListToolsResult => ({ tools: Object.entries(this._registeredTools) .filter(([, tool]) => tool.enabled) + // eslint-disable-next-line unicorn/no-array-sort -- Object.entries() already gives us a fresh array. + .sort(([left], [right]) => left.localeCompare(right)) .map(([name, tool]): Tool => { const toolDefinition: Tool = { name, diff --git a/test/integration/test/server/mcp.test.ts b/test/integration/test/server/mcp.test.ts index 92af09744c..190e481571 100644 --- a/test/integration/test/server/mcp.test.ts +++ b/test/integration/test/server/mcp.test.ts @@ -1280,6 +1280,28 @@ describe('Zod v4', () => { mcpServer.registerTool('tool2', {}, () => ({ content: [] })); }); + test('should list registered tools in deterministic name order', async () => { + const mcpServer = new McpServer({ + name: 'test server', + version: '1.0' + }); + const client = new Client({ + name: 'test client', + version: '1.0' + }); + + mcpServer.registerTool('zeta', {}, () => ({ content: [] })); + mcpServer.registerTool('alpha', {}, () => ({ content: [] })); + mcpServer.registerTool('middle', {}, () => ({ content: [] })); + + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]); + + const result = await client.request({ method: 'tools/list' }); + + expect(result.tools.map(tool => tool.name)).toEqual(['alpha', 'middle', 'zeta']); + }); + /*** * Test: Tool with Output Schema and Structured Content */