opera-devtools-mcp 0.2.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -8
- package/build/src/McpContext.js +365 -365
- package/build/src/McpPage.js +475 -48
- package/build/src/McpResponse.js +462 -288
- package/build/src/SlimMcpResponse.js +1 -1
- package/build/src/TextSnapshot.js +101 -22
- package/build/src/ToolHandler.js +142 -20
- package/build/src/bin/check-latest-version.js +27 -1
- package/build/src/bin/chrome-devtools-cli-options.js +614 -10
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +119 -32
- package/build/src/bin/chrome-devtools-mcp-main.js +50 -7
- package/build/src/bin/chrome-devtools-mcp.js +7 -4
- package/build/src/bin/chrome-devtools.js +57 -14
- package/build/src/bin/opera-devtools-cli-options.js +3 -829
- package/build/src/bin/opera-devtools-mcp-cli-options.js +3 -348
- package/build/src/bin/opera-devtools-mcp-main.js +3 -37
- package/build/src/bin/opera-devtools-mcp.js +4 -19
- package/build/src/bin/opera-devtools.js +4 -185
- package/build/src/browser.js +45 -5
- package/build/src/{PageCollector.js → collectors/PageCollector.js} +64 -133
- package/build/src/collectors/ServiceWorkerCollector.js +171 -0
- package/build/src/daemon/client.js +75 -17
- package/build/src/daemon/daemon.js +92 -30
- package/build/src/daemon/utils.js +24 -8
- package/build/src/{DevtoolsUtils.js → devtools/DevtoolsUtils.js} +94 -88
- package/build/src/devtools/McpHostBindingAdapter.js +165 -0
- package/build/src/{issue-descriptions.js → devtools/issueDescriptions.js} +8 -6
- package/build/src/formatters/ConsoleFormatter.js +44 -11
- package/build/src/formatters/HeapSnapshotFormatter.js +140 -9
- package/build/src/formatters/IssueFormatter.js +7 -7
- package/build/src/formatters/NetworkFormatter.js +10 -8
- package/build/src/index.js +101 -232
- package/build/src/opera/branding.js +34 -0
- package/build/src/opera/browserFlags.js +85 -0
- package/build/src/opera/browserLaunch.js +52 -0
- package/build/src/opera/policy.js +50 -0
- package/build/src/opera/serviceWorkerRetry.js +37 -0
- package/build/src/opera/toolHandlerHooks.js +39 -0
- package/build/src/{tools → opera/tools}/opera.js +116 -21
- package/build/src/processors/HeapSnapshotManager.js +258 -0
- package/build/src/{trace-processing/parse.js → processors/PerformanceTrace.js} +9 -9
- package/build/src/telemetry/ClearcutLogger.js +12 -119
- package/build/src/telemetry/WatchdogClient.js +5 -5
- package/build/src/telemetry/errors.js +1 -1
- package/build/src/telemetry/flagUtils.js +6 -6
- package/build/src/telemetry/{toolMetricsUtils.js → metricsRegistry.js} +4 -11
- package/build/src/telemetry/persistence.js +3 -3
- package/build/src/telemetry/transformation.js +193 -0
- package/build/src/telemetry/watchdog/ClearcutSender.js +11 -11
- package/build/src/telemetry/watchdog/main.js +6 -6
- package/build/src/third_party/THIRD_PARTY_NOTICES +232 -1061
- package/build/src/third_party/bundled-packages.json +7 -8
- package/build/src/third_party/devtools-formatter-worker.js +63 -22
- package/build/src/third_party/devtools-heap-snapshot-worker.js +809 -182
- package/build/src/third_party/index.js +69558 -74691
- package/build/src/third_party/issue-descriptions/CoepCorpNotSameOrigin.md +2 -2
- package/build/src/third_party/issue-descriptions/CoepCorpNotSameOriginAfterDefaultedToSameOriginByCoep.md +3 -3
- package/build/src/third_party/issue-descriptions/CoepCorpNotSameSite.md +2 -2
- package/build/src/third_party/issue-descriptions/CoepFrameResourceNeedsCoepHeader.md +1 -1
- package/build/src/third_party/issue-descriptions/CompatibilityModeQuirks.md +1 -1
- package/build/src/third_party/issue-descriptions/CookieAttributeValueExceedsMaxSize.md +2 -2
- package/build/src/third_party/issue-descriptions/LowTextContrast.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorRead.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorSet.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnRead.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnSet.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeRead.md +3 -3
- package/build/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeSet.md +3 -3
- package/build/src/third_party/issue-descriptions/bounceTrackingMitigations.md +1 -1
- package/build/src/third_party/issue-descriptions/connectionAllowlistEmbeddingRequirementNotSatisfied.md +26 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistIFrameAttributeLoosensEmbeddingRequirement.md +19 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowConnectionAllowlistFrom.md +21 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowlistItemType.md +2 -2
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidHeader.md +2 -2
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidUrlPattern.md +3 -3
- package/build/src/third_party/issue-descriptions/connectionAllowlistItemNotInnerList.md +2 -2
- package/build/src/third_party/issue-descriptions/connectionAllowlistMoreThanOneList.md +2 -2
- package/build/src/third_party/issue-descriptions/connectionAllowlistReportingEndpointNotToken.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieCrossSiteRedirectDowngrade.md +3 -3
- package/build/src/third_party/issue-descriptions/cookieExcludeBlockedWithinRelatedWebsiteSet.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieExcludePortMismatch.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieExcludeSchemeMismatch.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieExcludeThirdPartyPhaseoutSet.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieWarnThirdPartyPhaseoutRead.md +1 -1
- package/build/src/third_party/issue-descriptions/corsAllowCredentialsRequired.md +1 -1
- package/build/src/third_party/issue-descriptions/corsDisabledScheme.md +2 -2
- package/build/src/third_party/issue-descriptions/corsDisallowedByMode.md +2 -2
- package/build/src/third_party/issue-descriptions/corsHeaderDisallowedByPreflightResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/corsInvalidHeaderValues.md +3 -3
- package/build/src/third_party/issue-descriptions/corsLocalNetworkAccessPermissionDenied.md +1 -1
- package/build/src/third_party/issue-descriptions/corsMethodDisallowedByPreflightResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/corsNoCorsRedirectModeNotFollow.md +1 -1
- package/build/src/third_party/issue-descriptions/corsOriginMismatch.md +3 -3
- package/build/src/third_party/issue-descriptions/corsPreflightResponseInvalid.md +2 -2
- package/build/src/third_party/issue-descriptions/corsRedirectContainsCredentials.md +3 -3
- package/build/src/third_party/issue-descriptions/corsWildcardOriginNotAllowed.md +4 -3
- package/build/src/third_party/issue-descriptions/cspEvalViolation.md +4 -4
- package/build/src/third_party/issue-descriptions/cspInlineViolation.md +2 -2
- package/build/src/third_party/issue-descriptions/cspTrustedTypesPolicyViolation.md +2 -2
- package/build/src/third_party/issue-descriptions/cspTrustedTypesSinkViolation.md +4 -4
- package/build/src/third_party/issue-descriptions/cspURLViolation.md +4 -4
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsEmptyList.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsFetchFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsInvalidRecord.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestInvalidEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestKeyBindingSigningFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestRpOriginIsOpaque.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidSdJwt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenMalformedSdJwt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidAudience.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidIssuedAt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidNonce.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidSdHash.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidTyp.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingAud.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingCnf.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingIat.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingNonce.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingSdHash.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbSignatureFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmailVerified.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidHolderKey.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuedAt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuer.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtJwksMissingKeys.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingCnf.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIat.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIss.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtSignatureFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtUnsupportedHeaderAlg.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestUserLoggedOut.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownAccountsEndpointCrossOrigin.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownIssuanceEndpointCrossOrigin.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownListEmpty.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingAccountsEndpoint.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingIssuanceEndpoint.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownUnsupportedSigningAlgorithm.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsHttpNotFound.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsInvalidResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsNoResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestApprovalDeclined.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestCanceled.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestErrorFetchingSignin.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestErrorIdToken.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenHttpNotFound.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidRequest.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenNoResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestInvalidSigninResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestHttpNotFound.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestInvalidResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestNoResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestTooManyRequests.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidAccountsResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidConfigOrWellKnown.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoAccountSharingPermission.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoApiPermission.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoReturningUserFromFetchedAccounts.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotIframe.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotPotentiallyTrustworthy.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSameOrigin.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSignedInWithIdp.md +1 -1
- package/build/src/third_party/issue-descriptions/fetchingPartitionedBlobURL.md +2 -2
- package/build/src/third_party/issue-descriptions/genericBackUINavigationWouldSkipAd.md +4 -0
- package/build/src/third_party/issue-descriptions/genericFormAriaLabelledByToNonExistingIdError.md +2 -2
- package/build/src/third_party/issue-descriptions/genericFormAutocompleteAttributeEmptyError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormDuplicateIdForInputError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormEmptyIdAndNameAttributesForInputError.md +2 -2
- package/build/src/third_party/issue-descriptions/genericFormInputAssignedAutocompleteValueToIdOrNameAttributeError.md +2 -2
- package/build/src/third_party/issue-descriptions/genericFormInputHasWrongButWellIntendedAutocompleteValueError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormInputWithNoLabelError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormLabelForMatchesNonExistingIdError.md +2 -2
- package/build/src/third_party/issue-descriptions/genericFormLabelForNameError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormLabelHasNeitherForNorNestedInputError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormModelContextMissingToolName.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormModelContextParameterMissingName.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormModelContextRequiredParameterMissingName.md +1 -1
- package/build/src/third_party/issue-descriptions/genericNavigationEntryMarkedSkippable.md +2 -2
- package/build/src/third_party/issue-descriptions/heavyAd.md +1 -1
- package/build/src/third_party/issue-descriptions/lazyLoadImageZeroSize.md +7 -0
- package/build/src/third_party/issue-descriptions/navigatingPartitionedBlobURL.md +3 -3
- package/build/src/third_party/issue-descriptions/permissionElementActivationDisabled.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluder.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluderParent.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementFencedFrameDisallowed.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementFontSizeTooLarge.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementFontSizeTooSmall.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementInsetBoxShadowUnsupported.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementInvalidDisplayStyle.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementInvalidSizeValue.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementInvalidType.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementInvalidTypeActivation.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementNonOpaqueColor.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementPaddingBottomUnsupported.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementPaddingRightUnsupported.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementRequestInProgress.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementSecurityChecksFailed.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementTypeNotSupported.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementUntrustedEvent.md +1 -1
- package/build/src/third_party/issue-descriptions/placeholderDescriptionForInvisibleIssues.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedOptGroupChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedSelectChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentAttributesSelectDescendant.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentLegendChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentOptionChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityNonPhrasingContentOptionChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectivePermissionsIntervention.md +3 -3
- package/build/src/third_party/issue-descriptions/sharedArrayBuffer.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorCrossOriginNoCorsRequest.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorMatchingDictionaryNotUsed.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorUnexpectedContentDictionaryHeader.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorCossOriginNoCorsRequest.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorDisallowedBySettings.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorExpiredResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorFeatureDisabled.md +2 -2
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidStructuredHeader.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNavigationRequest.md +2 -2
- package/build/src/third_party/issue-descriptions/sriInvalidSignatureHeader.md +4 -4
- package/build/src/third_party/issue-descriptions/sriInvalidSignatureInputHeader.md +4 -4
- package/build/src/third_party/issue-descriptions/sriMissingSignatureHeader.md +1 -1
- package/build/src/third_party/issue-descriptions/sriMissingSignatureInputHeader.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsIncorrectLength.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsNotByteSequence.md +4 -4
- package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsParameterized.md +4 -4
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentName.md +1 -1
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentType.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidDerivedComponentParameter.md +1 -1
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidHeaderComponentParameter.md +1 -1
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidParameter.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderKeyIdLength.md +5 -5
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingLabel.md +3 -3
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingRequiredParameters.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderValueMissingComponents.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderValueNotInnerList.md +2 -2
- package/build/src/third_party/issue-descriptions/sriValidationFailedIntegrityMismatch.md +4 -4
- package/build/src/third_party/issue-descriptions/sriValidationFailedInvalidLength.md +3 -3
- package/build/src/third_party/issue-descriptions/sriValidationFailedSignatureExpired.md +2 -2
- package/build/src/third_party/issue-descriptions/sriValidationFailedSignatureMismatch.md +2 -2
- package/build/src/third_party/issue-descriptions/stylesheetLateImport.md +1 -1
- package/build/src/third_party/issue-descriptions/summaryElementAccessibilityInteractiveContentSummaryDescendant.md +1 -1
- package/build/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestLength.md +2 -2
- package/build/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestType.md +3 -3
- package/build/src/third_party/issue-descriptions/unencodedDigestMalformedDictionary.md +2 -2
- package/build/src/third_party/issue-descriptions/unencodedDigestUnknownAlgorithm.md +1 -1
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +3177 -6340
- package/build/src/tools/ToolDefinition.js +2 -2
- package/build/src/tools/categories.js +3 -0
- package/build/src/tools/console.js +13 -0
- package/build/src/tools/emulation.js +30 -3
- package/build/src/tools/extensions.js +8 -1
- package/build/src/tools/input.js +246 -128
- package/build/src/tools/lighthouse.js +14 -7
- package/build/src/tools/memory.js +289 -25
- package/build/src/tools/network.js +10 -7
- package/build/src/tools/pages.js +33 -92
- package/build/src/tools/performance.js +67 -52
- package/build/src/tools/pwa.js +128 -0
- package/build/src/tools/screencast.js +35 -12
- package/build/src/tools/screenshot.js +234 -75
- package/build/src/tools/script.js +130 -45
- package/build/src/tools/slim/tools.js +3 -0
- package/build/src/tools/snapshot.js +7 -4
- package/build/src/tools/thirdPartyDeveloper.js +19 -9
- package/build/src/tools/tools.js +3 -1
- package/build/src/tools/webmcp.js +3 -1
- package/build/src/utils/WaitForHelper.js +299 -0
- package/build/src/utils/check-for-updates.js +8 -2
- package/build/src/utils/files.js +42 -3
- package/build/src/utils/logger.js +51 -0
- package/build/src/utils/polyfill.js +11 -0
- package/build/src/utils/url.js +43 -0
- package/build/src/version.js +1 -4
- package/package.json +43 -33
- package/build/src/DevToolsConnectionAdapter.js +0 -70
- package/build/src/HeapSnapshotManager.js +0 -110
- package/build/src/Mutex.js +0 -38
- package/build/src/WaitForHelper.js +0 -195
- package/build/src/bin/cliDefinitions.js +0 -616
- package/build/src/logger.js +0 -37
- package/build/src/polyfill.js +0 -8
- package/build/src/telemetry/metricUtils.js +0 -15
- package/build/src/third_party/issue-descriptions/SameSiteExcludeContextDowngradeRead.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteExcludeContextDowngradeSet.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteExcludeNavigationContextDowngrade.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteWarnCrossDowngradeRead.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteWarnCrossDowngradeSet.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteWarnStrictLaxDowngradeStrict.md +0 -8
- package/build/src/third_party/issue-descriptions/arInsecureContext.md +0 -7
- package/build/src/third_party/issue-descriptions/arInvalidInfoHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arInvalidRegisterOsSourceHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arInvalidRegisterOsTriggerHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arInvalidRegisterSourceHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arInvalidRegisterTriggerHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNavigationRegistrationUniqueScopeAlreadySet.md +0 -5
- package/build/src/third_party/issue-descriptions/arNavigationRegistrationWithoutTransientUserActivation.md +0 -6
- package/build/src/third_party/issue-descriptions/arNoRegisterOsSourceHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNoRegisterOsTriggerHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNoRegisterSourceHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNoRegisterTriggerHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNoWebOrOsSupport.md +0 -4
- package/build/src/third_party/issue-descriptions/arOsSourceIgnored.md +0 -18
- package/build/src/third_party/issue-descriptions/arOsTriggerIgnored.md +0 -19
- package/build/src/third_party/issue-descriptions/arPermissionPolicyDisabled.md +0 -8
- package/build/src/third_party/issue-descriptions/arSourceAndTriggerHeaders.md +0 -9
- package/build/src/third_party/issue-descriptions/arSourceIgnored.md +0 -13
- package/build/src/third_party/issue-descriptions/arTriggerIgnored.md +0 -12
- package/build/src/third_party/issue-descriptions/arUntrustworthyReportingOrigin.md +0 -10
- package/build/src/third_party/issue-descriptions/arWebAndOsHeaders.md +0 -11
- package/build/src/utils/string.js +0 -37
- package/build/src/utils/types.js +0 -7
package/build/src/McpContext.js
CHANGED
|
@@ -7,17 +7,19 @@ import fs from 'node:fs/promises';
|
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
10
|
-
import {
|
|
11
|
-
import { HeapSnapshotManager } from './HeapSnapshotManager.js';
|
|
10
|
+
import { overrideDevToolsGlobals } from './devtools/DevtoolsUtils.js';
|
|
11
|
+
import { HeapSnapshotManager } from './processors/HeapSnapshotManager.js';
|
|
12
12
|
import { McpPage } from './McpPage.js';
|
|
13
|
-
import {
|
|
14
|
-
import { Locator,
|
|
13
|
+
import { ServiceWorkerConsoleCollector } from './collectors/ServiceWorkerCollector.js';
|
|
14
|
+
import { Locator, } from './third_party/index.js';
|
|
15
15
|
import { listPages } from './tools/pages.js';
|
|
16
16
|
import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js';
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
import { getTempFilePath, resolveCanonicalPath } from './utils/files.js';
|
|
18
|
+
// Page ids are handed out from a process-wide counter so they stay unique
|
|
19
|
+
// across all contexts, in particular across browser reconnects. An id issued
|
|
20
|
+
// before a reconnect then fails to resolve instead of hitting an unrelated
|
|
21
|
+
// page of the reconnected browser.
|
|
22
|
+
let nextPageId = 1;
|
|
21
23
|
export class McpContext {
|
|
22
24
|
browser;
|
|
23
25
|
logger;
|
|
@@ -25,16 +27,14 @@ export class McpContext {
|
|
|
25
27
|
#isolatedContexts = new Map();
|
|
26
28
|
// Auto-generated name counter for when no name is provided.
|
|
27
29
|
#nextIsolatedContextId = 1;
|
|
28
|
-
#pages = [];
|
|
29
30
|
#extensionServiceWorkers = [];
|
|
30
31
|
#mcpPages = new Map();
|
|
31
32
|
#selectedPage;
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#devtoolsUniverseManager;
|
|
33
|
+
#selectedPageFallback;
|
|
34
|
+
#serviceWorkerConsoleCollector;
|
|
35
35
|
#isRunningTrace = false;
|
|
36
36
|
#screenRecorderData = null;
|
|
37
|
-
#
|
|
37
|
+
#reconnectNotice = false;
|
|
38
38
|
#extensionPages = new WeakMap();
|
|
39
39
|
#extensionServiceWorkerMap = new WeakMap();
|
|
40
40
|
#nextExtensionServiceWorkerId = 1;
|
|
@@ -43,38 +43,33 @@ export class McpContext {
|
|
|
43
43
|
#options;
|
|
44
44
|
#heapSnapshotManager = new HeapSnapshotManager();
|
|
45
45
|
#roots = undefined;
|
|
46
|
+
#allowUnrestrictedPaths;
|
|
46
47
|
constructor(browser, logger, options, locatorClass) {
|
|
48
|
+
overrideDevToolsGlobals({
|
|
49
|
+
loadResource: (url) => {
|
|
50
|
+
return this.loadResource(url);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
47
53
|
this.browser = browser;
|
|
48
54
|
this.logger = logger;
|
|
49
55
|
this.#locatorClass = locatorClass;
|
|
50
56
|
this.#options = options;
|
|
51
|
-
this.#
|
|
52
|
-
this.#
|
|
53
|
-
|
|
54
|
-
console: event => {
|
|
55
|
-
collect(event);
|
|
56
|
-
},
|
|
57
|
-
uncaughtError: event => {
|
|
58
|
-
collect(event);
|
|
59
|
-
},
|
|
60
|
-
devtoolsAggregatedIssue: event => {
|
|
61
|
-
collect(event);
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
});
|
|
65
|
-
this.#devtoolsUniverseManager = new UniverseManager(this.browser);
|
|
57
|
+
this.#allowUnrestrictedPaths = options.allowUnrestrictedPaths ?? false;
|
|
58
|
+
this.#reconnectNotice = options.reconnected ?? false;
|
|
59
|
+
this.#serviceWorkerConsoleCollector = new ServiceWorkerConsoleCollector(this.browser);
|
|
66
60
|
}
|
|
67
61
|
async #init() {
|
|
68
|
-
|
|
69
|
-
await this.createExtensionServiceWorkersSnapshot();
|
|
70
|
-
await this.#
|
|
71
|
-
|
|
72
|
-
|
|
62
|
+
await this.createPagesSnapshot();
|
|
63
|
+
const workers = await this.createExtensionServiceWorkersSnapshot();
|
|
64
|
+
await this.#serviceWorkerConsoleCollector.init(workers);
|
|
65
|
+
this.browser.on('targetcreated', this.#onTargetCreated);
|
|
66
|
+
this.browser.on('targetdestroyed', this.#onTargetDestroyed);
|
|
73
67
|
}
|
|
74
68
|
dispose() {
|
|
75
|
-
this
|
|
76
|
-
this
|
|
77
|
-
this.#
|
|
69
|
+
this.browser.off('targetcreated', this.#onTargetCreated);
|
|
70
|
+
this.browser.off('targetdestroyed', this.#onTargetDestroyed);
|
|
71
|
+
this.#serviceWorkerConsoleCollector.dispose();
|
|
72
|
+
this.#heapSnapshotManager.dispose();
|
|
78
73
|
for (const mcpPage of this.#mcpPages.values()) {
|
|
79
74
|
mcpPage.dispose();
|
|
80
75
|
}
|
|
@@ -84,6 +79,40 @@ export class McpContext {
|
|
|
84
79
|
// without destroying browser state.
|
|
85
80
|
this.#isolatedContexts.clear();
|
|
86
81
|
}
|
|
82
|
+
#onTargetCreated = async (target) => {
|
|
83
|
+
try {
|
|
84
|
+
const page = await target.page();
|
|
85
|
+
if (!page) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
void this.#createMcpPage(page);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
this.logger?.('Error handling targetcreated', err);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
#onTargetDestroyed = (target) => {
|
|
95
|
+
try {
|
|
96
|
+
let foundPage;
|
|
97
|
+
for (const page of this.#mcpPages.keys()) {
|
|
98
|
+
if (page.target() === target) {
|
|
99
|
+
foundPage = page;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (!foundPage) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const mcpPage = this.#mcpPages.get(foundPage);
|
|
107
|
+
if (mcpPage) {
|
|
108
|
+
mcpPage.dispose();
|
|
109
|
+
this.#mcpPages.delete(foundPage);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
this.logger?.('Error handling targetdestroyed', err);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
87
116
|
static async from(browser, logger, opts,
|
|
88
117
|
/* Let tests use unbundled Locator class to avoid overly strict checks within puppeteer that fail when mixing bundled and unbundled class instances */
|
|
89
118
|
locatorClass = Locator) {
|
|
@@ -91,12 +120,12 @@ export class McpContext {
|
|
|
91
120
|
await context.#init();
|
|
92
121
|
return context;
|
|
93
122
|
}
|
|
123
|
+
static resetPageIdsForTesting() {
|
|
124
|
+
nextPageId = 1;
|
|
125
|
+
}
|
|
94
126
|
roots() {
|
|
95
|
-
if (this.#roots === undefined) {
|
|
96
|
-
return undefined;
|
|
97
|
-
}
|
|
98
127
|
return [
|
|
99
|
-
...this.#roots,
|
|
128
|
+
...(this.#roots ?? []),
|
|
100
129
|
{
|
|
101
130
|
uri: pathToFileURL(os.tmpdir()).href,
|
|
102
131
|
name: 'temp',
|
|
@@ -106,53 +135,64 @@ export class McpContext {
|
|
|
106
135
|
setRoots(roots) {
|
|
107
136
|
this.#roots = roots;
|
|
108
137
|
}
|
|
109
|
-
validatePath(filePath) {
|
|
138
|
+
async validatePath(filePath) {
|
|
110
139
|
if (filePath === undefined) {
|
|
111
140
|
return;
|
|
112
141
|
}
|
|
113
|
-
|
|
114
|
-
|
|
142
|
+
// If the client never negotiated roots and the operator has explicitly
|
|
143
|
+
// opted into unrestricted access via --allow-unrestricted-paths, restore
|
|
144
|
+
// the previous permissive behavior and skip validation.
|
|
145
|
+
if (this.#roots === undefined && this.#allowUnrestrictedPaths) {
|
|
115
146
|
return;
|
|
116
147
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
148
|
+
// roots() always returns at least the temp directory, even if the
|
|
149
|
+
// connecting client never negotiated the optional `roots` capability.
|
|
150
|
+
// Path validation must not be skipped just because no workspace roots
|
|
151
|
+
// were configured.
|
|
152
|
+
const roots = this.roots();
|
|
153
|
+
let canonicalPath;
|
|
154
|
+
try {
|
|
155
|
+
canonicalPath = await resolveCanonicalPath(filePath);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
159
|
+
console.error(`[MCP Context] Error resolving real path for ${filePath}: ${errMsg}`);
|
|
160
|
+
throw new Error(`Access denied: Cannot resolve base path for ${filePath}.`);
|
|
161
|
+
}
|
|
162
|
+
let allowed = false;
|
|
163
|
+
const resolvedRoots = await Promise.allSettled(roots.map(async (root) => {
|
|
164
|
+
const rootPathUri = root.uri;
|
|
165
|
+
const rootPath = path.resolve(fileURLToPath(rootPathUri));
|
|
166
|
+
return await fs.realpath(rootPath);
|
|
167
|
+
}));
|
|
168
|
+
for (let i = 0; i < roots.length; i++) {
|
|
169
|
+
const root = roots[i];
|
|
170
|
+
const result = resolvedRoots[i];
|
|
171
|
+
if (result.status === 'fulfilled') {
|
|
172
|
+
const canonicalRoot = result.value;
|
|
173
|
+
if (canonicalPath === canonicalRoot ||
|
|
174
|
+
canonicalPath.startsWith(canonicalRoot + path.sep)) {
|
|
175
|
+
allowed = true;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
const rootErr = result.reason;
|
|
181
|
+
const errMsg = rootErr instanceof Error ? rootErr.message : String(rootErr);
|
|
182
|
+
console.warn(`[MCP Context] Could not resolve configured root ${root.uri}: ${errMsg}`);
|
|
183
|
+
// Skip this root if it cannot be resolved.
|
|
123
184
|
}
|
|
124
185
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
resolveCdpRequestId(page, cdpRequestId) {
|
|
128
|
-
if (!cdpRequestId) {
|
|
129
|
-
this.logger('no network request');
|
|
130
|
-
return;
|
|
186
|
+
if (!allowed) {
|
|
187
|
+
throw new Error(`Access denied: path ${filePath} (canonical: ${canonicalPath}) is not within any of the configured workspace roots.`);
|
|
131
188
|
}
|
|
132
|
-
const request = this.#networkCollector.find(page.pptrPage, request => {
|
|
133
|
-
// @ts-expect-error id is internal.
|
|
134
|
-
return request.id === cdpRequestId;
|
|
135
|
-
});
|
|
136
|
-
if (!request) {
|
|
137
|
-
this.logger('no network request for ' + cdpRequestId);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
return this.#networkCollector.getIdForResource(request);
|
|
141
|
-
}
|
|
142
|
-
getNetworkRequests(page, includePreservedRequests) {
|
|
143
|
-
return this.#networkCollector.getData(page.pptrPage, includePreservedRequests);
|
|
144
|
-
}
|
|
145
|
-
getConsoleData(page, includePreservedMessages) {
|
|
146
|
-
return this.#consoleCollector.getData(page.pptrPage, includePreservedMessages);
|
|
147
|
-
}
|
|
148
|
-
getDevToolsUniverse(page) {
|
|
149
|
-
return this.#devtoolsUniverseManager.get(page.pptrPage);
|
|
150
|
-
}
|
|
151
|
-
getConsoleMessageStableId(message) {
|
|
152
|
-
return this.#consoleCollector.getIdForResource(message);
|
|
153
189
|
}
|
|
154
|
-
|
|
155
|
-
|
|
190
|
+
async ensureExtension(filePath, extension) {
|
|
191
|
+
const resolvedPath = path.resolve(filePath);
|
|
192
|
+
const currentExtension = path.extname(resolvedPath);
|
|
193
|
+
const outputPath = `${resolvedPath.slice(0, resolvedPath.length - currentExtension.length)}${extension}`;
|
|
194
|
+
await this.validatePath(outputPath);
|
|
195
|
+
return outputPath;
|
|
156
196
|
}
|
|
157
197
|
async newPage(background, isolatedContextName) {
|
|
158
198
|
let page;
|
|
@@ -167,14 +207,13 @@ export class McpContext {
|
|
|
167
207
|
else {
|
|
168
208
|
page = await this.browser.newPage({ background });
|
|
169
209
|
}
|
|
210
|
+
const mcpPage = await this.#createMcpPage(page);
|
|
170
211
|
await this.createPagesSnapshot();
|
|
171
|
-
this.selectPage(
|
|
172
|
-
|
|
173
|
-
this.#consoleCollector.addPage(page);
|
|
174
|
-
return this.#getMcpPage(page);
|
|
212
|
+
this.selectPage(mcpPage);
|
|
213
|
+
return mcpPage;
|
|
175
214
|
}
|
|
176
215
|
async closePage(pageId) {
|
|
177
|
-
if (this.#
|
|
216
|
+
if (this.#mcpPages.size === 1) {
|
|
178
217
|
throw new Error(CLOSE_PAGE_ERROR);
|
|
179
218
|
}
|
|
180
219
|
const page = this.getPageById(pageId);
|
|
@@ -184,90 +223,20 @@ export class McpContext {
|
|
|
184
223
|
}
|
|
185
224
|
await page.pptrPage.close({ runBeforeUnload: false });
|
|
186
225
|
}
|
|
187
|
-
|
|
188
|
-
return this.#
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
else if (options.networkConditions === 'Offline') {
|
|
203
|
-
await page.emulateNetworkConditions({
|
|
204
|
-
offline: true,
|
|
205
|
-
download: 0,
|
|
206
|
-
upload: 0,
|
|
207
|
-
latency: 0,
|
|
208
|
-
});
|
|
209
|
-
newSettings.networkConditions = 'Offline';
|
|
210
|
-
}
|
|
211
|
-
else if (options.networkConditions in PredefinedNetworkConditions) {
|
|
212
|
-
const networkCondition = PredefinedNetworkConditions[options.networkConditions];
|
|
213
|
-
await page.emulateNetworkConditions(networkCondition);
|
|
214
|
-
newSettings.networkConditions = options.networkConditions;
|
|
215
|
-
}
|
|
216
|
-
if (!options.cpuThrottlingRate) {
|
|
217
|
-
await page.emulateCPUThrottling(1);
|
|
218
|
-
delete newSettings.cpuThrottlingRate;
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
await page.emulateCPUThrottling(options.cpuThrottlingRate);
|
|
222
|
-
newSettings.cpuThrottlingRate = options.cpuThrottlingRate;
|
|
223
|
-
}
|
|
224
|
-
if (!options.geolocation) {
|
|
225
|
-
await page.setGeolocation({ latitude: 0, longitude: 0 });
|
|
226
|
-
delete newSettings.geolocation;
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
await page.setGeolocation(options.geolocation);
|
|
230
|
-
newSettings.geolocation = options.geolocation;
|
|
231
|
-
}
|
|
232
|
-
if (!options.userAgent) {
|
|
233
|
-
await page.setUserAgent({ userAgent: undefined });
|
|
234
|
-
delete newSettings.userAgent;
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
await page.setUserAgent({ userAgent: options.userAgent });
|
|
238
|
-
newSettings.userAgent = options.userAgent;
|
|
239
|
-
}
|
|
240
|
-
if (!options.colorScheme || options.colorScheme === 'auto') {
|
|
241
|
-
await page.emulateMediaFeatures([
|
|
242
|
-
{ name: 'prefers-color-scheme', value: '' },
|
|
243
|
-
]);
|
|
244
|
-
delete newSettings.colorScheme;
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
await page.emulateMediaFeatures([
|
|
248
|
-
{ name: 'prefers-color-scheme', value: options.colorScheme },
|
|
249
|
-
]);
|
|
250
|
-
newSettings.colorScheme = options.colorScheme;
|
|
251
|
-
}
|
|
252
|
-
if (!options.viewport) {
|
|
253
|
-
await page.setViewport(null);
|
|
254
|
-
delete newSettings.viewport;
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
const defaults = {
|
|
258
|
-
deviceScaleFactor: 1,
|
|
259
|
-
isMobile: false,
|
|
260
|
-
hasTouch: false,
|
|
261
|
-
isLandscape: false,
|
|
262
|
-
};
|
|
263
|
-
const viewport = { ...defaults, ...options.viewport };
|
|
264
|
-
await page.setViewport(viewport);
|
|
265
|
-
newSettings.viewport = viewport;
|
|
266
|
-
}
|
|
267
|
-
mcpPage.emulationSettings = Object.keys(newSettings).length
|
|
268
|
-
? newSettings
|
|
269
|
-
: {};
|
|
270
|
-
this.#updateSelectedPageTimeouts();
|
|
226
|
+
get #hasNetworkBlockOrAllowlist() {
|
|
227
|
+
return !!(this.#options.allowList || this.#options.blocklist);
|
|
228
|
+
}
|
|
229
|
+
installPWA(options) {
|
|
230
|
+
return this.browser.installPWA(options);
|
|
231
|
+
}
|
|
232
|
+
uninstallPWA(options) {
|
|
233
|
+
return this.browser.uninstallPWA(options);
|
|
234
|
+
}
|
|
235
|
+
launchPWA(options) {
|
|
236
|
+
return this.browser.launchPWA(options);
|
|
237
|
+
}
|
|
238
|
+
getPWAState(options) {
|
|
239
|
+
return this.browser.getPWAState(options);
|
|
271
240
|
}
|
|
272
241
|
setIsRunningPerformanceTrace(x) {
|
|
273
242
|
this.#isRunningTrace = x;
|
|
@@ -284,7 +253,10 @@ export class McpContext {
|
|
|
284
253
|
isCruxEnabled() {
|
|
285
254
|
return this.#options.performanceCrux;
|
|
286
255
|
}
|
|
287
|
-
|
|
256
|
+
getPages() {
|
|
257
|
+
return Array.from(this.#mcpPages.values());
|
|
258
|
+
}
|
|
259
|
+
getSelectedMcpPage() {
|
|
288
260
|
const page = this.#selectedPage;
|
|
289
261
|
if (!page) {
|
|
290
262
|
throw new Error('No page selected');
|
|
@@ -292,11 +264,37 @@ export class McpContext {
|
|
|
292
264
|
if (page.pptrPage.isClosed()) {
|
|
293
265
|
throw new Error(`The selected page has been closed. Call ${listPages().name} to see open pages.`);
|
|
294
266
|
}
|
|
295
|
-
return page
|
|
267
|
+
return page;
|
|
296
268
|
}
|
|
297
|
-
|
|
298
|
-
const
|
|
299
|
-
|
|
269
|
+
async getDevToolsData(page) {
|
|
270
|
+
const targetPage = page ?? this.#selectedPage;
|
|
271
|
+
if (!targetPage) {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
let timeoutId;
|
|
275
|
+
const timeoutPromise = new Promise(resolve => {
|
|
276
|
+
timeoutId = setTimeout(() => resolve(undefined), 500);
|
|
277
|
+
});
|
|
278
|
+
const dataPromise = targetPage.getDevToolsData();
|
|
279
|
+
try {
|
|
280
|
+
return await Promise.race([dataPromise, timeoutPromise]);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
clearTimeout(timeoutId);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Returns true once if this context was created by reconnecting after the
|
|
291
|
+
* previous browser connection was lost, so the next response can surface a
|
|
292
|
+
* note. Cleared on first call.
|
|
293
|
+
*/
|
|
294
|
+
consumeReconnectNotice() {
|
|
295
|
+
const notice = this.#reconnectNotice;
|
|
296
|
+
this.#reconnectNotice = false;
|
|
297
|
+
return notice;
|
|
300
298
|
}
|
|
301
299
|
getPageById(pageId) {
|
|
302
300
|
const page = this.#mcpPages.values().find(mcpPage => mcpPage.id === pageId);
|
|
@@ -305,55 +303,27 @@ export class McpContext {
|
|
|
305
303
|
}
|
|
306
304
|
return page;
|
|
307
305
|
}
|
|
308
|
-
getPageId(page) {
|
|
309
|
-
return this.#mcpPages.get(page)?.id;
|
|
310
|
-
}
|
|
311
|
-
#getMcpPage(page) {
|
|
312
|
-
const mcpPage = this.#mcpPages.get(page);
|
|
313
|
-
if (!mcpPage) {
|
|
314
|
-
throw new Error('No McpPage found for the given page.');
|
|
315
|
-
}
|
|
316
|
-
return mcpPage;
|
|
317
|
-
}
|
|
318
|
-
#getSelectedMcpPage() {
|
|
319
|
-
return this.#getMcpPage(this.getSelectedPptrPage());
|
|
320
|
-
}
|
|
321
306
|
isPageSelected(page) {
|
|
322
|
-
return this.#selectedPage
|
|
307
|
+
return this.#selectedPage === page;
|
|
323
308
|
}
|
|
324
309
|
selectPage(newPage) {
|
|
325
310
|
this.#selectedPage = newPage;
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
// Increased in case we throttle the network requests
|
|
337
|
-
const networkMultiplier = getNetworkMultiplierFromString(page.networkConditions);
|
|
338
|
-
page.pptrPage.setDefaultNavigationTimeout(NAVIGATION_TIMEOUT * networkMultiplier);
|
|
339
|
-
}
|
|
340
|
-
// Linear scan over per-page snapshots. The page count is small (typically
|
|
341
|
-
// 2-10) so a reverse index isn't worthwhile given the uid-reuse lifecycle
|
|
342
|
-
// complexity it would introduce.
|
|
343
|
-
getAXNodeByUid(uid) {
|
|
344
|
-
for (const mcpPage of this.#mcpPages.values()) {
|
|
345
|
-
const node = mcpPage.textSnapshot?.idToNode.get(uid);
|
|
346
|
-
if (node) {
|
|
347
|
-
return node;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
return undefined;
|
|
311
|
+
newPage.updateTimeouts();
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Returns details about the last page snapshot automatically replacing the
|
|
315
|
+
* selection because the selected page disappeared from the page list, or
|
|
316
|
+
* `undefined` if the snapshot left the selection intact. Recomputed on every
|
|
317
|
+
* createPagesSnapshot() call.
|
|
318
|
+
*/
|
|
319
|
+
getSelectedPageFallback() {
|
|
320
|
+
return this.#selectedPageFallback;
|
|
351
321
|
}
|
|
352
322
|
/**
|
|
353
323
|
* Creates a snapshot of the extension service workers.
|
|
354
324
|
*/
|
|
355
325
|
async createExtensionServiceWorkersSnapshot() {
|
|
356
|
-
const allTargets =
|
|
326
|
+
const allTargets = this.browser.targets();
|
|
357
327
|
const serviceWorkers = allTargets.filter(target => {
|
|
358
328
|
return (target.type() === 'service_worker' &&
|
|
359
329
|
target.url().includes('chrome-extension://'));
|
|
@@ -372,74 +342,16 @@ export class McpContext {
|
|
|
372
342
|
});
|
|
373
343
|
return this.#extensionServiceWorkers;
|
|
374
344
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
for (const page of allPages) {
|
|
378
|
-
let mcpPage = this.#mcpPages.get(page);
|
|
379
|
-
if (!mcpPage) {
|
|
380
|
-
mcpPage = new McpPage(page, this.#nextPageId++);
|
|
381
|
-
this.#mcpPages.set(page, mcpPage);
|
|
382
|
-
// We emulate a focused page for all pages to support multi-agent workflows.
|
|
383
|
-
void page.emulateFocusedPage(true).catch(error => {
|
|
384
|
-
this.logger('Error turning on focused page emulation', error);
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
mcpPage.isolatedContextName = isolatedContextNames.get(page);
|
|
388
|
-
}
|
|
389
|
-
// Prune orphaned #mcpPages entries (pages that no longer exist).
|
|
390
|
-
const currentPages = new Set(allPages);
|
|
391
|
-
for (const [page, mcpPage] of this.#mcpPages) {
|
|
392
|
-
if (!currentPages.has(page)) {
|
|
393
|
-
mcpPage.dispose();
|
|
394
|
-
this.#mcpPages.delete(page);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
this.#pages = allPages.filter(page => {
|
|
398
|
-
return (this.#options.experimentalDevToolsDebugging ||
|
|
399
|
-
!page.url().startsWith('devtools://'));
|
|
400
|
-
});
|
|
401
|
-
if ((!this.#selectedPage ||
|
|
402
|
-
this.#pages.indexOf(this.#selectedPage.pptrPage) === -1) &&
|
|
403
|
-
this.#pages[0]) {
|
|
404
|
-
this.selectPage(this.#getMcpPage(this.#pages[0]));
|
|
405
|
-
}
|
|
406
|
-
await this.detectOpenDevToolsWindows();
|
|
407
|
-
return this.#pages;
|
|
345
|
+
getServiceWorkerConsoleData(extensionId) {
|
|
346
|
+
return this.#serviceWorkerConsoleCollector.getData(extensionId);
|
|
408
347
|
}
|
|
409
|
-
|
|
410
|
-
const defaultCtx = this.browser.defaultBrowserContext();
|
|
411
|
-
const allPages = await this.browser.pages(this.#options.experimentalIncludeAllPages);
|
|
412
|
-
const allTargets = this.browser.targets();
|
|
413
|
-
const extensionTargets = allTargets.filter(target => {
|
|
414
|
-
return (target.url().startsWith('chrome-extension://') &&
|
|
415
|
-
target.type() === 'page');
|
|
416
|
-
});
|
|
417
|
-
for (const target of extensionTargets) {
|
|
418
|
-
// Right now target.page() returns null for popup and side panel pages.
|
|
419
|
-
let page = await target.page();
|
|
420
|
-
if (!page) {
|
|
421
|
-
// We need to cache pages instances for targets because target.asPage()
|
|
422
|
-
// returns a new page instance every time.
|
|
423
|
-
page = this.#extensionPages.get(target) ?? null;
|
|
424
|
-
if (!page) {
|
|
425
|
-
try {
|
|
426
|
-
page = await target.asPage();
|
|
427
|
-
this.#extensionPages.set(target, page);
|
|
428
|
-
}
|
|
429
|
-
catch (e) {
|
|
430
|
-
this.logger('Failed to get page for extension target', e);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
if (page && !allPages.includes(page)) {
|
|
435
|
-
allPages.push(page);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
348
|
+
#getBrowserContextToNameMap() {
|
|
438
349
|
// Build a reverse lookup from BrowserContext instance → name.
|
|
439
350
|
const contextToName = new Map();
|
|
440
351
|
for (const [name, ctx] of this.#isolatedContexts) {
|
|
441
352
|
contextToName.set(ctx, name);
|
|
442
353
|
}
|
|
354
|
+
const defaultCtx = this.browser.defaultBrowserContext();
|
|
443
355
|
// Auto-discover BrowserContexts not in our mapping (e.g., externally
|
|
444
356
|
// created incognito contexts) and assign generated names.
|
|
445
357
|
const knownContexts = new Set(this.#isolatedContexts.values());
|
|
@@ -450,40 +362,77 @@ export class McpContext {
|
|
|
450
362
|
contextToName.set(ctx, name);
|
|
451
363
|
}
|
|
452
364
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
365
|
+
return contextToName;
|
|
366
|
+
}
|
|
367
|
+
async #createMcpPage(page) {
|
|
368
|
+
let mcpPage = this.#mcpPages.get(page);
|
|
369
|
+
if (!mcpPage) {
|
|
370
|
+
mcpPage = new McpPage(page, nextPageId++, {
|
|
371
|
+
locatorClass: this.#locatorClass,
|
|
372
|
+
hasNetworkBlockOrAllowlist: this.#hasNetworkBlockOrAllowlist,
|
|
373
|
+
isolatedContextName: this.#getBrowserContextToNameMap().get(page.browserContext()),
|
|
374
|
+
navigationTimeout: this.#options.navigationTimeout,
|
|
375
|
+
});
|
|
376
|
+
this.#mcpPages.set(page, mcpPage);
|
|
377
|
+
await mcpPage.init();
|
|
461
378
|
}
|
|
462
|
-
return
|
|
379
|
+
return mcpPage;
|
|
463
380
|
}
|
|
464
|
-
async
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
381
|
+
async createPagesSnapshot() {
|
|
382
|
+
const allPages = await this.#fetchBrowserPages();
|
|
383
|
+
await Promise.allSettled(allPages.map(page => this.#createMcpPage(page)));
|
|
384
|
+
// Prune orphaned #mcpPages entries (pages that no longer exist).
|
|
385
|
+
const currentPages = new Set(allPages);
|
|
386
|
+
for (const [page, mcpPage] of this.#mcpPages) {
|
|
387
|
+
if (!currentPages.has(page)) {
|
|
388
|
+
mcpPage.dispose();
|
|
389
|
+
this.#mcpPages.delete(page);
|
|
471
390
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
391
|
+
}
|
|
392
|
+
const pages = Array.from(this.#mcpPages.values());
|
|
393
|
+
// Only fall back when the selected page is actually gone. Gating on
|
|
394
|
+
// `isClosed()` instead of `pages` membership avoids silently swapping a
|
|
395
|
+
// live page that is momentarily missing from the snapshot.
|
|
396
|
+
this.#selectedPageFallback = undefined;
|
|
397
|
+
if ((!this.#selectedPage || this.#selectedPage.pptrPage.isClosed()) &&
|
|
398
|
+
pages[0]) {
|
|
399
|
+
// Record the automatic change so the response can surface it. Skipped on
|
|
400
|
+
// first connect, when there was no prior selection to replace.
|
|
401
|
+
if (this.#selectedPage) {
|
|
402
|
+
this.#selectedPageFallback = {
|
|
403
|
+
wasClosed: this.#selectedPage.pptrPage.isClosed(),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
this.selectPage(pages[0]);
|
|
407
|
+
}
|
|
408
|
+
return pages.map(p => p.pptrPage);
|
|
409
|
+
}
|
|
410
|
+
async #fetchBrowserPages() {
|
|
411
|
+
const allPages = (await this.browser.pages(this.#options.experimentalIncludeAllPages)).filter(page => {
|
|
412
|
+
return (this.#options.experimentalDevToolsDebugging ||
|
|
413
|
+
!page.url().startsWith('devtools://'));
|
|
414
|
+
});
|
|
415
|
+
const allTargets = this.browser.targets();
|
|
416
|
+
const extensionTargets = allTargets.filter(target => {
|
|
417
|
+
return (target.url().startsWith('chrome-extension://') &&
|
|
418
|
+
target.type() === 'page');
|
|
419
|
+
});
|
|
420
|
+
await Promise.allSettled(extensionTargets.map(async (target) => {
|
|
475
421
|
try {
|
|
476
|
-
|
|
477
|
-
|
|
422
|
+
let page = await target.page();
|
|
423
|
+
if (!page) {
|
|
424
|
+
page = await target.asPage();
|
|
478
425
|
}
|
|
479
|
-
|
|
480
|
-
|
|
426
|
+
this.#extensionPages.set(target, page);
|
|
427
|
+
if (page && !allPages.includes(page)) {
|
|
428
|
+
allPages.push(page);
|
|
481
429
|
}
|
|
482
430
|
}
|
|
483
|
-
catch {
|
|
484
|
-
|
|
431
|
+
catch (e) {
|
|
432
|
+
this.logger?.('Failed to get page for extension target', e);
|
|
485
433
|
}
|
|
486
434
|
}));
|
|
435
|
+
return allPages;
|
|
487
436
|
}
|
|
488
437
|
getExtensionServiceWorkers() {
|
|
489
438
|
return this.#extensionServiceWorkers;
|
|
@@ -491,35 +440,37 @@ export class McpContext {
|
|
|
491
440
|
getExtensionServiceWorkerId(extensionServiceWorker) {
|
|
492
441
|
return this.#extensionServiceWorkerMap.get(extensionServiceWorker.target);
|
|
493
442
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
getIsolatedContextName(page) {
|
|
498
|
-
return this.#mcpPages.get(page)?.isolatedContextName;
|
|
499
|
-
}
|
|
500
|
-
async saveTemporaryFile(data, filename) {
|
|
501
|
-
const filepath = await getTempFilePath(filename);
|
|
502
|
-
this.validatePath(filepath);
|
|
443
|
+
async #writeFile(filepath, data) {
|
|
444
|
+
await this.validatePath(filepath);
|
|
503
445
|
try {
|
|
504
|
-
await fs.
|
|
446
|
+
await fs.mkdir(path.dirname(filepath), { recursive: true });
|
|
447
|
+
// Open the file with flags to:
|
|
448
|
+
// - O_WRONLY: Write-only
|
|
449
|
+
// - O_CREAT: Create if it doesn't exist
|
|
450
|
+
// - O_TRUNC: Truncate to zero length if it exists
|
|
451
|
+
// - O_NOFOLLOW: DO NOT follow symlinks.
|
|
452
|
+
// - 0o600: Permissions: read/write for owner, no permissions for others.
|
|
453
|
+
await fs.writeFile(filepath, data, {
|
|
454
|
+
flag: fs.constants.O_WRONLY |
|
|
455
|
+
fs.constants.O_CREAT |
|
|
456
|
+
fs.constants.O_TRUNC |
|
|
457
|
+
fs.constants.O_NOFOLLOW,
|
|
458
|
+
mode: 0o600,
|
|
459
|
+
});
|
|
505
460
|
}
|
|
506
461
|
catch (err) {
|
|
507
|
-
throw new Error(
|
|
462
|
+
throw new Error(`Could not write ${filepath}`, { cause: err });
|
|
508
463
|
}
|
|
464
|
+
}
|
|
465
|
+
async saveTemporaryFile(data, filename) {
|
|
466
|
+
const filepath = await getTempFilePath(filename);
|
|
467
|
+
await this.#writeFile(filepath, data);
|
|
509
468
|
return { filepath };
|
|
510
469
|
}
|
|
511
470
|
async saveFile(data, clientProvidedFilePath, extension) {
|
|
512
|
-
this.
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
516
|
-
await fs.writeFile(filePath, data);
|
|
517
|
-
return { filename: filePath };
|
|
518
|
-
}
|
|
519
|
-
catch (err) {
|
|
520
|
-
this.logger(err);
|
|
521
|
-
throw new Error('Could not save a file', { cause: err });
|
|
522
|
-
}
|
|
471
|
+
const filePath = await this.ensureExtension(clientProvidedFilePath, extension);
|
|
472
|
+
await this.#writeFile(filePath, data);
|
|
473
|
+
return { filename: filePath };
|
|
523
474
|
}
|
|
524
475
|
storeTraceRecording(result) {
|
|
525
476
|
// Clear the trace results because we only consume the latest trace currently.
|
|
@@ -529,45 +480,18 @@ export class McpContext {
|
|
|
529
480
|
recordedTraces() {
|
|
530
481
|
return this.#traceResults;
|
|
531
482
|
}
|
|
532
|
-
getNetworkRequestStableId(request) {
|
|
533
|
-
return this.#networkCollector.getIdForResource(request);
|
|
534
|
-
}
|
|
535
|
-
waitForTextOnPage(text, timeout, targetPage) {
|
|
536
|
-
const page = targetPage ?? this.getSelectedPptrPage();
|
|
537
|
-
const frames = page.frames();
|
|
538
|
-
let locator = this.#locatorClass.race(frames.flatMap(frame => text.flatMap(value => [
|
|
539
|
-
frame.locator(`aria/${value}`),
|
|
540
|
-
frame.locator(`text/${value}`),
|
|
541
|
-
])));
|
|
542
|
-
if (timeout) {
|
|
543
|
-
locator = locator.setTimeout(timeout);
|
|
544
|
-
}
|
|
545
|
-
return locator.wait();
|
|
546
|
-
}
|
|
547
|
-
/**
|
|
548
|
-
* We need to ignore favicon request as they make our test flaky
|
|
549
|
-
*/
|
|
550
|
-
async setUpNetworkCollectorForTesting() {
|
|
551
|
-
this.#networkCollector = new NetworkCollector(this.browser, collect => {
|
|
552
|
-
return {
|
|
553
|
-
request: req => {
|
|
554
|
-
if (req.url().includes('favicon.ico')) {
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
collect(req);
|
|
558
|
-
},
|
|
559
|
-
};
|
|
560
|
-
});
|
|
561
|
-
const { pages } = await this.#getAllPages();
|
|
562
|
-
await this.#networkCollector.init(pages);
|
|
563
|
-
}
|
|
564
483
|
async installExtension(extensionPath) {
|
|
565
|
-
|
|
566
|
-
|
|
484
|
+
const id = await Promise.race([
|
|
485
|
+
this.browser.installExtension(extensionPath),
|
|
486
|
+
new Promise((_, rej) => setTimeout(() => rej(new Error('Timeout installing extension')), 30000)),
|
|
487
|
+
]);
|
|
567
488
|
return id;
|
|
568
489
|
}
|
|
569
490
|
async uninstallExtension(id) {
|
|
570
|
-
await
|
|
491
|
+
await Promise.race([
|
|
492
|
+
this.browser.uninstallExtension(id),
|
|
493
|
+
new Promise((_, rej) => setTimeout(() => rej(new Error('Timeout uninstalling extension')), 30000)),
|
|
494
|
+
]);
|
|
571
495
|
}
|
|
572
496
|
async triggerExtensionAction(id) {
|
|
573
497
|
const extensions = await this.browser.extensions();
|
|
@@ -575,7 +499,7 @@ export class McpContext {
|
|
|
575
499
|
if (!extension) {
|
|
576
500
|
throw new Error(`Extension with ID ${id} not found.`);
|
|
577
501
|
}
|
|
578
|
-
const page = this.
|
|
502
|
+
const page = this.getSelectedMcpPage().pptrPage;
|
|
579
503
|
await extension.triggerAction(page);
|
|
580
504
|
}
|
|
581
505
|
listExtensions() {
|
|
@@ -585,21 +509,97 @@ export class McpContext {
|
|
|
585
509
|
const pptrExtensions = await this.browser.extensions();
|
|
586
510
|
return pptrExtensions.get(id);
|
|
587
511
|
}
|
|
588
|
-
async getHeapSnapshotAggregates(filePath) {
|
|
589
|
-
this.
|
|
590
|
-
|
|
512
|
+
async getHeapSnapshotAggregates(filePath, filterName, objectId) {
|
|
513
|
+
return await this.#heapSnapshotManager.getAggregates(filePath, filterName, objectId);
|
|
514
|
+
}
|
|
515
|
+
async getHeapSnapshotDuplicateStrings(filePath) {
|
|
516
|
+
return await this.#heapSnapshotManager.getDuplicateStrings(filePath);
|
|
591
517
|
}
|
|
592
518
|
async getHeapSnapshotStats(filePath) {
|
|
593
|
-
this.validatePath(filePath);
|
|
594
519
|
return await this.#heapSnapshotManager.getStats(filePath);
|
|
595
520
|
}
|
|
596
521
|
async getHeapSnapshotStaticData(filePath) {
|
|
597
|
-
this.validatePath(filePath);
|
|
598
522
|
return await this.#heapSnapshotManager.getStaticData(filePath);
|
|
599
523
|
}
|
|
600
|
-
async
|
|
601
|
-
this.
|
|
602
|
-
|
|
524
|
+
async getHeapSnapshotNativeContextSizes(filePath) {
|
|
525
|
+
return await this.#heapSnapshotManager.getNativeContextSizes(filePath);
|
|
526
|
+
}
|
|
527
|
+
async getHeapSnapshotRetainedByContextSummary(filePath) {
|
|
528
|
+
return await this.#heapSnapshotManager.getRetainedByContextSummary(filePath);
|
|
529
|
+
}
|
|
530
|
+
async getHeapSnapshotNodesById(filePath, id, filterName, objectId) {
|
|
531
|
+
return await this.#heapSnapshotManager.getNodesById(filePath, id, filterName, objectId);
|
|
532
|
+
}
|
|
533
|
+
async getHeapSnapshotRetainers(filePath, nodeId) {
|
|
534
|
+
return await this.#heapSnapshotManager.getRetainers(filePath, nodeId);
|
|
535
|
+
}
|
|
536
|
+
async getHeapSnapshotObjectDetails(filePath, nodeId) {
|
|
537
|
+
return await this.#heapSnapshotManager.getObjectInfo(filePath, nodeId);
|
|
538
|
+
}
|
|
539
|
+
async closeHeapSnapshot(filePath) {
|
|
540
|
+
return this.#heapSnapshotManager.disposeSnapshot(filePath);
|
|
541
|
+
}
|
|
542
|
+
hasHeapSnapshots() {
|
|
543
|
+
return this.#heapSnapshotManager.hasSnapshots();
|
|
544
|
+
}
|
|
545
|
+
async getHeapSnapshotRetainingPaths(filePath, nodeId, maxDepth, maxNodes, maxSiblings) {
|
|
546
|
+
return await this.#heapSnapshotManager.getRetainingPaths(filePath, nodeId, maxDepth, maxNodes, maxSiblings);
|
|
547
|
+
}
|
|
548
|
+
async getHeapSnapshotDominators(filePath, nodeId) {
|
|
549
|
+
return await this.#heapSnapshotManager.getDominatorsOf(filePath, nodeId);
|
|
550
|
+
}
|
|
551
|
+
#validateUrlNotBlocked(url) {
|
|
552
|
+
if (!this.#options.blocklist) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
for (const block of this.#options.blocklist) {
|
|
556
|
+
const pattern = new URLPattern(block);
|
|
557
|
+
if (pattern.test(url)) {
|
|
558
|
+
throw new Error(`Blocked by blocklist: ${url}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
#validateUrlAllowed(url) {
|
|
563
|
+
if (!this.#options.allowList) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
for (const allow of this.#options.allowList) {
|
|
567
|
+
const pattern = new URLPattern(allow);
|
|
568
|
+
if (pattern.test(url)) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
throw new Error(`Not allowed by allowlist: ${url}`);
|
|
573
|
+
}
|
|
574
|
+
async loadResource(path) {
|
|
575
|
+
const url = new URL(path);
|
|
576
|
+
this.#validateUrlNotBlocked(url);
|
|
577
|
+
switch (url.protocol) {
|
|
578
|
+
case 'https:':
|
|
579
|
+
case 'http:': {
|
|
580
|
+
this.#validateUrlAllowed(url);
|
|
581
|
+
const response = await fetch(url);
|
|
582
|
+
if (!response.ok) {
|
|
583
|
+
throw new Error(`Failed to load resource: ${url}`);
|
|
584
|
+
}
|
|
585
|
+
return response.text();
|
|
586
|
+
}
|
|
587
|
+
case 'file:': {
|
|
588
|
+
await this.validatePath(fileURLToPath(url));
|
|
589
|
+
return await fs.readFile(url, 'utf-8');
|
|
590
|
+
}
|
|
591
|
+
default:
|
|
592
|
+
throw new Error(`Unsupported protocol for: ${url}`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
async getHeapSnapshotEdges(filePath, nodeId, options) {
|
|
596
|
+
return await this.#heapSnapshotManager.getEdges(filePath, nodeId, options);
|
|
597
|
+
}
|
|
598
|
+
async getHeapSnapshotClassDiffs(baseFilePath, currentFilePath) {
|
|
599
|
+
return await this.#heapSnapshotManager.getClassDiffs(baseFilePath, currentFilePath);
|
|
600
|
+
}
|
|
601
|
+
async getHeapSnapshotDetailedClassDiff(baseFilePath, currentFilePath, classIndex) {
|
|
602
|
+
return await this.#heapSnapshotManager.getDetailedClassDiff(baseFilePath, currentFilePath, classIndex);
|
|
603
603
|
}
|
|
604
604
|
}
|
|
605
605
|
//# sourceMappingURL=McpContext.js.map
|