@kaitranntt/ccs 7.69.1-dev.1 → 7.69.1-dev.2
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/mcp/ccs-browser-server.cjs +119 -27
- package/package.json +1 -1
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function loadWebSocketImplementation() {
|
|
4
|
+
if (typeof globalThis.WebSocket === 'function') {
|
|
5
|
+
return globalThis.WebSocket;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const { WebSocket } = require('undici');
|
|
10
|
+
if (typeof WebSocket === 'function') {
|
|
11
|
+
return WebSocket;
|
|
12
|
+
}
|
|
13
|
+
} catch {
|
|
14
|
+
// Fall through to the legacy ws dependency when available.
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const { WebSocket } = require('ws');
|
|
19
|
+
if (typeof WebSocket === 'function') {
|
|
20
|
+
return WebSocket;
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
// Surface a dedicated error below if no implementation is available.
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
throw new Error(
|
|
27
|
+
'Browser MCP could not find a WebSocket implementation. Tried globalThis.WebSocket, undici, and ws.'
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const WebSocket = loadWebSocketImplementation();
|
|
4
32
|
|
|
5
33
|
const PROTOCOL_VERSION = '2024-11-05';
|
|
6
34
|
const SERVER_NAME = 'ccs-browser';
|
|
@@ -29,6 +57,66 @@ const NAVIGATION_POLL_INTERVAL_MS = 100;
|
|
|
29
57
|
let inputBuffer = Buffer.alloc(0);
|
|
30
58
|
let requestCounter = 0;
|
|
31
59
|
|
|
60
|
+
function addSocketListener(socket, eventName, handler) {
|
|
61
|
+
if (typeof socket.addEventListener === 'function') {
|
|
62
|
+
socket.addEventListener(eventName, handler);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof socket.on === 'function') {
|
|
67
|
+
socket.on(eventName, handler);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function getSocketMessageText(message) {
|
|
72
|
+
const data = message && typeof message === 'object' && 'data' in message ? message.data : message;
|
|
73
|
+
|
|
74
|
+
if (typeof data === 'string') {
|
|
75
|
+
return data;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (Buffer.isBuffer(data)) {
|
|
79
|
+
return data.toString('utf8');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (data instanceof ArrayBuffer) {
|
|
83
|
+
return Buffer.from(data).toString('utf8');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (ArrayBuffer.isView(data)) {
|
|
87
|
+
return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('utf8');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (data && typeof data.text === 'function') {
|
|
91
|
+
return await data.text();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return String(data);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function closeSocket(socket) {
|
|
98
|
+
if (typeof socket.close === 'function') {
|
|
99
|
+
socket.close();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function abortSocket(socket) {
|
|
104
|
+
if (typeof socket.terminate === 'function') {
|
|
105
|
+
socket.terminate();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
closeSocket(socket);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function toSocketError(error) {
|
|
113
|
+
if (error instanceof Error) {
|
|
114
|
+
return error;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return new Error('Browser MCP lost the DevTools websocket connection.');
|
|
118
|
+
}
|
|
119
|
+
|
|
32
120
|
function shouldExposeTools() {
|
|
33
121
|
return Boolean(process.env.CCS_BROWSER_DEVTOOLS_HTTP_URL);
|
|
34
122
|
}
|
|
@@ -298,7 +386,7 @@ async function sendCdpCommand(page, method, params = {}) {
|
|
|
298
386
|
const timer = setTimeout(() => {
|
|
299
387
|
if (!settled) {
|
|
300
388
|
settled = true;
|
|
301
|
-
ws
|
|
389
|
+
abortSocket(ws);
|
|
302
390
|
reject(new Error('Browser MCP timed out waiting for a DevTools response.'));
|
|
303
391
|
}
|
|
304
392
|
}, CDP_TIMEOUT_MS);
|
|
@@ -309,10 +397,10 @@ async function sendCdpCommand(page, method, params = {}) {
|
|
|
309
397
|
}
|
|
310
398
|
clearTimeout(timer);
|
|
311
399
|
settled = true;
|
|
312
|
-
reject(error);
|
|
400
|
+
reject(toSocketError(error));
|
|
313
401
|
}
|
|
314
402
|
|
|
315
|
-
ws
|
|
403
|
+
addSocketListener(ws, 'open', () => {
|
|
316
404
|
ws.send(
|
|
317
405
|
JSON.stringify({
|
|
318
406
|
id: requestId,
|
|
@@ -322,39 +410,43 @@ async function sendCdpCommand(page, method, params = {}) {
|
|
|
322
410
|
);
|
|
323
411
|
});
|
|
324
412
|
|
|
325
|
-
ws
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
413
|
+
addSocketListener(ws, 'message', (data) => {
|
|
414
|
+
void (async () => {
|
|
415
|
+
const raw = await getSocketMessageText(data);
|
|
329
416
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
} catch {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
417
|
+
if (settled) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
336
420
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
421
|
+
let message;
|
|
422
|
+
try {
|
|
423
|
+
message = JSON.parse(raw);
|
|
424
|
+
} catch {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
340
427
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
428
|
+
if (message.id !== requestId) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
344
431
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
432
|
+
clearTimeout(timer);
|
|
433
|
+
settled = true;
|
|
434
|
+
closeSocket(ws);
|
|
435
|
+
|
|
436
|
+
if (message.error) {
|
|
437
|
+
reject(new Error(message.error.message || 'DevTools request failed.'));
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
349
440
|
|
|
350
|
-
|
|
441
|
+
resolve(message.result || null);
|
|
442
|
+
})().catch(settleError);
|
|
351
443
|
});
|
|
352
444
|
|
|
353
|
-
ws
|
|
445
|
+
addSocketListener(ws, 'error', (error) => {
|
|
354
446
|
settleError(error);
|
|
355
447
|
});
|
|
356
448
|
|
|
357
|
-
ws
|
|
449
|
+
addSocketListener(ws, 'close', () => {
|
|
358
450
|
if (settled) {
|
|
359
451
|
return;
|
|
360
452
|
}
|