opera-devtools-mcp 0.2.3 → 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 +3493 -0
  52. package/build/src/third_party/bundled-packages.json +9 -0
  53. package/build/src/third_party/devtools-formatter-worker.js +15340 -7
  54. package/build/src/third_party/devtools-heap-snapshot-worker.js +10337 -7
  55. package/build/src/third_party/index.js +179960 -26
  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
@@ -3,55 +3,10 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { logger } from '../logger.js';
7
6
  import { zod } from '../third_party/index.js';
7
+ import { logger } from '../utils/logger.js';
8
8
  import { ToolCategory } from './categories.js';
9
9
  import { CLOSE_PAGE_ERROR, definePageTool, defineTool, timeoutSchema, } from './ToolDefinition.js';
10
- async function navigateWithInterception(page, action, allowListString, timeout) {
11
- const allowList = allowListString
12
- ? allowListString.split(',').map((p) => new URLPattern(p.trim()))
13
- : undefined;
14
- const requestHandler = (interceptedRequest) => {
15
- if (!interceptedRequest.isNavigationRequest()) {
16
- void interceptedRequest.continue();
17
- return;
18
- }
19
- const requestUrl = interceptedRequest.url();
20
- const isAllowed = allowList.some((pattern) => pattern.test(requestUrl));
21
- if (isAllowed) {
22
- void interceptedRequest.continue();
23
- }
24
- else {
25
- logger(`Blocking request to: ${requestUrl}`);
26
- void interceptedRequest.abort('blockedbyclient');
27
- }
28
- };
29
- const cleanupInterception = async () => {
30
- if (allowList) {
31
- page.pptrPage.off('request', requestHandler);
32
- await page.pptrPage.setRequestInterception(false).catch(error => {
33
- logger(`Failed to disable request interception`, error);
34
- });
35
- }
36
- };
37
- if (allowList) {
38
- await page.pptrPage.setRequestInterception(true);
39
- page.pptrPage.on('request', requestHandler);
40
- }
41
- try {
42
- await page.waitForEventsAfterAction(async () => {
43
- try {
44
- await action();
45
- }
46
- finally {
47
- await cleanupInterception();
48
- }
49
- }, { timeout });
50
- }
51
- finally {
52
- await cleanupInterception();
53
- }
54
- }
55
10
  export const listPages = defineTool(args => {
56
11
  return {
57
12
  name: 'list_pages',
@@ -62,6 +17,7 @@ export const listPages = defineTool(args => {
62
17
  },
63
18
  schema: {},
64
19
  blockedByDialog: false,
20
+ verifyFilesSchema: {},
65
21
  handler: async (_request, response) => {
66
22
  response.setIncludePages(true);
67
23
  response.setListThirdPartyDeveloperTools();
@@ -86,6 +42,7 @@ export const selectPage = defineTool({
86
42
  .describe('Whether to focus the page and bring it to the top.'),
87
43
  },
88
44
  blockedByDialog: false,
45
+ verifyFilesSchema: {},
89
46
  handler: async (request, response, context) => {
90
47
  const page = context.getPageById(request.params.pageId);
91
48
  context.selectPage(page);
@@ -110,6 +67,7 @@ export const closePage = defineTool({
110
67
  .describe('The ID of the page to close. Call list_pages to list pages.'),
111
68
  },
112
69
  blockedByDialog: false,
70
+ verifyFilesSchema: {},
113
71
  handler: async (request, response, context) => {
114
72
  try {
115
73
  await context.closePage(request.params.pageId);
@@ -126,7 +84,7 @@ export const closePage = defineTool({
126
84
  response.setListThirdPartyDeveloperTools();
127
85
  },
128
86
  });
129
- export const newPage = defineTool(args => {
87
+ export const newPage = defineTool(() => {
130
88
  return {
131
89
  name: 'new_page',
132
90
  description: `Open a new tab and load a URL. Use project URL if not specified otherwise.`,
@@ -146,28 +104,23 @@ export const newPage = defineTool(args => {
146
104
  .describe('If specified, the page is created in an isolated browser context with the given name. ' +
147
105
  'Pages in the same browser context share cookies and storage. ' +
148
106
  'Pages in different browser contexts are fully isolated.'),
149
- ...(args?.experimentalNavigationAllowlist
150
- ? {
151
- allowList: zod
152
- .string()
153
- .optional()
154
- .describe('Optional comma-separated list of URL patterns to allow. If provided, all other navigations will be blocked.'),
155
- }
156
- : {}),
157
107
  ...timeoutSchema,
158
108
  },
159
109
  blockedByDialog: false,
110
+ verifyFilesSchema: {},
160
111
  handler: async (request, response, context) => {
161
112
  const page = await context.newPage(request.params.background, request.params.isolatedContext);
162
- await navigateWithInterception(page, () => page.pptrPage.goto(request.params.url, {
163
- timeout: request.params.timeout,
164
- }), request.params.allowList, request.params.timeout);
113
+ await page.waitForEventsAfterAction(async () => {
114
+ await page.pptrPage.goto(request.params.url, {
115
+ timeout: request.params.timeout,
116
+ });
117
+ }, { timeout: request.params.timeout });
165
118
  response.setIncludePages(true);
166
119
  response.setListThirdPartyDeveloperTools();
167
120
  },
168
121
  };
169
122
  });
170
- export const navigatePage = definePageTool(args => {
123
+ export const navigatePage = definePageTool(() => {
171
124
  return {
172
125
  name: 'navigate_page',
173
126
  description: `Go to a URL, or back, forward, or reload. Use project URL if not specified otherwise.`,
@@ -186,24 +139,17 @@ export const navigatePage = definePageTool(args => {
186
139
  .optional()
187
140
  .describe('Whether to ignore cache on reload.'),
188
141
  handleBeforeUnload: zod
189
- .enum(['accept', 'decline'])
142
+ .enum(['accept', 'dismiss'])
190
143
  .optional()
191
144
  .describe('Whether to auto accept or beforeunload dialogs triggered by this navigation. Default is accept.'),
192
145
  initScript: zod
193
146
  .string()
194
147
  .optional()
195
148
  .describe('A JavaScript script to be executed on each new document before any other scripts for the next navigation.'),
196
- ...(args?.experimentalNavigationAllowlist
197
- ? {
198
- allowList: zod
199
- .string()
200
- .optional()
201
- .describe('Optional comma-separated list of URL patterns to allow. If provided, all other navigations will be blocked.'),
202
- }
203
- : {}),
204
149
  ...timeoutSchema,
205
150
  },
206
151
  blockedByDialog: false,
152
+ verifyFilesSchema: {},
207
153
  handler: async (request, response) => {
208
154
  const page = request.page;
209
155
  const options = {
@@ -215,29 +161,14 @@ export const navigatePage = definePageTool(args => {
215
161
  if (!request.params.type) {
216
162
  request.params.type = 'url';
217
163
  }
218
- const handleBeforeUnload = request.params.handleBeforeUnload ?? 'accept';
219
- const dialogHandler = (dialog) => {
220
- if (dialog.type() === 'beforeunload') {
221
- if (handleBeforeUnload === 'accept') {
222
- response.appendResponseLine(`Accepted a beforeunload dialog.`);
223
- void dialog.accept();
224
- }
225
- else {
226
- response.appendResponseLine(`Declined a beforeunload dialog.`);
227
- void dialog.dismiss();
228
- }
229
- // We are not going to report the dialog like regular dialogs.
230
- page.clearDialog();
231
- }
232
- };
233
164
  let initScriptId;
234
165
  if (request.params.initScript) {
235
166
  const { identifier } = await page.pptrPage.evaluateOnNewDocument(request.params.initScript);
236
167
  initScriptId = identifier;
237
168
  }
238
- page.pptrPage.on('dialog', dialogHandler);
239
169
  try {
240
- await navigateWithInterception(page, async () => {
170
+ const action = request.params.handleBeforeUnload ?? 'accept';
171
+ const result = await page.waitForEventsAfterAction(async () => {
241
172
  switch (request.params.type) {
242
173
  case 'url':
243
174
  if (!request.params.url) {
@@ -282,15 +213,21 @@ export const navigatePage = definePageTool(args => {
282
213
  }
283
214
  break;
284
215
  }
285
- }, request.params.allowList, request.params.timeout);
216
+ }, {
217
+ timeout: request.params.timeout,
218
+ handleDialog: { beforeunload: action },
219
+ });
220
+ if (result.dialogHandled) {
221
+ response.appendResponseLine(`${action === 'dismiss' ? 'Dismissed' : 'Accepted'} a beforeunload dialog.`);
222
+ page.clearDialog();
223
+ }
286
224
  }
287
225
  finally {
288
- page.pptrPage.off('dialog', dialogHandler);
289
226
  if (initScriptId) {
290
227
  await page.pptrPage
291
228
  .removeScriptToEvaluateOnNewDocument(initScriptId)
292
229
  .catch(error => {
293
- logger(`Failed to remove init script`, error);
230
+ logger?.(`Failed to remove init script`, error);
294
231
  });
295
232
  }
296
233
  }
@@ -312,7 +249,8 @@ export const resizePage = definePageTool({
312
249
  height: zod.number().describe('Page height'),
313
250
  },
314
251
  blockedByDialog: false,
315
- handler: async (request, response, _context) => {
252
+ verifyFilesSchema: {},
253
+ handler: async (request, response) => {
316
254
  const page = request.page;
317
255
  try {
318
256
  const browser = page.pptrPage.browser();
@@ -354,7 +292,8 @@ export const handleDialog = definePageTool({
354
292
  .describe('Optional prompt text to enter into the dialog.'),
355
293
  },
356
294
  blockedByDialog: false,
357
- handler: async (request, response, _context) => {
295
+ verifyFilesSchema: {},
296
+ handler: async (request, response) => {
358
297
  const page = request.page;
359
298
  const dialog = page.getDialog();
360
299
  if (!dialog) {
@@ -367,7 +306,7 @@ export const handleDialog = definePageTool({
367
306
  }
368
307
  catch (err) {
369
308
  // Likely already handled by the user outside of MCP.
370
- logger(err);
309
+ logger?.(err);
371
310
  }
372
311
  response.appendResponseLine('Successfully accepted the dialog');
373
312
  break;
@@ -378,7 +317,7 @@ export const handleDialog = definePageTool({
378
317
  }
379
318
  catch (err) {
380
319
  // Likely already handled.
381
- logger(err);
320
+ logger?.(err);
382
321
  }
383
322
  response.appendResponseLine('Successfully dismissed the dialog');
384
323
  break;
@@ -402,10 +341,12 @@ export const getTabId = definePageTool({
402
341
  .describe(`The ID of the page to get the tab ID for. Call ${listPages().name} to get available pages.`),
403
342
  },
404
343
  blockedByDialog: false,
344
+ verifyFilesSchema: {},
405
345
  handler: async (request, response, context) => {
406
346
  const page = context.getPageById(request.params.pageId);
407
347
  const tabId = page.pptrPage._tabId;
408
348
  response.setTabId(tabId);
349
+ response.appendResponseLine(`Tab ID: ${tabId}`);
409
350
  },
410
351
  });
411
352
  //# sourceMappingURL=pages.js.map
@@ -4,9 +4,9 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import zlib from 'node:zlib';
7
- import { logger } from '../logger.js';
8
7
  import { zod, DevTools } from '../third_party/index.js';
9
- import { parseRawTraceBuffer, traceResultIsSuccess, } from '../trace-processing/parse.js';
8
+ import { parseRawTraceBuffer, traceResultIsSuccess, } from '../processors/PerformanceTrace.js';
9
+ import { logger } from '../utils/logger.js';
10
10
  import { ToolCategory } from './categories.js';
11
11
  import { definePageTool } from './ToolDefinition.js';
12
12
  const filePathSchema = zod
@@ -32,8 +32,10 @@ export const startTrace = definePageTool({
32
32
  filePath: filePathSchema,
33
33
  },
34
34
  blockedByDialog: true,
35
+ verifyFilesSchema: {
36
+ filePath: true,
37
+ },
35
38
  handler: async (request, response, context) => {
36
- context.validatePath(request.params.filePath);
37
39
  if (context.isRunningPerformanceTrace()) {
38
40
  response.appendResponseLine('Error: a performance trace is already running. Use performance_stop_trace to stop it. Only one trace can be running at any given time.');
39
41
  return;
@@ -41,47 +43,54 @@ export const startTrace = definePageTool({
41
43
  context.setIsRunningPerformanceTrace(true);
42
44
  const page = request.page;
43
45
  const pageUrlForTracing = page.pptrPage.url();
44
- if (request.params.reload) {
45
- // Before starting the recording, navigate to about:blank to clear out any state.
46
- await page.pptrPage.goto('about:blank', {
47
- waitUntil: ['networkidle0'],
48
- });
49
- }
50
- // Keep in sync with the categories arrays in:
51
- // https://source.chromium.org/chromium/chromium/src/+/main:third_party/devtools-frontend/src/front_end/panels/timeline/TimelineController.ts
52
- // https://github.com/GoogleChrome/lighthouse/blob/master/lighthouse-core/gather/gatherers/trace.js
53
- const categories = [
54
- '-*',
55
- 'blink.console',
56
- 'blink.user_timing',
57
- 'devtools.timeline',
58
- 'disabled-by-default-devtools.screenshot',
59
- 'disabled-by-default-devtools.timeline',
60
- 'disabled-by-default-devtools.timeline.invalidationTracking',
61
- 'disabled-by-default-devtools.timeline.frame',
62
- 'disabled-by-default-devtools.timeline.stack',
63
- 'disabled-by-default-v8.cpu_profiler',
64
- 'disabled-by-default-v8.cpu_profiler.hires',
65
- 'latencyInfo',
66
- 'loading',
67
- 'disabled-by-default-lighthouse',
68
- 'v8.execute',
69
- 'v8',
70
- ];
71
- await page.pptrPage.tracing.start({
72
- categories,
73
- });
74
- if (request.params.reload) {
75
- await page.pptrPage.goto(pageUrlForTracing, {
76
- waitUntil: ['load'],
46
+ try {
47
+ if (request.params.reload) {
48
+ // Before starting the recording, navigate to about:blank to clear out any state.
49
+ // We use `load` because `networkidle0` is known to be flaky for about:blank in Puppeteer.
50
+ await page.pptrPage.goto('about:blank', {
51
+ waitUntil: 'load',
52
+ });
53
+ }
54
+ const categories = [
55
+ '-*',
56
+ ...DevTools.TracingDefaultCategories,
57
+ // These categories are optional in DevTools, but enabled by default in the DevTools UI, so we enable them here too.
58
+ ...DevTools.TracingOptionalCategories.JsSampling,
59
+ ...DevTools.TracingOptionalCategories.Screenshot,
60
+ ];
61
+ await page.pptrPage.tracing.start({
62
+ categories,
77
63
  });
64
+ if (request.params.reload) {
65
+ await page.pptrPage.goto(pageUrlForTracing, {
66
+ waitUntil: ['load'],
67
+ });
68
+ }
69
+ if (request.params.autoStop) {
70
+ await new Promise(resolve => setTimeout(resolve, 5_000));
71
+ await stopTracingAndAppendOutput(page, response, context, request.params.filePath);
72
+ }
73
+ else {
74
+ response.appendResponseLine(`The performance trace is being recorded. Use performance_stop_trace to stop it.`);
75
+ }
78
76
  }
79
- if (request.params.autoStop) {
80
- await new Promise(resolve => setTimeout(resolve, 5_000));
81
- await stopTracingAndAppendOutput(page.pptrPage, response, context, request.params.filePath);
82
- }
83
- else {
84
- response.appendResponseLine(`The performance trace is being recorded. Use performance_stop_trace to stop it.`);
77
+ catch (error) {
78
+ // If a setup step (navigation, tracing.start) throws before
79
+ // stopTracingAndAppendOutput runs, the running flag would otherwise stay
80
+ // stuck `true` for the rest of the session, blocking all future traces.
81
+ // Unwind here: stop tracing if it was started and clear the flag. When
82
+ // autoStop already ran stopTracingAndAppendOutput the flag is already
83
+ // false and this is a no-op.
84
+ if (context.isRunningPerformanceTrace()) {
85
+ try {
86
+ await page.pptrPage.tracing.stop();
87
+ }
88
+ catch {
89
+ // Tracing may not have started yet; ignore.
90
+ }
91
+ context.setIsRunningPerformanceTrace(false);
92
+ }
93
+ throw error;
85
94
  }
86
95
  },
87
96
  });
@@ -96,13 +105,15 @@ export const stopTrace = definePageTool({
96
105
  filePath: filePathSchema,
97
106
  },
98
107
  blockedByDialog: true,
108
+ verifyFilesSchema: {
109
+ filePath: true,
110
+ },
99
111
  handler: async (request, response, context) => {
100
- context.validatePath(request.params.filePath);
101
112
  if (!context.isRunningPerformanceTrace()) {
102
113
  return;
103
114
  }
104
115
  const page = request.page;
105
- await stopTracingAndAppendOutput(page.pptrPage, response, context, request.params.filePath);
116
+ await stopTracingAndAppendOutput(page, response, context, request.params.filePath);
106
117
  },
107
118
  });
108
119
  export const analyzeInsight = definePageTool({
@@ -121,6 +132,7 @@ export const analyzeInsight = definePageTool({
121
132
  .describe('The name of the Insight you want more information on. For example: "DocumentLatency" or "LCPBreakdown"'),
122
133
  },
123
134
  blockedByDialog: false,
135
+ verifyFilesSchema: {},
124
136
  handler: async (request, response, context) => {
125
137
  const lastRecording = context.recordedTraces().at(-1);
126
138
  if (!lastRecording) {
@@ -132,7 +144,7 @@ export const analyzeInsight = definePageTool({
132
144
  });
133
145
  async function stopTracingAndAppendOutput(page, response, context, filePath) {
134
146
  try {
135
- const traceEventsBuffer = await page.tracing.stop();
147
+ const traceEventsBuffer = await page.pptrPage.tracing.stop();
136
148
  if (filePath && traceEventsBuffer) {
137
149
  let dataToWrite = traceEventsBuffer;
138
150
  if (filePath.endsWith('.gz')) {
@@ -147,10 +159,13 @@ async function stopTracingAndAppendOutput(page, response, context, filePath) {
147
159
  });
148
160
  });
149
161
  }
150
- const file = await context.saveFile(dataToWrite, filePath, filePath.endsWith('.gz') ? '.json.gz' : '.json');
162
+ const file = await context.saveFile(dataToWrite, filePath, filePath.endsWith('.gz') ? '.gz' : '.json');
151
163
  response.appendResponseLine(`The raw trace data was saved to ${file.filename}.`);
152
164
  }
153
- const result = await parseRawTraceBuffer(traceEventsBuffer);
165
+ const result = await parseRawTraceBuffer(traceEventsBuffer, {
166
+ cpuThrottling: page.cpuThrottlingRate,
167
+ networkThrottling: page.networkConditions ?? undefined,
168
+ });
154
169
  response.appendResponseLine('The performance trace has been stopped.');
155
170
  if (traceResultIsSuccess(result)) {
156
171
  if (context.isCruxEnabled()) {
@@ -169,8 +184,8 @@ async function stopTracingAndAppendOutput(page, response, context, filePath) {
169
184
  }
170
185
  /** We tell CrUXManager to fetch data so it's available when DevTools.PerformanceTraceFormatter is invoked */
171
186
  async function populateCruxData(result) {
172
- logger('populateCruxData called');
173
- const cruxManager = DevTools.CrUXManager.instance();
187
+ logger?.('populateCruxData called');
188
+ const cruxManager = DevTools.CrUXManager.CrUXManager.instance();
174
189
  // go/jtfbx. Yes, we're aware this API key is public. ;)
175
190
  cruxManager.setEndpointForTesting('https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=AIzaSyBn5gimNjhiEyA_euicSKko6IlD3HdgUfk');
176
191
  const cruxSetting = DevTools.Common.Settings.Settings.instance().createSetting('field-data', {
@@ -182,13 +197,13 @@ async function populateCruxData(result) {
182
197
  urls.push(result.parsedTrace.data.Meta.mainFrameURL);
183
198
  const urlSet = new Set(urls);
184
199
  if (urlSet.size === 0) {
185
- logger('No URLs found for CrUX data');
200
+ logger?.('No URLs found for CrUX data');
186
201
  return;
187
202
  }
188
- logger(`Fetching CrUX data for ${urlSet.size} URLs: ${Array.from(urlSet).join(', ')}`);
203
+ logger?.(`Fetching CrUX data for ${urlSet.size} URLs: ${Array.from(urlSet).join(', ')}`);
189
204
  const cruxData = await Promise.all(Array.from(urlSet).map(async (url) => {
190
205
  const data = await cruxManager.getFieldDataForPage(url);
191
- logger(`CrUX data for ${url}: ${data ? 'found' : 'not found'}`);
206
+ logger?.(`CrUX data for ${url}: ${data ? 'found' : 'not found'}`);
192
207
  return data;
193
208
  }));
194
209
  result.parsedTrace.metadata.cruxFieldData = cruxData;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { zod } from '../third_party/index.js';
7
+ import { ToolCategory } from './categories.js';
8
+ import { defineTool } from './ToolDefinition.js';
9
+ const manifestIdSchema = zod
10
+ .string()
11
+ .describe('The manifest ID of the web app: the resolved `id` member of its manifest. ' +
12
+ 'If `id` is omitted, it defaults to the resolved `start_url` ' +
13
+ '(e.g. "https://example.com/"). See https://w3c.github.io/manifest/#id-member.');
14
+ const displayModeSchema = zod
15
+ .enum(['standalone', 'browser'])
16
+ .optional()
17
+ .describe('Optional user display mode preference applied after install. ' +
18
+ '"standalone" opens the app in its own window; "browser" opens it as a ' +
19
+ 'tab. Installs via the PWA CDP domain default to "browser" because they ' +
20
+ 'do not simulate the install dialog, so pass "standalone" to get an ' +
21
+ 'app-window experience.');
22
+ export const installPwa = defineTool({
23
+ name: 'install_pwa',
24
+ description: 'Installs a Progressive Web App (PWA) identified by its manifest ID. ' +
25
+ 'This installs through the PWA CDP domain without a user gesture or install ' +
26
+ 'dialog. DevTools installs default to browser display mode.',
27
+ annotations: {
28
+ category: ToolCategory.PWA,
29
+ readOnlyHint: false,
30
+ },
31
+ schema: {
32
+ manifestId: manifestIdSchema,
33
+ installUrlOrBundleUrl: zod
34
+ .string()
35
+ .describe('The location of the app or bundle. For a normal site this is the page ' +
36
+ 'URL; for an Isolated Web App it can be a file:// or http(s):// ' +
37
+ 'signed web bundle.'),
38
+ displayMode: displayModeSchema,
39
+ },
40
+ blockedByDialog: false,
41
+ // We do not validate file paths for remote browser instances
42
+ // because they are on the remote host and not accessed by the MCP server.
43
+ verifyFilesSchema: {
44
+ installUrlOrBundleUrl: {
45
+ local: true,
46
+ remote: false,
47
+ },
48
+ },
49
+ handler: async (request, response, context) => {
50
+ const { manifestId, installUrlOrBundleUrl, displayMode } = request.params;
51
+ await context.installPWA({
52
+ manifestId,
53
+ installUrlOrBundleUrl,
54
+ displayMode,
55
+ });
56
+ response.appendResponseLine(`Installed PWA with manifest ID: ${manifestId}`);
57
+ if (displayMode) {
58
+ response.appendResponseLine(`Display mode set to: ${displayMode}`);
59
+ }
60
+ },
61
+ });
62
+ export const uninstallPwa = defineTool({
63
+ name: 'uninstall_pwa',
64
+ description: 'Uninstalls a Progressive Web App identified by its manifest ID and ' +
65
+ 'closes any open app windows.',
66
+ annotations: {
67
+ category: ToolCategory.PWA,
68
+ readOnlyHint: false,
69
+ },
70
+ schema: {
71
+ manifestId: manifestIdSchema,
72
+ },
73
+ blockedByDialog: false,
74
+ verifyFilesSchema: {},
75
+ handler: async (request, response, context) => {
76
+ const { manifestId } = request.params;
77
+ await context.uninstallPWA({ manifestId });
78
+ response.appendResponseLine(`Uninstalled PWA with manifest ID: ${manifestId}`);
79
+ response.setIncludePages(true);
80
+ },
81
+ });
82
+ export const launchPwa = defineTool({
83
+ name: 'launch_pwa',
84
+ description: 'Launches an installed Progressive Web App using its saved display mode. ' +
85
+ 'Optionally opens a specific URL within the same app instead of the default ' +
86
+ 'start URL.',
87
+ annotations: {
88
+ category: ToolCategory.PWA,
89
+ readOnlyHint: false,
90
+ },
91
+ schema: {
92
+ manifestId: manifestIdSchema,
93
+ url: zod
94
+ .string()
95
+ .optional()
96
+ .describe('Optional URL within the app to open instead of the default start URL.'),
97
+ },
98
+ blockedByDialog: false,
99
+ verifyFilesSchema: {},
100
+ handler: async (request, response, context) => {
101
+ const { manifestId, url } = request.params;
102
+ const page = await context.launchPWA({ manifestId, url });
103
+ response.appendResponseLine(`Launched PWA with manifest ID: ${manifestId} (${page.url()})`);
104
+ response.setIncludePages(true);
105
+ },
106
+ });
107
+ export const getOsAppState = defineTool({
108
+ name: 'get_os_app_state',
109
+ description: 'Returns the OS integration state (badge count and registered file ' +
110
+ 'handlers) for an installed web app, identified by its manifest ID.',
111
+ annotations: {
112
+ category: ToolCategory.PWA,
113
+ readOnlyHint: true,
114
+ },
115
+ schema: {
116
+ manifestId: manifestIdSchema,
117
+ },
118
+ blockedByDialog: false,
119
+ verifyFilesSchema: {},
120
+ handler: async (request, response, context) => {
121
+ const { manifestId } = request.params;
122
+ const state = await context.getPWAState({ manifestId });
123
+ response.appendResponseLine(`OS app state for manifest ID: ${manifestId}`);
124
+ response.appendResponseLine(`Badge count: ${state.badgeCount}`);
125
+ response.appendResponseLine(`File handlers: ${JSON.stringify(state.fileHandlers)}`);
126
+ },
127
+ });
128
+ //# sourceMappingURL=pwa.js.map
@@ -7,7 +7,6 @@ import fs from 'node:fs/promises';
7
7
  import os from 'node:os';
8
8
  import path from 'node:path';
9
9
  import { zod } from '../third_party/index.js';
10
- import { ensureExtension } from '../utils/files.js';
11
10
  import { ToolCategory } from './categories.js';
12
11
  import { definePageTool } from './ToolDefinition.js';
13
12
  async function generateTempFilePath() {
@@ -30,23 +29,32 @@ export const startScreencast = definePageTool(args => ({
30
29
  .describe(`Output file path (${supportedExtensions.join(',')} are supported). Uses mkdtemp to generate a unique path if not provided.`),
31
30
  },
32
31
  blockedByDialog: false,
32
+ verifyFilesSchema: {
33
+ filePath: true,
34
+ },
33
35
  handler: async (request, response, context) => {
34
- context.validatePath(request.params.filePath);
35
36
  if (context.getScreenRecorder() !== null) {
36
37
  response.appendResponseLine('Error: a screencast recording is already in progress. Use screencast_stop to stop it before starting a new one.');
37
38
  return;
38
39
  }
39
- const filePath = request.params.filePath ?? (await generateTempFilePath());
40
- let enforcedExtension = '.mp4';
41
- let format = 'mp4';
42
- for (const supportedExtension of supportedExtensions) {
43
- if (filePath.endsWith(supportedExtension)) {
44
- enforcedExtension = supportedExtension;
45
- format = supportedExtension.substring(1);
46
- break;
47
- }
40
+ const requestedFilePath = request.params.filePath;
41
+ const filePath = requestedFilePath ?? (await generateTempFilePath());
42
+ // Match the extension case-insensitively so e.g. `.WEBM` is recognized as
43
+ // WebM. An explicitly requested but unsupported extension is rejected
44
+ // rather than being silently rewritten to `.mp4` (which would change both
45
+ // the format and the output path from what was requested). A missing
46
+ // extension falls back to `.mp4`. The matched extension is normalized to
47
+ // lower case.
48
+ const requestedExtension = path.extname(filePath);
49
+ const matchedExtension = supportedExtensions.find(supportedExtension => supportedExtension === requestedExtension.toLowerCase());
50
+ if (!matchedExtension && requestedExtension !== '') {
51
+ throw new Error(`Unsupported screencast file extension "${requestedExtension}". ` +
52
+ `Supported formats: ${supportedExtensions.join(', ')} (case-insensitive).`);
48
53
  }
49
- const resolvedPath = ensureExtension(path.resolve(filePath), enforcedExtension);
54
+ const enforcedExtension = matchedExtension ?? '.mp4';
55
+ const format = (matchedExtension?.substring(1) ??
56
+ 'mp4');
57
+ const resolvedPath = await context.ensureExtension(filePath, enforcedExtension);
50
58
  const page = request.page;
51
59
  let recorder;
52
60
  try {
@@ -57,6 +65,19 @@ export const startScreencast = definePageTool(args => ({
57
65
  });
58
66
  }
59
67
  catch (err) {
68
+ // If we generated a temporary directory for this recording, remove it so
69
+ // a failed start (e.g. ffmpeg missing) does not leak an empty directory.
70
+ if (requestedFilePath === undefined) {
71
+ try {
72
+ await fs.rm(path.dirname(resolvedPath), {
73
+ recursive: true,
74
+ force: true,
75
+ });
76
+ }
77
+ catch {
78
+ // no-op
79
+ }
80
+ }
60
81
  const message = err instanceof Error ? err.message : String(err);
61
82
  if (message.includes('ENOENT') && message.includes('ffmpeg')) {
62
83
  throw new Error('ffmpeg is required for screencast recording but was not found. ' +
@@ -78,9 +99,11 @@ export const stopScreencast = definePageTool({
78
99
  },
79
100
  schema: {},
80
101
  blockedByDialog: false,
102
+ verifyFilesSchema: {},
81
103
  handler: async (_request, response, context) => {
82
104
  const data = context.getScreenRecorder();
83
105
  if (!data) {
106
+ response.appendResponseLine('Error: no active screencast recording to stop.');
84
107
  return;
85
108
  }
86
109
  try {