From 9f3f78ba5af8ef41338468b35fccb936c7625bea Mon Sep 17 00:00:00 2001 From: Oz Tamir Date: Fri, 26 Sep 2025 09:49:13 +0300 Subject: [PATCH 01/10] feat: Support filtering network request by resource type --- src/McpResponse.ts | 59 ++++++++++++++++++++++++------------- src/tools/ToolDefinition.ts | 10 +++---- src/tools/network.ts | 34 +++++++++++++++++++-- tests/utils.ts | 16 ++++++---- 4 files changed, 85 insertions(+), 34 deletions(-) diff --git a/src/McpResponse.ts b/src/McpResponse.ts index 3f44c8f14..475b33df3 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -7,28 +7,32 @@ import type { ImageContent, TextContent, } from '@modelcontextprotocol/sdk/types.js'; +import type { ResourceType } from 'puppeteer-core'; -import {formatConsoleEvent} from './formatters/consoleFormatter.js'; +import { formatConsoleEvent } from './formatters/consoleFormatter.js'; import { getFormattedHeaderValue, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js'; -import {formatA11ySnapshot} from './formatters/snapshotFormatter.js'; -import type {McpContext} from './McpContext.js'; -import type {ImageContentData, Response} from './tools/ToolDefinition.js'; -import {paginate, type PaginationOptions} from './utils/pagination.js'; +import { formatA11ySnapshot } from './formatters/snapshotFormatter.js'; +import type { McpContext } from './McpContext.js'; +import type { ImageContentData, Response } from './tools/ToolDefinition.js'; +import { paginate, type PaginationOptions } from './utils/pagination.js'; export class McpResponse implements Response { #includePages = false; #includeSnapshot = false; - #includeNetworkRequests = false; #attachedNetworkRequestUrl?: string; #includeConsoleData = false; #textResponseLines: string[] = []; #formattedConsoleData?: string[]; #images: ImageContentData[] = []; - #networkRequestsPaginationOptions?: PaginationOptions; + #networkRequestsOptions?: { + include: boolean; + pagination?: PaginationOptions; + resourceTypes?: ResourceType[]; + }; setIncludePages(value: boolean): void { this.#includePages = value; @@ -40,17 +44,20 @@ export class McpResponse implements Response { setIncludeNetworkRequests( value: boolean, - options?: {pageSize?: number; pageIdx?: number}, + options?: { pageSize?: number; pageIdx?: number; resourceTypes?: ResourceType[] }, ): void { - this.#includeNetworkRequests = value; - if (!value || !options) { - this.#networkRequestsPaginationOptions = undefined; + if (!value) { + this.#networkRequestsOptions = undefined; return; } - this.#networkRequestsPaginationOptions = { - pageSize: options.pageSize, - pageIdx: options.pageIdx, + this.#networkRequestsOptions = { + include: value, + pagination: options?.pageSize || options?.pageIdx ? { + pageSize: options.pageSize, + pageIdx: options.pageIdx, + } : undefined, + resourceTypes: options?.resourceTypes, }; } @@ -67,7 +74,7 @@ export class McpResponse implements Response { } get includeNetworkRequests(): boolean { - return this.#includeNetworkRequests; + return this.#networkRequestsOptions?.include ?? false; } get includeConsoleData(): boolean { @@ -77,7 +84,7 @@ export class McpResponse implements Response { return this.#attachedNetworkRequestUrl; } get networkRequestsPageIdx(): number | undefined { - return this.#networkRequestsPaginationOptions?.pageIdx; + return this.#networkRequestsOptions?.pagination?.pageIdx; } appendResponseLine(value: string): void { @@ -179,25 +186,35 @@ Call browser_handle_dialog to handle it before continuing.`); response.push(...this.#getIncludeNetworkRequestsData(context)); - if (this.#includeNetworkRequests) { - const requests = context.getNetworkRequests(); + if (this.#networkRequestsOptions?.include) { + let requests = context.getNetworkRequests(); + + // Apply resource type filtering if specified + if (this.#networkRequestsOptions.resourceTypes) { + const normalizedTypes = new Set(this.#networkRequestsOptions.resourceTypes); + requests = requests.filter(request => { + const type = request.resourceType(); + return normalizedTypes.has(type); + }); + } + response.push('## Network requests'); if (requests.length) { const paginationResult = paginate( requests, - this.#networkRequestsPaginationOptions, + this.#networkRequestsOptions.pagination, ); if (paginationResult.invalidPage) { response.push('Invalid page number provided. Showing first page.'); } - const {startIndex, endIndex, currentPage, totalPages} = + const { startIndex, endIndex, currentPage, totalPages } = paginationResult; response.push( `Showing ${startIndex + 1}-${endIndex} of ${requests.length} (Page ${currentPage + 1} of ${totalPages}).`, ); - if (this.#networkRequestsPaginationOptions) { + if (this.#networkRequestsOptions.pagination) { if (paginationResult.hasNextPage) { response.push(`Next page: ${currentPage + 1}`); } diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index 8e8f027f7..17df65eb6 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -4,12 +4,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type {Dialog, ElementHandle, Page} from 'puppeteer-core'; +import type { Dialog, ElementHandle, Page } from 'puppeteer-core'; import type z from 'zod'; -import type {TraceResult} from '../trace-processing/parse.js'; +import type { TraceResult } from '../trace-processing/parse.js'; -import type {ToolCategories} from './categories.js'; +import type { ToolCategories } from './categories.js'; export interface ToolDefinition { name: string; @@ -44,7 +44,7 @@ export interface Response { setIncludePages(value: boolean): void; setIncludeNetworkRequests( value: boolean, - options?: {pageSize?: number; pageIdx?: number}, + options?: { pageSize?: number; pageIdx?: number; resourceTypes?: string[] }, ): void; setIncludeConsoleData(value: boolean): void; setIncludeSnapshot(value: boolean): void; @@ -73,7 +73,7 @@ export type Context = Readonly<{ saveTemporaryFile( data: Uint8Array, mimeType: 'image/png' | 'image/jpeg', - ): Promise<{filename: string}>; + ): Promise<{ filename: string }>; waitForEventsAfterAction(action: () => Promise): Promise; }>; diff --git a/src/tools/network.ts b/src/tools/network.ts index f4367bf53..0a3972a53 100644 --- a/src/tools/network.ts +++ b/src/tools/network.ts @@ -4,10 +4,33 @@ * SPDX-License-Identifier: Apache-2.0 */ +import type { ResourceType } from 'puppeteer-core'; import z from 'zod'; -import {ToolCategories} from './categories.js'; -import {defineTool} from './ToolDefinition.js'; +import { ToolCategories } from './categories.js'; +import { defineTool } from './ToolDefinition.js'; + +export const FILTERABLE_RESOURCE_TYPES = [ + 'document', + 'stylesheet', + 'image', + 'media', + 'font', + 'script', + 'texttrack', + 'xhr', + 'fetch', + 'prefetch', + 'eventsource', + 'websocket', + 'manifest', + 'signedexchange', + 'ping', + 'cspviolationreport', + 'preflight', + 'fedcm', + 'other', +] as const satisfies readonly ResourceType[]; export const listNetworkRequests = defineTool({ name: 'list_network_requests', @@ -33,11 +56,18 @@ export const listNetworkRequests = defineTool({ .describe( 'Page number to return (0-based). When omitted, returns the first page.', ), + resourceType: z + .array(z.enum(FILTERABLE_RESOURCE_TYPES as any)) + .optional() + .describe( + 'Filter requests by resource type. When omitted, returns all requests.', + ), }, handler: async (request, response) => { response.setIncludeNetworkRequests(true, { pageSize: request.params.pageSize, pageIdx: request.params.pageIdx, + resourceTypes: request.params.resourceType, }); }, }); diff --git a/tests/utils.ts b/tests/utils.ts index b13a6a72b..ed0f32151 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -5,20 +5,20 @@ */ import logger from 'debug'; -import type {Browser} from 'puppeteer'; +import type { Browser } from 'puppeteer'; import puppeteer from 'puppeteer'; -import type {HTTPRequest, HTTPResponse} from 'puppeteer-core'; +import type { HTTPRequest, HTTPResponse } from 'puppeteer-core'; -import {McpContext} from '../src/McpContext.js'; -import {McpResponse} from '../src/McpResponse.js'; +import { McpContext } from '../src/McpContext.js'; +import { McpResponse } from '../src/McpResponse.js'; let browser: Browser | undefined; export async function withBrowser( cb: (response: McpResponse, context: McpContext) => Promise, - options: {debug?: boolean} = {}, + options: { debug?: boolean } = {}, ) { - const {debug = false} = options; + const { debug = false } = options; if (!browser) { browser = await puppeteer.launch({ executablePath: process.env.PUPPETEER_EXECUTABLE_PATH, @@ -46,6 +46,7 @@ export function getMockRequest( method?: string; response?: HTTPResponse; failure?: HTTPRequest['failure']; + resourceType?: string; } = {}, ): HTTPRequest { return { @@ -61,6 +62,9 @@ export function getMockRequest( failure() { return options.failure?.() ?? null; }, + resourceType() { + return options.resourceType ?? 'document'; + }, headers(): Record { return { 'content-size': '10', From ed59c392f5cedb0b2da1c485e934fc10f330cbb7 Mon Sep 17 00:00:00 2001 From: Oz Tamir Date: Fri, 26 Sep 2025 10:05:53 +0300 Subject: [PATCH 02/10] feat: test network resource filtering --- tests/McpResponse.test.ts | 120 ++++++++++++++++++++++++++++++++++---- 1 file changed, 110 insertions(+), 10 deletions(-) diff --git a/tests/McpResponse.test.ts b/tests/McpResponse.test.ts index 7ac63acf0..f81435d37 100644 --- a/tests/McpResponse.test.ts +++ b/tests/McpResponse.test.ts @@ -4,9 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; -import {describe, it} from 'node:test'; +import { describe, it } from 'node:test'; -import {getMockRequest, html, withBrowser} from './utils.js'; +import { getMockRequest, html, withBrowser } from './utils.js'; describe('McpResponse', () => { it('list pages', async () => { @@ -120,7 +120,7 @@ Navigation timeout set to 100000 ms`, }); it('adds image when image is attached', async () => { await withBrowser(async (response, context) => { - response.attachImage({data: 'imageBase64', mimeType: 'image/png'}); + response.attachImage({ data: 'imageBase64', mimeType: 'image/png' }); const result = await response.handle('test', context); assert.strictEqual(result[0].text, `# test response`); assert.equal(result[1].type, 'image'); @@ -190,6 +190,7 @@ http://example.com GET [pending]`, ); }); }); + it('does not include network requests when setting is false', async () => { await withBrowser(async (response, context) => { response.setIncludeNetworkRequests(false); @@ -264,10 +265,109 @@ Log>`), }); }); +describe('McpResponse network request filtering', () => { + it('filters network requests by resource type', async () => { + await withBrowser(async (response, context) => { + response.setIncludeNetworkRequests(true, { + resourceTypes: ['script', 'stylesheet'], + }); + context.getNetworkRequests = () => { + return [ + getMockRequest({ resourceType: 'script' }), + getMockRequest({ resourceType: 'image' }), + getMockRequest({ resourceType: 'stylesheet' }), + getMockRequest({ resourceType: 'document' }), + ]; + }; + const result = await response.handle('test', context); + assert.strictEqual( + result[0].text, + `# test response +## Network requests +Showing 1-2 of 2 (Page 1 of 1). +http://example.com GET [pending] +http://example.com GET [pending]`, + ); + }); + }); + + it('filters network requests by single resource type', async () => { + await withBrowser(async (response, context) => { + response.setIncludeNetworkRequests(true, { + resourceTypes: ['image'], + }); + context.getNetworkRequests = () => { + return [ + getMockRequest({ resourceType: 'script' }), + getMockRequest({ resourceType: 'image' }), + getMockRequest({ resourceType: 'stylesheet' }), + ]; + }; + const result = await response.handle('test', context); + assert.strictEqual( + result[0].text, + `# test response +## Network requests +Showing 1-1 of 1 (Page 1 of 1). +http://example.com GET [pending]`, + ); + }); + }); + + it('shows no requests when filter matches nothing', async () => { + await withBrowser(async (response, context) => { + response.setIncludeNetworkRequests(true, { + resourceTypes: ['font'], + }); + context.getNetworkRequests = () => { + return [ + getMockRequest({ resourceType: 'script' }), + getMockRequest({ resourceType: 'image' }), + getMockRequest({ resourceType: 'stylesheet' }), + ]; + }; + const result = await response.handle('test', context); + assert.strictEqual( + result[0].text, + `# test response +## Network requests +No requests found.`, + ); + }); + }); + + it('shows all requests when no filters are provided', async () => { + await withBrowser(async (response, context) => { + response.setIncludeNetworkRequests(true); + context.getNetworkRequests = () => { + return [ + getMockRequest({ resourceType: 'script' }), + getMockRequest({ resourceType: 'image' }), + getMockRequest({ resourceType: 'stylesheet' }), + getMockRequest({ resourceType: 'document' }), + getMockRequest({ resourceType: 'font' }), + ]; + }; + const result = await response.handle('test', context); + assert.strictEqual( + result[0].text, + `# test response +## Network requests +Showing 1-5 of 5 (Page 1 of 1). +http://example.com GET [pending] +http://example.com GET [pending] +http://example.com GET [pending] +http://example.com GET [pending] +http://example.com GET [pending]`, + ); + }); + }); +}); + describe('McpResponse network pagination', () => { it('returns all requests when pagination is not provided', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({length: 5}, () => getMockRequest()); + const requests = Array.from({ length: 5 }, () => getMockRequest()); context.getNetworkRequests = () => requests; response.setIncludeNetworkRequests(true); const result = await response.handle('test', context); @@ -280,13 +380,13 @@ describe('McpResponse network pagination', () => { it('returns first page by default', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({length: 30}, (_, idx) => - getMockRequest({method: `GET-${idx}`}), + const requests = Array.from({ length: 30 }, (_, idx) => + getMockRequest({ method: `GET-${idx}` }), ); context.getNetworkRequests = () => { return requests; }; - response.setIncludeNetworkRequests(true, {pageSize: 10}); + response.setIncludeNetworkRequests(true, { pageSize: 10 }); const result = await response.handle('test', context); const text = (result[0].text as string).toString(); assert.ok(text.includes('Showing 1-10 of 30 (Page 1 of 3).')); @@ -297,8 +397,8 @@ describe('McpResponse network pagination', () => { it('returns subsequent page when pageIdx provided', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({length: 25}, (_, idx) => - getMockRequest({method: `GET-${idx}`}), + const requests = Array.from({ length: 25 }, (_, idx) => + getMockRequest({ method: `GET-${idx}` }), ); context.getNetworkRequests = () => requests; response.setIncludeNetworkRequests(true, { @@ -315,7 +415,7 @@ describe('McpResponse network pagination', () => { it('handles invalid page number by showing first page', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({length: 5}, () => getMockRequest()); + const requests = Array.from({ length: 5 }, () => getMockRequest()); context.getNetworkRequests = () => requests; response.setIncludeNetworkRequests(true, { pageSize: 2, From fc5c9d397adc151f57388221dc05bca65c4b401a Mon Sep 17 00:00:00 2001 From: Oz Tamir Date: Fri, 26 Sep 2025 10:20:34 +0300 Subject: [PATCH 03/10] fix: don't export filterable resource --- src/tools/network.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/network.ts b/src/tools/network.ts index 0a3972a53..439b731ff 100644 --- a/src/tools/network.ts +++ b/src/tools/network.ts @@ -10,7 +10,7 @@ import z from 'zod'; import { ToolCategories } from './categories.js'; import { defineTool } from './ToolDefinition.js'; -export const FILTERABLE_RESOURCE_TYPES = [ +const FILTERABLE_RESOURCE_TYPES = [ 'document', 'stylesheet', 'image', From ddc8362ef64be4a13a6dbe69cb3a0e4a35abceae Mon Sep 17 00:00:00 2001 From: Oz Tamir Date: Fri, 26 Sep 2025 10:24:35 +0300 Subject: [PATCH 04/10] docs: generate docs + code formatting --- docs/tool-reference.md | 1 + src/McpResponse.ts | 35 ++++++++++++++++---------- src/tools/ToolDefinition.ts | 10 ++++---- src/tools/network.ts | 15 ++++++++--- tests/McpResponse.test.ts | 50 ++++++++++++++++++------------------- tests/utils.ts | 12 ++++----- 6 files changed, 70 insertions(+), 53 deletions(-) diff --git a/docs/tool-reference.md b/docs/tool-reference.md index 83873579b..45e683fc5 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -266,6 +266,7 @@ - **pageIdx** (integer) _(optional)_: Page number to return (0-based). When omitted, returns the first page. - **pageSize** (integer) _(optional)_: Maximum number of requests to return. When omitted, returns all requests. +- **resourceType** (array) _(optional)_: Filter requests by resource type. When omitted, returns all requests. --- diff --git a/src/McpResponse.ts b/src/McpResponse.ts index 475b33df3..62286f0cc 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -7,18 +7,18 @@ import type { ImageContent, TextContent, } from '@modelcontextprotocol/sdk/types.js'; -import type { ResourceType } from 'puppeteer-core'; +import type {ResourceType} from 'puppeteer-core'; -import { formatConsoleEvent } from './formatters/consoleFormatter.js'; +import {formatConsoleEvent} from './formatters/consoleFormatter.js'; import { getFormattedHeaderValue, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js'; -import { formatA11ySnapshot } from './formatters/snapshotFormatter.js'; -import type { McpContext } from './McpContext.js'; -import type { ImageContentData, Response } from './tools/ToolDefinition.js'; -import { paginate, type PaginationOptions } from './utils/pagination.js'; +import {formatA11ySnapshot} from './formatters/snapshotFormatter.js'; +import type {McpContext} from './McpContext.js'; +import type {ImageContentData, Response} from './tools/ToolDefinition.js'; +import {paginate, type PaginationOptions} from './utils/pagination.js'; export class McpResponse implements Response { #includePages = false; @@ -44,7 +44,11 @@ export class McpResponse implements Response { setIncludeNetworkRequests( value: boolean, - options?: { pageSize?: number; pageIdx?: number; resourceTypes?: ResourceType[] }, + options?: { + pageSize?: number; + pageIdx?: number; + resourceTypes?: ResourceType[]; + }, ): void { if (!value) { this.#networkRequestsOptions = undefined; @@ -53,10 +57,13 @@ export class McpResponse implements Response { this.#networkRequestsOptions = { include: value, - pagination: options?.pageSize || options?.pageIdx ? { - pageSize: options.pageSize, - pageIdx: options.pageIdx, - } : undefined, + pagination: + options?.pageSize || options?.pageIdx + ? { + pageSize: options.pageSize, + pageIdx: options.pageIdx, + } + : undefined, resourceTypes: options?.resourceTypes, }; } @@ -191,7 +198,9 @@ Call browser_handle_dialog to handle it before continuing.`); // Apply resource type filtering if specified if (this.#networkRequestsOptions.resourceTypes) { - const normalizedTypes = new Set(this.#networkRequestsOptions.resourceTypes); + const normalizedTypes = new Set( + this.#networkRequestsOptions.resourceTypes, + ); requests = requests.filter(request => { const type = request.resourceType(); return normalizedTypes.has(type); @@ -208,7 +217,7 @@ Call browser_handle_dialog to handle it before continuing.`); response.push('Invalid page number provided. Showing first page.'); } - const { startIndex, endIndex, currentPage, totalPages } = + const {startIndex, endIndex, currentPage, totalPages} = paginationResult; response.push( `Showing ${startIndex + 1}-${endIndex} of ${requests.length} (Page ${currentPage + 1} of ${totalPages}).`, diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index 17df65eb6..a0741f4cd 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -4,12 +4,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { Dialog, ElementHandle, Page } from 'puppeteer-core'; +import type {Dialog, ElementHandle, Page} from 'puppeteer-core'; import type z from 'zod'; -import type { TraceResult } from '../trace-processing/parse.js'; +import type {TraceResult} from '../trace-processing/parse.js'; -import type { ToolCategories } from './categories.js'; +import type {ToolCategories} from './categories.js'; export interface ToolDefinition { name: string; @@ -44,7 +44,7 @@ export interface Response { setIncludePages(value: boolean): void; setIncludeNetworkRequests( value: boolean, - options?: { pageSize?: number; pageIdx?: number; resourceTypes?: string[] }, + options?: {pageSize?: number; pageIdx?: number; resourceTypes?: string[]}, ): void; setIncludeConsoleData(value: boolean): void; setIncludeSnapshot(value: boolean): void; @@ -73,7 +73,7 @@ export type Context = Readonly<{ saveTemporaryFile( data: Uint8Array, mimeType: 'image/png' | 'image/jpeg', - ): Promise<{ filename: string }>; + ): Promise<{filename: string}>; waitForEventsAfterAction(action: () => Promise): Promise; }>; diff --git a/src/tools/network.ts b/src/tools/network.ts index 439b731ff..c86fd93bd 100644 --- a/src/tools/network.ts +++ b/src/tools/network.ts @@ -4,11 +4,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { ResourceType } from 'puppeteer-core'; +import type {ResourceType} from 'puppeteer-core'; import z from 'zod'; -import { ToolCategories } from './categories.js'; -import { defineTool } from './ToolDefinition.js'; +import {ToolCategories} from './categories.js'; +import {defineTool} from './ToolDefinition.js'; const FILTERABLE_RESOURCE_TYPES = [ 'document', @@ -57,7 +57,14 @@ export const listNetworkRequests = defineTool({ 'Page number to return (0-based). When omitted, returns the first page.', ), resourceType: z - .array(z.enum(FILTERABLE_RESOURCE_TYPES as any)) + .array( + z.enum( + FILTERABLE_RESOURCE_TYPES as unknown as [ + ResourceType, + ...ResourceType[], + ], + ), + ) .optional() .describe( 'Filter requests by resource type. When omitted, returns all requests.', diff --git a/tests/McpResponse.test.ts b/tests/McpResponse.test.ts index f81435d37..2a8cdd6a9 100644 --- a/tests/McpResponse.test.ts +++ b/tests/McpResponse.test.ts @@ -4,9 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; -import { describe, it } from 'node:test'; +import {describe, it} from 'node:test'; -import { getMockRequest, html, withBrowser } from './utils.js'; +import {getMockRequest, html, withBrowser} from './utils.js'; describe('McpResponse', () => { it('list pages', async () => { @@ -120,7 +120,7 @@ Navigation timeout set to 100000 ms`, }); it('adds image when image is attached', async () => { await withBrowser(async (response, context) => { - response.attachImage({ data: 'imageBase64', mimeType: 'image/png' }); + response.attachImage({data: 'imageBase64', mimeType: 'image/png'}); const result = await response.handle('test', context); assert.strictEqual(result[0].text, `# test response`); assert.equal(result[1].type, 'image'); @@ -273,10 +273,10 @@ describe('McpResponse network request filtering', () => { }); context.getNetworkRequests = () => { return [ - getMockRequest({ resourceType: 'script' }), - getMockRequest({ resourceType: 'image' }), - getMockRequest({ resourceType: 'stylesheet' }), - getMockRequest({ resourceType: 'document' }), + getMockRequest({resourceType: 'script'}), + getMockRequest({resourceType: 'image'}), + getMockRequest({resourceType: 'stylesheet'}), + getMockRequest({resourceType: 'document'}), ]; }; const result = await response.handle('test', context); @@ -298,9 +298,9 @@ http://example.com GET [pending]`, }); context.getNetworkRequests = () => { return [ - getMockRequest({ resourceType: 'script' }), - getMockRequest({ resourceType: 'image' }), - getMockRequest({ resourceType: 'stylesheet' }), + getMockRequest({resourceType: 'script'}), + getMockRequest({resourceType: 'image'}), + getMockRequest({resourceType: 'stylesheet'}), ]; }; const result = await response.handle('test', context); @@ -321,9 +321,9 @@ http://example.com GET [pending]`, }); context.getNetworkRequests = () => { return [ - getMockRequest({ resourceType: 'script' }), - getMockRequest({ resourceType: 'image' }), - getMockRequest({ resourceType: 'stylesheet' }), + getMockRequest({resourceType: 'script'}), + getMockRequest({resourceType: 'image'}), + getMockRequest({resourceType: 'stylesheet'}), ]; }; const result = await response.handle('test', context); @@ -341,11 +341,11 @@ No requests found.`, response.setIncludeNetworkRequests(true); context.getNetworkRequests = () => { return [ - getMockRequest({ resourceType: 'script' }), - getMockRequest({ resourceType: 'image' }), - getMockRequest({ resourceType: 'stylesheet' }), - getMockRequest({ resourceType: 'document' }), - getMockRequest({ resourceType: 'font' }), + getMockRequest({resourceType: 'script'}), + getMockRequest({resourceType: 'image'}), + getMockRequest({resourceType: 'stylesheet'}), + getMockRequest({resourceType: 'document'}), + getMockRequest({resourceType: 'font'}), ]; }; const result = await response.handle('test', context); @@ -367,7 +367,7 @@ http://example.com GET [pending]`, describe('McpResponse network pagination', () => { it('returns all requests when pagination is not provided', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({ length: 5 }, () => getMockRequest()); + const requests = Array.from({length: 5}, () => getMockRequest()); context.getNetworkRequests = () => requests; response.setIncludeNetworkRequests(true); const result = await response.handle('test', context); @@ -380,13 +380,13 @@ describe('McpResponse network pagination', () => { it('returns first page by default', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({ length: 30 }, (_, idx) => - getMockRequest({ method: `GET-${idx}` }), + const requests = Array.from({length: 30}, (_, idx) => + getMockRequest({method: `GET-${idx}`}), ); context.getNetworkRequests = () => { return requests; }; - response.setIncludeNetworkRequests(true, { pageSize: 10 }); + response.setIncludeNetworkRequests(true, {pageSize: 10}); const result = await response.handle('test', context); const text = (result[0].text as string).toString(); assert.ok(text.includes('Showing 1-10 of 30 (Page 1 of 3).')); @@ -397,8 +397,8 @@ describe('McpResponse network pagination', () => { it('returns subsequent page when pageIdx provided', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({ length: 25 }, (_, idx) => - getMockRequest({ method: `GET-${idx}` }), + const requests = Array.from({length: 25}, (_, idx) => + getMockRequest({method: `GET-${idx}`}), ); context.getNetworkRequests = () => requests; response.setIncludeNetworkRequests(true, { @@ -415,7 +415,7 @@ describe('McpResponse network pagination', () => { it('handles invalid page number by showing first page', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({ length: 5 }, () => getMockRequest()); + const requests = Array.from({length: 5}, () => getMockRequest()); context.getNetworkRequests = () => requests; response.setIncludeNetworkRequests(true, { pageSize: 2, diff --git a/tests/utils.ts b/tests/utils.ts index ed0f32151..6fc2cb797 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -5,20 +5,20 @@ */ import logger from 'debug'; -import type { Browser } from 'puppeteer'; +import type {Browser} from 'puppeteer'; import puppeteer from 'puppeteer'; -import type { HTTPRequest, HTTPResponse } from 'puppeteer-core'; +import type {HTTPRequest, HTTPResponse} from 'puppeteer-core'; -import { McpContext } from '../src/McpContext.js'; -import { McpResponse } from '../src/McpResponse.js'; +import {McpContext} from '../src/McpContext.js'; +import {McpResponse} from '../src/McpResponse.js'; let browser: Browser | undefined; export async function withBrowser( cb: (response: McpResponse, context: McpContext) => Promise, - options: { debug?: boolean } = {}, + options: {debug?: boolean} = {}, ) { - const { debug = false } = options; + const {debug = false} = options; if (!browser) { browser = await puppeteer.launch({ executablePath: process.env.PUPPETEER_EXECUTABLE_PATH, From be7cfcbf576bf324976db4334576f59204afd770 Mon Sep 17 00:00:00 2001 From: Oz Tamir Date: Fri, 26 Sep 2025 10:31:15 +0300 Subject: [PATCH 05/10] feat: better parameter description --- docs/tool-reference.md | 2 +- src/tools/network.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tool-reference.md b/docs/tool-reference.md index 45e683fc5..e9a76b080 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -266,7 +266,7 @@ - **pageIdx** (integer) _(optional)_: Page number to return (0-based). When omitted, returns the first page. - **pageSize** (integer) _(optional)_: Maximum number of requests to return. When omitted, returns all requests. -- **resourceType** (array) _(optional)_: Filter requests by resource type. When omitted, returns all requests. +- **resourceType** (array) _(optional)_: Filter requests to only return requests of the specified resource types. When omitted, returns all requests. --- diff --git a/src/tools/network.ts b/src/tools/network.ts index c86fd93bd..0d60be3fc 100644 --- a/src/tools/network.ts +++ b/src/tools/network.ts @@ -67,7 +67,7 @@ export const listNetworkRequests = defineTool({ ) .optional() .describe( - 'Filter requests by resource type. When omitted, returns all requests.', + 'Filter requests to only return requests of the specified resource types. When omitted, returns all requests.', ), }, handler: async (request, response) => { From 3dfcbabda62bea92c4964721a4c6213b35f14594 Mon Sep 17 00:00:00 2001 From: Oz Tamir Date: Fri, 26 Sep 2025 14:18:22 +0300 Subject: [PATCH 06/10] fix: typo in parameter name --- src/tools/network.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tools/network.ts b/src/tools/network.ts index 0d60be3fc..2b87b9ff5 100644 --- a/src/tools/network.ts +++ b/src/tools/network.ts @@ -4,11 +4,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type {ResourceType} from 'puppeteer-core'; +import type { ResourceType } from 'puppeteer-core'; import z from 'zod'; -import {ToolCategories} from './categories.js'; -import {defineTool} from './ToolDefinition.js'; +import { ToolCategories } from './categories.js'; +import { defineTool } from './ToolDefinition.js'; const FILTERABLE_RESOURCE_TYPES = [ 'document', @@ -56,7 +56,7 @@ export const listNetworkRequests = defineTool({ .describe( 'Page number to return (0-based). When omitted, returns the first page.', ), - resourceType: z + resourceTypes: z .array( z.enum( FILTERABLE_RESOURCE_TYPES as unknown as [ @@ -74,7 +74,7 @@ export const listNetworkRequests = defineTool({ response.setIncludeNetworkRequests(true, { pageSize: request.params.pageSize, pageIdx: request.params.pageIdx, - resourceTypes: request.params.resourceType, + resourceTypes: request.params.resourceTypes, }); }, }); From 28ef64ad0e586ff6c6c200526cff9e9667c31de5 Mon Sep 17 00:00:00 2001 From: Oz Tamir Date: Fri, 26 Sep 2025 14:18:55 +0300 Subject: [PATCH 07/10] fix: ensure that resourceTypes has elements --- src/McpResponse.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/McpResponse.ts b/src/McpResponse.ts index 62286f0cc..d495fc347 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -7,18 +7,18 @@ import type { ImageContent, TextContent, } from '@modelcontextprotocol/sdk/types.js'; -import type {ResourceType} from 'puppeteer-core'; +import type { ResourceType } from 'puppeteer-core'; -import {formatConsoleEvent} from './formatters/consoleFormatter.js'; +import { formatConsoleEvent } from './formatters/consoleFormatter.js'; import { getFormattedHeaderValue, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js'; -import {formatA11ySnapshot} from './formatters/snapshotFormatter.js'; -import type {McpContext} from './McpContext.js'; -import type {ImageContentData, Response} from './tools/ToolDefinition.js'; -import {paginate, type PaginationOptions} from './utils/pagination.js'; +import { formatA11ySnapshot } from './formatters/snapshotFormatter.js'; +import type { McpContext } from './McpContext.js'; +import type { ImageContentData, Response } from './tools/ToolDefinition.js'; +import { paginate, type PaginationOptions } from './utils/pagination.js'; export class McpResponse implements Response { #includePages = false; @@ -60,9 +60,9 @@ export class McpResponse implements Response { pagination: options?.pageSize || options?.pageIdx ? { - pageSize: options.pageSize, - pageIdx: options.pageIdx, - } + pageSize: options.pageSize, + pageIdx: options.pageIdx, + } : undefined, resourceTypes: options?.resourceTypes, }; @@ -197,7 +197,7 @@ Call browser_handle_dialog to handle it before continuing.`); let requests = context.getNetworkRequests(); // Apply resource type filtering if specified - if (this.#networkRequestsOptions.resourceTypes) { + if (this.#networkRequestsOptions.resourceTypes?.length) { const normalizedTypes = new Set( this.#networkRequestsOptions.resourceTypes, ); @@ -217,7 +217,7 @@ Call browser_handle_dialog to handle it before continuing.`); response.push('Invalid page number provided. Showing first page.'); } - const {startIndex, endIndex, currentPage, totalPages} = + const { startIndex, endIndex, currentPage, totalPages } = paginationResult; response.push( `Showing ${startIndex + 1}-${endIndex} of ${requests.length} (Page ${currentPage + 1} of ${totalPages}).`, From 8dd9e9794b35cdf8d786c4c86bd5abd1dee540e4 Mon Sep 17 00:00:00 2001 From: Oz Tamir Date: Fri, 26 Sep 2025 14:20:05 +0300 Subject: [PATCH 08/10] fix: proper typing for FILTERABLE_RESOURCE_TYPES --- src/tools/network.ts | 52 +++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/tools/network.ts b/src/tools/network.ts index 2b87b9ff5..d0d741955 100644 --- a/src/tools/network.ts +++ b/src/tools/network.ts @@ -10,27 +10,30 @@ import z from 'zod'; import { ToolCategories } from './categories.js'; import { defineTool } from './ToolDefinition.js'; -const FILTERABLE_RESOURCE_TYPES = [ - 'document', - 'stylesheet', - 'image', - 'media', - 'font', - 'script', - 'texttrack', - 'xhr', - 'fetch', - 'prefetch', - 'eventsource', - 'websocket', - 'manifest', - 'signedexchange', - 'ping', - 'cspviolationreport', - 'preflight', - 'fedcm', - 'other', -] as const satisfies readonly ResourceType[]; +const FILTERABLE_RESOURCE_TYPES: readonly [ + ResourceType, + ...ResourceType[], +] = [ + 'document', + 'stylesheet', + 'image', + 'media', + 'font', + 'script', + 'texttrack', + 'xhr', + 'fetch', + 'prefetch', + 'eventsource', + 'websocket', + 'manifest', + 'signedexchange', + 'ping', + 'cspviolationreport', + 'preflight', + 'fedcm', + 'other', + ]; export const listNetworkRequests = defineTool({ name: 'list_network_requests', @@ -58,12 +61,7 @@ export const listNetworkRequests = defineTool({ ), resourceTypes: z .array( - z.enum( - FILTERABLE_RESOURCE_TYPES as unknown as [ - ResourceType, - ...ResourceType[], - ], - ), + z.enum(FILTERABLE_RESOURCE_TYPES), ) .optional() .describe( From 9ac7923ca6ffb16d639477ec8cf23f7623bfd1f4 Mon Sep 17 00:00:00 2001 From: Oz Tamir Date: Fri, 26 Sep 2025 14:24:19 +0300 Subject: [PATCH 09/10] fix: add test for empty resourceTypes array --- src/tools/network.ts | 2 +- tests/McpResponse.test.ts | 79 ++++++++++++++++++++++++++------------- 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/src/tools/network.ts b/src/tools/network.ts index d0d741955..18bd05071 100644 --- a/src/tools/network.ts +++ b/src/tools/network.ts @@ -65,7 +65,7 @@ export const listNetworkRequests = defineTool({ ) .optional() .describe( - 'Filter requests to only return requests of the specified resource types. When omitted, returns all requests.', + 'Filter requests to only return requests of the specified resource types. When omitted or empty, returns all requests.', ), }, handler: async (request, response) => { diff --git a/tests/McpResponse.test.ts b/tests/McpResponse.test.ts index 2a8cdd6a9..c7981b54f 100644 --- a/tests/McpResponse.test.ts +++ b/tests/McpResponse.test.ts @@ -4,9 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; -import {describe, it} from 'node:test'; +import { describe, it } from 'node:test'; -import {getMockRequest, html, withBrowser} from './utils.js'; +import { getMockRequest, html, withBrowser } from './utils.js'; describe('McpResponse', () => { it('list pages', async () => { @@ -120,7 +120,7 @@ Navigation timeout set to 100000 ms`, }); it('adds image when image is attached', async () => { await withBrowser(async (response, context) => { - response.attachImage({data: 'imageBase64', mimeType: 'image/png'}); + response.attachImage({ data: 'imageBase64', mimeType: 'image/png' }); const result = await response.handle('test', context); assert.strictEqual(result[0].text, `# test response`); assert.equal(result[1].type, 'image'); @@ -273,10 +273,10 @@ describe('McpResponse network request filtering', () => { }); context.getNetworkRequests = () => { return [ - getMockRequest({resourceType: 'script'}), - getMockRequest({resourceType: 'image'}), - getMockRequest({resourceType: 'stylesheet'}), - getMockRequest({resourceType: 'document'}), + getMockRequest({ resourceType: 'script' }), + getMockRequest({ resourceType: 'image' }), + getMockRequest({ resourceType: 'stylesheet' }), + getMockRequest({ resourceType: 'document' }), ]; }; const result = await response.handle('test', context); @@ -298,9 +298,9 @@ http://example.com GET [pending]`, }); context.getNetworkRequests = () => { return [ - getMockRequest({resourceType: 'script'}), - getMockRequest({resourceType: 'image'}), - getMockRequest({resourceType: 'stylesheet'}), + getMockRequest({ resourceType: 'script' }), + getMockRequest({ resourceType: 'image' }), + getMockRequest({ resourceType: 'stylesheet' }), ]; }; const result = await response.handle('test', context); @@ -321,9 +321,9 @@ http://example.com GET [pending]`, }); context.getNetworkRequests = () => { return [ - getMockRequest({resourceType: 'script'}), - getMockRequest({resourceType: 'image'}), - getMockRequest({resourceType: 'stylesheet'}), + getMockRequest({ resourceType: 'script' }), + getMockRequest({ resourceType: 'image' }), + getMockRequest({ resourceType: 'stylesheet' }), ]; }; const result = await response.handle('test', context); @@ -341,11 +341,40 @@ No requests found.`, response.setIncludeNetworkRequests(true); context.getNetworkRequests = () => { return [ - getMockRequest({resourceType: 'script'}), - getMockRequest({resourceType: 'image'}), - getMockRequest({resourceType: 'stylesheet'}), - getMockRequest({resourceType: 'document'}), - getMockRequest({resourceType: 'font'}), + getMockRequest({ resourceType: 'script' }), + getMockRequest({ resourceType: 'image' }), + getMockRequest({ resourceType: 'stylesheet' }), + getMockRequest({ resourceType: 'document' }), + getMockRequest({ resourceType: 'font' }), + ]; + }; + const result = await response.handle('test', context); + assert.strictEqual( + result[0].text, + `# test response +## Network requests +Showing 1-5 of 5 (Page 1 of 1). +http://example.com GET [pending] +http://example.com GET [pending] +http://example.com GET [pending] +http://example.com GET [pending] +http://example.com GET [pending]`, + ); + }); + }); + + it('shows all requests when empty resourceTypes array is provided', async () => { + await withBrowser(async (response, context) => { + response.setIncludeNetworkRequests(true, { + resourceTypes: [], + }); + context.getNetworkRequests = () => { + return [ + getMockRequest({ resourceType: 'script' }), + getMockRequest({ resourceType: 'image' }), + getMockRequest({ resourceType: 'stylesheet' }), + getMockRequest({ resourceType: 'document' }), + getMockRequest({ resourceType: 'font' }), ]; }; const result = await response.handle('test', context); @@ -367,7 +396,7 @@ http://example.com GET [pending]`, describe('McpResponse network pagination', () => { it('returns all requests when pagination is not provided', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({length: 5}, () => getMockRequest()); + const requests = Array.from({ length: 5 }, () => getMockRequest()); context.getNetworkRequests = () => requests; response.setIncludeNetworkRequests(true); const result = await response.handle('test', context); @@ -380,13 +409,13 @@ describe('McpResponse network pagination', () => { it('returns first page by default', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({length: 30}, (_, idx) => - getMockRequest({method: `GET-${idx}`}), + const requests = Array.from({ length: 30 }, (_, idx) => + getMockRequest({ method: `GET-${idx}` }), ); context.getNetworkRequests = () => { return requests; }; - response.setIncludeNetworkRequests(true, {pageSize: 10}); + response.setIncludeNetworkRequests(true, { pageSize: 10 }); const result = await response.handle('test', context); const text = (result[0].text as string).toString(); assert.ok(text.includes('Showing 1-10 of 30 (Page 1 of 3).')); @@ -397,8 +426,8 @@ describe('McpResponse network pagination', () => { it('returns subsequent page when pageIdx provided', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({length: 25}, (_, idx) => - getMockRequest({method: `GET-${idx}`}), + const requests = Array.from({ length: 25 }, (_, idx) => + getMockRequest({ method: `GET-${idx}` }), ); context.getNetworkRequests = () => requests; response.setIncludeNetworkRequests(true, { @@ -415,7 +444,7 @@ describe('McpResponse network pagination', () => { it('handles invalid page number by showing first page', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({length: 5}, () => getMockRequest()); + const requests = Array.from({ length: 5 }, () => getMockRequest()); context.getNetworkRequests = () => requests; response.setIncludeNetworkRequests(true, { pageSize: 2, From 68307dca4e04e6e251c873e20d95a9e31e142810 Mon Sep 17 00:00:00 2001 From: Oz Tamir Date: Fri, 26 Sep 2025 14:24:50 +0300 Subject: [PATCH 10/10] docs: auto generated docs + formatting --- docs/tool-reference.md | 2 +- src/McpResponse.ts | 20 ++++++------- src/tools/network.ts | 55 ++++++++++++++++------------------- tests/McpResponse.test.ts | 60 +++++++++++++++++++-------------------- 4 files changed, 66 insertions(+), 71 deletions(-) diff --git a/docs/tool-reference.md b/docs/tool-reference.md index e9a76b080..3356b06ab 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -266,7 +266,7 @@ - **pageIdx** (integer) _(optional)_: Page number to return (0-based). When omitted, returns the first page. - **pageSize** (integer) _(optional)_: Maximum number of requests to return. When omitted, returns all requests. -- **resourceType** (array) _(optional)_: Filter requests to only return requests of the specified resource types. When omitted, returns all requests. +- **resourceTypes** (array) _(optional)_: Filter requests to only return requests of the specified resource types. When omitted or empty, returns all requests. --- diff --git a/src/McpResponse.ts b/src/McpResponse.ts index d495fc347..eff7e7085 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -7,18 +7,18 @@ import type { ImageContent, TextContent, } from '@modelcontextprotocol/sdk/types.js'; -import type { ResourceType } from 'puppeteer-core'; +import type {ResourceType} from 'puppeteer-core'; -import { formatConsoleEvent } from './formatters/consoleFormatter.js'; +import {formatConsoleEvent} from './formatters/consoleFormatter.js'; import { getFormattedHeaderValue, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js'; -import { formatA11ySnapshot } from './formatters/snapshotFormatter.js'; -import type { McpContext } from './McpContext.js'; -import type { ImageContentData, Response } from './tools/ToolDefinition.js'; -import { paginate, type PaginationOptions } from './utils/pagination.js'; +import {formatA11ySnapshot} from './formatters/snapshotFormatter.js'; +import type {McpContext} from './McpContext.js'; +import type {ImageContentData, Response} from './tools/ToolDefinition.js'; +import {paginate, type PaginationOptions} from './utils/pagination.js'; export class McpResponse implements Response { #includePages = false; @@ -60,9 +60,9 @@ export class McpResponse implements Response { pagination: options?.pageSize || options?.pageIdx ? { - pageSize: options.pageSize, - pageIdx: options.pageIdx, - } + pageSize: options.pageSize, + pageIdx: options.pageIdx, + } : undefined, resourceTypes: options?.resourceTypes, }; @@ -217,7 +217,7 @@ Call browser_handle_dialog to handle it before continuing.`); response.push('Invalid page number provided. Showing first page.'); } - const { startIndex, endIndex, currentPage, totalPages } = + const {startIndex, endIndex, currentPage, totalPages} = paginationResult; response.push( `Showing ${startIndex + 1}-${endIndex} of ${requests.length} (Page ${currentPage + 1} of ${totalPages}).`, diff --git a/src/tools/network.ts b/src/tools/network.ts index 18bd05071..6ff2bb92c 100644 --- a/src/tools/network.ts +++ b/src/tools/network.ts @@ -4,36 +4,33 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { ResourceType } from 'puppeteer-core'; +import type {ResourceType} from 'puppeteer-core'; import z from 'zod'; -import { ToolCategories } from './categories.js'; -import { defineTool } from './ToolDefinition.js'; +import {ToolCategories} from './categories.js'; +import {defineTool} from './ToolDefinition.js'; -const FILTERABLE_RESOURCE_TYPES: readonly [ - ResourceType, - ...ResourceType[], -] = [ - 'document', - 'stylesheet', - 'image', - 'media', - 'font', - 'script', - 'texttrack', - 'xhr', - 'fetch', - 'prefetch', - 'eventsource', - 'websocket', - 'manifest', - 'signedexchange', - 'ping', - 'cspviolationreport', - 'preflight', - 'fedcm', - 'other', - ]; +const FILTERABLE_RESOURCE_TYPES: readonly [ResourceType, ...ResourceType[]] = [ + 'document', + 'stylesheet', + 'image', + 'media', + 'font', + 'script', + 'texttrack', + 'xhr', + 'fetch', + 'prefetch', + 'eventsource', + 'websocket', + 'manifest', + 'signedexchange', + 'ping', + 'cspviolationreport', + 'preflight', + 'fedcm', + 'other', +]; export const listNetworkRequests = defineTool({ name: 'list_network_requests', @@ -60,9 +57,7 @@ export const listNetworkRequests = defineTool({ 'Page number to return (0-based). When omitted, returns the first page.', ), resourceTypes: z - .array( - z.enum(FILTERABLE_RESOURCE_TYPES), - ) + .array(z.enum(FILTERABLE_RESOURCE_TYPES)) .optional() .describe( 'Filter requests to only return requests of the specified resource types. When omitted or empty, returns all requests.', diff --git a/tests/McpResponse.test.ts b/tests/McpResponse.test.ts index c7981b54f..e72cd99cd 100644 --- a/tests/McpResponse.test.ts +++ b/tests/McpResponse.test.ts @@ -4,9 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; -import { describe, it } from 'node:test'; +import {describe, it} from 'node:test'; -import { getMockRequest, html, withBrowser } from './utils.js'; +import {getMockRequest, html, withBrowser} from './utils.js'; describe('McpResponse', () => { it('list pages', async () => { @@ -120,7 +120,7 @@ Navigation timeout set to 100000 ms`, }); it('adds image when image is attached', async () => { await withBrowser(async (response, context) => { - response.attachImage({ data: 'imageBase64', mimeType: 'image/png' }); + response.attachImage({data: 'imageBase64', mimeType: 'image/png'}); const result = await response.handle('test', context); assert.strictEqual(result[0].text, `# test response`); assert.equal(result[1].type, 'image'); @@ -273,10 +273,10 @@ describe('McpResponse network request filtering', () => { }); context.getNetworkRequests = () => { return [ - getMockRequest({ resourceType: 'script' }), - getMockRequest({ resourceType: 'image' }), - getMockRequest({ resourceType: 'stylesheet' }), - getMockRequest({ resourceType: 'document' }), + getMockRequest({resourceType: 'script'}), + getMockRequest({resourceType: 'image'}), + getMockRequest({resourceType: 'stylesheet'}), + getMockRequest({resourceType: 'document'}), ]; }; const result = await response.handle('test', context); @@ -298,9 +298,9 @@ http://example.com GET [pending]`, }); context.getNetworkRequests = () => { return [ - getMockRequest({ resourceType: 'script' }), - getMockRequest({ resourceType: 'image' }), - getMockRequest({ resourceType: 'stylesheet' }), + getMockRequest({resourceType: 'script'}), + getMockRequest({resourceType: 'image'}), + getMockRequest({resourceType: 'stylesheet'}), ]; }; const result = await response.handle('test', context); @@ -321,9 +321,9 @@ http://example.com GET [pending]`, }); context.getNetworkRequests = () => { return [ - getMockRequest({ resourceType: 'script' }), - getMockRequest({ resourceType: 'image' }), - getMockRequest({ resourceType: 'stylesheet' }), + getMockRequest({resourceType: 'script'}), + getMockRequest({resourceType: 'image'}), + getMockRequest({resourceType: 'stylesheet'}), ]; }; const result = await response.handle('test', context); @@ -341,11 +341,11 @@ No requests found.`, response.setIncludeNetworkRequests(true); context.getNetworkRequests = () => { return [ - getMockRequest({ resourceType: 'script' }), - getMockRequest({ resourceType: 'image' }), - getMockRequest({ resourceType: 'stylesheet' }), - getMockRequest({ resourceType: 'document' }), - getMockRequest({ resourceType: 'font' }), + getMockRequest({resourceType: 'script'}), + getMockRequest({resourceType: 'image'}), + getMockRequest({resourceType: 'stylesheet'}), + getMockRequest({resourceType: 'document'}), + getMockRequest({resourceType: 'font'}), ]; }; const result = await response.handle('test', context); @@ -370,11 +370,11 @@ http://example.com GET [pending]`, }); context.getNetworkRequests = () => { return [ - getMockRequest({ resourceType: 'script' }), - getMockRequest({ resourceType: 'image' }), - getMockRequest({ resourceType: 'stylesheet' }), - getMockRequest({ resourceType: 'document' }), - getMockRequest({ resourceType: 'font' }), + getMockRequest({resourceType: 'script'}), + getMockRequest({resourceType: 'image'}), + getMockRequest({resourceType: 'stylesheet'}), + getMockRequest({resourceType: 'document'}), + getMockRequest({resourceType: 'font'}), ]; }; const result = await response.handle('test', context); @@ -396,7 +396,7 @@ http://example.com GET [pending]`, describe('McpResponse network pagination', () => { it('returns all requests when pagination is not provided', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({ length: 5 }, () => getMockRequest()); + const requests = Array.from({length: 5}, () => getMockRequest()); context.getNetworkRequests = () => requests; response.setIncludeNetworkRequests(true); const result = await response.handle('test', context); @@ -409,13 +409,13 @@ describe('McpResponse network pagination', () => { it('returns first page by default', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({ length: 30 }, (_, idx) => - getMockRequest({ method: `GET-${idx}` }), + const requests = Array.from({length: 30}, (_, idx) => + getMockRequest({method: `GET-${idx}`}), ); context.getNetworkRequests = () => { return requests; }; - response.setIncludeNetworkRequests(true, { pageSize: 10 }); + response.setIncludeNetworkRequests(true, {pageSize: 10}); const result = await response.handle('test', context); const text = (result[0].text as string).toString(); assert.ok(text.includes('Showing 1-10 of 30 (Page 1 of 3).')); @@ -426,8 +426,8 @@ describe('McpResponse network pagination', () => { it('returns subsequent page when pageIdx provided', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({ length: 25 }, (_, idx) => - getMockRequest({ method: `GET-${idx}` }), + const requests = Array.from({length: 25}, (_, idx) => + getMockRequest({method: `GET-${idx}`}), ); context.getNetworkRequests = () => requests; response.setIncludeNetworkRequests(true, { @@ -444,7 +444,7 @@ describe('McpResponse network pagination', () => { it('handles invalid page number by showing first page', async () => { await withBrowser(async (response, context) => { - const requests = Array.from({ length: 5 }, () => getMockRequest()); + const requests = Array.from({length: 5}, () => getMockRequest()); context.getNetworkRequests = () => requests; response.setIncludeNetworkRequests(true, { pageSize: 2,