Skip to content

Commit f6e67b4

Browse files
committed
add filters and pagination to the console messages tool
1 parent 579819b commit f6e67b4

File tree

7 files changed

+189
-166
lines changed

7 files changed

+189
-166
lines changed

src/McpResponse.ts

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,18 @@ export class McpResponse implements Response {
3333
#includePages = false;
3434
#includeSnapshot = false;
3535
#attachedNetworkRequestData?: NetworkRequestData;
36-
#includeConsoleData = false;
3736
#textResponseLines: string[] = [];
38-
#formattedConsoleData?: string[];
3937
#images: ImageContentData[] = [];
4038
#networkRequestsOptions?: {
4139
include: boolean;
4240
pagination?: PaginationOptions;
4341
resourceTypes?: ResourceType[];
4442
};
43+
#consoleDataOptions?: {
44+
include: boolean;
45+
pagination?: PaginationOptions;
46+
types?: string[];
47+
};
4548

4649
setIncludePages(value: boolean): void {
4750
this.#includePages = value;
@@ -77,8 +80,30 @@ export class McpResponse implements Response {
7780
};
7881
}
7982

80-
setIncludeConsoleData(value: boolean): void {
81-
this.#includeConsoleData = value;
83+
setIncludeConsoleData(
84+
value: boolean,
85+
options?: {
86+
pageSize?: number;
87+
pageIdx?: number;
88+
types?: string[];
89+
},
90+
): void {
91+
if (!value) {
92+
this.#consoleDataOptions = undefined;
93+
return;
94+
}
95+
96+
this.#consoleDataOptions = {
97+
include: value,
98+
pagination:
99+
options?.pageSize || options?.pageIdx
100+
? {
101+
pageSize: options.pageSize,
102+
pageIdx: options.pageIdx,
103+
}
104+
: undefined,
105+
types: options?.types,
106+
};
82107
}
83108

84109
attachNetworkRequest(reqid: number): void {
@@ -96,14 +121,20 @@ export class McpResponse implements Response {
96121
}
97122

98123
get includeConsoleData(): boolean {
99-
return this.#includeConsoleData;
124+
return this.#consoleDataOptions?.include ?? false;
100125
}
101126
get attachedNetworkRequestId(): number | undefined {
102127
return this.#attachedNetworkRequestData?.networkRequestStableId;
103128
}
104129
get networkRequestsPageIdx(): number | undefined {
105130
return this.#networkRequestsOptions?.pagination?.pageIdx;
106131
}
132+
get consoleMessagesPageIdx(): number | undefined {
133+
return this.#consoleDataOptions?.pagination?.pageIdx;
134+
}
135+
get consoleMessagesTypes(): string[] | undefined {
136+
return this.#consoleDataOptions?.types;
137+
}
107138

108139
appendResponseLine(value: string): void {
109140
this.#textResponseLines.push(value);
@@ -136,8 +167,6 @@ export class McpResponse implements Response {
136167
await context.createTextSnapshot();
137168
}
138169

139-
let formattedConsoleMessages: string[];
140-
141170
if (this.#attachedNetworkRequestData?.networkRequestStableId) {
142171
const request = context.getNetworkRequestById(
143172
this.#attachedNetworkRequestData.networkRequestStableId,
@@ -153,23 +182,13 @@ export class McpResponse implements Response {
153182
}
154183
}
155184

156-
if (this.#includeConsoleData) {
157-
const consoleMessages = context.getConsoleData();
158-
if (consoleMessages) {
159-
formattedConsoleMessages = await Promise.all(
160-
consoleMessages.map(message => formatConsoleEvent(message)),
161-
);
162-
this.#formattedConsoleData = formattedConsoleMessages;
163-
}
164-
}
165-
166-
return this.format(toolName, context);
185+
return await this.format(toolName, context);
167186
}
168187

169-
format(
188+
async format(
170189
toolName: string,
171190
context: McpContext,
172-
): Array<TextContent | ImageContent> {
191+
): Promise<Array<TextContent | ImageContent>> {
173192
const response = [`# ${toolName} response`];
174193
for (const line of this.#textResponseLines) {
175194
response.push(line);
@@ -258,10 +277,32 @@ Call ${handleDialog.name} to handle it before continuing.`);
258277
}
259278
}
260279

261-
if (this.#includeConsoleData && this.#formattedConsoleData) {
280+
if (this.#consoleDataOptions?.include) {
281+
let messages = context.getConsoleData();
282+
283+
if (this.#consoleDataOptions.types?.length) {
284+
const normalizedTypes = new Set(this.#consoleDataOptions.types);
285+
messages = messages.filter(message => {
286+
if (!('type' in message)) {
287+
return normalizedTypes.has('error');
288+
}
289+
const type = message.type();
290+
return normalizedTypes.has(type);
291+
});
292+
}
293+
262294
response.push('## Console messages');
263-
if (this.#formattedConsoleData.length) {
264-
response.push(...this.#formattedConsoleData);
295+
if (messages.length) {
296+
const data = this.#dataWithPagination(
297+
messages,
298+
this.#consoleDataOptions.pagination,
299+
);
300+
response.push(...data.info);
301+
response.push(
302+
...(await Promise.all(
303+
data.items.map(message => formatConsoleEvent(message)),
304+
)),
305+
);
265306
} else {
266307
response.push('<no console messages found>');
267308
}

src/formatters/consoleFormatter.ts

Lines changed: 26 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import type {
8-
ConsoleMessage,
9-
JSHandle,
10-
ConsoleMessageLocation,
11-
} from 'puppeteer-core';
7+
import type {ConsoleMessage, JSHandle} from 'puppeteer-core';
128

139
const logLevels: Record<string, string> = {
1410
log: 'Log',
@@ -31,66 +27,37 @@ export async function formatConsoleEvent(
3127

3228
async function formatConsoleMessage(msg: ConsoleMessage): Promise<string> {
3329
const logLevel = logLevels[msg.type()];
30+
const text = msg.text();
3431
const args = msg.args();
3532

36-
if (logLevel === 'Error') {
37-
let message = `${logLevel}> `;
38-
if (msg.text() === 'JSHandle@error') {
39-
const errorHandle = args[0] as JSHandle<Error>;
40-
message += await errorHandle
41-
.evaluate(error => {
42-
return error.toString();
43-
})
44-
.catch(() => {
45-
return 'Error occurred';
46-
});
47-
void errorHandle.dispose().catch();
33+
const formattedArgs = await formatArgs(args, text);
34+
return `${logLevel}> ${text} ${formattedArgs}`.trim();
35+
}
4836

49-
const formattedArgs = await formatArgs(args.slice(1));
50-
if (formattedArgs) {
51-
message += ` ${formattedArgs}`;
52-
}
53-
} else {
54-
message += msg.text();
55-
const formattedArgs = await formatArgs(args);
56-
if (formattedArgs) {
57-
message += ` ${formattedArgs}`;
58-
}
59-
for (const frame of msg.stackTrace()) {
60-
message += '\n' + formatStackFrame(frame);
61-
}
62-
}
63-
return message;
37+
// Only includes the first arg and indicates that there are more args
38+
async function formatArgs(
39+
args: readonly JSHandle[],
40+
messageText: string,
41+
): Promise<string> {
42+
if (args.length === 0) {
43+
return '';
6444
}
6545

66-
const formattedArgs = await formatArgs(args);
67-
const text = msg.text();
68-
69-
return `${logLevel}> ${formatStackFrame(
70-
msg.location(),
71-
)}: ${text} ${formattedArgs}`.trim();
72-
}
73-
74-
async function formatArgs(args: readonly JSHandle[]): Promise<string> {
75-
const argValues = await Promise.all(
76-
args.map(arg =>
77-
arg.jsonValue().catch(() => {
78-
// Ignore errors
79-
}),
80-
),
81-
);
46+
let formattedArgs = '';
47+
const firstArg = await args[0].jsonValue().catch(() => {
48+
// Ignore errors
49+
});
8250

83-
return argValues
84-
.map(value => {
85-
return typeof value === 'object' ? JSON.stringify(value) : String(value);
86-
})
87-
.join(' ');
88-
}
51+
if (firstArg !== messageText) {
52+
formattedArgs +=
53+
typeof firstArg === 'object'
54+
? JSON.stringify(firstArg)
55+
: String(firstArg);
56+
}
8957

90-
function formatStackFrame(stackFrame: ConsoleMessageLocation): string {
91-
if (!stackFrame?.url) {
92-
return '<unknown>';
58+
if (args.length > 1) {
59+
return `${formattedArgs} ...`;
9360
}
94-
const filename = stackFrame.url.replace(/^.*\//, '');
95-
return `${filename}:${stackFrame.lineNumber}:${stackFrame.columnNumber}`;
61+
62+
return formattedArgs;
9663
}

src/tools/ToolDefinition.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ export interface Response {
4747
value: boolean,
4848
options?: {pageSize?: number; pageIdx?: number; resourceTypes?: string[]},
4949
): void;
50-
setIncludeConsoleData(value: boolean): void;
50+
setIncludeConsoleData(
51+
value: boolean,
52+
options?: {pageSize?: number; pageIdx?: number; types?: string[]},
53+
): void;
5154
setIncludeSnapshot(value: boolean): void;
5255
attachImage(value: ImageContentData): void;
5356
attachNetworkRequest(reqid: number): void;

src/tools/console.ts

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,74 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7+
import type {ConsoleMessageType} from 'puppeteer-core';
8+
import z from 'zod';
9+
710
import {ToolCategories} from './categories.js';
811
import {defineTool} from './ToolDefinition.js';
912

13+
const FILTERABLE_MESSAGE_TYPES: readonly [
14+
ConsoleMessageType,
15+
...ConsoleMessageType[],
16+
] = [
17+
'log',
18+
'debug',
19+
'info',
20+
'error',
21+
'warn',
22+
'dir',
23+
'dirxml',
24+
'table',
25+
'trace',
26+
'clear',
27+
'startGroup',
28+
'startGroupCollapsed',
29+
'endGroup',
30+
'assert',
31+
'profile',
32+
'profileEnd',
33+
'count',
34+
'timeEnd',
35+
'verbose',
36+
];
37+
1038
export const consoleTool = defineTool({
1139
name: 'list_console_messages',
1240
description:
1341
'List all console messages for the currently selected page since the last navigation.',
1442
annotations: {
15-
category: ToolCategories.DEBUGGING,
43+
category: ToolCategories.NETWORK,
1644
readOnlyHint: true,
1745
},
18-
schema: {},
19-
handler: async (_request, response) => {
20-
response.setIncludeConsoleData(true);
46+
schema: {
47+
pageSize: z
48+
.number()
49+
.int()
50+
.positive()
51+
.optional()
52+
.describe(
53+
'Maximum number of messages to return. When omitted, returns all requests.',
54+
),
55+
pageIdx: z
56+
.number()
57+
.int()
58+
.min(0)
59+
.optional()
60+
.describe(
61+
'Page number to return (0-based). When omitted, returns the first page.',
62+
),
63+
types: z
64+
.array(z.enum(FILTERABLE_MESSAGE_TYPES))
65+
.optional()
66+
.describe(
67+
'Filter messages to only return messages of the specified resource types. When omitted or empty, returns all messages.',
68+
),
69+
},
70+
handler: async (request, response) => {
71+
response.setIncludeConsoleData(true, {
72+
pageSize: request.params.pageSize,
73+
pageIdx: request.params.pageIdx,
74+
types: request.params.types,
75+
});
2176
},
2277
});

tests/McpResponse.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,7 @@ reqid=1 GET http://example.com [pending]`,
319319
// Cannot check the full text because it contains local file path
320320
assert.ok(
321321
result[0].text.toString().startsWith(`# test response
322-
## Console messages
323-
Log>`),
322+
## Console messages`),
324323
);
325324
assert.ok(result[0].text.toString().includes('Hello from the test'));
326325
});

0 commit comments

Comments
 (0)