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