diff --git a/packages/playwright/src/mcp/browser/context.ts b/packages/playwright/src/mcp/browser/context.ts index 180ce53cb8ab7..657194f8e9057 100644 --- a/packages/playwright/src/mcp/browser/context.ts +++ b/packages/playwright/src/mcp/browser/context.ts @@ -52,7 +52,6 @@ export class Context { private _tabs: Tab[] = []; private _currentTab: Tab | undefined; private _clientInfo: ClientInfo; - private _extraHTTPHeaders: Record | undefined; private static _allContexts: Set = new Set(); private _closeBrowserContextPromise: Promise | undefined; @@ -221,20 +220,6 @@ export class Context { return browserContext; } - async setExtraHTTPHeaders(headers: Record) { - if (!Object.keys(headers).length) - throw new Error('Please provide at least one header to set.'); - - for (const name of Object.keys(headers)) { - if (!name.trim()) - throw new Error('Header names must be non-empty strings.'); - } - - this._extraHTTPHeaders = { ...headers }; - const { browserContext } = await this._ensureBrowserContext(); - await browserContext.setExtraHTTPHeaders(this._extraHTTPHeaders); - } - private _ensureBrowserContext() { if (!this._browserContextPromise) { this._browserContextPromise = this._setupBrowserContext(); @@ -255,8 +240,6 @@ export class Context { const result = await this._browserContextFactory.createContext(this._clientInfo, this._abortController.signal, this._runningToolName); const { browserContext } = result; await this._setupRequestInterception(browserContext); - if (this._extraHTTPHeaders) - await browserContext.setExtraHTTPHeaders(this._extraHTTPHeaders); if (this.sessionLog) await InputRecorder.create(this, browserContext); for (const page of browserContext.pages()) diff --git a/packages/playwright/src/mcp/browser/tools.ts b/packages/playwright/src/mcp/browser/tools.ts index 98ec122d330bb..3ebbe7591e58f 100644 --- a/packages/playwright/src/mcp/browser/tools.ts +++ b/packages/playwright/src/mcp/browser/tools.ts @@ -26,7 +26,6 @@ import mouse from './tools/mouse'; import navigate from './tools/navigate'; import network from './tools/network'; import pdf from './tools/pdf'; -import headers from './tools/headers'; import snapshot from './tools/snapshot'; import screenshot from './tools/screenshot'; import tabs from './tools/tabs'; @@ -48,7 +47,6 @@ export const browserTools: Tool[] = [ ...keyboard, ...navigate, ...network, - ...headers, ...mouse, ...pdf, ...screenshot, diff --git a/packages/playwright/src/mcp/browser/tools/headers.ts b/packages/playwright/src/mcp/browser/tools/headers.ts deleted file mode 100644 index 578eab1f4035a..0000000000000 --- a/packages/playwright/src/mcp/browser/tools/headers.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { z } from '../../sdk/bundle'; -import { defineTool } from './tool'; - -const setHeaders = defineTool({ - capability: 'headers', - - schema: { - name: 'browser_set_headers', - title: 'Set extra HTTP headers', - description: 'Persistently set custom HTTP headers on the active browser context.', - inputSchema: z.object({ - headers: z.record(z.string(), z.string()).describe('Header names mapped to the values that should be sent with every request.'), - }), - type: 'action', - }, - - handle: async (context, params, response) => { - try { - await context.setExtraHTTPHeaders(params.headers); - } catch (error) { - response.addError((error as Error).message); - return; - } - - const count = Object.keys(params.headers).length; - response.addResult(`Configured ${count} ${count === 1 ? 'header' : 'headers'} for this session.`); - response.addCode(`await context.setExtraHTTPHeaders(${JSON.stringify(params.headers, null, 2)});`); - }, -}); - -export default [ - setHeaders, -]; diff --git a/packages/playwright/src/mcp/config.d.ts b/packages/playwright/src/mcp/config.d.ts index ba0f0977bf65d..7a43731f3d717 100644 --- a/packages/playwright/src/mcp/config.d.ts +++ b/packages/playwright/src/mcp/config.d.ts @@ -16,7 +16,7 @@ import type * as playwright from 'playwright-core'; -export type ToolCapability = 'core' | 'core-tabs' | 'core-install' | 'vision' | 'pdf' | 'testing' | 'tracing' | 'headers'; +export type ToolCapability = 'core' | 'core-tabs' | 'core-install' | 'vision' | 'pdf' | 'testing' | 'tracing'; export type Config = { /** @@ -99,7 +99,6 @@ export type Config = { * - 'core': Core browser automation features. * - 'pdf': PDF generation and manipulation. * - 'vision': Coordinate-based interactions. - * - 'headers': Manage persistent custom HTTP headers. */ capabilities?: ToolCapability[]; @@ -172,3 +171,4 @@ export type Config = { */ imageResponses?: 'allow' | 'omit'; }; + diff --git a/packages/playwright/src/mcp/program.ts b/packages/playwright/src/mcp/program.ts index 54460437b97a2..8f979cc3d37cf 100644 --- a/packages/playwright/src/mcp/program.ts +++ b/packages/playwright/src/mcp/program.ts @@ -33,7 +33,7 @@ export function decorateCommand(command: Command, version: string) { .option('--blocked-origins ', 'semicolon-separated list of origins to block the browser from requesting. Blocklist is evaluated before allowlist. If used without the allowlist, requests not matching the blocklist are still allowed.', semicolonSeparatedList) .option('--block-service-workers', 'block service workers') .option('--browser ', 'browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.') - .option('--caps ', 'comma-separated list of additional capabilities to enable, possible values: vision, pdf, headers.', commaSeparatedList) + .option('--caps ', 'comma-separated list of additional capabilities to enable, possible values: vision, pdf.', commaSeparatedList) .option('--cdp-endpoint ', 'CDP endpoint to connect to.') .option('--cdp-header ', 'CDP headers to send with the connect request, multiple can be specified.', headerParser) .option('--config ', 'path to the configuration file.') diff --git a/tests/mcp/generator.spec.ts b/tests/mcp/generator.spec.ts index 7c4b7141e5201..d0feace8e79eb 100644 --- a/tests/mcp/generator.spec.ts +++ b/tests/mcp/generator.spec.ts @@ -41,7 +41,6 @@ test('generator tools intent', async ({ startClient }) => { 'browser_type', 'browser_navigate', 'browser_navigate_back', - 'browser_set_headers', 'browser_mouse_move_xy', 'browser_mouse_click_xy', 'browser_mouse_drag_xy', diff --git a/tests/mcp/headers.spec.ts b/tests/mcp/headers.spec.ts deleted file mode 100644 index 839e8d2b5a01f..0000000000000 --- a/tests/mcp/headers.spec.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { test, expect } from './fixtures'; - -test('headers tool requires capability', async ({ client, startClient }) => { - const { tools } = await client.listTools(); - expect(tools.map(tool => tool.name)).not.toContain('browser_set_headers'); - - const { client: headersClient } = await startClient({ args: ['--caps=headers'] }); - const headersToolList = await headersClient.listTools(); - expect(headersToolList.tools.map(tool => tool.name)).toContain('browser_set_headers'); -}); - -test('browser_set_headers rejects empty input', async ({ startClient }) => { - const { client } = await startClient({ args: ['--caps=headers'] }); - - const response = await client.callTool({ - name: 'browser_set_headers', - arguments: { headers: {} }, - }); - - expect(response).toHaveResponse({ - isError: true, - result: 'Please provide at least one header to set.', - }); -}); - -test('browser_set_headers rejects header names without characters', async ({ startClient }) => { - const { client } = await startClient({ args: ['--caps=headers'] }); - - const response = await client.callTool({ - name: 'browser_set_headers', - arguments: { headers: { ' ': 'value' } }, - }); - - expect(response).toHaveResponse({ - isError: true, - result: 'Header names must be non-empty strings.', - }); -}); - -test('browser_set_headers persists headers across navigations', async ({ startClient, server }) => { - server.setContent('/first', 'First', 'text/html'); - server.setContent('/second', 'Second', 'text/html'); - - const { client } = await startClient({ args: ['--caps=headers'] }); - - expect(await client.callTool({ - name: 'browser_set_headers', - arguments: { - headers: { 'X-Tenant-ID': 'tenant-123' }, - }, - })).toHaveResponse({ - result: 'Configured 1 header for this session.', - }); - - const firstRequestPromise = server.waitForRequest('/first'); - await client.callTool({ - name: 'browser_navigate', - arguments: { url: `${server.PREFIX}/first` }, - }); - const firstRequest = await firstRequestPromise; - expect(firstRequest.headers['x-tenant-id']).toBe('tenant-123'); - - const secondRequestPromise = server.waitForRequest('/second'); - await client.callTool({ - name: 'browser_navigate', - arguments: { url: `${server.PREFIX}/second` }, - }); - const secondRequest = await secondRequestPromise; - expect(secondRequest.headers['x-tenant-id']).toBe('tenant-123'); -}); - -test('browser_set_headers sends headers with requests', async ({ startClient, server }) => { - server.setContent('/page', 'Page', 'text/html'); - - const { client } = await startClient({ args: ['--caps=headers'] }); - - expect(await client.callTool({ - name: 'browser_set_headers', - arguments: { - headers: { 'X-Custom-Header': 'custom-value' }, - }, - })).toHaveResponse({ - result: 'Configured 1 header for this session.', - }); - - const requestPromise = server.waitForRequest('/page'); - await client.callTool({ - name: 'browser_navigate', - arguments: { url: `${server.PREFIX}/page` }, - }); - - const request = await requestPromise; - expect(request.headers['x-custom-header']).toBe('custom-value'); -});