opera-devtools-mcp 0.2.4 → 0.3.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.
Files changed (330) hide show
  1. package/README.md +104 -7
  2. package/build/src/McpContext.js +365 -365
  3. package/build/src/McpPage.js +475 -48
  4. package/build/src/McpResponse.js +462 -288
  5. package/build/src/SlimMcpResponse.js +1 -1
  6. package/build/src/TextSnapshot.js +101 -22
  7. package/build/src/ToolHandler.js +142 -20
  8. package/build/src/bin/check-latest-version.js +27 -1
  9. package/build/src/bin/chrome-devtools-cli-options.js +573 -10
  10. package/build/src/bin/chrome-devtools-mcp-cli-options.js +119 -32
  11. package/build/src/bin/chrome-devtools-mcp-main.js +50 -7
  12. package/build/src/bin/chrome-devtools-mcp.js +7 -4
  13. package/build/src/bin/chrome-devtools.js +57 -14
  14. package/build/src/bin/opera-devtools-cli-options.js +3 -829
  15. package/build/src/bin/opera-devtools-mcp-cli-options.js +3 -348
  16. package/build/src/bin/opera-devtools-mcp-main.js +3 -37
  17. package/build/src/bin/opera-devtools-mcp.js +4 -19
  18. package/build/src/bin/opera-devtools.js +4 -185
  19. package/build/src/browser.js +45 -5
  20. package/build/src/{PageCollector.js → collectors/PageCollector.js} +64 -133
  21. package/build/src/collectors/ServiceWorkerCollector.js +171 -0
  22. package/build/src/daemon/client.js +75 -17
  23. package/build/src/daemon/daemon.js +92 -30
  24. package/build/src/daemon/utils.js +24 -8
  25. package/build/src/{DevtoolsUtils.js → devtools/DevtoolsUtils.js} +94 -88
  26. package/build/src/devtools/McpHostBindingAdapter.js +165 -0
  27. package/build/src/{issue-descriptions.js → devtools/issueDescriptions.js} +8 -6
  28. package/build/src/formatters/ConsoleFormatter.js +44 -11
  29. package/build/src/formatters/HeapSnapshotFormatter.js +140 -9
  30. package/build/src/formatters/IssueFormatter.js +7 -7
  31. package/build/src/formatters/NetworkFormatter.js +10 -8
  32. package/build/src/index.js +101 -232
  33. package/build/src/opera/branding.js +34 -0
  34. package/build/src/opera/browserFlags.js +85 -0
  35. package/build/src/opera/browserLaunch.js +52 -0
  36. package/build/src/opera/policy.js +50 -0
  37. package/build/src/opera/serviceWorkerRetry.js +37 -0
  38. package/build/src/opera/toolHandlerHooks.js +39 -0
  39. package/build/src/{tools → opera/tools}/opera.js +9 -21
  40. package/build/src/processors/HeapSnapshotManager.js +258 -0
  41. package/build/src/{trace-processing/parse.js → processors/PerformanceTrace.js} +9 -9
  42. package/build/src/telemetry/ClearcutLogger.js +12 -119
  43. package/build/src/telemetry/WatchdogClient.js +5 -5
  44. package/build/src/telemetry/errors.js +1 -1
  45. package/build/src/telemetry/flagUtils.js +6 -6
  46. package/build/src/telemetry/{toolMetricsUtils.js → metricsRegistry.js} +4 -11
  47. package/build/src/telemetry/persistence.js +3 -3
  48. package/build/src/telemetry/transformation.js +184 -0
  49. package/build/src/telemetry/watchdog/ClearcutSender.js +11 -11
  50. package/build/src/telemetry/watchdog/main.js +6 -6
  51. package/build/src/third_party/THIRD_PARTY_NOTICES +232 -1061
  52. package/build/src/third_party/bundled-packages.json +6 -8
  53. package/build/src/third_party/devtools-formatter-worker.js +63 -22
  54. package/build/src/third_party/devtools-heap-snapshot-worker.js +809 -182
  55. package/build/src/third_party/index.js +69558 -74691
  56. package/build/src/third_party/issue-descriptions/CoepCorpNotSameOrigin.md +2 -2
  57. package/build/src/third_party/issue-descriptions/CoepCorpNotSameOriginAfterDefaultedToSameOriginByCoep.md +3 -3
  58. package/build/src/third_party/issue-descriptions/CoepCorpNotSameSite.md +2 -2
  59. package/build/src/third_party/issue-descriptions/CoepFrameResourceNeedsCoepHeader.md +1 -1
  60. package/build/src/third_party/issue-descriptions/CompatibilityModeQuirks.md +1 -1
  61. package/build/src/third_party/issue-descriptions/CookieAttributeValueExceedsMaxSize.md +2 -2
  62. package/build/src/third_party/issue-descriptions/LowTextContrast.md +1 -1
  63. package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorRead.md +1 -1
  64. package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorSet.md +1 -1
  65. package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnRead.md +1 -1
  66. package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnSet.md +1 -1
  67. package/build/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeRead.md +3 -3
  68. package/build/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeSet.md +3 -3
  69. package/build/src/third_party/issue-descriptions/bounceTrackingMitigations.md +1 -1
  70. package/build/src/third_party/issue-descriptions/connectionAllowlistEmbeddingRequirementNotSatisfied.md +26 -0
  71. package/build/src/third_party/issue-descriptions/connectionAllowlistIFrameAttributeLoosensEmbeddingRequirement.md +19 -0
  72. package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowConnectionAllowlistFrom.md +21 -0
  73. package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowlistItemType.md +2 -2
  74. package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidHeader.md +2 -2
  75. package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidUrlPattern.md +3 -3
  76. package/build/src/third_party/issue-descriptions/connectionAllowlistItemNotInnerList.md +2 -2
  77. package/build/src/third_party/issue-descriptions/connectionAllowlistMoreThanOneList.md +2 -2
  78. package/build/src/third_party/issue-descriptions/connectionAllowlistReportingEndpointNotToken.md +1 -1
  79. package/build/src/third_party/issue-descriptions/cookieCrossSiteRedirectDowngrade.md +3 -3
  80. package/build/src/third_party/issue-descriptions/cookieExcludeBlockedWithinRelatedWebsiteSet.md +1 -1
  81. package/build/src/third_party/issue-descriptions/cookieExcludePortMismatch.md +1 -1
  82. package/build/src/third_party/issue-descriptions/cookieExcludeSchemeMismatch.md +1 -1
  83. package/build/src/third_party/issue-descriptions/cookieExcludeThirdPartyPhaseoutSet.md +1 -1
  84. package/build/src/third_party/issue-descriptions/cookieWarnThirdPartyPhaseoutRead.md +1 -1
  85. package/build/src/third_party/issue-descriptions/corsAllowCredentialsRequired.md +1 -1
  86. package/build/src/third_party/issue-descriptions/corsDisabledScheme.md +2 -2
  87. package/build/src/third_party/issue-descriptions/corsDisallowedByMode.md +2 -2
  88. package/build/src/third_party/issue-descriptions/corsHeaderDisallowedByPreflightResponse.md +1 -1
  89. package/build/src/third_party/issue-descriptions/corsInvalidHeaderValues.md +3 -3
  90. package/build/src/third_party/issue-descriptions/corsLocalNetworkAccessPermissionDenied.md +1 -1
  91. package/build/src/third_party/issue-descriptions/corsMethodDisallowedByPreflightResponse.md +1 -1
  92. package/build/src/third_party/issue-descriptions/corsNoCorsRedirectModeNotFollow.md +1 -1
  93. package/build/src/third_party/issue-descriptions/corsOriginMismatch.md +3 -3
  94. package/build/src/third_party/issue-descriptions/corsPreflightResponseInvalid.md +2 -2
  95. package/build/src/third_party/issue-descriptions/corsRedirectContainsCredentials.md +3 -3
  96. package/build/src/third_party/issue-descriptions/corsWildcardOriginNotAllowed.md +4 -3
  97. package/build/src/third_party/issue-descriptions/cspEvalViolation.md +4 -4
  98. package/build/src/third_party/issue-descriptions/cspInlineViolation.md +2 -2
  99. package/build/src/third_party/issue-descriptions/cspTrustedTypesPolicyViolation.md +2 -2
  100. package/build/src/third_party/issue-descriptions/cspTrustedTypesSinkViolation.md +4 -4
  101. package/build/src/third_party/issue-descriptions/cspURLViolation.md +4 -4
  102. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsEmptyList.md +1 -0
  103. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsHttpNotFound.md +1 -0
  104. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidContentType.md +1 -0
  105. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidResponse.md +1 -0
  106. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsNoResponse.md +1 -0
  107. package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsFetchFailed.md +1 -0
  108. package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsInvalidRecord.md +1 -0
  109. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownHttpNotFound.md +1 -0
  110. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidContentType.md +1 -0
  111. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidResponse.md +1 -0
  112. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownNoResponse.md +1 -0
  113. package/build/src/third_party/issue-descriptions/emailVerificationRequestInvalidEmail.md +1 -0
  114. package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksHttpNotFound.md +1 -0
  115. package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksInvalidResponse.md +1 -0
  116. package/build/src/third_party/issue-descriptions/emailVerificationRequestKeyBindingSigningFailed.md +1 -0
  117. package/build/src/third_party/issue-descriptions/emailVerificationRequestRpOriginIsOpaque.md +1 -0
  118. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenHttpNotFound.md +1 -0
  119. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidContentType.md +1 -0
  120. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidResponse.md +1 -0
  121. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidSdJwt.md +1 -0
  122. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenMalformedSdJwt.md +1 -0
  123. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenNoResponse.md +1 -0
  124. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidAudience.md +1 -0
  125. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidIssuedAt.md +1 -0
  126. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidNonce.md +1 -0
  127. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidSdHash.md +1 -0
  128. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidTyp.md +1 -0
  129. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingAud.md +1 -0
  130. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingCnf.md +1 -0
  131. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingIat.md +1 -0
  132. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingNonce.md +1 -0
  133. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingSdHash.md +1 -0
  134. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbSignatureFailed.md +1 -0
  135. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmail.md +1 -0
  136. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmailVerified.md +1 -0
  137. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidHolderKey.md +1 -0
  138. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuedAt.md +1 -0
  139. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuer.md +1 -0
  140. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtJwksMissingKeys.md +1 -0
  141. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingCnf.md +1 -0
  142. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingEmail.md +1 -0
  143. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIat.md +1 -0
  144. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIss.md +1 -0
  145. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtSignatureFailed.md +1 -0
  146. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtUnsupportedHeaderAlg.md +1 -0
  147. package/build/src/third_party/issue-descriptions/emailVerificationRequestUserLoggedOut.md +1 -0
  148. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownAccountsEndpointCrossOrigin.md +1 -0
  149. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownHttpNotFound.md +1 -0
  150. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidContentType.md +1 -0
  151. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidResponse.md +1 -0
  152. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownIssuanceEndpointCrossOrigin.md +1 -0
  153. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownListEmpty.md +1 -0
  154. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingAccountsEndpoint.md +1 -0
  155. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingIssuanceEndpoint.md +1 -0
  156. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownNoResponse.md +1 -0
  157. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownUnsupportedSigningAlgorithm.md +1 -0
  158. package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsHttpNotFound.md +1 -1
  159. package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsInvalidResponse.md +1 -1
  160. package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsNoResponse.md +1 -1
  161. package/build/src/third_party/issue-descriptions/federatedAuthRequestApprovalDeclined.md +1 -1
  162. package/build/src/third_party/issue-descriptions/federatedAuthRequestCanceled.md +1 -1
  163. package/build/src/third_party/issue-descriptions/federatedAuthRequestErrorFetchingSignin.md +1 -1
  164. package/build/src/third_party/issue-descriptions/federatedAuthRequestErrorIdToken.md +1 -1
  165. package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenHttpNotFound.md +1 -1
  166. package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidRequest.md +1 -1
  167. package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidResponse.md +1 -1
  168. package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenNoResponse.md +1 -1
  169. package/build/src/third_party/issue-descriptions/federatedAuthRequestInvalidSigninResponse.md +1 -1
  170. package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestHttpNotFound.md +1 -1
  171. package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestInvalidResponse.md +1 -1
  172. package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestNoResponse.md +1 -1
  173. package/build/src/third_party/issue-descriptions/federatedAuthRequestTooManyRequests.md +1 -1
  174. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidAccountsResponse.md +1 -1
  175. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidConfigOrWellKnown.md +1 -1
  176. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoAccountSharingPermission.md +1 -1
  177. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoApiPermission.md +1 -1
  178. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoReturningUserFromFetchedAccounts.md +1 -1
  179. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotIframe.md +1 -1
  180. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotPotentiallyTrustworthy.md +1 -1
  181. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSameOrigin.md +1 -1
  182. package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSignedInWithIdp.md +1 -1
  183. package/build/src/third_party/issue-descriptions/fetchingPartitionedBlobURL.md +2 -2
  184. package/build/src/third_party/issue-descriptions/genericBackUINavigationWouldSkipAd.md +4 -0
  185. package/build/src/third_party/issue-descriptions/genericFormAriaLabelledByToNonExistingIdError.md +2 -2
  186. package/build/src/third_party/issue-descriptions/genericFormAutocompleteAttributeEmptyError.md +1 -1
  187. package/build/src/third_party/issue-descriptions/genericFormDuplicateIdForInputError.md +1 -1
  188. package/build/src/third_party/issue-descriptions/genericFormEmptyIdAndNameAttributesForInputError.md +2 -2
  189. package/build/src/third_party/issue-descriptions/genericFormInputAssignedAutocompleteValueToIdOrNameAttributeError.md +2 -2
  190. package/build/src/third_party/issue-descriptions/genericFormInputHasWrongButWellIntendedAutocompleteValueError.md +1 -1
  191. package/build/src/third_party/issue-descriptions/genericFormInputWithNoLabelError.md +1 -1
  192. package/build/src/third_party/issue-descriptions/genericFormLabelForMatchesNonExistingIdError.md +2 -2
  193. package/build/src/third_party/issue-descriptions/genericFormLabelForNameError.md +1 -1
  194. package/build/src/third_party/issue-descriptions/genericFormLabelHasNeitherForNorNestedInputError.md +1 -1
  195. package/build/src/third_party/issue-descriptions/genericFormModelContextMissingToolName.md +1 -1
  196. package/build/src/third_party/issue-descriptions/genericFormModelContextParameterMissingName.md +1 -1
  197. package/build/src/third_party/issue-descriptions/genericFormModelContextRequiredParameterMissingName.md +1 -1
  198. package/build/src/third_party/issue-descriptions/genericNavigationEntryMarkedSkippable.md +2 -2
  199. package/build/src/third_party/issue-descriptions/heavyAd.md +1 -1
  200. package/build/src/third_party/issue-descriptions/lazyLoadImageZeroSize.md +7 -0
  201. package/build/src/third_party/issue-descriptions/navigatingPartitionedBlobURL.md +3 -3
  202. package/build/src/third_party/issue-descriptions/permissionElementActivationDisabled.md +1 -1
  203. package/build/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluder.md +1 -1
  204. package/build/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluderParent.md +1 -1
  205. package/build/src/third_party/issue-descriptions/permissionElementFencedFrameDisallowed.md +2 -2
  206. package/build/src/third_party/issue-descriptions/permissionElementFontSizeTooLarge.md +2 -2
  207. package/build/src/third_party/issue-descriptions/permissionElementFontSizeTooSmall.md +2 -2
  208. package/build/src/third_party/issue-descriptions/permissionElementInsetBoxShadowUnsupported.md +1 -1
  209. package/build/src/third_party/issue-descriptions/permissionElementInvalidDisplayStyle.md +2 -2
  210. package/build/src/third_party/issue-descriptions/permissionElementInvalidSizeValue.md +2 -2
  211. package/build/src/third_party/issue-descriptions/permissionElementInvalidType.md +2 -2
  212. package/build/src/third_party/issue-descriptions/permissionElementInvalidTypeActivation.md +2 -2
  213. package/build/src/third_party/issue-descriptions/permissionElementNonOpaqueColor.md +1 -1
  214. package/build/src/third_party/issue-descriptions/permissionElementPaddingBottomUnsupported.md +2 -2
  215. package/build/src/third_party/issue-descriptions/permissionElementPaddingRightUnsupported.md +2 -2
  216. package/build/src/third_party/issue-descriptions/permissionElementRequestInProgress.md +2 -2
  217. package/build/src/third_party/issue-descriptions/permissionElementSecurityChecksFailed.md +1 -1
  218. package/build/src/third_party/issue-descriptions/permissionElementTypeNotSupported.md +2 -2
  219. package/build/src/third_party/issue-descriptions/permissionElementUntrustedEvent.md +1 -1
  220. package/build/src/third_party/issue-descriptions/placeholderDescriptionForInvisibleIssues.md +1 -1
  221. package/build/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedOptGroupChild.md +1 -1
  222. package/build/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedSelectChild.md +1 -1
  223. package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentAttributesSelectDescendant.md +1 -1
  224. package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentLegendChild.md +1 -1
  225. package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentOptionChild.md +1 -1
  226. package/build/src/third_party/issue-descriptions/selectElementAccessibilityNonPhrasingContentOptionChild.md +1 -1
  227. package/build/src/third_party/issue-descriptions/selectivePermissionsIntervention.md +3 -3
  228. package/build/src/third_party/issue-descriptions/sharedArrayBuffer.md +1 -1
  229. package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorCrossOriginNoCorsRequest.md +1 -1
  230. package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorMatchingDictionaryNotUsed.md +1 -1
  231. package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorUnexpectedContentDictionaryHeader.md +1 -1
  232. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorCossOriginNoCorsRequest.md +1 -1
  233. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorDisallowedBySettings.md +1 -1
  234. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorExpiredResponse.md +1 -1
  235. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorFeatureDisabled.md +2 -2
  236. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidStructuredHeader.md +1 -1
  237. package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNavigationRequest.md +2 -2
  238. package/build/src/third_party/issue-descriptions/sriInvalidSignatureHeader.md +4 -4
  239. package/build/src/third_party/issue-descriptions/sriInvalidSignatureInputHeader.md +4 -4
  240. package/build/src/third_party/issue-descriptions/sriMissingSignatureHeader.md +1 -1
  241. package/build/src/third_party/issue-descriptions/sriMissingSignatureInputHeader.md +2 -2
  242. package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsIncorrectLength.md +2 -2
  243. package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsNotByteSequence.md +4 -4
  244. package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsParameterized.md +4 -4
  245. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentName.md +1 -1
  246. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentType.md +2 -2
  247. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidDerivedComponentParameter.md +1 -1
  248. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidHeaderComponentParameter.md +1 -1
  249. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidParameter.md +2 -2
  250. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderKeyIdLength.md +5 -5
  251. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingLabel.md +3 -3
  252. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingRequiredParameters.md +2 -2
  253. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderValueMissingComponents.md +2 -2
  254. package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderValueNotInnerList.md +2 -2
  255. package/build/src/third_party/issue-descriptions/sriValidationFailedIntegrityMismatch.md +4 -4
  256. package/build/src/third_party/issue-descriptions/sriValidationFailedInvalidLength.md +3 -3
  257. package/build/src/third_party/issue-descriptions/sriValidationFailedSignatureExpired.md +2 -2
  258. package/build/src/third_party/issue-descriptions/sriValidationFailedSignatureMismatch.md +2 -2
  259. package/build/src/third_party/issue-descriptions/stylesheetLateImport.md +1 -1
  260. package/build/src/third_party/issue-descriptions/summaryElementAccessibilityInteractiveContentSummaryDescendant.md +1 -1
  261. package/build/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestLength.md +2 -2
  262. package/build/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestType.md +3 -3
  263. package/build/src/third_party/issue-descriptions/unencodedDigestMalformedDictionary.md +2 -2
  264. package/build/src/third_party/issue-descriptions/unencodedDigestUnknownAlgorithm.md +1 -1
  265. package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +3177 -6340
  266. package/build/src/tools/ToolDefinition.js +2 -2
  267. package/build/src/tools/categories.js +3 -0
  268. package/build/src/tools/console.js +13 -0
  269. package/build/src/tools/emulation.js +30 -3
  270. package/build/src/tools/extensions.js +8 -1
  271. package/build/src/tools/input.js +246 -128
  272. package/build/src/tools/lighthouse.js +14 -7
  273. package/build/src/tools/memory.js +289 -25
  274. package/build/src/tools/network.js +10 -7
  275. package/build/src/tools/pages.js +33 -92
  276. package/build/src/tools/performance.js +67 -52
  277. package/build/src/tools/pwa.js +128 -0
  278. package/build/src/tools/screencast.js +35 -12
  279. package/build/src/tools/screenshot.js +234 -75
  280. package/build/src/tools/script.js +130 -45
  281. package/build/src/tools/slim/tools.js +3 -0
  282. package/build/src/tools/snapshot.js +7 -4
  283. package/build/src/tools/thirdPartyDeveloper.js +19 -9
  284. package/build/src/tools/tools.js +3 -1
  285. package/build/src/tools/webmcp.js +3 -1
  286. package/build/src/utils/WaitForHelper.js +299 -0
  287. package/build/src/utils/check-for-updates.js +8 -2
  288. package/build/src/utils/files.js +42 -3
  289. package/build/src/utils/logger.js +51 -0
  290. package/build/src/utils/polyfill.js +11 -0
  291. package/build/src/utils/url.js +43 -0
  292. package/build/src/version.js +1 -1
  293. package/package.json +42 -33
  294. package/build/src/DevToolsConnectionAdapter.js +0 -70
  295. package/build/src/HeapSnapshotManager.js +0 -110
  296. package/build/src/Mutex.js +0 -38
  297. package/build/src/WaitForHelper.js +0 -195
  298. package/build/src/bin/cliDefinitions.js +0 -616
  299. package/build/src/logger.js +0 -37
  300. package/build/src/polyfill.js +0 -8
  301. package/build/src/telemetry/metricUtils.js +0 -15
  302. package/build/src/third_party/issue-descriptions/SameSiteExcludeContextDowngradeRead.md +0 -8
  303. package/build/src/third_party/issue-descriptions/SameSiteExcludeContextDowngradeSet.md +0 -8
  304. package/build/src/third_party/issue-descriptions/SameSiteExcludeNavigationContextDowngrade.md +0 -8
  305. package/build/src/third_party/issue-descriptions/SameSiteWarnCrossDowngradeRead.md +0 -8
  306. package/build/src/third_party/issue-descriptions/SameSiteWarnCrossDowngradeSet.md +0 -8
  307. package/build/src/third_party/issue-descriptions/SameSiteWarnStrictLaxDowngradeStrict.md +0 -8
  308. package/build/src/third_party/issue-descriptions/arInsecureContext.md +0 -7
  309. package/build/src/third_party/issue-descriptions/arInvalidInfoHeader.md +0 -5
  310. package/build/src/third_party/issue-descriptions/arInvalidRegisterOsSourceHeader.md +0 -5
  311. package/build/src/third_party/issue-descriptions/arInvalidRegisterOsTriggerHeader.md +0 -5
  312. package/build/src/third_party/issue-descriptions/arInvalidRegisterSourceHeader.md +0 -5
  313. package/build/src/third_party/issue-descriptions/arInvalidRegisterTriggerHeader.md +0 -5
  314. package/build/src/third_party/issue-descriptions/arNavigationRegistrationUniqueScopeAlreadySet.md +0 -5
  315. package/build/src/third_party/issue-descriptions/arNavigationRegistrationWithoutTransientUserActivation.md +0 -6
  316. package/build/src/third_party/issue-descriptions/arNoRegisterOsSourceHeader.md +0 -5
  317. package/build/src/third_party/issue-descriptions/arNoRegisterOsTriggerHeader.md +0 -5
  318. package/build/src/third_party/issue-descriptions/arNoRegisterSourceHeader.md +0 -5
  319. package/build/src/third_party/issue-descriptions/arNoRegisterTriggerHeader.md +0 -5
  320. package/build/src/third_party/issue-descriptions/arNoWebOrOsSupport.md +0 -4
  321. package/build/src/third_party/issue-descriptions/arOsSourceIgnored.md +0 -18
  322. package/build/src/third_party/issue-descriptions/arOsTriggerIgnored.md +0 -19
  323. package/build/src/third_party/issue-descriptions/arPermissionPolicyDisabled.md +0 -8
  324. package/build/src/third_party/issue-descriptions/arSourceAndTriggerHeaders.md +0 -9
  325. package/build/src/third_party/issue-descriptions/arSourceIgnored.md +0 -13
  326. package/build/src/third_party/issue-descriptions/arTriggerIgnored.md +0 -12
  327. package/build/src/third_party/issue-descriptions/arUntrustworthyReportingOrigin.md +0 -10
  328. package/build/src/third_party/issue-descriptions/arWebAndOsHeaders.md +0 -11
  329. package/build/src/utils/string.js +0 -37
  330. package/build/src/utils/types.js +0 -7
@@ -5,22 +5,43 @@
5
5
  *
6
6
  * Modified by Opera Software AS.
7
7
  */
8
- import { closeBrowserIfOpen, ensureBrowserConnected, ensureBrowserLaunched, getCurrentBrowser, } from './browser.js';
9
- import { loadIssueDescriptions } from './issue-descriptions.js';
10
- import { logger } from './logger.js';
8
+ import { ensureBrowserConnected, ensureBrowserLaunched } from './browser.js';
9
+ import { loadIssueDescriptions } from './devtools/issueDescriptions.js';
11
10
  import { McpContext } from './McpContext.js';
12
- import { McpResponse } from './McpResponse.js';
13
- import { Mutex } from './Mutex.js';
14
- import { SlimMcpResponse } from './SlimMcpResponse.js';
15
- import { bucketizeLatency } from './telemetry/metricUtils.js';
11
+ import { BROWSER_EXPOSURE_DISCLAIMER, showUsageStatisticsDisclaimer, } from './opera/policy.js';
12
+ import { createOperaToolHooks } from './opera/toolHandlerHooks.js';
13
+ import { buildLaunchOptions } from './opera/browserLaunch.js';
14
+ import { ClearcutLogger } from './telemetry/ClearcutLogger.js';
15
+ import { FilePersistence } from './telemetry/persistence.js';
16
16
  import { McpServer, SetLevelRequestSchema, ListRootsResultSchema, RootsListChangedNotificationSchema, } from './third_party/index.js';
17
- import { ToolCategory } from './tools/categories.js';
18
- import { pageIdSchema } from './tools/ToolDefinition.js';
17
+ import { ToolHandler } from './ToolHandler.js';
19
18
  import { createTools } from './tools/tools.js';
19
+ import { logger } from './utils/logger.js';
20
+ import { Mutex } from './third_party/index.js';
20
21
  import { VERSION } from './version.js';
21
22
  export { buildFlag } from './ToolHandler.js';
23
+ /**
24
+ * Timeout for a `roots/list` that a tool call is waiting on, matching the 5s
25
+ * default used for page operations. `getContext()` awaits it while
26
+ * `ToolHandler` holds the tool mutex, so leaving it unbounded lets a client
27
+ * that negotiates `roots` but does not answer block every tool for the SDK's
28
+ * default of 60s. Background refreshes are not bounded by this, so roots a
29
+ * slow client sends late still land.
30
+ */
31
+ const ROOTS_REQUEST_TIMEOUT = 5_000;
22
32
  export async function createMcpServer(serverArgs, options) {
23
- let clearcutLogger;
33
+ // Opera forces `usageStatistics` off in ./opera/policy.ts, so this stays
34
+ // dormant. Kept identical to upstream so the seam is the policy, not this file.
35
+ if (serverArgs.usageStatistics) {
36
+ ClearcutLogger.initialize({
37
+ persistence: new FilePersistence(),
38
+ logFile: serverArgs.logFile,
39
+ appVersion: VERSION,
40
+ clearcutEndpoint: serverArgs.clearcutEndpoint,
41
+ clearcutForceFlushIntervalMs: serverArgs.clearcutForceFlushIntervalMs,
42
+ clearcutIncludePidHeader: serverArgs.clearcutIncludePidHeader,
43
+ });
44
+ }
24
45
  const server = new McpServer({
25
46
  name: 'chrome_devtools',
26
47
  title: 'Chrome DevTools MCP server',
@@ -29,22 +50,30 @@ export async function createMcpServer(serverArgs, options) {
29
50
  server.server.setRequestHandler(SetLevelRequestSchema, () => {
30
51
  return {};
31
52
  });
32
- const updateRoots = async () => {
53
+ // Roots are client state rather than browser state, so the last listing stays
54
+ // valid across browser reconnects and only the client can invalidate it, via
55
+ // the `roots/list_changed` notification handled below
56
+ let lastRoots;
57
+ // `timeout` is only passed where a tool call is waiting on the result – the
58
+ // background refreshes below block nobody, so bounding them would just discard
59
+ // roots a slow client was about to send
60
+ const updateRoots = async (timeout) => {
33
61
  if (!server.server.getClientCapabilities()?.roots) {
34
62
  return;
35
63
  }
36
64
  try {
37
- const roots = await server.server.request({ method: 'roots/list' }, ListRootsResultSchema);
38
- context?.setRoots(roots.roots);
65
+ const roots = await server.server.request({ method: 'roots/list' }, ListRootsResultSchema, timeout === undefined ? undefined : { timeout });
66
+ lastRoots = roots.roots;
67
+ context?.setRoots(lastRoots);
39
68
  }
40
69
  catch (e) {
41
- logger('Failed to list roots', e);
70
+ logger?.('Failed to list roots', e);
42
71
  }
43
72
  };
44
73
  server.server.oninitialized = () => {
45
74
  const clientName = server.server.getClientVersion()?.name;
46
75
  if (clientName) {
47
- clearcutLogger?.setClientName(clientName);
76
+ ClearcutLogger.get()?.setClientName(clientName);
48
77
  }
49
78
  if (server.server.getClientCapabilities()?.roots) {
50
79
  void updateRoots();
@@ -52,16 +81,22 @@ export async function createMcpServer(serverArgs, options) {
52
81
  void updateRoots();
53
82
  });
54
83
  }
84
+ else if (!serverArgs.allowUnrestrictedPaths) {
85
+ console.warn('[chrome-devtools-mcp] The connecting client did not negotiate the MCP roots ' +
86
+ 'capability. File-writing tools will be restricted to the OS temp directory. ' +
87
+ 'To restore the previous unrestricted behavior, start the server with ' +
88
+ '--allow-unrestricted-paths.');
89
+ }
55
90
  };
56
91
  let context;
57
- let browserHasOperaFlags = false;
58
92
  async function getContext() {
59
- const chromeArgs = (serverArgs.chromeArg ?? []).map(String);
60
- const ignoreDefaultChromeArgs = (serverArgs.ignoreDefaultChromeArg ?? []).map(String);
61
- if (serverArgs.proxyServer) {
62
- chromeArgs.push(`--proxy-server=${serverArgs.proxyServer}`);
63
- }
64
93
  const devtools = serverArgs.experimentalDevtools ?? false;
94
+ const blocklist = serverArgs.blockedUrlPattern
95
+ ? serverArgs.blockedUrlPattern.map(String)
96
+ : undefined;
97
+ const allowlist = serverArgs.allowedUrlPattern
98
+ ? serverArgs.allowedUrlPattern.map(String)
99
+ : undefined;
65
100
  const browser = serverArgs.browserUrl || serverArgs.wsEndpoint || serverArgs.autoConnect
66
101
  ? await ensureBrowserConnected({
67
102
  browserURL: serverArgs.browserUrl,
@@ -73,232 +108,63 @@ export async function createMcpServer(serverArgs, options) {
73
108
  : undefined,
74
109
  userDataDir: serverArgs.userDataDir,
75
110
  devtools,
111
+ blocklist,
112
+ allowlist,
76
113
  })
77
- : await ensureBrowserLaunched({
78
- headless: serverArgs.headless,
79
- executablePath: serverArgs.executablePath,
80
- channel: serverArgs.channel,
81
- isolated: serverArgs.isolated ?? false,
82
- userDataDir: serverArgs.userDataDir,
83
- logFile: options.logFile,
84
- viewport: serverArgs.viewport,
85
- chromeArgs,
86
- ignoreDefaultChromeArgs,
87
- acceptInsecureCerts: serverArgs.acceptInsecureCerts,
114
+ : await ensureBrowserLaunched(
115
+ // Pass the already-derived values so the launched browser uses the
116
+ // exact same flags as the connected branch and `McpContext`.
117
+ buildLaunchOptions(serverArgs, options.logFile, {
88
118
  devtools,
89
- enableExtensions: serverArgs.categoryExtensions,
90
- viaCli: serverArgs.viaCli,
91
- });
119
+ blocklist,
120
+ allowlist,
121
+ }));
92
122
  if (context?.browser !== browser) {
123
+ context?.dispose();
93
124
  context = await McpContext.from(browser, logger, {
94
125
  experimentalDevToolsDebugging: devtools,
95
126
  experimentalIncludeAllPages: serverArgs.experimentalIncludeAllPages,
96
127
  performanceCrux: serverArgs.performanceCrux,
128
+ allowList: allowlist,
129
+ blocklist: blocklist,
130
+ allowUnrestrictedPaths: serverArgs.allowUnrestrictedPaths,
131
+ // Surfaces a one-time note in the next response after a reconnect.
132
+ reconnected: context !== undefined,
97
133
  });
98
- await updateRoots();
134
+ if (lastRoots === undefined) {
135
+ // Nothing listed yet, so this call has to wait – bounded, since it is
136
+ // holding the tool mutex, and a later background refresh still lands
137
+ await updateRoots(ROOTS_REQUEST_TIMEOUT);
138
+ }
139
+ else {
140
+ // Carry the known roots over and refresh out of band, so a reconnect
141
+ // never pays for a client round-trip
142
+ context.setRoots(lastRoots);
143
+ void updateRoots();
144
+ }
99
145
  }
100
146
  return context;
101
147
  }
102
- async function restartBrowserWithoutOperaFlags() {
103
- context?.dispose();
104
- context = undefined;
105
- browserHasOperaFlags = false;
106
- await closeBrowserIfOpen();
107
- // getContext() will relaunch without --disable-blink-features=AutomationControlled
108
- }
109
- async function restartBrowserForOpera() {
110
- context?.dispose();
111
- context = undefined;
112
- browserHasOperaFlags = false;
113
- await closeBrowserIfOpen();
114
- const chromeArgs = [
115
- '--disable-blink-features=AutomationControlled',
116
- ...(serverArgs.chromeArg ?? []).map(String),
117
- ];
118
- if (serverArgs.proxyServer) {
119
- chromeArgs.push(`--proxy-server=${serverArgs.proxyServer}`);
120
- }
121
- await ensureBrowserLaunched({
122
- headless: serverArgs.headless,
123
- executablePath: serverArgs.executablePath,
124
- channel: serverArgs.channel,
125
- isolated: serverArgs.isolated ?? false,
126
- userDataDir: serverArgs.userDataDir,
127
- logFile: options.logFile,
128
- viewport: serverArgs.viewport,
129
- chromeArgs,
130
- ignoreDefaultChromeArgs: (serverArgs.ignoreDefaultChromeArg ?? []).map(String),
131
- acceptInsecureCerts: serverArgs.acceptInsecureCerts,
132
- devtools: serverArgs.experimentalDevtools ?? false,
133
- enableExtensions: serverArgs.categoryExtensions,
134
- viaCli: serverArgs.viaCli,
135
- });
136
- browserHasOperaFlags = true;
137
- }
148
+ const operaHooks = createOperaToolHooks({
149
+ serverArgs,
150
+ logFile: options.logFile,
151
+ resetContext: () => {
152
+ context?.dispose();
153
+ context = undefined;
154
+ },
155
+ });
138
156
  const toolMutex = new Mutex();
139
157
  function registerTool(tool) {
140
- if (tool.annotations.category === ToolCategory.EMULATION &&
141
- serverArgs.categoryEmulation === false) {
142
- return;
143
- }
144
- if (tool.annotations.category === ToolCategory.PERFORMANCE &&
145
- serverArgs.categoryPerformance === false) {
146
- return;
147
- }
148
- if (tool.annotations.category === ToolCategory.NETWORK &&
149
- serverArgs.categoryNetwork === false) {
150
- return;
151
- }
152
- if (tool.annotations.category === ToolCategory.EXTENSIONS &&
153
- !serverArgs.categoryExtensions) {
154
- return;
155
- }
156
- if (tool.annotations.category === ToolCategory.WEBMCP &&
157
- !serverArgs.categoryExperimentalWebmcp) {
158
+ const toolHandler = new ToolHandler(tool, serverArgs, getContext, toolMutex, operaHooks);
159
+ if (!toolHandler.shouldRegister) {
158
160
  return;
159
161
  }
160
- if (tool.annotations.category === ToolCategory.THIRD_PARTY &&
161
- !serverArgs.categoryExperimentalThirdParty) {
162
- return;
163
- }
164
- if (tool.annotations.conditions?.includes('experimentalVision') &&
165
- !serverArgs.experimentalVision) {
166
- return;
167
- }
168
- if (tool.annotations.conditions?.includes('experimentalInteropTools') &&
169
- !serverArgs.experimentalInteropTools) {
170
- return;
171
- }
172
- if (tool.annotations.conditions?.includes('experimentalScreencast') &&
173
- !serverArgs.experimentalScreencast) {
174
- return;
175
- }
176
- if (tool.annotations.conditions?.includes('experimentalMemory') &&
177
- !serverArgs.experimentalMemory) {
178
- return;
179
- }
180
- const schema = 'pageScoped' in tool &&
181
- tool.pageScoped &&
182
- serverArgs.experimentalPageIdRouting &&
183
- !serverArgs.slim
184
- ? { ...tool.schema, ...pageIdSchema }
185
- : tool.schema;
186
162
  server.registerTool(tool.name, {
187
163
  description: tool.description,
188
- inputSchema: schema,
164
+ inputSchema: toolHandler.registeredInputSchema,
189
165
  annotations: tool.annotations,
190
166
  }, async (params, extra) => {
191
- const guard = tool.annotations.category === ToolCategory.OPERA
192
- ? null
193
- : await toolMutex.acquire();
194
- const startTime = Date.now();
195
- let success = false;
196
- try {
197
- const isLaunchMode = !serverArgs.browserUrl &&
198
- !serverArgs.wsEndpoint &&
199
- !serverArgs.autoConnect;
200
- const needsOperaFlags = tool.name === 'opera_do' || tool.name === 'opera_research';
201
- const browserConnected = getCurrentBrowser()?.connected ?? false;
202
- if (isLaunchMode) {
203
- if (needsOperaFlags &&
204
- !(browserHasOperaFlags && browserConnected)) {
205
- await restartBrowserForOpera();
206
- }
207
- else if (!needsOperaFlags &&
208
- browserHasOperaFlags &&
209
- browserConnected) {
210
- await restartBrowserWithoutOperaFlags();
211
- }
212
- }
213
- logger(`${tool.name} request: ${JSON.stringify(params, null, ' ')}`);
214
- const context = await getContext();
215
- logger(`${tool.name} context: resolved`);
216
- await context.detectOpenDevToolsWindows();
217
- const logCallback = (message) => {
218
- // `logger` carries the MCP request ID so the opera-cli bridge can route
219
- // this chunk to the correct HTTP response (see bridge.ts requestLoggers).
220
- // `data` stays a plain string so non-bridge MCP hosts (Claude Desktop,
221
- // VS Code, etc.) continue to render it as readable text.
222
- void extra.sendNotification({
223
- method: 'notifications/message',
224
- params: {
225
- level: 'info',
226
- data: message,
227
- logger: String(extra.requestId),
228
- },
229
- });
230
- };
231
- const response = serverArgs.slim
232
- ? new SlimMcpResponse(serverArgs, logCallback)
233
- : new McpResponse(serverArgs, logCallback);
234
- try {
235
- if ('pageScoped' in tool && tool.pageScoped) {
236
- const page = serverArgs.experimentalPageIdRouting &&
237
- params.pageId &&
238
- !serverArgs.slim
239
- ? context.getPageById(params.pageId)
240
- : context.getSelectedMcpPage();
241
- response.setPage(page);
242
- if (tool.blockedByDialog) {
243
- page.throwIfDialogOpen();
244
- }
245
- await tool.handler({
246
- params,
247
- page,
248
- signal: extra.signal,
249
- }, response, context);
250
- }
251
- else {
252
- await tool.handler(
253
- // @ts-expect-error types do not match.
254
- {
255
- params,
256
- signal: extra.signal,
257
- }, response, context);
258
- }
259
- }
260
- catch (err) {
261
- response.setError(err);
262
- }
263
- const { content, structuredContent } = await response.handle(tool.name, context);
264
- const result = {
265
- content,
266
- };
267
- if (response.error) {
268
- result.isError = true;
269
- }
270
- success = true;
271
- if (serverArgs.experimentalStructuredContent) {
272
- result.structuredContent = structuredContent;
273
- }
274
- return result;
275
- }
276
- catch (err) {
277
- logger(`${tool.name} error:`, err, err?.stack);
278
- let errorText = err && 'message' in err ? err.message : String(err);
279
- if ('cause' in err && err.cause) {
280
- errorText += `\nCause: ${err.cause.message}`;
281
- }
282
- return {
283
- content: [
284
- {
285
- type: 'text',
286
- text: errorText,
287
- },
288
- ],
289
- isError: true,
290
- };
291
- }
292
- finally {
293
- void clearcutLogger?.logToolInvocation({
294
- toolName: tool.name,
295
- params: params,
296
- schema: tool.schema,
297
- success,
298
- latencyMs: bucketizeLatency(Date.now() - startTime),
299
- });
300
- guard?.dispose();
301
- }
167
+ return await toolHandler.handle(params, extra);
302
168
  });
303
169
  }
304
170
  const tools = createTools(serverArgs);
@@ -306,14 +172,17 @@ export async function createMcpServer(serverArgs, options) {
306
172
  registerTool(tool);
307
173
  }
308
174
  await loadIssueDescriptions();
309
- return { server, clearcutLogger };
175
+ return { server };
310
176
  }
311
177
  export const logDisclaimers = (args) => {
312
- console.error(`opera-devtools-mcp exposes content of the browser instance to the MCP clients allowing them to inspect,
313
- debug, and modify any data in the browser or DevTools.
314
- Avoid sharing sensitive or personal information that you do not want to share with MCP clients.`);
178
+ console.error(BROWSER_EXPOSURE_DISCLAIMER);
315
179
  if (!args.slim && args.performanceCrux) {
316
180
  console.error(`Performance tools may send trace URLs to the Google CrUX API to fetch real-user experience data. To disable, run with --no-performance-crux.`);
317
181
  }
182
+ if (showUsageStatisticsDisclaimer() && !args.slim && args.usageStatistics) {
183
+ console.error(`
184
+ Google collects usage statistics to improve Chrome DevTools MCP. To opt-out, run with --no-usage-statistics.
185
+ For more details, visit: https://github.com/ChromeDevTools/chrome-devtools-mcp#usage-statistics`);
186
+ }
318
187
  };
319
188
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Opera Software AS.
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /**
7
+ * Single source of truth for every product string that differs from upstream
8
+ * `chrome-devtools-mcp`.
9
+ *
10
+ * Upstream files reference these constants instead of hardcoding literals, so
11
+ * the merge seam for rebranding is one changed line per string rather than a
12
+ * forked copy of the file.
13
+ */
14
+ /** npm package name. Doubles as the MCP server binary name. */
15
+ export const PACKAGE_NAME = 'opera-devtools-mcp';
16
+ /** MCP server binary / daemon app name, also used as `process.title`. */
17
+ export const MCP_BIN_NAME = PACKAGE_NAME;
18
+ /** CLI binary name, e.g. `opera-devtools start`. */
19
+ export const CLI_BIN_NAME = 'opera-devtools';
20
+ /** Human-readable product name used in log lines. */
21
+ export const PRODUCT_NAME = 'Opera DevTools MCP Server';
22
+ /** Public repository, referenced from help text and disclaimers. */
23
+ export const REPO_URL = 'https://github.com/operasoftware/opera-devtools-mcp';
24
+ /** Directory under `$HOME/.cache` holding the update-check snapshot. */
25
+ export const CACHE_DIR_NAME = PACKAGE_NAME;
26
+ /** Bin script the daemon spawns to run the MCP server. */
27
+ export const INDEX_SCRIPT_NAME = `${MCP_BIN_NAME}.js`;
28
+ /** Upstream reads `CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS`. */
29
+ export const ENV_NO_USAGE_STATISTICS = 'OPERA_DEVTOOLS_NO_USAGE_STATISTICS';
30
+ /** Upstream reads `CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS`. */
31
+ export const ENV_NO_UPDATE_CHECKS = 'OPERA_DEVTOOLS_NO_UPDATE_CHECKS';
32
+ /** Upstream reads `CHROME_DEVTOOLS_MCP_CRASH_ON_UNCAUGHT`. */
33
+ export const ENV_CRASH_ON_UNCAUGHT = 'OPERA_DEVTOOLS_CRASH_ON_UNCAUGHT';
34
+ //# sourceMappingURL=branding.js.map
@@ -0,0 +1,85 @@
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
+ import { closeBrowserIfOpen, ensureBrowserLaunched, getCurrentBrowser, } from '../browser.js';
8
+ import { buildLaunchOptions } from './browserLaunch.js';
9
+ import { logger } from '../utils/logger.js';
10
+ /**
11
+ * Opera's AI features refuse to run when the page reports itself as
12
+ * automation-controlled, so the browser has to be launched with this flag for
13
+ * them. It is deliberately NOT applied to every launch: it changes observable
14
+ * page behaviour, which would silently alter results for ordinary DevTools
15
+ * tools.
16
+ */
17
+ export const OPERA_AUTOMATION_FLAGS = [
18
+ '--disable-blink-features=AutomationControlled',
19
+ ];
20
+ /**
21
+ * Only the tools that drive Opera's agentic AI need the automation flags.
22
+ * `opera_chat`, `opera_make` and `opera_list_models` talk to the service worker
23
+ * directly and work without them.
24
+ */
25
+ const TOOLS_REQUIRING_OPERA_FLAGS = new Set(['opera_do', 'opera_research']);
26
+ export function toolRequiresOperaFlags(toolName) {
27
+ return TOOLS_REQUIRING_OPERA_FLAGS.has(toolName);
28
+ }
29
+ /**
30
+ * Tracks whether the currently running browser was launched with
31
+ * {@link OPERA_AUTOMATION_FLAGS}. Module-level rather than per-server because
32
+ * the browser itself is a module-level singleton in `../browser.ts`.
33
+ */
34
+ let browserHasOperaFlags = false;
35
+ export function browserWasLaunchedWithOperaFlags() {
36
+ return browserHasOperaFlags;
37
+ }
38
+ /** Test seam: forget what we believe about the current browser. */
39
+ export function resetOperaFlagState() {
40
+ browserHasOperaFlags = false;
41
+ }
42
+ /**
43
+ * True when this server launched the browser itself. When the user attached to
44
+ * an existing browser we must never kill and relaunch it.
45
+ */
46
+ function isLaunchMode(serverArgs) {
47
+ return (!serverArgs.browserUrl && !serverArgs.wsEndpoint && !serverArgs.autoConnect);
48
+ }
49
+ /**
50
+ * Makes sure the running browser has (or lacks) the Opera automation flags to
51
+ * match what `toolName` needs, relaunching it if not. No-op when we did not
52
+ * launch the browser ourselves, or when the flags already match.
53
+ */
54
+ export async function ensureBrowserFlagsForTool(toolName, serverArgs, logFile, control,
55
+ // Injected so tests do not need a real browser singleton.
56
+ deps = { closeBrowserIfOpen, ensureBrowserLaunched }) {
57
+ if (!isLaunchMode(serverArgs)) {
58
+ return;
59
+ }
60
+ const needsOperaFlags = toolRequiresOperaFlags(toolName);
61
+ const browserConnected = getCurrentBrowser()?.connected ?? false;
62
+ // A disconnected browser tells us nothing about the flags of the next one.
63
+ if (needsOperaFlags && browserHasOperaFlags && browserConnected) {
64
+ return;
65
+ }
66
+ if (!needsOperaFlags && !(browserHasOperaFlags && browserConnected)) {
67
+ return;
68
+ }
69
+ logger?.(`Relaunching browser ${needsOperaFlags ? 'with' : 'without'} Opera automation flags for ${toolName}`);
70
+ control.resetContext();
71
+ browserHasOperaFlags = false;
72
+ await deps.closeBrowserIfOpen();
73
+ if (needsOperaFlags) {
74
+ // Launch options come from the shared `buildLaunchOptions` so the relaunch
75
+ // applies exactly the same flags (including blocklist/allowlist and proxy)
76
+ // as the normal launch in `index.ts`, plus the automation flags.
77
+ await deps.ensureBrowserLaunched(buildLaunchOptions(serverArgs, logFile, {
78
+ extraChromeArgs: OPERA_AUTOMATION_FLAGS,
79
+ }));
80
+ browserHasOperaFlags = true;
81
+ }
82
+ // Otherwise leave the browser closed: getContext() relaunches it without the
83
+ // Opera flags on the next call.
84
+ }
85
+ //# sourceMappingURL=browserFlags.js.map
@@ -0,0 +1,52 @@
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
+ * Single source of truth for mapping parsed server args to
9
+ * `ensureBrowserLaunched` options. Used by the normal launch in `src/index.ts`
10
+ * (`getContext`) and by the Opera browser relaunch in `opera/browserFlags.ts`.
11
+ *
12
+ * `index.ts` and `browserFlags.ts` used to build these options independently,
13
+ * so a relaunch could quietly omit a launch flag (notably `blocklist` /
14
+ * `allowlist`) and the two could drift apart on an upstream intake. Keep the
15
+ * mapping here so the relaunch path always launches the browser exactly like
16
+ * the normal path does, plus whatever `extraChromeArgs` the caller adds.
17
+ */
18
+ export function buildLaunchOptions(serverArgs, logFile = undefined, extra = {}) {
19
+ const chromeArgs = [
20
+ ...(extra.extraChromeArgs ?? []),
21
+ ...(serverArgs.chromeArg ?? []).map(String),
22
+ ];
23
+ if (serverArgs.proxyServer) {
24
+ chromeArgs.push(`--proxy-server=${serverArgs.proxyServer}`);
25
+ }
26
+ const blocklist = extra.blocklist ??
27
+ (serverArgs.blockedUrlPattern
28
+ ? serverArgs.blockedUrlPattern.map(String)
29
+ : undefined);
30
+ const allowlist = extra.allowlist ??
31
+ (serverArgs.allowedUrlPattern
32
+ ? serverArgs.allowedUrlPattern.map(String)
33
+ : undefined);
34
+ return {
35
+ headless: serverArgs.headless,
36
+ executablePath: serverArgs.executablePath,
37
+ channel: serverArgs.channel,
38
+ isolated: serverArgs.isolated ?? false,
39
+ userDataDir: serverArgs.userDataDir,
40
+ logFile,
41
+ viewport: serverArgs.viewport,
42
+ chromeArgs,
43
+ ignoreDefaultChromeArgs: (serverArgs.ignoreDefaultChromeArg ?? []).map(String),
44
+ acceptInsecureCerts: serverArgs.acceptInsecureCerts,
45
+ devtools: extra.devtools ?? serverArgs.experimentalDevtools ?? false,
46
+ enableExtensions: serverArgs.categoryExtensions,
47
+ viaCli: serverArgs.viaCli,
48
+ blocklist,
49
+ allowlist,
50
+ };
51
+ }
52
+ //# sourceMappingURL=browserLaunch.js.map
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Opera Software AS.
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /**
7
+ * Opera's behavioural deltas on top of upstream `chrome-devtools-mcp`.
8
+ *
9
+ * The fork does not collect usage statistics and does not send performance
10
+ * trace URLs to Google's CrUX API. Upstream defaults both to `true`; here they
11
+ * default to `false`, the help text is inverted accordingly, and the MCP
12
+ * server entry point additionally forces them off even if a flag asked for
13
+ * them.
14
+ */
15
+ import { ENV_NO_USAGE_STATISTICS, MCP_BIN_NAME } from './branding.js';
16
+ /** Upstream default is `true`. */
17
+ export const USAGE_STATISTICS_DEFAULT = false;
18
+ /** Upstream default is `true`. */
19
+ export const PERFORMANCE_CRUX_DEFAULT = false;
20
+ /** Inverted from upstream's "Set to false to disable ..." wording. */
21
+ export const PERFORMANCE_CRUX_DESCRIPTION = 'Set to true to enable sending URLs from performance traces to CrUX API to get field performance data.';
22
+ /** Inverted from upstream's "Set to false to opt-out ..." wording. */
23
+ export const USAGE_STATISTICS_DESCRIPTION = `Set to true to opt-in to usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if \`${ENV_NO_USAGE_STATISTICS}\` or \`CI\` env variables are set.`;
24
+ /**
25
+ * Hard-off switch applied by the MCP server entry point after argument
26
+ * parsing. Defaults alone are not enough: a client could pass
27
+ * `--usageStatistics` or `--performanceCrux` explicitly.
28
+ */
29
+ export function enforceTelemetryPolicy(args) {
30
+ if (args.usageStatistics || args.performanceCrux) {
31
+ console.error('Warning: usage statistics or CrUX were enabled via flags — forcing off.');
32
+ }
33
+ args.usageStatistics = false;
34
+ args.performanceCrux = false;
35
+ }
36
+ /** Branded replacement for upstream's browser-exposure disclaimer. */
37
+ export const BROWSER_EXPOSURE_DISCLAIMER = `${MCP_BIN_NAME} exposes content of the browser instance to the MCP clients allowing them to inspect,
38
+ debug, and modify any data in the browser or DevTools.
39
+ Avoid sharing sensitive or personal information that you do not want to share with MCP clients.`;
40
+ /**
41
+ * Upstream prints a Google usage-statistics opt-out paragraph from
42
+ * `logDisclaimers`. Opera never collects them, so the paragraph is suppressed.
43
+ *
44
+ * A function rather than a `false` constant so that upstream's block in
45
+ * `src/index.ts` stays live code and merges untouched.
46
+ */
47
+ export function showUsageStatisticsDisclaimer() {
48
+ return false;
49
+ }
50
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1,37 @@
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
+ * Opera's AI service worker may not be running yet when the first CDP command
9
+ * arrives, so dispatches are retried with a fixed backoff.
10
+ *
11
+ * Exposed as a mutable object so tests can drive the retry loop without waiting
12
+ * on real time. Faking timers is not a workable alternative here: sinon's fake
13
+ * clock replaces the globals `node:test` uses to schedule subtests, which
14
+ * silently drops whole suites from the run.
15
+ */
16
+ export const serviceWorkerRetryPolicy = {
17
+ maxAttempts: 5,
18
+ delayMs: 2500,
19
+ };
20
+ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
21
+ export async function withServiceWorkerRetry(fn) {
22
+ const { maxAttempts, delayMs } = serviceWorkerRetryPolicy;
23
+ let lastError;
24
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
25
+ try {
26
+ return await fn();
27
+ }
28
+ catch (e) {
29
+ lastError = e;
30
+ if (attempt < maxAttempts - 1) {
31
+ await sleep(delayMs);
32
+ }
33
+ }
34
+ }
35
+ throw lastError;
36
+ }
37
+ //# sourceMappingURL=serviceWorkerRetry.js.map