ringo-patchright-mcp 1.0.0
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/cli.js +25 -0
- package/index.js +19 -0
- package/monkey-patch.js +168 -0
- package/package.json +28 -0
- package/patch-patchright.js +22 -0
- package/tools/dom.js +0 -0
- package/tools/extras.js +0 -0
- package/tools/network.js +0 -0
- package/tools/record.js +0 -0
- package/tools/rrweb-inject.js +0 -0
package/cli.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
require('./monkey-patch.js');
|
|
19
|
+
const { program } = require("patchright-core/lib/utilsBundle");
|
|
20
|
+
const { decorateCommand } = require('patchright/lib/mcp/program');
|
|
21
|
+
|
|
22
|
+
const packageJSON = require("./node_modules/patchright/package.json");
|
|
23
|
+
const p = program.version('Version ' + packageJSON.version).name('Playwright MCP');
|
|
24
|
+
decorateCommand(p, packageJSON.version)
|
|
25
|
+
void program.parseAsync(process.argv);
|
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 { createConnection } = require('patchright/lib/mcp/index');
|
|
19
|
+
module.exports = { createConnection };
|
package/monkey-patch.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
|
|
2
|
+
const tools = require("patchright/lib/mcp/browser/tools");
|
|
3
|
+
const tool = require("patchright/lib/mcp/browser/tools/tool");
|
|
4
|
+
const defineTabTool = tool.defineTabTool;
|
|
5
|
+
|
|
6
|
+
let z = require('zod');
|
|
7
|
+
|
|
8
|
+
const requests = defineTabTool({
|
|
9
|
+
capability: 'core',
|
|
10
|
+
|
|
11
|
+
schema: {
|
|
12
|
+
name: 'browser_network_requests_v2',
|
|
13
|
+
title: 'List network requests',
|
|
14
|
+
description: 'Returns all network requests since loading the page',
|
|
15
|
+
inputSchema: z.object({
|
|
16
|
+
// includeStatic: z.boolean()
|
|
17
|
+
// .default(false)
|
|
18
|
+
// .describe('Whether to include successful static resources like images, fonts, scripts, etc. Defaults to false.'),
|
|
19
|
+
|
|
20
|
+
"hidePing": z.boolean()
|
|
21
|
+
.default(true)
|
|
22
|
+
.describe('Whether to hide ping requests. Defaults to true.'),
|
|
23
|
+
|
|
24
|
+
"hideOther": z.boolean()
|
|
25
|
+
.default(true)
|
|
26
|
+
.describe('Whether to hide other requests. Defaults to true.'),
|
|
27
|
+
|
|
28
|
+
"hideImage": z.boolean()
|
|
29
|
+
.default(true)
|
|
30
|
+
.describe('Whether to hide image requests. Defaults to true.'),
|
|
31
|
+
|
|
32
|
+
"hideFont": z.boolean()
|
|
33
|
+
.default(true)
|
|
34
|
+
.describe('Whether to hide font requests. Defaults to true.'),
|
|
35
|
+
|
|
36
|
+
"hideMedia": z.boolean()
|
|
37
|
+
.default(true)
|
|
38
|
+
.describe('Whether to hide media requests. Defaults to true.'),
|
|
39
|
+
|
|
40
|
+
"hideStylesheet": z.boolean()
|
|
41
|
+
.default(true)
|
|
42
|
+
.describe('Whether to hide stylesheet requests. Defaults to true.'),
|
|
43
|
+
|
|
44
|
+
"hideScript": z.boolean()
|
|
45
|
+
.default(false)
|
|
46
|
+
.describe('Whether to hide script requests. Defaults to false.'),
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
}),
|
|
51
|
+
type: 'readOnly',
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
handle: async (tab, params, response) => {
|
|
55
|
+
|
|
56
|
+
function isHidden(request) {
|
|
57
|
+
if (params.hidePing && request.resourceType() === 'ping')
|
|
58
|
+
return true;
|
|
59
|
+
if (params.hideOther && request.resourceType() === 'other')
|
|
60
|
+
return true;
|
|
61
|
+
if (params.hideImage && request.resourceType() === 'image')
|
|
62
|
+
return true;
|
|
63
|
+
if (params.hideFont && request.resourceType() === 'font')
|
|
64
|
+
return true;
|
|
65
|
+
if (params.hideMedia && request.resourceType() === 'media')
|
|
66
|
+
return true;
|
|
67
|
+
if (params.hideStylesheet && request.resourceType() === 'stylesheet')
|
|
68
|
+
return true;
|
|
69
|
+
if (params.hideScript && request.resourceType() === 'script')
|
|
70
|
+
return true;
|
|
71
|
+
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
const requests = await tab.requests();
|
|
77
|
+
for (const request of requests) {
|
|
78
|
+
if (isHidden(request))
|
|
79
|
+
continue;
|
|
80
|
+
const rendered = await renderRequest(request,);
|
|
81
|
+
if (rendered)
|
|
82
|
+
response.addResult(rendered);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const requestMap = new Map();
|
|
88
|
+
const requestIdMap = new Map();
|
|
89
|
+
|
|
90
|
+
function getId(request) {
|
|
91
|
+
if (requestIdMap.has(request))
|
|
92
|
+
return requestIdMap.get(request);
|
|
93
|
+
const id = "req_" + (requestMap.size + 1);
|
|
94
|
+
requestMap.set(id, request);
|
|
95
|
+
requestIdMap.set(request, id);
|
|
96
|
+
return id;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getRequestById(id) {
|
|
100
|
+
return requestMap.get(id);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function renderRequest(request, includeStatic) {
|
|
104
|
+
const response = (request)._hasResponse ? await request.response() : undefined;
|
|
105
|
+
const isStaticRequest = ['document', 'stylesheet', 'image', 'media', 'font', 'script', 'manifest'].includes(request.resourceType());
|
|
106
|
+
const isSuccessfulRequest = !response || response.status() < 400;
|
|
107
|
+
|
|
108
|
+
if (isStaticRequest && isSuccessfulRequest && !includeStatic)
|
|
109
|
+
return undefined;
|
|
110
|
+
|
|
111
|
+
let id = getId(request);
|
|
112
|
+
const result = [];
|
|
113
|
+
result.push(`[${id}] [${request.method().toUpperCase()}] ${request.url()} [${request.resourceType()}]`);
|
|
114
|
+
if (response)
|
|
115
|
+
result.push(`=> [${response.status()}] ${response.statusText()} `);
|
|
116
|
+
return result.join(' ');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const response = defineTabTool({
|
|
120
|
+
capability: 'core',
|
|
121
|
+
schema: {
|
|
122
|
+
name: 'browser_network_full_response_by_id',
|
|
123
|
+
title: 'List network full request response by id',
|
|
124
|
+
description: 'Returns all network full request response by id since loading the page',
|
|
125
|
+
inputSchema: z.object({
|
|
126
|
+
id: z.string().describe('Id of the network request'),
|
|
127
|
+
}),
|
|
128
|
+
type: 'readOnly',
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
handle: async (tab, params, mcpResponse) => {
|
|
132
|
+
const request = getRequestById(params.id);
|
|
133
|
+
if (!request) {
|
|
134
|
+
mcpResponse.addError(`Request with id ${params.id} not found`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let response = (request)._hasResponse ? await request.response() : undefined;
|
|
139
|
+
|
|
140
|
+
let requestInfo = `[${params.id}] ${request.method().toUpperCase()} ${request.url()}`;
|
|
141
|
+
if (response) {
|
|
142
|
+
requestInfo += ` => ${response.status()} ${response.statusText()}`;
|
|
143
|
+
}
|
|
144
|
+
let reqHeadersInfo = `\n ======Request Headers======\n${JSON.stringify(await request.allHeaders())}\n`;
|
|
145
|
+
|
|
146
|
+
let requestBodyInfo = "";
|
|
147
|
+
if (request.postData()) {
|
|
148
|
+
requestBodyInfo = `\n ======Request Body======\n${request.postData()}\n`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let finalStr = `${requestInfo}\n${reqHeadersInfo}${requestBodyInfo}`;
|
|
152
|
+
if (response) {
|
|
153
|
+
let bodyText = await response.text();
|
|
154
|
+
let respHeadersInfo = JSON.stringify(await response.allHeaders());
|
|
155
|
+
finalStr += `\n ======Response Headers======\n${respHeadersInfo}\n`;
|
|
156
|
+
finalStr += `\n ======Response Body======\n${bodyText}\n`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
mcpResponse.addResult(finalStr);
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
tools.browserTools.push(requests)
|
|
165
|
+
tools.browserTools.push(response)
|
|
166
|
+
|
|
167
|
+
//移除 tools.browserTools中的browser_network_requests
|
|
168
|
+
tools.browserTools = tools.browserTools.filter((tool) => tool.schema.name !== 'browser_network_requests');
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ringo-patchright-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Patchright-based MCP server for browser automation",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ringo-patchright-mcp": "cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"cli.js",
|
|
11
|
+
"index.js",
|
|
12
|
+
"monkey-patch.js",
|
|
13
|
+
"patch-patchright.js",
|
|
14
|
+
"tools/",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"postinstall": "node ./patch-patchright.js"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"patchright": "^1.57.0",
|
|
22
|
+
"rrweb": "^2.0.0-alpha.4",
|
|
23
|
+
"rrweb-player": "^1.0.0-alpha.4",
|
|
24
|
+
"zod": "^3.25.76"
|
|
25
|
+
},
|
|
26
|
+
"author": "",
|
|
27
|
+
"license": "Apache-2.0"
|
|
28
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// import fs from "fs";
|
|
2
|
+
// import path from "path";
|
|
3
|
+
|
|
4
|
+
// const pkgPath = path.resolve("node_modules/patchright/package.json");
|
|
5
|
+
// const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
6
|
+
|
|
7
|
+
// // 增加你想暴露的内部模块
|
|
8
|
+
// pkg.exports["./lib/mcp/browser/tab"] = "./lib/mcp/browser/tab.js";
|
|
9
|
+
|
|
10
|
+
// // 写回文件
|
|
11
|
+
// fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
12
|
+
|
|
13
|
+
// console.log("patchright patched");
|
|
14
|
+
|
|
15
|
+
let fs = require("fs");
|
|
16
|
+
let path = require("path");
|
|
17
|
+
const pkgPath = path.resolve("node_modules/patchright/package.json");
|
|
18
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
19
|
+
|
|
20
|
+
pkg.exports["./lib/mcp/browser/tools/tool"] = "./lib/mcp/browser/tools/tool.js";
|
|
21
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
22
|
+
console.log("patchright mcp export patched");
|
package/tools/dom.js
ADDED
|
File without changes
|
package/tools/extras.js
ADDED
|
File without changes
|
package/tools/network.js
ADDED
|
File without changes
|
package/tools/record.js
ADDED
|
File without changes
|
|
File without changes
|