@vibebrowser/chrome-devtools-mcp 0.26.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 +873 -0
- package/build/src/DevToolsConnectionAdapter.js +70 -0
- package/build/src/DevtoolsUtils.js +295 -0
- package/build/src/HeapSnapshotManager.js +110 -0
- package/build/src/McpContext.js +605 -0
- package/build/src/McpPage.js +315 -0
- package/build/src/McpResponse.js +858 -0
- package/build/src/Mutex.js +38 -0
- package/build/src/PageCollector.js +297 -0
- package/build/src/SlimMcpResponse.js +19 -0
- package/build/src/TextSnapshot.js +236 -0
- package/build/src/ToolHandler.js +217 -0
- package/build/src/WaitForHelper.js +190 -0
- package/build/src/bin/check-latest-version.js +50 -0
- package/build/src/bin/chrome-devtools-cli-options.js +840 -0
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +350 -0
- package/build/src/bin/chrome-devtools-mcp-main.js +94 -0
- package/build/src/bin/chrome-devtools-mcp.js +31 -0
- package/build/src/bin/chrome-devtools.js +189 -0
- package/build/src/bin/install-service.js +246 -0
- package/build/src/bin/service/chrome-devtools-mcp.service.template +17 -0
- package/build/src/bin/service/com.vibebrowser.chrome-devtools-mcp.plist.template +37 -0
- package/build/src/browser.js +204 -0
- package/build/src/daemon/client.js +154 -0
- package/build/src/daemon/daemon.js +204 -0
- package/build/src/daemon/types.js +7 -0
- package/build/src/daemon/utils.js +115 -0
- package/build/src/formatters/ConsoleFormatter.js +288 -0
- package/build/src/formatters/HeapSnapshotFormatter.js +54 -0
- package/build/src/formatters/IssueFormatter.js +193 -0
- package/build/src/formatters/NetworkFormatter.js +236 -0
- package/build/src/formatters/SnapshotFormatter.js +135 -0
- package/build/src/index.js +140 -0
- package/build/src/issue-descriptions.js +40 -0
- package/build/src/logger.js +37 -0
- package/build/src/polyfill.js +8 -0
- package/build/src/telemetry/ClearcutLogger.js +169 -0
- package/build/src/telemetry/WatchdogClient.js +61 -0
- package/build/src/telemetry/errors.js +18 -0
- package/build/src/telemetry/flagUtils.js +89 -0
- package/build/src/telemetry/metricsRegistry.js +89 -0
- package/build/src/telemetry/persistence.js +72 -0
- package/build/src/telemetry/transformation.js +134 -0
- package/build/src/telemetry/types.js +31 -0
- package/build/src/telemetry/watchdog/ClearcutSender.js +205 -0
- package/build/src/telemetry/watchdog/main.js +128 -0
- package/build/src/third_party/devtools-formatter-worker.js +8 -0
- package/build/src/third_party/devtools-heap-snapshot-worker.js +8 -0
- package/build/src/third_party/index.js +32 -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/genericFormModelContextMissingToolDescription.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormModelContextMissingToolName.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormModelContextParameterMissingName.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormModelContextParameterMissingTitleAndDescription.md +5 -0
- package/build/src/third_party/issue-descriptions/genericFormModelContextRequiredParameterMissingName.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 +61598 -0
- package/build/src/tools/ToolDefinition.js +73 -0
- package/build/src/tools/categories.js +36 -0
- package/build/src/tools/console.js +91 -0
- package/build/src/tools/emulation.js +57 -0
- package/build/src/tools/extensions.js +96 -0
- package/build/src/tools/input.js +461 -0
- package/build/src/tools/lighthouse.js +131 -0
- package/build/src/tools/memory.js +106 -0
- package/build/src/tools/network.js +125 -0
- package/build/src/tools/pages.js +411 -0
- package/build/src/tools/performance.js +196 -0
- package/build/src/tools/screencast.js +95 -0
- package/build/src/tools/screenshot.js +87 -0
- package/build/src/tools/script.js +151 -0
- package/build/src/tools/slim/tools.js +85 -0
- package/build/src/tools/snapshot.js +60 -0
- package/build/src/tools/thirdPartyDeveloper.js +75 -0
- package/build/src/tools/tools.js +56 -0
- package/build/src/tools/webmcp.js +64 -0
- package/build/src/trace-processing/parse.js +85 -0
- package/build/src/types.js +7 -0
- package/build/src/utils/check-for-updates.js +74 -0
- package/build/src/utils/files.js +18 -0
- package/build/src/utils/id.js +16 -0
- package/build/src/utils/keyboard.js +297 -0
- package/build/src/utils/pagination.js +50 -0
- package/build/src/utils/string.js +37 -0
- package/build/src/utils/types.js +7 -0
- package/build/src/version.js +10 -0
- package/package.json +93 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2025 Google LLC
|
|
5
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { execSync } from 'node:child_process';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import process from 'node:process';
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const DEFAULT_PORT = 9333;
|
|
14
|
+
function getNodePath() {
|
|
15
|
+
return process.execPath;
|
|
16
|
+
}
|
|
17
|
+
function getBinPath() {
|
|
18
|
+
return path.resolve(__dirname, 'chrome-devtools-mcp.js');
|
|
19
|
+
}
|
|
20
|
+
function printMcpConfig(url) {
|
|
21
|
+
console.log(`\nAdd to your agent config (opencode, copilot, claude, etc.):\n`);
|
|
22
|
+
console.log(JSON.stringify({
|
|
23
|
+
mcpServers: {
|
|
24
|
+
'chrome-devtools': { url },
|
|
25
|
+
},
|
|
26
|
+
}, null, 2));
|
|
27
|
+
console.log('');
|
|
28
|
+
}
|
|
29
|
+
function parseArgs() {
|
|
30
|
+
const args = process.argv.slice(2);
|
|
31
|
+
let port = DEFAULT_PORT;
|
|
32
|
+
let tailscale = false;
|
|
33
|
+
let action = 'install';
|
|
34
|
+
for (let i = 0; i < args.length; i++) {
|
|
35
|
+
if (args[i] === '--port' || args[i] === '-p') {
|
|
36
|
+
port = parseInt(args[++i], 10);
|
|
37
|
+
}
|
|
38
|
+
else if (args[i] === '--tailscale') {
|
|
39
|
+
tailscale = true;
|
|
40
|
+
}
|
|
41
|
+
else if (args[i] === 'uninstall') {
|
|
42
|
+
action = 'uninstall';
|
|
43
|
+
}
|
|
44
|
+
else if (args[i] === 'status') {
|
|
45
|
+
action = 'status';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { port, action, tailscale };
|
|
49
|
+
}
|
|
50
|
+
function installMacOS(port) {
|
|
51
|
+
const templatePath = path.resolve(__dirname, 'service', 'com.vibebrowser.chrome-devtools-mcp.plist.template');
|
|
52
|
+
const template = fs.readFileSync(templatePath, 'utf-8');
|
|
53
|
+
const logDir = path.join(process.env['HOME'] || '/tmp', 'Library', 'Logs', 'chrome-devtools-mcp');
|
|
54
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
55
|
+
const plist = template
|
|
56
|
+
.replaceAll('{{NODE_PATH}}', getNodePath())
|
|
57
|
+
.replaceAll('{{BIN_PATH}}', getBinPath())
|
|
58
|
+
.replaceAll('{{PORT}}', String(port))
|
|
59
|
+
.replaceAll('{{LOG_DIR}}', logDir);
|
|
60
|
+
const plistDir = path.join(process.env['HOME'] || '/tmp', 'Library', 'LaunchAgents');
|
|
61
|
+
fs.mkdirSync(plistDir, { recursive: true });
|
|
62
|
+
const plistPath = path.join(plistDir, 'com.vibebrowser.chrome-devtools-mcp.plist');
|
|
63
|
+
// Unload if already loaded
|
|
64
|
+
try {
|
|
65
|
+
execSync(`launchctl unload "${plistPath}" 2>/dev/null`);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// ignore
|
|
69
|
+
}
|
|
70
|
+
fs.writeFileSync(plistPath, plist);
|
|
71
|
+
execSync(`launchctl load "${plistPath}"`);
|
|
72
|
+
console.log(`✅ Installed and started launchd service`);
|
|
73
|
+
console.log(` Plist: ${plistPath}`);
|
|
74
|
+
console.log(` Logs: ${logDir}/`);
|
|
75
|
+
console.log(` URL: http://localhost:${port}/mcp`);
|
|
76
|
+
}
|
|
77
|
+
function uninstallMacOS() {
|
|
78
|
+
const plistPath = path.join(process.env['HOME'] || '/tmp', 'Library', 'LaunchAgents', 'com.vibebrowser.chrome-devtools-mcp.plist');
|
|
79
|
+
try {
|
|
80
|
+
execSync(`launchctl unload "${plistPath}" 2>/dev/null`);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// ignore
|
|
84
|
+
}
|
|
85
|
+
if (fs.existsSync(plistPath)) {
|
|
86
|
+
fs.unlinkSync(plistPath);
|
|
87
|
+
console.log(`✅ Uninstalled launchd service`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.log(`⚠️ Service not installed`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function statusMacOS() {
|
|
94
|
+
try {
|
|
95
|
+
const output = execSync('launchctl list com.vibebrowser.chrome-devtools-mcp 2>&1', { encoding: 'utf-8' });
|
|
96
|
+
console.log(`Service status:\n${output}`);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
console.log('Service is not loaded');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function installLinux(port) {
|
|
103
|
+
const templatePath = path.resolve(__dirname, 'service', 'chrome-devtools-mcp.service.template');
|
|
104
|
+
const template = fs.readFileSync(templatePath, 'utf-8');
|
|
105
|
+
const service = template
|
|
106
|
+
.replaceAll('{{NODE_PATH}}', getNodePath())
|
|
107
|
+
.replaceAll('{{BIN_PATH}}', getBinPath())
|
|
108
|
+
.replaceAll('{{PORT}}', String(port));
|
|
109
|
+
const serviceDir = path.join(process.env['HOME'] || '/tmp', '.config', 'systemd', 'user');
|
|
110
|
+
fs.mkdirSync(serviceDir, { recursive: true });
|
|
111
|
+
const servicePath = path.join(serviceDir, 'chrome-devtools-mcp.service');
|
|
112
|
+
fs.writeFileSync(servicePath, service);
|
|
113
|
+
execSync('systemctl --user daemon-reload');
|
|
114
|
+
execSync('systemctl --user enable chrome-devtools-mcp.service');
|
|
115
|
+
execSync('systemctl --user start chrome-devtools-mcp.service');
|
|
116
|
+
console.log(`✅ Installed and started systemd user service`);
|
|
117
|
+
console.log(` Unit: ${servicePath}`);
|
|
118
|
+
console.log(` URL: http://localhost:${port}/mcp`);
|
|
119
|
+
console.log(` Logs: journalctl --user -u chrome-devtools-mcp`);
|
|
120
|
+
}
|
|
121
|
+
function uninstallLinux() {
|
|
122
|
+
try {
|
|
123
|
+
execSync('systemctl --user stop chrome-devtools-mcp.service 2>/dev/null');
|
|
124
|
+
execSync('systemctl --user disable chrome-devtools-mcp.service 2>/dev/null');
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// ignore
|
|
128
|
+
}
|
|
129
|
+
const servicePath = path.join(process.env['HOME'] || '/tmp', '.config', 'systemd', 'user', 'chrome-devtools-mcp.service');
|
|
130
|
+
if (fs.existsSync(servicePath)) {
|
|
131
|
+
fs.unlinkSync(servicePath);
|
|
132
|
+
execSync('systemctl --user daemon-reload');
|
|
133
|
+
console.log(`✅ Uninstalled systemd service`);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
console.log(`⚠️ Service not installed`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function statusLinux() {
|
|
140
|
+
try {
|
|
141
|
+
const output = execSync('systemctl --user status chrome-devtools-mcp.service 2>&1', { encoding: 'utf-8' });
|
|
142
|
+
console.log(output);
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
console.log(e.stdout || 'Service is not installed');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function installTailscale(port) {
|
|
149
|
+
// Check tailscale is available
|
|
150
|
+
try {
|
|
151
|
+
execSync('tailscale version', { stdio: 'pipe' });
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
console.error('❌ tailscale CLI not found. Install from https://tailscale.com/download');
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
// Check tailscale is connected
|
|
158
|
+
try {
|
|
159
|
+
const status = execSync('tailscale status --json', { encoding: 'utf-8', stdio: 'pipe' });
|
|
160
|
+
const parsed = JSON.parse(status);
|
|
161
|
+
if (parsed.BackendState !== 'Running') {
|
|
162
|
+
console.error('❌ Tailscale is not connected. Run: tailscale up');
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
console.error('❌ Could not get tailscale status. Is it running?');
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
// Expose via tailscale serve
|
|
171
|
+
try {
|
|
172
|
+
execSync(`tailscale serve --bg --https=443 http://localhost:${port}`, {
|
|
173
|
+
stdio: 'inherit',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// Retry without --https (older tailscale versions)
|
|
178
|
+
try {
|
|
179
|
+
execSync(`tailscale serve --bg ${port}`, { stdio: 'inherit' });
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
console.error('❌ Failed to configure tailscale serve:', e.message);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Get the tailscale hostname
|
|
187
|
+
try {
|
|
188
|
+
const dnsName = execSync('tailscale status --json', { encoding: 'utf-8', stdio: 'pipe' });
|
|
189
|
+
const parsed = JSON.parse(dnsName);
|
|
190
|
+
const self = parsed.Self;
|
|
191
|
+
const hostname = self?.DNSName?.replace(/\.$/, '') || '<your-machine>.tailnet.ts.net';
|
|
192
|
+
console.log(`\n✅ Tailscale serve configured`);
|
|
193
|
+
console.log(` Remote URL: https://${hostname}/mcp`);
|
|
194
|
+
console.log(` Accessible from any device on your tailnet`);
|
|
195
|
+
printMcpConfig(`https://${hostname}/mcp`);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
console.log(`\n✅ Tailscale serve configured`);
|
|
199
|
+
console.log(` Remote URL: https://<your-machine>.tailnet.ts.net/mcp`);
|
|
200
|
+
printMcpConfig('https://<your-machine>.tailnet.ts.net/mcp');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function uninstallTailscale(port) {
|
|
204
|
+
try {
|
|
205
|
+
execSync(`tailscale serve --remove / 2>/dev/null`, { stdio: 'pipe' });
|
|
206
|
+
console.log('✅ Removed tailscale serve');
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// ignore — might not have been configured
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Main
|
|
213
|
+
const { port, action, tailscale } = parseArgs();
|
|
214
|
+
const platform = process.platform;
|
|
215
|
+
if (platform === 'darwin') {
|
|
216
|
+
if (action === 'install')
|
|
217
|
+
installMacOS(port);
|
|
218
|
+
else if (action === 'uninstall') {
|
|
219
|
+
uninstallTailscale(port);
|
|
220
|
+
uninstallMacOS();
|
|
221
|
+
}
|
|
222
|
+
else
|
|
223
|
+
statusMacOS();
|
|
224
|
+
}
|
|
225
|
+
else if (platform === 'linux') {
|
|
226
|
+
if (action === 'install')
|
|
227
|
+
installLinux(port);
|
|
228
|
+
else if (action === 'uninstall') {
|
|
229
|
+
uninstallTailscale(port);
|
|
230
|
+
uninstallLinux();
|
|
231
|
+
}
|
|
232
|
+
else
|
|
233
|
+
statusLinux();
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
console.error(`❌ Unsupported platform: ${platform}`);
|
|
237
|
+
console.error(' Supported: macOS (launchd), Linux (systemd)');
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
if (action === 'install' && tailscale) {
|
|
241
|
+
installTailscale(port);
|
|
242
|
+
}
|
|
243
|
+
else if (action === 'install') {
|
|
244
|
+
printMcpConfig(`http://localhost:${port}/mcp`);
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=install-service.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Chrome DevTools MCP Server (Streamable HTTP)
|
|
3
|
+
After=network.target
|
|
4
|
+
|
|
5
|
+
[Service]
|
|
6
|
+
Type=simple
|
|
7
|
+
ExecStart={{NODE_PATH}} --max-old-space-size=4096 {{BIN_PATH}} --autoConnect --experimentalPageIdRouting --port {{PORT}}
|
|
8
|
+
Restart=on-failure
|
|
9
|
+
RestartSec=5
|
|
10
|
+
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
11
|
+
Environment=CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS=1
|
|
12
|
+
Environment=CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS=1
|
|
13
|
+
StandardOutput=journal
|
|
14
|
+
StandardError=journal
|
|
15
|
+
|
|
16
|
+
[Install]
|
|
17
|
+
WantedBy=default.target
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>Label</key>
|
|
6
|
+
<string>com.vibebrowser.chrome-devtools-mcp</string>
|
|
7
|
+
<key>ProgramArguments</key>
|
|
8
|
+
<array>
|
|
9
|
+
<string>{{NODE_PATH}}</string>
|
|
10
|
+
<string>--max-old-space-size=4096</string>
|
|
11
|
+
<string>{{BIN_PATH}}</string>
|
|
12
|
+
<string>--autoConnect</string>
|
|
13
|
+
<string>--experimentalPageIdRouting</string>
|
|
14
|
+
<string>--port</string>
|
|
15
|
+
<string>{{PORT}}</string>
|
|
16
|
+
</array>
|
|
17
|
+
<key>RunAtLoad</key>
|
|
18
|
+
<true/>
|
|
19
|
+
<key>KeepAlive</key>
|
|
20
|
+
<true/>
|
|
21
|
+
<key>StandardOutPath</key>
|
|
22
|
+
<string>{{LOG_DIR}}/chrome-devtools-mcp.stdout.log</string>
|
|
23
|
+
<key>StandardErrorPath</key>
|
|
24
|
+
<string>{{LOG_DIR}}/chrome-devtools-mcp.stderr.log</string>
|
|
25
|
+
<key>EnvironmentVariables</key>
|
|
26
|
+
<dict>
|
|
27
|
+
<key>PATH</key>
|
|
28
|
+
<string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
|
|
29
|
+
<key>CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS</key>
|
|
30
|
+
<string>1</string>
|
|
31
|
+
<key>CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS</key>
|
|
32
|
+
<string>1</string>
|
|
33
|
+
</dict>
|
|
34
|
+
<key>ThrottleInterval</key>
|
|
35
|
+
<integer>5</integer>
|
|
36
|
+
</dict>
|
|
37
|
+
</plist>
|
|
@@ -0,0 +1,204 @@
|
|
|
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(['chrome://', 'chrome-untrusted://']);
|
|
15
|
+
if (!enableExtensions) {
|
|
16
|
+
ignoredPrefixes.add('chrome-extension://');
|
|
17
|
+
}
|
|
18
|
+
return function targetFilter(target) {
|
|
19
|
+
if (target.url() === 'chrome://newtab/') {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
// Could be the only page opened in the browser.
|
|
23
|
+
if (target.url().startsWith('chrome://inspect')) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
for (const prefix of ignoredPrefixes) {
|
|
27
|
+
if (target.url().startsWith(prefix)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export async function ensureBrowserConnected(options) {
|
|
35
|
+
const { channel, enableExtensions } = options;
|
|
36
|
+
if (browser?.connected) {
|
|
37
|
+
return browser;
|
|
38
|
+
}
|
|
39
|
+
const connectOptions = {
|
|
40
|
+
targetFilter: makeTargetFilter(enableExtensions),
|
|
41
|
+
defaultViewport: null,
|
|
42
|
+
handleDevToolsAsPage: true,
|
|
43
|
+
};
|
|
44
|
+
let autoConnect = false;
|
|
45
|
+
if (options.wsEndpoint) {
|
|
46
|
+
connectOptions.browserWSEndpoint = options.wsEndpoint;
|
|
47
|
+
if (options.wsHeaders) {
|
|
48
|
+
connectOptions.headers = options.wsHeaders;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else if (options.browserURL) {
|
|
52
|
+
connectOptions.browserURL = options.browserURL;
|
|
53
|
+
}
|
|
54
|
+
else if (channel || options.userDataDir) {
|
|
55
|
+
const userDataDir = options.userDataDir;
|
|
56
|
+
if (userDataDir) {
|
|
57
|
+
autoConnect = true;
|
|
58
|
+
// TODO: re-expose this logic via Puppeteer.
|
|
59
|
+
const portPath = path.join(userDataDir, 'DevToolsActivePort');
|
|
60
|
+
try {
|
|
61
|
+
const fileContent = await fs.promises.readFile(portPath, 'utf8');
|
|
62
|
+
const [rawPort, rawPath] = fileContent
|
|
63
|
+
.split('\n')
|
|
64
|
+
.map(line => {
|
|
65
|
+
return line.trim();
|
|
66
|
+
})
|
|
67
|
+
.filter(line => {
|
|
68
|
+
return !!line;
|
|
69
|
+
});
|
|
70
|
+
if (!rawPort || !rawPath) {
|
|
71
|
+
throw new Error(`Invalid DevToolsActivePort '${fileContent}' found`);
|
|
72
|
+
}
|
|
73
|
+
const port = parseInt(rawPort, 10);
|
|
74
|
+
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
75
|
+
throw new Error(`Invalid port '${rawPort}' found`);
|
|
76
|
+
}
|
|
77
|
+
const browserWSEndpoint = `ws://127.0.0.1:${port}${rawPath}`;
|
|
78
|
+
connectOptions.browserWSEndpoint = browserWSEndpoint;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
throw new Error(`Could not connect to Chrome in ${userDataDir}. Check if Chrome is running and remote debugging is enabled by going to chrome://inspect/#remote-debugging.`, {
|
|
82
|
+
cause: error,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
if (!channel) {
|
|
88
|
+
throw new Error('Channel must be provided if userDataDir is missing');
|
|
89
|
+
}
|
|
90
|
+
connectOptions.channel = (channel === 'stable' ? 'chrome' : `chrome-${channel}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
throw new Error('Either browserURL, wsEndpoint, channel or userDataDir must be provided');
|
|
95
|
+
}
|
|
96
|
+
logger('Connecting Puppeteer to ', JSON.stringify(connectOptions));
|
|
97
|
+
try {
|
|
98
|
+
browser = await puppeteer.connect(connectOptions);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
throw new Error(`Could not connect to Chrome. ${autoConnect ? `Check if Chrome is running and remote debugging is enabled by going to chrome://inspect/#remote-debugging.` : `Check if Chrome is running.`}`, {
|
|
102
|
+
cause: err,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
logger('Connected Puppeteer');
|
|
106
|
+
return browser;
|
|
107
|
+
}
|
|
108
|
+
export function detectDisplay() {
|
|
109
|
+
// Only detect display on Linux/UNIX.
|
|
110
|
+
if (os.platform() === 'win32' || os.platform() === 'darwin') {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (!process.env['DISPLAY']) {
|
|
114
|
+
try {
|
|
115
|
+
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`);
|
|
116
|
+
const display = result.toString('utf8').trim();
|
|
117
|
+
process.env['DISPLAY'] = display;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// no-op
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export async function launch(options) {
|
|
125
|
+
const { channel, executablePath, headless, isolated } = options;
|
|
126
|
+
const profileDirName = channel && channel !== 'stable'
|
|
127
|
+
? `chrome-profile-${channel}`
|
|
128
|
+
: 'chrome-profile';
|
|
129
|
+
let userDataDir = options.userDataDir;
|
|
130
|
+
if (!isolated && !userDataDir) {
|
|
131
|
+
userDataDir = path.join(os.homedir(), '.cache', options.viaCli ? 'chrome-devtools-mcp-cli' : 'chrome-devtools-mcp', profileDirName);
|
|
132
|
+
await fs.promises.mkdir(userDataDir, {
|
|
133
|
+
recursive: true,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const args = [
|
|
137
|
+
...(options.chromeArgs ?? []),
|
|
138
|
+
'--hide-crash-restore-bubble',
|
|
139
|
+
];
|
|
140
|
+
const ignoreDefaultArgs = options.ignoreDefaultChromeArgs ?? false;
|
|
141
|
+
if (headless) {
|
|
142
|
+
args.push('--screen-info={3840x2160}');
|
|
143
|
+
}
|
|
144
|
+
let puppeteerChannel;
|
|
145
|
+
if (options.devtools) {
|
|
146
|
+
args.push('--auto-open-devtools-for-tabs');
|
|
147
|
+
}
|
|
148
|
+
if (!executablePath) {
|
|
149
|
+
puppeteerChannel =
|
|
150
|
+
channel && channel !== 'stable'
|
|
151
|
+
? `chrome-${channel}`
|
|
152
|
+
: 'chrome';
|
|
153
|
+
}
|
|
154
|
+
if (!headless) {
|
|
155
|
+
detectDisplay();
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const browser = await puppeteer.launch({
|
|
159
|
+
channel: puppeteerChannel,
|
|
160
|
+
targetFilter: makeTargetFilter(options.enableExtensions),
|
|
161
|
+
executablePath,
|
|
162
|
+
defaultViewport: null,
|
|
163
|
+
userDataDir,
|
|
164
|
+
pipe: true,
|
|
165
|
+
headless,
|
|
166
|
+
args,
|
|
167
|
+
ignoreDefaultArgs: ignoreDefaultArgs,
|
|
168
|
+
acceptInsecureCerts: options.acceptInsecureCerts,
|
|
169
|
+
handleDevToolsAsPage: true,
|
|
170
|
+
enableExtensions: options.enableExtensions,
|
|
171
|
+
});
|
|
172
|
+
if (options.logFile) {
|
|
173
|
+
// FIXME: we are probably subscribing too late to catch startup logs. We
|
|
174
|
+
// should expose the process earlier or expose the getRecentLogs() getter.
|
|
175
|
+
browser.process()?.stderr?.pipe(options.logFile);
|
|
176
|
+
browser.process()?.stdout?.pipe(options.logFile);
|
|
177
|
+
}
|
|
178
|
+
if (options.viewport) {
|
|
179
|
+
const [page] = await browser.pages();
|
|
180
|
+
await page?.resize({
|
|
181
|
+
contentWidth: options.viewport.width,
|
|
182
|
+
contentHeight: options.viewport.height,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return browser;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
if (userDataDir &&
|
|
189
|
+
error.message.includes('The browser is already running')) {
|
|
190
|
+
throw new Error(`The browser is already running for ${userDataDir}. Use --isolated to run multiple browser instances.`, {
|
|
191
|
+
cause: error,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export async function ensureBrowserLaunched(options) {
|
|
198
|
+
if (browser?.connected) {
|
|
199
|
+
return browser;
|
|
200
|
+
}
|
|
201
|
+
browser = await launch(options);
|
|
202
|
+
return browser;
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1,154 @@
|
|
|
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 { getTempFilePath } 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 = [], sessionId) {
|
|
52
|
+
if (isDaemonRunning(sessionId)) {
|
|
53
|
+
logger('Daemon is already running');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const pidFilePath = getPidFilePath(sessionId);
|
|
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, CHROME_DEVTOOLS_MCP_SESSION_ID: sessionId },
|
|
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, sessionId) {
|
|
76
|
+
const socketPath = getSocketPath(sessionId);
|
|
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(sessionId) {
|
|
106
|
+
if (!isDaemonRunning(sessionId)) {
|
|
107
|
+
logger('Daemon is not running');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const pidFilePath = getPidFilePath(sessionId);
|
|
111
|
+
await sendCommand({ method: 'stop' }, sessionId);
|
|
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 'image/webp':
|
|
139
|
+
extension = '.webp';
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
const data = Buffer.from(imageData, 'base64');
|
|
143
|
+
const name = crypto.randomUUID();
|
|
144
|
+
const filepath = await getTempFilePath(`${name}${extension}`);
|
|
145
|
+
fs.writeFileSync(filepath, data);
|
|
146
|
+
chunks.push(`Saved to ${filepath}.`);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
throw new Error('Not supported response content type');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return format === 'md' ? chunks.join(' ') : JSON.stringify(chunks);
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=client.js.map
|