@playwright/mcp 0.0.20 → 0.0.24

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.
@@ -21,8 +21,10 @@ const snapshot = defineTool({
21
21
  capability: 'core',
22
22
  schema: {
23
23
  name: 'browser_snapshot',
24
+ title: 'Page snapshot',
24
25
  description: 'Capture accessibility snapshot of the current page, this is better than screenshot',
25
26
  inputSchema: z.object({}),
27
+ type: 'readOnly',
26
28
  },
27
29
  handle: async (context) => {
28
30
  await context.ensureTab();
@@ -41,8 +43,10 @@ const click = defineTool({
41
43
  capability: 'core',
42
44
  schema: {
43
45
  name: 'browser_click',
46
+ title: 'Click',
44
47
  description: 'Perform click on a web page',
45
48
  inputSchema: elementSchema,
49
+ type: 'destructive',
46
50
  },
47
51
  handle: async (context, params) => {
48
52
  const tab = context.currentTabOrDie();
@@ -63,6 +67,7 @@ const drag = defineTool({
63
67
  capability: 'core',
64
68
  schema: {
65
69
  name: 'browser_drag',
70
+ title: 'Drag mouse',
66
71
  description: 'Perform drag and drop between two elements',
67
72
  inputSchema: z.object({
68
73
  startElement: z.string().describe('Human-readable source element description used to obtain the permission to interact with the element'),
@@ -70,6 +75,7 @@ const drag = defineTool({
70
75
  endElement: z.string().describe('Human-readable target element description used to obtain the permission to interact with the element'),
71
76
  endRef: z.string().describe('Exact target element reference from the page snapshot'),
72
77
  }),
78
+ type: 'destructive',
73
79
  },
74
80
  handle: async (context, params) => {
75
81
  const snapshot = context.currentTabOrDie().snapshotOrDie();
@@ -91,8 +97,10 @@ const hover = defineTool({
91
97
  capability: 'core',
92
98
  schema: {
93
99
  name: 'browser_hover',
100
+ title: 'Hover mouse',
94
101
  description: 'Hover over element on page',
95
102
  inputSchema: elementSchema,
103
+ type: 'readOnly',
96
104
  },
97
105
  handle: async (context, params) => {
98
106
  const snapshot = context.currentTabOrDie().snapshotOrDie();
@@ -118,8 +126,10 @@ const type = defineTool({
118
126
  capability: 'core',
119
127
  schema: {
120
128
  name: 'browser_type',
129
+ title: 'Type text',
121
130
  description: 'Type text into editable element',
122
131
  inputSchema: typeSchema,
132
+ type: 'destructive',
123
133
  },
124
134
  handle: async (context, params) => {
125
135
  const snapshot = context.currentTabOrDie().snapshotOrDie();
@@ -156,8 +166,10 @@ const selectOption = defineTool({
156
166
  capability: 'core',
157
167
  schema: {
158
168
  name: 'browser_select_option',
169
+ title: 'Select option',
159
170
  description: 'Select an option in a dropdown',
160
171
  inputSchema: selectOptionSchema,
172
+ type: 'destructive',
161
173
  },
162
174
  handle: async (context, params) => {
163
175
  const snapshot = context.currentTabOrDie().snapshotOrDie();
@@ -176,6 +188,7 @@ const selectOption = defineTool({
176
188
  });
177
189
  const screenshotSchema = z.object({
178
190
  raw: z.boolean().optional().describe('Whether to return without compression (in PNG format). Default is false, which returns a JPEG image.'),
191
+ filename: z.string().optional().describe('File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified.'),
179
192
  element: z.string().optional().describe('Human-readable element description used to obtain permission to screenshot the element. If not provided, the screenshot will be taken of viewport. If element is provided, ref must be provided too.'),
180
193
  ref: z.string().optional().describe('Exact target element reference from the page snapshot. If not provided, the screenshot will be taken of viewport. If ref is provided, element must be provided too.'),
181
194
  }).refine(data => {
@@ -188,14 +201,16 @@ const screenshot = defineTool({
188
201
  capability: 'core',
189
202
  schema: {
190
203
  name: 'browser_take_screenshot',
204
+ title: 'Take a screenshot',
191
205
  description: `Take a screenshot of the current page. You can't perform actions based on the screenshot, use browser_snapshot for actions.`,
192
206
  inputSchema: screenshotSchema,
207
+ type: 'readOnly',
193
208
  },
194
209
  handle: async (context, params) => {
195
210
  const tab = context.currentTabOrDie();
196
211
  const snapshot = tab.snapshotOrDie();
197
212
  const fileType = params.raw ? 'png' : 'jpeg';
198
- const fileName = await outputFile(context.config, `page-${new Date().toISOString()}.${fileType}`);
213
+ const fileName = await outputFile(context.config, params.filename ?? `page-${new Date().toISOString()}.${fileType}`);
199
214
  const options = { type: fileType, quality: fileType === 'png' ? undefined : 50, scale: 'css', path: fileName };
200
215
  const isElementScreenshot = params.element && params.ref;
201
216
  const code = [
@@ -206,7 +221,7 @@ const screenshot = defineTool({
206
221
  code.push(`await page.${await generateLocator(locator)}.screenshot(${javascript.formatObject(options)});`);
207
222
  else
208
223
  code.push(`await page.screenshot(${javascript.formatObject(options)});`);
209
- const includeBase64 = !context.config.tools?.browser_take_screenshot?.omitBase64;
224
+ const includeBase64 = !context.config.noImageResponses;
210
225
  const action = async () => {
211
226
  const screenshot = locator ? await locator.screenshot(options) : await tab.page.screenshot(options);
212
227
  return {
package/lib/tools/tabs.js CHANGED
@@ -19,8 +19,10 @@ const listTabs = defineTool({
19
19
  capability: 'tabs',
20
20
  schema: {
21
21
  name: 'browser_tab_list',
22
+ title: 'List tabs',
22
23
  description: 'List browser tabs',
23
24
  inputSchema: z.object({}),
25
+ type: 'readOnly',
24
26
  },
25
27
  handle: async (context) => {
26
28
  await context.ensureTab();
@@ -41,10 +43,12 @@ const selectTab = captureSnapshot => defineTool({
41
43
  capability: 'tabs',
42
44
  schema: {
43
45
  name: 'browser_tab_select',
46
+ title: 'Select a tab',
44
47
  description: 'Select a tab by index',
45
48
  inputSchema: z.object({
46
49
  index: z.number().describe('The index of the tab to select'),
47
50
  }),
51
+ type: 'readOnly',
48
52
  },
49
53
  handle: async (context, params) => {
50
54
  await context.selectTab(params.index);
@@ -62,10 +66,12 @@ const newTab = captureSnapshot => defineTool({
62
66
  capability: 'tabs',
63
67
  schema: {
64
68
  name: 'browser_tab_new',
69
+ title: 'Open a new tab',
65
70
  description: 'Open a new tab',
66
71
  inputSchema: z.object({
67
72
  url: z.string().optional().describe('The URL to navigate to in the new tab. If not provided, the new tab will be blank.'),
68
73
  }),
74
+ type: 'readOnly',
69
75
  },
70
76
  handle: async (context, params) => {
71
77
  await context.newTab();
@@ -85,10 +91,12 @@ const closeTab = captureSnapshot => defineTool({
85
91
  capability: 'tabs',
86
92
  schema: {
87
93
  name: 'browser_tab_close',
94
+ title: 'Close a tab',
88
95
  description: 'Close a tab',
89
96
  inputSchema: z.object({
90
97
  index: z.number().optional().describe('The index of the tab to close. Closes current tab if not provided.'),
91
98
  }),
99
+ type: 'destructive',
92
100
  },
93
101
  handle: async (context, params) => {
94
102
  await context.closeTab(params.index);
@@ -24,8 +24,10 @@ const generateTest = defineTool({
24
24
  capability: 'testing',
25
25
  schema: {
26
26
  name: 'browser_generate_playwright_test',
27
+ title: 'Generate a Playwright test',
27
28
  description: 'Generate a Playwright test for given scenario',
28
29
  inputSchema: generateTestSchema,
30
+ type: 'readOnly',
29
31
  },
30
32
  handle: async (context, params) => {
31
33
  return {
package/lib/tools.js ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import common from './tools/common.js';
17
+ import console from './tools/console.js';
18
+ import dialogs from './tools/dialogs.js';
19
+ import files from './tools/files.js';
20
+ import install from './tools/install.js';
21
+ import keyboard from './tools/keyboard.js';
22
+ import navigate from './tools/navigate.js';
23
+ import network from './tools/network.js';
24
+ import pdf from './tools/pdf.js';
25
+ import snapshot from './tools/snapshot.js';
26
+ import tabs from './tools/tabs.js';
27
+ import screen from './tools/screen.js';
28
+ import testing from './tools/testing.js';
29
+ export const snapshotTools = [
30
+ ...common(true),
31
+ ...console,
32
+ ...dialogs(true),
33
+ ...files(true),
34
+ ...install,
35
+ ...keyboard(true),
36
+ ...navigate(true),
37
+ ...network,
38
+ ...pdf,
39
+ ...snapshot,
40
+ ...tabs(true),
41
+ ...testing,
42
+ ];
43
+ export const screenshotTools = [
44
+ ...common(false),
45
+ ...console,
46
+ ...dialogs(false),
47
+ ...files(false),
48
+ ...install,
49
+ ...keyboard(false),
50
+ ...navigate(false),
51
+ ...network,
52
+ ...pdf,
53
+ ...screen,
54
+ ...tabs(false),
55
+ ...testing,
56
+ ];
package/lib/transport.js CHANGED
@@ -16,14 +16,16 @@
16
16
  import http from 'node:http';
17
17
  import assert from 'node:assert';
18
18
  import crypto from 'node:crypto';
19
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
20
19
  import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
21
20
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
22
- export async function startStdioTransport(serverList) {
23
- const server = await serverList.create();
24
- await server.connect(new StdioServerTransport());
21
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
22
+ import { createConnection } from './connection.js';
23
+ export async function startStdioTransport(config, connectionList) {
24
+ const connection = await createConnection(config);
25
+ await connection.connect(new StdioServerTransport());
26
+ connectionList.push(connection);
25
27
  }
26
- async function handleSSE(req, res, url, serverList, sessions) {
28
+ async function handleSSE(config, req, res, url, sessions, connectionList) {
27
29
  if (req.method === 'POST') {
28
30
  const sessionId = url.searchParams.get('sessionId');
29
31
  if (!sessionId) {
@@ -40,20 +42,22 @@ async function handleSSE(req, res, url, serverList, sessions) {
40
42
  else if (req.method === 'GET') {
41
43
  const transport = new SSEServerTransport('/sse', res);
42
44
  sessions.set(transport.sessionId, transport);
43
- const server = await serverList.create();
45
+ const connection = await createConnection(config);
46
+ await connection.connect(transport);
47
+ connectionList.push(connection);
44
48
  res.on('close', () => {
45
49
  sessions.delete(transport.sessionId);
46
- serverList.close(server).catch(e => {
50
+ connection.close().catch(e => {
47
51
  // eslint-disable-next-line no-console
48
52
  console.error(e);
49
53
  });
50
54
  });
51
- return await server.connect(transport);
55
+ return;
52
56
  }
53
57
  res.statusCode = 405;
54
58
  res.end('Method not allowed');
55
59
  }
56
- async function handleStreamable(req, res, serverList, sessions) {
60
+ async function handleStreamable(config, req, res, sessions, connectionList) {
57
61
  const sessionId = req.headers['mcp-session-id'];
58
62
  if (sessionId) {
59
63
  const transport = sessions.get(sessionId);
@@ -75,22 +79,26 @@ async function handleStreamable(req, res, serverList, sessions) {
75
79
  if (transport.sessionId)
76
80
  sessions.delete(transport.sessionId);
77
81
  };
78
- const server = await serverList.create();
79
- await server.connect(transport);
80
- return await transport.handleRequest(req, res);
82
+ const connection = await createConnection(config);
83
+ connectionList.push(connection);
84
+ await Promise.all([
85
+ connection.connect(transport),
86
+ transport.handleRequest(req, res),
87
+ ]);
88
+ return;
81
89
  }
82
90
  res.statusCode = 400;
83
91
  res.end('Invalid request');
84
92
  }
85
- export function startHttpTransport(port, hostname, serverList) {
93
+ export function startHttpTransport(config, port, hostname, connectionList) {
86
94
  const sseSessions = new Map();
87
95
  const streamableSessions = new Map();
88
96
  const httpServer = http.createServer(async (req, res) => {
89
97
  const url = new URL(`http://localhost${req.url}`);
90
98
  if (url.pathname.startsWith('/mcp'))
91
- await handleStreamable(req, res, serverList, streamableSessions);
99
+ await handleStreamable(config, req, res, streamableSessions, connectionList);
92
100
  else
93
- await handleSSE(req, res, url, serverList, sseSessions);
101
+ await handleSSE(config, req, res, url, sseSessions, connectionList);
94
102
  });
95
103
  httpServer.listen(port, hostname, () => {
96
104
  const address = httpServer.address();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playwright/mcp",
3
- "version": "0.0.20",
3
+ "version": "0.0.24",
4
4
  "description": "Playwright Tools for MCP",
5
5
  "type": "module",
6
6
  "repository": {
@@ -17,7 +17,7 @@
17
17
  "license": "Apache-2.0",
18
18
  "scripts": {
19
19
  "build": "tsc",
20
- "lint": "npm run update-readme && eslint .",
20
+ "lint": "npm run update-readme && eslint . && tsc --noEmit",
21
21
  "update-readme": "node utils/update-readme.js",
22
22
  "watch": "tsc --watch",
23
23
  "test": "playwright test",
@@ -35,16 +35,15 @@
35
35
  }
36
36
  },
37
37
  "dependencies": {
38
- "@modelcontextprotocol/sdk": "^1.10.1",
38
+ "@modelcontextprotocol/sdk": "^1.11.0",
39
39
  "commander": "^13.1.0",
40
- "playwright": "1.53.0-alpha-1746218818000",
41
- "yaml": "^2.7.1",
40
+ "playwright": "1.53.0-alpha-1746832516000",
42
41
  "zod-to-json-schema": "^3.24.4"
43
42
  },
44
43
  "devDependencies": {
45
44
  "@eslint/eslintrc": "^3.2.0",
46
45
  "@eslint/js": "^9.19.0",
47
- "@playwright/test": "1.53.0-alpha-1746218818000",
46
+ "@playwright/test": "1.53.0-alpha-1746832516000",
48
47
  "@stylistic/eslint-plugin": "^3.0.1",
49
48
  "@types/node": "^22.13.10",
50
49
  "@typescript-eslint/eslint-plugin": "^8.26.1",