opera-devtools-mcp 0.7.0 → 0.8.1

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.
Files changed (55) hide show
  1. package/README.md +1 -1
  2. package/build/src/ToolHandler.js +9 -2
  3. package/build/src/bin/chrome-devtools.js +30 -97
  4. package/build/src/bin/opera-browser-cli.js +102 -0
  5. package/build/src/bin/opera-devtools-mcp.js +20 -1
  6. package/build/src/browser.js +18 -9
  7. package/build/src/daemon/client.js +46 -40
  8. package/build/src/daemon/daemon.js +62 -39
  9. package/build/src/opera/branding.js +4 -2
  10. package/build/src/opera/browserActivity.js +62 -0
  11. package/build/src/opera/browserCleanup.js +123 -0
  12. package/build/src/opera/browserErrors.js +66 -0
  13. package/build/src/opera/browserFlags.js +184 -38
  14. package/build/src/opera/browserTarget.js +513 -0
  15. package/build/src/opera/cdpErrors.js +391 -0
  16. package/build/src/opera/cliCommands.js +378 -0
  17. package/build/src/opera/cliOutput.js +284 -0
  18. package/build/src/opera/compactSnapshot.js +525 -0
  19. package/build/src/opera/config.js +166 -0
  20. package/build/src/opera/daemonLifecycle.js +257 -0
  21. package/build/src/opera/daemonLog.js +103 -0
  22. package/build/src/opera/daemonPidFile.js +83 -0
  23. package/build/src/opera/daemonShutdown.js +66 -0
  24. package/build/src/opera/daemonSocket.js +87 -0
  25. package/build/src/opera/daemonStreaming.js +130 -0
  26. package/build/src/opera/daemonToolCall.js +26 -0
  27. package/build/src/opera/detect.js +114 -0
  28. package/build/src/opera/doctor.js +317 -0
  29. package/build/src/opera/envConfig.js +229 -0
  30. package/build/src/opera/launcherNotice.js +116 -0
  31. package/build/src/opera/legacyBridgeCleanup.js +297 -0
  32. package/build/src/opera/logs.js +133 -0
  33. package/build/src/opera/mcpServerSupervisor.js +128 -0
  34. package/build/src/opera/migrationShared.js +164 -0
  35. package/build/src/opera/operaPages.js +56 -0
  36. package/build/src/opera/pageIdRouting.js +35 -0
  37. package/build/src/opera/pageRecovery.js +53 -0
  38. package/build/src/opera/profile.js +270 -0
  39. package/build/src/opera/refArgs.js +36 -0
  40. package/build/src/opera/serviceWorkerRetry.js +46 -4
  41. package/build/src/opera/setup.js +290 -0
  42. package/build/src/opera/skills/SKILL.md +160 -0
  43. package/build/src/opera/streamingTools.js +73 -0
  44. package/build/src/opera/suggestions.js +67 -0
  45. package/build/src/opera/toolHandlerHooks.js +25 -1
  46. package/build/src/opera/tools/opera.js +107 -38
  47. package/build/src/opera/urlResolver.js +69 -0
  48. package/build/src/opera/webStorageWarning.js +92 -0
  49. package/build/src/third_party/devtools-formatter-worker.js +1 -0
  50. package/build/src/third_party/devtools-heap-snapshot-worker.js +1 -0
  51. package/build/src/third_party/index.js +2 -1
  52. package/build/src/utils/url.js +6 -0
  53. package/build/src/version.js +1 -1
  54. package/package.json +12 -10
  55. 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
@@ -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:') {
@@ -5,5 +5,5 @@
5
5
  *
6
6
  * Modified by Opera Software AS.
7
7
  */
8
- export const VERSION = '0.7.0';
8
+ export const VERSION = '0.8.0';
9
9
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "opera-devtools-mcp",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
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