brave-mcp 0.1.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/LICENSE +202 -0
- package/README.md +802 -0
- package/build/src/DevToolsConnectionAdapter.js +69 -0
- package/build/src/DevtoolsUtils.js +294 -0
- package/build/src/McpContext.js +676 -0
- package/build/src/McpPage.js +108 -0
- package/build/src/McpResponse.js +718 -0
- package/build/src/Mutex.js +37 -0
- package/build/src/PageCollector.js +310 -0
- package/build/src/SlimMcpResponse.js +18 -0
- package/build/src/WaitForHelper.js +153 -0
- package/build/src/bin/brave-devtools-cli-options.js +651 -0
- package/build/src/bin/brave-devtools-mcp-cli-options.js +312 -0
- package/build/src/bin/brave-devtools-mcp-main.js +35 -0
- package/build/src/bin/brave-devtools-mcp.js +22 -0
- package/build/src/bin/brave-devtools.js +190 -0
- package/build/src/bin/check-latest-version.js +25 -0
- package/build/src/bin/cliDefinitions.js +615 -0
- package/build/src/browser.js +295 -0
- package/build/src/daemon/client.js +152 -0
- package/build/src/daemon/daemon.js +202 -0
- package/build/src/daemon/types.js +6 -0
- package/build/src/daemon/utils.js +109 -0
- package/build/src/formatters/ConsoleFormatter.js +241 -0
- package/build/src/formatters/IssueFormatter.js +192 -0
- package/build/src/formatters/NetworkFormatter.js +218 -0
- package/build/src/formatters/SnapshotFormatter.js +134 -0
- package/build/src/index.js +216 -0
- package/build/src/issue-descriptions.js +39 -0
- package/build/src/logger.js +36 -0
- package/build/src/polyfill.js +7 -0
- package/build/src/telemetry/ClearcutLogger.js +234 -0
- package/build/src/telemetry/WatchdogClient.js +60 -0
- package/build/src/telemetry/flagUtils.js +45 -0
- package/build/src/telemetry/metricUtils.js +14 -0
- package/build/src/telemetry/persistence.js +53 -0
- package/build/src/telemetry/toolMetricsUtils.js +88 -0
- package/build/src/telemetry/types.js +38 -0
- package/build/src/telemetry/watchdog/ClearcutSender.js +203 -0
- package/build/src/telemetry/watchdog/main.js +127 -0
- package/build/src/third_party/devtools-formatter-worker.js +7 -0
- package/build/src/third_party/index.js +28 -0
- package/build/src/third_party/issue-descriptions/CoepCoopSandboxedIframeCannotNavigateToCoopPage.md +4 -0
- package/build/src/third_party/issue-descriptions/CoepCorpNotSameOrigin.md +8 -0
- package/build/src/third_party/issue-descriptions/CoepCorpNotSameOriginAfterDefaultedToSameOriginByCoep.md +18 -0
- package/build/src/third_party/issue-descriptions/CoepCorpNotSameSite.md +7 -0
- package/build/src/third_party/issue-descriptions/CoepFrameResourceNeedsCoepHeader.md +10 -0
- package/build/src/third_party/issue-descriptions/CompatibilityModeQuirks.md +5 -0
- package/build/src/third_party/issue-descriptions/CookieAttributeValueExceedsMaxSize.md +5 -0
- package/build/src/third_party/issue-descriptions/LowTextContrast.md +5 -0
- package/build/src/third_party/issue-descriptions/SameSiteExcludeContextDowngradeRead.md +8 -0
- package/build/src/third_party/issue-descriptions/SameSiteExcludeContextDowngradeSet.md +8 -0
- package/build/src/third_party/issue-descriptions/SameSiteExcludeNavigationContextDowngrade.md +8 -0
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorRead.md +8 -0
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorSet.md +8 -0
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnRead.md +8 -0
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnSet.md +8 -0
- package/build/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeRead.md +9 -0
- package/build/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeSet.md +9 -0
- package/build/src/third_party/issue-descriptions/SameSiteWarnCrossDowngradeRead.md +8 -0
- package/build/src/third_party/issue-descriptions/SameSiteWarnCrossDowngradeSet.md +8 -0
- package/build/src/third_party/issue-descriptions/SameSiteWarnStrictLaxDowngradeStrict.md +8 -0
- package/build/src/third_party/issue-descriptions/arInsecureContext.md +7 -0
- package/build/src/third_party/issue-descriptions/arInvalidInfoHeader.md +5 -0
- package/build/src/third_party/issue-descriptions/arInvalidRegisterOsSourceHeader.md +5 -0
- package/build/src/third_party/issue-descriptions/arInvalidRegisterOsTriggerHeader.md +5 -0
- package/build/src/third_party/issue-descriptions/arInvalidRegisterSourceHeader.md +5 -0
- package/build/src/third_party/issue-descriptions/arInvalidRegisterTriggerHeader.md +5 -0
- package/build/src/third_party/issue-descriptions/arNavigationRegistrationUniqueScopeAlreadySet.md +5 -0
- package/build/src/third_party/issue-descriptions/arNavigationRegistrationWithoutTransientUserActivation.md +6 -0
- package/build/src/third_party/issue-descriptions/arNoRegisterOsSourceHeader.md +5 -0
- package/build/src/third_party/issue-descriptions/arNoRegisterOsTriggerHeader.md +5 -0
- package/build/src/third_party/issue-descriptions/arNoRegisterSourceHeader.md +5 -0
- package/build/src/third_party/issue-descriptions/arNoRegisterTriggerHeader.md +5 -0
- package/build/src/third_party/issue-descriptions/arNoWebOrOsSupport.md +4 -0
- package/build/src/third_party/issue-descriptions/arOsSourceIgnored.md +18 -0
- package/build/src/third_party/issue-descriptions/arOsTriggerIgnored.md +19 -0
- package/build/src/third_party/issue-descriptions/arPermissionPolicyDisabled.md +8 -0
- package/build/src/third_party/issue-descriptions/arSourceAndTriggerHeaders.md +9 -0
- package/build/src/third_party/issue-descriptions/arSourceIgnored.md +13 -0
- package/build/src/third_party/issue-descriptions/arTriggerIgnored.md +12 -0
- package/build/src/third_party/issue-descriptions/arUntrustworthyReportingOrigin.md +10 -0
- package/build/src/third_party/issue-descriptions/arWebAndOsHeaders.md +11 -0
- package/build/src/third_party/issue-descriptions/bounceTrackingMitigations.md +3 -0
- package/build/src/third_party/issue-descriptions/clientHintMetaTagAllowListInvalidOrigin.md +4 -0
- package/build/src/third_party/issue-descriptions/clientHintMetaTagModifiedHTML.md +4 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowlistItemType.md +12 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidHeader.md +12 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidUrlPattern.md +8 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistItemNotInnerList.md +12 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistMoreThanOneList.md +7 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistReportingEndpointNotToken.md +10 -0
- package/build/src/third_party/issue-descriptions/cookieCrossSiteRedirectDowngrade.md +12 -0
- package/build/src/third_party/issue-descriptions/cookieExcludeBlockedWithinRelatedWebsiteSet.md +4 -0
- package/build/src/third_party/issue-descriptions/cookieExcludeDomainNonAscii.md +11 -0
- package/build/src/third_party/issue-descriptions/cookieExcludePortMismatch.md +8 -0
- package/build/src/third_party/issue-descriptions/cookieExcludeSchemeMismatch.md +7 -0
- package/build/src/third_party/issue-descriptions/cookieExcludeThirdPartyPhaseoutRead.md +6 -0
- package/build/src/third_party/issue-descriptions/cookieExcludeThirdPartyPhaseoutSet.md +6 -0
- package/build/src/third_party/issue-descriptions/cookieWarnDomainNonAscii.md +11 -0
- package/build/src/third_party/issue-descriptions/cookieWarnMetadataGrantRead.md +4 -0
- package/build/src/third_party/issue-descriptions/cookieWarnMetadataGrantSet.md +4 -0
- package/build/src/third_party/issue-descriptions/cookieWarnThirdPartyPhaseoutRead.md +6 -0
- package/build/src/third_party/issue-descriptions/cookieWarnThirdPartyPhaseoutSet.md +6 -0
- package/build/src/third_party/issue-descriptions/corsAllowCredentialsRequired.md +6 -0
- package/build/src/third_party/issue-descriptions/corsDisabledScheme.md +7 -0
- package/build/src/third_party/issue-descriptions/corsDisallowedByMode.md +7 -0
- package/build/src/third_party/issue-descriptions/corsHeaderDisallowedByPreflightResponse.md +5 -0
- package/build/src/third_party/issue-descriptions/corsInvalidHeaderValues.md +7 -0
- package/build/src/third_party/issue-descriptions/corsLocalNetworkAccessPermissionDenied.md +19 -0
- package/build/src/third_party/issue-descriptions/corsMethodDisallowedByPreflightResponse.md +5 -0
- package/build/src/third_party/issue-descriptions/corsNoCorsRedirectModeNotFollow.md +5 -0
- package/build/src/third_party/issue-descriptions/corsOriginMismatch.md +6 -0
- package/build/src/third_party/issue-descriptions/corsPreflightResponseInvalid.md +5 -0
- package/build/src/third_party/issue-descriptions/corsRedirectContainsCredentials.md +5 -0
- package/build/src/third_party/issue-descriptions/corsWildcardOriginNotAllowed.md +8 -0
- package/build/src/third_party/issue-descriptions/cspEvalViolation.md +9 -0
- package/build/src/third_party/issue-descriptions/cspInlineViolation.md +10 -0
- package/build/src/third_party/issue-descriptions/cspTrustedTypesPolicyViolation.md +5 -0
- package/build/src/third_party/issue-descriptions/cspTrustedTypesSinkViolation.md +8 -0
- package/build/src/third_party/issue-descriptions/cspURLViolation.md +10 -0
- package/build/src/third_party/issue-descriptions/deprecation.md +3 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestApprovalDeclined.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestCanceled.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestErrorFetchingSignin.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestErrorIdToken.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidRequest.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestInvalidSigninResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestTooManyRequests.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidAccountsResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidConfigOrWellKnown.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoAccountSharingPermission.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoApiPermission.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoReturningUserFromFetchedAccounts.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotIframe.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotPotentiallyTrustworthy.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSameOrigin.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSignedInWithIdp.md +1 -0
- package/build/src/third_party/issue-descriptions/fetchingPartitionedBlobURL.md +7 -0
- package/build/src/third_party/issue-descriptions/genericFormAriaLabelledByToNonExistingIdError.md +8 -0
- package/build/src/third_party/issue-descriptions/genericFormAutocompleteAttributeEmptyError.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormDuplicateIdForInputError.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormEmptyIdAndNameAttributesForInputError.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormInputAssignedAutocompleteValueToIdOrNameAttributeError.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormInputHasWrongButWellIntendedAutocompleteValueError.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormInputWithNoLabelError.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormLabelForMatchesNonExistingIdError.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormLabelForNameError.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormLabelHasNeitherForNorNestedInputError.md +5 -0
- package/build/src/third_party/issue-descriptions/genericNavigationEntryMarkedSkippable.md +7 -0
- package/build/src/third_party/issue-descriptions/genericResponseWasBlockedByORB.md +4 -0
- package/build/src/third_party/issue-descriptions/heavyAd.md +10 -0
- package/build/src/third_party/issue-descriptions/mixedContent.md +5 -0
- package/build/src/third_party/issue-descriptions/navigatingPartitionedBlobURL.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementActivationDisabled.md +7 -0
- package/build/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluder.md +9 -0
- package/build/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluderParent.md +9 -0
- package/build/src/third_party/issue-descriptions/permissionElementCspFrameAncestorsMissing.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementFencedFrameDisallowed.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementFontSizeTooLarge.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementFontSizeTooSmall.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementGeolocationDeprecated.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementInsetBoxShadowUnsupported.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementInvalidDisplayStyle.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementInvalidSizeValue.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementInvalidType.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementInvalidTypeActivation.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementLowContrast.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementNonOpaqueColor.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementPaddingBottomUnsupported.md +6 -0
- package/build/src/third_party/issue-descriptions/permissionElementPaddingRightUnsupported.md +6 -0
- package/build/src/third_party/issue-descriptions/permissionElementPermissionsPolicyBlocked.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementRegistrationFailed.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementRequestInProgress.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementSecurityChecksFailed.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementTypeNotSupported.md +5 -0
- package/build/src/third_party/issue-descriptions/permissionElementUntrustedEvent.md +7 -0
- package/build/src/third_party/issue-descriptions/placeholderDescriptionForInvisibleIssues.md +3 -0
- package/build/src/third_party/issue-descriptions/propertyRuleInvalidNameIssue.md +3 -0
- package/build/src/third_party/issue-descriptions/propertyRuleIssue.md +7 -0
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedOptGroupChild.md +7 -0
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedSelectChild.md +7 -0
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentAttributesSelectDescendant.md +3 -0
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentLegendChild.md +3 -0
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentOptionChild.md +3 -0
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityNonPhrasingContentOptionChild.md +3 -0
- package/build/src/third_party/issue-descriptions/selectivePermissionsIntervention.md +7 -0
- package/build/src/third_party/issue-descriptions/sharedArrayBuffer.md +7 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorCrossOriginNoCorsRequest.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorDictionaryLoadFailure.md +3 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorMatchingDictionaryNotUsed.md +3 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorUnexpectedContentDictionaryHeader.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorCossOriginNoCorsRequest.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorDisallowedBySettings.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorExpiredResponse.md +3 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorFeatureDisabled.md +3 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInsufficientResources.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidMatchField.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidStructuredHeader.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidTTLField.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNavigationRequest.md +3 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNoMatchField.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonIntegerTTLField.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonListMatchDestField.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonSecureContext.md +3 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringIdField.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringInMatchDestList.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringMatchField.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonTokenTypeField.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorRequestAborted.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorShuttingDown.md +1 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorTooLongIdField.md +3 -0
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorUnsupportedType.md +3 -0
- package/build/src/third_party/issue-descriptions/sriInvalidSignatureHeader.md +14 -0
- package/build/src/third_party/issue-descriptions/sriInvalidSignatureInputHeader.md +15 -0
- package/build/src/third_party/issue-descriptions/sriMissingSignatureHeader.md +8 -0
- package/build/src/third_party/issue-descriptions/sriMissingSignatureInputHeader.md +7 -0
- package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsIncorrectLength.md +11 -0
- package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsNotByteSequence.md +14 -0
- package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsParameterized.md +15 -0
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentName.md +8 -0
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentType.md +13 -0
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidDerivedComponentParameter.md +4 -0
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidHeaderComponentParameter.md +5 -0
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidParameter.md +11 -0
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderKeyIdLength.md +12 -0
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingLabel.md +6 -0
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingRequiredParameters.md +8 -0
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderValueMissingComponents.md +11 -0
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderValueNotInnerList.md +11 -0
- package/build/src/third_party/issue-descriptions/sriValidationFailedIntegrityMismatch.md +10 -0
- package/build/src/third_party/issue-descriptions/sriValidationFailedInvalidLength.md +5 -0
- package/build/src/third_party/issue-descriptions/sriValidationFailedSignatureExpired.md +6 -0
- package/build/src/third_party/issue-descriptions/sriValidationFailedSignatureMismatch.md +11 -0
- package/build/src/third_party/issue-descriptions/stylesheetLateImport.md +4 -0
- package/build/src/third_party/issue-descriptions/stylesheetRequestFailed.md +3 -0
- package/build/src/third_party/issue-descriptions/summaryElementAccessibilityInteractiveContentSummaryDescendant.md +3 -0
- package/build/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestLength.md +12 -0
- package/build/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestType.md +17 -0
- package/build/src/third_party/issue-descriptions/unencodedDigestMalformedDictionary.md +14 -0
- package/build/src/third_party/issue-descriptions/unencodedDigestUnknownAlgorithm.md +15 -0
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +59418 -0
- package/build/src/tools/ToolDefinition.js +72 -0
- package/build/src/tools/categories.js +26 -0
- package/build/src/tools/console.js +85 -0
- package/build/src/tools/emulation.js +55 -0
- package/build/src/tools/extensions.js +96 -0
- package/build/src/tools/inPage.js +105 -0
- package/build/src/tools/input.js +376 -0
- package/build/src/tools/lighthouse.js +123 -0
- package/build/src/tools/memory.js +28 -0
- package/build/src/tools/network.js +120 -0
- package/build/src/tools/pages.js +336 -0
- package/build/src/tools/performance.js +190 -0
- package/build/src/tools/screencast.js +79 -0
- package/build/src/tools/screenshot.js +84 -0
- package/build/src/tools/script.js +125 -0
- package/build/src/tools/slim/tools.js +81 -0
- package/build/src/tools/snapshot.js +56 -0
- package/build/src/tools/tools.js +53 -0
- package/build/src/trace-processing/parse.js +84 -0
- package/build/src/types.js +6 -0
- package/build/src/utils/ExtensionRegistry.js +35 -0
- package/build/src/utils/check-for-updates.js +72 -0
- package/build/src/utils/files.js +19 -0
- package/build/src/utils/keyboard.js +296 -0
- package/build/src/utils/pagination.js +49 -0
- package/build/src/utils/string.js +36 -0
- package/build/src/utils/types.js +6 -0
- package/build/src/version.js +6 -0
- package/package.json +87 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { logger } from './logger.js';
|
|
11
|
+
import { puppeteer } from './third_party/index.js';
|
|
12
|
+
let browser;
|
|
13
|
+
function makeTargetFilter(enableExtensions = false) {
|
|
14
|
+
const ignoredPrefixes = new Set([
|
|
15
|
+
'chrome://',
|
|
16
|
+
'chrome-untrusted://',
|
|
17
|
+
'brave://',
|
|
18
|
+
]);
|
|
19
|
+
if (!enableExtensions) {
|
|
20
|
+
ignoredPrefixes.add('chrome-extension://');
|
|
21
|
+
}
|
|
22
|
+
return function targetFilter(target) {
|
|
23
|
+
if (target.url() === 'brave://newtab/' || target.url() === 'chrome://newtab/') {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
if (target.url().startsWith('brave://inspect') || target.url().startsWith('chrome://inspect')) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
for (const prefix of ignoredPrefixes) {
|
|
30
|
+
if (target.url().startsWith(prefix)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function resolveBraveExecutablePath(channel) {
|
|
38
|
+
const envPath = process.env['BRAVE_PATH'];
|
|
39
|
+
if (envPath) {
|
|
40
|
+
if (!fs.existsSync(envPath)) {
|
|
41
|
+
throw new Error(`BRAVE_PATH points to ${envPath} but that file does not exist.`);
|
|
42
|
+
}
|
|
43
|
+
return envPath;
|
|
44
|
+
}
|
|
45
|
+
const platform = os.platform();
|
|
46
|
+
if (platform === 'darwin') {
|
|
47
|
+
const paths = {
|
|
48
|
+
release: '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
|
|
49
|
+
beta: '/Applications/Brave Browser Beta.app/Contents/MacOS/Brave Browser Beta',
|
|
50
|
+
nightly: '/Applications/Brave Browser Nightly.app/Contents/MacOS/Brave Browser Nightly',
|
|
51
|
+
dev: '/Applications/Brave Browser Dev.app/Contents/MacOS/Brave Browser Dev',
|
|
52
|
+
};
|
|
53
|
+
const resolved = paths[channel ?? 'release'];
|
|
54
|
+
if (fs.existsSync(resolved)) {
|
|
55
|
+
return resolved;
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`Could not find Brave Browser (${channel ?? 'release'}) at ${resolved}. Install Brave or set the BRAVE_PATH environment variable.`);
|
|
58
|
+
}
|
|
59
|
+
if (platform === 'linux') {
|
|
60
|
+
const paths = {
|
|
61
|
+
release: ['brave-browser', 'brave-browser-stable'],
|
|
62
|
+
beta: ['brave-browser-beta'],
|
|
63
|
+
nightly: ['brave-browser-nightly'],
|
|
64
|
+
dev: ['brave-browser-dev'],
|
|
65
|
+
};
|
|
66
|
+
const candidates = paths[channel ?? 'release'];
|
|
67
|
+
for (const candidate of candidates) {
|
|
68
|
+
try {
|
|
69
|
+
const resolvedPath = execSync(`which ${candidate}`, { encoding: 'utf8' }).trim();
|
|
70
|
+
if (resolvedPath) {
|
|
71
|
+
return resolvedPath;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// try next candidate
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
throw new Error(`Could not find Brave Browser (${channel ?? 'release'}) in PATH. Install Brave or set the BRAVE_PATH environment variable.`);
|
|
79
|
+
}
|
|
80
|
+
if (platform === 'win32') {
|
|
81
|
+
const programFiles = process.env['PROGRAMFILES'] ?? 'C:\\Program Files';
|
|
82
|
+
const localAppData = process.env['LOCALAPPDATA'] ?? '';
|
|
83
|
+
const paths = {
|
|
84
|
+
release: [
|
|
85
|
+
path.join(programFiles, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
|
|
86
|
+
path.join(localAppData, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
|
|
87
|
+
],
|
|
88
|
+
beta: [
|
|
89
|
+
path.join(programFiles, 'BraveSoftware', 'Brave-Browser-Beta', 'Application', 'brave.exe'),
|
|
90
|
+
path.join(localAppData, 'BraveSoftware', 'Brave-Browser-Beta', 'Application', 'brave.exe'),
|
|
91
|
+
],
|
|
92
|
+
nightly: [
|
|
93
|
+
path.join(programFiles, 'BraveSoftware', 'Brave-Browser-Nightly', 'Application', 'brave.exe'),
|
|
94
|
+
path.join(localAppData, 'BraveSoftware', 'Brave-Browser-Nightly', 'Application', 'brave.exe'),
|
|
95
|
+
],
|
|
96
|
+
dev: [
|
|
97
|
+
path.join(programFiles, 'BraveSoftware', 'Brave-Browser-Dev', 'Application', 'brave.exe'),
|
|
98
|
+
path.join(localAppData, 'BraveSoftware', 'Brave-Browser-Dev', 'Application', 'brave.exe'),
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
const candidates = paths[channel ?? 'release'];
|
|
102
|
+
for (const candidate of candidates) {
|
|
103
|
+
if (fs.existsSync(candidate)) {
|
|
104
|
+
return candidate;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
throw new Error(`Could not find Brave Browser (${channel ?? 'release'}). Install Brave or set the BRAVE_PATH environment variable.`);
|
|
108
|
+
}
|
|
109
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
110
|
+
}
|
|
111
|
+
function resolveBraveUserDataDir(channel) {
|
|
112
|
+
const platform = os.platform();
|
|
113
|
+
const home = os.homedir();
|
|
114
|
+
if (platform === 'darwin') {
|
|
115
|
+
const dirs = {
|
|
116
|
+
release: path.join(home, 'Library', 'Application Support', 'BraveSoftware', 'Brave-Browser'),
|
|
117
|
+
beta: path.join(home, 'Library', 'Application Support', 'BraveSoftware', 'Brave-Browser-Beta'),
|
|
118
|
+
nightly: path.join(home, 'Library', 'Application Support', 'BraveSoftware', 'Brave-Browser-Nightly'),
|
|
119
|
+
dev: path.join(home, 'Library', 'Application Support', 'BraveSoftware', 'Brave-Browser-Dev'),
|
|
120
|
+
};
|
|
121
|
+
return dirs[channel ?? 'release'];
|
|
122
|
+
}
|
|
123
|
+
if (platform === 'linux') {
|
|
124
|
+
const configDir = process.env['XDG_CONFIG_HOME'] ?? path.join(home, '.config');
|
|
125
|
+
const dirs = {
|
|
126
|
+
release: path.join(configDir, 'BraveSoftware', 'Brave-Browser'),
|
|
127
|
+
beta: path.join(configDir, 'BraveSoftware', 'Brave-Browser-Beta'),
|
|
128
|
+
nightly: path.join(configDir, 'BraveSoftware', 'Brave-Browser-Nightly'),
|
|
129
|
+
dev: path.join(configDir, 'BraveSoftware', 'Brave-Browser-Dev'),
|
|
130
|
+
};
|
|
131
|
+
return dirs[channel ?? 'release'];
|
|
132
|
+
}
|
|
133
|
+
if (platform === 'win32') {
|
|
134
|
+
const localAppData = process.env['LOCALAPPDATA'] ?? path.join(home, 'AppData', 'Local');
|
|
135
|
+
const dirs = {
|
|
136
|
+
release: path.join(localAppData, 'BraveSoftware', 'Brave-Browser', 'User Data'),
|
|
137
|
+
beta: path.join(localAppData, 'BraveSoftware', 'Brave-Browser-Beta', 'User Data'),
|
|
138
|
+
nightly: path.join(localAppData, 'BraveSoftware', 'Brave-Browser-Nightly', 'User Data'),
|
|
139
|
+
dev: path.join(localAppData, 'BraveSoftware', 'Brave-Browser-Dev', 'User Data'),
|
|
140
|
+
};
|
|
141
|
+
return dirs[channel ?? 'release'];
|
|
142
|
+
}
|
|
143
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
144
|
+
}
|
|
145
|
+
export async function ensureBrowserConnected(options) {
|
|
146
|
+
const { channel, enableExtensions } = options;
|
|
147
|
+
if (browser?.connected) {
|
|
148
|
+
return browser;
|
|
149
|
+
}
|
|
150
|
+
const connectOptions = {
|
|
151
|
+
targetFilter: makeTargetFilter(enableExtensions),
|
|
152
|
+
defaultViewport: null,
|
|
153
|
+
handleDevToolsAsPage: true,
|
|
154
|
+
};
|
|
155
|
+
let autoConnect = false;
|
|
156
|
+
if (options.wsEndpoint) {
|
|
157
|
+
connectOptions.browserWSEndpoint = options.wsEndpoint;
|
|
158
|
+
if (options.wsHeaders) {
|
|
159
|
+
connectOptions.headers = options.wsHeaders;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else if (options.browserURL) {
|
|
163
|
+
connectOptions.browserURL = options.browserURL;
|
|
164
|
+
}
|
|
165
|
+
else if (channel || options.userDataDir) {
|
|
166
|
+
const userDataDir = options.userDataDir ?? resolveBraveUserDataDir(channel);
|
|
167
|
+
autoConnect = true;
|
|
168
|
+
const portPath = path.join(userDataDir, 'DevToolsActivePort');
|
|
169
|
+
try {
|
|
170
|
+
const fileContent = await fs.promises.readFile(portPath, 'utf8');
|
|
171
|
+
const [rawPort, rawPath] = fileContent
|
|
172
|
+
.split('\n')
|
|
173
|
+
.map(line => {
|
|
174
|
+
return line.trim();
|
|
175
|
+
})
|
|
176
|
+
.filter(line => {
|
|
177
|
+
return !!line;
|
|
178
|
+
});
|
|
179
|
+
if (!rawPort || !rawPath) {
|
|
180
|
+
throw new Error(`Invalid DevToolsActivePort '${fileContent}' found`);
|
|
181
|
+
}
|
|
182
|
+
const port = parseInt(rawPort, 10);
|
|
183
|
+
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
184
|
+
throw new Error(`Invalid port '${rawPort}' found`);
|
|
185
|
+
}
|
|
186
|
+
const browserWSEndpoint = `ws://127.0.0.1:${port}${rawPath}`;
|
|
187
|
+
connectOptions.browserWSEndpoint = browserWSEndpoint;
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
throw new Error(`Could not connect to Brave in ${userDataDir}. Check if Brave is running and remote debugging is enabled by going to brave://inspect/#remote-debugging.`, {
|
|
191
|
+
cause: error,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
throw new Error('Either browserURL, wsEndpoint, channel or userDataDir must be provided');
|
|
197
|
+
}
|
|
198
|
+
logger('Connecting Puppeteer to ', JSON.stringify(connectOptions));
|
|
199
|
+
try {
|
|
200
|
+
browser = await puppeteer.connect(connectOptions);
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
throw new Error(`Could not connect to Brave. ${autoConnect ? `Check if Brave is running and remote debugging is enabled by going to brave://inspect/#remote-debugging.` : `Check if Brave is running.`}`, {
|
|
204
|
+
cause: err,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
logger('Connected Puppeteer');
|
|
208
|
+
return browser;
|
|
209
|
+
}
|
|
210
|
+
export function detectDisplay() {
|
|
211
|
+
if (os.platform() === 'win32' || os.platform() === 'darwin') {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (!process.env['DISPLAY']) {
|
|
215
|
+
try {
|
|
216
|
+
const result = execSync(`ps -u $(id -u) -o pid= | xargs -I{} cat /proc/{}/environ 2>/dev/null | tr '\\0' '\\n' | grep -m1 '^DISPLAY=' | cut -d= -f2`);
|
|
217
|
+
const display = result.toString('utf8').trim();
|
|
218
|
+
process.env['DISPLAY'] = display;
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// no-op
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
export async function launch(options) {
|
|
226
|
+
const { channel, headless, isolated } = options;
|
|
227
|
+
const profileDirName = channel && channel !== 'release'
|
|
228
|
+
? `brave-profile-${channel}`
|
|
229
|
+
: 'brave-profile';
|
|
230
|
+
const executablePath = options.executablePath ?? resolveBraveExecutablePath(channel);
|
|
231
|
+
let userDataDir = options.userDataDir;
|
|
232
|
+
if (!isolated && !userDataDir) {
|
|
233
|
+
userDataDir = path.join(os.homedir(), '.cache', options.viaCli ? 'brave-devtools-mcp-cli' : 'brave-devtools-mcp', profileDirName);
|
|
234
|
+
await fs.promises.mkdir(userDataDir, {
|
|
235
|
+
recursive: true,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
const args = [
|
|
239
|
+
...(options.braveArgs ?? []),
|
|
240
|
+
'--hide-crash-restore-bubble',
|
|
241
|
+
];
|
|
242
|
+
const ignoreDefaultArgs = options.ignoreDefaultBraveArgs ?? false;
|
|
243
|
+
if (headless) {
|
|
244
|
+
args.push('--screen-info={3840x2160}');
|
|
245
|
+
}
|
|
246
|
+
if (options.devtools) {
|
|
247
|
+
args.push('--auto-open-devtools-for-tabs');
|
|
248
|
+
}
|
|
249
|
+
if (!headless) {
|
|
250
|
+
detectDisplay();
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
const browser = await puppeteer.launch({
|
|
254
|
+
targetFilter: makeTargetFilter(options.enableExtensions),
|
|
255
|
+
executablePath,
|
|
256
|
+
defaultViewport: null,
|
|
257
|
+
userDataDir,
|
|
258
|
+
pipe: true,
|
|
259
|
+
headless,
|
|
260
|
+
args,
|
|
261
|
+
ignoreDefaultArgs: ignoreDefaultArgs,
|
|
262
|
+
acceptInsecureCerts: options.acceptInsecureCerts,
|
|
263
|
+
handleDevToolsAsPage: true,
|
|
264
|
+
enableExtensions: options.enableExtensions,
|
|
265
|
+
});
|
|
266
|
+
if (options.logFile) {
|
|
267
|
+
browser.process()?.stderr?.pipe(options.logFile);
|
|
268
|
+
browser.process()?.stdout?.pipe(options.logFile);
|
|
269
|
+
}
|
|
270
|
+
if (options.viewport) {
|
|
271
|
+
const [page] = await browser.pages();
|
|
272
|
+
await page?.resize({
|
|
273
|
+
contentWidth: options.viewport.width,
|
|
274
|
+
contentHeight: options.viewport.height,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return browser;
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
if (userDataDir &&
|
|
281
|
+
error.message.includes('The browser is already running')) {
|
|
282
|
+
throw new Error(`The browser is already running for ${userDataDir}. Use --isolated to run multiple browser instances.`, {
|
|
283
|
+
cause: error,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
export async function ensureBrowserLaunched(options) {
|
|
290
|
+
if (browser?.connected) {
|
|
291
|
+
return browser;
|
|
292
|
+
}
|
|
293
|
+
browser = await launch(options);
|
|
294
|
+
return browser;
|
|
295
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import net from 'node:net';
|
|
9
|
+
import { logger } from '../logger.js';
|
|
10
|
+
import { PipeTransport } from '../third_party/index.js';
|
|
11
|
+
import { saveTemporaryFile } from '../utils/files.js';
|
|
12
|
+
import { DAEMON_SCRIPT_PATH, getSocketPath, getPidFilePath, isDaemonRunning, } from './utils.js';
|
|
13
|
+
const FILE_TIMEOUT = 10_000;
|
|
14
|
+
/**
|
|
15
|
+
* Waits for a file to be created and populated (removed = false) or removed (removed = true).
|
|
16
|
+
*/
|
|
17
|
+
function waitForFile(filePath, removed = false) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const check = () => {
|
|
20
|
+
const exists = fs.existsSync(filePath);
|
|
21
|
+
if (removed) {
|
|
22
|
+
return !exists;
|
|
23
|
+
}
|
|
24
|
+
if (!exists) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return fs.statSync(filePath).size > 0;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
if (check()) {
|
|
35
|
+
resolve();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const timer = setTimeout(() => {
|
|
39
|
+
fs.unwatchFile(filePath);
|
|
40
|
+
reject(new Error(`Timeout: file ${filePath} ${removed ? 'not removed' : 'not found'} within ${FILE_TIMEOUT}ms`));
|
|
41
|
+
}, FILE_TIMEOUT);
|
|
42
|
+
fs.watchFile(filePath, { interval: 500 }, () => {
|
|
43
|
+
if (check()) {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
fs.unwatchFile(filePath);
|
|
46
|
+
resolve();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export async function startDaemon(mcpArgs = []) {
|
|
52
|
+
if (isDaemonRunning()) {
|
|
53
|
+
logger('Daemon is already running');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const pidFilePath = getPidFilePath();
|
|
57
|
+
if (fs.existsSync(pidFilePath)) {
|
|
58
|
+
fs.unlinkSync(pidFilePath);
|
|
59
|
+
}
|
|
60
|
+
logger('Starting daemon...', ...mcpArgs);
|
|
61
|
+
const child = spawn(process.execPath, [DAEMON_SCRIPT_PATH, ...mcpArgs], {
|
|
62
|
+
detached: true,
|
|
63
|
+
stdio: 'ignore',
|
|
64
|
+
env: process.env,
|
|
65
|
+
cwd: process.cwd(),
|
|
66
|
+
windowsHide: true,
|
|
67
|
+
});
|
|
68
|
+
child.unref();
|
|
69
|
+
await waitForFile(pidFilePath);
|
|
70
|
+
}
|
|
71
|
+
const SEND_COMMAND_TIMEOUT = 60_000; // ms
|
|
72
|
+
/**
|
|
73
|
+
* `sendCommand` opens a socket connection sends a single command and disconnects.
|
|
74
|
+
*/
|
|
75
|
+
export async function sendCommand(command) {
|
|
76
|
+
const socketPath = getSocketPath();
|
|
77
|
+
const socket = net.createConnection({
|
|
78
|
+
path: socketPath,
|
|
79
|
+
});
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const timer = setTimeout(() => {
|
|
82
|
+
socket.destroy();
|
|
83
|
+
reject(new Error('Timeout waiting for daemon response'));
|
|
84
|
+
}, SEND_COMMAND_TIMEOUT);
|
|
85
|
+
const transport = new PipeTransport(socket, socket);
|
|
86
|
+
transport.onmessage = async (message) => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
logger('onmessage', message);
|
|
89
|
+
resolve(JSON.parse(message));
|
|
90
|
+
};
|
|
91
|
+
socket.on('error', error => {
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
logger('Socket error:', error);
|
|
94
|
+
reject(error);
|
|
95
|
+
});
|
|
96
|
+
socket.on('close', () => {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
logger('Socket closed:');
|
|
99
|
+
reject(new Error('Socket closed'));
|
|
100
|
+
});
|
|
101
|
+
logger('Sending message', command);
|
|
102
|
+
transport.send(JSON.stringify(command));
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
export async function stopDaemon() {
|
|
106
|
+
if (!isDaemonRunning()) {
|
|
107
|
+
logger('Daemon is not running');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const pidFilePath = getPidFilePath();
|
|
111
|
+
await sendCommand({ method: 'stop' });
|
|
112
|
+
await waitForFile(pidFilePath, /*removed=*/ true);
|
|
113
|
+
}
|
|
114
|
+
export async function handleResponse(response, format) {
|
|
115
|
+
if (response.isError) {
|
|
116
|
+
return JSON.stringify(response.content);
|
|
117
|
+
}
|
|
118
|
+
if (format === 'json') {
|
|
119
|
+
if (response.structuredContent) {
|
|
120
|
+
return JSON.stringify(response.structuredContent);
|
|
121
|
+
}
|
|
122
|
+
// Fall-through to text for backward compatibility.
|
|
123
|
+
}
|
|
124
|
+
const chunks = [];
|
|
125
|
+
for (const content of response.content) {
|
|
126
|
+
if (content.type === 'text') {
|
|
127
|
+
chunks.push(content.text);
|
|
128
|
+
}
|
|
129
|
+
else if (content.type === 'image') {
|
|
130
|
+
const imageData = content.data;
|
|
131
|
+
const mimeType = content.mimeType;
|
|
132
|
+
let extension = '.png';
|
|
133
|
+
switch (mimeType) {
|
|
134
|
+
case 'image/jpg':
|
|
135
|
+
case 'image/jpeg':
|
|
136
|
+
extension = '.jpeg';
|
|
137
|
+
break;
|
|
138
|
+
case 'webp':
|
|
139
|
+
extension = '.webp';
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
const data = Buffer.from(imageData, 'base64');
|
|
143
|
+
const name = crypto.randomUUID();
|
|
144
|
+
const { filepath } = await saveTemporaryFile(data, `${name}${extension}`);
|
|
145
|
+
chunks.push(`Saved to ${filepath}.`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
throw new Error('Not supported response content type');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return format === 'md' ? chunks.join(' ') : JSON.stringify(chunks);
|
|
152
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Google LLC
|
|
5
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import { createServer } from 'node:net';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import process from 'node:process';
|
|
11
|
+
import { logger } from '../logger.js';
|
|
12
|
+
import { Client, PipeTransport, StdioClientTransport, } from '../third_party/index.js';
|
|
13
|
+
import { VERSION } from '../version.js';
|
|
14
|
+
import { DAEMON_CLIENT_NAME, getDaemonPid, getPidFilePath, getSocketPath, INDEX_SCRIPT_PATH, IS_WINDOWS, isDaemonRunning, } from './utils.js';
|
|
15
|
+
const pid = getDaemonPid();
|
|
16
|
+
if (isDaemonRunning(pid)) {
|
|
17
|
+
logger('Another daemon process is running.');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const pidFilePath = getPidFilePath();
|
|
21
|
+
fs.mkdirSync(path.dirname(pidFilePath), {
|
|
22
|
+
recursive: true,
|
|
23
|
+
});
|
|
24
|
+
fs.writeFileSync(pidFilePath, process.pid.toString());
|
|
25
|
+
logger(`Writing ${process.pid.toString()} to ${pidFilePath}`);
|
|
26
|
+
const socketPath = getSocketPath();
|
|
27
|
+
const startDate = new Date();
|
|
28
|
+
const mcpServerArgs = process.argv.slice(2);
|
|
29
|
+
let mcpClient = null;
|
|
30
|
+
let mcpTransport = null;
|
|
31
|
+
let server = null;
|
|
32
|
+
async function setupMCPClient() {
|
|
33
|
+
console.log('Setting up MCP client connection...');
|
|
34
|
+
// Create stdio transport for brave-devtools-mcp
|
|
35
|
+
mcpTransport = new StdioClientTransport({
|
|
36
|
+
command: process.execPath,
|
|
37
|
+
args: [INDEX_SCRIPT_PATH, ...mcpServerArgs],
|
|
38
|
+
env: process.env,
|
|
39
|
+
});
|
|
40
|
+
mcpClient = new Client({
|
|
41
|
+
name: DAEMON_CLIENT_NAME,
|
|
42
|
+
version: VERSION,
|
|
43
|
+
}, {
|
|
44
|
+
capabilities: {},
|
|
45
|
+
});
|
|
46
|
+
await mcpClient.connect(mcpTransport);
|
|
47
|
+
console.log('MCP client connected');
|
|
48
|
+
}
|
|
49
|
+
async function handleRequest(msg) {
|
|
50
|
+
try {
|
|
51
|
+
if (msg.method === 'invoke_tool') {
|
|
52
|
+
if (!mcpClient) {
|
|
53
|
+
throw new Error('MCP client not initialized');
|
|
54
|
+
}
|
|
55
|
+
const { tool, args } = msg;
|
|
56
|
+
const result = (await mcpClient.callTool({
|
|
57
|
+
name: tool,
|
|
58
|
+
arguments: args || {},
|
|
59
|
+
}));
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
result: JSON.stringify(result),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
else if (msg.method === 'stop') {
|
|
66
|
+
// Ensure we are not interrupting in-progress starting.
|
|
67
|
+
await started;
|
|
68
|
+
// Trigger cleanup asynchronously.
|
|
69
|
+
setImmediate(() => {
|
|
70
|
+
void cleanup();
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
success: true,
|
|
74
|
+
message: 'stopping',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
else if (msg.method === 'status') {
|
|
78
|
+
return {
|
|
79
|
+
success: true,
|
|
80
|
+
result: JSON.stringify({
|
|
81
|
+
pid: process.pid,
|
|
82
|
+
socketPath,
|
|
83
|
+
startDate: startDate.toISOString(),
|
|
84
|
+
version: VERSION,
|
|
85
|
+
args: mcpServerArgs,
|
|
86
|
+
}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
{
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
error: `Unknown method: ${JSON.stringify(msg, null, 2)}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
98
|
+
return {
|
|
99
|
+
success: false,
|
|
100
|
+
error: errorMessage,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function startSocketServer() {
|
|
105
|
+
// Remove existing socket file if it exists (only on non-Windows)
|
|
106
|
+
if (!IS_WINDOWS) {
|
|
107
|
+
try {
|
|
108
|
+
fs.unlinkSync(socketPath);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// ignore errors.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return await new Promise((resolve, reject) => {
|
|
115
|
+
server = createServer(socket => {
|
|
116
|
+
const transport = new PipeTransport(socket, socket);
|
|
117
|
+
transport.onmessage = async (message) => {
|
|
118
|
+
logger('onmessage', message);
|
|
119
|
+
const response = await handleRequest(JSON.parse(message));
|
|
120
|
+
transport.send(JSON.stringify(response));
|
|
121
|
+
socket.end();
|
|
122
|
+
};
|
|
123
|
+
socket.on('error', error => {
|
|
124
|
+
logger('Socket error:', error);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
server.listen({
|
|
128
|
+
path: socketPath,
|
|
129
|
+
readableAll: false,
|
|
130
|
+
writableAll: false,
|
|
131
|
+
}, async () => {
|
|
132
|
+
console.log(`Daemon server listening on ${socketPath}`);
|
|
133
|
+
try {
|
|
134
|
+
// Setup MCP client
|
|
135
|
+
await setupMCPClient();
|
|
136
|
+
resolve();
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
reject(err);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
server.on('error', error => {
|
|
143
|
+
logger('Server error:', error);
|
|
144
|
+
reject(error);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async function cleanup() {
|
|
149
|
+
console.log('Cleaning up daemon...');
|
|
150
|
+
try {
|
|
151
|
+
await mcpClient?.close();
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
logger('Error closing MCP client:', error);
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
await mcpTransport?.close();
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
logger('Error closing MCP transport:', error);
|
|
161
|
+
}
|
|
162
|
+
if (server) {
|
|
163
|
+
await new Promise(resolve => {
|
|
164
|
+
server.close(() => resolve());
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (!IS_WINDOWS) {
|
|
168
|
+
try {
|
|
169
|
+
fs.unlinkSync(socketPath);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// ignore errors
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
logger(`unlinking ${pidFilePath}`);
|
|
176
|
+
if (fs.existsSync(pidFilePath)) {
|
|
177
|
+
fs.unlinkSync(pidFilePath);
|
|
178
|
+
}
|
|
179
|
+
process.exit(0);
|
|
180
|
+
}
|
|
181
|
+
// Handle shutdown signals
|
|
182
|
+
process.on('SIGTERM', () => {
|
|
183
|
+
void cleanup();
|
|
184
|
+
});
|
|
185
|
+
process.on('SIGINT', () => {
|
|
186
|
+
void cleanup();
|
|
187
|
+
});
|
|
188
|
+
process.on('SIGHUP', () => {
|
|
189
|
+
void cleanup();
|
|
190
|
+
});
|
|
191
|
+
// Handle uncaught errors
|
|
192
|
+
process.on('uncaughtException', error => {
|
|
193
|
+
logger('Uncaught exception:', error);
|
|
194
|
+
});
|
|
195
|
+
process.on('unhandledRejection', error => {
|
|
196
|
+
logger('Unhandled rejection:', error);
|
|
197
|
+
});
|
|
198
|
+
// Start the server
|
|
199
|
+
const started = startSocketServer().catch(error => {
|
|
200
|
+
logger('Failed to start daemon server:', error);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
});
|