@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.
- package/README.md +95 -16
- package/config.d.ts +15 -15
- package/index.d.ts +8 -2
- package/index.js +2 -2
- package/lib/config.js +16 -18
- package/lib/{server.js → connection.js} +31 -28
- package/lib/context.js +37 -3
- package/lib/index.js +3 -51
- package/lib/pageSnapshot.js +8 -55
- package/lib/program.js +15 -9
- package/lib/tab.js +24 -10
- package/lib/tools/common.js +30 -5
- package/lib/tools/console.js +3 -1
- package/lib/tools/dialogs.js +2 -0
- package/lib/tools/files.js +2 -0
- package/lib/tools/install.js +7 -5
- package/lib/tools/keyboard.js +2 -0
- package/lib/tools/navigate.js +6 -0
- package/lib/tools/network.js +2 -0
- package/lib/tools/pdf.js +8 -3
- package/lib/tools/screen.js +10 -0
- package/lib/tools/snapshot.js +17 -2
- package/lib/tools/tabs.js +8 -0
- package/lib/tools/testing.js +2 -0
- package/lib/tools.js +56 -0
- package/lib/transport.js +23 -15
- package/package.json +5 -6
package/lib/tools/snapshot.js
CHANGED
|
@@ -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.
|
|
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);
|
package/lib/tools/testing.js
CHANGED
|
@@ -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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
50
|
+
connection.close().catch(e => {
|
|
47
51
|
// eslint-disable-next-line no-console
|
|
48
52
|
console.error(e);
|
|
49
53
|
});
|
|
50
54
|
});
|
|
51
|
-
return
|
|
55
|
+
return;
|
|
52
56
|
}
|
|
53
57
|
res.statusCode = 405;
|
|
54
58
|
res.end('Method not allowed');
|
|
55
59
|
}
|
|
56
|
-
async function handleStreamable(req, res,
|
|
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
|
|
79
|
-
|
|
80
|
-
|
|
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,
|
|
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,
|
|
99
|
+
await handleStreamable(config, req, res, streamableSessions, connectionList);
|
|
92
100
|
else
|
|
93
|
-
await handleSSE(req, res, url,
|
|
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.
|
|
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.
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.11.0",
|
|
39
39
|
"commander": "^13.1.0",
|
|
40
|
-
"playwright": "1.53.0-alpha-
|
|
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-
|
|
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",
|