From f6e67b435f0afa3136cff225dfb5ad0f6dd423a2 Mon Sep 17 00:00:00 2001 From: alinavarkki Date: Wed, 15 Oct 2025 11:09:13 +0200 Subject: [PATCH 1/4] add filters and pagination to the console messages tool --- src/McpResponse.ts | 87 ++++++++++++++------ src/formatters/consoleFormatter.ts | 85 ++++++------------- src/tools/ToolDefinition.ts | 5 +- src/tools/console.ts | 63 ++++++++++++++- tests/McpResponse.test.ts | 3 +- tests/formatters/consoleFormatter.test.ts | 99 +++++------------------ tests/tools/console.test.ts | 13 +++ 7 files changed, 189 insertions(+), 166 deletions(-) diff --git a/src/McpResponse.ts b/src/McpResponse.ts index 98a979db..366cca1b 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -33,15 +33,18 @@ export class McpResponse implements Response { #includePages = false; #includeSnapshot = false; #attachedNetworkRequestData?: NetworkRequestData; - #includeConsoleData = false; #textResponseLines: string[] = []; - #formattedConsoleData?: string[]; #images: ImageContentData[] = []; #networkRequestsOptions?: { include: boolean; pagination?: PaginationOptions; resourceTypes?: ResourceType[]; }; + #consoleDataOptions?: { + include: boolean; + pagination?: PaginationOptions; + types?: string[]; + }; setIncludePages(value: boolean): void { this.#includePages = value; @@ -77,8 +80,30 @@ export class McpResponse implements Response { }; } - setIncludeConsoleData(value: boolean): void { - this.#includeConsoleData = value; + setIncludeConsoleData( + value: boolean, + options?: { + pageSize?: number; + pageIdx?: number; + types?: string[]; + }, + ): void { + if (!value) { + this.#consoleDataOptions = undefined; + return; + } + + this.#consoleDataOptions = { + include: value, + pagination: + options?.pageSize || options?.pageIdx + ? { + pageSize: options.pageSize, + pageIdx: options.pageIdx, + } + : undefined, + types: options?.types, + }; } attachNetworkRequest(reqid: number): void { @@ -96,7 +121,7 @@ export class McpResponse implements Response { } get includeConsoleData(): boolean { - return this.#includeConsoleData; + return this.#consoleDataOptions?.include ?? false; } get attachedNetworkRequestId(): number | undefined { return this.#attachedNetworkRequestData?.networkRequestStableId; @@ -104,6 +129,12 @@ export class McpResponse implements Response { get networkRequestsPageIdx(): number | undefined { return this.#networkRequestsOptions?.pagination?.pageIdx; } + get consoleMessagesPageIdx(): number | undefined { + return this.#consoleDataOptions?.pagination?.pageIdx; + } + get consoleMessagesTypes(): string[] | undefined { + return this.#consoleDataOptions?.types; + } appendResponseLine(value: string): void { this.#textResponseLines.push(value); @@ -136,8 +167,6 @@ export class McpResponse implements Response { await context.createTextSnapshot(); } - let formattedConsoleMessages: string[]; - if (this.#attachedNetworkRequestData?.networkRequestStableId) { const request = context.getNetworkRequestById( this.#attachedNetworkRequestData.networkRequestStableId, @@ -153,23 +182,13 @@ export class McpResponse implements Response { } } - if (this.#includeConsoleData) { - const consoleMessages = context.getConsoleData(); - if (consoleMessages) { - formattedConsoleMessages = await Promise.all( - consoleMessages.map(message => formatConsoleEvent(message)), - ); - this.#formattedConsoleData = formattedConsoleMessages; - } - } - - return this.format(toolName, context); + return await this.format(toolName, context); } - format( + async format( toolName: string, context: McpContext, - ): Array { + ): Promise> { const response = [`# ${toolName} response`]; for (const line of this.#textResponseLines) { response.push(line); @@ -258,10 +277,32 @@ Call ${handleDialog.name} to handle it before continuing.`); } } - if (this.#includeConsoleData && this.#formattedConsoleData) { + if (this.#consoleDataOptions?.include) { + let messages = context.getConsoleData(); + + if (this.#consoleDataOptions.types?.length) { + const normalizedTypes = new Set(this.#consoleDataOptions.types); + messages = messages.filter(message => { + if (!('type' in message)) { + return normalizedTypes.has('error'); + } + const type = message.type(); + return normalizedTypes.has(type); + }); + } + response.push('## Console messages'); - if (this.#formattedConsoleData.length) { - response.push(...this.#formattedConsoleData); + if (messages.length) { + const data = this.#dataWithPagination( + messages, + this.#consoleDataOptions.pagination, + ); + response.push(...data.info); + response.push( + ...(await Promise.all( + data.items.map(message => formatConsoleEvent(message)), + )), + ); } else { response.push(''); } diff --git a/src/formatters/consoleFormatter.ts b/src/formatters/consoleFormatter.ts index b6627496..6b74b0b5 100644 --- a/src/formatters/consoleFormatter.ts +++ b/src/formatters/consoleFormatter.ts @@ -4,11 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { - ConsoleMessage, - JSHandle, - ConsoleMessageLocation, -} from 'puppeteer-core'; +import type {ConsoleMessage, JSHandle} from 'puppeteer-core'; const logLevels: Record = { log: 'Log', @@ -31,66 +27,37 @@ export async function formatConsoleEvent( async function formatConsoleMessage(msg: ConsoleMessage): Promise { const logLevel = logLevels[msg.type()]; + const text = msg.text(); const args = msg.args(); - if (logLevel === 'Error') { - let message = `${logLevel}> `; - if (msg.text() === 'JSHandle@error') { - const errorHandle = args[0] as JSHandle; - message += await errorHandle - .evaluate(error => { - return error.toString(); - }) - .catch(() => { - return 'Error occurred'; - }); - void errorHandle.dispose().catch(); + const formattedArgs = await formatArgs(args, text); + return `${logLevel}> ${text} ${formattedArgs}`.trim(); +} - const formattedArgs = await formatArgs(args.slice(1)); - if (formattedArgs) { - message += ` ${formattedArgs}`; - } - } else { - message += msg.text(); - const formattedArgs = await formatArgs(args); - if (formattedArgs) { - message += ` ${formattedArgs}`; - } - for (const frame of msg.stackTrace()) { - message += '\n' + formatStackFrame(frame); - } - } - return message; +// Only includes the first arg and indicates that there are more args +async function formatArgs( + args: readonly JSHandle[], + messageText: string, +): Promise { + if (args.length === 0) { + return ''; } - const formattedArgs = await formatArgs(args); - const text = msg.text(); - - return `${logLevel}> ${formatStackFrame( - msg.location(), - )}: ${text} ${formattedArgs}`.trim(); -} - -async function formatArgs(args: readonly JSHandle[]): Promise { - const argValues = await Promise.all( - args.map(arg => - arg.jsonValue().catch(() => { - // Ignore errors - }), - ), - ); + let formattedArgs = ''; + const firstArg = await args[0].jsonValue().catch(() => { + // Ignore errors + }); - return argValues - .map(value => { - return typeof value === 'object' ? JSON.stringify(value) : String(value); - }) - .join(' '); -} + if (firstArg !== messageText) { + formattedArgs += + typeof firstArg === 'object' + ? JSON.stringify(firstArg) + : String(firstArg); + } -function formatStackFrame(stackFrame: ConsoleMessageLocation): string { - if (!stackFrame?.url) { - return ''; + if (args.length > 1) { + return `${formattedArgs} ...`; } - const filename = stackFrame.url.replace(/^.*\//, ''); - return `${filename}:${stackFrame.lineNumber}:${stackFrame.columnNumber}`; + + return formattedArgs; } diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index 6b1c92ae..7e902255 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -47,7 +47,10 @@ export interface Response { value: boolean, options?: {pageSize?: number; pageIdx?: number; resourceTypes?: string[]}, ): void; - setIncludeConsoleData(value: boolean): void; + setIncludeConsoleData( + value: boolean, + options?: {pageSize?: number; pageIdx?: number; types?: string[]}, + ): void; setIncludeSnapshot(value: boolean): void; attachImage(value: ImageContentData): void; attachNetworkRequest(reqid: number): void; diff --git a/src/tools/console.ts b/src/tools/console.ts index 4fb752f2..47a1ed17 100644 --- a/src/tools/console.ts +++ b/src/tools/console.ts @@ -4,19 +4,74 @@ * SPDX-License-Identifier: Apache-2.0 */ +import type {ConsoleMessageType} from 'puppeteer-core'; +import z from 'zod'; + import {ToolCategories} from './categories.js'; import {defineTool} from './ToolDefinition.js'; +const FILTERABLE_MESSAGE_TYPES: readonly [ + ConsoleMessageType, + ...ConsoleMessageType[], +] = [ + 'log', + 'debug', + 'info', + 'error', + 'warn', + 'dir', + 'dirxml', + 'table', + 'trace', + 'clear', + 'startGroup', + 'startGroupCollapsed', + 'endGroup', + 'assert', + 'profile', + 'profileEnd', + 'count', + 'timeEnd', + 'verbose', +]; + export const consoleTool = defineTool({ name: 'list_console_messages', description: 'List all console messages for the currently selected page since the last navigation.', annotations: { - category: ToolCategories.DEBUGGING, + category: ToolCategories.NETWORK, readOnlyHint: true, }, - schema: {}, - handler: async (_request, response) => { - response.setIncludeConsoleData(true); + schema: { + pageSize: z + .number() + .int() + .positive() + .optional() + .describe( + 'Maximum number of messages to return. When omitted, returns all requests.', + ), + pageIdx: z + .number() + .int() + .min(0) + .optional() + .describe( + 'Page number to return (0-based). When omitted, returns the first page.', + ), + types: z + .array(z.enum(FILTERABLE_MESSAGE_TYPES)) + .optional() + .describe( + 'Filter messages to only return messages of the specified resource types. When omitted or empty, returns all messages.', + ), + }, + handler: async (request, response) => { + response.setIncludeConsoleData(true, { + pageSize: request.params.pageSize, + pageIdx: request.params.pageIdx, + types: request.params.types, + }); }, }); diff --git a/tests/McpResponse.test.ts b/tests/McpResponse.test.ts index 30fdf2a2..17cd4ae4 100644 --- a/tests/McpResponse.test.ts +++ b/tests/McpResponse.test.ts @@ -319,8 +319,7 @@ reqid=1 GET http://example.com [pending]`, // Cannot check the full text because it contains local file path assert.ok( result[0].text.toString().startsWith(`# test response -## Console messages -Log>`), +## Console messages`), ); assert.ok(result[0].text.toString().includes('Hello from the test')); }); diff --git a/tests/formatters/consoleFormatter.test.ts b/tests/formatters/consoleFormatter.test.ts index 4fd6213c..4edebb00 100644 --- a/tests/formatters/consoleFormatter.test.ts +++ b/tests/formatters/consoleFormatter.test.ts @@ -65,17 +65,22 @@ describe('consoleFormatter', () => { const message = getMockConsoleMessage({ type: 'log', text: 'Hello, world!', - location: { - url: 'http://example.com/script.js', - lineNumber: 10, - columnNumber: 5, - }, }); const result = await formatConsoleEvent(message); - assert.equal(result, 'Log> script.js:10:5: Hello, world!'); + assert.equal(result, 'Log> Hello, world!'); }); - it('formats a console.log message with arguments', async () => { + it('formats a console.log message with one argument', async () => { + const message = getMockConsoleMessage({ + type: 'log', + text: 'Processing file:', + args: ['file.txt'], + }); + const result = await formatConsoleEvent(message); + assert.equal(result, 'Log> Processing file: file.txt'); + }); + + it('formats a console.log message with multiple arguments', async () => { const message = getMockConsoleMessage({ type: 'log', text: 'Processing file:', @@ -87,10 +92,7 @@ describe('consoleFormatter', () => { }, }); const result = await formatConsoleEvent(message); - assert.equal( - result, - 'Log> script.js:10:5: Processing file: file.txt {"id":1,"status":"done"}', - ); + assert.equal(result, 'Log> Processing file: file.txt ...'); }); it('formats a console.error message', async () => { @@ -102,76 +104,42 @@ describe('consoleFormatter', () => { assert.equal(result, 'Error> Something went wrong'); }); - it('formats a console.error message with arguments', async () => { + it('formats a console.error message with one argument', async () => { const message = getMockConsoleMessage({ type: 'error', text: 'Something went wrong:', - args: ['details', {code: 500}], - }); - const result = await formatConsoleEvent(message); - assert.equal(result, 'Error> Something went wrong: details {"code":500}'); - }); - - it('formats a console.error message with a stack trace', async () => { - const message = getMockConsoleMessage({ - type: 'error', - text: 'Something went wrong', - stackTrace: [ - { - url: 'http://example.com/script.js', - lineNumber: 10, - columnNumber: 5, - }, - { - url: 'http://example.com/script2.js', - lineNumber: 20, - columnNumber: 10, - }, - ], + args: ['details'], }); const result = await formatConsoleEvent(message); - assert.equal( - result, - 'Error> Something went wrong\nscript.js:10:5\nscript2.js:20:10', - ); + assert.equal(result, 'Error> Something went wrong: details'); }); - it('formats a console.error message with a JSHandle@error', async () => { + it('formats a console.error message with multiple arguments', async () => { const message = getMockConsoleMessage({ type: 'error', - text: 'JSHandle@error', - args: [new Error('mock stack')], + text: 'Something went wrong:', + args: ['details', {code: 500}], }); const result = await formatConsoleEvent(message); - assert.ok(result.startsWith('Error> Error: mock stack')); + assert.equal(result, 'Error> Something went wrong: details ...'); }); it('formats a console.warn message', async () => { const message = getMockConsoleMessage({ type: 'warning', text: 'This is a warning', - location: { - url: 'http://example.com/script.js', - lineNumber: 10, - columnNumber: 5, - }, }); const result = await formatConsoleEvent(message); - assert.equal(result, 'Warning> script.js:10:5: This is a warning'); + assert.equal(result, 'Warning> This is a warning'); }); it('formats a console.info message', async () => { const message = getMockConsoleMessage({ type: 'info', text: 'This is an info message', - location: { - url: 'http://example.com/script.js', - lineNumber: 10, - columnNumber: 5, - }, }); const result = await formatConsoleEvent(message); - assert.equal(result, 'Info> script.js:10:5: This is an info message'); + assert.equal(result, 'Info> This is an info message'); }); it('formats a page error', async () => { @@ -187,28 +155,5 @@ describe('consoleFormatter', () => { const result = await formatConsoleEvent(error); assert.equal(result, 'Error: Page crashed'); }); - - it('formats a console.log message from a removed iframe - no location', async () => { - const message = getMockConsoleMessage({ - type: 'log', - text: 'Hello from iframe', - location: {}, - }); - const result = await formatConsoleEvent(message); - assert.equal(result, 'Log> : Hello from iframe'); - }); - - it('formats a console.log message from a removed iframe with partial location', async () => { - const message = getMockConsoleMessage({ - type: 'log', - text: 'Hello from iframe', - location: { - lineNumber: 10, - columnNumber: 5, - }, - }); - const result = await formatConsoleEvent(message); - assert.equal(result, 'Log> : Hello from iframe'); - }); }); }); diff --git a/tests/tools/console.test.ts b/tests/tools/console.test.ts index b25ef15b..170b7959 100644 --- a/tests/tools/console.test.ts +++ b/tests/tools/console.test.ts @@ -17,5 +17,18 @@ describe('console', () => { assert.ok(response.includeConsoleData); }); }); + + it('lists error messages', async () => { + await withBrowser(async (response, context) => { + const page = await context.newPage(); + await page.setContent( + '', + ); + await consoleTool.handler({params: {}}, response, context); + const formattedResponse = await response.format('test', context); + const textContent = formattedResponse[0] as {text: string}; + assert.ok(textContent.text.includes('This is an error')); + }); + }); }); }); From 3107fa12ed877353c4ebc7253388a4938cbfff9b Mon Sep 17 00:00:00 2001 From: alinavarkki Date: Wed, 15 Oct 2025 16:23:45 +0200 Subject: [PATCH 2/4] make format not async --- src/McpResponse.ts | 63 +++++--- src/formatters/consoleFormatter.ts | 30 +--- tests/formatters/consoleFormatter.test.ts | 170 ++++++++-------------- tests/tools/console.test.ts | 6 +- 4 files changed, 119 insertions(+), 150 deletions(-) diff --git a/src/McpResponse.ts b/src/McpResponse.ts index 366cca1b..47354089 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -7,7 +7,7 @@ import type { ImageContent, TextContent, } from '@modelcontextprotocol/sdk/types.js'; -import type {ResourceType} from 'puppeteer-core'; +import type {ConsoleMessage, ResourceType} from 'puppeteer-core'; import {formatConsoleEvent} from './formatters/consoleFormatter.js'; import { @@ -29,10 +29,17 @@ interface NetworkRequestData { responseBody?: string; } +export interface ConsoleMessageData { + type: string; + message: string; + args: string[]; +} + export class McpResponse implements Response { #includePages = false; #includeSnapshot = false; #attachedNetworkRequestData?: NetworkRequestData; + #consoleMessagesData?: ConsoleMessageData[]; #textResponseLines: string[] = []; #images: ImageContentData[] = []; #networkRequestsOptions?: { @@ -182,13 +189,44 @@ export class McpResponse implements Response { } } - return await this.format(toolName, context); + if (this.#consoleDataOptions?.include) { + const messages = context.getConsoleData(); + + this.#consoleMessagesData = await Promise.all( + messages.map(async (item): Promise => { + if ('args' in item) { + const consoleMessage = item as ConsoleMessage; + return { + type: consoleMessage.type(), + message: consoleMessage.text(), + args: await Promise.all( + consoleMessage.args().map(async arg => { + const stringArg = await arg.jsonValue().catch(() => { + // Ignore errors. + }); + return typeof stringArg === 'object' + ? JSON.stringify(stringArg) + : String(stringArg); + }), + ), + }; + } + return { + type: 'error', + message: (item as Error).message, + args: [], + }; + }), + ); + } + + return this.format(toolName, context); } - async format( + format( toolName: string, context: McpContext, - ): Promise> { + ): Array { const response = [`# ${toolName} response`]; for (const line of this.#textResponseLines) { response.push(line); @@ -278,18 +316,7 @@ Call ${handleDialog.name} to handle it before continuing.`); } if (this.#consoleDataOptions?.include) { - let messages = context.getConsoleData(); - - if (this.#consoleDataOptions.types?.length) { - const normalizedTypes = new Set(this.#consoleDataOptions.types); - messages = messages.filter(message => { - if (!('type' in message)) { - return normalizedTypes.has('error'); - } - const type = message.type(); - return normalizedTypes.has(type); - }); - } + const messages = this.#consoleMessagesData ?? []; response.push('## Console messages'); if (messages.length) { @@ -299,9 +326,7 @@ Call ${handleDialog.name} to handle it before continuing.`); ); response.push(...data.info); response.push( - ...(await Promise.all( - data.items.map(message => formatConsoleEvent(message)), - )), + ...data.items.map(message => formatConsoleEvent(message)), ); } else { response.push(''); diff --git a/src/formatters/consoleFormatter.ts b/src/formatters/consoleFormatter.ts index 6b74b0b5..40f60830 100644 --- a/src/formatters/consoleFormatter.ts +++ b/src/formatters/consoleFormatter.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type {ConsoleMessage, JSHandle} from 'puppeteer-core'; +import type {ConsoleMessageData} from '../McpResponse.js'; const logLevels: Record = { log: 'Log', @@ -15,38 +15,22 @@ const logLevels: Record = { assert: 'Assert', }; -export async function formatConsoleEvent( - event: ConsoleMessage | Error, -): Promise { - // Check if the event object has the .type() method, which is unique to ConsoleMessage - if ('type' in event) { - return await formatConsoleMessage(event); - } - return `Error: ${event.message}`; -} - -async function formatConsoleMessage(msg: ConsoleMessage): Promise { - const logLevel = logLevels[msg.type()]; - const text = msg.text(); - const args = msg.args(); +export function formatConsoleEvent(msg: ConsoleMessageData): string { + const logLevel = logLevels[msg.type] ?? 'Log'; + const text = msg.message; - const formattedArgs = await formatArgs(args, text); + const formattedArgs = formatArgs(msg.args, text); return `${logLevel}> ${text} ${formattedArgs}`.trim(); } // Only includes the first arg and indicates that there are more args -async function formatArgs( - args: readonly JSHandle[], - messageText: string, -): Promise { +function formatArgs(args: string[], messageText: string): string { if (args.length === 0) { return ''; } let formattedArgs = ''; - const firstArg = await args[0].jsonValue().catch(() => { - // Ignore errors - }); + const firstArg = args[0]; if (firstArg !== messageText) { formattedArgs += diff --git a/tests/formatters/consoleFormatter.test.ts b/tests/formatters/consoleFormatter.test.ts index 4edebb00..b64adbca 100644 --- a/tests/formatters/consoleFormatter.test.ts +++ b/tests/formatters/consoleFormatter.test.ts @@ -7,153 +7,109 @@ import assert from 'node:assert'; import {describe, it} from 'node:test'; -import type {ConsoleMessage} from 'puppeteer-core'; - import {formatConsoleEvent} from '../../src/formatters/consoleFormatter.js'; - -function getMockConsoleMessage(options: { - type: string; - text: string; - location?: { - url?: string; - lineNumber?: number; - columnNumber?: number; - }; - stackTrace?: Array<{ - url: string; - lineNumber: number; - columnNumber: number; - }>; - args?: unknown[]; -}): ConsoleMessage { - return { - type() { - return options.type; - }, - text() { - return options.text; - }, - location() { - return options.location ?? {}; - }, - stackTrace() { - return options.stackTrace ?? []; - }, - args() { - return ( - options.args?.map(arg => { - return { - evaluate(fn: (arg: unknown) => unknown) { - return Promise.resolve(fn(arg)); - }, - jsonValue() { - return Promise.resolve(arg); - }, - dispose() { - return Promise.resolve(); - }, - }; - }) ?? [] - ); - }, - } as ConsoleMessage; -} +import type {ConsoleMessageData} from '../../src/McpResponse.js'; describe('consoleFormatter', () => { describe('formatConsoleEvent', () => { - it('formats a console.log message', async () => { - const message = getMockConsoleMessage({ + it('formats a console.log message', () => { + const message: ConsoleMessageData = { type: 'log', - text: 'Hello, world!', - }); - const result = await formatConsoleEvent(message); + message: 'Hello, world!', + args: [], + }; + const result = formatConsoleEvent(message); assert.equal(result, 'Log> Hello, world!'); }); - it('formats a console.log message with one argument', async () => { - const message = getMockConsoleMessage({ + it('formats a console.log message with one argument', () => { + const message: ConsoleMessageData = { type: 'log', - text: 'Processing file:', + message: 'Processing file:', args: ['file.txt'], - }); - const result = await formatConsoleEvent(message); + }; + const result = formatConsoleEvent(message); assert.equal(result, 'Log> Processing file: file.txt'); }); - it('formats a console.log message with multiple arguments', async () => { - const message = getMockConsoleMessage({ + it('formats a console.log message with multiple arguments', () => { + const message: ConsoleMessageData = { type: 'log', - text: 'Processing file:', - args: ['file.txt', {id: 1, status: 'done'}], - location: { - url: 'http://example.com/script.js', - lineNumber: 10, - columnNumber: 5, - }, - }); - const result = await formatConsoleEvent(message); + message: 'Processing file:', + args: ['file.txt', JSON.stringify({id: 1, status: 'done'})], + }; + const result = formatConsoleEvent(message); assert.equal(result, 'Log> Processing file: file.txt ...'); }); - it('formats a console.error message', async () => { - const message = getMockConsoleMessage({ + it('formats a console.error message', () => { + const message: ConsoleMessageData = { type: 'error', - text: 'Something went wrong', - }); - const result = await formatConsoleEvent(message); + message: 'Something went wrong', + args: [], + }; + const result = formatConsoleEvent(message); assert.equal(result, 'Error> Something went wrong'); }); - it('formats a console.error message with one argument', async () => { - const message = getMockConsoleMessage({ + it('formats a console.error message with one argument', () => { + const message: ConsoleMessageData = { type: 'error', - text: 'Something went wrong:', + message: 'Something went wrong:', args: ['details'], - }); - const result = await formatConsoleEvent(message); + }; + const result = formatConsoleEvent(message); assert.equal(result, 'Error> Something went wrong: details'); }); - it('formats a console.error message with multiple arguments', async () => { - const message = getMockConsoleMessage({ + it('formats a console.error message with multiple arguments', () => { + const message: ConsoleMessageData = { type: 'error', - text: 'Something went wrong:', - args: ['details', {code: 500}], - }); - const result = await formatConsoleEvent(message); + message: 'Something went wrong:', + args: ['details', JSON.stringify({code: 500})], + }; + const result = formatConsoleEvent(message); assert.equal(result, 'Error> Something went wrong: details ...'); }); - it('formats a console.warn message', async () => { - const message = getMockConsoleMessage({ + it('formats a console.warn message', () => { + const message: ConsoleMessageData = { type: 'warning', - text: 'This is a warning', - }); - const result = await formatConsoleEvent(message); + message: 'This is a warning', + args: [], + }; + const result = formatConsoleEvent(message); assert.equal(result, 'Warning> This is a warning'); }); - it('formats a console.info message', async () => { - const message = getMockConsoleMessage({ + it('formats a console.info message', () => { + const message: ConsoleMessageData = { type: 'info', - text: 'This is an info message', - }); - const result = await formatConsoleEvent(message); + message: 'This is an info message', + args: [], + }; + const result = formatConsoleEvent(message); assert.equal(result, 'Info> This is an info message'); }); - it('formats a page error', async () => { - const error = new Error('Page crashed'); - error.stack = 'Error: Page crashed\n at :1:1'; - const result = await formatConsoleEvent(error); - assert.equal(result, 'Error: Page crashed'); + it('formats a page error', () => { + const error: ConsoleMessageData = { + type: 'error', + message: 'Error: Page crashed', + args: [], + }; + const result = formatConsoleEvent(error); + assert.equal(result, 'Error> Error: Page crashed'); }); - it('formats a page error without a stack', async () => { - const error = new Error('Page crashed'); - error.stack = undefined; - const result = await formatConsoleEvent(error); - assert.equal(result, 'Error: Page crashed'); + it('formats a page error without a stack', () => { + const error: ConsoleMessageData = { + type: 'error', + message: 'Error: Page crashed', + args: [], + }; + const result = formatConsoleEvent(error); + assert.equal(result, 'Error> Error: Page crashed'); }); }); }); diff --git a/tests/tools/console.test.ts b/tests/tools/console.test.ts index 170b7959..cd91587c 100644 --- a/tests/tools/console.test.ts +++ b/tests/tools/console.test.ts @@ -25,8 +25,12 @@ describe('console', () => { '', ); await consoleTool.handler({params: {}}, response, context); - const formattedResponse = await response.format('test', context); + await response.handle('test', context); + + const formattedResponse = response.format('test', context); + const textContent = formattedResponse[0] as {text: string}; + assert.ok(textContent.text.includes('Error>')); assert.ok(textContent.text.includes('This is an error')); }); }); From 68f09f974fdbdd88d7d69d53e38b62e03922265b Mon Sep 17 00:00:00 2001 From: alinavarkki Date: Wed, 15 Oct 2025 16:24:45 +0200 Subject: [PATCH 3/4] revert accidental change --- src/tools/console.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/console.ts b/src/tools/console.ts index 47a1ed17..5b049111 100644 --- a/src/tools/console.ts +++ b/src/tools/console.ts @@ -40,7 +40,7 @@ export const consoleTool = defineTool({ description: 'List all console messages for the currently selected page since the last navigation.', annotations: { - category: ToolCategories.NETWORK, + category: ToolCategories.DEBUGGING, readOnlyHint: true, }, schema: { From 90081df9f0adb70f7743ed526b80bead97322729 Mon Sep 17 00:00:00 2001 From: alinavarkki Date: Wed, 15 Oct 2025 17:28:24 +0200 Subject: [PATCH 4/4] docs --- docs/tool-reference.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/tool-reference.md b/docs/tool-reference.md index 1836c924..a7e3925d 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -300,7 +300,11 @@ so returned values have to JSON-serializable. **Description:** List all console messages for the currently selected page since the last navigation. -**Parameters:** None +**Parameters:** + +- **pageIdx** (integer) _(optional)_: Page number to return (0-based). When omitted, returns the first page. +- **pageSize** (integer) _(optional)_: Maximum number of messages to return. When omitted, returns all requests. +- **types** (array) _(optional)_: Filter messages to only return messages of the specified resource types. When omitted or empty, returns all messages. ---