@mcp-b/chrome-devtools-mcp 2.0.2 → 2.0.6

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 (149) hide show
  1. package/README.md +120 -10
  2. package/build/src/McpContext.js +69 -5
  3. package/build/src/browser.js +157 -51
  4. package/build/src/cli.js +11 -5
  5. package/build/src/formatters/IssueFormatter.js +190 -0
  6. package/build/src/main.js +83 -2
  7. package/build/src/telemetry/clearcut-logger.js +102 -0
  8. package/build/src/telemetry/flag-utils.js +45 -0
  9. package/build/src/telemetry/metric-utils.js +14 -0
  10. package/build/src/telemetry/persistence.js +53 -0
  11. package/build/src/telemetry/types.js +33 -0
  12. package/build/src/telemetry/watchdog/clearcut-sender.js +201 -0
  13. package/build/src/telemetry/watchdog/main.js +127 -0
  14. package/build/src/telemetry/watchdog-client.js +60 -0
  15. package/build/src/third_party/devtools-formatter-worker.js +7 -0
  16. package/build/src/third_party/index.js +1 -1
  17. package/build/src/tools/browser.js +92 -0
  18. package/build/src/tools/extension.js +31 -0
  19. package/build/src/tools/extensions.js +79 -0
  20. package/build/src/tools/input.js +6 -1
  21. package/build/src/tools/pages.js +7 -1
  22. package/build/src/tools/script.js +31 -4
  23. package/build/src/tools/tools.js +4 -0
  24. package/build/src/transports/CDPClientTransport.js +184 -0
  25. package/build/src/transports/WebMCPBridgeScript.js +11 -2
  26. package/build/src/utils/ExtensionRegistry.js +35 -0
  27. package/build/src/utils/string.js +36 -0
  28. package/build/vendor/chrome-devtools-frontend/front_end/core/common/Base64.js +20 -2
  29. package/build/vendor/chrome-devtools-frontend/front_end/core/common/Debouncer.js +8 -1
  30. package/build/vendor/chrome-devtools-frontend/front_end/core/common/Gzip.js +11 -0
  31. package/build/vendor/chrome-devtools-frontend/front_end/core/common/Object.js +6 -1
  32. package/build/vendor/chrome-devtools-frontend/front_end/core/common/ParsedURL.js +3 -0
  33. package/build/vendor/chrome-devtools-frontend/front_end/core/common/ResourceType.js +6 -0
  34. package/build/vendor/chrome-devtools-frontend/front_end/core/common/Revealer.js +0 -5
  35. package/build/vendor/chrome-devtools-frontend/front_end/core/common/Settings.js +18 -8
  36. package/build/vendor/chrome-devtools-frontend/front_end/core/host/AidaClient.js +24 -0
  37. package/build/vendor/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHostStub.js +11 -3
  38. package/build/vendor/chrome-devtools-frontend/front_end/core/host/ResourceLoader.js +1 -1
  39. package/build/vendor/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +27 -20
  40. package/build/vendor/chrome-devtools-frontend/front_end/core/i18n/collect-ui-strings.js +7 -8
  41. package/build/vendor/chrome-devtools-frontend/front_end/core/i18n/generate-locales-js.js +4 -5
  42. package/build/vendor/chrome-devtools-frontend/front_end/core/platform/ArrayUtilities.js +10 -0
  43. package/build/vendor/chrome-devtools-frontend/front_end/core/platform/StringUtilities.js +63 -12
  44. package/build/vendor/chrome-devtools-frontend/front_end/core/protocol_client/CDPConnection.js +1 -0
  45. package/build/vendor/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +4 -1
  46. package/build/vendor/chrome-devtools-frontend/front_end/core/root/ExperimentNames.js +30 -0
  47. package/build/vendor/chrome-devtools-frontend/front_end/core/root/root.js +2 -1
  48. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/AnimationModel.js +0 -4
  49. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +69 -9
  50. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSMetadata.js +6 -6
  51. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSModel.js +28 -13
  52. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSProperty.js +1 -1
  53. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParserMatchers.js +6 -0
  54. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/ConsoleModel.js +0 -2
  55. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CookieModel.js +1 -1
  56. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/DOMModel.js +170 -13
  57. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +5 -39
  58. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/HeapProfilerModel.js +8 -1
  59. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +20 -5
  60. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +12 -21
  61. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/OverlayModel.js +19 -6
  62. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +5 -1
  63. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/ResourceTreeModel.js +8 -5
  64. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +15 -10
  65. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/SourceMapManager.js +1 -1
  66. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/SourceMapScopesInfo.js +13 -27
  67. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/Target.js +3 -1
  68. package/build/vendor/chrome-devtools-frontend/front_end/generated/ARIAProperties.js +1 -7
  69. package/build/vendor/chrome-devtools-frontend/front_end/generated/Deprecation.js +1 -16
  70. package/build/vendor/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +82 -22
  71. package/build/vendor/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +265 -123
  72. package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.js +2 -1
  73. package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js +10 -16
  74. package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js +97 -26
  75. package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AICallTree.js +35 -0
  76. package/build/vendor/chrome-devtools-frontend/front_end/models/annotations/AnnotationRepository.js +163 -0
  77. package/build/vendor/chrome-devtools-frontend/front_end/models/annotations/AnnotationType.js +10 -0
  78. package/build/vendor/chrome-devtools-frontend/front_end/models/annotations/annotations.js +5 -0
  79. package/build/vendor/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +5 -3
  80. package/build/vendor/chrome-devtools-frontend/front_end/models/bindings/DebuggerLanguagePlugins.js +29 -58
  81. package/build/vendor/chrome-devtools-frontend/front_end/models/bindings/DebuggerWorkspaceBinding.js +7 -45
  82. package/build/vendor/chrome-devtools-frontend/front_end/models/emulation/DeviceModeModel.js +1 -1
  83. package/build/vendor/chrome-devtools-frontend/front_end/models/emulation/EmulatedDevices.js +14 -0
  84. package/build/vendor/chrome-devtools-frontend/front_end/models/formatter/FormatterWorkerPool.js +8 -5
  85. package/build/vendor/chrome-devtools-frontend/front_end/models/greendev/Prototypes.js +33 -0
  86. package/build/vendor/chrome-devtools-frontend/front_end/models/greendev/greendev.js +4 -0
  87. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/ContrastCheckTrigger.js +2 -2
  88. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/CookieIssue.js +0 -21
  89. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/CorsIssue.js +1 -38
  90. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/IssueAggregator.js +8 -0
  91. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/IssuesManager.js +6 -12
  92. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/PermissionElementIssue.js +243 -0
  93. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementActivationDisabled.md +7 -0
  94. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementActivationDisabledWithOccluder.md +9 -0
  95. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementActivationDisabledWithOccluderParent.md +9 -0
  96. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementCspFrameAncestorsMissing.md +5 -0
  97. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementFencedFrameDisallowed.md +5 -0
  98. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementFontSizeTooLarge.md +5 -0
  99. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementFontSizeTooSmall.md +5 -0
  100. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementGeolocationDeprecated.md +5 -0
  101. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInsetBoxShadowUnsupported.md +5 -0
  102. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidDisplayStyle.md +5 -0
  103. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidSizeValue.md +5 -0
  104. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidType.md +5 -0
  105. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidTypeActivation.md +5 -0
  106. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementLowContrast.md +5 -0
  107. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementNonOpaqueColor.md +5 -0
  108. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementPaddingBottomUnsupported.md +6 -0
  109. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementPaddingRightUnsupported.md +6 -0
  110. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementPermissionsPolicyBlocked.md +5 -0
  111. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementRegistrationFailed.md +5 -0
  112. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementRequestInProgress.md +5 -0
  113. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementSecurityChecksFailed.md +5 -0
  114. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementTypeNotSupported.md +5 -0
  115. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementUntrustedEvent.md +7 -0
  116. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/issues_manager.js +2 -1
  117. package/build/vendor/chrome-devtools-frontend/front_end/models/logs/NetworkLog.js +0 -8
  118. package/build/vendor/chrome-devtools-frontend/front_end/models/source_map_scopes/NamesResolver.js +4 -8
  119. package/build/vendor/chrome-devtools-frontend/front_end/models/stack_trace/StackTrace.js +30 -1
  120. package/build/vendor/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceImpl.js +70 -1
  121. package/build/vendor/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceModel.js +82 -30
  122. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/EventsSerializer.js +10 -2
  123. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/LanternComputationData.js +2 -2
  124. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/ModelImpl.js +0 -3
  125. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/Processor.js +18 -19
  126. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/Styles.js +12 -4
  127. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/extras/Initiators.js +46 -0
  128. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/extras/TraceTree.js +4 -3
  129. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/extras/extras.js +1 -0
  130. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/LargestImagePaintHandler.js +2 -2
  131. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/LayoutShiftsHandler.js +1 -1
  132. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.js +6 -0
  133. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/NetworkRequestsHandler.js +10 -1
  134. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/PageLoadMetricsHandler.js +44 -27
  135. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/helpers/Timing.js +9 -2
  136. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/Common.js +1 -6
  137. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +2 -2
  138. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/LCPDiscovery.js +2 -4
  139. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +3 -2
  140. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/RenderBlocking.js +1 -1
  141. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +33 -11
  142. package/build/vendor/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js +51 -18
  143. package/build/vendor/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js +1 -1
  144. package/build/vendor/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/scopes.js +4 -0
  145. package/build/vendor/chrome-devtools-frontend/mcp/HostBindings.js +4 -0
  146. package/build/vendor/chrome-devtools-frontend/mcp/mcp.js +4 -0
  147. package/package.json +28 -21
  148. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/SameSiteInvalidSameParty.md +0 -8
  149. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/SameSiteSamePartyCrossPartyContextSet.md +0 -10
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  [![27 Tools](https://img.shields.io/badge/MCP_Tools-27-green?style=flat-square)](./docs/tool-reference.md)
9
9
  [![Chrome](https://img.shields.io/badge/Chrome-DevTools-4285F4?style=flat-square&logo=googlechrome)](https://developer.chrome.com/docs/devtools/)
10
10
 
11
- 📖 **[WebMCP Documentation](https://docs.mcp-b.ai)** | 🚀 **[Quick Start](https://docs.mcp-b.ai/quickstart)** | 🔌 **[Connecting Agents](https://docs.mcp-b.ai/connecting-agents)** | 🎯 **[Chrome DevTools Quickstart](https://github.com/WebMCP-org/chrome-devtools-quickstart)**
11
+ **[WebMCP Documentation](https://docs.mcp-b.ai)** | **[Quick Start](https://docs.mcp-b.ai/quickstart)** | **[Connecting Agents](https://docs.mcp-b.ai/connecting-agents)** | **[Chrome DevTools Quickstart](https://github.com/WebMCP-org/chrome-devtools-quickstart)**
12
12
 
13
13
  **@mcp-b/chrome-devtools-mcp** lets AI coding agents like Claude, Gemini, Cursor, and Copilot control and inspect a live Chrome browser via the Model Context Protocol (MCP). Get performance insights, debug network requests, take screenshots, and interact with website-specific MCP tools through WebMCP integration.
14
14
 
@@ -48,13 +48,13 @@ This fork adds **WebMCP integration** - the ability to call MCP tools that are r
48
48
 
49
49
  | Feature | Chrome DevTools MCP | @mcp-b/chrome-devtools-mcp |
50
50
  |---------|--------------------|-----------------------------|
51
- | Browser automation | | |
52
- | Performance analysis | | |
53
- | Network inspection | | |
54
- | Screenshot/snapshot | | |
55
- | **Call website MCP tools** | | |
56
- | **List website MCP tools** | | |
57
- | **AI-driven tool development** | | |
51
+ | Browser automation | Yes | Yes |
52
+ | Performance analysis | Yes | Yes |
53
+ | Network inspection | Yes | Yes |
54
+ | Screenshot/snapshot | Yes | Yes |
55
+ | **Call website MCP tools** | No | Yes |
56
+ | **List website MCP tools** | No | Yes |
57
+ | **AI-driven tool development** | No | Yes |
58
58
 
59
59
  The key addition is automatic WebMCP tool discovery and registration. When you visit a page with [@mcp-b/global](https://www.npmjs.com/package/@mcp-b/global), its tools are automatically registered as first-class MCP tools that your AI agent can call directly.
60
60
 
@@ -366,6 +366,30 @@ The same way `@mcp-b/chrome-devtools-mcp` can be configured for JetBrains Junie
366
366
 
367
367
  </details>
368
368
 
369
+ <details>
370
+ <summary>Katalon Studio</summary>
371
+
372
+ The Chrome DevTools MCP server can be used with <a href="https://docs.katalon.com/katalon-studio/studioassist/mcp-servers/setting-up-chrome-devtools-mcp-server-for-studioassist">Katalon StudioAssist</a> via an MCP proxy.
373
+
374
+ **Step 1:** Install the MCP proxy by following the <a href="https://docs.katalon.com/katalon-studio/studioassist/mcp-servers/setting-up-mcp-proxy-for-stdio-mcp-servers">MCP proxy setup guide</a>.
375
+
376
+ **Step 2:** Start the Chrome DevTools MCP server with the proxy:
377
+
378
+ ```bash
379
+ mcp-proxy --transport streamablehttp --port 8080 -- npx -y @mcp-b/chrome-devtools-mcp@latest
380
+ ```
381
+
382
+ **Note:** You may need to pick another port if 8080 is already in use.
383
+
384
+ **Step 3:** In Katalon Studio, add the server to StudioAssist with the following settings:
385
+
386
+ - **Connection URL:** `http://127.0.0.1:8080/mcp`
387
+ - **Transport type:** `HTTP`
388
+
389
+ Once connected, the Chrome DevTools MCP tools will be available in StudioAssist.
390
+
391
+ </details>
392
+
369
393
  <details>
370
394
  <summary>Kiro</summary>
371
395
 
@@ -375,6 +399,25 @@ Or, from the IDE **Activity Bar** > `Kiro` > `MCP Servers` > `Click Open MCP Con
375
399
 
376
400
  </details>
377
401
 
402
+ <details>
403
+ <summary>OpenCode</summary>
404
+
405
+ Add the following configuration to your `opencode.json` file. If you don't have one, create it at `~/.config/opencode/opencode.json` (<a href="https://opencode.ai/docs/mcp-servers">guide</a>):
406
+
407
+ ```json
408
+ {
409
+ "$schema": "https://opencode.ai/config.json",
410
+ "mcp": {
411
+ "chrome-devtools": {
412
+ "type": "local",
413
+ "command": ["npx", "-y", "@mcp-b/chrome-devtools-mcp@latest"]
414
+ }
415
+ }
416
+ }
417
+ ```
418
+
419
+ </details>
420
+
378
421
  <details>
379
422
  <summary>Qoder</summary>
380
423
 
@@ -546,6 +589,11 @@ The Chrome DevTools MCP server supports the following configuration option:
546
589
 
547
590
  <!-- BEGIN AUTO GENERATED OPTIONS -->
548
591
 
592
+ - **`--autoConnect`**
593
+ If specified, automatically connects to a browser (Chrome 144+) running in the user data directory identified by the channel param. Requires the remote debugging server to be started in the Chrome instance via chrome://inspect/#remote-debugging.
594
+ - **Type:** boolean
595
+ - **Default:** `true`
596
+
549
597
  - **`--browserUrl`, `-u`**
550
598
  Connect to a running, debuggable Chrome instance (e.g. `http://127.0.0.1:9222`). For more details see: https://github.com/ChromeDevTools/chrome-devtools-mcp#connecting-to-a-running-chrome-instance.
551
599
  - **Type:** string
@@ -600,6 +648,10 @@ The Chrome DevTools MCP server supports the following configuration option:
600
648
  Additional arguments for Chrome. Only applies when Chrome is launched by `@mcp-b/chrome-devtools-mcp`.
601
649
  - **Type:** array
602
650
 
651
+ - **`--ignoreDefaultChromeArg`**
652
+ Explicitly disable default arguments for Chrome. Only applies when Chrome is launched by `@mcp-b/chrome-devtools-mcp`.
653
+ - **Type:** array
654
+
603
655
  - **`--categoryEmulation`**
604
656
  Set to false to exclude tools related to emulation.
605
657
  - **Type:** boolean
@@ -683,9 +735,65 @@ the browser is closed.
683
735
 
684
736
  ### Connecting to a running Chrome instance
685
737
 
686
- You can connect to a running Chrome instance by using the `--browser-url` option. This is useful if you want to use your existing Chrome profile or if you are running the MCP server in a sandboxed environment that does not allow starting a new Chrome instance.
738
+ By default, the Chrome DevTools MCP server will start a new Chrome instance with a dedicated profile. This might not be ideal in all situations:
687
739
 
688
- Here is a step-by-step guide on how to connect to a running Chrome Stable instance:
740
+ - If you would like to maintain the same application state when alternating between manual site testing and agent-driven testing.
741
+ - When the MCP needs to sign into a website. Some accounts may prevent sign-in when the browser is controlled via WebDriver (the default launch mechanism for the Chrome DevTools MCP server).
742
+ - If you're running your LLM inside a sandboxed environment, but you would like to connect to a Chrome instance that runs outside the sandbox.
743
+
744
+ In these cases, start Chrome first and let the Chrome DevTools MCP server connect to it. There are two ways to do so:
745
+
746
+ - **Automatic connection (available in Chrome 144+)**: best for sharing state between manual and agent-driven testing.
747
+ - **Manual connection via remote debugging port**: best when running inside a sandboxed environment.
748
+
749
+ #### Automatically connecting to a running Chrome instance
750
+
751
+ **Step 1:** Set up remote debugging in Chrome
752
+
753
+ In Chrome (>= M144), do the following to set up remote debugging:
754
+
755
+ 1. Navigate to `chrome://inspect/#remote-debugging` to enable remote debugging.
756
+ 2. Follow the dialog UI to allow or disallow incoming debugging connections.
757
+
758
+ **Step 2:** Configure the MCP server to automatically connect
759
+
760
+ To connect the `@mcp-b/chrome-devtools-mcp` server to the running Chrome instance, use
761
+ the `--autoConnect` command line argument (enabled by default):
762
+
763
+ ```json
764
+ {
765
+ "mcpServers": {
766
+ "chrome-devtools": {
767
+ "command": "npx",
768
+ "args": ["@mcp-b/chrome-devtools-mcp@latest", "--autoConnect"]
769
+ }
770
+ }
771
+ }
772
+ ```
773
+
774
+ **Step 3:** Test your setup
775
+
776
+ Make sure your browser is running. Open your MCP client and run the following prompt:
777
+
778
+ ```
779
+ Check the performance of https://developers.chrome.com
780
+ ```
781
+
782
+ > [!NOTE]
783
+ > The `autoConnect` option requires the user to start Chrome. If the user has multiple active profiles, the MCP server will connect to the default profile (as determined by Chrome). The MCP server has access to all open windows for the selected profile.
784
+
785
+ The Chrome DevTools MCP server will try to connect to your running Chrome
786
+ instance. It shows a dialog asking for user permission.
787
+
788
+ Clicking **Allow** results in the Chrome DevTools MCP server opening
789
+ [developers.chrome.com](http://developers.chrome.com) and taking a performance
790
+ trace.
791
+
792
+ #### Manual connection using port forwarding
793
+
794
+ You can connect to a running Chrome instance by using the `--browser-url` option. This is useful if you are running the MCP server in a sandboxed environment that does not allow starting a new Chrome instance.
795
+
796
+ Here is a step-by-step guide:
689
797
 
690
798
  **Step 1: Configure the MCP client**
691
799
 
@@ -748,6 +856,8 @@ For more details on remote debugging, see the [Chrome DevTools documentation](ht
748
856
 
749
857
  ## Known limitations
750
858
 
859
+ See [Troubleshooting](./docs/troubleshooting.md) for the full list.
860
+
751
861
  ### Operating system sandboxes
752
862
 
753
863
  Some MCP clients allow sandboxing the MCP server using macOS Seatbelt or Linux
@@ -89,6 +89,7 @@ export class McpContext {
89
89
  #networkConditionsMap = new WeakMap();
90
90
  #cpuThrottlingRateMap = new WeakMap();
91
91
  #geolocationMap = new WeakMap();
92
+ #bypassCSPMap = new WeakMap();
92
93
  #dialog;
93
94
  #nextSnapshotId = 1;
94
95
  #traceResults = [];
@@ -160,7 +161,7 @@ export class McpContext {
160
161
  // Skip chrome:// and devtools:// pages
161
162
  const url = page.url();
162
163
  if (url.startsWith('chrome://') ||
163
- url.startsWith('chrome-extension://') ||
164
+ (!this.#options.includeExtensionPages && url.startsWith('chrome-extension://')) ||
164
165
  url.startsWith('devtools://') ||
165
166
  url === 'about:blank') {
166
167
  continue;
@@ -302,7 +303,7 @@ export class McpContext {
302
303
  // Skip chrome:// and devtools:// pages
303
304
  const url = page.url();
304
305
  if (url.startsWith('chrome://') ||
305
- url.startsWith('chrome-extension://') ||
306
+ (!this.#options.includeExtensionPages && url.startsWith('chrome-extension://')) ||
306
307
  url.startsWith('devtools://')) {
307
308
  return;
308
309
  }
@@ -317,7 +318,7 @@ export class McpContext {
317
318
  // Skip internal pages
318
319
  const newUrl = page.url();
319
320
  if (newUrl.startsWith('chrome://') ||
320
- newUrl.startsWith('chrome-extension://') ||
321
+ (!this.#options.includeExtensionPages && newUrl.startsWith('chrome-extension://')) ||
321
322
  newUrl.startsWith('devtools://') ||
322
323
  newUrl === 'about:blank') {
323
324
  return;
@@ -616,6 +617,20 @@ export class McpContext {
616
617
  const page = this.getSelectedPage();
617
618
  return this.#geolocationMap.get(page) ?? null;
618
619
  }
620
+ async setBypassCSP(enabled) {
621
+ const page = this.getSelectedPage();
622
+ await page.setBypassCSP(enabled);
623
+ if (enabled) {
624
+ this.#bypassCSPMap.set(page, true);
625
+ }
626
+ else {
627
+ this.#bypassCSPMap.delete(page);
628
+ }
629
+ }
630
+ getBypassCSP() {
631
+ const page = this.getSelectedPage();
632
+ return this.#bypassCSPMap.get(page) ?? false;
633
+ }
619
634
  setIsRunningPerformanceTrace(x) {
620
635
  this.#isRunningTrace = x;
621
636
  }
@@ -916,7 +931,8 @@ export class McpContext {
916
931
  try {
917
932
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'chrome-devtools-mcp-'));
918
933
  const filename = path.join(dir, `screenshot.${getExtensionFromMimeType(mimeType)}`);
919
- await fs.writeFile(filename, data);
934
+ // Use mode 0o600 (owner read/write only) for secure temp file creation
935
+ await fs.writeFile(filename, data, { mode: 0o600 });
920
936
  return { filename };
921
937
  }
922
938
  catch (err) {
@@ -927,7 +943,8 @@ export class McpContext {
927
943
  async saveFile(data, filename) {
928
944
  try {
929
945
  const filePath = path.resolve(filename);
930
- await fs.writeFile(filePath, data);
946
+ // Use mode 0o644 (owner read/write, others read) for user-specified paths
947
+ await fs.writeFile(filePath, data, { mode: 0o644 });
931
948
  return { filename };
932
949
  }
933
950
  catch (err) {
@@ -1051,6 +1068,53 @@ export class McpContext {
1051
1068
  };
1052
1069
  }
1053
1070
  }
1071
+ /**
1072
+ * Evaluate a JavaScript expression in a Chrome extension's background context.
1073
+ *
1074
+ * Supports both MV3 service workers (type "service_worker") and MV2/dev-mode
1075
+ * background pages (type "background_page"). Uses waitForTarget to handle
1076
+ * cases where the worker hasn't been discovered yet.
1077
+ *
1078
+ * @param expression - JavaScript expression to evaluate.
1079
+ * @param extensionId - Optional extension ID. If omitted, uses the first found.
1080
+ * @returns The result of the evaluation.
1081
+ */
1082
+ async evaluateInExtensionWorker(expression, extensionId) {
1083
+ const isExtensionBackground = (t) => (t.type() === 'service_worker' || t.type() === 'background_page') &&
1084
+ t.url().startsWith('chrome-extension://') &&
1085
+ (!extensionId || t.url().includes(extensionId));
1086
+ // First check already-known targets
1087
+ let target = this.browser.targets().find(isExtensionBackground);
1088
+ // If not found, wait briefly for the target to appear (handles startup timing)
1089
+ if (!target) {
1090
+ try {
1091
+ target = await this.browser.waitForTarget(isExtensionBackground, { timeout: 5000 });
1092
+ }
1093
+ catch {
1094
+ throw new Error('No extension background target found. Ensure the extension is loaded in the browser. ' +
1095
+ 'If connecting to an existing browser, verify the extension is installed and active.');
1096
+ }
1097
+ }
1098
+ if (target.type() === 'service_worker') {
1099
+ const worker = await target.worker();
1100
+ if (!worker) {
1101
+ throw new Error('Could not attach to service worker');
1102
+ }
1103
+ return worker.evaluate(expression);
1104
+ }
1105
+ // background_page — get a Page handle instead
1106
+ const page = await target.page();
1107
+ if (!page) {
1108
+ throw new Error('Could not attach to extension background page');
1109
+ }
1110
+ return page.evaluate(expression);
1111
+ }
1112
+ async reconnectBrowser(options) {
1113
+ if (!this.#options.onReconnect) {
1114
+ throw new Error('Browser reconnection is not supported in this configuration');
1115
+ }
1116
+ return this.#options.onReconnect(options);
1117
+ }
1054
1118
  /**
1055
1119
  * We need to ignore favicon request as they make our test flaky
1056
1120
  */
@@ -10,6 +10,61 @@ import { logger } from './logger.js';
10
10
  import { puppeteer } from './third_party/index.js';
11
11
  /** Cached browser instance for reuse across calls. */
12
12
  let browser;
13
+ /**
14
+ * Get Chrome's default user data directory for the given platform and channel.
15
+ *
16
+ * @returns The platform-specific path to Chrome's user data directory.
17
+ */
18
+ function getChromeDefaultUserDataDir(channel = 'stable') {
19
+ const platform = os.platform();
20
+ if (platform === 'darwin') {
21
+ const suffix = channel === 'stable'
22
+ ? ''
23
+ : ` ${channel.charAt(0).toUpperCase() + channel.slice(1)}`;
24
+ return path.join(os.homedir(), 'Library', 'Application Support', 'Google', `Chrome${suffix}`);
25
+ }
26
+ if (platform === 'win32') {
27
+ const appData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local');
28
+ const suffix = channel === 'stable'
29
+ ? ''
30
+ : ` ${channel.charAt(0).toUpperCase() + channel.slice(1)}`;
31
+ return path.join(appData, 'Google', `Chrome${suffix}`, 'User Data');
32
+ }
33
+ // Linux
34
+ const channelSuffix = channel === 'stable'
35
+ ? ''
36
+ : channel === 'beta'
37
+ ? '-beta'
38
+ : '-unstable';
39
+ return path.join(os.homedir(), '.config', `google-chrome${channelSuffix}`);
40
+ }
41
+ /**
42
+ * Try to read a WebSocket endpoint from a DevToolsActivePort file.
43
+ *
44
+ * @param userDataDir - Directory containing the DevToolsActivePort file.
45
+ * @returns The WebSocket endpoint URL, or undefined if the file doesn't exist or is invalid.
46
+ */
47
+ function readDevToolsActivePort(userDataDir) {
48
+ const portPath = path.join(userDataDir, 'DevToolsActivePort');
49
+ try {
50
+ const fileContent = fs.readFileSync(portPath, 'utf8');
51
+ const [rawPort, rawPath] = fileContent
52
+ .split('\n')
53
+ .map(line => line.trim())
54
+ .filter(line => !!line);
55
+ if (!rawPort || !rawPath) {
56
+ return undefined;
57
+ }
58
+ const port = parseInt(rawPort, 10);
59
+ if (isNaN(port) || port <= 0 || port > 65535) {
60
+ return undefined;
61
+ }
62
+ return `ws://127.0.0.1:${port}${rawPath}`;
63
+ }
64
+ catch {
65
+ return undefined;
66
+ }
67
+ }
13
68
  /**
14
69
  * Create a target filter for Puppeteer that excludes internal Chrome pages.
15
70
  *
@@ -18,10 +73,10 @@ let browser;
18
73
  *
19
74
  * @returns A filter function for Puppeteer's targetFilter option.
20
75
  */
21
- function makeTargetFilter() {
76
+ function makeTargetFilter(includeExtensionPages = false) {
22
77
  const ignoredPrefixes = new Set([
23
78
  'chrome://',
24
- 'chrome-extension://',
79
+ ...(includeExtensionPages ? [] : ['chrome-extension://']),
25
80
  'chrome-untrusted://',
26
81
  ]);
27
82
  return function targetFilter(target) {
@@ -53,14 +108,18 @@ function makeTargetFilter() {
53
108
  * @throws Error if connection fails or no connection method specified.
54
109
  */
55
110
  export async function ensureBrowserConnected(options) {
56
- const { channel } = options;
111
+ const { channel, includeExtensionPages } = options;
57
112
  if (browser?.connected) {
58
113
  return browser;
59
114
  }
60
115
  const connectOptions = {
61
- targetFilter: makeTargetFilter(),
116
+ targetFilter: makeTargetFilter(includeExtensionPages),
62
117
  defaultViewport: null,
63
118
  handleDevToolsAsPage: true,
119
+ ...(includeExtensionPages && {
120
+ isPageTargetCallback: (target) => target.type() === 'page' ||
121
+ target.url().startsWith('chrome-extension://'),
122
+ }),
64
123
  };
65
124
  if (options.wsEndpoint) {
66
125
  connectOptions.browserWSEndpoint = options.wsEndpoint;
@@ -74,64 +133,59 @@ export async function ensureBrowserConnected(options) {
74
133
  else if (channel || options.userDataDir) {
75
134
  const userDataDir = options.userDataDir;
76
135
  if (userDataDir) {
77
- // TODO: re-expose this logic via Puppeteer.
78
- const portPath = path.join(userDataDir, 'DevToolsActivePort');
79
- try {
80
- const fileContent = await fs.promises.readFile(portPath, 'utf8');
81
- const [rawPort, rawPath] = fileContent
82
- .split('\n')
83
- .map(line => {
84
- return line.trim();
85
- })
86
- .filter(line => {
87
- return !!line;
88
- });
89
- if (!rawPort || !rawPath) {
90
- throw new Error(`Invalid DevToolsActivePort '${fileContent}' found`);
91
- }
92
- const port = parseInt(rawPort, 10);
93
- if (isNaN(port) || port <= 0 || port > 65535) {
94
- throw new Error(`Invalid port '${rawPort}' found`);
95
- }
96
- const browserWSEndpoint = `ws://127.0.0.1:${port}${rawPath}`;
97
- connectOptions.browserWSEndpoint = browserWSEndpoint;
136
+ // Explicit user data dir provided
137
+ const wsEndpoint = readDevToolsActivePort(userDataDir);
138
+ if (wsEndpoint) {
139
+ connectOptions.browserWSEndpoint = wsEndpoint;
98
140
  }
99
- catch (error) {
100
- throw new Error(`Could not connect to Chrome in ${userDataDir}. Check if Chrome is running and remote debugging is enabled.`, {
101
- cause: error,
102
- });
141
+ else {
142
+ throw new Error(`Could not connect to Chrome in ${userDataDir}. Check if Chrome is running and remote debugging is enabled.`);
103
143
  }
104
144
  }
105
145
  else {
106
146
  if (!channel) {
107
147
  throw new Error('Channel must be provided if userDataDir is missing');
108
148
  }
109
- // Derive the default userDataDir from the channel (same as launch does)
149
+ // Collect candidate WebSocket endpoints from multiple directories.
150
+ // Try each one in order — stale DevToolsActivePort files are common,
151
+ // so we attempt the actual connection before moving to the next.
110
152
  const profileDirName = channel && channel !== 'stable'
111
153
  ? `chrome-profile-${channel}`
112
154
  : 'chrome-profile';
113
- const derivedUserDataDir = path.join(os.homedir(), '.cache', 'chrome-devtools-mcp', profileDirName);
114
- // Try to read DevToolsActivePort from the derived userDataDir
115
- const portPath = path.join(derivedUserDataDir, 'DevToolsActivePort');
116
- try {
117
- const fileContent = await fs.promises.readFile(portPath, 'utf8');
118
- const [rawPort, rawPath] = fileContent
119
- .split('\n')
120
- .map(line => line.trim())
121
- .filter(line => !!line);
122
- if (!rawPort || !rawPath) {
123
- throw new Error(`Invalid DevToolsActivePort '${fileContent}' found`);
155
+ const mcpUserDataDir = path.join(os.homedir(), '.cache', 'chrome-devtools-mcp', profileDirName);
156
+ const chromeUserDataDir = getChromeDefaultUserDataDir(channel);
157
+ // Chrome's default profile is checked first — this is the user's
158
+ // real browser with remote debugging enabled via chrome://inspect.
159
+ // The MCP cache dir is checked second as a fallback for instances
160
+ // launched by the MCP server itself.
161
+ const candidates = [];
162
+ const chromeWsEndpoint = readDevToolsActivePort(chromeUserDataDir);
163
+ if (chromeWsEndpoint) {
164
+ candidates.push({ dir: chromeUserDataDir, wsEndpoint: chromeWsEndpoint });
165
+ }
166
+ const mcpWsEndpoint = readDevToolsActivePort(mcpUserDataDir);
167
+ if (mcpWsEndpoint) {
168
+ candidates.push({ dir: mcpUserDataDir, wsEndpoint: mcpWsEndpoint });
169
+ }
170
+ if (candidates.length === 0) {
171
+ throw new Error(`Could not connect to Chrome ${channel} channel. Checked ${mcpUserDataDir} and ${chromeUserDataDir}. Ensure Chrome is running with remote debugging enabled (chrome://inspect/#remote-debugging).`);
172
+ }
173
+ // Try each candidate endpoint, returning the first that connects
174
+ for (const candidate of candidates) {
175
+ try {
176
+ logger(`Trying DevToolsActivePort from ${candidate.dir}: ${candidate.wsEndpoint}`);
177
+ browser = await puppeteer.connect({
178
+ ...connectOptions,
179
+ browserWSEndpoint: candidate.wsEndpoint,
180
+ });
181
+ logger(`Connected via ${candidate.dir}`);
182
+ return browser;
124
183
  }
125
- const port = parseInt(rawPort, 10);
126
- if (isNaN(port) || port <= 0 || port > 65535) {
127
- throw new Error(`Invalid port '${rawPort}' found`);
184
+ catch (err) {
185
+ logger(`Failed to connect via ${candidate.dir}: ${err.message}`);
128
186
  }
129
- const browserWSEndpoint = `ws://127.0.0.1:${port}${rawPath}`;
130
- connectOptions.browserWSEndpoint = browserWSEndpoint;
131
- }
132
- catch (error) {
133
- throw new Error(`Could not connect to Chrome ${channel} channel in ${derivedUserDataDir}. Check if Chrome is running and was launched with remote debugging enabled.`, { cause: error });
134
187
  }
188
+ throw new Error(`Could not connect to Chrome ${channel} channel. Tried ${candidates.map(c => c.dir).join(' and ')}. Ensure Chrome is running with remote debugging enabled (chrome://inspect/#remote-debugging).`);
135
189
  }
136
190
  }
137
191
  else {
@@ -201,7 +255,7 @@ export async function launch(options) {
201
255
  try {
202
256
  const browser = await puppeteer.launch({
203
257
  channel: puppeteerChannel,
204
- targetFilter: makeTargetFilter(),
258
+ targetFilter: makeTargetFilter(options.includeExtensionPages),
205
259
  executablePath,
206
260
  defaultViewport: null,
207
261
  userDataDir,
@@ -210,6 +264,11 @@ export async function launch(options) {
210
264
  args,
211
265
  acceptInsecureCerts: options.acceptInsecureCerts,
212
266
  handleDevToolsAsPage: true,
267
+ ...(options.includeExtensionPages && {
268
+ enableExtensions: true,
269
+ isPageTargetCallback: (target) => target.type === 'page' ||
270
+ target.url?.startsWith('chrome-extension://') === true,
271
+ }),
213
272
  });
214
273
  if (options.logFile) {
215
274
  // FIXME: we are probably subscribing too late to catch startup logs. We
@@ -219,7 +278,6 @@ export async function launch(options) {
219
278
  }
220
279
  if (options.viewport) {
221
280
  const [page] = await browser.pages();
222
- // @ts-expect-error internal API for now.
223
281
  await page?.resize({
224
282
  contentWidth: options.viewport.width,
225
283
  contentHeight: options.viewport.height,
@@ -250,3 +308,51 @@ export async function ensureBrowserLaunched(options) {
250
308
  browser = await launch(options);
251
309
  return browser;
252
310
  }
311
+ /**
312
+ * Disconnect from the current browser without killing the process.
313
+ * Clears the cached browser reference so future calls can connect to a new instance.
314
+ */
315
+ export function disconnectBrowser() {
316
+ if (browser) {
317
+ try {
318
+ browser.disconnect();
319
+ }
320
+ catch {
321
+ // Ignore disconnect errors — browser may already be gone
322
+ }
323
+ browser = undefined;
324
+ }
325
+ }
326
+ /**
327
+ * Connect to a new browser instance, replacing the cached reference.
328
+ *
329
+ * @param options - Connection options (browserURL or wsEndpoint).
330
+ * @returns Connected browser instance.
331
+ */
332
+ export async function connectToNewBrowser(options) {
333
+ const connectOptions = {
334
+ targetFilter: makeTargetFilter(options.includeExtensionPages),
335
+ defaultViewport: null,
336
+ handleDevToolsAsPage: true,
337
+ ...(options.includeExtensionPages && {
338
+ isPageTargetCallback: (target) => target.type() === 'page' ||
339
+ target.url().startsWith('chrome-extension://'),
340
+ }),
341
+ };
342
+ if (options.wsEndpoint) {
343
+ connectOptions.browserWSEndpoint = options.wsEndpoint;
344
+ if (options.wsHeaders) {
345
+ connectOptions.headers = options.wsHeaders;
346
+ }
347
+ }
348
+ else if (options.browserURL) {
349
+ connectOptions.browserURL = options.browserURL;
350
+ }
351
+ else {
352
+ throw new Error('Either browserURL or wsEndpoint must be provided');
353
+ }
354
+ logger('Connecting Puppeteer to new browser:', JSON.stringify(connectOptions));
355
+ browser = await puppeteer.connect(connectOptions);
356
+ logger('Connected to new browser');
357
+ return browser;
358
+ }
package/build/src/cli.js CHANGED
@@ -162,6 +162,11 @@ export const cliOptions = {
162
162
  default: true,
163
163
  describe: 'Set to false to exclude tools related to network.',
164
164
  },
165
+ includeExtensionPages: {
166
+ type: 'boolean',
167
+ describe: 'Include chrome-extension:// pages in page listing and tool discovery.',
168
+ default: false,
169
+ },
165
170
  };
166
171
  export function parseArguments(version, argv = process.argv) {
167
172
  const yargsInstance = yargs(hideBin(argv))
@@ -170,11 +175,12 @@ export function parseArguments(version, argv = process.argv) {
170
175
  .check(args => {
171
176
  // We can't set default in the options else
172
177
  // Yargs will complain
173
- if (!args.channel &&
174
- !args.browserUrl &&
175
- !args.wsEndpoint &&
176
- !args.executablePath) {
177
- args.channel = 'dev';
178
+ // Note: Use explicit undefined checks since empty strings are valid falsy values
179
+ if (args.channel === undefined &&
180
+ args.browserUrl === undefined &&
181
+ args.wsEndpoint === undefined &&
182
+ args.executablePath === undefined) {
183
+ args.channel = 'stable';
178
184
  }
179
185
  return true;
180
186
  })