Skip to content

Commit 9205593

Browse files
authored
feat: tool to get a verbose single console message (#435)
1 parent 5a062ce commit 9205593

File tree

9 files changed

+283
-86
lines changed

9 files changed

+283
-86
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,9 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
265265
- **Network** (2 tools)
266266
- [`get_network_request`](docs/tool-reference.md#get_network_request)
267267
- [`list_network_requests`](docs/tool-reference.md#list_network_requests)
268-
- **Debugging** (4 tools)
268+
- **Debugging** (5 tools)
269269
- [`evaluate_script`](docs/tool-reference.md#evaluate_script)
270+
- [`get_console_message`](docs/tool-reference.md#get_console_message)
270271
- [`list_console_messages`](docs/tool-reference.md#list_console_messages)
271272
- [`take_screenshot`](docs/tool-reference.md#take_screenshot)
272273
- [`take_snapshot`](docs/tool-reference.md#take_snapshot)

docs/tool-reference.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@
2929
- **[Network](#network)** (2 tools)
3030
- [`get_network_request`](#get_network_request)
3131
- [`list_network_requests`](#list_network_requests)
32-
- **[Debugging](#debugging)** (4 tools)
32+
- **[Debugging](#debugging)** (5 tools)
3333
- [`evaluate_script`](#evaluate_script)
34+
- [`get_console_message`](#get_console_message)
3435
- [`list_console_messages`](#list_console_messages)
3536
- [`take_screenshot`](#take_screenshot)
3637
- [`take_snapshot`](#take_snapshot)
@@ -296,6 +297,16 @@ so returned values have to JSON-serializable.
296297

297298
---
298299

300+
### `get_console_message`
301+
302+
**Description:** Gets a console message by its ID. You can get all messages by calling [`list_console_messages`](#list_console_messages).
303+
304+
**Parameters:**
305+
306+
- **msgid** (number) **(required)**: The msgid of a console message on the page from the listed console messages
307+
308+
---
309+
299310
### `list_console_messages`
300311

301312
**Description:** List all console messages for the currently selected page since the last navigation.

src/McpContext.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ export class McpContext implements Context {
132132
return this.#consoleCollector.getData(page);
133133
}
134134

135+
getConsoleMessageStableId(message: ConsoleMessage | Error): number {
136+
return this.#consoleCollector.getIdForResource(message);
137+
}
138+
139+
getConsoleMessageById(id: number): ConsoleMessage | Error {
140+
return this.#consoleCollector.getById(this.getSelectedPage(), id);
141+
}
142+
135143
async newPage(): Promise<Page> {
136144
const page = await this.browser.newPage();
137145
const pages = await this.createPagesSnapshot();

src/McpResponse.ts

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
*/
66
import type {ConsoleMessage, ResourceType} from 'puppeteer-core';
77

8-
import {formatConsoleEvent} from './formatters/consoleFormatter.js';
8+
import {
9+
formatConsoleEventShort,
10+
formatConsoleEventVerbose,
11+
} from './formatters/consoleFormatter.js';
912
import {
1013
getFormattedHeaderValue,
1114
getFormattedResponseBody,
@@ -31,16 +34,18 @@ interface NetworkRequestData {
3134
}
3235

3336
export interface ConsoleMessageData {
34-
type: string;
35-
message: string;
36-
args: string[];
37+
consoleMessageStableId: number;
38+
type?: string;
39+
message?: string;
40+
args?: string[];
3741
}
3842

3943
export class McpResponse implements Response {
4044
#includePages = false;
4145
#includeSnapshot = false;
4246
#includeVerboseSnapshot = false;
4347
#attachedNetworkRequestData?: NetworkRequestData;
48+
#attachedConsoleMessageData?: ConsoleMessageData;
4449
#consoleMessagesData?: ConsoleMessageData[];
4550
#textResponseLines: string[] = [];
4651
#images: ImageContentData[] = [];
@@ -118,6 +123,12 @@ export class McpResponse implements Response {
118123
};
119124
}
120125

126+
attachConsoleMessage(id: number): void {
127+
this.#attachedConsoleMessageData = {
128+
consoleMessageStableId: id,
129+
};
130+
}
131+
121132
get includePages(): boolean {
122133
return this.#includePages;
123134
}
@@ -192,14 +203,62 @@ export class McpResponse implements Response {
192203
}
193204
}
194205

206+
if (this.#attachedConsoleMessageData?.consoleMessageStableId) {
207+
const message = context.getConsoleMessageById(
208+
this.#attachedConsoleMessageData.consoleMessageStableId,
209+
);
210+
const consoleMessageStableId =
211+
this.#attachedConsoleMessageData.consoleMessageStableId;
212+
let data: ConsoleMessageData;
213+
if ('args' in message) {
214+
const consoleMessage = message as ConsoleMessage;
215+
data = {
216+
consoleMessageStableId,
217+
type: consoleMessage.type(),
218+
message: consoleMessage.text(),
219+
args: await Promise.all(
220+
consoleMessage.args().map(async arg => {
221+
const stringArg = await arg.jsonValue().catch(() => {
222+
// Ignore errors.
223+
});
224+
return typeof stringArg === 'object'
225+
? JSON.stringify(stringArg)
226+
: String(stringArg);
227+
}),
228+
),
229+
};
230+
} else {
231+
data = {
232+
consoleMessageStableId,
233+
type: 'error',
234+
message: (message as Error).message,
235+
args: [],
236+
};
237+
}
238+
this.#attachedConsoleMessageData = data;
239+
}
240+
195241
if (this.#consoleDataOptions?.include) {
196-
const messages = context.getConsoleData();
242+
let messages = context.getConsoleData();
243+
244+
if (this.#consoleDataOptions.types?.length) {
245+
const normalizedTypes = new Set(this.#consoleDataOptions.types);
246+
messages = messages.filter(message => {
247+
if ('type' in message) {
248+
return normalizedTypes.has(message.type());
249+
}
250+
return normalizedTypes.has('error');
251+
});
252+
}
197253

198254
this.#consoleMessagesData = await Promise.all(
199255
messages.map(async (item): Promise<ConsoleMessageData> => {
256+
const consoleMessageStableId =
257+
context.getConsoleMessageStableId(item);
200258
if ('args' in item) {
201259
const consoleMessage = item as ConsoleMessage;
202260
return {
261+
consoleMessageStableId,
203262
type: consoleMessage.type(),
204263
message: consoleMessage.text(),
205264
args: await Promise.all(
@@ -215,6 +274,7 @@ export class McpResponse implements Response {
215274
};
216275
}
217276
return {
277+
consoleMessageStableId,
218278
type: 'error',
219279
message: (item as Error).message,
220280
args: [],
@@ -283,6 +343,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
283343
}
284344

285345
response.push(...this.#getIncludeNetworkRequestsData(context));
346+
response.push(...this.#getAttachedConsoleMessageData());
286347

287348
if (this.#networkRequestsOptions?.include) {
288349
let requests = context.getNetworkRequests();
@@ -329,7 +390,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
329390
);
330391
response.push(...data.info);
331392
response.push(
332-
...data.items.map(message => formatConsoleEvent(message)),
393+
...data.items.map(message => formatConsoleEventShort(message)),
333394
);
334395
} else {
335396
response.push('<no console messages found>');
@@ -376,6 +437,18 @@ Call ${handleDialog.name} to handle it before continuing.`);
376437
};
377438
}
378439

440+
#getAttachedConsoleMessageData(): string[] {
441+
const response: string[] = [];
442+
const data = this.#attachedConsoleMessageData;
443+
if (!data) {
444+
return response;
445+
}
446+
447+
response.push(`## Console Message ${data.consoleMessageStableId}`);
448+
response.push(formatConsoleEventVerbose(data));
449+
return response;
450+
}
451+
379452
#getIncludeNetworkRequestsData(context: McpContext): string[] {
380453
const response: string[] = [];
381454
const url = this.#attachedNetworkRequestData?.networkRequestStableId;

src/formatters/consoleFormatter.ts

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,55 @@ const logLevels: Record<string, string> = {
1515
assert: 'Assert',
1616
};
1717

18-
export function formatConsoleEvent(msg: ConsoleMessageData): string {
19-
const logLevel = logLevels[msg.type] ?? 'Log';
20-
const text = msg.message;
18+
// The short format for a console message, based on a previous format.
19+
export function formatConsoleEventShort(msg: ConsoleMessageData): string {
20+
const args = msg.args ? formatArgs(msg, false) : '';
21+
return `msgid=${msg.consoleMessageStableId} [${msg.type}] ${msg.message}${args}`;
22+
}
23+
24+
// The verbose format for a console message, including all details.
25+
export function formatConsoleEventVerbose(msg: ConsoleMessageData): string {
26+
const logLevel = msg.type ? (logLevels[msg.type] ?? 'Log') : 'Log';
27+
let result = `${logLevel}> ${msg.message}`;
28+
29+
if (msg.args && msg.args.length > 0) {
30+
result += formatArgs(msg, true);
31+
}
32+
33+
result += `
34+
ID: ${msg.consoleMessageStableId}`;
35+
result += `
36+
Type: ${msg.type}`;
2137

22-
const formattedArgs = formatArgs(msg.args, text);
23-
return `${logLevel}> ${text} ${formattedArgs}`.trim();
38+
return result;
2439
}
2540

26-
// Only includes the first arg and indicates that there are more args
27-
function formatArgs(args: string[], messageText: string): string {
28-
if (args.length === 0) {
41+
// If `includeAllArgs` is false, only includes the first arg and indicates that there are more args.
42+
function formatArgs(
43+
consoleData: ConsoleMessageData,
44+
includeAllArgs = false,
45+
): string {
46+
if (!consoleData.args || consoleData.args.length === 0) {
2947
return '';
3048
}
3149

3250
let formattedArgs = '';
33-
const firstArg = args[0];
51+
// In the short format version, we only include the first arg.
52+
const messageArgsToFormat = includeAllArgs
53+
? consoleData.args
54+
: [consoleData.args[0]];
3455

35-
if (firstArg !== messageText) {
36-
formattedArgs +=
37-
typeof firstArg === 'object'
38-
? JSON.stringify(firstArg)
39-
: String(firstArg);
56+
for (const arg of messageArgsToFormat) {
57+
if (arg !== consoleData.message) {
58+
formattedArgs += ' ';
59+
formattedArgs +=
60+
typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
61+
}
4062
}
4163

42-
if (args.length > 1) {
43-
return `${formattedArgs} ...`;
64+
if (!includeAllArgs && consoleData.args.length > 1) {
65+
formattedArgs += ` ...`;
4466
}
4567

46-
return formattedArgs;
68+
return formattedArgs.length > 0 ? ` Args:${formattedArgs}` : '';
4769
}

src/tools/ToolDefinition.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export interface Response {
6262
setIncludeSnapshot(value: boolean, verbose?: boolean): void;
6363
attachImage(value: ImageContentData): void;
6464
attachNetworkRequest(reqid: number): void;
65+
attachConsoleMessage(msgid: number): void;
6566
}
6667

6768
/**

src/tools/console.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const FILTERABLE_MESSAGE_TYPES: readonly [
3636
'verbose',
3737
];
3838

39-
export const consoleTool = defineTool({
39+
export const listConsoleMessages = defineTool({
4040
name: 'list_console_messages',
4141
description:
4242
'List all console messages for the currently selected page since the last navigation.',
@@ -76,3 +76,22 @@ export const consoleTool = defineTool({
7676
});
7777
},
7878
});
79+
80+
export const getConsoleMessage = defineTool({
81+
name: 'get_console_message',
82+
description: `Gets a console message by its ID. You can get all messages by calling ${listConsoleMessages.name}.`,
83+
annotations: {
84+
category: ToolCategories.DEBUGGING,
85+
readOnlyHint: true,
86+
},
87+
schema: {
88+
msgid: zod
89+
.number()
90+
.describe(
91+
'The msgid of a console message on the page from the listed console messages',
92+
),
93+
},
94+
handler: async (request, response) => {
95+
response.attachConsoleMessage(request.params.msgid);
96+
},
97+
});

0 commit comments

Comments
 (0)