diff --git a/src/McpContext.ts b/src/McpContext.ts index d1037935..61d9b5fd 100644 --- a/src/McpContext.ts +++ b/src/McpContext.ts @@ -134,9 +134,16 @@ export class McpContext implements Context { return this.#networkCollector.getData(page); } - getConsoleData(): Array { + getConsoleData(tail?: number): Array { const page = this.getSelectedPage(); - return this.#consoleCollector.getData(page); + const allMessages = this.#consoleCollector.getData(page); + + // If tail is specified, return only the last N messages + if (tail !== undefined && tail > 0) { + return allMessages.slice(-tail); + } + + return allMessages; } async newPage(): Promise { diff --git a/src/McpResponse.ts b/src/McpResponse.ts index fa3c69ae..154ba578 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -26,6 +26,7 @@ export class McpResponse implements Response { #includeSnapshot = false; #attachedNetworkRequestUrl?: string; #includeConsoleData = false; + #consoleTail?: number; #textResponseLines: string[] = []; #formattedConsoleData?: string[]; #images: ImageContentData[] = []; @@ -69,8 +70,9 @@ export class McpResponse implements Response { }; } - setIncludeConsoleData(value: boolean): void { + setIncludeConsoleData(value: boolean, tail?: number): void { this.#includeConsoleData = value; + this.#consoleTail = tail; } attachNetworkRequest(url: string): void { @@ -128,7 +130,7 @@ export class McpResponse implements Response { let formattedConsoleMessages: string[]; if (this.#includeConsoleData) { - const consoleMessages = context.getConsoleData(); + const consoleMessages = context.getConsoleData(this.#consoleTail); if (consoleMessages) { formattedConsoleMessages = await Promise.all( consoleMessages.map(message => formatConsoleEvent(message)), diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index fe2fae7b..5e5753e7 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -46,7 +46,7 @@ export interface Response { value: boolean, options?: {pageSize?: number; pageIdx?: number; resourceTypes?: string[]}, ): void; - setIncludeConsoleData(value: boolean): void; + setIncludeConsoleData(value: boolean, tail?: number): void; setIncludeSnapshot(value: boolean): void; attachImage(value: ImageContentData): void; attachNetworkRequest(url: string): void; diff --git a/src/tools/console.ts b/src/tools/console.ts index 9a3ff114..80dcee42 100644 --- a/src/tools/console.ts +++ b/src/tools/console.ts @@ -4,6 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import z from 'zod'; + import {ToolCategories} from './categories.js'; import {defineTool} from './ToolDefinition.js'; @@ -14,8 +16,18 @@ export const consoleTool = defineTool({ category: ToolCategories.DEBUGGING, readOnlyHint: true, }, - schema: {}, - handler: async (_request, response) => { - response.setIncludeConsoleData(true); + schema: { + tail: z + .number() + .int() + .positive() + .optional() + .default(50) + .describe( + 'Maximum number of recent messages to return. Defaults to 50. Omit or set to null to return all messages.', + ), + }, + handler: async (request, response) => { + response.setIncludeConsoleData(true, request.params.tail); }, }); diff --git a/tests/tools/console.test.ts b/tests/tools/console.test.ts index b25ef15b..2c8796ff 100644 --- a/tests/tools/console.test.ts +++ b/tests/tools/console.test.ts @@ -13,9 +13,81 @@ describe('console', () => { describe('list_console_messages', () => { it('list messages', async () => { await withBrowser(async (response, context) => { - await consoleTool.handler({params: {}}, response, context); + await consoleTool.handler({params: {tail: 50}}, response, context); assert.ok(response.includeConsoleData); }); }); + + it('uses default tail of 50 messages', async () => { + await withBrowser(async (response, context) => { + // Generate 60 console messages + const page = context.getSelectedPage(); + await page.evaluate(() => { + for (let i = 1; i <= 60; i++) { + console.log(`Message ${i}`); + } + }); + + // Call handler without tail param (should default to 50) + await consoleTool.handler({params: {tail: 50}}, response, context); + + const result = await response.handle('test', context); + const text = (result[0] as any).text.toString(); + + // Should include message 11 (first of last 50) + assert.ok(text.includes('Message 11'), 'Should include Message 11'); + // Should include message 60 (last message) + assert.ok(text.includes('Message 60'), 'Should include Message 60'); + // Should NOT include message 10 (51st from end) + assert.ok(!text.includes('Message 10'), 'Should NOT include Message 10'); + }); + }); + + it('respects custom tail parameter', async () => { + await withBrowser(async (response, context) => { + // Generate 20 console messages + const page = context.getSelectedPage(); + await page.evaluate(() => { + for (let i = 1; i <= 20; i++) { + console.log(`Message ${i}`); + } + }); + + // Call handler with tail=5 + await consoleTool.handler({params: {tail: 5}}, response, context); + + const result = await response.handle('test', context); + const text = (result[0] as any).text.toString(); + + // Should include messages 16-20 (last 5) + assert.ok(text.includes('Message 16'), 'Should include Message 16'); + assert.ok(text.includes('Message 20'), 'Should include Message 20'); + // Should NOT include message 15 + assert.ok(!text.includes('Message 15'), 'Should NOT include Message 15'); + }); + }); + + it('returns only last 10 messages with default tail when less than 50 exist', async () => { + await withBrowser(async (response, context) => { + // Generate 10 console messages + const page = context.getSelectedPage(); + await page.evaluate(() => { + for (let i = 1; i <= 10; i++) { + console.log(`Message ${i}`); + } + }); + + // Call handler with default tail (50, but only 10 messages exist) + await consoleTool.handler({params: {tail: 50}}, response, context); + + const result = await response.handle('test', context); + const text = (result[0] as any).text.toString(); + + // Should include all 10 messages since there are less than 50 + for (let i = 1; i <= 10; i++) { + assert.ok(text.includes(`Message ${i}`), `Should include Message ${i}`); + } + }); + }); }); });