@wordbricks/playwright-mcp 0.1.19 → 0.1.22
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 +54 -44
- package/cli-wrapper.js +15 -14
- package/cli.js +1 -1
- package/config.d.ts +11 -6
- package/index.d.ts +7 -5
- package/index.js +1 -1
- package/lib/browserContextFactory.js +131 -58
- package/lib/browserServerBackend.js +14 -12
- package/lib/config.js +60 -46
- package/lib/context.js +41 -39
- package/lib/extension/cdpRelay.js +67 -61
- package/lib/extension/extensionContextFactory.js +10 -10
- package/lib/frameworkPatterns.js +21 -21
- package/lib/hooks/antiBotDetectionHook.js +178 -0
- package/lib/hooks/core.js +11 -10
- package/lib/hooks/eventConsumer.js +29 -16
- package/lib/hooks/events.js +3 -3
- package/lib/hooks/formatToolCallEvent.js +3 -7
- package/lib/hooks/frameworkStateHook.js +40 -40
- package/lib/hooks/grouping.js +3 -3
- package/lib/hooks/jsonLdDetectionHook.js +44 -37
- package/lib/hooks/networkFilters.js +24 -15
- package/lib/hooks/networkSetup.js +11 -6
- package/lib/hooks/networkTrackingHook.js +31 -19
- package/lib/hooks/pageHeightHook.js +9 -9
- package/lib/hooks/registry.js +18 -16
- package/lib/hooks/requireTabHook.js +3 -3
- package/lib/hooks/schema.js +44 -32
- package/lib/hooks/waitHook.js +7 -7
- package/lib/index.js +12 -10
- package/lib/mcp/inProcessTransport.js +3 -4
- package/lib/mcp/proxyBackend.js +43 -28
- package/lib/mcp/server.js +24 -19
- package/lib/mcp/tool.js +14 -8
- package/lib/mcp/transport.js +60 -53
- package/lib/playwrightTransformer.js +129 -106
- package/lib/program.js +54 -52
- package/lib/response.js +36 -30
- package/lib/sessionLog.js +19 -17
- package/lib/tab.js +41 -39
- package/lib/tools/common.js +19 -19
- package/lib/tools/console.js +11 -11
- package/lib/tools/dialogs.js +18 -15
- package/lib/tools/evaluate.js +26 -17
- package/lib/tools/extractFrameworkState.js +48 -37
- package/lib/tools/files.js +17 -14
- package/lib/tools/form.js +32 -23
- package/lib/tools/getSnapshot.js +14 -15
- package/lib/tools/getVisibleHtml.js +33 -17
- package/lib/tools/install.js +20 -20
- package/lib/tools/keyboard.js +29 -24
- package/lib/tools/mouse.js +29 -31
- package/lib/tools/navigate.js +19 -23
- package/lib/tools/network.js +12 -14
- package/lib/tools/networkDetail.js +68 -61
- package/lib/tools/networkSearch/bodySearch.js +46 -32
- package/lib/tools/networkSearch/grouping.js +15 -6
- package/lib/tools/networkSearch/helpers.js +4 -4
- package/lib/tools/networkSearch/searchHtml.js +25 -16
- package/lib/tools/networkSearch/urlSearch.js +56 -14
- package/lib/tools/networkSearch.js +65 -35
- package/lib/tools/pdf.js +13 -12
- package/lib/tools/repl.js +66 -54
- package/lib/tools/screenshot.js +57 -33
- package/lib/tools/scroll.js +29 -24
- package/lib/tools/snapshot.js +66 -49
- package/lib/tools/tabs.js +22 -19
- package/lib/tools/tool.js +5 -3
- package/lib/tools/utils.js +17 -13
- package/lib/tools/wait.js +24 -19
- package/lib/tools.js +21 -20
- package/lib/utils/adBlockFilter.js +29 -26
- package/lib/utils/codegen.js +20 -16
- package/lib/utils/extensionPath.js +4 -4
- package/lib/utils/fileUtils.js +17 -13
- package/lib/utils/graphql.js +69 -58
- package/lib/utils/guid.js +3 -3
- package/lib/utils/httpServer.js +9 -9
- package/lib/utils/log.js +3 -3
- package/lib/utils/manualPromise.js +7 -7
- package/lib/utils/networkFormat.js +7 -5
- package/lib/utils/package.js +4 -4
- package/lib/utils/sanitizeHtml.js +66 -34
- package/lib/utils/truncate.js +25 -25
- package/lib/utils/withTimeout.js +1 -1
- package/package.json +34 -57
- package/src/index.ts +27 -17
- package/LICENSE +0 -202
package/lib/mcp/tool.js
CHANGED
|
@@ -13,18 +13,24 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
const typesWithIntent = [
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
18
|
+
const typesWithIntent = ["action", "assertion", "input"];
|
|
19
19
|
export function toMcpTool(tool, options) {
|
|
20
|
-
const inputSchema = options?.addIntent && typesWithIntent.includes(tool.type)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
const inputSchema = options?.addIntent && typesWithIntent.includes(tool.type)
|
|
21
|
+
? tool.inputSchema.extend({
|
|
22
|
+
intent: z
|
|
23
|
+
.string()
|
|
24
|
+
.describe("The intent of the call, for example the test step description plan idea"),
|
|
25
|
+
})
|
|
26
|
+
: tool.inputSchema;
|
|
27
|
+
const readOnly = tool.type === "readOnly" || tool.type === "assertion";
|
|
24
28
|
return {
|
|
25
29
|
name: tool.name,
|
|
26
30
|
description: tool.description,
|
|
27
|
-
inputSchema: zodToJsonSchema(inputSchema, {
|
|
31
|
+
inputSchema: zodToJsonSchema(inputSchema, {
|
|
32
|
+
strictUnions: true,
|
|
33
|
+
}),
|
|
28
34
|
annotations: {
|
|
29
35
|
title: tool.title,
|
|
30
36
|
readOnlyHint: readOnly,
|
package/lib/mcp/transport.js
CHANGED
|
@@ -13,19 +13,19 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import { StreamableHTTPServerTransport } from
|
|
20
|
-
import {
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
import { httpAddressToString, startHttpServer } from
|
|
26
|
-
import * as mcpServer from
|
|
16
|
+
import { pipe } from "@fxts/core";
|
|
17
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
18
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
20
|
+
import { isInitializeRequest, isJSONRPCRequest, LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, } from "@modelcontextprotocol/sdk/types.js";
|
|
21
|
+
import contentType from "content-type";
|
|
22
|
+
import crypto from "crypto";
|
|
23
|
+
import debug from "debug";
|
|
24
|
+
import getRawBody from "raw-body";
|
|
25
|
+
import { httpAddressToString, startHttpServer } from "../utils/httpServer.js";
|
|
26
|
+
import * as mcpServer from "./server.js";
|
|
27
27
|
// @see node_modules/@modelcontextprotocol/sdk/dist/esm/server/streamableHttp.js
|
|
28
|
-
const MAXIMUM_MESSAGE_SIZE =
|
|
28
|
+
const MAXIMUM_MESSAGE_SIZE = "4mb";
|
|
29
29
|
export async function start(serverBackendFactory, options) {
|
|
30
30
|
if (options.port !== undefined) {
|
|
31
31
|
const httpServer = await startHttpServer(options);
|
|
@@ -38,17 +38,17 @@ export async function start(serverBackendFactory, options) {
|
|
|
38
38
|
async function startStdioTransport(serverBackendFactory) {
|
|
39
39
|
await mcpServer.connect(serverBackendFactory, new StdioServerTransport(), false);
|
|
40
40
|
}
|
|
41
|
-
const testDebug = debug(
|
|
41
|
+
const testDebug = debug("pw:mcp:test");
|
|
42
42
|
async function handleStreamableReinitiate(req, res, transport, serverBackendFactory, sessionId, serverInfos) {
|
|
43
|
-
const ct = req.headers[
|
|
44
|
-
if (!ct || !ct.includes(
|
|
43
|
+
const ct = req.headers["content-type"];
|
|
44
|
+
if (!ct || !ct.includes("application/json")) {
|
|
45
45
|
res.writeHead(415).end(JSON.stringify({
|
|
46
|
-
jsonrpc:
|
|
46
|
+
jsonrpc: "2.0",
|
|
47
47
|
error: {
|
|
48
48
|
code: -32000,
|
|
49
|
-
message:
|
|
49
|
+
message: "Unsupported Media Type: Content-Type must be application/json",
|
|
50
50
|
},
|
|
51
|
-
id: null
|
|
51
|
+
id: null,
|
|
52
52
|
}));
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
@@ -57,28 +57,35 @@ async function handleStreamableReinitiate(req, res, transport, serverBackendFact
|
|
|
57
57
|
const body = await pipe(getRawBody(req, {
|
|
58
58
|
limit: MAXIMUM_MESSAGE_SIZE,
|
|
59
59
|
encoding,
|
|
60
|
-
}), raw => raw.toString(), JSON.parse);
|
|
61
|
-
const msg = Array.isArray(body)
|
|
60
|
+
}), (raw) => raw.toString(), JSON.parse);
|
|
61
|
+
const msg = Array.isArray(body)
|
|
62
|
+
? body.length === 1
|
|
63
|
+
? body[0]
|
|
64
|
+
: undefined
|
|
65
|
+
: body;
|
|
62
66
|
if (body && isJSONRPCRequest(msg) && isInitializeRequest(msg)) {
|
|
63
67
|
const requestedVersion = msg.params.protocolVersion;
|
|
64
|
-
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion)
|
|
68
|
+
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion)
|
|
69
|
+
? requestedVersion
|
|
70
|
+
: LATEST_PROTOCOL_VERSION;
|
|
65
71
|
const headers = {
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
"Content-Type": "text/event-stream",
|
|
73
|
+
"Mcp-Session-Id": transport.sessionId,
|
|
68
74
|
};
|
|
69
75
|
res.writeHead(200, headers);
|
|
70
|
-
const info = serverInfos.get(sessionId) ||
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
const info = serverInfos.get(sessionId) ||
|
|
77
|
+
(() => {
|
|
78
|
+
const backend = serverBackendFactory();
|
|
79
|
+
return { name: backend.name, version: backend.version };
|
|
80
|
+
})();
|
|
74
81
|
const response = {
|
|
75
|
-
jsonrpc:
|
|
82
|
+
jsonrpc: "2.0",
|
|
76
83
|
id: msg.id,
|
|
77
84
|
result: {
|
|
78
85
|
protocolVersion,
|
|
79
86
|
capabilities: { tools: {} },
|
|
80
87
|
serverInfo: info,
|
|
81
|
-
}
|
|
88
|
+
},
|
|
82
89
|
};
|
|
83
90
|
res.write(`event: message\n`);
|
|
84
91
|
res.write(`data: ${JSON.stringify(response)}\n\n`);
|
|
@@ -88,48 +95,48 @@ async function handleStreamableReinitiate(req, res, transport, serverBackendFact
|
|
|
88
95
|
return await transport.handleRequest(req, res, body);
|
|
89
96
|
}
|
|
90
97
|
async function handleSSE(serverBackendFactory, req, res, url, sessions) {
|
|
91
|
-
if (req.method ===
|
|
92
|
-
const sessionId = url.searchParams.get(
|
|
98
|
+
if (req.method === "POST") {
|
|
99
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
93
100
|
if (!sessionId) {
|
|
94
101
|
res.statusCode = 400;
|
|
95
|
-
return res.end(
|
|
102
|
+
return res.end("Missing sessionId");
|
|
96
103
|
}
|
|
97
104
|
const transport = sessions.get(sessionId);
|
|
98
105
|
if (!transport) {
|
|
99
106
|
res.statusCode = 404;
|
|
100
|
-
return res.end(
|
|
107
|
+
return res.end("Session not found");
|
|
101
108
|
}
|
|
102
109
|
return await transport.handlePostMessage(req, res);
|
|
103
110
|
}
|
|
104
|
-
else if (req.method ===
|
|
105
|
-
const transport = new SSEServerTransport(
|
|
111
|
+
else if (req.method === "GET") {
|
|
112
|
+
const transport = new SSEServerTransport("/sse", res);
|
|
106
113
|
sessions.set(transport.sessionId, transport);
|
|
107
114
|
testDebug(`create SSE session: ${transport.sessionId}`);
|
|
108
115
|
await mcpServer.connect(serverBackendFactory, transport, false);
|
|
109
|
-
res.on(
|
|
116
|
+
res.on("close", () => {
|
|
110
117
|
testDebug(`delete SSE session: ${transport.sessionId}`);
|
|
111
118
|
sessions.delete(transport.sessionId);
|
|
112
119
|
});
|
|
113
120
|
return;
|
|
114
121
|
}
|
|
115
122
|
res.statusCode = 405;
|
|
116
|
-
res.end(
|
|
123
|
+
res.end("Method not allowed");
|
|
117
124
|
}
|
|
118
125
|
// Streamable transport: 'initialize' handling per MCP Lifecycle (Initialization) https://modelcontextprotocol.io/specification/draft/basic/lifecycle#initialization
|
|
119
126
|
async function handleStreamable(serverBackendFactory, req, res, sessions, serverInfos) {
|
|
120
|
-
const sessionId = req.headers[
|
|
127
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
121
128
|
if (sessionId) {
|
|
122
129
|
const transport = sessions.get(sessionId);
|
|
123
130
|
if (!transport) {
|
|
124
131
|
res.statusCode = 404;
|
|
125
|
-
res.end(
|
|
132
|
+
res.end("Session not found");
|
|
126
133
|
return;
|
|
127
134
|
}
|
|
128
|
-
if (req.method ===
|
|
135
|
+
if (req.method === "POST")
|
|
129
136
|
return await handleStreamableReinitiate(req, res, transport, serverBackendFactory, sessionId, serverInfos);
|
|
130
137
|
return await transport.handleRequest(req, res);
|
|
131
138
|
}
|
|
132
|
-
if (req.method ===
|
|
139
|
+
if (req.method === "POST") {
|
|
133
140
|
const transport = new StreamableHTTPServerTransport({
|
|
134
141
|
sessionIdGenerator: () => crypto.randomUUID(),
|
|
135
142
|
onsessioninitialized: async (sessionId) => {
|
|
@@ -137,7 +144,7 @@ async function handleStreamable(serverBackendFactory, req, res, sessions, server
|
|
|
137
144
|
const serverInfo = await mcpServer.connect(serverBackendFactory, transport, false);
|
|
138
145
|
sessions.set(sessionId, transport);
|
|
139
146
|
serverInfos.set(sessionId, serverInfo);
|
|
140
|
-
}
|
|
147
|
+
},
|
|
141
148
|
});
|
|
142
149
|
transport.onclose = () => {
|
|
143
150
|
if (!transport.sessionId)
|
|
@@ -150,15 +157,15 @@ async function handleStreamable(serverBackendFactory, req, res, sessions, server
|
|
|
150
157
|
return;
|
|
151
158
|
}
|
|
152
159
|
res.statusCode = 400;
|
|
153
|
-
res.end(
|
|
160
|
+
res.end("Invalid request");
|
|
154
161
|
}
|
|
155
162
|
function startHttpTransport(httpServer, serverBackendFactory) {
|
|
156
163
|
const sseSessions = new Map();
|
|
157
164
|
const streamableSessions = new Map();
|
|
158
165
|
const streamableServerInfos = new Map();
|
|
159
|
-
httpServer.on(
|
|
166
|
+
httpServer.on("request", async (req, res) => {
|
|
160
167
|
const url = new URL(`http://localhost${req.url}`);
|
|
161
|
-
if (url.pathname.startsWith(
|
|
168
|
+
if (url.pathname.startsWith("/sse"))
|
|
162
169
|
await handleSSE(serverBackendFactory, req, res, url, sseSessions);
|
|
163
170
|
else
|
|
164
171
|
await handleStreamable(serverBackendFactory, req, res, streamableSessions, streamableServerInfos);
|
|
@@ -166,16 +173,16 @@ function startHttpTransport(httpServer, serverBackendFactory) {
|
|
|
166
173
|
const url = httpAddressToString(httpServer.address());
|
|
167
174
|
const message = [
|
|
168
175
|
`Listening on ${url}`,
|
|
169
|
-
|
|
176
|
+
"Put this in your client config:",
|
|
170
177
|
JSON.stringify({
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
}
|
|
178
|
+
mcpServers: {
|
|
179
|
+
playwright: {
|
|
180
|
+
url: `${url}/mcp`,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
176
183
|
}, undefined, 2),
|
|
177
|
-
|
|
178
|
-
].join(
|
|
184
|
+
"For legacy SSE transport support, you can use the /sse endpoint instead.",
|
|
185
|
+
].join("\n");
|
|
179
186
|
// eslint-disable-next-line no-console
|
|
180
187
|
console.error(message);
|
|
181
188
|
}
|