From e8020dfa145bf93b5f89033363f4778ebd388028 Mon Sep 17 00:00:00 2001 From: Pedro Durek Date: Fri, 13 Mar 2026 18:46:15 -0600 Subject: [PATCH] feat: add set_extra_http_headers tool for Network.setExtraHTTPHeaders --- src/tools/network.ts | 27 +++++++++++++ tests/tools/network.test.ts | 80 +++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/src/tools/network.ts b/src/tools/network.ts index 2df445cc3..8bd59e4fb 100644 --- a/src/tools/network.ts +++ b/src/tools/network.ts @@ -138,3 +138,30 @@ export const getNetworkRequest = definePageTool({ } }, }); + +export const setExtraHTTPHeaders = definePageTool({ + name: 'set_extra_http_headers', + description: `Set extra HTTP headers to be sent with every request made by the selected page. Headers persist across navigations until cleared. Pass an empty object to clear all extra headers.`, + annotations: { + category: ToolCategory.NETWORK, + readOnlyHint: false, + }, + schema: { + headers: zod + .record(zod.string(), zod.string()) + .describe( + 'Header name-value pairs to set, e.g. {"X-Custom": "value"}. Pass an empty object {} to clear all extra headers.', + ), + }, + handler: async (request, response) => { + await request.page.pptrPage.setExtraHTTPHeaders(request.params.headers); + const headerCount = Object.keys(request.params.headers).length; + if (headerCount === 0) { + response.appendResponseLine('Extra HTTP headers cleared.'); + } else { + response.appendResponseLine( + `Set ${headerCount} extra HTTP header${headerCount === 1 ? '' : 's'}: ${Object.keys(request.params.headers).join(', ')}`, + ); + } + }, +}); diff --git a/tests/tools/network.test.ts b/tests/tools/network.test.ts index 142555006..4268ebcda 100644 --- a/tests/tools/network.test.ts +++ b/tests/tools/network.test.ts @@ -10,6 +10,7 @@ import {describe, it} from 'node:test'; import { getNetworkRequest, listNetworkRequests, + setExtraHTTPHeaders, } from '../../src/tools/network.js'; import {serverHooks} from '../server.js'; import { @@ -187,4 +188,83 @@ describe('network', () => { }); }); }); + + describe('set_extra_http_headers', () => { + it('sets extra headers on requests', async () => { + let receivedHeaders: Record = {}; + server.addRoute('/headers-test', async (req, res) => { + receivedHeaders = req.headers as Record; + res.writeHead(200, {'Content-Type': 'text/html'}); + res.end('
Headers Test
'); + }); + + await withMcpContext(async (response, context) => { + const page = context.getSelectedPptrPage(); + await setExtraHTTPHeaders.handler( + { + params: {headers: {'X-Custom-Header': 'test-value'}}, + page: context.getSelectedMcpPage(), + }, + response, + context, + ); + assert.ok( + response.responseLines[0]?.includes('1 extra HTTP header'), + ); + + await page.goto(server.getRoute('/headers-test')); + assert.strictEqual(receivedHeaders['x-custom-header'], 'test-value'); + }); + }); + + it('clears extra headers when empty object is passed', async () => { + await withMcpContext(async (response, context) => { + const page = context.getSelectedPptrPage(); + // Set headers first. + await page.setExtraHTTPHeaders({'X-To-Clear': 'value'}); + + await setExtraHTTPHeaders.handler( + { + params: {headers: {}}, + page: context.getSelectedMcpPage(), + }, + response, + context, + ); + assert.ok(response.responseLines[0]?.includes('cleared')); + }); + }); + + it('headers persist across navigations', async () => { + const receivedHeaders: Array> = []; + server.addRoute('/persist-one', async (req, res) => { + receivedHeaders.push({...req.headers} as Record); + res.writeHead(200, {'Content-Type': 'text/html'}); + res.end('
Page One
'); + }); + server.addRoute('/persist-two', async (req, res) => { + receivedHeaders.push({...req.headers} as Record); + res.writeHead(200, {'Content-Type': 'text/html'}); + res.end('
Page Two
'); + }); + + await withMcpContext(async (response, context) => { + const page = context.getSelectedPptrPage(); + await setExtraHTTPHeaders.handler( + { + params: {headers: {'X-Persist': 'yes'}}, + page: context.getSelectedMcpPage(), + }, + response, + context, + ); + + await page.goto(server.getRoute('/persist-one')); + await page.goto(server.getRoute('/persist-two')); + + assert.strictEqual(receivedHeaders[0]?.['x-persist'], 'yes'); + assert.strictEqual(receivedHeaders[1]?.['x-persist'], 'yes'); + }); + }); + }); });