@tontoko/fast-playwright-mcp 0.0.8 → 0.1.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/README.md +277 -117
- package/lib/batch/batch-executor.js +4 -5
- package/lib/browser-context-factory.js +2 -4
- package/lib/browser-server-backend.js +5 -7
- package/lib/config.js +1 -1
- package/lib/context.js +1 -4
- package/lib/diagnostics/common/error-enrichment-utils.js +3 -2
- package/lib/diagnostics/common/index.js +4 -55
- package/lib/diagnostics/element-discovery.js +1 -2
- package/lib/diagnostics/frame-reference-manager.js +5 -6
- package/lib/diagnostics/resource-manager.js +1 -2
- package/lib/diagnostics/smart-config.js +5 -6
- package/lib/diagnostics/smart-handle.js +1 -2
- package/lib/extension/cdp-relay.js +32 -34
- package/lib/extension/extension-context-factory.js +4 -5
- package/lib/in-process-client.js +1 -1
- package/lib/loop/loop.js +5 -5
- package/lib/loopTools/main.js +1 -1
- package/lib/mcp/proxy-backend.js +2 -2
- package/lib/mcp/server.js +4 -6
- package/lib/{log.js → mcp/tool.js} +17 -11
- package/lib/mcp/transport.js +2 -4
- package/lib/program.js +2 -3
- package/lib/response.js +1 -2
- package/lib/session-log.js +1 -1
- package/lib/tab.js +2 -4
- package/lib/tools/diagnose/diagnose-config-handler.js +2 -3
- package/lib/tools/evaluate.js +1 -1
- package/lib/tools/keyboard.js +1 -1
- package/lib/tools/network.js +97 -6
- package/lib/tools/pdf.js +1 -1
- package/lib/tools/screenshot.js +1 -1
- package/lib/tools/snapshot.js +1 -1
- package/lib/tools/utils.js +6 -7
- package/lib/{javascript.js → utils/codegen.js} +1 -1
- package/lib/utils/common-formatters.js +2 -3
- package/lib/utils/error-handler-middleware.js +1 -2
- package/lib/{utils.js → utils/guid.js} +1 -1
- package/lib/utils/index.js +6 -0
- package/lib/utils/log.js +90 -0
- package/lib/utils/network-filter.js +114 -0
- package/lib/{package.js → utils/package.js} +2 -2
- package/lib/utils/request-logger.js +1 -1
- package/package.json +3 -3
|
@@ -1,56 +1,5 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
-
for (let key of __getOwnPropNames(mod))
|
|
11
|
-
if (!__hasOwnProp.call(to, key))
|
|
12
|
-
__defProp(to, key, {
|
|
13
|
-
get: () => mod[key],
|
|
14
|
-
enumerable: true
|
|
15
|
-
});
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
-
|
|
20
1
|
// src/diagnostics/common/index.ts
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
analyzeErrorPatterns,
|
|
26
|
-
createEnrichedError,
|
|
27
|
-
generateRecoverySuggestions,
|
|
28
|
-
generateSuggestions,
|
|
29
|
-
safeDispose,
|
|
30
|
-
safeDisposeAll
|
|
31
|
-
} from "./error-enrichment-utils.js";
|
|
32
|
-
import {
|
|
33
|
-
createAdvancedStage,
|
|
34
|
-
createCoreStage,
|
|
35
|
-
createDependentStage,
|
|
36
|
-
InitializationManager
|
|
37
|
-
} from "./initialization-manager.js";
|
|
38
|
-
import {
|
|
39
|
-
globalPerformanceTracker,
|
|
40
|
-
PerformanceTracker
|
|
41
|
-
} from "./performance-tracker.js";
|
|
42
|
-
export {
|
|
43
|
-
safeDisposeAll,
|
|
44
|
-
safeDispose,
|
|
45
|
-
globalPerformanceTracker,
|
|
46
|
-
generateSuggestions,
|
|
47
|
-
generateRecoverySuggestions,
|
|
48
|
-
createEnrichedError,
|
|
49
|
-
createDependentStage,
|
|
50
|
-
createCoreStage,
|
|
51
|
-
createAdvancedStage,
|
|
52
|
-
analyzeErrorPatterns,
|
|
53
|
-
PerformanceTracker,
|
|
54
|
-
InitializationManager,
|
|
55
|
-
DiagnosticBase
|
|
56
|
-
};
|
|
2
|
+
export * from "./diagnostic-base.js";
|
|
3
|
+
export * from "./error-enrichment-utils.js";
|
|
4
|
+
export * from "./initialization-manager.js";
|
|
5
|
+
export * from "./performance-tracker.js";
|
|
@@ -18,11 +18,10 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/diagnostics/element-discovery.ts
|
|
21
|
-
import
|
|
21
|
+
import { elementDiscoveryDebug } from "../utils/log.js";
|
|
22
22
|
import { DiagnosticBase } from "./common/diagnostic-base.js";
|
|
23
23
|
import { safeDispose } from "./common/error-enrichment-utils.js";
|
|
24
24
|
import { SmartHandleBatch } from "./smart-handle.js";
|
|
25
|
-
var elementDiscoveryDebug = debug("pw:mcp:element-discovery");
|
|
26
25
|
class ElementDiscovery extends DiagnosticBase {
|
|
27
26
|
smartHandleBatch;
|
|
28
27
|
maxBatchSize = 100;
|
|
@@ -18,8 +18,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/diagnostics/frame-reference-manager.ts
|
|
21
|
-
import
|
|
22
|
-
var frameDebug = debug("pw:mcp:frame");
|
|
21
|
+
import { frameReferenceDebug } from "../utils/log.js";
|
|
23
22
|
|
|
24
23
|
class FrameReferenceManager {
|
|
25
24
|
frameRefs = new WeakMap;
|
|
@@ -44,7 +43,7 @@ class FrameReferenceManager {
|
|
|
44
43
|
this.frameRefs.set(frame, metadata);
|
|
45
44
|
this.activeFrames.add(frame);
|
|
46
45
|
} catch (error) {
|
|
47
|
-
|
|
46
|
+
frameReferenceDebug("Frame tracking failed (frame might be detached):", {
|
|
48
47
|
error: error instanceof Error ? error.message : "Unknown error"
|
|
49
48
|
});
|
|
50
49
|
}
|
|
@@ -81,9 +80,9 @@ class FrameReferenceManager {
|
|
|
81
80
|
try {
|
|
82
81
|
frameUrl = frame.url();
|
|
83
82
|
} catch (urlError) {
|
|
84
|
-
|
|
83
|
+
frameReferenceDebug("Could not retrieve frame URL:", urlError);
|
|
85
84
|
}
|
|
86
|
-
|
|
85
|
+
frameReferenceDebug("Frame accessibility check failed (frame likely detached):", {
|
|
87
86
|
url: frameUrl,
|
|
88
87
|
error: error instanceof Error ? error.message : "Unknown error"
|
|
89
88
|
});
|
|
@@ -157,7 +156,7 @@ class FrameReferenceManager {
|
|
|
157
156
|
startCleanupTimer() {
|
|
158
157
|
this.cleanupInterval = setInterval(() => {
|
|
159
158
|
this.cleanupDetachedFrames().catch((error) => {
|
|
160
|
-
|
|
159
|
+
frameReferenceDebug("Frame cleanup timer failed:", error instanceof Error ? error.message : "Unknown error");
|
|
161
160
|
});
|
|
162
161
|
}, 30000);
|
|
163
162
|
}
|
|
@@ -18,8 +18,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/diagnostics/resource-manager.ts
|
|
21
|
-
import
|
|
22
|
-
var resourceDebug = debug("pw:mcp:resource");
|
|
21
|
+
import { resourceDebug } from "../utils/log.js";
|
|
23
22
|
|
|
24
23
|
class ResourceManager {
|
|
25
24
|
resources = new Map;
|
|
@@ -18,10 +18,9 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/diagnostics/smart-config.ts
|
|
21
|
-
import
|
|
21
|
+
import { smartConfigDebug } from "../utils/log.js";
|
|
22
22
|
import { DiagnosticLevel } from "./diagnostic-level.js";
|
|
23
23
|
import { DiagnosticThresholds } from "./diagnostic-thresholds.js";
|
|
24
|
-
var configDebug = debug("pw:mcp:config");
|
|
25
24
|
|
|
26
25
|
class SmartConfigManager {
|
|
27
26
|
static instance;
|
|
@@ -38,7 +37,7 @@ class SmartConfigManager {
|
|
|
38
37
|
static getInstance(initialConfig) {
|
|
39
38
|
SmartConfigManager.instance ??= new SmartConfigManager(initialConfig);
|
|
40
39
|
if (!SmartConfigManager.instance) {
|
|
41
|
-
|
|
40
|
+
smartConfigDebug("Critical: SmartConfigManager instance is null after creation");
|
|
42
41
|
SmartConfigManager.instance = new SmartConfigManager(initialConfig);
|
|
43
42
|
}
|
|
44
43
|
return SmartConfigManager.instance;
|
|
@@ -46,7 +45,7 @@ class SmartConfigManager {
|
|
|
46
45
|
static resetInstance() {
|
|
47
46
|
const wasNull = SmartConfigManager.instance === null;
|
|
48
47
|
SmartConfigManager.instance = null;
|
|
49
|
-
|
|
48
|
+
smartConfigDebug("SmartConfigManager instance reset", {
|
|
50
49
|
wasAlreadyNull: wasNull
|
|
51
50
|
});
|
|
52
51
|
}
|
|
@@ -136,7 +135,7 @@ class SmartConfigManager {
|
|
|
136
135
|
try {
|
|
137
136
|
listener(this.config);
|
|
138
137
|
} catch (error) {
|
|
139
|
-
|
|
138
|
+
smartConfigDebug("Config change listener failed:", error);
|
|
140
139
|
}
|
|
141
140
|
}
|
|
142
141
|
}
|
|
@@ -229,7 +228,7 @@ class SmartConfigManager {
|
|
|
229
228
|
};
|
|
230
229
|
this.thresholdsManager.updateThresholds(thresholdsConfig);
|
|
231
230
|
} catch (error) {
|
|
232
|
-
|
|
231
|
+
smartConfigDebug("Failed to sync thresholds:", error);
|
|
233
232
|
}
|
|
234
233
|
}
|
|
235
234
|
getThresholdsFromManager() {
|
|
@@ -18,11 +18,10 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/diagnostics/smart-handle.ts
|
|
21
|
-
import
|
|
21
|
+
import { smartHandleDebug } from "../utils/log.js";
|
|
22
22
|
import {
|
|
23
23
|
globalResourceManager
|
|
24
24
|
} from "./resource-manager.js";
|
|
25
|
-
var smartHandleDebug = debug("pw:mcp:smart-handle");
|
|
26
25
|
|
|
27
26
|
class SmartHandle {
|
|
28
27
|
disposed = false;
|
|
@@ -19,13 +19,11 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
19
19
|
|
|
20
20
|
// src/extension/cdp-relay.ts
|
|
21
21
|
import { spawn } from "node:child_process";
|
|
22
|
-
import debug from "debug";
|
|
23
22
|
import { WebSocket, WebSocketServer } from "ws";
|
|
24
23
|
import { httpAddressToString } from "../http-server.js";
|
|
25
|
-
import { logUnhandledError } from "../log.js";
|
|
26
24
|
import { ManualPromise } from "../manual-promise.js";
|
|
25
|
+
import { cdpRelayDebug, logUnhandledError } from "../utils/log.js";
|
|
27
26
|
var { registry } = await import("playwright-core/lib/server/registry/index");
|
|
28
|
-
var debugLogger = debug("pw:mcp:relay");
|
|
29
27
|
var HTTP_TO_WS_REGEX = /^http/;
|
|
30
28
|
var EXTENSION_ID_REGEX = /^[a-p]{32}$/;
|
|
31
29
|
var DANGEROUS_PATH_PATTERNS = [
|
|
@@ -64,17 +62,17 @@ class CDPRelayServer {
|
|
|
64
62
|
return `${this._wsHost}${this._extensionPath}`;
|
|
65
63
|
}
|
|
66
64
|
async ensureExtensionConnectionForMCPContext(clientInfo, abortSignal) {
|
|
67
|
-
|
|
65
|
+
cdpRelayDebug("Ensuring extension connection for MCP context");
|
|
68
66
|
if (this._extensionConnection) {
|
|
69
67
|
return;
|
|
70
68
|
}
|
|
71
69
|
this._connectBrowser(clientInfo);
|
|
72
|
-
|
|
70
|
+
cdpRelayDebug("Waiting for incoming extension connection");
|
|
73
71
|
await Promise.race([
|
|
74
72
|
this._extensionConnectionPromise,
|
|
75
73
|
new Promise((_, reject) => abortSignal.addEventListener("abort", reject))
|
|
76
74
|
]);
|
|
77
|
-
|
|
75
|
+
cdpRelayDebug("Extension connection established");
|
|
78
76
|
}
|
|
79
77
|
_connectBrowser(clientInfo) {
|
|
80
78
|
const mcpRelayEndpoint = `${this._wsHost}${this._extensionPath}`;
|
|
@@ -127,7 +125,7 @@ class CDPRelayServer {
|
|
|
127
125
|
_safeJsonParse(jsonString) {
|
|
128
126
|
try {
|
|
129
127
|
if (jsonString.includes("__proto__") || jsonString.includes("constructor") || jsonString.includes("prototype")) {
|
|
130
|
-
|
|
128
|
+
cdpRelayDebug("Potential prototype pollution attempt detected");
|
|
131
129
|
return null;
|
|
132
130
|
}
|
|
133
131
|
const result = JSON.parse(jsonString);
|
|
@@ -137,7 +135,7 @@ class CDPRelayServer {
|
|
|
137
135
|
this._sanitizeObject(result);
|
|
138
136
|
return result;
|
|
139
137
|
} catch (error) {
|
|
140
|
-
|
|
138
|
+
cdpRelayDebug("JSON parsing failed:", error);
|
|
141
139
|
return null;
|
|
142
140
|
}
|
|
143
141
|
}
|
|
@@ -173,19 +171,19 @@ class CDPRelayServer {
|
|
|
173
171
|
}
|
|
174
172
|
_onConnection(ws, request) {
|
|
175
173
|
const url = new URL(`http://localhost${request.url}`);
|
|
176
|
-
|
|
174
|
+
cdpRelayDebug(`New connection to ${url.pathname}`);
|
|
177
175
|
if (url.pathname === this._cdpPath) {
|
|
178
176
|
this._handlePlaywrightConnection(ws);
|
|
179
177
|
} else if (url.pathname === this._extensionPath) {
|
|
180
178
|
this._handleExtensionConnection(ws);
|
|
181
179
|
} else {
|
|
182
|
-
|
|
180
|
+
cdpRelayDebug(`Invalid path: ${url.pathname}`);
|
|
183
181
|
ws.close(4004, "Invalid path");
|
|
184
182
|
}
|
|
185
183
|
}
|
|
186
184
|
_handlePlaywrightConnection(ws) {
|
|
187
185
|
if (this._playwrightConnection) {
|
|
188
|
-
|
|
186
|
+
cdpRelayDebug("Rejecting second Playwright connection");
|
|
189
187
|
ws.close(1000, "Another CDP client already connected");
|
|
190
188
|
return;
|
|
191
189
|
}
|
|
@@ -194,18 +192,18 @@ class CDPRelayServer {
|
|
|
194
192
|
try {
|
|
195
193
|
const messageString = data.toString();
|
|
196
194
|
if (messageString.length > 1048576) {
|
|
197
|
-
|
|
195
|
+
cdpRelayDebug("Message too large, rejecting");
|
|
198
196
|
return;
|
|
199
197
|
}
|
|
200
198
|
const message = this._safeJsonParse(messageString);
|
|
201
199
|
if (message === null) {
|
|
202
|
-
|
|
200
|
+
cdpRelayDebug("Invalid JSON message received from Playwright");
|
|
203
201
|
return;
|
|
204
202
|
}
|
|
205
203
|
await this._handlePlaywrightMessage(message);
|
|
206
204
|
} catch (error) {
|
|
207
205
|
const truncatedData = String(data).slice(0, 500);
|
|
208
|
-
|
|
206
|
+
cdpRelayDebug(`Error while handling Playwright message
|
|
209
207
|
${truncatedData}...
|
|
210
208
|
`, error);
|
|
211
209
|
}
|
|
@@ -216,12 +214,12 @@ ${truncatedData}...
|
|
|
216
214
|
}
|
|
217
215
|
this._playwrightConnection = null;
|
|
218
216
|
this._closeExtensionConnection("Playwright client disconnected");
|
|
219
|
-
|
|
217
|
+
cdpRelayDebug("Playwright WebSocket closed");
|
|
220
218
|
});
|
|
221
219
|
ws.on("error", (error) => {
|
|
222
|
-
|
|
220
|
+
cdpRelayDebug("Playwright WebSocket error:", error);
|
|
223
221
|
});
|
|
224
|
-
|
|
222
|
+
cdpRelayDebug("Playwright MCP connected");
|
|
225
223
|
}
|
|
226
224
|
_closeExtensionConnection(reason) {
|
|
227
225
|
this._extensionConnection?.close(reason);
|
|
@@ -247,7 +245,7 @@ ${truncatedData}...
|
|
|
247
245
|
}
|
|
248
246
|
this._extensionConnection = new ExtensionConnection(ws);
|
|
249
247
|
this._extensionConnection.onclose = (c, reason) => {
|
|
250
|
-
|
|
248
|
+
cdpRelayDebug("Extension WebSocket closed:", reason, c === this._extensionConnection);
|
|
251
249
|
if (this._extensionConnection !== c) {
|
|
252
250
|
return;
|
|
253
251
|
}
|
|
@@ -269,26 +267,26 @@ ${truncatedData}...
|
|
|
269
267
|
break;
|
|
270
268
|
}
|
|
271
269
|
case "detachedFromTab":
|
|
272
|
-
|
|
270
|
+
cdpRelayDebug("← Debugger detached from tab:", params);
|
|
273
271
|
this._connectedTabInfo = undefined;
|
|
274
272
|
break;
|
|
275
273
|
default:
|
|
276
|
-
|
|
274
|
+
cdpRelayDebug(`← Extension: unhandled method ${method}`, params);
|
|
277
275
|
break;
|
|
278
276
|
}
|
|
279
277
|
}
|
|
280
278
|
async _handlePlaywrightMessage(message) {
|
|
281
279
|
if (!this._isValidCDPCommand(message)) {
|
|
282
|
-
|
|
280
|
+
cdpRelayDebug("Invalid CDP command received from Playwright");
|
|
283
281
|
return;
|
|
284
282
|
}
|
|
285
|
-
|
|
283
|
+
cdpRelayDebug("← Playwright:", `${message.method} (id=${message.id})`);
|
|
286
284
|
const { id, sessionId, method, params } = message;
|
|
287
285
|
try {
|
|
288
286
|
const result = await this._handleCDPCommand(method, params, sessionId);
|
|
289
287
|
this._sendToPlaywright({ id, sessionId, result });
|
|
290
288
|
} catch (e) {
|
|
291
|
-
|
|
289
|
+
cdpRelayDebug("Error in the extension:", e);
|
|
292
290
|
this._sendToPlaywright({
|
|
293
291
|
id,
|
|
294
292
|
sessionId,
|
|
@@ -319,7 +317,7 @@ ${truncatedData}...
|
|
|
319
317
|
targetInfo,
|
|
320
318
|
sessionId: `pw-tab-${this._nextSessionId++}`
|
|
321
319
|
};
|
|
322
|
-
|
|
320
|
+
cdpRelayDebug("Simulating auto-attach");
|
|
323
321
|
this._sendToPlaywright({
|
|
324
322
|
method: "Target.attachedToTarget",
|
|
325
323
|
params: {
|
|
@@ -358,7 +356,7 @@ ${truncatedData}...
|
|
|
358
356
|
}
|
|
359
357
|
_sendToPlaywright(message) {
|
|
360
358
|
const messageDesc = message.method ?? `response(id=${message.id})`;
|
|
361
|
-
|
|
359
|
+
cdpRelayDebug("→ Playwright:", messageDesc);
|
|
362
360
|
this._playwrightConnection?.send(JSON.stringify(message));
|
|
363
361
|
}
|
|
364
362
|
}
|
|
@@ -387,7 +385,7 @@ class ExtensionConnection {
|
|
|
387
385
|
});
|
|
388
386
|
}
|
|
389
387
|
close(message) {
|
|
390
|
-
|
|
388
|
+
cdpRelayDebug("closing extension connection:", message);
|
|
391
389
|
if (this._ws.readyState === WebSocket.OPEN) {
|
|
392
390
|
this._ws.close(1000, message);
|
|
393
391
|
}
|
|
@@ -395,7 +393,7 @@ class ExtensionConnection {
|
|
|
395
393
|
_parseJsonSafely(jsonString) {
|
|
396
394
|
try {
|
|
397
395
|
if (jsonString.includes("__proto__") || jsonString.includes("constructor") || jsonString.includes("prototype")) {
|
|
398
|
-
|
|
396
|
+
cdpRelayDebug("Potential prototype pollution attempt detected");
|
|
399
397
|
return null;
|
|
400
398
|
}
|
|
401
399
|
const result = JSON.parse(jsonString);
|
|
@@ -405,7 +403,7 @@ class ExtensionConnection {
|
|
|
405
403
|
this._sanitizeJsonObject(result);
|
|
406
404
|
return result;
|
|
407
405
|
} catch (error) {
|
|
408
|
-
|
|
406
|
+
cdpRelayDebug("JSON parsing failed:", error);
|
|
409
407
|
return null;
|
|
410
408
|
}
|
|
411
409
|
}
|
|
@@ -427,13 +425,13 @@ class ExtensionConnection {
|
|
|
427
425
|
_onMessage(event) {
|
|
428
426
|
const eventData = event.toString();
|
|
429
427
|
if (eventData.length > 1048576) {
|
|
430
|
-
|
|
428
|
+
cdpRelayDebug("<closing ws> Message too large, closing websocket");
|
|
431
429
|
this._ws.close();
|
|
432
430
|
return;
|
|
433
431
|
}
|
|
434
432
|
const parsedJson = this._parseJsonSafely(eventData);
|
|
435
433
|
if (parsedJson === null) {
|
|
436
|
-
|
|
434
|
+
cdpRelayDebug(`<closing ws> Closing websocket due to malformed JSON. eventData=${eventData.slice(0, 200)}...`);
|
|
437
435
|
this._ws.close();
|
|
438
436
|
return;
|
|
439
437
|
}
|
|
@@ -441,7 +439,7 @@ class ExtensionConnection {
|
|
|
441
439
|
this._handleParsedMessage(parsedJson);
|
|
442
440
|
} catch (e) {
|
|
443
441
|
const errorMessage = e?.message;
|
|
444
|
-
|
|
442
|
+
cdpRelayDebug(`<closing ws> Closing websocket due to failed onmessage callback. eventData=${eventData} e=${errorMessage}`);
|
|
445
443
|
this._ws.close();
|
|
446
444
|
}
|
|
447
445
|
}
|
|
@@ -460,18 +458,18 @@ class ExtensionConnection {
|
|
|
460
458
|
callback.resolve(object.result);
|
|
461
459
|
}
|
|
462
460
|
} else if (object.id) {
|
|
463
|
-
|
|
461
|
+
cdpRelayDebug("← Extension: unexpected response", object);
|
|
464
462
|
} else if (object.method) {
|
|
465
463
|
this.onmessage?.(object.method, object.params ?? {});
|
|
466
464
|
}
|
|
467
465
|
}
|
|
468
466
|
_onClose(event) {
|
|
469
|
-
|
|
467
|
+
cdpRelayDebug(`<ws closed> code=${event.code} reason=${event.reason}`);
|
|
470
468
|
this._dispose();
|
|
471
469
|
this.onclose?.(this, event.reason);
|
|
472
470
|
}
|
|
473
471
|
_onError(event) {
|
|
474
|
-
|
|
472
|
+
cdpRelayDebug(`<ws error> message=${event.message} type=${event.type} target=${String(event.target)}`);
|
|
475
473
|
this._dispose();
|
|
476
474
|
}
|
|
477
475
|
_dispose() {
|
|
@@ -18,11 +18,10 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/extension/extension-context-factory.ts
|
|
21
|
-
import debug from "debug";
|
|
22
21
|
import { chromium } from "playwright";
|
|
23
22
|
import { startHttpServer } from "../http-server.js";
|
|
23
|
+
import { extensionContextFactoryDebug } from "../utils/log.js";
|
|
24
24
|
import { CDPRelayServer } from "./cdp-relay.js";
|
|
25
|
-
var debugLogger = debug("pw:mcp:relay");
|
|
26
25
|
|
|
27
26
|
class ExtensionContextFactory {
|
|
28
27
|
name = "extension";
|
|
@@ -39,7 +38,7 @@ class ExtensionContextFactory {
|
|
|
39
38
|
return {
|
|
40
39
|
browserContext: browser.contexts()[0],
|
|
41
40
|
close: async () => {
|
|
42
|
-
|
|
41
|
+
extensionContextFactoryDebug("close() called for browser context");
|
|
43
42
|
await browser.close();
|
|
44
43
|
this._browserPromise = undefined;
|
|
45
44
|
}
|
|
@@ -53,14 +52,14 @@ class ExtensionContextFactory {
|
|
|
53
52
|
const browser = await chromium.connectOverCDP(relay.cdpEndpoint());
|
|
54
53
|
browser.on("disconnected", () => {
|
|
55
54
|
this._browserPromise = undefined;
|
|
56
|
-
|
|
55
|
+
extensionContextFactoryDebug("Browser disconnected");
|
|
57
56
|
});
|
|
58
57
|
return browser;
|
|
59
58
|
}
|
|
60
59
|
async _startRelay(abortSignal) {
|
|
61
60
|
const httpServer = await startHttpServer({});
|
|
62
61
|
const cdpRelayServer = new CDPRelayServer(httpServer, this._browserChannel);
|
|
63
|
-
|
|
62
|
+
extensionContextFactoryDebug(`CDP relay server started, extension endpoint: ${cdpRelayServer.extensionEndpoint()}.`);
|
|
64
63
|
if (abortSignal.aborted) {
|
|
65
64
|
cdpRelayServer.stop();
|
|
66
65
|
} else {
|
package/lib/in-process-client.js
CHANGED
|
@@ -23,7 +23,7 @@ import { ListRootsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
|
23
23
|
import { BrowserServerBackend } from "./browser-server-backend.js";
|
|
24
24
|
import { InProcessTransport } from "./mcp/in-process-transport.js";
|
|
25
25
|
import { createServer } from "./mcp/server.js";
|
|
26
|
-
import { packageJSON } from "./package.js";
|
|
26
|
+
import { packageJSON } from "./utils/package.js";
|
|
27
27
|
|
|
28
28
|
class InProcessClientFactory {
|
|
29
29
|
name;
|
package/lib/loop/loop.js
CHANGED
|
@@ -18,8 +18,8 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/loop/loop.ts
|
|
21
|
-
import debug from "debug";
|
|
22
21
|
import { getErrorMessage } from "../utils/common-formatters.js";
|
|
22
|
+
import { historyDebug, toolDebug } from "../utils/log.js";
|
|
23
23
|
async function runTask(delegate, client, task, oneShot = false) {
|
|
24
24
|
const { tools } = await client.listTools();
|
|
25
25
|
const taskContent = createTaskContent(task, oneShot);
|
|
@@ -44,7 +44,7 @@ function shouldTerminateLoop(result, oneShot) {
|
|
|
44
44
|
return result.isDone || oneShot;
|
|
45
45
|
}
|
|
46
46
|
async function executeIteration(delegate, client, conversation, iteration) {
|
|
47
|
-
|
|
47
|
+
historyDebug("Making API call for iteration", iteration);
|
|
48
48
|
const toolCalls = await delegate.makeApiCall(conversation);
|
|
49
49
|
validateToolCallsPresent(toolCalls);
|
|
50
50
|
const { toolResults, isDone } = await processToolCalls(delegate, client, toolCalls);
|
|
@@ -138,14 +138,14 @@ function shouldBreakOnError(result, toolCalls, currentToolCall, toolResults) {
|
|
|
138
138
|
async function executeToolCall(client, toolCall) {
|
|
139
139
|
const { name, arguments: args, id } = toolCall;
|
|
140
140
|
try {
|
|
141
|
-
|
|
141
|
+
toolDebug(name, args);
|
|
142
142
|
const response = await client.callTool({ name, arguments: args });
|
|
143
143
|
const responseContent = response.content ?? [];
|
|
144
|
-
|
|
144
|
+
toolDebug(responseContent);
|
|
145
145
|
const text = extractTextFromResponse(responseContent);
|
|
146
146
|
return { toolCallId: id, content: text };
|
|
147
147
|
} catch (error) {
|
|
148
|
-
|
|
148
|
+
toolDebug(error);
|
|
149
149
|
return {
|
|
150
150
|
toolCallId: id,
|
|
151
151
|
content: `Error while executing tool "${name}": ${getErrorMessage(error)}
|
package/lib/loopTools/main.js
CHANGED
|
@@ -20,7 +20,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
20
20
|
// src/loopTools/main.ts
|
|
21
21
|
import dotenv from "dotenv";
|
|
22
22
|
import { start } from "../mcp/transport.js";
|
|
23
|
-
import { packageJSON } from "../package.js";
|
|
23
|
+
import { packageJSON } from "../utils/package.js";
|
|
24
24
|
import { Context } from "./context.js";
|
|
25
25
|
import { perform } from "./perform.js";
|
|
26
26
|
import { snapshot } from "./snapshot.js";
|
package/lib/mcp/proxy-backend.js
CHANGED
|
@@ -19,9 +19,9 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
19
19
|
|
|
20
20
|
// src/mcp/proxy-backend.ts
|
|
21
21
|
import { z } from "zod";
|
|
22
|
-
import { logUnhandledError } from "../log.js";
|
|
23
|
-
import { packageJSON } from "../package.js";
|
|
24
22
|
import { defineTool } from "../tools/tool.js";
|
|
23
|
+
import { logUnhandledError } from "../utils/log.js";
|
|
24
|
+
import { packageJSON } from "../utils/package.js";
|
|
25
25
|
|
|
26
26
|
class ProxyBackend {
|
|
27
27
|
name = "Playwright MCP Client Switcher";
|
package/lib/mcp/server.js
CHANGED
|
@@ -23,13 +23,11 @@ import {
|
|
|
23
23
|
CallToolRequestSchema,
|
|
24
24
|
ListToolsRequestSchema
|
|
25
25
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
26
|
-
import debug from "debug";
|
|
27
26
|
import { z } from "zod";
|
|
28
27
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
29
|
-
import { logUnhandledError } from "../log.js";
|
|
30
28
|
import { ManualPromise } from "../manual-promise.js";
|
|
29
|
+
import { logUnhandledError, mcpServerDebug } from "../utils/log.js";
|
|
31
30
|
import { logRequest } from "../utils/request-logger.js";
|
|
32
|
-
var serverDebug = debug("pw:mcp:server");
|
|
33
31
|
async function connect(serverBackendFactory, transport, runHeartbeat) {
|
|
34
32
|
const backend = serverBackendFactory();
|
|
35
33
|
const server = createServer(backend, runHeartbeat);
|
|
@@ -98,16 +96,16 @@ var startHeartbeat = (server) => {
|
|
|
98
96
|
]);
|
|
99
97
|
setTimeout(beat, 3000);
|
|
100
98
|
} catch (error) {
|
|
101
|
-
|
|
99
|
+
mcpServerDebug("Heartbeat ping failed:", error);
|
|
102
100
|
try {
|
|
103
101
|
await server.close();
|
|
104
102
|
} catch (closeError) {
|
|
105
|
-
|
|
103
|
+
mcpServerDebug("Failed to close server after heartbeat failure:", closeError);
|
|
106
104
|
}
|
|
107
105
|
}
|
|
108
106
|
};
|
|
109
107
|
beat().catch((error) => {
|
|
110
|
-
|
|
108
|
+
mcpServerDebug("Heartbeat initialization failed:", error);
|
|
111
109
|
});
|
|
112
110
|
};
|
|
113
111
|
function addServerListener(server, event, listener) {
|
|
@@ -17,17 +17,23 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
17
17
|
};
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
|
-
// src/
|
|
21
|
-
import
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
// src/mcp/tool.ts
|
|
21
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
22
|
+
function toMcpTool(tool) {
|
|
23
|
+
return {
|
|
24
|
+
name: tool.name,
|
|
25
|
+
description: tool.description,
|
|
26
|
+
inputSchema: zodToJsonSchema(tool.inputSchema, {
|
|
27
|
+
strictUnions: true
|
|
28
|
+
}),
|
|
29
|
+
annotations: {
|
|
30
|
+
title: tool.title,
|
|
31
|
+
readOnlyHint: tool.type === "readOnly",
|
|
32
|
+
destructiveHint: tool.type === "destructive",
|
|
33
|
+
openWorldHint: true
|
|
34
|
+
}
|
|
35
|
+
};
|
|
26
36
|
}
|
|
27
|
-
var testDebug = debug("pw:mcp:test");
|
|
28
|
-
var requestDebug = requestsDebug;
|
|
29
37
|
export {
|
|
30
|
-
|
|
31
|
-
requestDebug,
|
|
32
|
-
logUnhandledError
|
|
38
|
+
toMcpTool
|
|
33
39
|
};
|
package/lib/mcp/transport.js
CHANGED
|
@@ -22,9 +22,9 @@ import crypto from "node:crypto";
|
|
|
22
22
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
23
23
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24
24
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
25
|
-
import debug from "debug";
|
|
26
25
|
import { httpAddressToString, startHttpServer } from "../http-server.js";
|
|
27
26
|
import { connect } from "./server.js";
|
|
27
|
+
import { mcpTransportDebug, testDebug } from "../utils/log.js";
|
|
28
28
|
async function start(serverBackendFactory, options) {
|
|
29
29
|
if (options.port !== undefined) {
|
|
30
30
|
const httpServer = await startHttpServer(options);
|
|
@@ -36,8 +36,6 @@ async function start(serverBackendFactory, options) {
|
|
|
36
36
|
async function startStdioTransport(serverBackendFactory) {
|
|
37
37
|
await connect(serverBackendFactory, new StdioServerTransport, false);
|
|
38
38
|
}
|
|
39
|
-
var testDebug = debug("pw:mcp:test");
|
|
40
|
-
var transportDebug = debug("pw:mcp:transport");
|
|
41
39
|
async function handleSSE(serverBackendFactory, req, res, url, sessions) {
|
|
42
40
|
if (req.method === "POST") {
|
|
43
41
|
const sessionId = url.searchParams.get("sessionId");
|
|
@@ -152,7 +150,7 @@ function startHttpTransport(httpServer, serverBackendFactory) {
|
|
|
152
150
|
"For legacy SSE transport support, you can use the /sse endpoint instead."
|
|
153
151
|
].join(`
|
|
154
152
|
`);
|
|
155
|
-
|
|
153
|
+
mcpTransportDebug("Server listening:", message);
|
|
156
154
|
}
|
|
157
155
|
export {
|
|
158
156
|
start
|