Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,11 @@ The Chrome DevTools MCP server supports the following configuration option:
Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.
- **Type:** boolean

- **`--redactNetworkHeaders`/ `--redact-network-headers`**
If true, redacts some of the network headers considered senstive before returning to the client.
- **Type:** boolean
- **Default:** `false`

<!-- END AUTO GENERATED OPTIONS -->

Pass them via the `args` property in the JSON configuration. For example:
Expand Down
7 changes: 7 additions & 0 deletions src/McpResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ export class McpResponse implements Response {
#tabId?: string;
#args: ParsedArguments;
#page?: McpPage;
#redactNetworkHeaders = true;

constructor(args: ParsedArguments) {
this.#args = args;
Expand All @@ -194,6 +195,10 @@ export class McpResponse implements Response {
this.#page = page;
}

setRedactNetworkHeaders(value: boolean): void {
this.#redactNetworkHeaders = value;
}

attachDevToolsData(data: DevToolsData): void {
this.#devToolsData = data;
}
Expand Down Expand Up @@ -425,6 +430,7 @@ export class McpResponse implements Response {
requestFilePath: this.#attachedNetworkRequestOptions?.requestFilePath,
responseFilePath: this.#attachedNetworkRequestOptions?.responseFilePath,
saveFile: (data, filename) => context.saveFile(data, filename),
redactNetworkHeaders: this.#redactNetworkHeaders,
});
detailedNetworkRequest = formatter;
}
Expand Down Expand Up @@ -568,6 +574,7 @@ export class McpResponse implements Response {
this.#networkRequestsOptions?.networkRequestIdInDevToolsUI,
fetchData: false,
saveFile: (data, filename) => context.saveFile(data, filename),
redactNetworkHeaders: this.#redactNetworkHeaders,
}),
),
);
Expand Down
6 changes: 6 additions & 0 deletions src/bin/chrome-devtools-mcp-cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,12 @@ export const cliOptions = {
'Set by Chrome DevTools CLI if the MCP server is started via the CLI client (this arg exists for usage stats)',
hidden: true,
},
redactNetworkHeaders: {
type: 'boolean',
describe:
'If true, redacts some of the network headers considered senstive before returning to the client.',
default: false,
},
} satisfies Record<string, YargsOptions>;

export type ParsedArguments = ReturnType<typeof parseArguments>;
Expand Down
33 changes: 30 additions & 3 deletions src/formatters/NetworkFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@

import {isUtf8} from 'node:buffer';

import type {HTTPRequest, HTTPResponse} from '../third_party/index.js';
import {
DevTools,
type HTTPRequest,
type HTTPResponse,
} from '../third_party/index.js';

const BODY_CONTEXT_SIZE_LIMIT = 10000;

Expand All @@ -21,6 +25,7 @@ export interface NetworkFormatterOptions {
data: Uint8Array<ArrayBufferLike>,
filename: string,
) => Promise<{filename: string}>;
redactNetworkHeaders: boolean;
}

interface NetworkRequestConcise {
Expand Down Expand Up @@ -150,6 +155,20 @@ export class NetworkFormatter {
};
}

#redactNetworkHeaders(
headers: Record<string, string>,
): Record<string, string> {
const headersList = Object.entries(headers).map(item => {
return {name: item[0], value: item[1]};
});
const redacted =
DevTools.NetworkRequestFormatter.sanitizeHeaders(headersList);
return redacted.reduce<Record<string, string>>((acc, item) => {
acc[item.name] = item.value;
return acc;
}, {});
}

toJSONDetailed(): NetworkRequestDetailed {
const redirectChain = this.#request.redirectChain();
const formattedRedirectChain = redirectChain.reverse().map(request => {
Expand All @@ -159,16 +178,24 @@ export class NetworkFormatter {
const formatter = new NetworkFormatter(request, {
requestId: id,
saveFile: this.#options.saveFile,
redactNetworkHeaders: this.#options.redactNetworkHeaders,
});
return formatter.toJSON();
});

const responseHeaders = this.#request.response()?.headers();

return {
...this.toJSON(),
requestHeaders: this.#request.headers(),
requestHeaders: this.#options.redactNetworkHeaders
? this.#redactNetworkHeaders(this.#request.headers())
: this.#request.headers(),
requestBody: this.#requestBody,
requestBodyFilePath: this.#requestBodyFilePath,
responseHeaders: this.#request.response()?.headers(),
responseHeaders:
this.#options.redactNetworkHeaders && responseHeaders
? this.#redactNetworkHeaders(responseHeaders)
: this.#request.response()?.headers(),
responseBody: this.#responseBody,
responseBodyFilePath: this.#responseBodyFilePath,
failure: this.#request.failure()?.errorText,
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ export async function createMcpServer(
const response = serverArgs.slim
? new SlimMcpResponse(serverArgs)
: new McpResponse(serverArgs);

response.setRedactNetworkHeaders(serverArgs.redactNetworkHeaders);
if ('pageScoped' in tool && tool.pageScoped) {
const page =
serverArgs.experimentalPageIdRouting &&
Expand Down
2 changes: 1 addition & 1 deletion tests/McpContext.test.js.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ exports[`McpContext > should include detailed network request in structured cont
"url": "http://example.com/detail",
"status": "pending",
"requestHeaders": {
"content-size": "10"
"content-size": "<redacted>"
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions tests/McpResponse.test.js.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ exports[`McpResponse > add network request when attached 1`] = `
## Request http://example.com
Status: pending
### Request Headers
- content-size:10
- content-size:<redacted>
## Network requests
Showing 1-1 of 1 (Page 1 of 1).
reqid=1 GET http://example.com [pending]
Expand All @@ -16,7 +16,7 @@ exports[`McpResponse > add network request when attached 2`] = `
"url": "http://example.com",
"status": "pending",
"requestHeaders": {
"content-size": "10"
"content-size": "<redacted>"
}
},
"pagination": {
Expand Down Expand Up @@ -44,7 +44,7 @@ exports[`McpResponse > add network request when attached with POST data 1`] = `
## Request http://example.com
Status: 200
### Request Headers
- content-size:10
- content-size:<redacted>
### Request Body
{"request":"body"}
### Response Headers
Expand All @@ -64,7 +64,7 @@ exports[`McpResponse > add network request when attached with POST data 2`] = `
"url": "http://example.com",
"status": "200",
"requestHeaders": {
"content-size": "10"
"content-size": "<redacted>"
},
"requestBody": "{\\"request\\":\\"body\\"}",
"responseHeaders": {
Expand Down
2 changes: 2 additions & 0 deletions tests/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ describe('cli args parsing', () => {
performanceCrux: true,
'usage-statistics': true,
usageStatistics: true,
'redact-network-headers': false,
redactNetworkHeaders: false,
};

it('parses with default args', async () => {
Expand Down
51 changes: 51 additions & 0 deletions tests/formatters/NetworkFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('NetworkFormatter', () => {
const formatter = await NetworkFormatter.from(request, {
requestId: 1,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: false,
});

assert.equal(
Expand All @@ -43,6 +44,7 @@ describe('NetworkFormatter', () => {
const formatter = await NetworkFormatter.from(request, {
requestId: 1,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: false,
});

assert.equal(
Expand All @@ -56,6 +58,7 @@ describe('NetworkFormatter', () => {
const formatter = await NetworkFormatter.from(request, {
requestId: 1,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: false,
});

assert.equal(
Expand All @@ -71,6 +74,7 @@ describe('NetworkFormatter', () => {
const formatter = await NetworkFormatter.from(request, {
requestId: 1,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: false,
});

assert.equal(
Expand All @@ -86,6 +90,7 @@ describe('NetworkFormatter', () => {
const formatter = await NetworkFormatter.from(request, {
requestId: 1,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: false,
});

assert.equal(
Expand All @@ -104,6 +109,7 @@ describe('NetworkFormatter', () => {
const formatter = await NetworkFormatter.from(request, {
requestId: 1,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: false,
});

assert.equal(
Expand All @@ -118,6 +124,7 @@ describe('NetworkFormatter', () => {
requestId: 1,
selectedInDevToolsUI: true,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: false,
});

assert.equal(
Expand All @@ -138,6 +145,7 @@ describe('NetworkFormatter', () => {
requestId: 200,
fetchData: true,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: false,
});
const result = formatter.toStringDetailed();
assert.match(result, /test/);
Expand All @@ -154,6 +162,7 @@ describe('NetworkFormatter', () => {
requestId: 200,
fetchData: true,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: false,
});
const result = formatter.toStringDetailed();

Expand All @@ -176,6 +185,7 @@ describe('NetworkFormatter', () => {
requestId: 20,
fetchData: true,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: false,
});
const result = formatter.toStringDetailed();
assert.match(result, /some text/);
Expand Down Expand Up @@ -209,6 +219,7 @@ describe('NetworkFormatter', () => {
await writeFile(filename, data);
return {filename};
},
redactNetworkHeaders: false,
});

const json = formatter.toJSONDetailed() as {
Expand Down Expand Up @@ -252,6 +263,7 @@ describe('NetworkFormatter', () => {
await writeFile(filename, data);
return {filename};
},
redactNetworkHeaders: false,
});

const reqContent = await readFile(reqPath, 'utf8');
Expand All @@ -272,6 +284,7 @@ describe('NetworkFormatter', () => {
requestId: 200,
fetchData: true,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: false,
});
const result = formatter.toStringDetailed();

Expand All @@ -289,6 +302,7 @@ describe('NetworkFormatter', () => {
requestId: 1,
requestIdResolver: () => 2,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: false,
});
const result = formatter.toStringDetailed();
assert.match(result, /Redirect chain/);
Expand Down Expand Up @@ -322,6 +336,7 @@ describe('NetworkFormatter', () => {
await writeFile(filename, data);
return {filename};
},
redactNetworkHeaders: false,
});

const result = formatter.toStringDetailed();
Expand Down Expand Up @@ -361,6 +376,7 @@ describe('NetworkFormatter', () => {
await writeFile(filename, data);
return {filename};
},
redactNetworkHeaders: false,
});

const result = formatter.toStringDetailed();
Expand All @@ -379,6 +395,7 @@ describe('NetworkFormatter', () => {
requestId: 1,
selectedInDevToolsUI: true,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: false,
});
const result = formatter.toJSON();
assert.deepEqual(result, {
Expand All @@ -404,6 +421,7 @@ describe('NetworkFormatter', () => {
requestId: 1,
fetchData: true,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: false,
});
const result = formatter.toJSONDetailed();
assert.deepEqual(result, {
Expand All @@ -425,6 +443,38 @@ describe('NetworkFormatter', () => {
});
});

it('redacts headers', async () => {
const response = getMockResponse({
headers: {
'set-cookie': 'secret=123',
'content-type': 'text/plain',
},
});
response.buffer = () => Promise.resolve(Buffer.from('response'));
const request = getMockRequest({
response,
headers: {
cookie: 'secret=123',
'user-agent': 'test',
},
});
const formatter = await NetworkFormatter.from(request, {
requestId: 1,
fetchData: true,
saveFile: async () => ({filename: ''}),
redactNetworkHeaders: true,
});
const result = formatter.toJSONDetailed();
assert.deepEqual(result.requestHeaders, {
cookie: '<redacted>',
'user-agent': 'test',
});
assert.deepEqual(result.responseHeaders, {
'set-cookie': '<redacted>',
'content-type': 'text/plain',
});
});

it('returns file paths in structured detailed data', async () => {
const request = {
method: () => 'POST',
Expand Down Expand Up @@ -453,6 +503,7 @@ describe('NetworkFormatter', () => {
await writeFile(filename, data);
return {filename};
},
redactNetworkHeaders: false,
});

const result = formatter.toJSONDetailed() as {
Expand Down
Loading
Loading