From bb70f1936bbdad4b80f696f34315dde83ad3ca0a Mon Sep 17 00:00:00 2001 From: Nicholas Roscino Date: Fri, 3 Oct 2025 12:52:43 +0000 Subject: [PATCH 01/10] feat: Add request and response bodies --- src/McpResponse.ts | 31 +++++++++++--- src/formatters/networkFormatter.ts | 66 +++++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/src/McpResponse.ts b/src/McpResponse.ts index fa3c69ae..12033965 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -12,8 +12,11 @@ import type {ResourceType} from 'puppeteer-core'; import {formatConsoleEvent} from './formatters/consoleFormatter.js'; import { getFormattedHeaderValue, + getFormattedResponseBody, + getFormattedRequestBody, getShortDescriptionForRequest, getStatusFromRequest, + BODY_CONTEXT_SIZE_LIMIT, } from './formatters/networkFormatter.js'; import {formatA11ySnapshot} from './formatters/snapshotFormatter.js'; import type {McpContext} from './McpContext.js'; @@ -137,13 +140,13 @@ export class McpResponse implements Response { } } - 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); @@ -192,7 +195,7 @@ Call ${handleDialog.name} to handle it before continuing.`); } } - response.push(...this.#getIncludeNetworkRequestsData(context)); + response.push(...(await this.#getIncludeNetworkRequestsData(context))); if (this.#networkRequestsOptions?.include) { let requests = context.getNetworkRequests(); @@ -272,7 +275,7 @@ Call ${handleDialog.name} to handle it before continuing.`); }; } - #getIncludeNetworkRequestsData(context: McpContext): string[] { + async #getIncludeNetworkRequestsData(context: McpContext): Promise { const response: string[] = []; const url = this.#attachedNetworkRequestUrl; if (!url) { @@ -286,12 +289,30 @@ Call ${handleDialog.name} to handle it before continuing.`); response.push(line); } + const formattedRequestData = await getFormattedRequestBody( + '### Request body', + httpRequest, + BODY_CONTEXT_SIZE_LIMIT, + ); + if (formattedRequestData.length > 0) { + response.push(formattedRequestData); + } + const httpResponse = httpRequest.response(); if (httpResponse) { response.push(`### Response Headers`); for (const line of getFormattedHeaderValue(httpResponse.headers())) { response.push(line); } + + const formattedResponseData = await getFormattedResponseBody( + '### Response body', + httpResponse, + BODY_CONTEXT_SIZE_LIMIT, + ); + if (formattedResponseData.length > 0) { + response.push(formattedResponseData); + } } const httpFailure = httpRequest.failure(); diff --git a/src/formatters/networkFormatter.ts b/src/formatters/networkFormatter.ts index f74e954f..aaaf9872 100644 --- a/src/formatters/networkFormatter.ts +++ b/src/formatters/networkFormatter.ts @@ -4,7 +4,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type {HTTPRequest} from 'puppeteer-core'; +import {isUtf8} from 'node:buffer'; + +import type {HTTPRequest, HTTPResponse} from 'puppeteer-core'; + +export const BODY_CONTEXT_SIZE_LIMIT = 10000; export function getShortDescriptionForRequest(request: HTTPRequest): string { return `${request.url()} ${request.method()} ${getStatusFromRequest(request)}`; @@ -37,3 +41,63 @@ export function getFormattedHeaderValue( } return response; } + +export async function getFormattedResponseBody( + title: string, + httpResponse: HTTPResponse, + sizeLimit: number, +): Promise { + try { + const responseBuffer = await httpResponse.buffer(); + + if (isUtf8(responseBuffer)) { + const responseAsTest = responseBuffer.toString('utf-8'); + + if (responseAsTest.length === 0) { + return `${title}\n`; + } + + return `${title}\n${getSizeLimitedString(responseAsTest, sizeLimit)}`; + } + + return `${title}\n`; + } catch { + // buffer() call might fail with CDP exception, in this case we don't print anything in the context + return ''; + } +} + +export async function getFormattedRequestBody( + title: string, + httpRequest: HTTPRequest, + sizeLimit: number, +): Promise { + if (httpRequest.hasPostData()) { + const data = httpRequest.postData(); + + if (data) { + return `${title}\n${getSizeLimitedString(data, sizeLimit)}`; + } + + try { + const fetchData = await httpRequest.fetchPostData(); + + if (fetchData) { + return `${title}\n${getSizeLimitedString(fetchData, sizeLimit)}`; + } + } catch { + // fetchPostData() call might fail with CDP exception, in this case we don't print anything in the context + return ''; + } + } + + return ''; +} + +function getSizeLimitedString(text: string, sizeLimit: number) { + if (text.length > sizeLimit) { + return `${text.substring(0, sizeLimit) + '... '}`; + } + + return `${text}`; +} From ba4f512c741885685c88aaa27eb3d14cda760265 Mon Sep 17 00:00:00 2001 From: Nicholas Roscino Date: Fri, 3 Oct 2025 12:53:20 +0000 Subject: [PATCH 02/10] chore: Add tests --- tests/formatters/networkFormatter.test.ts | 139 +++++++++++++++++++++- tests/utils.ts | 12 ++ 2 files changed, 149 insertions(+), 2 deletions(-) diff --git a/tests/formatters/networkFormatter.test.ts b/tests/formatters/networkFormatter.test.ts index 515d94ee..3c2dfb42 100644 --- a/tests/formatters/networkFormatter.test.ts +++ b/tests/formatters/networkFormatter.test.ts @@ -7,8 +7,12 @@ import assert from 'node:assert'; import {describe, it} from 'node:test'; +import {ProtocolError} from 'puppeteer-core'; + import { getFormattedHeaderValue, + getFormattedRequestBody, + getFormattedResponseBody, getShortDescriptionForRequest, } from '../../src/formatters/networkFormatter.js'; import {getMockRequest, getMockResponse} from '../utils.js'; @@ -34,7 +38,6 @@ describe('networkFormatter', () => { assert.equal(result, 'http://example.com GET [success - 200]'); }); - it('shows correct status for request with response code in 100', async () => { const response = getMockResponse({ status: 199, @@ -53,7 +56,6 @@ describe('networkFormatter', () => { assert.equal(result, 'http://example.com GET [failed - 300]'); }); - it('shows correct status for request that failed', async () => { const request = getMockRequest({ failure() { @@ -100,4 +102,137 @@ describe('networkFormatter', () => { assert.deepEqual(result, []); }); }); + + describe('getFormattedRequestBody', () => { + it('shows data from fetchPostData if postData is undefined', async () => { + const request = getMockRequest({ + hasPostData: true, + postData: undefined, + fetchPostData: Promise.resolve('test'), + }); + + const result = await getFormattedRequestBody('body', request, 200); + + assert.strictEqual(result, 'body\ntest'); + }); + it('shows empty string when no postData available', async () => { + const request = getMockRequest({ + hasPostData: false, + }); + + const result = await getFormattedRequestBody('body', request, 200); + + assert.strictEqual(result, ''); + }); + it('shows request body when postData is available', async () => { + const request = getMockRequest({ + postData: JSON.stringify({ + request: 'body', + }), + hasPostData: true, + }); + + const result = await getFormattedRequestBody('body', request, 200); + + assert.strictEqual( + result, + `body\n${JSON.stringify({ + request: 'body', + })}`, + ); + }); + it('shows trunkated string correctly with postData', async () => { + const response = getMockRequest({ + postData: 'some text that is longer than expected', + hasPostData: true, + }); + + const result = await getFormattedRequestBody('body', response, 20); + + assert.strictEqual(result, 'body\nsome text that is lo... '); + }); + it('shows trunkated string correctly with fetchPostData', async () => { + const response = getMockRequest({ + fetchPostData: Promise.resolve( + 'some text that is longer than expected', + ), + postData: undefined, + hasPostData: true, + }); + + const result = await getFormattedRequestBody('body', response, 20); + + assert.strictEqual(result, 'body\nsome text that is lo... '); + }); + it('shows nothing on exception', async () => { + const request = getMockRequest({ + hasPostData: true, + postData: undefined, + fetchPostData: Promise.reject(new ProtocolError()), + }); + + const result = await getFormattedRequestBody('body', request, 200); + + assert.strictEqual(result, ''); + }); + }); + + describe('getFormattedResponseBody', () => { + it('handles empty buffer correctly', async () => { + const response = getMockResponse(); + response.buffer = () => { + return Promise.resolve(Buffer.from('')); + }; + + const result = await getFormattedResponseBody('body', response, 20); + + assert.strictEqual(result, 'body\n'); + }); + it('handles base64 text correctly', async () => { + const binaryBuffer = Buffer.from([ + 0xde, 0xad, 0xbe, 0xef, 0x00, 0x41, 0x42, 0x43, + ]); + const response = getMockResponse(); + response.buffer = () => { + return Promise.resolve(binaryBuffer); + }; + + const result = await getFormattedResponseBody('body', response, 20); + + assert.strictEqual(result, 'body\n'); + }); + it('handles the text limit correctly', async () => { + const response = getMockResponse(); + response.buffer = () => { + return Promise.resolve( + Buffer.from('some text that is longer than expected'), + ); + }; + + const result = await getFormattedResponseBody('body', response, 20); + + assert.strictEqual(result, 'body\nsome text that is lo... '); + }); + it('handles the text format correctly', async () => { + const response = getMockResponse(); + response.buffer = () => { + return Promise.resolve(Buffer.from(JSON.stringify({response: 'body'}))); + }; + + const result = await getFormattedResponseBody('body', response, 200); + + assert.strictEqual(result, `body\n${JSON.stringify({response: 'body'})}`); + }); + it('handles error correctly', async () => { + const response = getMockResponse(); + response.buffer = () => { + // CDP Error simulation + return Promise.reject(new ProtocolError()); + }; + + const result = await getFormattedResponseBody('body', response, 200); + + assert.strictEqual(result, ''); + }); + }); }); diff --git a/tests/utils.ts b/tests/utils.ts index 82b4da4e..0197e181 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -46,6 +46,9 @@ export function getMockRequest( response?: HTTPResponse; failure?: HTTPRequest['failure']; resourceType?: string; + hasPostData?: boolean; + postData?: string; + fetchPostData?: Promise; } = {}, ): HTTPRequest { return { @@ -55,6 +58,15 @@ export function getMockRequest( method() { return options.method ?? 'GET'; }, + fetchPostData() { + return options.fetchPostData ?? Promise.reject(); + }, + hasPostData() { + return options.hasPostData ?? false; + }, + postData() { + return options.postData; + }, response() { return options.response ?? null; }, From 4ebb5ea429013b0e049f8f0b3e66a5cc9f41007e Mon Sep 17 00:00:00 2001 From: Nicholas Roscino Date: Tue, 7 Oct 2025 14:15:13 +0000 Subject: [PATCH 03/10] feat: fetching data before format to preserve format sync --- src/McpResponse.ts | 115 +++++++++++++++------- src/formatters/networkFormatter.ts | 12 +-- tests/McpResponse.test.ts | 105 ++++++++++++++------ tests/formatters/networkFormatter.test.ts | 42 ++++---- 4 files changed, 181 insertions(+), 93 deletions(-) diff --git a/src/McpResponse.ts b/src/McpResponse.ts index 12033965..4d069a4a 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -7,9 +7,9 @@ import type { ImageContent, TextContent, } from '@modelcontextprotocol/sdk/types.js'; -import type {ResourceType} from 'puppeteer-core'; +import type { HTTPRequest, HTTPResponse, ResourceType } from 'puppeteer-core'; -import {formatConsoleEvent} from './formatters/consoleFormatter.js'; +import { formatConsoleEvent } from './formatters/consoleFormatter.js'; import { getFormattedHeaderValue, getFormattedResponseBody, @@ -18,16 +18,23 @@ import { getStatusFromRequest, BODY_CONTEXT_SIZE_LIMIT, } from './formatters/networkFormatter.js'; -import {formatA11ySnapshot} from './formatters/snapshotFormatter.js'; -import type {McpContext} from './McpContext.js'; -import {handleDialog} from './tools/pages.js'; -import type {ImageContentData, Response} from './tools/ToolDefinition.js'; -import {paginate, type PaginationOptions} from './utils/pagination.js'; +import { formatA11ySnapshot } from './formatters/snapshotFormatter.js'; +import { McpContext } from './McpContext.js'; +import { handleDialog } from './tools/pages.js'; +import type { ImageContentData, Response } from './tools/ToolDefinition.js'; + +import { paginate, type PaginationOptions } from './utils/pagination.js'; + +export type NetworkRequestData = { + networkRequestUrl: string; + requestBody: string | null; + responseBody: string | null; +} export class McpResponse implements Response { #includePages = false; #includeSnapshot = false; - #attachedNetworkRequestUrl?: string; + #attachedNetworkRequestData?: NetworkRequestData; #includeConsoleData = false; #textResponseLines: string[] = []; #formattedConsoleData?: string[]; @@ -64,9 +71,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, }; @@ -77,7 +84,11 @@ export class McpResponse implements Response { } attachNetworkRequest(url: string): void { - this.#attachedNetworkRequestUrl = url; + this.#attachedNetworkRequestData = { + networkRequestUrl: url, + responseBody: null, + requestBody: null + }; } get includePages(): boolean { @@ -92,7 +103,7 @@ export class McpResponse implements Response { return this.#includeConsoleData; } get attachedNetworkRequestUrl(): string | undefined { - return this.#attachedNetworkRequestUrl; + return this.#attachedNetworkRequestData?.networkRequestUrl; } get networkRequestsPageIdx(): number | undefined { return this.#networkRequestsOptions?.pagination?.pageIdx; @@ -130,6 +141,14 @@ export class McpResponse implements Response { } let formattedConsoleMessages: string[]; + + if (this.#attachedNetworkRequestData?.networkRequestUrl) { + const request = context.getNetworkRequestByUrl(this.#attachedNetworkRequestData.networkRequestUrl); + + this.#attachedNetworkRequestData.requestBody = await this.processRequestBody(request); + this.#attachedNetworkRequestData.responseBody = await this.processResponseBody(request.response()); + } + if (this.#includeConsoleData) { const consoleMessages = context.getConsoleData(); if (consoleMessages) { @@ -140,13 +159,46 @@ export class McpResponse implements Response { } } - return await this.format(toolName, context); + return this.format(toolName, context); + } + + async processResponseBody(httpResponse: HTTPResponse | null): Promise { + if (!httpResponse) { + return null; + } + + const formattedResponseData = await getFormattedResponseBody( + httpResponse, + BODY_CONTEXT_SIZE_LIMIT, + ); + if (formattedResponseData.length > 0) { + return formattedResponseData; + } + + return null; + } + + + async processRequestBody(httpRequest: HTTPRequest): Promise { + if (!httpRequest) { + return null; + } + + const formattedResponseData = await getFormattedRequestBody( + httpRequest, + BODY_CONTEXT_SIZE_LIMIT, + ); + if (formattedResponseData.length > 0) { + return formattedResponseData; + } + + return null; } - async format( + format( toolName: string, context: McpContext, - ): Promise> { + ): Array { const response = [`# ${toolName} response`]; for (const line of this.#textResponseLines) { response.push(line); @@ -195,7 +247,7 @@ Call ${handleDialog.name} to handle it before continuing.`); } } - response.push(...(await this.#getIncludeNetworkRequestsData(context))); + response.push(...this.#getIncludeNetworkRequestsData(context)); if (this.#networkRequestsOptions?.include) { let requests = context.getNetworkRequests(); @@ -256,7 +308,7 @@ Call ${handleDialog.name} to handle it before continuing.`); response.push('Invalid page number provided. Showing first page.'); } - const {startIndex, endIndex, currentPage, totalPages} = paginationResult; + const { startIndex, endIndex, currentPage, totalPages } = paginationResult; response.push( `Showing ${startIndex + 1}-${endIndex} of ${data.length} (Page ${currentPage + 1} of ${totalPages}).`, ); @@ -275,12 +327,13 @@ Call ${handleDialog.name} to handle it before continuing.`); }; } - async #getIncludeNetworkRequestsData(context: McpContext): Promise { + #getIncludeNetworkRequestsData(context: McpContext): string[] { const response: string[] = []; - const url = this.#attachedNetworkRequestUrl; + const url = this.#attachedNetworkRequestData?.networkRequestUrl; if (!url) { return response; } + const httpRequest = context.getNetworkRequestByUrl(url); response.push(`## Request ${httpRequest.url()}`); response.push(`Status: ${getStatusFromRequest(httpRequest)}`); @@ -289,13 +342,9 @@ Call ${handleDialog.name} to handle it before continuing.`); response.push(line); } - const formattedRequestData = await getFormattedRequestBody( - '### Request body', - httpRequest, - BODY_CONTEXT_SIZE_LIMIT, - ); - if (formattedRequestData.length > 0) { - response.push(formattedRequestData); + if(this.#attachedNetworkRequestData?.requestBody) { + response.push(`### Request Body`); + response.push(this.#attachedNetworkRequestData.requestBody) } const httpResponse = httpRequest.response(); @@ -304,15 +353,11 @@ Call ${handleDialog.name} to handle it before continuing.`); for (const line of getFormattedHeaderValue(httpResponse.headers())) { response.push(line); } + } - const formattedResponseData = await getFormattedResponseBody( - '### Response body', - httpResponse, - BODY_CONTEXT_SIZE_LIMIT, - ); - if (formattedResponseData.length > 0) { - response.push(formattedResponseData); - } + if(this.#attachedNetworkRequestData?.responseBody) { + response.push(`### Response Body`); + response.push(this.#attachedNetworkRequestData.responseBody) } const httpFailure = httpRequest.failure(); diff --git a/src/formatters/networkFormatter.ts b/src/formatters/networkFormatter.ts index aaaf9872..4b3bb8fb 100644 --- a/src/formatters/networkFormatter.ts +++ b/src/formatters/networkFormatter.ts @@ -43,7 +43,6 @@ export function getFormattedHeaderValue( } export async function getFormattedResponseBody( - title: string, httpResponse: HTTPResponse, sizeLimit: number, ): Promise { @@ -54,13 +53,13 @@ export async function getFormattedResponseBody( const responseAsTest = responseBuffer.toString('utf-8'); if (responseAsTest.length === 0) { - return `${title}\n`; + return ``; } - return `${title}\n${getSizeLimitedString(responseAsTest, sizeLimit)}`; + return `${getSizeLimitedString(responseAsTest, sizeLimit)}`; } - return `${title}\n`; + return ``; } catch { // buffer() call might fail with CDP exception, in this case we don't print anything in the context return ''; @@ -68,7 +67,6 @@ export async function getFormattedResponseBody( } export async function getFormattedRequestBody( - title: string, httpRequest: HTTPRequest, sizeLimit: number, ): Promise { @@ -76,14 +74,14 @@ export async function getFormattedRequestBody( const data = httpRequest.postData(); if (data) { - return `${title}\n${getSizeLimitedString(data, sizeLimit)}`; + return `${getSizeLimitedString(data, sizeLimit)}`; } try { const fetchData = await httpRequest.fetchPostData(); if (fetchData) { - return `${title}\n${getSizeLimitedString(fetchData, sizeLimit)}`; + return `${getSizeLimitedString(fetchData, sizeLimit)}`; } } catch { // fetchPostData() call might fail with CDP exception, in this case we don't print anything in the context diff --git a/tests/McpResponse.test.ts b/tests/McpResponse.test.ts index 87529102..3bfa2263 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, getMockResponse, html, withBrowser } from './utils.js'; describe('McpResponse', () => { it('list pages', async () => { @@ -120,7 +120,7 @@ Default 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'); @@ -202,6 +202,51 @@ http://example.com GET [pending]`, }); }); + it('add network request when attached with POST data', async () => { + await withBrowser(async (response, context) => { + response.setIncludeNetworkRequests(true); + const httpResponse = getMockResponse(); + httpResponse.buffer = () => { + return Promise.resolve(Buffer.from(JSON.stringify({ response: "body" }))); + }; + httpResponse.headers = () => { + return { + "Content-Type": "application/json" + }; + } + const request = getMockRequest({ + method: "POST", + hasPostData: true, + postData: JSON.stringify({ request: "body" }), + response: httpResponse + }); + context.getNetworkRequests = () => { + return [request]; + }; + response.attachNetworkRequest(request.url()); + + const result = await response.handle('test', context); + + assert.strictEqual( + result[0].text, + `# test response +## Request http://example.com +Status: [success - 200] +### Request Headers +- content-size:10 +### Request Body +${JSON.stringify({ request: "body" })} +### Response Headers +- Content-Type:application/json +### Response Body +${JSON.stringify({ response: "body" })} +## Network requests +Showing 1-1 of 1 (Page 1 of 1). +http://example.com POST [success - 200]`, + ); + }); + }); + it('add network request when attached', async () => { await withBrowser(async (response, context) => { response.setIncludeNetworkRequests(true); @@ -273,10 +318,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 +343,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 +366,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 +386,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 +415,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 +441,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 +454,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 +471,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 +489,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/formatters/networkFormatter.test.ts b/tests/formatters/networkFormatter.test.ts index 3c2dfb42..9c85e2d5 100644 --- a/tests/formatters/networkFormatter.test.ts +++ b/tests/formatters/networkFormatter.test.ts @@ -111,16 +111,16 @@ describe('networkFormatter', () => { fetchPostData: Promise.resolve('test'), }); - const result = await getFormattedRequestBody('body', request, 200); + const result = await getFormattedRequestBody(request, 200); - assert.strictEqual(result, 'body\ntest'); + assert.strictEqual(result, 'test'); }); it('shows empty string when no postData available', async () => { const request = getMockRequest({ hasPostData: false, }); - const result = await getFormattedRequestBody('body', request, 200); + const result = await getFormattedRequestBody(request, 200); assert.strictEqual(result, ''); }); @@ -132,27 +132,27 @@ describe('networkFormatter', () => { hasPostData: true, }); - const result = await getFormattedRequestBody('body', request, 200); + const result = await getFormattedRequestBody(request, 200); assert.strictEqual( result, - `body\n${JSON.stringify({ + `${JSON.stringify({ request: 'body', })}`, ); }); it('shows trunkated string correctly with postData', async () => { - const response = getMockRequest({ + const request = getMockRequest({ postData: 'some text that is longer than expected', hasPostData: true, }); - const result = await getFormattedRequestBody('body', response, 20); + const result = await getFormattedRequestBody(request, 20); - assert.strictEqual(result, 'body\nsome text that is lo... '); + assert.strictEqual(result, 'some text that is lo... '); }); it('shows trunkated string correctly with fetchPostData', async () => { - const response = getMockRequest({ + const request = getMockRequest({ fetchPostData: Promise.resolve( 'some text that is longer than expected', ), @@ -160,9 +160,9 @@ describe('networkFormatter', () => { hasPostData: true, }); - const result = await getFormattedRequestBody('body', response, 20); + const result = await getFormattedRequestBody(request, 20); - assert.strictEqual(result, 'body\nsome text that is lo... '); + assert.strictEqual(result, 'some text that is lo... '); }); it('shows nothing on exception', async () => { const request = getMockRequest({ @@ -171,7 +171,7 @@ describe('networkFormatter', () => { fetchPostData: Promise.reject(new ProtocolError()), }); - const result = await getFormattedRequestBody('body', request, 200); + const result = await getFormattedRequestBody(request, 200); assert.strictEqual(result, ''); }); @@ -184,9 +184,9 @@ describe('networkFormatter', () => { return Promise.resolve(Buffer.from('')); }; - const result = await getFormattedResponseBody('body', response, 20); + const result = await getFormattedResponseBody(response, 200); - assert.strictEqual(result, 'body\n'); + assert.strictEqual(result, ''); }); it('handles base64 text correctly', async () => { const binaryBuffer = Buffer.from([ @@ -197,9 +197,9 @@ describe('networkFormatter', () => { return Promise.resolve(binaryBuffer); }; - const result = await getFormattedResponseBody('body', response, 20); + const result = await getFormattedResponseBody(response, 200); - assert.strictEqual(result, 'body\n'); + assert.strictEqual(result, ''); }); it('handles the text limit correctly', async () => { const response = getMockResponse(); @@ -209,9 +209,9 @@ describe('networkFormatter', () => { ); }; - const result = await getFormattedResponseBody('body', response, 20); + const result = await getFormattedResponseBody(response, 20); - assert.strictEqual(result, 'body\nsome text that is lo... '); + assert.strictEqual(result, 'some text that is lo... '); }); it('handles the text format correctly', async () => { const response = getMockResponse(); @@ -219,9 +219,9 @@ describe('networkFormatter', () => { return Promise.resolve(Buffer.from(JSON.stringify({response: 'body'}))); }; - const result = await getFormattedResponseBody('body', response, 200); + const result = await getFormattedResponseBody(response, 200); - assert.strictEqual(result, `body\n${JSON.stringify({response: 'body'})}`); + assert.strictEqual(result, `${JSON.stringify({response: 'body'})}`); }); it('handles error correctly', async () => { const response = getMockResponse(); @@ -230,7 +230,7 @@ describe('networkFormatter', () => { return Promise.reject(new ProtocolError()); }; - const result = await getFormattedResponseBody('body', response, 200); + const result = await getFormattedResponseBody(response, 200); assert.strictEqual(result, ''); }); From 2fc7b3a57ffd7dcfd591a1effd2f98f9290c38db Mon Sep 17 00:00:00 2001 From: Nicholas Roscino Date: Tue, 7 Oct 2025 14:19:41 +0000 Subject: [PATCH 04/10] chore: format run --- src/McpResponse.ts | 50 ++++++++++++++------------ tests/McpResponse.test.ts | 76 +++++++++++++++++++-------------------- 2 files changed, 65 insertions(+), 61 deletions(-) diff --git a/src/McpResponse.ts b/src/McpResponse.ts index 4d069a4a..e90c4202 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -7,9 +7,9 @@ import type { ImageContent, TextContent, } from '@modelcontextprotocol/sdk/types.js'; -import type { HTTPRequest, HTTPResponse, ResourceType } from 'puppeteer-core'; +import type {HTTPRequest, HTTPResponse, ResourceType} from 'puppeteer-core'; -import { formatConsoleEvent } from './formatters/consoleFormatter.js'; +import {formatConsoleEvent} from './formatters/consoleFormatter.js'; import { getFormattedHeaderValue, getFormattedResponseBody, @@ -18,14 +18,13 @@ import { getStatusFromRequest, BODY_CONTEXT_SIZE_LIMIT, } from './formatters/networkFormatter.js'; -import { formatA11ySnapshot } from './formatters/snapshotFormatter.js'; -import { McpContext } from './McpContext.js'; -import { handleDialog } from './tools/pages.js'; -import type { ImageContentData, Response } from './tools/ToolDefinition.js'; +import {formatA11ySnapshot} from './formatters/snapshotFormatter.js'; +import type {McpContext} from './McpContext.js'; +import {handleDialog} from './tools/pages.js'; +import type {ImageContentData, Response} from './tools/ToolDefinition.js'; +import {paginate, type PaginationOptions} from './utils/pagination.js'; -import { paginate, type PaginationOptions } from './utils/pagination.js'; - -export type NetworkRequestData = { +export interface NetworkRequestData { networkRequestUrl: string; requestBody: string | null; responseBody: string | null; @@ -71,9 +70,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, }; @@ -87,7 +86,7 @@ export class McpResponse implements Response { this.#attachedNetworkRequestData = { networkRequestUrl: url, responseBody: null, - requestBody: null + requestBody: null, }; } @@ -143,10 +142,14 @@ export class McpResponse implements Response { let formattedConsoleMessages: string[]; if (this.#attachedNetworkRequestData?.networkRequestUrl) { - const request = context.getNetworkRequestByUrl(this.#attachedNetworkRequestData.networkRequestUrl); + const request = context.getNetworkRequestByUrl( + this.#attachedNetworkRequestData.networkRequestUrl, + ); - this.#attachedNetworkRequestData.requestBody = await this.processRequestBody(request); - this.#attachedNetworkRequestData.responseBody = await this.processResponseBody(request.response()); + this.#attachedNetworkRequestData.requestBody = + await this.processRequestBody(request); + this.#attachedNetworkRequestData.responseBody = + await this.processResponseBody(request.response()); } if (this.#includeConsoleData) { @@ -162,7 +165,9 @@ export class McpResponse implements Response { return this.format(toolName, context); } - async processResponseBody(httpResponse: HTTPResponse | null): Promise { + async processResponseBody( + httpResponse: HTTPResponse | null, + ): Promise { if (!httpResponse) { return null; } @@ -178,7 +183,6 @@ export class McpResponse implements Response { return null; } - async processRequestBody(httpRequest: HTTPRequest): Promise { if (!httpRequest) { return null; @@ -308,7 +312,7 @@ Call ${handleDialog.name} to handle it before continuing.`); response.push('Invalid page number provided. Showing first page.'); } - const { startIndex, endIndex, currentPage, totalPages } = paginationResult; + const {startIndex, endIndex, currentPage, totalPages} = paginationResult; response.push( `Showing ${startIndex + 1}-${endIndex} of ${data.length} (Page ${currentPage + 1} of ${totalPages}).`, ); @@ -342,9 +346,9 @@ Call ${handleDialog.name} to handle it before continuing.`); response.push(line); } - if(this.#attachedNetworkRequestData?.requestBody) { + if (this.#attachedNetworkRequestData?.requestBody) { response.push(`### Request Body`); - response.push(this.#attachedNetworkRequestData.requestBody) + response.push(this.#attachedNetworkRequestData.requestBody); } const httpResponse = httpRequest.response(); @@ -355,9 +359,9 @@ Call ${handleDialog.name} to handle it before continuing.`); } } - if(this.#attachedNetworkRequestData?.responseBody) { + if (this.#attachedNetworkRequestData?.responseBody) { response.push(`### Response Body`); - response.push(this.#attachedNetworkRequestData.responseBody) + response.push(this.#attachedNetworkRequestData.responseBody); } const httpFailure = httpRequest.failure(); diff --git a/tests/McpResponse.test.ts b/tests/McpResponse.test.ts index 3bfa2263..586b524f 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, getMockResponse, html, withBrowser } from './utils.js'; +import {getMockRequest, getMockResponse, html, withBrowser} from './utils.js'; describe('McpResponse', () => { it('list pages', async () => { @@ -120,7 +120,7 @@ Default 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'); @@ -207,18 +207,18 @@ http://example.com GET [pending]`, response.setIncludeNetworkRequests(true); const httpResponse = getMockResponse(); httpResponse.buffer = () => { - return Promise.resolve(Buffer.from(JSON.stringify({ response: "body" }))); + return Promise.resolve(Buffer.from(JSON.stringify({response: 'body'}))); }; httpResponse.headers = () => { return { - "Content-Type": "application/json" + 'Content-Type': 'application/json', }; - } + }; const request = getMockRequest({ - method: "POST", + method: 'POST', hasPostData: true, - postData: JSON.stringify({ request: "body" }), - response: httpResponse + postData: JSON.stringify({request: 'body'}), + response: httpResponse, }); context.getNetworkRequests = () => { return [request]; @@ -235,11 +235,11 @@ Status: [success - 200] ### Request Headers - content-size:10 ### Request Body -${JSON.stringify({ request: "body" })} +${JSON.stringify({request: 'body'})} ### Response Headers - Content-Type:application/json ### Response Body -${JSON.stringify({ response: "body" })} +${JSON.stringify({response: 'body'})} ## Network requests Showing 1-1 of 1 (Page 1 of 1). http://example.com POST [success - 200]`, @@ -318,10 +318,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); @@ -343,9 +343,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); @@ -366,9 +366,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); @@ -386,11 +386,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); @@ -415,11 +415,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); @@ -441,7 +441,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); @@ -454,13 +454,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).')); @@ -471,8 +471,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, { @@ -489,7 +489,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 46709d19c9dcac4c939523c080dae7cc8c723be5 Mon Sep 17 00:00:00 2001 From: Nicholas Roscino Date: Tue, 7 Oct 2025 14:24:26 +0000 Subject: [PATCH 05/10] chore: minor improvements --- src/McpResponse.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/McpResponse.ts b/src/McpResponse.ts index e90c4202..faf72742 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -148,8 +148,12 @@ export class McpResponse implements Response { this.#attachedNetworkRequestData.requestBody = await this.processRequestBody(request); - this.#attachedNetworkRequestData.responseBody = - await this.processResponseBody(request.response()); + + const response = request.response(); + if (response) { + this.#attachedNetworkRequestData.responseBody = + await this.processResponseBody(response); + } } if (this.#includeConsoleData) { @@ -166,12 +170,8 @@ export class McpResponse implements Response { } async processResponseBody( - httpResponse: HTTPResponse | null, + httpResponse: HTTPResponse, ): Promise { - if (!httpResponse) { - return null; - } - const formattedResponseData = await getFormattedResponseBody( httpResponse, BODY_CONTEXT_SIZE_LIMIT, @@ -184,10 +184,6 @@ export class McpResponse implements Response { } async processRequestBody(httpRequest: HTTPRequest): Promise { - if (!httpRequest) { - return null; - } - const formattedResponseData = await getFormattedRequestBody( httpRequest, BODY_CONTEXT_SIZE_LIMIT, From 30c1145ffd451cad10eff5079a162e18378c0175 Mon Sep 17 00:00:00 2001 From: Nicholas Roscino Date: Tue, 7 Oct 2025 14:38:13 +0000 Subject: [PATCH 06/10] chore: make body size limit of payloads a default param --- src/McpResponse.ts | 15 ++++----------- src/formatters/networkFormatter.ts | 6 +++--- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/McpResponse.ts b/src/McpResponse.ts index faf72742..768c2bed 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -16,7 +16,6 @@ import { getFormattedRequestBody, getShortDescriptionForRequest, getStatusFromRequest, - BODY_CONTEXT_SIZE_LIMIT, } from './formatters/networkFormatter.js'; import {formatA11ySnapshot} from './formatters/snapshotFormatter.js'; import type {McpContext} from './McpContext.js'; @@ -172,10 +171,7 @@ export class McpResponse implements Response { async processResponseBody( httpResponse: HTTPResponse, ): Promise { - const formattedResponseData = await getFormattedResponseBody( - httpResponse, - BODY_CONTEXT_SIZE_LIMIT, - ); + const formattedResponseData = await getFormattedResponseBody(httpResponse); if (formattedResponseData.length > 0) { return formattedResponseData; } @@ -184,12 +180,9 @@ export class McpResponse implements Response { } async processRequestBody(httpRequest: HTTPRequest): Promise { - const formattedResponseData = await getFormattedRequestBody( - httpRequest, - BODY_CONTEXT_SIZE_LIMIT, - ); - if (formattedResponseData.length > 0) { - return formattedResponseData; + const formattedRequestData = await getFormattedRequestBody(httpRequest); + if (formattedRequestData.length > 0) { + return formattedRequestData; } return null; diff --git a/src/formatters/networkFormatter.ts b/src/formatters/networkFormatter.ts index 4b3bb8fb..bff0be9c 100644 --- a/src/formatters/networkFormatter.ts +++ b/src/formatters/networkFormatter.ts @@ -8,7 +8,7 @@ import {isUtf8} from 'node:buffer'; import type {HTTPRequest, HTTPResponse} from 'puppeteer-core'; -export const BODY_CONTEXT_SIZE_LIMIT = 10000; +const BODY_CONTEXT_SIZE_LIMIT = 10000; export function getShortDescriptionForRequest(request: HTTPRequest): string { return `${request.url()} ${request.method()} ${getStatusFromRequest(request)}`; @@ -44,7 +44,7 @@ export function getFormattedHeaderValue( export async function getFormattedResponseBody( httpResponse: HTTPResponse, - sizeLimit: number, + sizeLimit: number = BODY_CONTEXT_SIZE_LIMIT, ): Promise { try { const responseBuffer = await httpResponse.buffer(); @@ -68,7 +68,7 @@ export async function getFormattedResponseBody( export async function getFormattedRequestBody( httpRequest: HTTPRequest, - sizeLimit: number, + sizeLimit: number = BODY_CONTEXT_SIZE_LIMIT, ): Promise { if (httpRequest.hasPostData()) { const data = httpRequest.postData(); From 206abd852c60c2f0213774bcdb1301d3879ecef1 Mon Sep 17 00:00:00 2001 From: Nicholas Roscino Date: Tue, 7 Oct 2025 15:10:15 +0000 Subject: [PATCH 07/10] chore: change nulls with undefined --- src/McpResponse.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/McpResponse.ts b/src/McpResponse.ts index 768c2bed..8d2c4efd 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -23,10 +23,10 @@ import {handleDialog} from './tools/pages.js'; import type {ImageContentData, Response} from './tools/ToolDefinition.js'; import {paginate, type PaginationOptions} from './utils/pagination.js'; -export interface NetworkRequestData { +interface NetworkRequestData { networkRequestUrl: string; - requestBody: string | null; - responseBody: string | null; + requestBody?: string; + responseBody?: string; } export class McpResponse implements Response { @@ -84,8 +84,6 @@ export class McpResponse implements Response { attachNetworkRequest(url: string): void { this.#attachedNetworkRequestData = { networkRequestUrl: url, - responseBody: null, - requestBody: null, }; } @@ -170,22 +168,24 @@ export class McpResponse implements Response { async processResponseBody( httpResponse: HTTPResponse, - ): Promise { + ): Promise { const formattedResponseData = await getFormattedResponseBody(httpResponse); if (formattedResponseData.length > 0) { return formattedResponseData; } - return null; + return undefined; } - async processRequestBody(httpRequest: HTTPRequest): Promise { + async processRequestBody( + httpRequest: HTTPRequest, + ): Promise { const formattedRequestData = await getFormattedRequestBody(httpRequest); if (formattedRequestData.length > 0) { return formattedRequestData; } - return null; + return undefined; } format( From 23c26fe26599ad675ef104fe14cfa0bdbd9fa38f Mon Sep 17 00:00:00 2001 From: Nicholas Roscino Date: Tue, 7 Oct 2025 15:29:41 +0000 Subject: [PATCH 08/10] chore: move 'undefined' logic into formatting methods --- src/McpResponse.ts | 28 +++-------------------- src/formatters/networkFormatter.ts | 10 ++++---- tests/formatters/networkFormatter.test.ts | 6 ++--- 3 files changed, 11 insertions(+), 33 deletions(-) diff --git a/src/McpResponse.ts b/src/McpResponse.ts index 8d2c4efd..bf7603bf 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -7,7 +7,7 @@ import type { ImageContent, TextContent, } from '@modelcontextprotocol/sdk/types.js'; -import type {HTTPRequest, HTTPResponse, ResourceType} from 'puppeteer-core'; +import type {ResourceType} from 'puppeteer-core'; import {formatConsoleEvent} from './formatters/consoleFormatter.js'; import { @@ -144,12 +144,12 @@ export class McpResponse implements Response { ); this.#attachedNetworkRequestData.requestBody = - await this.processRequestBody(request); + await getFormattedRequestBody(request); const response = request.response(); if (response) { this.#attachedNetworkRequestData.responseBody = - await this.processResponseBody(response); + await getFormattedResponseBody(response); } } @@ -166,28 +166,6 @@ export class McpResponse implements Response { return this.format(toolName, context); } - async processResponseBody( - httpResponse: HTTPResponse, - ): Promise { - const formattedResponseData = await getFormattedResponseBody(httpResponse); - if (formattedResponseData.length > 0) { - return formattedResponseData; - } - - return undefined; - } - - async processRequestBody( - httpRequest: HTTPRequest, - ): Promise { - const formattedRequestData = await getFormattedRequestBody(httpRequest); - if (formattedRequestData.length > 0) { - return formattedRequestData; - } - - return undefined; - } - format( toolName: string, context: McpContext, diff --git a/src/formatters/networkFormatter.ts b/src/formatters/networkFormatter.ts index bff0be9c..9d5aa2c2 100644 --- a/src/formatters/networkFormatter.ts +++ b/src/formatters/networkFormatter.ts @@ -45,7 +45,7 @@ export function getFormattedHeaderValue( export async function getFormattedResponseBody( httpResponse: HTTPResponse, sizeLimit: number = BODY_CONTEXT_SIZE_LIMIT, -): Promise { +): Promise { try { const responseBuffer = await httpResponse.buffer(); @@ -62,14 +62,14 @@ export async function getFormattedResponseBody( return ``; } catch { // buffer() call might fail with CDP exception, in this case we don't print anything in the context - return ''; + return undefined; } } export async function getFormattedRequestBody( httpRequest: HTTPRequest, sizeLimit: number = BODY_CONTEXT_SIZE_LIMIT, -): Promise { +): Promise { if (httpRequest.hasPostData()) { const data = httpRequest.postData(); @@ -85,11 +85,11 @@ export async function getFormattedRequestBody( } } catch { // fetchPostData() call might fail with CDP exception, in this case we don't print anything in the context - return ''; + return undefined; } } - return ''; + return undefined; } function getSizeLimitedString(text: string, sizeLimit: number) { diff --git a/tests/formatters/networkFormatter.test.ts b/tests/formatters/networkFormatter.test.ts index 9c85e2d5..23c8a323 100644 --- a/tests/formatters/networkFormatter.test.ts +++ b/tests/formatters/networkFormatter.test.ts @@ -122,7 +122,7 @@ describe('networkFormatter', () => { const result = await getFormattedRequestBody(request, 200); - assert.strictEqual(result, ''); + assert.strictEqual(result, undefined); }); it('shows request body when postData is available', async () => { const request = getMockRequest({ @@ -173,7 +173,7 @@ describe('networkFormatter', () => { const result = await getFormattedRequestBody(request, 200); - assert.strictEqual(result, ''); + assert.strictEqual(result, undefined); }); }); @@ -232,7 +232,7 @@ describe('networkFormatter', () => { const result = await getFormattedResponseBody(response, 200); - assert.strictEqual(result, ''); + assert.strictEqual(result, undefined); }); }); }); From decbe12bf2eb98f29a5d08228be7113c9e34f8d9 Mon Sep 17 00:00:00 2001 From: Nicholas Roscino Date: Tue, 7 Oct 2025 17:35:52 +0200 Subject: [PATCH 09/10] chore: remove type annotation on sizeLimit Co-authored-by: Nikolay Vitkov <34244704+Lightning00Blade@users.noreply.github.com> --- src/formatters/networkFormatter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/formatters/networkFormatter.ts b/src/formatters/networkFormatter.ts index 9d5aa2c2..d8b72efa 100644 --- a/src/formatters/networkFormatter.ts +++ b/src/formatters/networkFormatter.ts @@ -44,7 +44,7 @@ export function getFormattedHeaderValue( export async function getFormattedResponseBody( httpResponse: HTTPResponse, - sizeLimit: number = BODY_CONTEXT_SIZE_LIMIT, + sizeLimit = BODY_CONTEXT_SIZE_LIMIT, ): Promise { try { const responseBuffer = await httpResponse.buffer(); From 0d44f91882a8cbb9bca01a50da8be284c4bbd578 Mon Sep 17 00:00:00 2001 From: Nicholas Roscino Date: Wed, 8 Oct 2025 08:25:32 +0000 Subject: [PATCH 10/10] chore: remove explicit undefined return --- src/formatters/networkFormatter.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/formatters/networkFormatter.ts b/src/formatters/networkFormatter.ts index d8b72efa..7796f01a 100644 --- a/src/formatters/networkFormatter.ts +++ b/src/formatters/networkFormatter.ts @@ -62,7 +62,7 @@ export async function getFormattedResponseBody( return ``; } catch { // buffer() call might fail with CDP exception, in this case we don't print anything in the context - return undefined; + return; } } @@ -85,11 +85,11 @@ export async function getFormattedRequestBody( } } catch { // fetchPostData() call might fail with CDP exception, in this case we don't print anything in the context - return undefined; + return; } } - return undefined; + return; } function getSizeLimitedString(text: string, sizeLimit: number) {