@playwright/mcp 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ A Model Context Protocol (MCP) server that provides browser automation capabilit
4
4
 
5
5
  ### Key Features
6
6
 
7
- - **Fast and lightweight**: Uses Playwrights accessibility tree, not pixel-based input.
7
+ - **Fast and lightweight**: Uses Playwright's accessibility tree, not pixel-based input.
8
8
  - **LLM-friendly**: No vision models needed, operates purely on structured data.
9
9
  - **Deterministic tool application**: Avoids ambiguity common with screenshot-based approaches.
10
10
 
@@ -23,13 +23,55 @@ A Model Context Protocol (MCP) server that provides browser automation capabilit
23
23
  "playwright": {
24
24
  "command": "npx",
25
25
  "args": [
26
- "@playwright/mcp@latest",
26
+ "@playwright/mcp@latest"
27
27
  ]
28
28
  }
29
29
  }
30
30
  }
31
31
  ```
32
32
 
33
+
34
+ #### Installation in VS Code
35
+
36
+ Install the Playwright MCP server in VS Code using one of these buttons:
37
+
38
+ <!--
39
+ // Generate using?:
40
+ const config = JSON.stringify({ name: 'playwright', command: 'npx', args: ["-y", "@playwright/mcp@latest"] });
41
+ const urlForWebsites = `vscode:mcp/install?${encodeURIComponent(config)}`;
42
+ // Github markdown does not allow linking to `vscode:` directly, so you can use our redirect:
43
+ const urlForGithub = `https://insiders.vscode.dev/redirect?url=${encodeURIComponent(urlForWebsites)}`;
44
+ -->
45
+
46
+ [<img alt="Install in VS Code Insiders" src="https://img.shields.io/badge/VS_Code_Insiders-VS_Code_Insiders?style=flat-square&label=Install%20Server&color=24bfa5">](https://insiders.vscode.dev/redirect?url=vscode-insiders%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522playwright%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522%2540playwright%252Fmcp%2540latest%2522%255D%257D)
47
+
48
+ Alternatively, you can install the Playwright MCP server using the VS Code CLI:
49
+
50
+ ```bash
51
+ # For VS Code
52
+ code --add-mcp '{"name":"playwright","command":"npx","args":["@playwright/mcp@latest"]}'
53
+ ```
54
+
55
+ ```bash
56
+ # For VS Code Insiders
57
+ code-insiders --add-mcp '{"name":"playwright","command":"npx","args":["@playwright/mcp@latest"]}'
58
+ ```
59
+
60
+ After installation, the Playwright MCP server will be available for use with your GitHub Copilot agent in VS Code.
61
+
62
+ ### User data directory
63
+
64
+ Playwright MCP will launch Chrome browser with the new profile, located at
65
+
66
+ ```
67
+ - `%USERPROFILE%\AppData\Local\ms-playwright\mcp-chrome-profile` on Windows
68
+ - `~/Library/Caches/ms-playwright/mcp-chrome-profile` on macOS
69
+ - `~/.cache/ms-playwright/mcp-chrome-profile` on Linux
70
+ ```
71
+
72
+ All the logged in information will be stored in that profile, you can delete it between sessions if you'dlike to clear the offline state.
73
+
74
+
33
75
  ### Running headless browser (Browser without GUI).
34
76
 
35
77
  This mode is useful for background or batch operations.
@@ -103,6 +145,20 @@ To use Vision Mode, add the `--vision` flag when starting the server:
103
145
  Vision Mode works best with the computer use models that are able to interact with elements using
104
146
  X Y coordinate space, based on the provided screenshot.
105
147
 
148
+ ### Programmatic usage with custom transports
149
+
150
+ ```js
151
+ import { createServer } from '@playwright/mcp';
152
+
153
+ // ...
154
+
155
+ const server = createServer({
156
+ launchOptions: { headless: true }
157
+ });
158
+ transport = new SSEServerTransport("/messages", res);
159
+ server.connect(transport);
160
+ ```
161
+
106
162
  ### Snapshot Mode
107
163
 
108
164
  The Playwright MCP provides a set of tools for browser automation. Here are all available tools:
@@ -148,6 +204,13 @@ The Playwright MCP provides a set of tools for browser automation. Here are all
148
204
  - `text` (string): Text to type into the element
149
205
  - `submit` (boolean): Whether to submit entered text (press Enter after)
150
206
 
207
+ - **browser_select_option**
208
+ - Description: Select option in a dropdown
209
+ - Parameters:
210
+ - `element` (string): Human-readable element description used to obtain permission to interact with the element
211
+ - `ref` (string): Exact target element reference from the page snapshot
212
+ - `values` (array): Array of values to select in the dropdown.
213
+
151
214
  - **browser_press_key**
152
215
  - Description: Press a key on the keyboard
153
216
  - Parameters:
package/index.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copyright (c) Microsoft Corporation.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ import type { LaunchOptions } from 'playwright';
19
+ import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
20
+
21
+ type Options = {
22
+ /**
23
+ * Path to the user data directory.
24
+ */
25
+ userDataDir?: string;
26
+
27
+ /**
28
+ * Launch options for the browser.
29
+ */
30
+ launchOptions?: LaunchOptions;
31
+
32
+ /**
33
+ * Use screenshots instead of snapshots. Less accurate, reliable and overall
34
+ * slower, but contains visual representation of the page.
35
+ * @default false
36
+ */
37
+ vision?: boolean;
38
+ };
39
+
40
+ export function createServer(options?: Options): Server;
package/index.js ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copyright (c) Microsoft Corporation.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ const { createServer } = require('./lib/index');
19
+ module.exports = { createServer };
package/lib/context.js CHANGED
@@ -51,47 +51,67 @@ Object.defineProperty(exports, "__esModule", { value: true });
51
51
  exports.Context = void 0;
52
52
  const playwright = __importStar(require("playwright"));
53
53
  class Context {
54
+ _userDataDir;
54
55
  _launchOptions;
56
+ _browser;
55
57
  _page;
56
58
  _console = [];
57
- _initializePromise;
58
- constructor(launchOptions) {
59
+ _createPagePromise;
60
+ constructor(userDataDir, launchOptions) {
61
+ this._userDataDir = userDataDir;
59
62
  this._launchOptions = launchOptions;
60
63
  }
61
- async ensurePage() {
62
- await this._initialize();
64
+ async createPage() {
65
+ if (this._createPagePromise)
66
+ return this._createPagePromise;
67
+ this._createPagePromise = (async () => {
68
+ const { browser, page } = await this._createPage();
69
+ page.on('console', event => this._console.push(event));
70
+ page.on('framenavigated', frame => {
71
+ if (!frame.parentFrame())
72
+ this._console.length = 0;
73
+ });
74
+ page.on('close', () => this._onPageClose());
75
+ this._page = page;
76
+ this._browser = browser;
77
+ return page;
78
+ })();
79
+ return this._createPagePromise;
80
+ }
81
+ _onPageClose() {
82
+ const browser = this._browser;
83
+ const page = this._page;
84
+ void page?.context()?.close().then(() => browser?.close()).catch(() => { });
85
+ this._createPagePromise = undefined;
86
+ this._browser = undefined;
87
+ this._page = undefined;
88
+ this._console.length = 0;
89
+ }
90
+ async existingPage() {
91
+ if (!this._page)
92
+ throw new Error('Navigate to a location to create a page');
63
93
  return this._page;
64
94
  }
65
- async ensureConsole() {
66
- await this._initialize();
95
+ async console() {
67
96
  return this._console;
68
97
  }
69
98
  async close() {
70
- const page = await this.ensurePage();
71
- await page.close();
72
- this._initializePromise = undefined;
73
- }
74
- async _initialize() {
75
- if (this._initializePromise)
76
- return this._initializePromise;
77
- this._initializePromise = (async () => {
78
- const browser = await this._createBrowser();
79
- this._page = await browser.newPage();
80
- this._page.on('console', event => this._console.push(event));
81
- this._page.on('framenavigated', frame => {
82
- if (!frame.parentFrame())
83
- this._console.length = 0;
84
- });
85
- })();
86
- return this._initializePromise;
99
+ if (!this._page)
100
+ return;
101
+ await this._page.close();
87
102
  }
88
- async _createBrowser() {
103
+ async _createPage() {
89
104
  if (process.env.PLAYWRIGHT_WS_ENDPOINT) {
90
105
  const url = new URL(process.env.PLAYWRIGHT_WS_ENDPOINT);
91
- url.searchParams.set('launch-options', JSON.stringify(this._launchOptions));
92
- return await playwright.chromium.connect(String(url));
106
+ if (this._launchOptions)
107
+ url.searchParams.set('launch-options', JSON.stringify(this._launchOptions));
108
+ const browser = await playwright.chromium.connect(String(url));
109
+ const page = await browser.newPage();
110
+ return { browser, page };
93
111
  }
94
- return await playwright.chromium.launch({ channel: 'chrome', ...this._launchOptions });
112
+ const context = await playwright.chromium.launchPersistentContext(this._userDataDir, this._launchOptions);
113
+ const [page] = context.pages();
114
+ return { page };
95
115
  }
96
116
  }
97
117
  exports.Context = Context;
package/lib/index.js ADDED
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Microsoft Corporation.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.createServer = createServer;
52
+ const server_1 = require("./server");
53
+ const snapshot = __importStar(require("./tools/snapshot"));
54
+ const common = __importStar(require("./tools/common"));
55
+ const screenshot = __importStar(require("./tools/screenshot"));
56
+ const console_1 = require("./resources/console");
57
+ const commonTools = [
58
+ common.pressKey,
59
+ common.wait,
60
+ common.pdf,
61
+ common.close,
62
+ ];
63
+ const snapshotTools = [
64
+ common.navigate(true),
65
+ common.goBack(true),
66
+ common.goForward(true),
67
+ snapshot.snapshot,
68
+ snapshot.click,
69
+ snapshot.hover,
70
+ snapshot.type,
71
+ snapshot.selectOption,
72
+ ...commonTools,
73
+ ];
74
+ const screenshotTools = [
75
+ common.navigate(false),
76
+ common.goBack(false),
77
+ common.goForward(false),
78
+ screenshot.screenshot,
79
+ screenshot.moveMouse,
80
+ screenshot.click,
81
+ screenshot.drag,
82
+ screenshot.type,
83
+ ...commonTools,
84
+ ];
85
+ const resources = [
86
+ console_1.console,
87
+ ];
88
+ const packageJSON = require('../package.json');
89
+ function createServer(options) {
90
+ const tools = options?.vision ? screenshotTools : snapshotTools;
91
+ return (0, server_1.createServerWithTools)({
92
+ name: 'Playwright',
93
+ version: packageJSON.version,
94
+ tools,
95
+ resources,
96
+ userDataDir: options?.userDataDir ?? '',
97
+ launchOptions: options?.launchOptions,
98
+ });
99
+ }
package/lib/program.js CHANGED
@@ -14,101 +14,55 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
- if (k2 === undefined) k2 = k;
19
- var desc = Object.getOwnPropertyDescriptor(m, k);
20
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
- desc = { enumerable: true, get: function() { return m[k]; } };
22
- }
23
- Object.defineProperty(o, k2, desc);
24
- }) : (function(o, m, k, k2) {
25
- if (k2 === undefined) k2 = k;
26
- o[k2] = m[k];
27
- }));
28
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
- Object.defineProperty(o, "default", { enumerable: true, value: v });
30
- }) : function(o, v) {
31
- o["default"] = v;
32
- });
33
- var __importStar = (this && this.__importStar) || (function () {
34
- var ownKeys = function(o) {
35
- ownKeys = Object.getOwnPropertyNames || function (o) {
36
- var ar = [];
37
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
- return ar;
39
- };
40
- return ownKeys(o);
41
- };
42
- return function (mod) {
43
- if (mod && mod.__esModule) return mod;
44
- var result = {};
45
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
- __setModuleDefault(result, mod);
47
- return result;
48
- };
49
- })();
17
+ var __importDefault = (this && this.__importDefault) || function (mod) {
18
+ return (mod && mod.__esModule) ? mod : { "default": mod };
19
+ };
50
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
+ const fs_1 = __importDefault(require("fs"));
22
+ const os_1 = __importDefault(require("os"));
23
+ const path_1 = __importDefault(require("path"));
51
24
  const commander_1 = require("commander");
52
- const server_1 = require("./server");
53
- const snapshot = __importStar(require("./tools/snapshot"));
54
- const common = __importStar(require("./tools/common"));
55
- const screenshot = __importStar(require("./tools/screenshot"));
56
- const console_1 = require("./resources/console");
25
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
26
+ const index_1 = require("./index");
57
27
  const packageJSON = require('../package.json');
58
28
  commander_1.program
59
29
  .version('Version ' + packageJSON.version)
60
30
  .name(packageJSON.name)
61
31
  .option('--headless', 'Run browser in headless mode, headed by default')
32
+ .option('--user-data-dir <path>', 'Path to the user data directory')
62
33
  .option('--vision', 'Run server that uses screenshots (Aria snapshots are used by default)')
63
34
  .action(async (options) => {
64
35
  const launchOptions = {
65
36
  headless: !!options.headless,
37
+ channel: 'chrome',
66
38
  };
67
- const tools = options.vision ? screenshotTools : snapshotTools;
68
- const server = new server_1.Server({
69
- name: 'Playwright',
70
- version: packageJSON.version,
71
- tools,
72
- resources,
73
- }, launchOptions);
39
+ const server = (0, index_1.createServer)({
40
+ userDataDir: options.userDataDir ?? await userDataDir(),
41
+ launchOptions,
42
+ });
74
43
  setupExitWatchdog(server);
75
- await server.start();
44
+ const transport = new stdio_js_1.StdioServerTransport();
45
+ await server.connect(transport);
76
46
  });
77
47
  function setupExitWatchdog(server) {
78
48
  process.stdin.on('close', async () => {
79
49
  setTimeout(() => process.exit(0), 15000);
80
- await server?.stop();
50
+ await server.close();
81
51
  process.exit(0);
82
52
  });
83
53
  }
84
- const commonTools = [
85
- common.pressKey,
86
- common.wait,
87
- common.pdf,
88
- common.close,
89
- ];
90
- const snapshotTools = [
91
- common.navigate(true),
92
- common.goBack(true),
93
- common.goForward(true),
94
- snapshot.snapshot,
95
- snapshot.click,
96
- snapshot.hover,
97
- snapshot.type,
98
- ...commonTools,
99
- ];
100
- const screenshotTools = [
101
- common.navigate(false),
102
- common.goBack(false),
103
- common.goForward(false),
104
- screenshot.screenshot,
105
- screenshot.moveMouse,
106
- screenshot.click,
107
- screenshot.drag,
108
- screenshot.type,
109
- ...commonTools,
110
- ];
111
- const resources = [
112
- console_1.console,
113
- ];
114
54
  commander_1.program.parse(process.argv);
55
+ async function userDataDir() {
56
+ let cacheDirectory;
57
+ if (process.platform === 'linux')
58
+ cacheDirectory = process.env.XDG_CACHE_HOME || path_1.default.join(os_1.default.homedir(), '.cache');
59
+ else if (process.platform === 'darwin')
60
+ cacheDirectory = path_1.default.join(os_1.default.homedir(), 'Library', 'Caches');
61
+ else if (process.platform === 'win32')
62
+ cacheDirectory = process.env.LOCALAPPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Local');
63
+ else
64
+ throw new Error('Unsupported platform: ' + process.platform);
65
+ const result = path_1.default.join(cacheDirectory, 'ms-playwright', 'mcp-chrome-profile');
66
+ await fs_1.default.promises.mkdir(result, { recursive: true });
67
+ return result;
68
+ }
@@ -23,14 +23,12 @@ exports.console = {
23
23
  mimeType: 'text/plain',
24
24
  },
25
25
  read: async (context, uri) => {
26
- const result = [];
27
- for (const message of await context.ensureConsole()) {
28
- result.push({
26
+ const messages = await context.console();
27
+ const log = messages.map(message => `[${message.type().toUpperCase()}] ${message.text()}`).join('\n');
28
+ return [{
29
29
  uri,
30
30
  mimeType: 'text/plain',
31
- text: `[${message.type().toUpperCase()}] ${message.text()}`,
32
- });
33
- }
34
- return result;
31
+ text: log
32
+ }];
35
33
  },
36
34
  };
package/lib/server.js CHANGED
@@ -15,66 +15,54 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.Server = void 0;
18
+ exports.createServerWithTools = createServerWithTools;
19
19
  const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
20
- const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
21
20
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
22
21
  const context_1 = require("./context");
23
- class Server {
24
- _server;
25
- _tools;
26
- _page;
27
- _context;
28
- constructor(options, launchOptions) {
29
- const { name, version, tools, resources } = options;
30
- this._context = new context_1.Context(launchOptions);
31
- this._server = new index_js_1.Server({ name, version }, {
32
- capabilities: {
33
- tools: {},
34
- resources: {},
35
- }
36
- });
37
- this._tools = tools;
38
- this._server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
39
- return { tools: tools.map(tool => tool.schema) };
40
- });
41
- this._server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
42
- return { resources: resources.map(resource => resource.schema) };
43
- });
44
- this._server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
45
- const tool = this._tools.find(tool => tool.schema.name === request.params.name);
46
- if (!tool) {
47
- return {
48
- content: [{ type: 'text', text: `Tool "${request.params.name}" not found` }],
49
- isError: true,
50
- };
51
- }
52
- try {
53
- const result = await tool.handle(this._context, request.params.arguments);
54
- return result;
55
- }
56
- catch (error) {
57
- return {
58
- content: [{ type: 'text', text: String(error) }],
59
- isError: true,
60
- };
61
- }
62
- });
63
- this._server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
64
- const resource = resources.find(resource => resource.schema.uri === request.params.uri);
65
- if (!resource)
66
- return { contents: [] };
67
- const contents = await resource.read(this._context, request.params.uri);
68
- return { contents };
69
- });
70
- }
71
- async start() {
72
- const transport = new stdio_js_1.StdioServerTransport();
73
- await this._server.connect(transport);
74
- }
75
- async stop() {
76
- await this._server.close();
77
- await this._page?.context()?.browser()?.close();
78
- }
22
+ function createServerWithTools(options) {
23
+ const { name, version, tools, resources, userDataDir, launchOptions } = options;
24
+ const context = new context_1.Context(userDataDir, launchOptions);
25
+ const server = new index_js_1.Server({ name, version }, {
26
+ capabilities: {
27
+ tools: {},
28
+ resources: {},
29
+ }
30
+ });
31
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
32
+ return { tools: tools.map(tool => tool.schema) };
33
+ });
34
+ server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
35
+ return { resources: resources.map(resource => resource.schema) };
36
+ });
37
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
38
+ const tool = tools.find(tool => tool.schema.name === request.params.name);
39
+ if (!tool) {
40
+ return {
41
+ content: [{ type: 'text', text: `Tool "${request.params.name}" not found` }],
42
+ isError: true,
43
+ };
44
+ }
45
+ try {
46
+ const result = await tool.handle(context, request.params.arguments);
47
+ return result;
48
+ }
49
+ catch (error) {
50
+ return {
51
+ content: [{ type: 'text', text: String(error) }],
52
+ isError: true,
53
+ };
54
+ }
55
+ });
56
+ server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
57
+ const resource = resources.find(resource => resource.schema.uri === request.params.uri);
58
+ if (!resource)
59
+ return { contents: [] };
60
+ const contents = await resource.read(context, request.params.uri);
61
+ return { contents };
62
+ });
63
+ server.close = async () => {
64
+ await server.close();
65
+ await context.close();
66
+ };
67
+ return server;
79
68
  }
80
- exports.Server = Server;
@@ -35,7 +35,7 @@ const navigate = snapshot => ({
35
35
  },
36
36
  handle: async (context, params) => {
37
37
  const validatedParams = navigateSchema.parse(params);
38
- const page = await context.ensurePage();
38
+ const page = await context.createPage();
39
39
  await page.goto(validatedParams.url, { waitUntil: 'domcontentloaded' });
40
40
  // Cap load event to 5 seconds, the page is operational at this point.
41
41
  await page.waitForLoadState('load', { timeout: 5000 }).catch(() => { });
@@ -58,10 +58,7 @@ const goBack = snapshot => ({
58
58
  inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(goBackSchema),
59
59
  },
60
60
  handle: async (context) => {
61
- return await (0, utils_1.runAndWait)(context, 'Navigated back', async () => {
62
- const page = await context.ensurePage();
63
- await page.goBack();
64
- }, snapshot);
61
+ return await (0, utils_1.runAndWait)(context, 'Navigated back', async (page) => page.goBack(), snapshot);
65
62
  },
66
63
  });
67
64
  exports.goBack = goBack;
@@ -73,10 +70,7 @@ const goForward = snapshot => ({
73
70
  inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(goForwardSchema),
74
71
  },
75
72
  handle: async (context) => {
76
- return await (0, utils_1.runAndWait)(context, 'Navigated forward', async () => {
77
- const page = await context.ensurePage();
78
- await page.goForward();
79
- }, snapshot);
73
+ return await (0, utils_1.runAndWait)(context, 'Navigated forward', async (page) => page.goForward(), snapshot);
80
74
  },
81
75
  });
82
76
  exports.goForward = goForward;
@@ -91,8 +85,7 @@ exports.wait = {
91
85
  },
92
86
  handle: async (context, params) => {
93
87
  const validatedParams = waitSchema.parse(params);
94
- const page = await context.ensurePage();
95
- await page.waitForTimeout(Math.min(10000, validatedParams.time * 1000));
88
+ await new Promise(f => setTimeout(f, Math.min(10000, validatedParams.time * 1000)));
96
89
  return {
97
90
  content: [{
98
91
  type: 'text',
@@ -125,7 +118,7 @@ exports.pdf = {
125
118
  inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(pdfSchema),
126
119
  },
127
120
  handle: async (context) => {
128
- const page = await context.ensurePage();
121
+ const page = await context.existingPage();
129
122
  const fileName = path_1.default.join(os_1.default.tmpdir(), `/page-${new Date().toISOString()}.pdf`);
130
123
  await page.pdf({ path: fileName });
131
124
  return {
@@ -26,7 +26,7 @@ exports.screenshot = {
26
26
  inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(zod_1.z.object({})),
27
27
  },
28
28
  handle: async (context) => {
29
- const page = await context.ensurePage();
29
+ const page = await context.existingPage();
30
30
  const screenshot = await page.screenshot({ type: 'jpeg', quality: 50, scale: 'css' });
31
31
  return {
32
32
  content: [{ type: 'image', data: screenshot.toString('base64'), mimeType: 'image/jpeg' }],
@@ -48,7 +48,7 @@ exports.moveMouse = {
48
48
  },
49
49
  handle: async (context, params) => {
50
50
  const validatedParams = moveMouseSchema.parse(params);
51
- const page = await context.ensurePage();
51
+ const page = await context.existingPage();
52
52
  await page.mouse.move(validatedParams.x, validatedParams.y);
53
53
  return {
54
54
  content: [{ type: 'text', text: `Moved mouse to (${validatedParams.x}, ${validatedParams.y})` }],
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
18
18
  return (mod && mod.__esModule) ? mod : { "default": mod };
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.type = exports.hover = exports.drag = exports.click = exports.snapshot = void 0;
21
+ exports.selectOption = exports.type = exports.hover = exports.drag = exports.click = exports.snapshot = void 0;
22
22
  const zod_1 = require("zod");
23
23
  const zod_to_json_schema_1 = __importDefault(require("zod-to-json-schema"));
24
24
  const utils_1 = require("./utils");
@@ -29,7 +29,7 @@ exports.snapshot = {
29
29
  inputSchema: (0, zod_to_json_schema_1.default)(zod_1.z.object({})),
30
30
  },
31
31
  handle: async (context) => {
32
- return await (0, utils_1.captureAriaSnapshot)(await context.ensurePage());
32
+ return await (0, utils_1.captureAriaSnapshot)(await context.existingPage());
33
33
  },
34
34
  };
35
35
  const elementSchema = zod_1.z.object({
@@ -99,6 +99,23 @@ exports.type = {
99
99
  }, true);
100
100
  },
101
101
  };
102
+ const selectOptionSchema = elementSchema.extend({
103
+ values: zod_1.z.array(zod_1.z.string()).describe('Array of values to select in the dropdown. This can be a single value or multiple values.'),
104
+ });
105
+ exports.selectOption = {
106
+ schema: {
107
+ name: 'browser_select_option',
108
+ description: 'Select an option in a dropdown',
109
+ inputSchema: (0, zod_to_json_schema_1.default)(selectOptionSchema),
110
+ },
111
+ handle: async (context, params) => {
112
+ const validatedParams = selectOptionSchema.parse(params);
113
+ return await (0, utils_1.runAndWait)(context, `Selected option in "${validatedParams.element}"`, async (page) => {
114
+ const locator = refLocator(page, validatedParams.ref);
115
+ await locator.selectOption(validatedParams.values);
116
+ }, true);
117
+ },
118
+ };
102
119
  function refLocator(page, ref) {
103
120
  return page.locator(`aria-ref=${ref}`);
104
121
  }
@@ -65,7 +65,7 @@ async function waitForCompletion(page, callback) {
65
65
  }
66
66
  }
67
67
  async function runAndWait(context, status, callback, snapshot = false) {
68
- const page = await context.ensurePage();
68
+ const page = await context.existingPage();
69
69
  await waitForCompletion(page, () => callback(page));
70
70
  return snapshot ? captureAriaSnapshot(page, status) : {
71
71
  content: [{ type: 'text', text: status }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playwright/mcp",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Playwright Tools for MCP",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,24 +21,22 @@
21
21
  "test": "playwright test"
22
22
  },
23
23
  "exports": {
24
- "./servers/server": "./lib/servers/server.js",
25
- "./servers/screenshot": "./lib/servers/screenshot.js",
26
- "./servers/snapshot": "./lib/servers/snapshot.js",
27
- "./tools/common": "./lib/tools/common.js",
28
- "./tools/screenshot": "./lib/tools/screenshot.js",
29
- "./tools/snapshot": "./lib/tools/snapshot.js",
30
- "./package.json": "./package.json"
24
+ "./package.json": "./package.json",
25
+ ".": {
26
+ "types": "./index.d.ts",
27
+ "default": "./index.js"
28
+ }
31
29
  },
32
30
  "dependencies": {
33
31
  "@modelcontextprotocol/sdk": "^1.6.1",
34
32
  "commander": "^13.1.0",
35
- "playwright": "1.52.0-alpha-2025-03-21",
33
+ "playwright": "1.52.0-alpha-1743011787000",
36
34
  "zod-to-json-schema": "^3.24.4"
37
35
  },
38
36
  "devDependencies": {
39
37
  "@eslint/eslintrc": "^3.2.0",
40
38
  "@eslint/js": "^9.19.0",
41
- "@playwright/test": "1.52.0-alpha-2025-03-21",
39
+ "@playwright/test": "1.52.0-alpha-1743011787000",
42
40
  "@stylistic/eslint-plugin": "^3.0.1",
43
41
  "@typescript-eslint/eslint-plugin": "^8.26.1",
44
42
  "@typescript-eslint/parser": "^8.26.1",
@@ -50,6 +48,6 @@
50
48
  "typescript": "^5.8.2"
51
49
  },
52
50
  "bin": {
53
- "mcp": "cli.js"
51
+ "mcp-server-playwright": "cli.js"
54
52
  }
55
53
  }