@midscene/web 1.7.4 → 1.7.5-beta-20260420031652.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/dist/es/bridge-mode/io-client.mjs +1 -1
- package/dist/es/bridge-mode/io-server.mjs +2 -2
- package/dist/es/bridge-mode/io-server.mjs.map +1 -1
- package/dist/es/bridge-mode/page-browser-side.mjs +1 -1
- package/dist/es/bridge-mode/page-browser-side.mjs.map +1 -1
- package/dist/es/cdp-proxy-constants.mjs +3 -1
- package/dist/es/cdp-proxy-constants.mjs.map +1 -1
- package/dist/es/cdp-proxy.mjs +86 -22
- package/dist/es/cdp-proxy.mjs.map +1 -1
- package/dist/es/cli.mjs +1 -1
- package/dist/es/mcp-server.mjs +1 -1
- package/dist/es/mcp-tools-cdp.mjs +97 -9
- package/dist/es/mcp-tools-cdp.mjs.map +1 -1
- package/dist/lib/bridge-mode/io-client.js +1 -1
- package/dist/lib/bridge-mode/io-server.js +2 -2
- package/dist/lib/bridge-mode/io-server.js.map +1 -1
- package/dist/lib/bridge-mode/page-browser-side.js +1 -1
- package/dist/lib/bridge-mode/page-browser-side.js.map +1 -1
- package/dist/lib/cdp-proxy-constants.js +10 -2
- package/dist/lib/cdp-proxy-constants.js.map +1 -1
- package/dist/lib/cdp-proxy.js +83 -19
- package/dist/lib/cdp-proxy.js.map +1 -1
- package/dist/lib/cli.js +1 -1
- package/dist/lib/mcp-server.js +1 -1
- package/dist/lib/mcp-tools-cdp.js +98 -7
- package/dist/lib/mcp-tools-cdp.js.map +1 -1
- package/dist/types/cdp-proxy-constants.d.ts +2 -0
- package/dist/types/cdp-proxy.d.ts +21 -5
- package/dist/types/mcp-tools-cdp.d.ts +31 -0
- package/package.json +4 -4
package/dist/lib/mcp-server.js
CHANGED
|
@@ -37,7 +37,7 @@ class WebMCPServer extends mcp_namespaceObject.BaseMCPServer {
|
|
|
37
37
|
constructor(toolsManager){
|
|
38
38
|
super({
|
|
39
39
|
name: '@midscene/web-bridge-mcp',
|
|
40
|
-
version: "1.7.
|
|
40
|
+
version: "1.7.5-beta-20260420031652.0",
|
|
41
41
|
description: 'Control the browser using natural language commands'
|
|
42
42
|
}, toolsManager);
|
|
43
43
|
}
|
|
@@ -33,6 +33,7 @@ var __webpack_require__ = {};
|
|
|
33
33
|
var __webpack_exports__ = {};
|
|
34
34
|
__webpack_require__.r(__webpack_exports__);
|
|
35
35
|
__webpack_require__.d(__webpack_exports__, {
|
|
36
|
+
__test__: ()=>__test__,
|
|
36
37
|
WebCdpMidsceneTools: ()=>WebCdpMidsceneTools
|
|
37
38
|
});
|
|
38
39
|
const external_node_child_process_namespaceObject = require("node:child_process");
|
|
@@ -120,6 +121,59 @@ function readProxyEndpoint() {
|
|
|
120
121
|
return null;
|
|
121
122
|
}
|
|
122
123
|
}
|
|
124
|
+
function readProxyUpstream() {
|
|
125
|
+
if (!(0, external_node_fs_namespaceObject.existsSync)(external_cdp_proxy_constants_js_namespaceObject.PROXY_UPSTREAM_FILE)) return null;
|
|
126
|
+
try {
|
|
127
|
+
return (0, external_node_fs_namespaceObject.readFileSync)(external_cdp_proxy_constants_js_namespaceObject.PROXY_UPSTREAM_FILE, 'utf-8').trim();
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function killProxy() {
|
|
133
|
+
if (!(0, external_node_fs_namespaceObject.existsSync)(external_cdp_proxy_constants_js_namespaceObject.PROXY_PID_FILE)) return;
|
|
134
|
+
try {
|
|
135
|
+
const pid = Number((0, external_node_fs_namespaceObject.readFileSync)(external_cdp_proxy_constants_js_namespaceObject.PROXY_PID_FILE, 'utf-8').trim());
|
|
136
|
+
process.kill(pid, 'SIGTERM');
|
|
137
|
+
debug('Killed proxy pid: %d', pid);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
debug('killProxy failed: %s', err);
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
if ((0, external_node_fs_namespaceObject.existsSync)(external_cdp_proxy_constants_js_namespaceObject.PROXY_ENDPOINT_FILE)) (0, external_node_fs_namespaceObject.unlinkSync)(external_cdp_proxy_constants_js_namespaceObject.PROXY_ENDPOINT_FILE);
|
|
143
|
+
} catch {}
|
|
144
|
+
try {
|
|
145
|
+
if ((0, external_node_fs_namespaceObject.existsSync)(external_cdp_proxy_constants_js_namespaceObject.PROXY_PID_FILE)) (0, external_node_fs_namespaceObject.unlinkSync)(external_cdp_proxy_constants_js_namespaceObject.PROXY_PID_FILE);
|
|
146
|
+
} catch {}
|
|
147
|
+
try {
|
|
148
|
+
if ((0, external_node_fs_namespaceObject.existsSync)(external_cdp_proxy_constants_js_namespaceObject.PROXY_UPSTREAM_FILE)) (0, external_node_fs_namespaceObject.unlinkSync)(external_cdp_proxy_constants_js_namespaceObject.PROXY_UPSTREAM_FILE);
|
|
149
|
+
} catch {}
|
|
150
|
+
cleanupTargetIdFile();
|
|
151
|
+
}
|
|
152
|
+
function readSavedTargetId() {
|
|
153
|
+
if (!(0, external_node_fs_namespaceObject.existsSync)(external_cdp_proxy_constants_js_namespaceObject.TARGET_ID_FILE)) return null;
|
|
154
|
+
try {
|
|
155
|
+
return (0, external_node_fs_namespaceObject.readFileSync)(external_cdp_proxy_constants_js_namespaceObject.TARGET_ID_FILE, 'utf-8').trim() || null;
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function saveTargetId(targetId) {
|
|
161
|
+
try {
|
|
162
|
+
(0, external_node_fs_namespaceObject.writeFileSync)(external_cdp_proxy_constants_js_namespaceObject.TARGET_ID_FILE, targetId, 'utf-8');
|
|
163
|
+
debug('Saved targetId: %s', targetId);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
debug('Failed to save targetId: %s', err);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function cleanupTargetIdFile() {
|
|
169
|
+
try {
|
|
170
|
+
if ((0, external_node_fs_namespaceObject.existsSync)(external_cdp_proxy_constants_js_namespaceObject.TARGET_ID_FILE)) (0, external_node_fs_namespaceObject.unlinkSync)(external_cdp_proxy_constants_js_namespaceObject.TARGET_ID_FILE);
|
|
171
|
+
} catch {}
|
|
172
|
+
}
|
|
173
|
+
function getTargetId(page) {
|
|
174
|
+
return page.target()._targetId;
|
|
175
|
+
}
|
|
176
|
+
const PROXY_STDERR_BUFFER_LIMIT = 8192;
|
|
123
177
|
function spawnProxy(chromeEndpoint) {
|
|
124
178
|
return new Promise((resolve, reject)=>{
|
|
125
179
|
const proxyScript = (0, external_node_path_namespaceObject.join)(__dirname, 'cdp-proxy.js');
|
|
@@ -131,16 +185,26 @@ function spawnProxy(chromeEndpoint) {
|
|
|
131
185
|
stdio: [
|
|
132
186
|
'ignore',
|
|
133
187
|
'pipe',
|
|
134
|
-
'
|
|
188
|
+
'pipe'
|
|
135
189
|
]
|
|
136
190
|
});
|
|
137
191
|
proc.unref();
|
|
138
192
|
let output = '';
|
|
193
|
+
let stderrBuf = '';
|
|
139
194
|
let settled = false;
|
|
195
|
+
const appendStderr = (chunk)=>{
|
|
196
|
+
stderrBuf += chunk.toString();
|
|
197
|
+
if (stderrBuf.length > PROXY_STDERR_BUFFER_LIMIT) stderrBuf = stderrBuf.slice(-PROXY_STDERR_BUFFER_LIMIT);
|
|
198
|
+
};
|
|
199
|
+
proc.stderr.on('data', appendStderr);
|
|
200
|
+
const formatStderr = ()=>{
|
|
201
|
+
const trimmed = stderrBuf.trim();
|
|
202
|
+
return trimmed ? ` (stderr: ${trimmed})` : '';
|
|
203
|
+
};
|
|
140
204
|
const timer = setTimeout(()=>{
|
|
141
205
|
if (!settled) {
|
|
142
206
|
settled = true;
|
|
143
|
-
reject(new Error(
|
|
207
|
+
reject(new Error(`Proxy startup timeout (10s)${formatStderr()}`));
|
|
144
208
|
}
|
|
145
209
|
}, 10000);
|
|
146
210
|
const onData = (chunk)=>{
|
|
@@ -152,6 +216,9 @@ function spawnProxy(chromeEndpoint) {
|
|
|
152
216
|
settled = true;
|
|
153
217
|
clearTimeout(timer);
|
|
154
218
|
proc.stdout.removeListener('data', onData);
|
|
219
|
+
proc.stderr.removeListener('data', appendStderr);
|
|
220
|
+
proc.stdout.destroy();
|
|
221
|
+
proc.stderr.destroy();
|
|
155
222
|
resolve(parsed.endpoint);
|
|
156
223
|
return;
|
|
157
224
|
}
|
|
@@ -165,11 +232,12 @@ function spawnProxy(chromeEndpoint) {
|
|
|
165
232
|
reject(new Error(`Failed to spawn proxy: ${err.message}`));
|
|
166
233
|
}
|
|
167
234
|
});
|
|
168
|
-
proc.on('exit', (code)=>{
|
|
235
|
+
proc.on('exit', (code, signal)=>{
|
|
169
236
|
if (!settled) {
|
|
170
237
|
settled = true;
|
|
171
238
|
clearTimeout(timer);
|
|
172
|
-
|
|
239
|
+
const how = signal ? `signal ${signal}` : `code ${code}`;
|
|
240
|
+
reject(new Error(`Proxy exited with ${how} before ready${formatStderr()}`));
|
|
173
241
|
}
|
|
174
242
|
});
|
|
175
243
|
});
|
|
@@ -187,7 +255,12 @@ async function getProxyEndpoint(chromeEndpoint) {
|
|
|
187
255
|
}
|
|
188
256
|
if (isProxyAlive()) {
|
|
189
257
|
const endpoint = readProxyEndpoint();
|
|
190
|
-
|
|
258
|
+
const savedUpstream = readProxyUpstream();
|
|
259
|
+
if (endpoint) if (!savedUpstream || savedUpstream === browserEndpoint) return endpoint;
|
|
260
|
+
else {
|
|
261
|
+
debug('Proxy connected to different upstream (%s), killing', savedUpstream);
|
|
262
|
+
killProxy();
|
|
263
|
+
}
|
|
191
264
|
}
|
|
192
265
|
try {
|
|
193
266
|
return await spawnProxy(browserEndpoint);
|
|
@@ -248,9 +321,18 @@ class WebCdpMidsceneTools extends mcp_namespaceObject.BaseMidsceneTools {
|
|
|
248
321
|
});
|
|
249
322
|
}
|
|
250
323
|
else {
|
|
251
|
-
|
|
324
|
+
const savedTargetId = readSavedTargetId();
|
|
325
|
+
let matchedPage;
|
|
326
|
+
if (savedTargetId && pages.length > 0) {
|
|
327
|
+
matchedPage = pages.find((p)=>getTargetId(p) === savedTargetId);
|
|
328
|
+
matchedPage ? debug('Matched saved targetId %s', savedTargetId) : debug('Saved targetId %s not found among %d pages, falling back', savedTargetId, pages.length);
|
|
329
|
+
}
|
|
330
|
+
page = matchedPage ? matchedPage : webPages.length > 0 ? webPages[webPages.length - 1] : pages.length > 0 ? pages[pages.length - 1] : await browser.newPage();
|
|
252
331
|
await page.bringToFront();
|
|
253
332
|
}
|
|
333
|
+
const targetId = getTargetId(page);
|
|
334
|
+
if (targetId) saveTargetId(targetId);
|
|
335
|
+
else debug('No targetId on page.target(); cross-command tab reuse disabled until puppeteer integration is updated.');
|
|
254
336
|
this.agent = new index_js_namespaceObject.PuppeteerAgent(page);
|
|
255
337
|
return this.agent;
|
|
256
338
|
}
|
|
@@ -310,6 +392,7 @@ class WebCdpMidsceneTools extends mcp_namespaceObject.BaseMidsceneTools {
|
|
|
310
392
|
this.activeBrowser.disconnect();
|
|
311
393
|
this.activeBrowser = null;
|
|
312
394
|
}
|
|
395
|
+
cleanupTargetIdFile();
|
|
313
396
|
return this.buildTextResult('Disconnected from web page (browser still running externally)');
|
|
314
397
|
}
|
|
315
398
|
}
|
|
@@ -320,9 +403,17 @@ class WebCdpMidsceneTools extends mcp_namespaceObject.BaseMidsceneTools {
|
|
|
320
403
|
this.cdpEndpoint = cdpEndpoint;
|
|
321
404
|
}
|
|
322
405
|
}
|
|
406
|
+
const __test__ = {
|
|
407
|
+
getProxyEndpoint,
|
|
408
|
+
killProxy,
|
|
409
|
+
readProxyUpstream,
|
|
410
|
+
isProxyAlive
|
|
411
|
+
};
|
|
323
412
|
exports.WebCdpMidsceneTools = __webpack_exports__.WebCdpMidsceneTools;
|
|
413
|
+
exports.__test__ = __webpack_exports__.__test__;
|
|
324
414
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
325
|
-
"WebCdpMidsceneTools"
|
|
415
|
+
"WebCdpMidsceneTools",
|
|
416
|
+
"__test__"
|
|
326
417
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
327
418
|
Object.defineProperty(exports, '__esModule', {
|
|
328
419
|
value: true
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-tools-cdp.js","sources":["webpack/runtime/compat_get_default_export","webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../src/mcp-tools-cdp.ts"],"sourcesContent":["// getDefaultExport function for compatibility with non-ESM modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};\n","__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { spawn } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport http from 'node:http';\nimport { join } from 'node:path';\nimport { ScreenshotItem, z } from '@midscene/core';\nimport { getDebug } from '@midscene/shared/logger';\nimport { BaseMidsceneTools, type ToolDefinition } from '@midscene/shared/mcp';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport { PROXY_ENDPOINT_FILE, PROXY_PID_FILE } from './cdp-proxy-constants';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst debug = getDebug('mcp:cdp');\n\n/** CDP target discovery may need a brief moment after WebSocket open. */\nconst CDP_TARGET_DISCOVERY_DELAY_MS = 500;\n\n/**\n * Check if a CDP endpoint is a page-level URL (e.g., /devtools/page/XXX).\n */\nfunction isPageLevelEndpoint(endpoint: string): boolean {\n return /\\/devtools\\/page\\//.test(endpoint);\n}\n\n/**\n * Try to resolve a page-level CDP endpoint to a browser-level endpoint\n * by fetching /json/version from the same host:port.\n */\nfunction resolveBrowserEndpoint(pageEndpoint: string): Promise<string> {\n return new Promise((resolve, reject) => {\n let host: string;\n try {\n const url = new URL(pageEndpoint);\n host = url.host; // host includes port (e.g. \"127.0.0.1:9222\")\n } catch {\n reject(new Error(`Invalid CDP endpoint URL: ${pageEndpoint}`));\n return;\n }\n\n const req = http.get(\n `http://${host}/json/version`,\n { timeout: 5000 },\n (res) => {\n if (res.statusCode && res.statusCode >= 400) {\n reject(new Error(`/json/version returned HTTP ${res.statusCode}`));\n res.resume();\n return;\n }\n let data = '';\n res.on('data', (chunk: Buffer) => {\n data += chunk.toString();\n });\n res.on('end', () => {\n try {\n const info = JSON.parse(data);\n if (info.webSocketDebuggerUrl) {\n resolve(info.webSocketDebuggerUrl);\n } else {\n reject(\n new Error(\n 'webSocketDebuggerUrl not found in /json/version response',\n ),\n );\n }\n } catch {\n reject(\n new Error(`Failed to parse /json/version response: ${data}`),\n );\n }\n });\n },\n );\n req.on('error', (err) =>\n reject(new Error(`Failed to fetch /json/version: ${err.message}`)),\n );\n req.on('timeout', () => {\n req.destroy();\n reject(new Error('Timeout fetching /json/version'));\n });\n });\n}\n\n/**\n * Check if a previously spawned proxy process is still alive.\n */\nfunction isProxyAlive(): boolean {\n if (!existsSync(PROXY_PID_FILE)) return false;\n try {\n const pid = Number(readFileSync(PROXY_PID_FILE, 'utf-8').trim());\n process.kill(pid, 0); // signal 0 = existence check\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Read the proxy endpoint written by cdp-proxy.ts.\n */\nfunction readProxyEndpoint(): string | null {\n if (!existsSync(PROXY_ENDPOINT_FILE)) return null;\n try {\n return readFileSync(PROXY_ENDPOINT_FILE, 'utf-8').trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Spawn the CDP proxy process and wait for it to print the endpoint.\n */\nfunction spawnProxy(chromeEndpoint: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const proxyScript = join(__dirname, 'cdp-proxy.js');\n const proc = spawn(process.execPath, [proxyScript, chromeEndpoint], {\n detached: true,\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n proc.unref();\n\n let output = '';\n let settled = false;\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true;\n reject(new Error('Proxy startup timeout (10s)'));\n }\n }, 10000);\n\n const onData = (chunk: Buffer) => {\n output += chunk.toString();\n const lines = output.split('\\n');\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const parsed = JSON.parse(line);\n if (parsed.endpoint && !settled) {\n settled = true;\n clearTimeout(timer);\n proc.stdout!.removeListener('data', onData);\n resolve(parsed.endpoint);\n return;\n }\n } catch {\n // stdout may contain non-JSON lines during startup — skip them\n }\n }\n };\n proc.stdout!.on('data', onData);\n\n proc.on('error', (err) => {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n reject(new Error(`Failed to spawn proxy: ${err.message}`));\n }\n });\n proc.on('exit', (code) => {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n reject(new Error(`Proxy exited with code ${code} before ready`));\n }\n });\n });\n}\n\n/**\n * Get the proxy endpoint, spawning the proxy if needed.\n * Falls back to direct connection if proxy cannot be started.\n *\n * If the user provides a page-level CDP URL, automatically resolves it\n * to a browser-level endpoint via /json/version.\n */\nasync function getProxyEndpoint(chromeEndpoint: string): Promise<string> {\n // If the user passed a page-level endpoint, resolve to browser-level first\n let browserEndpoint = chromeEndpoint;\n if (isPageLevelEndpoint(chromeEndpoint)) {\n debug(\n 'Page-level CDP endpoint detected, resolving via /json/version: %s',\n chromeEndpoint,\n );\n try {\n browserEndpoint = await resolveBrowserEndpoint(chromeEndpoint);\n debug('Resolved browser endpoint: %s', browserEndpoint);\n } catch (err) {\n throw new Error(\n `Cannot use page-level CDP endpoint directly. Puppeteer requires a browser-level endpoint (e.g., ws://host:port/devtools/browser/<id>). Auto-resolution via /json/version failed: ${(err as Error).message}. Please provide a browser-level CDP endpoint instead.`,\n );\n }\n }\n\n // If proxy is alive and endpoint file exists, reuse it\n if (isProxyAlive()) {\n const endpoint = readProxyEndpoint();\n if (endpoint) return endpoint;\n }\n\n // Spawn a new proxy\n try {\n return await spawnProxy(browserEndpoint);\n } catch (err) {\n console.warn(\n `[cdp] proxy failed, falling back to direct connection: ${err}`,\n );\n return browserEndpoint;\n }\n}\n\n/**\n * Tools manager for Web CDP-mode MCP.\n * Connects to an existing Chrome browser via CDP (Chrome DevTools Protocol) endpoint.\n * Unlike WebPuppeteerMidsceneTools which launches its own Chrome, this connects\n * to a browser that is already running with remote debugging enabled.\n *\n * Uses a persistent WebSocket proxy to avoid repeated Chrome permission popups\n * when Chrome's settings-based remote debugging is used.\n */\nexport class WebCdpMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n private cdpEndpoint: string;\n private activeBrowser: Browser | null = null;\n\n constructor(cdpEndpoint: string) {\n super();\n this.cdpEndpoint = cdpEndpoint;\n }\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(navigateToUrl?: string): Promise<PuppeteerAgent> {\n // Re-init if URL provided\n if (this.agent && navigateToUrl) {\n try {\n await this.agent?.destroy?.();\n } catch (error) {\n console.debug('Failed to destroy agent during re-init:', error);\n }\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n // Connect via proxy to avoid repeated Chrome permission popups\n if (!this.activeBrowser) {\n const endpoint = await getProxyEndpoint(this.cdpEndpoint);\n this.activeBrowser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n }\n\n const browser = this.activeBrowser;\n let pages = await browser.pages();\n\n // If no pages discovered, wait briefly and retry — some CDP targets\n // need a moment to appear after the WebSocket connection is established.\n if (pages.length === 0) {\n await new Promise((r) => setTimeout(r, CDP_TARGET_DISCOVERY_DELAY_MS));\n pages = await browser.pages();\n }\n\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n debug(\n 'Found %d page(s), %d web page(s): %o',\n pages.length,\n webPages.length,\n pages.map((p) => p.url()),\n );\n let page: Page;\n\n if (navigateToUrl) {\n if (webPages.length > 0) {\n // Reuse an existing page and navigate it — avoids creating invisible\n // tabs when Chrome uses settings-based remote debugging (no HTTP\n // discovery endpoints, /devtools/page/* returns 403).\n page = webPages[webPages.length - 1];\n await page.bringToFront();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // No existing web pages — fall back to creating a new tab\n page = await browser.newPage();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n }\n } else {\n // Reuse the last web page, or any existing page (including about:blank\n // which may be the user's active tab). Only create a new page as last resort.\n if (webPages.length > 0) {\n page = webPages[webPages.length - 1];\n } else if (pages.length > 0) {\n page = pages[pages.length - 1];\n } else {\n page = await browser.newPage();\n }\n\n await page.bringToFront();\n }\n\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage);\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page via CDP. Opens a new tab with the given URL, or reuses the current page.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to use current page)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Destroy existing agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch (e) {\n console.debug('Failed to destroy agent during connect:', e);\n }\n this.agent = undefined;\n }\n\n this.agent = await this.ensureAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current page';\n\n return {\n content: [\n { type: 'text', text: `Connected via CDP to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page. The browser stays running (managed externally).',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch (e) {\n console.debug('Failed to destroy agent during disconnect:', e);\n }\n this.agent = undefined;\n }\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n return this.buildTextResult(\n 'Disconnected from web page (browser still running externally)',\n );\n },\n },\n ];\n }\n}\n"],"names":["__webpack_require__","module","getter","definition","key","Object","obj","prop","Symbol","debug","getDebug","CDP_TARGET_DISCOVERY_DELAY_MS","isPageLevelEndpoint","endpoint","resolveBrowserEndpoint","pageEndpoint","Promise","resolve","reject","host","url","URL","Error","req","http","res","data","chunk","info","JSON","err","isProxyAlive","existsSync","PROXY_PID_FILE","pid","Number","readFileSync","process","readProxyEndpoint","PROXY_ENDPOINT_FILE","spawnProxy","chromeEndpoint","proxyScript","join","__dirname","proc","spawn","output","settled","timer","setTimeout","onData","lines","line","parsed","clearTimeout","code","getProxyEndpoint","browserEndpoint","console","WebCdpMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","navigateToUrl","error","undefined","puppeteer","browser","pages","r","webPages","p","page","PuppeteerAgent","z","args","e","screenshot","label","cdpEndpoint"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mcp-tools-cdp.js","sources":["webpack/runtime/compat_get_default_export","webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../src/mcp-tools-cdp.ts"],"sourcesContent":["// getDefaultExport function for compatibility with non-ESM modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};\n","__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { spawn } from 'node:child_process';\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport http from 'node:http';\nimport { join } from 'node:path';\nimport { ScreenshotItem, z } from '@midscene/core';\nimport { getDebug } from '@midscene/shared/logger';\nimport { BaseMidsceneTools, type ToolDefinition } from '@midscene/shared/mcp';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport {\n PROXY_ENDPOINT_FILE,\n PROXY_PID_FILE,\n PROXY_UPSTREAM_FILE,\n TARGET_ID_FILE,\n} from './cdp-proxy-constants';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst debug = getDebug('mcp:cdp');\n\n/** CDP target discovery may need a brief moment after WebSocket open. */\nconst CDP_TARGET_DISCOVERY_DELAY_MS = 500;\n\n/**\n * Check if a CDP endpoint is a page-level URL (e.g., /devtools/page/XXX).\n */\nfunction isPageLevelEndpoint(endpoint: string): boolean {\n return /\\/devtools\\/page\\//.test(endpoint);\n}\n\n/**\n * Try to resolve a page-level CDP endpoint to a browser-level endpoint\n * by fetching /json/version from the same host:port.\n */\nfunction resolveBrowserEndpoint(pageEndpoint: string): Promise<string> {\n return new Promise((resolve, reject) => {\n let host: string;\n try {\n const url = new URL(pageEndpoint);\n host = url.host; // host includes port (e.g. \"127.0.0.1:9222\")\n } catch {\n reject(new Error(`Invalid CDP endpoint URL: ${pageEndpoint}`));\n return;\n }\n\n const req = http.get(\n `http://${host}/json/version`,\n { timeout: 5000 },\n (res) => {\n if (res.statusCode && res.statusCode >= 400) {\n reject(new Error(`/json/version returned HTTP ${res.statusCode}`));\n res.resume();\n return;\n }\n let data = '';\n res.on('data', (chunk: Buffer) => {\n data += chunk.toString();\n });\n res.on('end', () => {\n try {\n const info = JSON.parse(data);\n if (info.webSocketDebuggerUrl) {\n resolve(info.webSocketDebuggerUrl);\n } else {\n reject(\n new Error(\n 'webSocketDebuggerUrl not found in /json/version response',\n ),\n );\n }\n } catch {\n reject(\n new Error(`Failed to parse /json/version response: ${data}`),\n );\n }\n });\n },\n );\n req.on('error', (err) =>\n reject(new Error(`Failed to fetch /json/version: ${err.message}`)),\n );\n req.on('timeout', () => {\n req.destroy();\n reject(new Error('Timeout fetching /json/version'));\n });\n });\n}\n\n/**\n * Check if a previously spawned proxy process is still alive.\n */\nfunction isProxyAlive(): boolean {\n if (!existsSync(PROXY_PID_FILE)) return false;\n try {\n const pid = Number(readFileSync(PROXY_PID_FILE, 'utf-8').trim());\n process.kill(pid, 0); // signal 0 = existence check\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Read the proxy endpoint written by cdp-proxy.ts.\n */\nfunction readProxyEndpoint(): string | null {\n if (!existsSync(PROXY_ENDPOINT_FILE)) return null;\n try {\n return readFileSync(PROXY_ENDPOINT_FILE, 'utf-8').trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Read the Chrome endpoint that the running proxy is connected to.\n */\nfunction readProxyUpstream(): string | null {\n if (!existsSync(PROXY_UPSTREAM_FILE)) return null;\n try {\n return readFileSync(PROXY_UPSTREAM_FILE, 'utf-8').trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Kill the running proxy process and clear all CDP-mode metadata files\n * (proxy endpoint/pid/upstream and the cross-command targetId).\n */\nfunction killProxy(): void {\n if (!existsSync(PROXY_PID_FILE)) return;\n try {\n const pid = Number(readFileSync(PROXY_PID_FILE, 'utf-8').trim());\n process.kill(pid, 'SIGTERM');\n debug('Killed proxy pid: %d', pid);\n } catch (err) {\n // ESRCH (already dead) is the common case; surface anything else\n // (e.g. EPERM) via debug so it does not vanish silently.\n debug('killProxy failed: %s', err);\n }\n try {\n if (existsSync(PROXY_ENDPOINT_FILE)) unlinkSync(PROXY_ENDPOINT_FILE);\n } catch {}\n try {\n if (existsSync(PROXY_PID_FILE)) unlinkSync(PROXY_PID_FILE);\n } catch {}\n try {\n if (existsSync(PROXY_UPSTREAM_FILE)) unlinkSync(PROXY_UPSTREAM_FILE);\n } catch {}\n // The saved targetId points into the now-defunct upstream's tab list,\n // so it cannot match anything in a fresh Chrome and must be discarded.\n cleanupTargetIdFile();\n}\n\n/**\n * Read the saved targetId from the temporary file.\n */\nfunction readSavedTargetId(): string | null {\n if (!existsSync(TARGET_ID_FILE)) return null;\n try {\n return readFileSync(TARGET_ID_FILE, 'utf-8').trim() || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Save a targetId to the temporary file for cross-command tab reuse.\n */\nfunction saveTargetId(targetId: string): void {\n try {\n writeFileSync(TARGET_ID_FILE, targetId, 'utf-8');\n debug('Saved targetId: %s', targetId);\n } catch (err) {\n debug('Failed to save targetId: %s', err);\n }\n}\n\n/**\n * Remove the saved targetId file.\n */\nfunction cleanupTargetIdFile(): void {\n try {\n if (existsSync(TARGET_ID_FILE)) unlinkSync(TARGET_ID_FILE);\n } catch {}\n}\n\n/**\n * puppeteer-core does not expose a public method for the underlying\n * CDP target id, so we reach into `_targetId`. Centralised here so that\n * a future puppeteer release exposing this properly only requires one\n * change. Callers must treat the result as optional.\n */\nfunction getTargetId(page: Page): string | undefined {\n return (page.target() as unknown as { _targetId?: string })._targetId;\n}\n\n/** Keep at most this many bytes of proxy stderr for diagnostics. */\nconst PROXY_STDERR_BUFFER_LIMIT = 8 * 1024;\n\n/**\n * Spawn the CDP proxy process and wait for it to print the endpoint.\n *\n * Captures the child's stderr so that when startup fails we can surface\n * the real reason (upstream closed / duplicate proxy / upstream error)\n * instead of the generic \"exited before ready\".\n */\nfunction spawnProxy(chromeEndpoint: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const proxyScript = join(__dirname, 'cdp-proxy.js');\n const proc = spawn(process.execPath, [proxyScript, chromeEndpoint], {\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n proc.unref();\n\n let output = '';\n let stderrBuf = '';\n let settled = false;\n\n const appendStderr = (chunk: Buffer) => {\n stderrBuf += chunk.toString();\n if (stderrBuf.length > PROXY_STDERR_BUFFER_LIMIT) {\n stderrBuf = stderrBuf.slice(-PROXY_STDERR_BUFFER_LIMIT);\n }\n };\n proc.stderr!.on('data', appendStderr);\n\n const formatStderr = () => {\n const trimmed = stderrBuf.trim();\n return trimmed ? ` (stderr: ${trimmed})` : '';\n };\n\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true;\n reject(new Error(`Proxy startup timeout (10s)${formatStderr()}`));\n }\n }, 10000);\n\n const onData = (chunk: Buffer) => {\n output += chunk.toString();\n const lines = output.split('\\n');\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const parsed = JSON.parse(line);\n if (parsed.endpoint && !settled) {\n settled = true;\n clearTimeout(timer);\n proc.stdout!.removeListener('data', onData);\n proc.stderr!.removeListener('data', appendStderr);\n // Destroy the stdio pipes so they don't keep the parent\n // process event loop alive after we've read the endpoint.\n proc.stdout!.destroy();\n proc.stderr!.destroy();\n resolve(parsed.endpoint);\n return;\n }\n } catch {\n // stdout may contain non-JSON lines during startup — skip them\n }\n }\n };\n proc.stdout!.on('data', onData);\n\n proc.on('error', (err) => {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n reject(new Error(`Failed to spawn proxy: ${err.message}`));\n }\n });\n proc.on('exit', (code, signal) => {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n const how = signal ? `signal ${signal}` : `code ${code}`;\n reject(\n new Error(`Proxy exited with ${how} before ready${formatStderr()}`),\n );\n }\n });\n });\n}\n\n/**\n * Get the proxy endpoint, spawning the proxy if needed.\n * Falls back to direct connection if proxy cannot be started.\n *\n * If the user provides a page-level CDP URL, automatically resolves it\n * to a browser-level endpoint via /json/version.\n */\nasync function getProxyEndpoint(chromeEndpoint: string): Promise<string> {\n // If the user passed a page-level endpoint, resolve to browser-level first\n let browserEndpoint = chromeEndpoint;\n if (isPageLevelEndpoint(chromeEndpoint)) {\n debug(\n 'Page-level CDP endpoint detected, resolving via /json/version: %s',\n chromeEndpoint,\n );\n try {\n browserEndpoint = await resolveBrowserEndpoint(chromeEndpoint);\n debug('Resolved browser endpoint: %s', browserEndpoint);\n } catch (err) {\n throw new Error(\n `Cannot use page-level CDP endpoint directly. Puppeteer requires a browser-level endpoint (e.g., ws://host:port/devtools/browser/<id>). Auto-resolution via /json/version failed: ${(err as Error).message}. Please provide a browser-level CDP endpoint instead.`,\n );\n }\n }\n\n // If proxy is alive and connected to the same Chrome, reuse it\n if (isProxyAlive()) {\n const endpoint = readProxyEndpoint();\n const savedUpstream = readProxyUpstream();\n if (endpoint) {\n if (savedUpstream && savedUpstream !== browserEndpoint) {\n // Proxy is connected to a different Chrome — kill it and start fresh\n debug(\n 'Proxy connected to different upstream (%s), killing',\n savedUpstream,\n );\n killProxy();\n } else {\n return endpoint;\n }\n }\n }\n\n // Spawn a new proxy\n try {\n return await spawnProxy(browserEndpoint);\n } catch (err) {\n console.warn(\n `[cdp] proxy failed, falling back to direct connection: ${err}`,\n );\n return browserEndpoint;\n }\n}\n\n/**\n * Tools manager for Web CDP-mode MCP.\n * Connects to an existing Chrome browser via CDP (Chrome DevTools Protocol) endpoint.\n * Unlike WebPuppeteerMidsceneTools which launches its own Chrome, this connects\n * to a browser that is already running with remote debugging enabled.\n *\n * Uses a persistent WebSocket proxy to avoid repeated Chrome permission popups\n * when Chrome's settings-based remote debugging is used.\n */\nexport class WebCdpMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n private cdpEndpoint: string;\n private activeBrowser: Browser | null = null;\n\n constructor(cdpEndpoint: string) {\n super();\n this.cdpEndpoint = cdpEndpoint;\n }\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(navigateToUrl?: string): Promise<PuppeteerAgent> {\n // Re-init if URL provided\n if (this.agent && navigateToUrl) {\n try {\n await this.agent?.destroy?.();\n } catch (error) {\n console.debug('Failed to destroy agent during re-init:', error);\n }\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n // Connect via proxy to avoid repeated Chrome permission popups\n if (!this.activeBrowser) {\n const endpoint = await getProxyEndpoint(this.cdpEndpoint);\n this.activeBrowser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n }\n\n const browser = this.activeBrowser;\n let pages = await browser.pages();\n\n // If no pages discovered, wait briefly and retry — some CDP targets\n // need a moment to appear after the WebSocket connection is established.\n if (pages.length === 0) {\n await new Promise((r) => setTimeout(r, CDP_TARGET_DISCOVERY_DELAY_MS));\n pages = await browser.pages();\n }\n\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n debug(\n 'Found %d page(s), %d web page(s): %o',\n pages.length,\n webPages.length,\n pages.map((p) => p.url()),\n );\n let page: Page;\n\n if (navigateToUrl) {\n if (webPages.length > 0) {\n // Reuse an existing page and navigate it — avoids creating invisible\n // tabs when Chrome uses settings-based remote debugging (no HTTP\n // discovery endpoints, /devtools/page/* returns 403).\n page = webPages[webPages.length - 1];\n await page.bringToFront();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // No existing web pages — fall back to creating a new tab\n page = await browser.newPage();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n }\n } else {\n // Try to find the exact tab from a previous `connect` command via saved targetId.\n const savedTargetId = readSavedTargetId();\n let matchedPage: Page | undefined;\n\n if (savedTargetId && pages.length > 0) {\n matchedPage = pages.find((p) => getTargetId(p) === savedTargetId);\n if (matchedPage) {\n debug('Matched saved targetId %s', savedTargetId);\n } else {\n debug(\n 'Saved targetId %s not found among %d pages, falling back',\n savedTargetId,\n pages.length,\n );\n }\n }\n\n if (matchedPage) {\n page = matchedPage;\n } else if (webPages.length > 0) {\n page = webPages[webPages.length - 1];\n } else if (pages.length > 0) {\n page = pages[pages.length - 1];\n } else {\n page = await browser.newPage();\n }\n\n await page.bringToFront();\n }\n\n // Persist the targetId so subsequent CLI commands can find this exact tab\n const targetId = getTargetId(page);\n if (targetId) {\n saveTargetId(targetId);\n } else {\n // If puppeteer ever drops the private _targetId field, this branch\n // makes the regression visible instead of silently disabling the\n // cross-command tab reuse path.\n debug(\n 'No targetId on page.target(); cross-command tab reuse disabled until puppeteer integration is updated.',\n );\n }\n\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage);\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page via CDP. Opens a new tab with the given URL, or reuses the current page.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to use current page)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Destroy existing agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch (e) {\n console.debug('Failed to destroy agent during connect:', e);\n }\n this.agent = undefined;\n }\n\n this.agent = await this.ensureAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current page';\n\n return {\n content: [\n { type: 'text', text: `Connected via CDP to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page. The browser stays running (managed externally).',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch (e) {\n console.debug('Failed to destroy agent during disconnect:', e);\n }\n this.agent = undefined;\n }\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n cleanupTargetIdFile();\n return this.buildTextResult(\n 'Disconnected from web page (browser still running externally)',\n );\n },\n },\n ];\n }\n}\n\n/**\n * Internal helpers exposed for unit tests. Not part of the public API.\n */\nexport const __test__ = {\n getProxyEndpoint,\n killProxy,\n readProxyUpstream,\n isProxyAlive,\n};\n"],"names":["__webpack_require__","module","getter","definition","key","Object","obj","prop","Symbol","debug","getDebug","CDP_TARGET_DISCOVERY_DELAY_MS","isPageLevelEndpoint","endpoint","resolveBrowserEndpoint","pageEndpoint","Promise","resolve","reject","host","url","URL","Error","req","http","res","data","chunk","info","JSON","err","isProxyAlive","existsSync","PROXY_PID_FILE","pid","Number","readFileSync","process","readProxyEndpoint","PROXY_ENDPOINT_FILE","readProxyUpstream","PROXY_UPSTREAM_FILE","killProxy","unlinkSync","cleanupTargetIdFile","readSavedTargetId","TARGET_ID_FILE","saveTargetId","targetId","writeFileSync","getTargetId","page","PROXY_STDERR_BUFFER_LIMIT","spawnProxy","chromeEndpoint","proxyScript","join","__dirname","proc","spawn","output","stderrBuf","settled","appendStderr","formatStderr","trimmed","timer","setTimeout","onData","lines","line","parsed","clearTimeout","code","signal","how","getProxyEndpoint","browserEndpoint","savedUpstream","console","WebCdpMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","navigateToUrl","error","undefined","puppeteer","browser","pages","r","webPages","p","savedTargetId","matchedPage","PuppeteerAgent","z","args","e","screenshot","label","cdpEndpoint","__test__"],"mappings":";;;IACAA,oBAAoB,CAAC,GAAG,CAACC;QACxB,IAAIC,SAASD,UAAUA,OAAO,UAAU,GACvC,IAAOA,MAAM,CAAC,UAAU,GACxB,IAAOA;QACRD,oBAAoB,CAAC,CAACE,QAAQ;YAAE,GAAGA;QAAO;QAC1C,OAAOA;IACR;;;ICPAF,oBAAoB,CAAC,GAAG,CAAC,UAASG;QACjC,IAAI,IAAIC,OAAOD,WACR,IAAGH,oBAAoB,CAAC,CAACG,YAAYC,QAAQ,CAACJ,oBAAoB,CAAC,CAAC,UAASI,MACzEC,OAAO,cAAc,CAAC,UAASD,KAAK;YAAE,YAAY;YAAM,KAAKD,UAAU,CAACC,IAAI;QAAC;IAGzF;;;ICNAJ,oBAAoB,CAAC,GAAG,CAACM,KAAKC,OAAUF,OAAO,SAAS,CAAC,cAAc,CAAC,IAAI,CAACC,KAAKC;;;ICClFP,oBAAoB,CAAC,GAAG,CAAC;QACxB,IAAG,AAAkB,eAAlB,OAAOQ,UAA0BA,OAAO,WAAW,EACrDH,OAAO,cAAc,CAAC,UAASG,OAAO,WAAW,EAAE;YAAE,OAAO;QAAS;QAEtEH,OAAO,cAAc,CAAC,UAAS,cAAc;YAAE,OAAO;QAAK;IAC5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACaA,MAAMI,QAAQC,AAAAA,IAAAA,uBAAAA,QAAAA,AAAAA,EAAS;AAGvB,MAAMC,gCAAgC;AAKtC,SAASC,oBAAoBC,QAAgB;IAC3C,OAAO,qBAAqB,IAAI,CAACA;AACnC;AAMA,SAASC,uBAAuBC,YAAoB;IAClD,OAAO,IAAIC,QAAQ,CAACC,SAASC;QAC3B,IAAIC;QACJ,IAAI;YACF,MAAMC,MAAM,IAAIC,IAAIN;YACpBI,OAAOC,IAAI,IAAI;QACjB,EAAE,OAAM;YACNF,OAAO,IAAII,MAAM,CAAC,0BAA0B,EAAEP,cAAc;YAC5D;QACF;QAEA,MAAMQ,MAAMC,6BAAAA,GAAQ,CAClB,CAAC,OAAO,EAAEL,KAAK,aAAa,CAAC,EAC7B;YAAE,SAAS;QAAK,GAChB,CAACM;YACC,IAAIA,IAAI,UAAU,IAAIA,IAAI,UAAU,IAAI,KAAK;gBAC3CP,OAAO,IAAII,MAAM,CAAC,4BAA4B,EAAEG,IAAI,UAAU,EAAE;gBAChEA,IAAI,MAAM;gBACV;YACF;YACA,IAAIC,OAAO;YACXD,IAAI,EAAE,CAAC,QAAQ,CAACE;gBACdD,QAAQC,MAAM,QAAQ;YACxB;YACAF,IAAI,EAAE,CAAC,OAAO;gBACZ,IAAI;oBACF,MAAMG,OAAOC,KAAK,KAAK,CAACH;oBACxB,IAAIE,KAAK,oBAAoB,EAC3BX,QAAQW,KAAK,oBAAoB;yBAEjCV,OACE,IAAII,MACF;gBAIR,EAAE,OAAM;oBACNJ,OACE,IAAII,MAAM,CAAC,wCAAwC,EAAEI,MAAM;gBAE/D;YACF;QACF;QAEFH,IAAI,EAAE,CAAC,SAAS,CAACO,MACfZ,OAAO,IAAII,MAAM,CAAC,+BAA+B,EAAEQ,IAAI,OAAO,EAAE;QAElEP,IAAI,EAAE,CAAC,WAAW;YAChBA,IAAI,OAAO;YACXL,OAAO,IAAII,MAAM;QACnB;IACF;AACF;AAKA,SAASS;IACP,IAAI,CAACC,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWC,gDAAAA,cAAcA,GAAG,OAAO;IACxC,IAAI;QACF,MAAMC,MAAMC,OAAOC,AAAAA,IAAAA,iCAAAA,YAAAA,AAAAA,EAAaH,gDAAAA,cAAcA,EAAE,SAAS,IAAI;QAC7DI,QAAQ,IAAI,CAACH,KAAK;QAClB,OAAO;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAKA,SAASI;IACP,IAAI,CAACN,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWO,gDAAAA,mBAAmBA,GAAG,OAAO;IAC7C,IAAI;QACF,OAAOH,AAAAA,IAAAA,iCAAAA,YAAAA,AAAAA,EAAaG,gDAAAA,mBAAmBA,EAAE,SAAS,IAAI;IACxD,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAKA,SAASC;IACP,IAAI,CAACR,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWS,gDAAAA,mBAAmBA,GAAG,OAAO;IAC7C,IAAI;QACF,OAAOL,AAAAA,IAAAA,iCAAAA,YAAAA,AAAAA,EAAaK,gDAAAA,mBAAmBA,EAAE,SAAS,IAAI;IACxD,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAMA,SAASC;IACP,IAAI,CAACV,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWC,gDAAAA,cAAcA,GAAG;IACjC,IAAI;QACF,MAAMC,MAAMC,OAAOC,AAAAA,IAAAA,iCAAAA,YAAAA,AAAAA,EAAaH,gDAAAA,cAAcA,EAAE,SAAS,IAAI;QAC7DI,QAAQ,IAAI,CAACH,KAAK;QAClBzB,MAAM,wBAAwByB;IAChC,EAAE,OAAOJ,KAAK;QAGZrB,MAAM,wBAAwBqB;IAChC;IACA,IAAI;QACF,IAAIE,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWO,gDAAAA,mBAAmBA,GAAGI,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWJ,gDAAAA,mBAAmBA;IACrE,EAAE,OAAM,CAAC;IACT,IAAI;QACF,IAAIP,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWC,gDAAAA,cAAcA,GAAGU,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWV,gDAAAA,cAAcA;IAC3D,EAAE,OAAM,CAAC;IACT,IAAI;QACF,IAAID,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWS,gDAAAA,mBAAmBA,GAAGE,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWF,gDAAAA,mBAAmBA;IACrE,EAAE,OAAM,CAAC;IAGTG;AACF;AAKA,SAASC;IACP,IAAI,CAACb,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWc,gDAAAA,cAAcA,GAAG,OAAO;IACxC,IAAI;QACF,OAAOV,AAAAA,IAAAA,iCAAAA,YAAAA,AAAAA,EAAaU,gDAAAA,cAAcA,EAAE,SAAS,IAAI,MAAM;IACzD,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAKA,SAASC,aAAaC,QAAgB;IACpC,IAAI;QACFC,IAAAA,iCAAAA,aAAAA,AAAAA,EAAcH,gDAAAA,cAAcA,EAAEE,UAAU;QACxCvC,MAAM,sBAAsBuC;IAC9B,EAAE,OAAOlB,KAAK;QACZrB,MAAM,+BAA+BqB;IACvC;AACF;AAKA,SAASc;IACP,IAAI;QACF,IAAIZ,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWc,gDAAAA,cAAcA,GAAGH,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWG,gDAAAA,cAAcA;IAC3D,EAAE,OAAM,CAAC;AACX;AAQA,SAASI,YAAYC,IAAU;IAC7B,OAAQA,KAAK,MAAM,GAAyC,SAAS;AACvE;AAGA,MAAMC,4BAA4B;AASlC,SAASC,WAAWC,cAAsB;IACxC,OAAO,IAAItC,QAAQ,CAACC,SAASC;QAC3B,MAAMqC,cAAcC,AAAAA,IAAAA,mCAAAA,IAAAA,AAAAA,EAAKC,WAAW;QACpC,MAAMC,OAAOC,AAAAA,IAAAA,4CAAAA,KAAAA,AAAAA,EAAMtB,QAAQ,QAAQ,EAAE;YAACkB;YAAaD;SAAe,EAAE;YAClE,UAAU;YACV,OAAO;gBAAC;gBAAU;gBAAQ;aAAO;QACnC;QACAI,KAAK,KAAK;QAEV,IAAIE,SAAS;QACb,IAAIC,YAAY;QAChB,IAAIC,UAAU;QAEd,MAAMC,eAAe,CAACpC;YACpBkC,aAAalC,MAAM,QAAQ;YAC3B,IAAIkC,UAAU,MAAM,GAAGT,2BACrBS,YAAYA,UAAU,KAAK,CAAC,CAACT;QAEjC;QACAM,KAAK,MAAM,CAAE,EAAE,CAAC,QAAQK;QAExB,MAAMC,eAAe;YACnB,MAAMC,UAAUJ,UAAU,IAAI;YAC9B,OAAOI,UAAU,CAAC,UAAU,EAAEA,QAAQ,CAAC,CAAC,GAAG;QAC7C;QAEA,MAAMC,QAAQC,WAAW;YACvB,IAAI,CAACL,SAAS;gBACZA,UAAU;gBACV5C,OAAO,IAAII,MAAM,CAAC,2BAA2B,EAAE0C,gBAAgB;YACjE;QACF,GAAG;QAEH,MAAMI,SAAS,CAACzC;YACdiC,UAAUjC,MAAM,QAAQ;YACxB,MAAM0C,QAAQT,OAAO,KAAK,CAAC;YAC3B,KAAK,MAAMU,QAAQD,MACjB,IAAKC,KAAK,IAAI,IACd,IAAI;gBACF,MAAMC,SAAS1C,KAAK,KAAK,CAACyC;gBAC1B,IAAIC,OAAO,QAAQ,IAAI,CAACT,SAAS;oBAC/BA,UAAU;oBACVU,aAAaN;oBACbR,KAAK,MAAM,CAAE,cAAc,CAAC,QAAQU;oBACpCV,KAAK,MAAM,CAAE,cAAc,CAAC,QAAQK;oBAGpCL,KAAK,MAAM,CAAE,OAAO;oBACpBA,KAAK,MAAM,CAAE,OAAO;oBACpBzC,QAAQsD,OAAO,QAAQ;oBACvB;gBACF;YACF,EAAE,OAAM,CAER;QAEJ;QACAb,KAAK,MAAM,CAAE,EAAE,CAAC,QAAQU;QAExBV,KAAK,EAAE,CAAC,SAAS,CAAC5B;YAChB,IAAI,CAACgC,SAAS;gBACZA,UAAU;gBACVU,aAAaN;gBACbhD,OAAO,IAAII,MAAM,CAAC,uBAAuB,EAAEQ,IAAI,OAAO,EAAE;YAC1D;QACF;QACA4B,KAAK,EAAE,CAAC,QAAQ,CAACe,MAAMC;YACrB,IAAI,CAACZ,SAAS;gBACZA,UAAU;gBACVU,aAAaN;gBACb,MAAMS,MAAMD,SAAS,CAAC,OAAO,EAAEA,QAAQ,GAAG,CAAC,KAAK,EAAED,MAAM;gBACxDvD,OACE,IAAII,MAAM,CAAC,kBAAkB,EAAEqD,IAAI,aAAa,EAAEX,gBAAgB;YAEtE;QACF;IACF;AACF;AASA,eAAeY,iBAAiBtB,cAAsB;IAEpD,IAAIuB,kBAAkBvB;IACtB,IAAI1C,oBAAoB0C,iBAAiB;QACvC7C,MACE,qEACA6C;QAEF,IAAI;YACFuB,kBAAkB,MAAM/D,uBAAuBwC;YAC/C7C,MAAM,iCAAiCoE;QACzC,EAAE,OAAO/C,KAAK;YACZ,MAAM,IAAIR,MACR,CAAC,iLAAiL,EAAGQ,IAAc,OAAO,CAAC,sDAAsD,CAAC;QAEtQ;IACF;IAGA,IAAIC,gBAAgB;QAClB,MAAMlB,WAAWyB;QACjB,MAAMwC,gBAAgBtC;QACtB,IAAI3B,UACF,IAAIiE,CAAAA,iBAAiBA,kBAAkBD,iBAQrC,OAAOhE;aAR+C;YAEtDJ,MACE,uDACAqE;YAEFpC;QACF;IAIJ;IAGA,IAAI;QACF,OAAO,MAAMW,WAAWwB;IAC1B,EAAE,OAAO/C,KAAK;QACZiD,QAAQ,IAAI,CACV,CAAC,uDAAuD,EAAEjD,KAAK;QAEjE,OAAO+C;IACT;AACF;AAWO,MAAMG,4BAA4BC,oBAAAA,iBAAiBA;IAS9C,wBAAwB;QAChC,OAAO,IAAIC,yCAAAA,UAAUA,CAAC;YACpB,YAAYC,qBAAAA,cAAAA,CAAAA,MAAqB,CAAC,IAAIC,KAAK,GAAG;YAC9C,UAAU;gBAAE,OAAO;gBAAM,QAAQ;YAAK;YACtC,0BAA0B;QAC5B;IACF;IAEA,MAAgB,YAAYC,aAAsB,EAA2B;QAE3E,IAAI,IAAI,CAAC,KAAK,IAAIA,eAAe;YAC/B,IAAI;gBACF,MAAM,IAAI,CAAC,KAAK,EAAE;YACpB,EAAE,OAAOC,OAAO;gBACdP,QAAQ,KAAK,CAAC,2CAA2CO;YAC3D;YACA,IAAI,CAAC,KAAK,GAAGC;QACf;QAEA,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,KAAK;QAGjC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE;YACvB,MAAM1E,WAAW,MAAM+D,iBAAiB,IAAI,CAAC,WAAW;YACxD,IAAI,CAAC,aAAa,GAAG,MAAMY,kCAAAA,OAAiB,CAAC;gBAC3C,mBAAmB3E;gBACnB,iBAAiB;YACnB;QACF;QAEA,MAAM4E,UAAU,IAAI,CAAC,aAAa;QAClC,IAAIC,QAAQ,MAAMD,QAAQ,KAAK;QAI/B,IAAIC,AAAiB,MAAjBA,MAAM,MAAM,EAAQ;YACtB,MAAM,IAAI1E,QAAQ,CAAC2E,IAAMxB,WAAWwB,GAAGhF;YACvC+E,QAAQ,MAAMD,QAAQ,KAAK;QAC7B;QAEA,MAAMG,WAAWF,MAAM,MAAM,CAAC,CAACG,IAAM,eAAe,IAAI,CAACA,EAAE,GAAG;QAC9DpF,MACE,wCACAiF,MAAM,MAAM,EACZE,SAAS,MAAM,EACfF,MAAM,GAAG,CAAC,CAACG,IAAMA,EAAE,GAAG;QAExB,IAAI1C;QAEJ,IAAIkC,eACF,IAAIO,SAAS,MAAM,GAAG,GAAG;YAIvBzC,OAAOyC,QAAQ,CAACA,SAAS,MAAM,GAAG,EAAE;YACpC,MAAMzC,KAAK,YAAY;YACvB,MAAMA,KAAK,IAAI,CAACkC,eAAe;gBAC7B,SAAS;gBACT,WAAW;YACb;QACF,OAAO;YAELlC,OAAO,MAAMsC,QAAQ,OAAO;YAC5B,MAAMtC,KAAK,IAAI,CAACkC,eAAe;gBAC7B,SAAS;gBACT,WAAW;YACb;QACF;aACK;YAEL,MAAMS,gBAAgBjD;YACtB,IAAIkD;YAEJ,IAAID,iBAAiBJ,MAAM,MAAM,GAAG,GAAG;gBACrCK,cAAcL,MAAM,IAAI,CAAC,CAACG,IAAM3C,YAAY2C,OAAOC;gBAC/CC,cACFtF,MAAM,6BAA6BqF,iBAEnCrF,MACE,4DACAqF,eACAJ,MAAM,MAAM;YAGlB;YAGEvC,OADE4C,cACKA,cACEH,SAAS,MAAM,GAAG,IACpBA,QAAQ,CAACA,SAAS,MAAM,GAAG,EAAE,GAC3BF,MAAM,MAAM,GAAG,IACjBA,KAAK,CAACA,MAAM,MAAM,GAAG,EAAE,GAEvB,MAAMD,QAAQ,OAAO;YAG9B,MAAMtC,KAAK,YAAY;QACzB;QAGA,MAAMH,WAAWE,YAAYC;QAC7B,IAAIH,UACFD,aAAaC;aAKbvC,MACE;QAIJ,IAAI,CAAC,KAAK,GAAG,IAAIuF,yBAAAA,cAAcA,CAAC7C;QAChC,OAAO,IAAI,CAAC,KAAK;IACnB;IAEA,MAAa,UAAyB;QACpC,MAAM,KAAK,CAAC;QACZ,IAAI,IAAI,CAAC,aAAa,EAAE;YACtB,IAAI,CAAC,aAAa,CAAC,UAAU;YAC7B,IAAI,CAAC,aAAa,GAAG;QACvB;IACF;IAEU,uBAAyC;QACjD,OAAO;YACL;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ;oBACN,KAAK8C,qBAAAA,CAAAA,CAAAA,MACI,GACN,GAAG,GACH,QAAQ,GACR,QAAQ,CAAC;gBACd;gBACA,SAAS,OAAOC;oBACd,MAAM,EAAE9E,GAAG,EAAE,GAAG8E;oBAGhB,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAOC,GAAG;4BACVpB,QAAQ,KAAK,CAAC,2CAA2CoB;wBAC3D;wBACA,IAAI,CAAC,KAAK,GAAGZ;oBACf;oBAEA,IAAI,CAAC,KAAK,GAAG,MAAM,IAAI,CAAC,WAAW,CAACnE;oBAEpC,MAAMgF,aAAa,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;oBAC1C,MAAMC,QAAQjF,OAAO;oBAErB,OAAO;wBACL,SAAS;4BACP;gCAAE,MAAM;gCAAQ,MAAM,CAAC,sBAAsB,EAAEiF,OAAO;4BAAC;+BACnDD,aAAa,IAAI,CAAC,sBAAsB,CAACA,cAAc,EAAE;yBAC9D;oBACH;gBACF;YACF;YACA;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ,CAAC;gBACT,SAAS;oBACP,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAOD,GAAG;4BACVpB,QAAQ,KAAK,CAAC,8CAA8CoB;wBAC9D;wBACA,IAAI,CAAC,KAAK,GAAGZ;oBACf;oBACA,IAAI,IAAI,CAAC,aAAa,EAAE;wBACtB,IAAI,CAAC,aAAa,CAAC,UAAU;wBAC7B,IAAI,CAAC,aAAa,GAAG;oBACvB;oBACA3C;oBACA,OAAO,IAAI,CAAC,eAAe,CACzB;gBAEJ;YACF;SACD;IACH;IAjMA,YAAY0D,WAAmB,CAAE;QAC/B,KAAK,IAJP,uBAAQ,eAAR,SACA,uBAAQ,iBAAgC;QAItC,IAAI,CAAC,WAAW,GAAGA;IACrB;AA+LF;AAKO,MAAMC,WAAW;IACtB3B;IACAlC;IACAF;IACAT;AACF"}
|
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CDP WebSocket Proxy — standalone process.
|
|
3
3
|
*
|
|
4
|
-
* Holds a
|
|
5
|
-
* exposes a local WebSocket server. Midscene CLI processes connect to the
|
|
6
|
-
* instead of Chrome directly, so Chrome's "Allow remote debugging"
|
|
7
|
-
* popup only fires once (when the proxy connects).
|
|
4
|
+
* Holds a persistent WebSocket connection to Chrome's CDP endpoint and
|
|
5
|
+
* exposes a local WebSocket server. Midscene CLI processes connect to the
|
|
6
|
+
* proxy instead of Chrome directly, so Chrome's "Allow remote debugging"
|
|
7
|
+
* permission popup only fires once (when the proxy connects).
|
|
8
|
+
*
|
|
9
|
+
* Lifecycle notes:
|
|
10
|
+
* - When all downstream clients disconnect the proxy stays running but
|
|
11
|
+
* marks the upstream as needing reconnection. The actual reconnect is
|
|
12
|
+
* deferred to the moment the next client connects, so Chrome's CDP
|
|
13
|
+
* state (notably Target.setDiscoverTargets) is reset and the new
|
|
14
|
+
* client receives all targetCreated events.
|
|
15
|
+
* - On startup, if another proxy is already alive the new instance
|
|
16
|
+
* announces "duplicate proxy detected" on stderr and exits 0 without
|
|
17
|
+
* touching the existing metadata files.
|
|
8
18
|
*
|
|
9
19
|
* Exit conditions:
|
|
10
20
|
* 1. Upstream Chrome connection closes or errors.
|
|
11
21
|
* 2. No downstream client message for IDLE_TIMEOUT_MS (default 5 min).
|
|
12
22
|
* 3. SIGTERM / SIGINT.
|
|
23
|
+
* 4. Duplicate proxy detected on startup (exits 0 with stderr notice).
|
|
13
24
|
*
|
|
14
25
|
* Usage (spawned by mcp-tools-cdp.ts):
|
|
15
26
|
* node cdp-proxy.js <chrome-ws-endpoint>
|
|
16
27
|
*
|
|
17
28
|
* On startup, prints the proxy endpoint to stdout as a single JSON line:
|
|
18
29
|
* {"endpoint":"ws://127.0.0.1:<port>/devtools/browser"}
|
|
19
|
-
* and writes
|
|
30
|
+
* and writes:
|
|
31
|
+
* - PROXY_ENDPOINT_FILE — the local proxy URL above
|
|
32
|
+
* - PROXY_PID_FILE — this process's pid
|
|
33
|
+
* - PROXY_UPSTREAM_FILE — the Chrome endpoint the proxy is connected to,
|
|
34
|
+
* so callers can detect when the requested
|
|
35
|
+
* upstream has changed and replace the proxy.
|
|
20
36
|
*/
|
|
21
37
|
export {};
|
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
import { BaseMidsceneTools, type ToolDefinition } from '@midscene/shared/mcp';
|
|
2
2
|
import { PuppeteerAgent } from './puppeteer';
|
|
3
3
|
import { StaticPage } from './static';
|
|
4
|
+
/**
|
|
5
|
+
* Check if a previously spawned proxy process is still alive.
|
|
6
|
+
*/
|
|
7
|
+
declare function isProxyAlive(): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Read the Chrome endpoint that the running proxy is connected to.
|
|
10
|
+
*/
|
|
11
|
+
declare function readProxyUpstream(): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Kill the running proxy process and clear all CDP-mode metadata files
|
|
14
|
+
* (proxy endpoint/pid/upstream and the cross-command targetId).
|
|
15
|
+
*/
|
|
16
|
+
declare function killProxy(): void;
|
|
17
|
+
/**
|
|
18
|
+
* Get the proxy endpoint, spawning the proxy if needed.
|
|
19
|
+
* Falls back to direct connection if proxy cannot be started.
|
|
20
|
+
*
|
|
21
|
+
* If the user provides a page-level CDP URL, automatically resolves it
|
|
22
|
+
* to a browser-level endpoint via /json/version.
|
|
23
|
+
*/
|
|
24
|
+
declare function getProxyEndpoint(chromeEndpoint: string): Promise<string>;
|
|
4
25
|
/**
|
|
5
26
|
* Tools manager for Web CDP-mode MCP.
|
|
6
27
|
* Connects to an existing Chrome browser via CDP (Chrome DevTools Protocol) endpoint.
|
|
@@ -19,3 +40,13 @@ export declare class WebCdpMidsceneTools extends BaseMidsceneTools<PuppeteerAgen
|
|
|
19
40
|
destroy(): Promise<void>;
|
|
20
41
|
protected preparePlatformTools(): ToolDefinition[];
|
|
21
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Internal helpers exposed for unit tests. Not part of the public API.
|
|
45
|
+
*/
|
|
46
|
+
export declare const __test__: {
|
|
47
|
+
getProxyEndpoint: typeof getProxyEndpoint;
|
|
48
|
+
killProxy: typeof killProxy;
|
|
49
|
+
readProxyUpstream: typeof readProxyUpstream;
|
|
50
|
+
isProxyAlive: typeof isProxyAlive;
|
|
51
|
+
};
|
|
52
|
+
export {};
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"Browser use",
|
|
9
9
|
"Android use"
|
|
10
10
|
],
|
|
11
|
-
"version": "1.7.
|
|
11
|
+
"version": "1.7.5-beta-20260420031652.0",
|
|
12
12
|
"repository": "https://github.com/web-infra-dev/midscene",
|
|
13
13
|
"homepage": "https://midscenejs.com/",
|
|
14
14
|
"main": "./dist/lib/index.js",
|
|
@@ -110,9 +110,9 @@
|
|
|
110
110
|
"socket.io": "^4.8.1",
|
|
111
111
|
"socket.io-client": "4.8.1",
|
|
112
112
|
"ws": "^8.18.1",
|
|
113
|
-
"@midscene/core": "1.7.
|
|
114
|
-
"@midscene/
|
|
115
|
-
"@midscene/
|
|
113
|
+
"@midscene/core": "1.7.5-beta-20260420031652.0",
|
|
114
|
+
"@midscene/playground": "1.7.5-beta-20260420031652.0",
|
|
115
|
+
"@midscene/shared": "1.7.5-beta-20260420031652.0"
|
|
116
116
|
},
|
|
117
117
|
"devDependencies": {
|
|
118
118
|
"@playwright/test": "^1.45.0",
|