Skip to content

Commit a3f936d

Browse files
committed
Merge branch 'main' into streamable-http
1 parent a355601 commit a3f936d

39 files changed

+694
-314
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,4 @@ jobs:
5858
run: npx playwright install --with-deps
5959

6060
- name: Run tests
61-
run: npm test
61+
run: npm test -- --forbid-only

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@ lib/
22
node_modules/
33
test-results/
44
.vscode/mcp.json
5+
6+
.idea
7+
.DS_Store

README.md

+22-2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ The Playwright MCP server supports the following command-line options:
7474
- `--executable-path <path>`: Path to the browser executable
7575
- `--headless`: Run browser in headless mode (headed by default)
7676
- `--port <port>`: Port to listen on for SSE transport
77+
- `--host <host>`: Host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.
7778
- `--user-data-dir <path>`: Path to the user data directory
7879
- `--vision`: Run server that uses screenshots (Aria snapshots are used by default)
7980

@@ -89,8 +90,7 @@ Playwright MCP will launch the browser with the new profile, located at
8990

9091
All the logged in information will be stored in that profile, you can delete it between sessions if you'd like to clear the offline state.
9192

92-
93-
### Running headless browser (Browser without GUI).
93+
### Running headless browser (Browser without GUI)
9494

9595
This mode is useful for background or batch operations.
9696

@@ -129,9 +129,28 @@ And then in MCP client config, set the `url` to the SSE endpoint:
129129
}
130130
```
131131

132+
When running in a remote server, you can use the `--host` flag to bind the server to `0.0.0.0` to make it accessible from outside.
133+
134+
```bash
135+
npx @playwright/mcp@latest --port 8931 --host 0.0.0.0
136+
```
137+
138+
In MCP client config, `$server-ip` is the IP address of the server:
139+
140+
```js
141+
{
142+
"mcpServers": {
143+
"playwright": {
144+
"url": "http://{$server-ip}:8931/sse"
145+
}
146+
}
147+
}
148+
```
149+
132150
### Docker
133151

134152
**NOTE:** The Docker implementation only supports headless chromium at the moment.
153+
135154
```js
136155
{
137156
"mcpServers": {
@@ -172,6 +191,7 @@ X Y coordinate space, based on the provided screenshot.
172191
### Build with Docker
173192

174193
You can build the Docker image yourself.
194+
175195
```
176196
docker build -t mcp/playwright .
177197
```

eslint.config.mjs

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const plugins = {
3333
};
3434

3535
export const baseRules = {
36+
"@typescript-eslint/no-floating-promises": "error",
3637
"@typescript-eslint/no-unused-vars": [
3738
2,
3839
{ args: "none", caughtErrors: "none" },
@@ -184,6 +185,9 @@ const languageOptions = {
184185
parser: tsParser,
185186
ecmaVersion: 9,
186187
sourceType: "module",
188+
parserOptions: {
189+
project: path.join(fileURLToPath(import.meta.url), "..", "tsconfig.all.json"),
190+
}
187191
};
188192

189193
export default [

package-lock.json

+15-15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@playwright/mcp",
3-
"version": "0.0.14",
3+
"version": "0.0.15",
44
"description": "Playwright Tools for MCP",
55
"repository": {
66
"type": "git",
@@ -36,14 +36,14 @@
3636
"dependencies": {
3737
"@modelcontextprotocol/sdk": "^1.10.1",
3838
"commander": "^13.1.0",
39-
"playwright": "^1.52.0-alpha-1743163434000",
39+
"playwright": "1.53.0-alpha-1745357020000",
4040
"yaml": "^2.7.1",
4141
"zod-to-json-schema": "^3.24.4"
4242
},
4343
"devDependencies": {
4444
"@eslint/eslintrc": "^3.2.0",
4545
"@eslint/js": "^9.19.0",
46-
"@playwright/test": "^1.52.0-alpha-1743163434000",
46+
"@playwright/test": "1.53.0-alpha-1745357020000",
4747
"@stylistic/eslint-plugin": "^3.0.1",
4848
"@types/node": "^22.13.10",
4949
"@typescript-eslint/eslint-plugin": "^8.26.1",

src/context.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export class Context {
124124

125125
async run(tool: Tool, params: Record<string, unknown> | undefined) {
126126
// Tab management is done outside of the action() call.
127-
const toolResult = await tool.handle(this, params);
127+
const toolResult = await tool.handle(this, tool.schema.inputSchema.parse(params));
128128
const { code, action, waitForNetwork, captureSnapshot, resultOverride } = toolResult;
129129
const racingAction = action ? () => this._raceAgainstModalDialogs(action) : undefined;
130130

@@ -317,6 +317,7 @@ export class Tab {
317317
readonly context: Context;
318318
readonly page: playwright.Page;
319319
private _console: playwright.ConsoleMessage[] = [];
320+
private _requests: Map<playwright.Request, playwright.Response | null> = new Map();
320321
private _snapshot: PageSnapshot | undefined;
321322
private _onPageClose: (tab: Tab) => void;
322323

@@ -325,9 +326,11 @@ export class Tab {
325326
this.page = page;
326327
this._onPageClose = onPageClose;
327328
page.on('console', event => this._console.push(event));
329+
page.on('request', request => this._requests.set(request, null));
330+
page.on('response', response => this._requests.set(response.request(), response));
328331
page.on('framenavigated', frame => {
329332
if (!frame.parentFrame())
330-
this._console.length = 0;
333+
this._clearCollectedArtifacts();
331334
});
332335
page.on('close', () => this._onClose());
333336
page.on('filechooser', chooser => {
@@ -342,8 +345,13 @@ export class Tab {
342345
page.setDefaultTimeout(5000);
343346
}
344347

345-
private _onClose() {
348+
private _clearCollectedArtifacts() {
346349
this._console.length = 0;
350+
this._requests.clear();
351+
}
352+
353+
private _onClose() {
354+
this._clearCollectedArtifacts();
347355
this._onPageClose(this);
348356
}
349357

@@ -363,10 +371,14 @@ export class Tab {
363371
return this._snapshot;
364372
}
365373

366-
async console(): Promise<playwright.ConsoleMessage[]> {
374+
console(): playwright.ConsoleMessage[] {
367375
return this._console;
368376
}
369377

378+
requests(): Map<playwright.Request, playwright.Response | null> {
379+
return this._requests;
380+
}
381+
370382
async captureSnapshot() {
371383
this._snapshot = await PageSnapshot.create(this.page);
372384
}
@@ -401,7 +413,7 @@ class PageSnapshot {
401413

402414
private async _snapshotFrame(frame: playwright.Page | playwright.FrameLocator) {
403415
const frameIndex = this._frameLocators.push(frame) - 1;
404-
const snapshotString = await frame.locator('body').ariaSnapshot({ ref: true });
416+
const snapshotString = await frame.locator('body').ariaSnapshot({ ref: true, emitGeneric: true });
405417
const snapshot = yaml.parseDocument(snapshotString);
406418

407419
const visit = async (node: any): Promise<unknown> => {

src/index.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import files from './tools/files';
2626
import install from './tools/install';
2727
import keyboard from './tools/keyboard';
2828
import navigate from './tools/navigate';
29+
import network from './tools/network';
2930
import pdf from './tools/pdf';
3031
import snapshot from './tools/snapshot';
3132
import tabs from './tools/tabs';
@@ -35,27 +36,29 @@ import type { Tool, ToolCapability } from './tools/tool';
3536
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
3637
import type { LaunchOptions } from 'playwright';
3738

38-
const snapshotTools: Tool[] = [
39+
const snapshotTools: Tool<any>[] = [
3940
...common(true),
4041
...console,
4142
...dialogs(true),
4243
...files(true),
4344
...install,
4445
...keyboard(true),
4546
...navigate(true),
47+
...network,
4648
...pdf,
4749
...snapshot,
4850
...tabs(true),
4951
];
5052

51-
const screenshotTools: Tool[] = [
53+
const screenshotTools: Tool<any>[] = [
5254
...common(false),
5355
...console,
5456
...dialogs(false),
5557
...files(false),
5658
...install,
5759
...keyboard(false),
5860
...navigate(false),
61+
...network,
5962
...pdf,
6063
...screen,
6164
...tabs(false),

src/program.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ program
3333
.option('--executable-path <path>', 'Path to the browser executable.')
3434
.option('--headless', 'Run browser in headless mode, headed by default')
3535
.option('--port <port>', 'Port to listen on for SSE transport.')
36+
.option('--host <host>', 'Host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.')
3637
.option('--user-data-dir <path>', 'Path to the user data directory')
3738
.option('--vision', 'Run server that uses screenshots (Aria snapshots are used by default)')
3839
.action(async options => {
@@ -48,7 +49,7 @@ program
4849
setupExitWatchdog(serverList);
4950

5051
if (options.port)
51-
await startHttpTransport(+options.port, serverList);
52+
await startHttpTransport(+options.port, options.host, serverList);
5253
else
5354
await startStdioTransport(serverList);
5455
});

src/server.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
1818
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
19+
import { zodToJsonSchema } from 'zod-to-json-schema';
1920

2021
import { Context } from './context';
2122

@@ -41,7 +42,13 @@ export function createServerWithTools(options: Options): Server {
4142
});
4243

4344
server.setRequestHandler(ListToolsRequestSchema, async () => {
44-
return { tools: tools.map(tool => tool.schema) };
45+
return {
46+
tools: tools.map(tool => ({
47+
name: tool.schema.name,
48+
description: tool.schema.description,
49+
inputSchema: zodToJsonSchema(tool.schema.inputSchema)
50+
})),
51+
};
4552
});
4653

4754
server.setRequestHandler(ListResourcesRequestSchema, async () => {

0 commit comments

Comments
 (0)