Skip to content

Commit 83bbf89

Browse files
committed
feat: Add CDP headers support for authenticated connections
- Add cdpHeaders option to browser configuration - Support --cdp-headers CLI option for JSON header specification - Add PLAYWRIGHT_MCP_CDP_HEADERS environment variable support - Update CdpContextFactory to pass headers to connectOverCDP - Add JSON parsing with error handling for CLI headers - Add tests for headers functionality and invalid JSON handling - Update documentation with usage examples This enables connecting to CDP endpoints that require authentication, such as AWS Bedrock AgentCore Browser instances.
1 parent 21ced70 commit 83bbf89

File tree

6 files changed

+82
-1
lines changed

6 files changed

+82
-1
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ Playwright MCP server supports following arguments. They can be provided in the
155155
--caps <caps> comma-separated list of additional capabilities
156156
to enable, possible values: vision, pdf.
157157
--cdp-endpoint <endpoint> CDP endpoint to connect to.
158+
--cdp-headers <headers> JSON string of headers to send with CDP
159+
connection, e.g. '{"Authorization": "Bearer token"}'
158160
--config <path> path to the configuration file.
159161
--device <device> device to emulate, for example: "iPhone 15"
160162
--executable-path <path> path to the browser executable.
@@ -231,6 +233,20 @@ state [here](https://playwright.dev/docs/auth).
231233
}
232234
```
233235

236+
#### CDP Headers
237+
238+
When connecting to a CDP endpoint that requires authentication, you can pass headers:
239+
240+
```bash
241+
npx @playwright/mcp --cdp-endpoint=http://localhost:9222 --cdp-headers='{"Authorization": "Bearer your-token"}'
242+
```
243+
244+
Or via environment variable:
245+
```bash
246+
export PLAYWRIGHT_MCP_CDP_HEADERS='{"Authorization": "Bearer your-token"}'
247+
npx @playwright/mcp --cdp-endpoint=http://localhost:9222
248+
```
249+
234250
### Configuration file
235251

236252
The Playwright MCP server can be configured using a JSON configuration file. You can specify the configuration file

config.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ export type Config = {
5959
*/
6060
cdpEndpoint?: string;
6161

62+
/**
63+
* Additional HTTP headers to be sent with CDP connect request.
64+
* Only used when cdpEndpoint is specified.
65+
*/
66+
cdpHeaders?: Record<string, string>;
67+
6268
/**
6369
* Remote endpoint to connect to an existing Playwright server.
6470
*/

src/browserContextFactory.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,11 @@ class CdpContextFactory extends BaseContextFactory {
134134
}
135135

136136
protected override async _doObtainBrowser(): Promise<playwright.Browser> {
137-
return playwright.chromium.connectOverCDP(this.config.browser.cdpEndpoint!);
137+
const options: any = {};
138+
if (this.config.browser.cdpHeaders) {
139+
options.headers = this.config.browser.cdpHeaders;
140+
}
141+
return playwright.chromium.connectOverCDP(this.config.browser.cdpEndpoint!, options);
138142
}
139143

140144
protected override async _doCreateContext(browser: playwright.Browser): Promise<playwright.BrowserContext> {

src/config.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export type CLIOptions = {
3030
browser?: string;
3131
caps?: string[];
3232
cdpEndpoint?: string;
33+
cdpHeaders?: Record<string, string>;
3334
config?: string;
3435
device?: string;
3536
executablePath?: string;
@@ -178,6 +179,7 @@ export function configFromCLIOptions(cliOptions: CLIOptions): Config {
178179
launchOptions,
179180
contextOptions,
180181
cdpEndpoint: cliOptions.cdpEndpoint,
182+
cdpHeaders: cliOptions.cdpHeaders,
181183
},
182184
server: {
183185
port: cliOptions.port,
@@ -205,6 +207,7 @@ function configFromEnv(): Config {
205207
options.browser = envToString(process.env.PLAYWRIGHT_MCP_BROWSER);
206208
options.caps = commaSeparatedList(process.env.PLAYWRIGHT_MCP_CAPS);
207209
options.cdpEndpoint = envToString(process.env.PLAYWRIGHT_MCP_CDP_ENDPOINT);
210+
options.cdpHeaders = parseJsonEnv(process.env.PLAYWRIGHT_MCP_CDP_HEADERS);
208211
options.config = envToString(process.env.PLAYWRIGHT_MCP_CONFIG);
209212
options.device = envToString(process.env.PLAYWRIGHT_MCP_DEVICE);
210213
options.executablePath = envToString(process.env.PLAYWRIGHT_MCP_EXECUTABLE_PATH);
@@ -295,6 +298,20 @@ export function semicolonSeparatedList(value: string | undefined): string[] | un
295298
return value.split(';').map(v => v.trim());
296299
}
297300

301+
function parseJsonEnv(value: string | undefined): Record<string, string> | undefined {
302+
if (!value)
303+
return undefined;
304+
try {
305+
const parsed = JSON.parse(value);
306+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
307+
throw new Error('Expected JSON object');
308+
}
309+
return parsed;
310+
} catch (error) {
311+
throw new Error(`Invalid JSON in environment variable: ${error}`);
312+
}
313+
}
314+
298315
export function commaSeparatedList(value: string | undefined): string[] | undefined {
299316
if (!value)
300317
return undefined;

src/program.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ program
3636
.option('--browser <browser>', 'browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.')
3737
.option('--caps <caps>', 'comma-separated list of additional capabilities to enable, possible values: vision, pdf.', commaSeparatedList)
3838
.option('--cdp-endpoint <endpoint>', 'CDP endpoint to connect to.')
39+
.option('--cdp-headers <headers>', 'JSON string of headers to send with CDP connection, e.g. \'{"Authorization": "Bearer token"}\'')
3940
.option('--config <path>', 'path to the configuration file.')
4041
.option('--device <device>', 'device to emulate, for example: "iPhone 15"')
4142
.option('--executable-path <path>', 'path to the browser executable.')
@@ -67,6 +68,18 @@ program
6768
console.error('The --vision option is deprecated, use --caps=vision instead');
6869
options.caps = 'vision';
6970
}
71+
72+
// Parse CDP headers if provided
73+
if (options.cdpHeaders) {
74+
try {
75+
options.cdpHeaders = JSON.parse(options.cdpHeaders);
76+
} catch (error) {
77+
// eslint-disable-next-line no-console
78+
console.error('Invalid JSON format for --cdp-headers:', error);
79+
process.exit(1);
80+
}
81+
}
82+
7083
const config = await resolveCLIConfig(options);
7184

7285
if (options.extension) {

tests/cdp.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,31 @@ test('should throw connection error and allow re-connecting', async ({ cdpServer
8484
});
8585
});
8686

87+
test('cdp server with headers', async ({ cdpServer, startClient, server }) => {
88+
await cdpServer.start();
89+
const headers = { 'X-Test-Header': 'test-value' };
90+
const { client } = await startClient({
91+
args: [`--cdp-endpoint=${cdpServer.endpoint}`, `--cdp-headers=${JSON.stringify(headers)}`]
92+
});
93+
expect(await client.callTool({
94+
name: 'browser_navigate',
95+
arguments: { url: server.HELLO_WORLD },
96+
})).toHaveResponse({
97+
pageState: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`),
98+
});
99+
});
100+
101+
test('cdp server with invalid headers JSON', async () => {
102+
const result = spawnSync('node', [
103+
path.join(__filename, '../../cli.js'),
104+
'--cdp-endpoint=http://localhost:1234',
105+
'--cdp-headers=invalid-json'
106+
]);
107+
expect(result.error).toBeUndefined();
108+
expect(result.status).toBe(1);
109+
expect(result.stderr.toString()).toContain('Invalid JSON format for --cdp-headers');
110+
});
111+
87112
// NOTE: Can be removed when we drop Node.js 18 support and changed to import.meta.filename.
88113
const __filename = url.fileURLToPath(import.meta.url);
89114

0 commit comments

Comments
 (0)