Skip to content

Commit bff1af0

Browse files
author
Nicholas Roscino
committed
feat: Add request and response bodies
1 parent 2714158 commit bff1af0

File tree

2 files changed

+91
-6
lines changed

2 files changed

+91
-6
lines changed

src/McpResponse.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ import type {ResourceType} from 'puppeteer-core';
1212
import {formatConsoleEvent} from './formatters/consoleFormatter.js';
1313
import {
1414
getFormattedHeaderValue,
15+
getFormattedResponseBody,
16+
getFormattedRequestBody,
1517
getShortDescriptionForRequest,
1618
getStatusFromRequest,
19+
BODY_CONTEXT_SIZE_LIMIT,
1720
} from './formatters/networkFormatter.js';
1821
import {formatA11ySnapshot} from './formatters/snapshotFormatter.js';
1922
import type {McpContext} from './McpContext.js';
@@ -137,13 +140,13 @@ export class McpResponse implements Response {
137140
}
138141
}
139142

140-
return this.format(toolName, context);
143+
return await this.format(toolName, context);
141144
}
142145

143-
format(
146+
async format(
144147
toolName: string,
145148
context: McpContext,
146-
): Array<TextContent | ImageContent> {
149+
): Promise<Array<TextContent | ImageContent>> {
147150
const response = [`# ${toolName} response`];
148151
for (const line of this.#textResponseLines) {
149152
response.push(line);
@@ -192,7 +195,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
192195
}
193196
}
194197

195-
response.push(...this.#getIncludeNetworkRequestsData(context));
198+
response.push(...(await this.#getIncludeNetworkRequestsData(context)));
196199

197200
if (this.#networkRequestsOptions?.include) {
198201
let requests = context.getNetworkRequests();
@@ -272,7 +275,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
272275
};
273276
}
274277

275-
#getIncludeNetworkRequestsData(context: McpContext): string[] {
278+
async #getIncludeNetworkRequestsData(context: McpContext): Promise<string[]> {
276279
const response: string[] = [];
277280
const url = this.#attachedNetworkRequestUrl;
278281
if (!url) {
@@ -286,12 +289,30 @@ Call ${handleDialog.name} to handle it before continuing.`);
286289
response.push(line);
287290
}
288291

292+
const formattedRequestData = await getFormattedRequestBody(
293+
'### Request body',
294+
httpRequest,
295+
BODY_CONTEXT_SIZE_LIMIT,
296+
);
297+
if (formattedRequestData.length > 0) {
298+
response.push(formattedRequestData);
299+
}
300+
289301
const httpResponse = httpRequest.response();
290302
if (httpResponse) {
291303
response.push(`### Response Headers`);
292304
for (const line of getFormattedHeaderValue(httpResponse.headers())) {
293305
response.push(line);
294306
}
307+
308+
const formattedResponseData = await getFormattedResponseBody(
309+
'### Response body',
310+
httpResponse,
311+
BODY_CONTEXT_SIZE_LIMIT,
312+
);
313+
if (formattedResponseData.length > 0) {
314+
response.push(formattedResponseData);
315+
}
295316
}
296317

297318
const httpFailure = httpRequest.failure();

src/formatters/networkFormatter.ts

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

7-
import type {HTTPRequest} from 'puppeteer-core';
7+
import {isUtf8} from 'node:buffer';
8+
9+
import type {HTTPRequest, HTTPResponse} from 'puppeteer-core';
10+
11+
export const BODY_CONTEXT_SIZE_LIMIT = 10000;
812

913
export function getShortDescriptionForRequest(request: HTTPRequest): string {
1014
return `${request.url()} ${request.method()} ${getStatusFromRequest(request)}`;
@@ -37,3 +41,63 @@ export function getFormattedHeaderValue(
3741
}
3842
return response;
3943
}
44+
45+
export async function getFormattedResponseBody(
46+
title: string,
47+
httpResponse: HTTPResponse,
48+
sizeLimit: number,
49+
): Promise<string> {
50+
try {
51+
const responseBuffer = await httpResponse.buffer();
52+
53+
if (isUtf8(responseBuffer)) {
54+
const responseAsTest = responseBuffer.toString('utf-8');
55+
56+
if (responseAsTest.length === 0) {
57+
return `${title}\n<empty response>`;
58+
}
59+
60+
return `${title}\n${getSizeLimitedString(responseAsTest, sizeLimit)}`;
61+
}
62+
63+
return `${title}\n<binary data>`;
64+
} catch {
65+
// buffer() call might fail with CDP exception, in this case we don't print anything in the context
66+
return '';
67+
}
68+
}
69+
70+
export async function getFormattedRequestBody(
71+
title: string,
72+
httpRequest: HTTPRequest,
73+
sizeLimit: number,
74+
): Promise<string> {
75+
if (httpRequest.hasPostData()) {
76+
const data = httpRequest.postData();
77+
78+
if (data) {
79+
return `${title}\n${getSizeLimitedString(data, sizeLimit)}`;
80+
}
81+
82+
try {
83+
const fetchData = await httpRequest.fetchPostData();
84+
85+
if (fetchData) {
86+
return `${title}\n${getSizeLimitedString(fetchData, sizeLimit)}`;
87+
}
88+
} catch {
89+
// fetchPostData() call might fail with CDP exception, in this case we don't print anything in the context
90+
return '';
91+
}
92+
}
93+
94+
return '';
95+
}
96+
97+
function getSizeLimitedString(text: string, sizeLimit: number) {
98+
if (text.length > sizeLimit) {
99+
return `${text.substring(0, sizeLimit) + '... <truncated>'}`;
100+
}
101+
102+
return `${text}`;
103+
}

0 commit comments

Comments
 (0)