@playwright/mcp 0.0.30 → 0.0.32
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 +180 -320
- package/config.d.ts +5 -14
- package/index.d.ts +1 -6
- package/lib/browserContextFactory.js +3 -35
- package/lib/browserServerBackend.js +54 -0
- package/lib/config.js +64 -7
- package/lib/context.js +50 -163
- package/lib/extension/cdpRelay.js +370 -0
- package/lib/extension/main.js +33 -0
- package/lib/httpServer.js +20 -182
- package/lib/index.js +3 -2
- package/lib/log.js +21 -0
- package/lib/loop/loop.js +69 -0
- package/lib/loop/loopClaude.js +152 -0
- package/lib/loop/loopOpenAI.js +141 -0
- package/lib/loop/main.js +60 -0
- package/lib/loopTools/context.js +66 -0
- package/lib/loopTools/main.js +49 -0
- package/lib/loopTools/perform.js +32 -0
- package/lib/loopTools/snapshot.js +29 -0
- package/lib/loopTools/tool.js +18 -0
- package/lib/mcp/inProcessTransport.js +72 -0
- package/lib/mcp/server.js +88 -0
- package/lib/{transport.js → mcp/transport.js} +30 -42
- package/lib/package.js +3 -3
- package/lib/program.js +47 -17
- package/lib/response.js +98 -0
- package/lib/sessionLog.js +70 -0
- package/lib/tab.js +166 -21
- package/lib/tools/common.js +13 -25
- package/lib/tools/console.js +4 -15
- package/lib/tools/dialogs.js +14 -19
- package/lib/tools/evaluate.js +53 -0
- package/lib/tools/files.js +13 -19
- package/lib/tools/install.js +4 -8
- package/lib/tools/keyboard.js +51 -17
- package/lib/tools/mouse.js +99 -0
- package/lib/tools/navigate.js +22 -42
- package/lib/tools/network.js +5 -15
- package/lib/tools/pdf.js +8 -15
- package/lib/tools/screenshot.js +29 -30
- package/lib/tools/snapshot.js +49 -109
- package/lib/tools/tabs.js +21 -52
- package/lib/tools/tool.js +14 -0
- package/lib/tools/utils.js +7 -6
- package/lib/tools/wait.js +8 -11
- package/lib/tools.js +15 -26
- package/package.json +12 -5
- package/lib/browserServer.js +0 -151
- package/lib/connection.js +0 -82
- package/lib/pageSnapshot.js +0 -43
- package/lib/server.js +0 -48
- package/lib/tools/testing.js +0 -60
- package/lib/tools/vision.js +0 -189
package/lib/tools/tabs.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { z } from 'zod';
|
|
17
17
|
import { defineTool } from './tool.js';
|
|
18
18
|
const listTabs = defineTool({
|
|
19
|
-
capability: 'tabs',
|
|
19
|
+
capability: 'core-tabs',
|
|
20
20
|
schema: {
|
|
21
21
|
name: 'browser_tab_list',
|
|
22
22
|
title: 'List tabs',
|
|
@@ -24,23 +24,13 @@ const listTabs = defineTool({
|
|
|
24
24
|
inputSchema: z.object({}),
|
|
25
25
|
type: 'readOnly',
|
|
26
26
|
},
|
|
27
|
-
handle: async (context) => {
|
|
27
|
+
handle: async (context, params, response) => {
|
|
28
28
|
await context.ensureTab();
|
|
29
|
-
|
|
30
|
-
code: [`// <internal code to list tabs>`],
|
|
31
|
-
captureSnapshot: false,
|
|
32
|
-
waitForNetwork: false,
|
|
33
|
-
resultOverride: {
|
|
34
|
-
content: [{
|
|
35
|
-
type: 'text',
|
|
36
|
-
text: await context.listTabsMarkdown(),
|
|
37
|
-
}],
|
|
38
|
-
},
|
|
39
|
-
};
|
|
29
|
+
response.setIncludeTabs();
|
|
40
30
|
},
|
|
41
31
|
});
|
|
42
|
-
const selectTab =
|
|
43
|
-
capability: 'tabs',
|
|
32
|
+
const selectTab = defineTool({
|
|
33
|
+
capability: 'core-tabs',
|
|
44
34
|
schema: {
|
|
45
35
|
name: 'browser_tab_select',
|
|
46
36
|
title: 'Select a tab',
|
|
@@ -50,20 +40,13 @@ const selectTab = captureSnapshot => defineTool({
|
|
|
50
40
|
}),
|
|
51
41
|
type: 'readOnly',
|
|
52
42
|
},
|
|
53
|
-
handle: async (context, params) => {
|
|
43
|
+
handle: async (context, params, response) => {
|
|
54
44
|
await context.selectTab(params.index);
|
|
55
|
-
|
|
56
|
-
`// <internal code to select tab ${params.index}>`,
|
|
57
|
-
];
|
|
58
|
-
return {
|
|
59
|
-
code,
|
|
60
|
-
captureSnapshot,
|
|
61
|
-
waitForNetwork: false
|
|
62
|
-
};
|
|
45
|
+
response.setIncludeSnapshot();
|
|
63
46
|
},
|
|
64
47
|
});
|
|
65
|
-
const newTab =
|
|
66
|
-
capability: 'tabs',
|
|
48
|
+
const newTab = defineTool({
|
|
49
|
+
capability: 'core-tabs',
|
|
67
50
|
schema: {
|
|
68
51
|
name: 'browser_tab_new',
|
|
69
52
|
title: 'Open a new tab',
|
|
@@ -73,22 +56,15 @@ const newTab = captureSnapshot => defineTool({
|
|
|
73
56
|
}),
|
|
74
57
|
type: 'readOnly',
|
|
75
58
|
},
|
|
76
|
-
handle: async (context, params) => {
|
|
77
|
-
await context.newTab();
|
|
59
|
+
handle: async (context, params, response) => {
|
|
60
|
+
const tab = await context.newTab();
|
|
78
61
|
if (params.url)
|
|
79
|
-
await
|
|
80
|
-
|
|
81
|
-
`// <internal code to open a new tab>`,
|
|
82
|
-
];
|
|
83
|
-
return {
|
|
84
|
-
code,
|
|
85
|
-
captureSnapshot,
|
|
86
|
-
waitForNetwork: false
|
|
87
|
-
};
|
|
62
|
+
await tab.navigate(params.url);
|
|
63
|
+
response.setIncludeSnapshot();
|
|
88
64
|
},
|
|
89
65
|
});
|
|
90
|
-
const closeTab =
|
|
91
|
-
capability: 'tabs',
|
|
66
|
+
const closeTab = defineTool({
|
|
67
|
+
capability: 'core-tabs',
|
|
92
68
|
schema: {
|
|
93
69
|
name: 'browser_tab_close',
|
|
94
70
|
title: 'Close a tab',
|
|
@@ -98,21 +74,14 @@ const closeTab = captureSnapshot => defineTool({
|
|
|
98
74
|
}),
|
|
99
75
|
type: 'destructive',
|
|
100
76
|
},
|
|
101
|
-
handle: async (context, params) => {
|
|
77
|
+
handle: async (context, params, response) => {
|
|
102
78
|
await context.closeTab(params.index);
|
|
103
|
-
|
|
104
|
-
`// <internal code to close tab ${params.index}>`,
|
|
105
|
-
];
|
|
106
|
-
return {
|
|
107
|
-
code,
|
|
108
|
-
captureSnapshot,
|
|
109
|
-
waitForNetwork: false
|
|
110
|
-
};
|
|
79
|
+
response.setIncludeSnapshot();
|
|
111
80
|
},
|
|
112
81
|
});
|
|
113
|
-
export default
|
|
82
|
+
export default [
|
|
114
83
|
listTabs,
|
|
115
|
-
newTab
|
|
116
|
-
selectTab
|
|
117
|
-
closeTab
|
|
84
|
+
newTab,
|
|
85
|
+
selectTab,
|
|
86
|
+
closeTab,
|
|
118
87
|
];
|
package/lib/tools/tool.js
CHANGED
|
@@ -16,3 +16,17 @@
|
|
|
16
16
|
export function defineTool(tool) {
|
|
17
17
|
return tool;
|
|
18
18
|
}
|
|
19
|
+
export function defineTabTool(tool) {
|
|
20
|
+
return {
|
|
21
|
+
...tool,
|
|
22
|
+
handle: async (context, params, response) => {
|
|
23
|
+
const tab = context.currentTabOrDie();
|
|
24
|
+
const modalStates = tab.modalStates().map(state => state.type);
|
|
25
|
+
if (tool.clearsModalState && !modalStates.includes(tool.clearsModalState))
|
|
26
|
+
throw new Error(`The tool "${tool.schema.name}" can only be used when there is related modal state present.\n` + tab.modalStatesMarkdown().join('\n'));
|
|
27
|
+
if (!tool.clearsModalState && modalStates.length)
|
|
28
|
+
throw new Error(`Tool "${tool.schema.name}" does not handle the modal state.\n` + tab.modalStatesMarkdown().join('\n'));
|
|
29
|
+
return tool.handle(tab, params, response);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
package/lib/tools/utils.js
CHANGED
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
// @ts-ignore
|
|
17
|
+
import { asLocator } from 'playwright-core/lib/utils';
|
|
18
|
+
export async function waitForCompletion(tab, callback) {
|
|
17
19
|
const requests = new Set();
|
|
18
20
|
let frameNavigated = false;
|
|
19
21
|
let waitCallback = () => { };
|
|
@@ -51,7 +53,7 @@ export async function waitForCompletion(context, tab, callback) {
|
|
|
51
53
|
if (!requests.size && !frameNavigated)
|
|
52
54
|
waitCallback();
|
|
53
55
|
await waitBarrier;
|
|
54
|
-
await
|
|
56
|
+
await tab.waitForTimeout(1000);
|
|
55
57
|
return result;
|
|
56
58
|
}
|
|
57
59
|
finally {
|
|
@@ -67,12 +69,11 @@ export function sanitizeForFilePath(s) {
|
|
|
67
69
|
}
|
|
68
70
|
export async function generateLocator(locator) {
|
|
69
71
|
try {
|
|
70
|
-
|
|
72
|
+
const { resolvedSelector } = await locator._resolveSelector();
|
|
73
|
+
return asLocator('javascript', resolvedSelector);
|
|
71
74
|
}
|
|
72
75
|
catch (e) {
|
|
73
|
-
|
|
74
|
-
throw new Error('Ref not found, likely because element was removed. Use browser_snapshot to see what elements are currently on the page.');
|
|
75
|
-
throw e;
|
|
76
|
+
throw new Error('Ref not found, likely because element was removed. Use browser_snapshot to see what elements are currently on the page.');
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
export async function callOnPageNoTrace(page, callback) {
|
package/lib/tools/wait.js
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { z } from 'zod';
|
|
17
17
|
import { defineTool } from './tool.js';
|
|
18
|
-
const wait =
|
|
19
|
-
capability: '
|
|
18
|
+
const wait = defineTool({
|
|
19
|
+
capability: 'core',
|
|
20
20
|
schema: {
|
|
21
21
|
name: 'browser_wait_for',
|
|
22
22
|
title: 'Wait for',
|
|
@@ -28,13 +28,13 @@ const wait = captureSnapshot => defineTool({
|
|
|
28
28
|
}),
|
|
29
29
|
type: 'readOnly',
|
|
30
30
|
},
|
|
31
|
-
handle: async (context, params) => {
|
|
31
|
+
handle: async (context, params, response) => {
|
|
32
32
|
if (!params.text && !params.textGone && !params.time)
|
|
33
33
|
throw new Error('Either time, text or textGone must be provided');
|
|
34
34
|
const code = [];
|
|
35
35
|
if (params.time) {
|
|
36
36
|
code.push(`await new Promise(f => setTimeout(f, ${params.time} * 1000));`);
|
|
37
|
-
await new Promise(f => setTimeout(f, Math.min(
|
|
37
|
+
await new Promise(f => setTimeout(f, Math.min(30000, params.time * 1000)));
|
|
38
38
|
}
|
|
39
39
|
const tab = context.currentTabOrDie();
|
|
40
40
|
const locator = params.text ? tab.page.getByText(params.text).first() : undefined;
|
|
@@ -47,13 +47,10 @@ const wait = captureSnapshot => defineTool({
|
|
|
47
47
|
code.push(`await page.getByText(${JSON.stringify(params.text)}).first().waitFor({ state: 'visible' });`);
|
|
48
48
|
await locator.waitFor({ state: 'visible' });
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
captureSnapshot,
|
|
53
|
-
waitForNetwork: false,
|
|
54
|
-
};
|
|
50
|
+
response.addResult(`Waited for ${params.text || params.textGone || params.time}`);
|
|
51
|
+
response.setIncludeSnapshot();
|
|
55
52
|
},
|
|
56
53
|
});
|
|
57
|
-
export default
|
|
58
|
-
wait
|
|
54
|
+
export default [
|
|
55
|
+
wait,
|
|
59
56
|
];
|
package/lib/tools.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import common from './tools/common.js';
|
|
17
17
|
import console from './tools/console.js';
|
|
18
18
|
import dialogs from './tools/dialogs.js';
|
|
19
|
+
import evaluate from './tools/evaluate.js';
|
|
19
20
|
import files from './tools/files.js';
|
|
20
21
|
import install from './tools/install.js';
|
|
21
22
|
import keyboard from './tools/keyboard.js';
|
|
@@ -25,37 +26,25 @@ import pdf from './tools/pdf.js';
|
|
|
25
26
|
import snapshot from './tools/snapshot.js';
|
|
26
27
|
import tabs from './tools/tabs.js';
|
|
27
28
|
import screenshot from './tools/screenshot.js';
|
|
28
|
-
import testing from './tools/testing.js';
|
|
29
|
-
import vision from './tools/vision.js';
|
|
30
29
|
import wait from './tools/wait.js';
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
import mouse from './tools/mouse.js';
|
|
31
|
+
export const allTools = [
|
|
32
|
+
...common,
|
|
33
33
|
...console,
|
|
34
|
-
...dialogs
|
|
35
|
-
...
|
|
34
|
+
...dialogs,
|
|
35
|
+
...evaluate,
|
|
36
|
+
...files,
|
|
36
37
|
...install,
|
|
37
|
-
...keyboard
|
|
38
|
-
...navigate
|
|
38
|
+
...keyboard,
|
|
39
|
+
...navigate,
|
|
39
40
|
...network,
|
|
41
|
+
...mouse,
|
|
40
42
|
...pdf,
|
|
41
43
|
...screenshot,
|
|
42
44
|
...snapshot,
|
|
43
|
-
...tabs
|
|
44
|
-
...
|
|
45
|
-
...wait(true),
|
|
46
|
-
];
|
|
47
|
-
export const visionTools = [
|
|
48
|
-
...common(false),
|
|
49
|
-
...console,
|
|
50
|
-
...dialogs(false),
|
|
51
|
-
...files(false),
|
|
52
|
-
...install,
|
|
53
|
-
...keyboard(false),
|
|
54
|
-
...navigate(false),
|
|
55
|
-
...network,
|
|
56
|
-
...pdf,
|
|
57
|
-
...tabs(false),
|
|
58
|
-
...testing,
|
|
59
|
-
...vision,
|
|
60
|
-
...wait(false),
|
|
45
|
+
...tabs,
|
|
46
|
+
...wait,
|
|
61
47
|
];
|
|
48
|
+
export function filteredTools(config) {
|
|
49
|
+
return allTools.filter(tool => tool.capability.startsWith('core') || config.capabilities?.includes(tool.capability));
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playwright/mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.32",
|
|
4
4
|
"description": "Playwright Tools for MCP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -17,15 +17,18 @@
|
|
|
17
17
|
"license": "Apache-2.0",
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsc",
|
|
20
|
+
"build:extension": "tsc --project extension",
|
|
20
21
|
"lint": "npm run update-readme && eslint . && tsc --noEmit",
|
|
22
|
+
"lint-fix": "eslint . --fix",
|
|
21
23
|
"update-readme": "node utils/update-readme.js",
|
|
22
24
|
"watch": "tsc --watch",
|
|
25
|
+
"watch:extension": "tsc --watch --project extension",
|
|
23
26
|
"test": "playwright test",
|
|
24
27
|
"ctest": "playwright test --project=chrome",
|
|
25
28
|
"ftest": "playwright test --project=firefox",
|
|
26
29
|
"wtest": "playwright test --project=webkit",
|
|
27
30
|
"run-server": "node lib/browserServer.js",
|
|
28
|
-
"clean": "rm -rf lib",
|
|
31
|
+
"clean": "rm -rf lib extension/lib",
|
|
29
32
|
"npm-publish": "npm run clean && npm run build && npm run test && npm publish"
|
|
30
33
|
},
|
|
31
34
|
"exports": {
|
|
@@ -36,18 +39,21 @@
|
|
|
36
39
|
}
|
|
37
40
|
},
|
|
38
41
|
"dependencies": {
|
|
39
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.16.0",
|
|
40
43
|
"commander": "^13.1.0",
|
|
41
44
|
"debug": "^4.4.1",
|
|
45
|
+
"dotenv": "^17.2.0",
|
|
42
46
|
"mime": "^4.0.7",
|
|
43
|
-
"playwright": "1.
|
|
47
|
+
"playwright": "1.55.0-alpha-1752701791000",
|
|
48
|
+
"playwright-core": "1.55.0-alpha-1752701791000",
|
|
44
49
|
"ws": "^8.18.1",
|
|
45
50
|
"zod-to-json-schema": "^3.24.4"
|
|
46
51
|
},
|
|
47
52
|
"devDependencies": {
|
|
53
|
+
"@anthropic-ai/sdk": "^0.57.0",
|
|
48
54
|
"@eslint/eslintrc": "^3.2.0",
|
|
49
55
|
"@eslint/js": "^9.19.0",
|
|
50
|
-
"@playwright/test": "1.
|
|
56
|
+
"@playwright/test": "1.55.0-alpha-1752701791000",
|
|
51
57
|
"@stylistic/eslint-plugin": "^3.0.1",
|
|
52
58
|
"@types/chrome": "^0.0.315",
|
|
53
59
|
"@types/debug": "^4.1.12",
|
|
@@ -59,6 +65,7 @@
|
|
|
59
65
|
"eslint": "^9.19.0",
|
|
60
66
|
"eslint-plugin-import": "^2.31.0",
|
|
61
67
|
"eslint-plugin-notice": "^1.0.0",
|
|
68
|
+
"openai": "^5.10.2",
|
|
62
69
|
"typescript": "^5.8.2"
|
|
63
70
|
},
|
|
64
71
|
"bin": {
|
package/lib/browserServer.js
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
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
|
-
/* eslint-disable no-console */
|
|
17
|
-
import net from 'net';
|
|
18
|
-
import { program } from 'commander';
|
|
19
|
-
import playwright from 'playwright';
|
|
20
|
-
import { HttpServer } from './httpServer.js';
|
|
21
|
-
import { packageJSON } from './package.js';
|
|
22
|
-
class BrowserServer {
|
|
23
|
-
_server = new HttpServer();
|
|
24
|
-
_entries = [];
|
|
25
|
-
constructor() {
|
|
26
|
-
this._setupExitHandler();
|
|
27
|
-
}
|
|
28
|
-
async start(port) {
|
|
29
|
-
await this._server.start({ port });
|
|
30
|
-
this._server.routePath('/json/list', (req, res) => {
|
|
31
|
-
this._handleJsonList(res);
|
|
32
|
-
});
|
|
33
|
-
this._server.routePath('/json/launch', async (req, res) => {
|
|
34
|
-
void this._handleLaunchBrowser(req, res).catch(e => console.error(e));
|
|
35
|
-
});
|
|
36
|
-
this._setEntries([]);
|
|
37
|
-
}
|
|
38
|
-
_handleJsonList(res) {
|
|
39
|
-
const list = this._entries.map(browser => browser.info);
|
|
40
|
-
res.end(JSON.stringify(list));
|
|
41
|
-
}
|
|
42
|
-
async _handleLaunchBrowser(req, res) {
|
|
43
|
-
const request = await readBody(req);
|
|
44
|
-
let info = this._entries.map(entry => entry.info).find(info => info.userDataDir === request.userDataDir);
|
|
45
|
-
if (!info || info.error)
|
|
46
|
-
info = await this._newBrowser(request);
|
|
47
|
-
res.end(JSON.stringify(info));
|
|
48
|
-
}
|
|
49
|
-
async _newBrowser(request) {
|
|
50
|
-
const cdpPort = await findFreePort();
|
|
51
|
-
request.launchOptions.cdpPort = cdpPort;
|
|
52
|
-
const info = {
|
|
53
|
-
browserType: request.browserType,
|
|
54
|
-
userDataDir: request.userDataDir,
|
|
55
|
-
cdpPort,
|
|
56
|
-
launchOptions: request.launchOptions,
|
|
57
|
-
contextOptions: request.contextOptions,
|
|
58
|
-
};
|
|
59
|
-
const browserType = playwright[request.browserType];
|
|
60
|
-
const { browser, error } = await browserType.launchPersistentContext(request.userDataDir, {
|
|
61
|
-
...request.launchOptions,
|
|
62
|
-
...request.contextOptions,
|
|
63
|
-
handleSIGINT: false,
|
|
64
|
-
handleSIGTERM: false,
|
|
65
|
-
}).then(context => {
|
|
66
|
-
return { browser: context.browser(), error: undefined };
|
|
67
|
-
}).catch(error => {
|
|
68
|
-
return { browser: undefined, error: error.message };
|
|
69
|
-
});
|
|
70
|
-
this._setEntries([...this._entries, {
|
|
71
|
-
browser,
|
|
72
|
-
info: {
|
|
73
|
-
browserType: request.browserType,
|
|
74
|
-
userDataDir: request.userDataDir,
|
|
75
|
-
cdpPort,
|
|
76
|
-
launchOptions: request.launchOptions,
|
|
77
|
-
contextOptions: request.contextOptions,
|
|
78
|
-
error,
|
|
79
|
-
},
|
|
80
|
-
}]);
|
|
81
|
-
browser?.on('disconnected', () => {
|
|
82
|
-
this._setEntries(this._entries.filter(entry => entry.browser !== browser));
|
|
83
|
-
});
|
|
84
|
-
return info;
|
|
85
|
-
}
|
|
86
|
-
_updateReport() {
|
|
87
|
-
// Clear the current line and move cursor to top of screen
|
|
88
|
-
process.stdout.write('\x1b[2J\x1b[H');
|
|
89
|
-
process.stdout.write(`Playwright Browser Server v${packageJSON.version}\n`);
|
|
90
|
-
process.stdout.write(`Listening on ${this._server.urlPrefix('human-readable')}\n\n`);
|
|
91
|
-
if (this._entries.length === 0) {
|
|
92
|
-
process.stdout.write('No browsers currently running\n');
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
process.stdout.write('Running browsers:\n');
|
|
96
|
-
for (const entry of this._entries) {
|
|
97
|
-
const status = entry.browser ? 'running' : 'error';
|
|
98
|
-
const statusColor = entry.browser ? '\x1b[32m' : '\x1b[31m'; // green for running, red for error
|
|
99
|
-
process.stdout.write(`${statusColor}${entry.info.browserType}\x1b[0m (${entry.info.userDataDir}) - ${statusColor}${status}\x1b[0m\n`);
|
|
100
|
-
if (entry.info.error)
|
|
101
|
-
process.stdout.write(` Error: ${entry.info.error}\n`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
_setEntries(entries) {
|
|
105
|
-
this._entries = entries;
|
|
106
|
-
this._updateReport();
|
|
107
|
-
}
|
|
108
|
-
_setupExitHandler() {
|
|
109
|
-
let isExiting = false;
|
|
110
|
-
const handleExit = async () => {
|
|
111
|
-
if (isExiting)
|
|
112
|
-
return;
|
|
113
|
-
isExiting = true;
|
|
114
|
-
setTimeout(() => process.exit(0), 15000);
|
|
115
|
-
for (const entry of this._entries)
|
|
116
|
-
await entry.browser?.close().catch(() => { });
|
|
117
|
-
process.exit(0);
|
|
118
|
-
};
|
|
119
|
-
process.stdin.on('close', handleExit);
|
|
120
|
-
process.on('SIGINT', handleExit);
|
|
121
|
-
process.on('SIGTERM', handleExit);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
program
|
|
125
|
-
.name('browser-agent')
|
|
126
|
-
.option('-p, --port <port>', 'Port to listen on', '9224')
|
|
127
|
-
.action(async (options) => {
|
|
128
|
-
await main(options);
|
|
129
|
-
});
|
|
130
|
-
void program.parseAsync(process.argv);
|
|
131
|
-
async function main(options) {
|
|
132
|
-
const server = new BrowserServer();
|
|
133
|
-
await server.start(+options.port);
|
|
134
|
-
}
|
|
135
|
-
function readBody(req) {
|
|
136
|
-
return new Promise((resolve, reject) => {
|
|
137
|
-
const chunks = [];
|
|
138
|
-
req.on('data', (chunk) => chunks.push(chunk));
|
|
139
|
-
req.on('end', () => resolve(JSON.parse(Buffer.concat(chunks).toString())));
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
async function findFreePort() {
|
|
143
|
-
return new Promise((resolve, reject) => {
|
|
144
|
-
const server = net.createServer();
|
|
145
|
-
server.listen(0, () => {
|
|
146
|
-
const { port } = server.address();
|
|
147
|
-
server.close(() => resolve(port));
|
|
148
|
-
});
|
|
149
|
-
server.on('error', reject);
|
|
150
|
-
});
|
|
151
|
-
}
|
package/lib/connection.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
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 { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';
|
|
17
|
-
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
18
|
-
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
19
|
-
import { Context } from './context.js';
|
|
20
|
-
import { snapshotTools, visionTools } from './tools.js';
|
|
21
|
-
import { packageJSON } from './package.js';
|
|
22
|
-
export function createConnection(config, browserContextFactory) {
|
|
23
|
-
const allTools = config.vision ? visionTools : snapshotTools;
|
|
24
|
-
const tools = allTools.filter(tool => !config.capabilities || tool.capability === 'core' || config.capabilities.includes(tool.capability));
|
|
25
|
-
const context = new Context(tools, config, browserContextFactory);
|
|
26
|
-
const server = new McpServer({ name: 'Playwright', version: packageJSON.version }, {
|
|
27
|
-
capabilities: {
|
|
28
|
-
tools: {},
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
32
|
-
return {
|
|
33
|
-
tools: tools.map(tool => ({
|
|
34
|
-
name: tool.schema.name,
|
|
35
|
-
description: tool.schema.description,
|
|
36
|
-
inputSchema: zodToJsonSchema(tool.schema.inputSchema),
|
|
37
|
-
annotations: {
|
|
38
|
-
title: tool.schema.title,
|
|
39
|
-
readOnlyHint: tool.schema.type === 'readOnly',
|
|
40
|
-
destructiveHint: tool.schema.type === 'destructive',
|
|
41
|
-
openWorldHint: true,
|
|
42
|
-
},
|
|
43
|
-
})),
|
|
44
|
-
};
|
|
45
|
-
});
|
|
46
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
47
|
-
const errorResult = (...messages) => ({
|
|
48
|
-
content: [{ type: 'text', text: messages.join('\n') }],
|
|
49
|
-
isError: true,
|
|
50
|
-
});
|
|
51
|
-
const tool = tools.find(tool => tool.schema.name === request.params.name);
|
|
52
|
-
if (!tool)
|
|
53
|
-
return errorResult(`Tool "${request.params.name}" not found`);
|
|
54
|
-
const modalStates = context.modalStates().map(state => state.type);
|
|
55
|
-
if (tool.clearsModalState && !modalStates.includes(tool.clearsModalState))
|
|
56
|
-
return errorResult(`The tool "${request.params.name}" can only be used when there is related modal state present.`, ...context.modalStatesMarkdown());
|
|
57
|
-
if (!tool.clearsModalState && modalStates.length)
|
|
58
|
-
return errorResult(`Tool "${request.params.name}" does not handle the modal state.`, ...context.modalStatesMarkdown());
|
|
59
|
-
try {
|
|
60
|
-
return await context.run(tool, request.params.arguments);
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
return errorResult(String(error));
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
return new Connection(server, context);
|
|
67
|
-
}
|
|
68
|
-
export class Connection {
|
|
69
|
-
server;
|
|
70
|
-
context;
|
|
71
|
-
constructor(server, context) {
|
|
72
|
-
this.server = server;
|
|
73
|
-
this.context = context;
|
|
74
|
-
this.server.oninitialized = () => {
|
|
75
|
-
this.context.clientVersion = this.server.getClientVersion();
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
async close() {
|
|
79
|
-
await this.server.close();
|
|
80
|
-
await this.context.close();
|
|
81
|
-
}
|
|
82
|
-
}
|
package/lib/pageSnapshot.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
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 { callOnPageNoTrace } from './tools/utils.js';
|
|
17
|
-
export class PageSnapshot {
|
|
18
|
-
_page;
|
|
19
|
-
_text;
|
|
20
|
-
constructor(page) {
|
|
21
|
-
this._page = page;
|
|
22
|
-
}
|
|
23
|
-
static async create(page) {
|
|
24
|
-
const snapshot = new PageSnapshot(page);
|
|
25
|
-
await snapshot._build();
|
|
26
|
-
return snapshot;
|
|
27
|
-
}
|
|
28
|
-
text() {
|
|
29
|
-
return this._text;
|
|
30
|
-
}
|
|
31
|
-
async _build() {
|
|
32
|
-
const snapshot = await callOnPageNoTrace(this._page, page => page._snapshotForAI());
|
|
33
|
-
this._text = [
|
|
34
|
-
`- Page Snapshot`,
|
|
35
|
-
'```yaml',
|
|
36
|
-
snapshot,
|
|
37
|
-
'```',
|
|
38
|
-
].join('\n');
|
|
39
|
-
}
|
|
40
|
-
refLocator(params) {
|
|
41
|
-
return this._page.locator(`aria-ref=${params.ref}`).describe(params.element);
|
|
42
|
-
}
|
|
43
|
-
}
|
package/lib/server.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
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 { createConnection } from './connection.js';
|
|
17
|
-
import { contextFactory } from './browserContextFactory.js';
|
|
18
|
-
export class Server {
|
|
19
|
-
config;
|
|
20
|
-
_connectionList = [];
|
|
21
|
-
_browserConfig;
|
|
22
|
-
_contextFactory;
|
|
23
|
-
constructor(config) {
|
|
24
|
-
this.config = config;
|
|
25
|
-
this._browserConfig = config.browser;
|
|
26
|
-
this._contextFactory = contextFactory(this._browserConfig);
|
|
27
|
-
}
|
|
28
|
-
async createConnection(transport) {
|
|
29
|
-
const connection = createConnection(this.config, this._contextFactory);
|
|
30
|
-
this._connectionList.push(connection);
|
|
31
|
-
await connection.server.connect(transport);
|
|
32
|
-
return connection;
|
|
33
|
-
}
|
|
34
|
-
setupExitWatchdog() {
|
|
35
|
-
let isExiting = false;
|
|
36
|
-
const handleExit = async () => {
|
|
37
|
-
if (isExiting)
|
|
38
|
-
return;
|
|
39
|
-
isExiting = true;
|
|
40
|
-
setTimeout(() => process.exit(0), 15000);
|
|
41
|
-
await Promise.all(this._connectionList.map(connection => connection.close()));
|
|
42
|
-
process.exit(0);
|
|
43
|
-
};
|
|
44
|
-
process.stdin.on('close', handleExit);
|
|
45
|
-
process.on('SIGINT', handleExit);
|
|
46
|
-
process.on('SIGTERM', handleExit);
|
|
47
|
-
}
|
|
48
|
-
}
|