opera-devtools-mcp 0.7.0 → 0.8.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 +1 -1
- package/build/src/ToolHandler.js +9 -2
- package/build/src/bin/chrome-devtools.js +30 -97
- package/build/src/bin/opera-browser-cli.js +102 -0
- package/build/src/bin/opera-devtools-mcp.js +20 -1
- package/build/src/browser.js +18 -9
- package/build/src/daemon/client.js +46 -40
- package/build/src/daemon/daemon.js +62 -39
- package/build/src/opera/branding.js +4 -2
- package/build/src/opera/browserActivity.js +62 -0
- package/build/src/opera/browserCleanup.js +123 -0
- package/build/src/opera/browserErrors.js +66 -0
- package/build/src/opera/browserFlags.js +184 -38
- package/build/src/opera/browserTarget.js +513 -0
- package/build/src/opera/cdpErrors.js +391 -0
- package/build/src/opera/cliCommands.js +378 -0
- package/build/src/opera/cliOutput.js +284 -0
- package/build/src/opera/compactSnapshot.js +525 -0
- package/build/src/opera/config.js +166 -0
- package/build/src/opera/daemonLifecycle.js +257 -0
- package/build/src/opera/daemonLog.js +103 -0
- package/build/src/opera/daemonPidFile.js +83 -0
- package/build/src/opera/daemonShutdown.js +66 -0
- package/build/src/opera/daemonSocket.js +87 -0
- package/build/src/opera/daemonStreaming.js +130 -0
- package/build/src/opera/daemonToolCall.js +26 -0
- package/build/src/opera/detect.js +114 -0
- package/build/src/opera/doctor.js +317 -0
- package/build/src/opera/envConfig.js +229 -0
- package/build/src/opera/launcherNotice.js +116 -0
- package/build/src/opera/legacyBridgeCleanup.js +297 -0
- package/build/src/opera/logs.js +133 -0
- package/build/src/opera/mcpServerSupervisor.js +128 -0
- package/build/src/opera/migrationShared.js +164 -0
- package/build/src/opera/operaPages.js +56 -0
- package/build/src/opera/pageIdRouting.js +35 -0
- package/build/src/opera/pageRecovery.js +53 -0
- package/build/src/opera/profile.js +270 -0
- package/build/src/opera/refArgs.js +36 -0
- package/build/src/opera/serviceWorkerRetry.js +46 -4
- package/build/src/opera/setup.js +290 -0
- package/build/src/opera/skills/SKILL.md +160 -0
- package/build/src/opera/streamingTools.js +73 -0
- package/build/src/opera/suggestions.js +67 -0
- package/build/src/opera/toolHandlerHooks.js +25 -1
- package/build/src/opera/tools/opera.js +107 -38
- package/build/src/opera/urlResolver.js +69 -0
- package/build/src/opera/webStorageWarning.js +92 -0
- package/build/src/third_party/devtools-formatter-worker.js +1 -0
- package/build/src/third_party/devtools-heap-snapshot-worker.js +1 -0
- package/build/src/third_party/index.js +2 -1
- package/build/src/utils/url.js +6 -0
- package/build/src/version.js +1 -1
- package/package.json +12 -10
- package/build/src/bin/opera-devtools.js +0 -10
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Opera Norway AS. All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This file is an original work developed by Opera.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Node's Web Storage warning, and the one read of `localStorage` that provokes
|
|
9
|
+
* it.
|
|
10
|
+
*
|
|
11
|
+
* Node defines `localStorage` as a lazy global accessor. Reading it without
|
|
12
|
+
* `--localstorage-file` yields `undefined` and prints, once, to stderr:
|
|
13
|
+
*
|
|
14
|
+
* ExperimentalWarning: localStorage is not available because
|
|
15
|
+
* --localstorage-file was not provided.
|
|
16
|
+
*
|
|
17
|
+
* The bundled `debug` package reads it when it initializes
|
|
18
|
+
* (`node_modules/debug/src/browser.js` → `localstorage()`, reached through
|
|
19
|
+
* `lighthouse-logger` and `third_party/lighthouse-devtools-mcp-bundle.js`), and
|
|
20
|
+
* every entry point reaches that: the warning preceded a CLI command's own
|
|
21
|
+
* output, led the MCP server's stderr, and opened the daemon's log. The read
|
|
22
|
+
* only ever wants the "no storage here" answer its `try { return localStorage }
|
|
23
|
+
* catch {}` falls back to anyway; the warning is its entire effect.
|
|
24
|
+
*
|
|
25
|
+
* This module takes the accessor away first, replacing it with the `undefined`
|
|
26
|
+
* the read produces. It is deliberately neither `--disable-warning` nor
|
|
27
|
+
* `process.removeAllListeners('warning')`: those hide every experimental
|
|
28
|
+
* warning, including the ones worth reading, and the flag cannot reach a
|
|
29
|
+
* Windows `.cmd` shim or a plain `node build/…/bin.js`.
|
|
30
|
+
*
|
|
31
|
+
* The guard installs on import, so a process whose own imports load
|
|
32
|
+
* `third_party` first — the daemon — can preload this file instead; see
|
|
33
|
+
* `preloadWebStorageWarningGuardInChildren`.
|
|
34
|
+
*/
|
|
35
|
+
import fs from 'node:fs';
|
|
36
|
+
import process from 'node:process';
|
|
37
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
38
|
+
/**
|
|
39
|
+
* Removes the accessor, so the read becomes the `undefined` it already fell back
|
|
40
|
+
* to and prints nothing.
|
|
41
|
+
*
|
|
42
|
+
* Nothing happens on a Node without the accessor, or when the process was told
|
|
43
|
+
* where to keep Web Storage: `--localstorage-file` is the whole of the opt-in,
|
|
44
|
+
* and with it the getter hands back a real `Storage` that must survive.
|
|
45
|
+
*/
|
|
46
|
+
function installWebStorageWarningGuard() {
|
|
47
|
+
const descriptor = Object.getOwnPropertyDescriptor(globalThis, 'localStorage');
|
|
48
|
+
// `--localstorage-file` can arrive as a Node flag or through NODE_OPTIONS,
|
|
49
|
+
// which the daemon inherits from this process.
|
|
50
|
+
const configured = [
|
|
51
|
+
...process.execArgv,
|
|
52
|
+
...(process.env.NODE_OPTIONS?.split(/\s+/) ?? []),
|
|
53
|
+
].some(arg => arg === '--localstorage-file' || arg.startsWith('--localstorage-file='));
|
|
54
|
+
// No accessor — older Node, or Web Storage not exposed at all — leaves
|
|
55
|
+
// nothing to do. A second call lands here too: the install below replaces the
|
|
56
|
+
// accessor with a plain value, so the `get` is gone.
|
|
57
|
+
if (!descriptor?.get || configured) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
Object.defineProperty(globalThis, 'localStorage', {
|
|
61
|
+
value: undefined,
|
|
62
|
+
configurable: true,
|
|
63
|
+
writable: true,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
installWebStorageWarningGuard();
|
|
67
|
+
/**
|
|
68
|
+
* Hand the guard to every Node child this process spawns, by preloading it
|
|
69
|
+
* through `NODE_OPTIONS`.
|
|
70
|
+
*
|
|
71
|
+
* The daemon is the child that needs it: `src/daemon/daemon.ts` reaches
|
|
72
|
+
* `third_party/index.ts` through its own static imports, so no module of ours
|
|
73
|
+
* can run first in that process — only a `--import` preload can. The daemon
|
|
74
|
+
* passes its environment on to the MCP server it supervises, so one call at the
|
|
75
|
+
* CLI's entry point covers both.
|
|
76
|
+
*
|
|
77
|
+
* A guard file that is not there is not a reason to fail a spawn: this removes
|
|
78
|
+
* a warning, and a preload that cannot be resolved would abort the child.
|
|
79
|
+
*/
|
|
80
|
+
export function preloadWebStorageWarningGuardInChildren() {
|
|
81
|
+
const guardFile = fileURLToPath(new URL('./webStorageWarning.js', import.meta.url));
|
|
82
|
+
if (!fs.existsSync(guardFile)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const option = `--import=${pathToFileURL(guardFile).href}`;
|
|
86
|
+
const current = process.env.NODE_OPTIONS?.trim();
|
|
87
|
+
if (current?.includes(option)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
process.env.NODE_OPTIONS = current ? `${current} ${option}` : option;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=webStorageWarning.js.map
|
|
@@ -15361,3 +15361,4 @@ HOST_RUNTIME.workerScope.onmessage = function (event) {
|
|
|
15361
15361
|
}
|
|
15362
15362
|
};
|
|
15363
15363
|
HOST_RUNTIME.workerScope.postMessage('workerReady');
|
|
15364
|
+
//# sourceMappingURL=devtools-formatter-worker.js.map
|
|
@@ -10416,3 +10416,4 @@ class HeapSnapshotWorkerDispatcher {
|
|
|
10416
10416
|
const dispatcher = new HeapSnapshotWorkerDispatcher(HOST_RUNTIME.workerScope.postMessage.bind(HOST_RUNTIME.workerScope));
|
|
10417
10417
|
HOST_RUNTIME.workerScope.onmessage = dispatcher.dispatchMessage.bind(dispatcher);
|
|
10418
10418
|
HOST_RUNTIME.workerScope.postMessage('workerReady');
|
|
10419
|
+
//# sourceMappingURL=devtools-heap-snapshot-worker.js.map
|
|
@@ -181165,4 +181165,5 @@ const snapshot = snapshot$1;
|
|
|
181165
181165
|
const navigation = navigation$1;
|
|
181166
181166
|
const generateReport = generateReport$1;
|
|
181167
181167
|
|
|
181168
|
-
export { AsyncDisposableStack, Browser as BrowserEnum, CDPSessionEvent, CdpPage, Client, mcp as DevTools, DisposableStack, KnownDevices, ListRootsRequestSchema, ListRootsResultSchema, Locator, McpServer, Mutex$2 as Mutex, PipeTransport, PredefinedNetworkConditions, RootsListChangedNotificationSchema, ScreenRecorder, SetLevelRequestSchema, StdioClientTransport, StdioServerTransport, SuppressedError$1 as SuppressedError, ajv, detectBrowserPlatform, generateReport, getGcfEncode, getToonEncode, hideBin, navigation, puppeteer, resolveDefaultUserDataDir, index as semver, snapshot, Yargs as yargs, z$2 as zod };
|
|
181168
|
+
export { AsyncDisposableStack, Browser as BrowserEnum, CDPSessionEvent, CdpPage, Client, ConnectionClosedError, mcp as DevTools, DisposableStack, KnownDevices, ListRootsRequestSchema, ListRootsResultSchema, Locator, LoggingMessageNotificationSchema, McpServer, Mutex$2 as Mutex, PipeTransport, PredefinedNetworkConditions, RootsListChangedNotificationSchema, ScreenRecorder, SetLevelRequestSchema, StdioClientTransport, StdioServerTransport, SuppressedError$1 as SuppressedError, TargetCloseError, ajv, detectBrowserPlatform, generateReport, getGcfEncode, getToonEncode, hideBin, navigation, puppeteer, resolveDefaultUserDataDir, index as semver, snapshot, Yargs as yargs, z$2 as zod };
|
|
181169
|
+
//# sourceMappingURL=index.js.map
|
package/build/src/utils/url.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Copyright 2026 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
+
import { isOperaStartPage } from '../opera/operaPages.js';
|
|
6
7
|
const ALLOWED_PROTOCOLS = new Set(['http:', 'https:', 'ws:', 'wss:']);
|
|
7
8
|
const IPV4_LOOPBACK_REGEX = /^127(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d\d?)){3}$/;
|
|
8
9
|
/**
|
|
@@ -72,6 +73,11 @@ export function isAllowedUrl(url, options) {
|
|
|
72
73
|
path.startsWith('inspect'))) {
|
|
73
74
|
return true;
|
|
74
75
|
}
|
|
76
|
+
// Opera's start page, which is where every tab the user opens by hand
|
|
77
|
+
// begins; see `opera/operaPages.ts` for why it cannot be filtered out.
|
|
78
|
+
if (isOperaStartPage(parsed)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
75
81
|
}
|
|
76
82
|
if (parsed.protocol === 'chrome:' ||
|
|
77
83
|
parsed.protocol === 'chrome-untrusted:') {
|
package/build/src/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opera-devtools-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "MCP server for Opera DevTools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"opera-devtools-mcp": "./build/src/bin/opera-devtools-mcp.js"
|
|
8
|
-
"opera-devtools": "./build/src/bin/opera-devtools.js"
|
|
7
|
+
"opera-devtools-mcp": "./build/src/bin/opera-devtools-mcp.js"
|
|
9
8
|
},
|
|
10
9
|
"main": "./build/src/index.js",
|
|
11
10
|
"scripts": {
|
|
@@ -13,10 +12,13 @@
|
|
|
13
12
|
"clean": "node -e \"require('fs').rmSync('build', {recursive: true, force: true})\"",
|
|
14
13
|
"bundle": "npm run clean && npm run build && rollup -c rollup.config.js && node -e \"require('fs').rmSync('build/node_modules', {recursive: true, force: true})\" && node scripts/append-lighthouse-notices.ts",
|
|
15
14
|
"build": "tsc && node scripts/post-build.ts",
|
|
15
|
+
"prepublishOnly": "npm run bundle",
|
|
16
16
|
"typecheck": "tsc --noEmit",
|
|
17
17
|
"format": "eslint --cache --fix . && prettier --write --cache .",
|
|
18
18
|
"check-format": "eslint --cache . && prettier --check --cache .;",
|
|
19
19
|
"gen": "npm run build && npm run docs:generate && npm run cli:generate && npm run update-metrics && npm run format",
|
|
20
|
+
"link": "node scripts/link.ts",
|
|
21
|
+
"link:cli": "node scripts/link.ts --with-cli",
|
|
20
22
|
"docs:generate": "node scripts/generate-docs.ts",
|
|
21
23
|
"start": "npm run build && node build/src/bin/opera-devtools-mcp.js",
|
|
22
24
|
"start-debug": "DEBUG=mcp:* DEBUG_COLORS=false npm run build && node build/src/bin/opera-devtools-mcp.js",
|
|
@@ -35,7 +37,9 @@
|
|
|
35
37
|
"eval": "npm run build && node scripts/eval_gemini.ts",
|
|
36
38
|
"count-tokens": "node scripts/count_tokens.ts",
|
|
37
39
|
"profile": "npm run build && node scripts/profile/profile_mcp.ts",
|
|
38
|
-
"test:memory": "npm run build && node scripts/profile/profile_mcp.ts --threshold-percentage 1"
|
|
40
|
+
"test:memory": "npm run build && node scripts/profile/profile_mcp.ts --threshold-percentage 1",
|
|
41
|
+
"test:stress": "npm run build && STRESS_ALLOW_PROCESS_KILLS=true node --import ./build/tests/setup.js --test --test-force-exit --test-timeout=600000 build/tests/stress/runner.test.js",
|
|
42
|
+
"test:stress:docker": "node scripts/stress-docker.js"
|
|
39
43
|
},
|
|
40
44
|
"files": [
|
|
41
45
|
"build/src",
|
|
@@ -51,6 +55,9 @@
|
|
|
51
55
|
},
|
|
52
56
|
"homepage": "https://github.com/operasoftware/opera-devtools-mcp#readme",
|
|
53
57
|
"mcpName": "io.github.operasoftware/opera-devtools-mcp",
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@toon-format/toon": "^4.1.0"
|
|
60
|
+
},
|
|
54
61
|
"devDependencies": {
|
|
55
62
|
"@blackwell-systems/gcf": "^2.2.2",
|
|
56
63
|
"@eslint/js": "^10.0.1",
|
|
@@ -60,7 +67,6 @@
|
|
|
60
67
|
"@rollup/plugin-json": "^6.1.0",
|
|
61
68
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
62
69
|
"@stylistic/eslint-plugin": "^5.4.0",
|
|
63
|
-
"@toon-format/toon": "^4.1.0",
|
|
64
70
|
"@types/filesystem": "^0.0.36",
|
|
65
71
|
"@types/node": "^26.0.0",
|
|
66
72
|
"@types/semver": "^7.7.1",
|
|
@@ -87,13 +93,9 @@
|
|
|
87
93
|
"zod": "^3.25.76"
|
|
88
94
|
},
|
|
89
95
|
"peerDependencies": {
|
|
90
|
-
"@blackwell-systems/gcf": "^2.2.2"
|
|
91
|
-
"@toon-format/toon": "^4.1.0"
|
|
96
|
+
"@blackwell-systems/gcf": "^2.2.2"
|
|
92
97
|
},
|
|
93
98
|
"peerDependenciesMeta": {
|
|
94
|
-
"@toon-format/toon": {
|
|
95
|
-
"optional": true
|
|
96
|
-
},
|
|
97
99
|
"@blackwell-systems/gcf": {
|
|
98
100
|
"optional": true
|
|
99
101
|
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* @license
|
|
4
|
-
* Copyright 2026 Opera Software AS.
|
|
5
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
6
|
-
*/
|
|
7
|
-
// Opera-named bin entry (see `package.json` `bin`). The implementation lives in
|
|
8
|
-
// the upstream-owned file and is branded via `src/opera/branding.ts`.
|
|
9
|
-
import './chrome-devtools.js';
|
|
10
|
-
//# sourceMappingURL=opera-devtools.js.map
|