brave-mcp 1.10.1 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -1
- package/{build → out/Default/gen}/src/BrowserManager.js +71 -50
- package/out/Default/gen/src/LogpointManager.js +307 -0
- package/{build → out/Default/gen}/src/McpContext.js +487 -109
- package/{build → out/Default/gen}/src/McpPage.js +477 -80
- package/{build → out/Default/gen}/src/McpResponse.js +257 -100
- package/{build → out/Default/gen}/src/SlimMcpResponse.js +5 -1
- package/{build → out/Default/gen}/src/TextSnapshot.js +40 -20
- package/out/Default/gen/src/ToolHandler.js +492 -0
- package/{build → out/Default/gen}/src/bin/brave-devtools-mcp-main.js +6 -4
- package/{build → out/Default/gen}/src/bin/brave-devtools-mcp.js +1 -1
- package/{build → out/Default/gen}/src/bin/brave-devtools.js +19 -12
- package/{build → out/Default/gen}/src/collectors/PageCollector.js +28 -12
- package/{build → out/Default/gen}/src/collectors/ServiceWorkerCollector.js +2 -2
- package/out/Default/gen/src/collectors/constants.js +7 -0
- package/{build → out/Default/gen}/src/config/ConfigLocator.js +5 -6
- package/out/Default/gen/src/config/ConfigParser.js +162 -0
- package/out/Default/gen/src/config/ConfigWatcher.js +94 -0
- package/out/Default/gen/src/config/ServerConfig.js +251 -0
- package/{build → out/Default/gen}/src/config/browser-options.js +13 -4
- package/{build → out/Default/gen}/src/config/category-options.js +9 -3
- package/out/Default/gen/src/config/cli-commands.js +186 -0
- package/{build → out/Default/gen}/src/config/cli-options.js +469 -58
- package/{build → out/Default/gen}/src/config/mcp-options.js +96 -48
- package/{build → out/Default/gen}/src/config/puppeteer-options.js +12 -13
- package/{build → out/Default/gen}/src/config/tool-options.js +4 -0
- package/{build → out/Default/gen}/src/daemon/client.js +38 -26
- package/{build → out/Default/gen}/src/daemon/daemon.js +11 -7
- package/{build → out/Default/gen}/src/daemon/utils.js +45 -2
- package/out/Default/gen/src/devtools/DevToolsCommentBridge.js +151 -0
- package/{build → out/Default/gen}/src/devtools/DevtoolsUtils.js +96 -22
- package/{build → out/Default/gen}/src/devtools/McpHostBindingAdapter.js +0 -1
- package/{build → out/Default/gen}/src/formatters/ConsoleFormatter.js +29 -2
- package/{build → out/Default/gen}/src/formatters/CssFormatter.js +3 -2
- package/{build → out/Default/gen}/src/formatters/HeapSnapshotFormatter.js +65 -64
- package/{build → out/Default/gen}/src/formatters/IssueFormatter.js +4 -3
- package/out/Default/gen/src/formatters/LogpointFormatter.js +62 -0
- package/{build → out/Default/gen}/src/formatters/NetworkFormatter.js +78 -24
- package/out/Default/gen/src/formatters/RemoteObjectFormatter.js +231 -0
- package/{build → out/Default/gen}/src/formatters/SnapshotFormatter.js +13 -3
- package/out/Default/gen/src/formatters/WebAuthnFormatter.js +56 -0
- package/out/Default/gen/src/index.js +434 -0
- package/{build → out/Default/gen}/src/processors/ChunkedTraceParser.js +11 -11
- package/out/Default/gen/src/processors/HeapSnapshotManager.js +569 -0
- package/{build → out/Default/gen}/src/processors/PerformanceTrace.js +9 -9
- package/{build → out/Default/gen}/src/telemetry/ClearcutLogger.js +1 -1
- package/{build → out/Default/gen}/src/telemetry/transformation.js +8 -12
- package/{build → out/Default/gen}/src/telemetry/watchdog/main.js +1 -1
- package/{build → out/Default/gen}/src/third_party/THIRD_PARTY_NOTICES +753 -717
- package/out/Default/gen/src/third_party/bundled-packages.json +11 -0
- package/out/Default/gen/src/third_party/cli.js +9239 -0
- package/out/Default/gen/src/third_party/devtools-formatter-worker.js +17825 -0
- package/out/Default/gen/src/third_party/devtools-heap-snapshot-worker.js +12575 -0
- package/{build → out/Default/gen}/src/third_party/index.js +67772 -56346
- package/out/Default/gen/src/third_party/issue-descriptions/genericGeolocationPromptWithoutUserGesture.md +5 -0
- package/out/Default/gen/src/third_party/lighthouse-devtools-mcp-bundle.js +60395 -0
- package/out/Default/gen/src/tools/ToolDefinition.js +180 -0
- package/{build → out/Default/gen}/src/tools/categories.js +2 -0
- package/{build → out/Default/gen}/src/tools/comments.js +55 -3
- package/{build → out/Default/gen}/src/tools/console.js +8 -6
- package/{build → out/Default/gen}/src/tools/css.js +1 -0
- package/{build → out/Default/gen}/src/tools/emulation.js +22 -8
- package/{build → out/Default/gen}/src/tools/extensions.js +5 -0
- package/{build → out/Default/gen}/src/tools/input.js +30 -13
- package/out/Default/gen/src/tools/lighthouse.js +273 -0
- package/out/Default/gen/src/tools/logpoints.js +51 -0
- package/{build → out/Default/gen}/src/tools/memory.js +138 -74
- package/{build → out/Default/gen}/src/tools/network.js +5 -4
- package/{build → out/Default/gen}/src/tools/pages.js +50 -8
- package/{build → out/Default/gen}/src/tools/performance.js +42 -8
- package/{build → out/Default/gen}/src/tools/pwa.js +7 -0
- package/out/Default/gen/src/tools/screencast.js +160 -0
- package/{build → out/Default/gen}/src/tools/screenshot.js +52 -21
- package/{build → out/Default/gen}/src/tools/script.js +39 -7
- package/{build → out/Default/gen}/src/tools/slim/tools.js +23 -12
- package/{build → out/Default/gen}/src/tools/snapshot.js +29 -5
- package/{build → out/Default/gen}/src/tools/thirdPartyDeveloper.js +9 -23
- package/out/Default/gen/src/tools/toolNames.js +10 -0
- package/{build → out/Default/gen}/src/tools/tools.js +4 -0
- package/out/Default/gen/src/tools/webauthn.js +51 -0
- package/{build → out/Default/gen}/src/tools/webmcp.js +11 -24
- package/out/Default/gen/src/types.js +21 -0
- package/out/Default/gen/src/utils/TaskRunner.js +136 -0
- package/{build → out/Default/gen}/src/utils/WaitForHelper.js +15 -2
- package/{build → out/Default/gen}/src/utils/bytes.js +2 -12
- package/{build → out/Default/gen}/src/utils/check-for-updates.js +2 -2
- package/out/Default/gen/src/utils/disclaimers.js +25 -0
- package/{build → out/Default/gen}/src/utils/errorHandling.js +6 -0
- package/{build → out/Default/gen}/src/utils/keyboard.js +15 -5
- package/out/Default/gen/src/utils/logger.js +131 -0
- package/out/Default/gen/src/utils/resourceType.js +27 -0
- package/out/Default/gen/src/utils/text.js +44 -0
- package/out/Default/gen/src/utils/timeout.js +14 -0
- package/{build → out/Default/gen}/src/utils/url.js +82 -10
- package/{build → out/Default/gen}/src/version.js +1 -1
- package/package.json +31 -23
- package/skills/a11y-debugging/SKILL.md +7 -12
- package/skills/brave-devtools/SKILL.md +1 -0
- package/skills/brave-devtools-cli/SKILL.md +15 -13
- package/skills/cookie-debugging/SKILL.md +8 -5
- package/skills/debug-optimize-lcp/SKILL.md +1 -1
- package/skills/troubleshooting/SKILL.md +4 -5
- package/build/src/ToolHandler.js +0 -374
- package/build/src/config/ConfigParser.js +0 -213
- package/build/src/config/cli-commands.js +0 -34
- package/build/src/devtools/DevToolsCommentBridge.js +0 -91
- package/build/src/index.js +0 -276
- package/build/src/processors/HeapSnapshotManager.js +0 -274
- package/build/src/third_party/bundled-packages.json +0 -11
- package/build/src/third_party/devtools-formatter-worker.js +0 -16545
- package/build/src/third_party/devtools-heap-snapshot-worker.js +0 -12067
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +0 -58435
- package/build/src/tools/ToolDefinition.js +0 -92
- package/build/src/tools/lighthouse.js +0 -149
- package/build/src/tools/screencast.js +0 -119
- package/build/src/types.js +0 -10
- package/build/src/utils/logger.js +0 -61
- package/build/src/utils/text.js +0 -15
- /package/{build → out/Default/gen}/src/McpWorker.js +0 -0
- /package/{build → out/Default/gen}/src/bin/check-latest-version.js +0 -0
- /package/{build → out/Default/gen}/src/daemon/types.js +0 -0
- /package/{build → out/Default/gen}/src/devtools/issueDescriptions.js +0 -0
- /package/{build → out/Default/gen}/src/formatters/CommentFormatter.js +0 -0
- /package/{build → out/Default/gen}/src/telemetry/WatchdogClient.js +0 -0
- /package/{build → out/Default/gen}/src/telemetry/errors.js +0 -0
- /package/{build → out/Default/gen}/src/telemetry/flagUtils.js +0 -0
- /package/{build → out/Default/gen}/src/telemetry/metricsRegistry.js +0 -0
- /package/{build → out/Default/gen}/src/telemetry/persistence.js +0 -0
- /package/{build → out/Default/gen}/src/telemetry/types.js +0 -0
- /package/{build → out/Default/gen}/src/telemetry/watchdog/ClearcutSender.js +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/CoepCoopSandboxedIframeCannotNavigateToCoopPage.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/CoepCorpNotSameOrigin.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/CoepCorpNotSameOriginAfterDefaultedToSameOriginByCoep.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/CoepCorpNotSameSite.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/CoepFrameResourceNeedsCoepHeader.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/CompatibilityModeQuirks.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/CookieAttributeValueExceedsMaxSize.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/LowTextContrast.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorRead.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorSet.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnRead.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnSet.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeRead.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeSet.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/bounceTrackingMitigations.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/clientHintMetaTagAllowListInvalidOrigin.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/clientHintMetaTagModifiedHTML.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistEmbeddingRequirementNotSatisfied.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistIFrameAttributeLoosensEmbeddingRequirement.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowConnectionAllowlistFrom.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowlistItemType.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistInvalidHeader.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistInvalidUrlPattern.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistItemNotInnerList.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistMoreThanOneList.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistReportingEndpointNotToken.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieCrossSiteRedirectDowngrade.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieExcludeDomainNonAscii.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieExcludePortMismatch.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieExcludeSchemeMismatch.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieExcludeThirdPartyPhaseoutRead.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieExcludeThirdPartyPhaseoutSet.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieWarnDomainNonAscii.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieWarnMetadataGrantRead.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieWarnMetadataGrantReadOptOut.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieWarnMetadataGrantSet.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieWarnMetadataGrantSetOptOut.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieWarnThirdPartyPhaseoutRead.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieWarnThirdPartyPhaseoutSet.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsAllowCredentialsRequired.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsDisabledScheme.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsDisallowedByMode.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsHeaderDisallowedByPreflightResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsInvalidHeaderValues.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsLocalNetworkAccessPermissionDenied.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsMethodDisallowedByPreflightResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsNoCorsRedirectModeNotFollow.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsOriginMismatch.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsPreflightResponseInvalid.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsRedirectContainsCredentials.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsWildcardOriginNotAllowed.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cspEvalViolation.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cspInlineViolation.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cspTrustedTypesPolicyViolation.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cspTrustedTypesSinkViolation.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cspURLViolation.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/deprecation.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestAccountsEmptyList.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestAccountsHttpNotFound.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidContentType.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestAccountsNoResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestDnsFetchFailed.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestDnsInvalidRecord.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownHttpNotFound.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidContentType.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownNoResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestInvalidEmail.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestJwksHttpNotFound.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestJwksInvalidResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestKeyBindingSigningFailed.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestRpOriginIsOpaque.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenHttpNotFound.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidContentType.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidSdJwt.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenMalformedSdJwt.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenNoResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidAudience.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidIssuedAt.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidNonce.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidSdHash.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidTyp.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingAud.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingCnf.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingIat.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingNonce.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingSdHash.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbSignatureFailed.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmail.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmailVerified.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidHolderKey.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuedAt.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuer.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtJwksMissingKeys.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingCnf.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingEmail.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIat.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIss.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtSignatureFailed.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtUnsupportedHeaderAlg.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestUserLoggedOut.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownAccountsEndpointCrossOrigin.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownHttpNotFound.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidContentType.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownIssuanceEndpointCrossOrigin.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownListEmpty.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingAccountsEndpoint.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingIssuanceEndpoint.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownNoResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownUnsupportedSigningAlgorithm.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestAccountsBlockedByConnectionAllowlist.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestAccountsHttpNotFound.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestAccountsInvalidResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestAccountsNoResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestApprovalDeclined.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestCanceled.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestConfigBlockedByConnectionAllowlist.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestErrorFetchingSignin.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestErrorIdToken.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestIdTokenBlockedByConnectionAllowlist.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestIdTokenHttpNotFound.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidRequest.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestIdTokenNoResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestInvalidSigninResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestManifestHttpNotFound.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestManifestInvalidResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestManifestNoResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestTooManyRequests.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestWellKnownBlockedByConnectionAllowlist.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidAccountsResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidConfigOrWellKnown.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoAccountSharingPermission.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoApiPermission.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoReturningUserFromFetchedAccounts.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotIframe.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotPotentiallyTrustworthy.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSameOrigin.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSignedInWithIdp.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/fetchingPartitionedBlobURL.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericBackUINavigationWouldSkipAd.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormAriaLabelledByToNonExistingIdError.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormAutocompleteAttributeEmptyError.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormDuplicateIdForInputError.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormEmptyIdAndNameAttributesForInputError.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormInputAssignedAutocompleteValueToIdOrNameAttributeError.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormInputHasWrongButWellIntendedAutocompleteValueError.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormInputWithNoLabelError.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormLabelForMatchesNonExistingIdError.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormLabelForNameError.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormLabelHasNeitherForNorNestedInputError.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormModelContextMissingToolDescription.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormModelContextMissingToolName.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormModelContextParameterMissingName.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormModelContextParameterMissingTitleAndDescription.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormModelContextRequiredParameterMissingName.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericNavigationEntryMarkedSkippable.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericResponseWasBlockedByORB.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/heavyAd.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/lazyLoadImageZeroSize.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/mixedContent.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/navigatingPartitionedBlobURL.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementActivationDisabled.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluder.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluderParent.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementCspFrameAncestorsMissing.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementFencedFrameDisallowed.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementFontSizeTooLarge.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementFontSizeTooSmall.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementGeolocationDeprecated.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementInsetBoxShadowUnsupported.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementInvalidDisplayStyle.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementInvalidSizeValue.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementInvalidType.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementInvalidTypeActivation.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementLowContrast.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementNonOpaqueColor.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementPaddingBottomUnsupported.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementPaddingRightUnsupported.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementPermissionsPolicyBlocked.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementRegistrationFailed.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementRequestInProgress.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementSecurityChecksFailed.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementTypeNotSupported.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementUntrustedEvent.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/placeholderDescriptionForInvisibleIssues.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/propertyRuleInvalidNameIssue.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/propertyRuleIssue.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedOptGroupChild.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedSelectChild.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentAttributesSelectDescendant.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentLegendChild.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentOptionChild.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/selectElementAccessibilityNonPhrasingContentOptionChild.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/selectivePermissionsIntervention.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedArrayBuffer.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryUseErrorCrossOriginNoCorsRequest.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryUseErrorDictionaryLoadFailure.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryUseErrorMatchingDictionaryNotUsed.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryUseErrorUnexpectedContentDictionaryHeader.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorCossOriginNoCorsRequest.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorDisallowedBySettings.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorExpiredResponse.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorFeatureDisabled.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInsufficientResources.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidMatchField.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidStructuredHeader.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidTTLField.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNavigationRequest.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNoMatchField.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonIntegerTTLField.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonListMatchDestField.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonSecureContext.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringIdField.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringInMatchDestList.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringMatchField.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonTokenTypeField.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorRequestAborted.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorShuttingDown.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorTooLongIdField.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorUnsupportedType.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriInvalidSignatureHeader.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriInvalidSignatureInputHeader.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriMissingSignatureHeader.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriMissingSignatureInputHeader.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureHeaderValueIsIncorrectLength.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureHeaderValueIsNotByteSequence.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureHeaderValueIsParameterized.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentName.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentType.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidDerivedComponentParameter.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidHeaderComponentParameter.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidParameter.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderKeyIdLength.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingLabel.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingRequiredParameters.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderValueMissingComponents.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderValueNotInnerList.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriValidationFailedIntegrityMismatch.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriValidationFailedInvalidLength.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriValidationFailedSignatureExpired.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriValidationFailedSignatureMismatch.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/stylesheetLateImport.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/stylesheetRequestFailed.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/summaryElementAccessibilityInteractiveContentSummaryDescendant.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestLength.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestType.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/unencodedDigestMalformedDictionary.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/unencodedDigestUnknownAlgorithm.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/webInstallManifestMissingId.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/webInstallManifestMissingNameOrShortName.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/webInstallManifestParsingOrNetworkError.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/webInstallNoManifest.md +0 -0
- /package/{build → out/Default/gen}/src/third_party/issue-descriptions/webInstallStartUrlInvalid.md +0 -0
- /package/{build → out/Default/gen}/src/utils/files.js +0 -0
- /package/{build → out/Default/gen}/src/utils/id.js +0 -0
- /package/{build → out/Default/gen}/src/utils/pagination.js +0 -0
- /package/{build → out/Default/gen}/src/utils/polyfill.js +0 -0
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ When the agent connects, Brave asks for permission. Click **Allow**.
|
|
|
62
62
|
|
|
63
63
|
Use [Chrome DevTools MCP](https://github.com/ChromeDevTools/chrome-devtools-mcp) when your target browser is Chrome. Use `brave-mcp` when your target browser is Brave and you want Brave-native discovery, profiles, channels, naming, and privacy defaults without giving up upstream features.
|
|
64
64
|
|
|
65
|
-
## [Tool reference](./docs/tool-reference.md) | [Changelog](./CHANGELOG.md) | [Contributing](./CONTRIBUTING.md) | [Troubleshooting](./docs/troubleshooting.md) | [Design principles](./docs/design-principles.md)
|
|
65
|
+
## [Tool reference](./docs/tool-reference.md) | [CLI](./docs/cli.md) | [Skills](#skills) | [Changelog](./CHANGELOG.md) | [Contributing](./CONTRIBUTING.md) | [Troubleshooting](./docs/troubleshooting.md) | [Design principles](./docs/design-principles.md)
|
|
66
66
|
|
|
67
67
|
## Key features
|
|
68
68
|
|
|
@@ -163,6 +163,70 @@ Brave asks you to allow the connection. After you click **Allow**, your MCP clie
|
|
|
163
163
|
> [!NOTE]
|
|
164
164
|
> The MCP server connects to Brave only when the MCP client first uses a tool that needs a browser, not when the client starts. Without `--autoConnect`, that is when the server launches its own Brave.
|
|
165
165
|
|
|
166
|
+
## CLI
|
|
167
|
+
|
|
168
|
+
The package also includes the `brave-devtools` CLI. Use it to control the browser from your terminal without MCP:
|
|
169
|
+
|
|
170
|
+
```sh
|
|
171
|
+
npm i brave-mcp@latest -g
|
|
172
|
+
brave-devtools navigate_page 1 --url "https://brave.com"
|
|
173
|
+
brave-devtools take_screenshot 1 --filePath screenshot.png
|
|
174
|
+
brave-devtools stop
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
See the [CLI guide][cli] for all commands and options.
|
|
178
|
+
|
|
179
|
+
## Skills
|
|
180
|
+
|
|
181
|
+
The package includes [skills](./skills) that teach your agent how to use Brave DevTools:
|
|
182
|
+
|
|
183
|
+
| Skill | Use it to |
|
|
184
|
+
| --------------------------------------------------------- | ---------------------------------------------- |
|
|
185
|
+
| [`brave-devtools`](./skills/brave-devtools) | Debug and automate pages with the MCP tools. |
|
|
186
|
+
| [`brave-devtools-cli`](./skills/brave-devtools-cli) | Run the CLI and write shell scripts with it. |
|
|
187
|
+
| [`a11y-debugging`](./skills/a11y-debugging) | Audit and fix accessibility issues. |
|
|
188
|
+
| [`cookie-debugging`](./skills/cookie-debugging) | Debug cookies, sessions, and authentication. |
|
|
189
|
+
| [`debug-optimize-lcp`](./skills/debug-optimize-lcp) | Debug and improve Largest Contentful Paint. |
|
|
190
|
+
| [`memory-leak-debugging`](./skills/memory-leak-debugging) | Find and fix memory leaks. |
|
|
191
|
+
| [`troubleshooting`](./skills/troubleshooting) | Fix connection problems between MCP and Brave. |
|
|
192
|
+
|
|
193
|
+
### Install skills as a plugin
|
|
194
|
+
|
|
195
|
+
A plugin installs the MCP server and all skills, including the CLI skill:
|
|
196
|
+
|
|
197
|
+
- **Claude Code:** Run `/plugin marketplace add triuzzi/brave-devtools-mcp`, then `/plugin install brave-devtools-mcp@brave-devtools-plugins`.
|
|
198
|
+
- **Copilot / VS Code:** Run **Chat: Install Plugin From Source** and enter `triuzzi/brave-devtools-mcp`.
|
|
199
|
+
- **Gemini CLI:** Run `gemini extensions install --auto-update https://github.com/triuzzi/brave-devtools-mcp`.
|
|
200
|
+
|
|
201
|
+
See the [Client Configurations Guide][client-configurations-guide] for more information.
|
|
202
|
+
|
|
203
|
+
### Install skills manually
|
|
204
|
+
|
|
205
|
+
If your agent does not support plugins, copy the skill folders into the skills directory of your agent. The npm package contains the skills:
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
npm i brave-mcp@latest -g
|
|
209
|
+
# Example for Claude Code. Use the skills directory of your agent.
|
|
210
|
+
mkdir -p ~/.claude/skills
|
|
211
|
+
cp -R "$(npm root -g)/brave-mcp/skills/brave-devtools-cli" ~/.claude/skills/
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
> [!IMPORTANT]
|
|
215
|
+
> The `brave-devtools-cli` skill requires the CLI. Install the package globally before you use the skill.
|
|
216
|
+
|
|
217
|
+
### Keep skills up to date
|
|
218
|
+
|
|
219
|
+
Skills describe the tools of a specific version. Update the skills each time you update the MCP server or the CLI. Outdated skills can tell your agent to use tools or parameters that changed.
|
|
220
|
+
|
|
221
|
+
- **Plugins:** Use the update command of the plugin manager of your agent. The Gemini CLI command above enables automatic updates with `--auto-update`.
|
|
222
|
+
- **Manual installs:** Update the package, then copy the skill folders again:
|
|
223
|
+
|
|
224
|
+
```sh
|
|
225
|
+
npm i brave-mcp@latest -g
|
|
226
|
+
mkdir -p ~/.claude/skills
|
|
227
|
+
cp -R "$(npm root -g)/brave-mcp/skills/brave-devtools-cli" ~/.claude/skills/
|
|
228
|
+
```
|
|
229
|
+
|
|
166
230
|
## Tools
|
|
167
231
|
|
|
168
232
|
If you run into any issues, checkout our [troubleshooting guide][troubleshooting].
|
|
@@ -172,6 +236,8 @@ See the full [Tool reference][tool-reference] for a complete list of all support
|
|
|
172
236
|
|
|
173
237
|
Find the complete list of server parameters (e.g., `--headless`, `--isolated`, `--slim`) and how to configure WebSocket connections in the [Configuration Guide][configuration-guide].
|
|
174
238
|
|
|
239
|
+
For basic browser tasks only, the `--slim` flag exposes a reduced set of tools. See the [Slim tool reference](./docs/slim-tool-reference.md).
|
|
240
|
+
|
|
175
241
|
## Advanced usage
|
|
176
242
|
|
|
177
243
|
For advanced features such as handling concurrent sessions, persistent user data directories, connecting to a running Brave instance instead of starting a new one, or debugging on Android, see our [Advanced Usage Guide][advanced-usage-guide].
|
|
@@ -187,6 +253,8 @@ If you are developing agentic tooling and want to provide an integrated browser
|
|
|
187
253
|
For a reference implementation, see the [Gemini CLI browser agent documentation](https://geminicli.com/docs/core/subagents/#browser-agent).
|
|
188
254
|
|
|
189
255
|
[advanced-usage-guide]: ./docs/advanced-usage.md
|
|
256
|
+
[cli]: ./docs/cli.md
|
|
257
|
+
[skills]: #skills
|
|
190
258
|
[client-configurations-guide]: ./docs/client-configurations.md
|
|
191
259
|
[configuration-guide]: ./docs/configuration.md
|
|
192
260
|
[tool-reference]: ./docs/tool-reference.md
|
|
@@ -60,20 +60,28 @@ import { execSync } from 'node:child_process';
|
|
|
60
60
|
import fs from 'node:fs';
|
|
61
61
|
import os from 'node:os';
|
|
62
62
|
import path from 'node:path';
|
|
63
|
-
import { Mutex, puppeteer } from './third_party/index.js';
|
|
64
|
-
import { logger, puppeteerLogger } from './utils/logger.js';
|
|
63
|
+
import { Mutex, puppeteer, } from './third_party/index.js';
|
|
64
|
+
import { logger, puppeteerLogger, redactConnectOptionsForLogging, } from './utils/logger.js';
|
|
65
65
|
import { isAllowedUrl } from './utils/url.js';
|
|
66
|
+
// Node runs a timer whose delay exceeds 2^31-1 ms after 1 ms instead, which
|
|
67
|
+
// would make every CDP call and tool call time out at once.
|
|
68
|
+
const MAXIMUM_TIMER_DELAY_MILLISECONDS = 2_147_483_647;
|
|
69
|
+
export function parseProtocolTimeoutMilliseconds(rawValue) {
|
|
70
|
+
const timeoutMilliseconds = Number(rawValue ?? '600000');
|
|
71
|
+
if (!Number.isSafeInteger(timeoutMilliseconds) ||
|
|
72
|
+
timeoutMilliseconds <= 0 ||
|
|
73
|
+
timeoutMilliseconds > MAXIMUM_TIMER_DELAY_MILLISECONDS) {
|
|
74
|
+
throw new Error(`BRAVE_DEVTOOLS_PROTOCOL_TIMEOUT_MS must be a positive integer no greater than ${MAXIMUM_TIMER_DELAY_MILLISECONDS}.`);
|
|
75
|
+
}
|
|
76
|
+
return timeoutMilliseconds;
|
|
77
|
+
}
|
|
66
78
|
// Heavy pages (e.g. Studio module dev bundles >100MB) cannot ack
|
|
67
79
|
// `Network.enable` and other auto-attached domain calls within
|
|
68
80
|
// puppeteer's default 180s. Once that fires, the CDP connection is
|
|
69
81
|
// marked dead and every subsequent call throws — only daemon restart
|
|
70
82
|
// recovers. Bumping the ceiling to 10min covers realistic loads;
|
|
71
83
|
// override via env for power users.
|
|
72
|
-
export const PROTOCOL_TIMEOUT_MILLISECONDS =
|
|
73
|
-
if (!Number.isSafeInteger(PROTOCOL_TIMEOUT_MILLISECONDS) ||
|
|
74
|
-
PROTOCOL_TIMEOUT_MILLISECONDS <= 0) {
|
|
75
|
-
throw new Error('BRAVE_DEVTOOLS_PROTOCOL_TIMEOUT_MS must be a positive integer.');
|
|
76
|
-
}
|
|
84
|
+
export const PROTOCOL_TIMEOUT_MILLISECONDS = parseProtocolTimeoutMilliseconds(process.env.BRAVE_DEVTOOLS_PROTOCOL_TIMEOUT_MS);
|
|
77
85
|
export function resolveBraveExecutablePath(channel = 'release') {
|
|
78
86
|
const environmentPath = process.env['BRAVE_PATH'];
|
|
79
87
|
if (environmentPath) {
|
|
@@ -169,11 +177,11 @@ export class BrowserManager {
|
|
|
169
177
|
#initPromise;
|
|
170
178
|
#mutex = new Mutex();
|
|
171
179
|
#closingCount = 0;
|
|
172
|
-
#
|
|
180
|
+
#config;
|
|
173
181
|
#options;
|
|
174
182
|
#browserAttempt = new BrowserAttempt();
|
|
175
|
-
constructor(
|
|
176
|
-
this.#
|
|
183
|
+
constructor(config, options = {}) {
|
|
184
|
+
this.#config = config;
|
|
177
185
|
this.#options = options;
|
|
178
186
|
}
|
|
179
187
|
static makeTargetFilter(enableExtensions = false) {
|
|
@@ -255,7 +263,7 @@ export class BrowserManager {
|
|
|
255
263
|
// a call silently adopt the freshly-rotated token as its own baseline
|
|
256
264
|
// once the lock frees up, defeating the abandonment check entirely.
|
|
257
265
|
const attempt = this.#browserAttempt;
|
|
258
|
-
const
|
|
266
|
+
const guard = __addDisposableResource(env_1, await this.#mutex.acquire(), false);
|
|
259
267
|
if (this.#closingCount > 0) {
|
|
260
268
|
throw new Error('Browser was closed while initializing.');
|
|
261
269
|
}
|
|
@@ -300,38 +308,40 @@ export class BrowserManager {
|
|
|
300
308
|
* McpContext initialization built on it — if ensureBrowser() already
|
|
301
309
|
* resolved and it's that later step hanging (e.g. a dead CDP transport),
|
|
302
310
|
* the token rotation alone does nothing, since #browser is already
|
|
303
|
-
* cached. Safe to forget unconditionally here:
|
|
304
|
-
*
|
|
311
|
+
* cached. Safe to forget unconditionally here: a tool call that already
|
|
312
|
+
* received a Browser keeps using it, so forgetting the cached value only
|
|
313
|
+
* makes the next ensureBrowser() reconnect.
|
|
305
314
|
*/
|
|
306
315
|
abandonPendingAttempt() {
|
|
307
316
|
this.#browserAttempt = new BrowserAttempt();
|
|
308
317
|
this.#initPromise = undefined;
|
|
309
|
-
|
|
310
|
-
this.forget(this.#browser);
|
|
311
|
-
}
|
|
318
|
+
this.#forgetCurrentBrowser();
|
|
312
319
|
}
|
|
313
320
|
async #initBrowser() {
|
|
314
|
-
if (this.#
|
|
315
|
-
this.#
|
|
316
|
-
this.#
|
|
321
|
+
if (this.#config.resolved.browserUrl ||
|
|
322
|
+
this.#config.resolved.wsEndpoint ||
|
|
323
|
+
this.#config.resolved.autoConnect) {
|
|
317
324
|
return await this.#connect();
|
|
318
325
|
}
|
|
319
326
|
return await this.#launch();
|
|
320
327
|
}
|
|
321
328
|
async #launch() {
|
|
322
|
-
const { channel, headless, isolated = false, categoryExtensions: enableExtensions, viaCli, viewport, acceptInsecureCerts, experimentalDevtools: devtools = false, blockedUrlPattern: blocklist, allowedUrlPattern: allowlist, ignoreDefaultBraveArg, proxyServer, } = this.#
|
|
329
|
+
const { channel, headless, isolated = false, categoryExtensions: enableExtensions, viaCli, viewport, acceptInsecureCerts, experimentalDevtools: devtools = false, blockedUrlPattern: blocklist, allowedUrlPattern: allowlist, ignoreDefaultBraveArg, proxyServer, } = this.#config.resolved;
|
|
323
330
|
const profileDirName = channel && channel !== 'release'
|
|
324
331
|
? `brave-profile-${channel}`
|
|
325
332
|
: 'brave-profile';
|
|
326
|
-
const executablePath = this.#
|
|
327
|
-
|
|
333
|
+
const executablePath = this.#config.resolved.executablePath ??
|
|
334
|
+
resolveBraveExecutablePath(channel);
|
|
335
|
+
let userDataDir = this.#config.resolved.userDataDir;
|
|
328
336
|
if (!isolated && !userDataDir) {
|
|
329
337
|
userDataDir = path.join(os.homedir(), '.cache', viaCli ? 'brave-devtools-mcp-cli' : 'brave-devtools-mcp', profileDirName);
|
|
330
338
|
await fs.promises.mkdir(userDataDir, {
|
|
331
339
|
recursive: true,
|
|
332
340
|
});
|
|
333
341
|
}
|
|
334
|
-
const args = [
|
|
342
|
+
const args = [
|
|
343
|
+
...(this.#config.resolved.braveArg ?? []),
|
|
344
|
+
];
|
|
335
345
|
if (proxyServer) {
|
|
336
346
|
args.push(`--proxy-server=${proxyServer}`);
|
|
337
347
|
}
|
|
@@ -364,6 +374,12 @@ export class BrowserManager {
|
|
|
364
374
|
blocklist,
|
|
365
375
|
allowlist,
|
|
366
376
|
logger: puppeteerLogger,
|
|
377
|
+
// The entry points own signal handling and call close(), which
|
|
378
|
+
// closes the browser gracefully. Puppeteer's handlers would SIGKILL
|
|
379
|
+
// Chrome synchronously on the same signal and win the race.
|
|
380
|
+
handleSIGINT: false,
|
|
381
|
+
handleSIGTERM: false,
|
|
382
|
+
handleSIGHUP: false,
|
|
367
383
|
});
|
|
368
384
|
if (this.#options.logFile) {
|
|
369
385
|
// FIXME: we are probably subscribing too late to catch startup logs. We
|
|
@@ -405,24 +421,31 @@ export class BrowserManager {
|
|
|
405
421
|
}
|
|
406
422
|
}
|
|
407
423
|
async #connect() {
|
|
408
|
-
const { browserUrl: browserURL, wsEndpoint, wsHeaders, autoConnect: isAutoConnect, userDataDir, blockedUrlPattern: blocklist, allowedUrlPattern: allowlist, } = this.#
|
|
424
|
+
const { browserUrl: browserURL, wsEndpoint, wsHeaders, autoConnect: isAutoConnect, userDataDir, blockedUrlPattern: blocklist, allowedUrlPattern: allowlist, categoryExtensions: enableExtensions, } = this.#config.resolved;
|
|
409
425
|
// Important: only pass channel, if autoConnect is true.
|
|
410
|
-
const channel = isAutoConnect ? this.#
|
|
426
|
+
const channel = isAutoConnect ? this.#config.resolved.channel : undefined;
|
|
411
427
|
const connectOptions = {
|
|
412
|
-
targetFilter: _a.makeTargetFilter(),
|
|
428
|
+
targetFilter: _a.makeTargetFilter(enableExtensions),
|
|
413
429
|
defaultViewport: null,
|
|
414
430
|
handleDevToolsAsPage: true,
|
|
415
431
|
protocolTimeout: PROTOCOL_TIMEOUT_MILLISECONDS,
|
|
416
432
|
blocklist,
|
|
417
433
|
allowlist,
|
|
418
434
|
logger: puppeteerLogger,
|
|
435
|
+
// Detects a transport that died without a close frame (e.g. an adb
|
|
436
|
+
// port-forward torn down mid-call). Puppeteer pings the socket
|
|
437
|
+
// periodically and terminates it on a missed pong, which rejects the
|
|
438
|
+
// in-flight CDP calls and fires `disconnected` so #evictIfCurrent()
|
|
439
|
+
// drops the stale handle. Without this, such a call would hang until
|
|
440
|
+
// protocolTimeout inside the context queue.
|
|
441
|
+
wsOptions: {
|
|
442
|
+
keepAlive: true,
|
|
443
|
+
headers: wsHeaders,
|
|
444
|
+
},
|
|
419
445
|
};
|
|
420
446
|
let autoConnect = false;
|
|
421
447
|
if (wsEndpoint) {
|
|
422
448
|
connectOptions.browserWSEndpoint = wsEndpoint;
|
|
423
|
-
if (wsHeaders) {
|
|
424
|
-
connectOptions.headers = wsHeaders;
|
|
425
|
-
}
|
|
426
449
|
}
|
|
427
450
|
else if (browserURL) {
|
|
428
451
|
connectOptions.browserURL = browserURL;
|
|
@@ -462,7 +485,7 @@ export class BrowserManager {
|
|
|
462
485
|
else {
|
|
463
486
|
throw new Error('Either browserURL, wsEndpoint, channel or userDataDir must be provided');
|
|
464
487
|
}
|
|
465
|
-
logger?.('Connecting Puppeteer to ', JSON.stringify(connectOptions));
|
|
488
|
+
logger?.('Connecting Puppeteer to ', JSON.stringify(redactConnectOptionsForLogging(connectOptions)));
|
|
466
489
|
try {
|
|
467
490
|
const connected = await puppeteer.connect(connectOptions);
|
|
468
491
|
logger?.('Connected Puppeteer');
|
|
@@ -478,32 +501,31 @@ export class BrowserManager {
|
|
|
478
501
|
}
|
|
479
502
|
}
|
|
480
503
|
/**
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
* closed cleanly).
|
|
504
|
+
* Drops the cached browser handle so the next ensureBrowser() call
|
|
505
|
+
* establishes a fresh connection. Only called from abandonPendingAttempt()
|
|
506
|
+
* when a getContext()-level timeout fires after ensureBrowser() already
|
|
507
|
+
* resolved, which means the handle looks connected but its CDP
|
|
508
|
+
* initialization is hanging.
|
|
487
509
|
*
|
|
488
|
-
* Also
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
* callbacks — instead of leaving a hung call to leak forever.
|
|
510
|
+
* Also tears the browser down in the background: closes it if it was
|
|
511
|
+
* launched (so the Brave subprocess doesn't leak), or disconnects if it
|
|
512
|
+
* was only connected to (so the transport and its listeners don't leak).
|
|
513
|
+
* Puppeteer's connection disposal rejects any in-flight CDP call, so a hung
|
|
514
|
+
* call settles instead of leaking forever.
|
|
494
515
|
*/
|
|
495
|
-
|
|
496
|
-
|
|
516
|
+
#forgetCurrentBrowser() {
|
|
517
|
+
const browser = this.#browser;
|
|
518
|
+
if (!browser) {
|
|
497
519
|
return;
|
|
498
520
|
}
|
|
499
521
|
const mode = this.#browserMode;
|
|
500
522
|
this.#browser = undefined;
|
|
501
523
|
this.#browserMode = undefined;
|
|
502
524
|
if (mode === 'launched') {
|
|
503
|
-
void this.#closeLaunchedBrowser(
|
|
525
|
+
void this.#closeLaunchedBrowser(browser);
|
|
504
526
|
}
|
|
505
527
|
else {
|
|
506
|
-
void
|
|
528
|
+
void browser.disconnect().catch(err => {
|
|
507
529
|
logger?.('Failed to disconnect forgotten browser', err);
|
|
508
530
|
});
|
|
509
531
|
}
|
|
@@ -514,9 +536,8 @@ export class BrowserManager {
|
|
|
514
536
|
* call reconnects proactively rather than only discovering the staleness
|
|
515
537
|
* lazily on the next call's `!this.#browser?.connected` check. No teardown
|
|
516
538
|
* here — the browser is already disconnected/closed, that's why this
|
|
517
|
-
* fired
|
|
518
|
-
*
|
|
519
|
-
* a safe no-op.
|
|
539
|
+
* fired. The identity guard makes a `disconnected` that fires late on an
|
|
540
|
+
* already-superseded browser a safe no-op.
|
|
520
541
|
*/
|
|
521
542
|
#evictIfCurrent(candidate) {
|
|
522
543
|
if (this.#browser !== candidate) {
|
|
@@ -571,7 +592,7 @@ export class BrowserManager {
|
|
|
571
592
|
try {
|
|
572
593
|
this.#initPromise = undefined;
|
|
573
594
|
this.#closingCount++;
|
|
574
|
-
const
|
|
595
|
+
const guard = __addDisposableResource(env_2, await this.#mutex.acquire(), false);
|
|
575
596
|
try {
|
|
576
597
|
await this.#closeBrowser();
|
|
577
598
|
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { Script } from 'node:vm';
|
|
7
|
+
import { waitForSourceMaps, } from './devtools/DevtoolsUtils.js';
|
|
8
|
+
import { DevTools } from './third_party/index.js';
|
|
9
|
+
/**
|
|
10
|
+
* Thrown by {@link LogpointManager.setLogpoints} when some locations could
|
|
11
|
+
* not be bound. The other logpoints were applied; `logpoints` holds the
|
|
12
|
+
* resulting state.
|
|
13
|
+
*/
|
|
14
|
+
export class LogpointError extends Error {
|
|
15
|
+
logpoints;
|
|
16
|
+
constructor(message, logpoints) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'LogpointError';
|
|
19
|
+
this.logpoints = logpoints;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export const LOGPOINT_FORMAT = '<url>:<lineNumber>[:<columnNumber>] <expression>';
|
|
23
|
+
/**
|
|
24
|
+
* Parses a logpoint given as `<url>:<lineNumber>[:<columnNumber>] <expression>`.
|
|
25
|
+
* The url may itself contain colons (for example a port), so the location is
|
|
26
|
+
* the shortest prefix that ends in one or two numbers followed by whitespace.
|
|
27
|
+
*/
|
|
28
|
+
export function parseLogpoint(text) {
|
|
29
|
+
const match = /^(\S+?):(\d+)(?::(\d+))?\s+(\S.*)$/s.exec(text.trim());
|
|
30
|
+
if (!match) {
|
|
31
|
+
throw new Error(`Invalid logpoint "${text}". Expected the format ${LOGPOINT_FORMAT}, for example "src/app.ts:42 'total', cart.total".`);
|
|
32
|
+
}
|
|
33
|
+
const [, url, lineNumber, columnNumber, expression] = match;
|
|
34
|
+
const logpoint = {
|
|
35
|
+
url,
|
|
36
|
+
lineNumber: Number(lineNumber),
|
|
37
|
+
expression,
|
|
38
|
+
};
|
|
39
|
+
if (columnNumber !== undefined) {
|
|
40
|
+
logpoint.columnNumber = Number(columnNumber);
|
|
41
|
+
}
|
|
42
|
+
if (logpoint.lineNumber < 1 || logpoint.columnNumber === 0) {
|
|
43
|
+
throw new Error(`Invalid logpoint "${text}": line and column numbers are 1-based.`);
|
|
44
|
+
}
|
|
45
|
+
return logpoint;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Checks that the expression parses as `console.log()` arguments. V8 is used
|
|
49
|
+
* for parsing both here and in the browser, so an expression that is rejected
|
|
50
|
+
* here would also be rejected as a breakpoint condition. The script is only
|
|
51
|
+
* compiled, never run.
|
|
52
|
+
*/
|
|
53
|
+
export function validateExpression(expression) {
|
|
54
|
+
try {
|
|
55
|
+
new Script(`console.log(${expression})`);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
throw new Error(`The logpoint expression \`${expression}\` does not compile: ${error instanceof Error ? error.message : String(error)}. Provide the expression like arguments to console.log(...).`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function toUserCondition(expression) {
|
|
62
|
+
return expression;
|
|
63
|
+
}
|
|
64
|
+
function isFromSourceMap(uiSourceCode) {
|
|
65
|
+
return uiSourceCode.contentType().isFromSourceMap();
|
|
66
|
+
}
|
|
67
|
+
function locationKey(url, lineNumber, columnNumber) {
|
|
68
|
+
return `${url}:${lineNumber}${columnNumber === undefined ? '' : `:${columnNumber}`}`;
|
|
69
|
+
}
|
|
70
|
+
function baseName(url) {
|
|
71
|
+
return url.slice(url.lastIndexOf('/') + 1).toLowerCase();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* How long to wait for the source maps of the page to load before an original
|
|
75
|
+
* source URL is reported as unknown. Loads still pending then continue in the
|
|
76
|
+
* background, so a retry finds the source.
|
|
77
|
+
*/
|
|
78
|
+
const SOURCE_MAP_LOAD_TIMEOUT_MS = 5_000;
|
|
79
|
+
/**
|
|
80
|
+
* Manages logpoints for a single page on top of the `BreakpointManager` of
|
|
81
|
+
* the page's DevTools universe.
|
|
82
|
+
*
|
|
83
|
+
* A logpoint is a DevTools breakpoint with `isLogpoint` set: its condition
|
|
84
|
+
* logs the expression and never pauses. The breakpoint manager resolves the
|
|
85
|
+
* location through source maps, substitutes minified variable names in the
|
|
86
|
+
* expression and keeps the breakpoint bound across reloads and rebuilt
|
|
87
|
+
* bundles. This class only maps the tool API (URLs and 1-based positions)
|
|
88
|
+
* onto workspace source codes.
|
|
89
|
+
*/
|
|
90
|
+
export class LogpointManager {
|
|
91
|
+
#universe;
|
|
92
|
+
#target;
|
|
93
|
+
/**
|
|
94
|
+
* The breakpoints that are logpoints of this page. The breakpoint manager
|
|
95
|
+
* keeps one `Breakpoint` per normalized location, so two requested
|
|
96
|
+
* locations can share one entry.
|
|
97
|
+
*/
|
|
98
|
+
#records = new Set();
|
|
99
|
+
constructor(devTools) {
|
|
100
|
+
this.#universe = devTools.universe;
|
|
101
|
+
this.#target = devTools.target;
|
|
102
|
+
}
|
|
103
|
+
get #debuggerModel() {
|
|
104
|
+
const model = this.#target.model(DevTools.DebuggerModel);
|
|
105
|
+
if (!model) {
|
|
106
|
+
throw new Error('The DevTools debugger model is not available.');
|
|
107
|
+
}
|
|
108
|
+
return model;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Makes the given logpoints the complete set of logpoints on the page:
|
|
112
|
+
* logpoints not in the list are removed, new ones are added and the
|
|
113
|
+
* expression of an existing logpoint at the same location is replaced.
|
|
114
|
+
* Expressions and locations are validated before anything is changed.
|
|
115
|
+
* Throws a {@link LogpointError} when a location has no executable code;
|
|
116
|
+
* the other logpoints are applied regardless.
|
|
117
|
+
*/
|
|
118
|
+
async setLogpoints(desired) {
|
|
119
|
+
for (const options of desired) {
|
|
120
|
+
validateExpression(options.expression);
|
|
121
|
+
}
|
|
122
|
+
const resolved = new Map();
|
|
123
|
+
for (const options of desired) {
|
|
124
|
+
const uiSourceCode = await this.#findUISourceCode(options.url);
|
|
125
|
+
const key = locationKey(uiSourceCode.url(), options.lineNumber, options.columnNumber);
|
|
126
|
+
// The last entry for a location wins, like setting it twice would.
|
|
127
|
+
resolved.set(key, { options, uiSourceCode, key });
|
|
128
|
+
}
|
|
129
|
+
const kept = new Set();
|
|
130
|
+
const unbound = [];
|
|
131
|
+
for (const { options, uiSourceCode, key } of resolved.values()) {
|
|
132
|
+
const breakpoint = await this.#universe.breakpointManager.setBreakpoint(uiSourceCode,
|
|
133
|
+
// The tool API is 1-based like the DevTools UI; the model is 0-based.
|
|
134
|
+
options.lineNumber - 1, options.columnNumber === undefined
|
|
135
|
+
? undefined
|
|
136
|
+
: options.columnNumber - 1, toUserCondition(options.expression),
|
|
137
|
+
/* enabled */ true,
|
|
138
|
+
/* isLogpoint */ true, "USER_ACTION" /* DevTools.BreakpointManager.BreakpointOrigin.USER_ACTION */);
|
|
139
|
+
if (!breakpoint) {
|
|
140
|
+
unbound.push(key);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// Wait for the breakpoint to be set in the backend so that the result
|
|
144
|
+
// reports whether the location resolved.
|
|
145
|
+
await breakpoint.updateBreakpoint();
|
|
146
|
+
if (!this.#records.has(breakpoint) && !breakpoint.bound()) {
|
|
147
|
+
await breakpoint.remove(/* keepInStorage */ false);
|
|
148
|
+
unbound.push(key);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
this.#records.add(breakpoint);
|
|
152
|
+
kept.add(breakpoint);
|
|
153
|
+
}
|
|
154
|
+
for (const breakpoint of [...this.#records]) {
|
|
155
|
+
if (!kept.has(breakpoint)) {
|
|
156
|
+
this.#records.delete(breakpoint);
|
|
157
|
+
await breakpoint.remove(/* keepInStorage */ false);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
this.#setLazySourceMapLoading(this.#records.size === 0);
|
|
161
|
+
const logpoints = this.#currentLogpoints();
|
|
162
|
+
if (unbound.length) {
|
|
163
|
+
throw new LogpointError(`No executable code at ${unbound.join(', ')} (for example a comment, type annotation or empty line, or a line not mapped to the generated script). Pick lines containing executable code. The other logpoints were applied.`, logpoints);
|
|
164
|
+
}
|
|
165
|
+
return logpoints;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Releases the records. The breakpoints are not removed from the backend:
|
|
169
|
+
* the universe is being disposed and its session detaches, which removes
|
|
170
|
+
* them along with the page.
|
|
171
|
+
*/
|
|
172
|
+
dispose() {
|
|
173
|
+
this.#records.clear();
|
|
174
|
+
}
|
|
175
|
+
#currentLogpoints() {
|
|
176
|
+
return [...this.#records].map(breakpoint => this.#snapshot(breakpoint));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Reads the current state of a breakpoint, so that the result reflects
|
|
180
|
+
* the normalized location and the re-binding done by the breakpoint
|
|
181
|
+
* manager after reloads. The breakpoint manager may have moved a
|
|
182
|
+
* generated location to its original source, so whether the logpoint is
|
|
183
|
+
* source-mapped follows from the breakpoint, not from the request.
|
|
184
|
+
*/
|
|
185
|
+
#snapshot(breakpoint) {
|
|
186
|
+
const sourceMapped = [...breakpoint.getUiSourceCodes()].some(isFromSourceMap);
|
|
187
|
+
const modelBreakpoint = breakpoint.modelBreakpoint(this.#debuggerModel);
|
|
188
|
+
const generatedLocations = [];
|
|
189
|
+
if (breakpoint.bound()) {
|
|
190
|
+
for (const position of modelBreakpoint?.currentState ?? []) {
|
|
191
|
+
generatedLocations.push({
|
|
192
|
+
url: position.url,
|
|
193
|
+
lineNumber: position.lineNumber + 1,
|
|
194
|
+
columnNumber: (position.columnNumber ?? 0) + 1,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const columnNumber = breakpoint.columnNumber();
|
|
199
|
+
return {
|
|
200
|
+
url: breakpoint.url(),
|
|
201
|
+
lineNumber: breakpoint.lineNumber() + 1,
|
|
202
|
+
columnNumber: columnNumber === undefined ? undefined : columnNumber + 1,
|
|
203
|
+
expression: breakpoint.condition(),
|
|
204
|
+
sourceMapped,
|
|
205
|
+
generatedLocations,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Source maps load lazily by default, so after a reload the original
|
|
210
|
+
* sources of a rebuilt bundle would not re-appear in the workspace and a
|
|
211
|
+
* logpoint on an original source file could not be re-bound to the new
|
|
212
|
+
* generated location. While logpoints exist, source maps are loaded
|
|
213
|
+
* eagerly instead, like DevTools does with the Sources panel open.
|
|
214
|
+
*/
|
|
215
|
+
#setLazySourceMapLoading(lazy) {
|
|
216
|
+
this.#universe.settings
|
|
217
|
+
.resolve(DevTools.SourceMapManager.lazyLoadingSettingDescriptor)
|
|
218
|
+
.set(lazy);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Finds the workspace source code for a script or original source URL,
|
|
222
|
+
* loading source maps if needed. Throws a descriptive error, including
|
|
223
|
+
* sources with the same file name, when nothing matches.
|
|
224
|
+
*/
|
|
225
|
+
async #findUISourceCode(url) {
|
|
226
|
+
// Source maps are loaded lazily in the DevTools universe and original
|
|
227
|
+
// sources only appear in the workspace once their source map has loaded.
|
|
228
|
+
let match = this.#matchUISourceCode(url);
|
|
229
|
+
let timedOut = false;
|
|
230
|
+
if (match && !isFromSourceMap(match)) {
|
|
231
|
+
// The script may be a transpiled file served at the URL of its original
|
|
232
|
+
// source (see #matchUISourceCode); its source map decides.
|
|
233
|
+
const matchedUrl = match.url();
|
|
234
|
+
const scripts = this.#debuggerModel
|
|
235
|
+
.scripts()
|
|
236
|
+
.filter(script => script.sourceURL === matchedUrl);
|
|
237
|
+
if (scripts.some(script => script.sourceMapURL)) {
|
|
238
|
+
const signal = AbortSignal.timeout(SOURCE_MAP_LOAD_TIMEOUT_MS);
|
|
239
|
+
await waitForSourceMaps(this.#debuggerModel, scripts, signal);
|
|
240
|
+
match = this.#matchUISourceCode(url);
|
|
241
|
+
if (signal.aborted && (!match || !isFromSourceMap(match))) {
|
|
242
|
+
// Setting the logpoint on the served script would use its line
|
|
243
|
+
// numbers instead of those of the original source.
|
|
244
|
+
throw new Error(`The source map of ${matchedUrl} was still loading after ${SOURCE_MAP_LOAD_TIMEOUT_MS / 1000} seconds; retry.`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (!match) {
|
|
249
|
+
const signal = AbortSignal.timeout(SOURCE_MAP_LOAD_TIMEOUT_MS);
|
|
250
|
+
await waitForSourceMaps(this.#debuggerModel, this.#debuggerModel.scripts(), signal);
|
|
251
|
+
timedOut = signal.aborted;
|
|
252
|
+
match = this.#matchUISourceCode(url);
|
|
253
|
+
}
|
|
254
|
+
if (match) {
|
|
255
|
+
return match;
|
|
256
|
+
}
|
|
257
|
+
const name = baseName(url);
|
|
258
|
+
const similar = [...this.#pageSourceCodes()]
|
|
259
|
+
.map(uiSourceCode => uiSourceCode.url())
|
|
260
|
+
.filter(candidate => baseName(candidate) === name)
|
|
261
|
+
.slice(0, 5);
|
|
262
|
+
throw new Error(`No parsed script or original source file matches "${url}". The url must match the URL of a script as served to the browser or an original source file from a source map, exactly or as a path suffix.${timedOut ? ` Some source maps were still loading after ${SOURCE_MAP_LOAD_TIMEOUT_MS / 1000} seconds; retry if the file comes from a source map.` : ''}${similar.length ? ` Sources with the same file name: ${similar.join(', ')}.` : ''}`);
|
|
263
|
+
}
|
|
264
|
+
/** The scripts and source-map sources of the page in the workspace. */
|
|
265
|
+
*#pageSourceCodes() {
|
|
266
|
+
for (const uiSourceCode of this.#universe.workspace.uiSourceCodes()) {
|
|
267
|
+
const projectType = uiSourceCode.project().type();
|
|
268
|
+
// Content scripts of extensions are left out: they are not part of the
|
|
269
|
+
// page under development and would make path-suffix matches ambiguous.
|
|
270
|
+
if (projectType === DevTools.Workspace.Workspace.projectTypes.Network) {
|
|
271
|
+
yield uiSourceCode;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Finds the workspace source code for a script or original source URL.
|
|
277
|
+
* An exact URL match wins; otherwise the URL is matched as a path suffix so
|
|
278
|
+
* that users can pass "src/app.ts" without knowing the bundler's URL
|
|
279
|
+
* scheme. An ambiguous suffix match is reported as an error.
|
|
280
|
+
*/
|
|
281
|
+
#matchUISourceCode(url) {
|
|
282
|
+
const candidates = new Map();
|
|
283
|
+
for (const uiSourceCode of this.#pageSourceCodes()) {
|
|
284
|
+
const candidateUrl = uiSourceCode.url();
|
|
285
|
+
if (candidateUrl !== url && !candidateUrl.endsWith('/' + url)) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
// Dev servers like Vite serve a transpiled file at the URL of its
|
|
289
|
+
// original source, with a source map pointing back at that same URL.
|
|
290
|
+
// The original source then has the line numbers of the file on disk,
|
|
291
|
+
// so it wins over the script served at the same URL.
|
|
292
|
+
const existing = candidates.get(candidateUrl);
|
|
293
|
+
if (!existing || isFromSourceMap(uiSourceCode)) {
|
|
294
|
+
candidates.set(candidateUrl, uiSourceCode);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const exact = candidates.get(url);
|
|
298
|
+
if (exact) {
|
|
299
|
+
return exact;
|
|
300
|
+
}
|
|
301
|
+
if (candidates.size > 1) {
|
|
302
|
+
throw new Error(`"${url}" matches multiple sources: ${[...candidates.keys()].join(', ')}. Specify a longer path.`);
|
|
303
|
+
}
|
|
304
|
+
return candidates.values().next().value ?? null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
//# sourceMappingURL=LogpointManager.js.map
|