UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,021 lines • 43.7 kB
/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { ConsoleFormatter } from './formatters/ConsoleFormatter.js'; import { HeapSnapshotFormatter, isEdgeLike, isNodeLike, } from './formatters/HeapSnapshotFormatter.js'; import { IssueFormatter } from './formatters/IssueFormatter.js'; import { NetworkFormatter } from './formatters/NetworkFormatter.js'; import { SnapshotFormatter } from './formatters/SnapshotFormatter.js'; import { UncaughtError } from './collectors/PageCollector.js'; import { TextSnapshot } from './TextSnapshot.js'; import { DevTools, getToonEncode, getGcfEncode } from './third_party/index.js'; import { handleDialog, listPages } from './tools/pages.js'; import { getInsightOutput, getTraceSummary, } from './processors/PerformanceTrace.js'; import { stableIdSymbol } from './utils/id.js'; import { paginate } from './utils/pagination.js'; const { formatBytesToKb } = DevTools.I18n.ByteUtilities; export class McpResponse { #includePages = false; #includeExtensionServiceWorkers = false; #includeExtensionPages = false; #snapshotParams; #attachedNetworkRequestId; #attachedNetworkRequestOptions; #attachedConsoleMessageId; #attachedTraceSummary; #attachedTraceInsight; #attachedLighthouseResult; #textResponseLines = []; #images = []; #heapSnapshotOptions; #networkRequestsOptions; #consoleDataOptions; #listExtensions; #listThirdPartyDeveloperTools; #listWebMcpTools; #devToolsData; #tabId; #args; #page; #redactNetworkHeaders = true; #error; #attachedWaitForResult; #reconnectNotice = false; get #deviceScope() { return this.#page?.viewport?.isMobile ? 'PHONE' : 'DESKTOP'; } constructor(args) { this.#args = args; } setPage(page) { this.#page = page; } setRedactNetworkHeaders(value) { this.#redactNetworkHeaders = value; } /** * Surfaces a one-time note that the browser reconnected and page ids changed. * Set by the tool handler when the context reports a pending reconnect notice. */ setReconnectNotice() { this.#reconnectNotice = true; } attachDevToolsData(data) { this.#devToolsData = data; } setTabId(tabId) { this.#tabId = tabId; } setIncludePages(value) { this.#includePages = value; if (this.#args.categoryExtensions) { this.#includeExtensionServiceWorkers = value; this.#includeExtensionPages = value; } } includeSnapshot(params) { this.#snapshotParams = params ?? { verbose: false, }; } setListExtensions() { this.#listExtensions = true; } setListThirdPartyDeveloperTools() { this.#listThirdPartyDeveloperTools = true; } setListWebMcpTools() { this.#listWebMcpTools = true; } setIncludeNetworkRequests(value, options) { if (!value) { this.#networkRequestsOptions = undefined; return; } this.#networkRequestsOptions = { include: value, pagination: options?.pageSize !== undefined || options?.pageIdx !== undefined ? { pageSize: options.pageSize, pageIdx: options.pageIdx, } : undefined, resourceTypes: options?.resourceTypes, includePreservedRequests: options?.includePreservedRequests, networkRequestIdInDevToolsUI: options?.networkRequestIdInDevToolsUI, }; } setIncludeConsoleData(value, options) { if (!value) { this.#consoleDataOptions = undefined; return; } this.#consoleDataOptions = { include: value, pagination: options?.pageSize !== undefined || options?.pageIdx !== undefined ? { pageSize: options.pageSize, pageIdx: options.pageIdx, } : undefined, types: options?.types, includePreservedMessages: options?.includePreservedMessages, includeStackTraces: options?.includeStackTraces, serviceWorkerId: options?.serviceWorkerId, }; } setError(error) { this.#error = error; } attachNetworkRequest(reqId, options) { this.#attachedNetworkRequestId = reqId; this.#attachedNetworkRequestOptions = options; } attachConsoleMessage(msgid) { this.#attachedConsoleMessageId = msgid; } attachTraceSummary(result) { this.#attachedTraceSummary = result; } attachTraceInsight(trace, insightSetId, insightName) { this.#attachedTraceInsight = { trace, insightSetId, insightName, }; } attachLighthouseResult(result) { this.#attachedLighthouseResult = result; } get includePages() { return this.#includePages; } get attachedTraceSummary() { return this.#attachedTraceSummary; } get attachedTracedInsight() { return this.#attachedTraceInsight; } get attachedLighthouseResult() { return this.#attachedLighthouseResult; } get includeNetworkRequests() { return this.#networkRequestsOptions?.include ?? false; } get includeConsoleData() { return this.#consoleDataOptions?.include ?? false; } get attachedNetworkRequestId() { return this.#attachedNetworkRequestId; } get networkRequestsPageIdx() { return this.#networkRequestsOptions?.pagination?.pageIdx; } get consoleMessagesPageIdx() { return this.#consoleDataOptions?.pagination?.pageIdx; } get consoleMessagesTypes() { return this.#consoleDataOptions?.types; } get error() { return this.#error; } appendResponseLine(value) { this.#textResponseLines.push(value); } attachWaitForResult(result) { this.#attachedWaitForResult = result; } setHeapSnapshotAggregates(aggregateData, options) { this.#heapSnapshotOptions = { ...this.#heapSnapshotOptions, include: true, aggregateData, pagination: options, }; } setHeapSnapshotStats(stats, staticData, nativeContextSizes, retainedByContextSummary) { this.#heapSnapshotOptions = { ...this.#heapSnapshotOptions, include: true, stats, staticData, nativeContextSizes, retainedByContextSummary, }; } setHeapSnapshotNodes(nodes, options) { this.#heapSnapshotOptions = { ...this.#heapSnapshotOptions, include: true, nodes, pagination: options, }; } setHeapSnapshotDuplicateStrings(duplicateStrings, options) { this.#heapSnapshotOptions = { ...this.#heapSnapshotOptions, include: true, duplicateStrings, pagination: options, }; } setHeapSnapshotRetainingPaths(retainingPaths) { this.#heapSnapshotOptions = { ...this.#heapSnapshotOptions, include: true, retainingPaths, }; } setHeapSnapshotDominators(dominators) { this.#heapSnapshotOptions = { ...this.#heapSnapshotOptions, include: true, dominators, }; } setHeapSnapshotClassDiffs(classDiffs) { this.#heapSnapshotOptions = { ...this.#heapSnapshotOptions, include: true, classDiffs, }; } setHeapSnapshotDetailedClassDiff(detailedClassDiff) { this.#heapSnapshotOptions = { ...this.#heapSnapshotOptions, include: true, detailedClassDiff, }; } setHeapSnapshotObjectDetails(objectInfo) { this.#heapSnapshotOptions = { ...this.#heapSnapshotOptions, include: true, objectInfo, }; } attachImage(value) { this.#images.push(value); } get responseLines() { return this.#textResponseLines; } get images() { return this.#images; } get snapshotParams() { return this.#snapshotParams; } get listWebMcpTools() { return this.#listWebMcpTools; } async #handleSnapshot(context) { if (this.#includePages) { await context.createPagesSnapshot(); } if (!this.#snapshotParams) { return undefined; } if (!this.#page) { throw new Error('Response must have a page'); } this.#page.textSnapshot = await TextSnapshot.create(this.#page, { verbose: this.#snapshotParams.verbose, devtoolsData: this.#devToolsData, }); const formatter = new SnapshotFormatter(this.#page.textSnapshot); if (this.#snapshotParams.filePath) { const result = await context.saveFile(new TextEncoder().encode(formatter.toString()), this.#snapshotParams.filePath, '.txt'); return result.filename; } else { return formatter; } } async #handleAttachedNetworkRequest(context) { if (!this.#attachedNetworkRequestId) { return undefined; } if (!this.#page) { throw new Error(`Response must have an McpPage`); } const request = this.#page.getNetworkRequestById(this.#attachedNetworkRequestId); return await NetworkFormatter.from(request, { requestId: this.#attachedNetworkRequestId, requestIdResolver: req => this.getNetworkRequestStableId(req), fetchData: true, requestFilePath: this.#attachedNetworkRequestOptions?.requestFilePath, responseFilePath: this.#attachedNetworkRequestOptions?.responseFilePath, saveFile: (data, filename, extension) => context.saveFile(data, filename, extension), redactNetworkHeaders: this.#redactNetworkHeaders, }); } async #handleAttachedConsoleMessage() { if (!this.#attachedConsoleMessageId) { return undefined; } if (!this.#page) { throw new Error(`Response must have an McpPage`); } const message = this.#page.getConsoleMessageById(this.#attachedConsoleMessageId); const consoleMessageStableId = this.#attachedConsoleMessageId; if ('args' in message || message instanceof UncaughtError) { const consoleMessage = message; return await ConsoleFormatter.from(consoleMessage, { id: consoleMessageStableId, fetchDetailedData: true, devTools: this.#page.devtoolsUniverse, }); } else if (message instanceof DevTools.AggregatedIssue) { const formatter = new IssueFormatter(message, { id: consoleMessageStableId, requestIdResolver: this.#page.resolveCdpRequestId.bind(this.#page), elementIdResolver: this.#page.textSnapshot?.resolveCdpElementId.bind(this.#page.textSnapshot), }); if (!formatter.isValid()) { throw new Error("Can't provide details for the msgid " + consoleMessageStableId); } return formatter; } else { return undefined; } } async #handleThirdPartyDevelopeTools() { if (this.#args.categoryExperimentalThirdParty && this.#listThirdPartyDeveloperTools && this.#page) { return await this.#page.getToolGroups(); } return undefined; } async #handleWebMCP() { if (this.#args.categoryExperimentalWebmcp && this.#listWebMcpTools && this.#page) { return this.#page.getWebMcpTools(); } return undefined; } async #handleConsoleList(context) { if (!this.#consoleDataOptions?.include) { return undefined; } let messages; let page; if (this.#consoleDataOptions.serviceWorkerId) { messages = context.getServiceWorkerConsoleData(this.#consoleDataOptions.serviceWorkerId); } else { page = this.#page; if (!page) { throw new Error(`Response must have an McpPage`); } messages = page.getConsoleData(this.#consoleDataOptions.includePreservedMessages); } if (this.#consoleDataOptions.types?.length) { const normalizedTypes = new Set(this.#consoleDataOptions.types); messages = messages.filter(message => { if ('type' in message) { return normalizedTypes.has(message.type()); } if (message instanceof DevTools.AggregatedIssue) { return normalizedTypes.has('issue'); } return normalizedTypes.has('error'); }); } return (await Promise.all(messages.map(async (item) => { const consoleMessageStableId = this.getConsoleMessageStableId(item); if ('args' in item || item instanceof UncaughtError) { const consoleMessage = item; return await ConsoleFormatter.from(consoleMessage, { id: consoleMessageStableId, fetchDetailedData: false, fetchStackTrace: this.#consoleDataOptions?.includeStackTraces, devTools: page ? page.devtoolsUniverse : undefined, }); } if (item instanceof DevTools.AggregatedIssue) { const formatter = new IssueFormatter(item, { id: consoleMessageStableId, }); if (!formatter.isValid()) { return null; } return formatter; } return null; }))).filter(item => item !== null); } async #handleNetworkRequestList(context) { if (!this.#networkRequestsOptions?.include) { return undefined; } if (!this.#page) { throw new Error(`Response must have an McpPage`); } let requests = this.#page.getNetworkRequests(this.#networkRequestsOptions?.includePreservedRequests); // Apply resource type filtering if specified if (this.#networkRequestsOptions.resourceTypes?.length) { const normalizedTypes = new Set(this.#networkRequestsOptions.resourceTypes); requests = requests.filter(request => { const type = request.resourceType(); return normalizedTypes.has(type); }); } return await Promise.all(requests.map(request => NetworkFormatter.from(request, { requestId: this.getNetworkRequestStableId(request), selectedInDevToolsUI: this.getNetworkRequestStableId(request) === this.#networkRequestsOptions?.networkRequestIdInDevToolsUI, fetchData: false, saveFile: (data, filename, extension) => context.saveFile(data, filename, extension), redactNetworkHeaders: this.#redactNetworkHeaders, }))); } async handle(context, dataFormat = 'default') { const [snapshot, detailedNetworkRequest, detailedConsoleMessage, thirdPartyDeveloperTools, webmcpTools, consoleMessages, networkRequests,] = await Promise.all([ this.#handleSnapshot(context), this.#handleAttachedNetworkRequest(context), this.#handleAttachedConsoleMessage(), this.#handleThirdPartyDevelopeTools(), this.#handleWebMCP(), this.#handleConsoleList(context), this.#handleNetworkRequestList(context), ]); if (this.#includeExtensionServiceWorkers) { await context.createExtensionServiceWorkersSnapshot(); } let extensions; if (this.#listExtensions) { extensions = await context.listExtensions(); } return this.format(context, { detailedConsoleMessage, consoleMessages, snapshot, detailedNetworkRequest, networkRequests, traceInsight: this.#attachedTraceInsight, traceSummary: this.#attachedTraceSummary, extensions, lighthouseResult: this.#attachedLighthouseResult, thirdPartyDeveloperTools, webmcpTools, errorMessage: this.#error?.message, }, dataFormat); } getConsoleMessageStableId(message) { return message[stableIdSymbol] ?? -1; } getNetworkRequestStableId(request) { return request[stableIdSymbol] ?? -1; } async format(context, data, dataFormat = 'default') { const structuredContent = {}; // Resolve the compact encoder based on the chosen format let compactEncode; if (dataFormat === 'toon') { try { compactEncode = await getToonEncode(); } catch { throw new Error('The `@toon-format/toon` package is required to use --experimentalDataFormat=toon. ' + 'Make sure the peer dependency is installed:\n' + '- For npx: npx --package chrome-devtools-mcp@latest --package @toon-format/toon@latest chrome-devtools-mcp --experimentalDataFormat=toon\n' + '- For npm: npm install @toon-format/toon (add -g if installed globally)'); } } else if (dataFormat === 'gcf') { try { compactEncode = await getGcfEncode(); } catch { throw new Error('The `@blackwell-systems/gcf` package is required to use --experimentalDataFormat=gcf. ' + 'Make sure the peer dependency is installed:\n' + '- For npx: npx --package chrome-devtools-mcp@latest --package @blackwell-systems/gcf@latest chrome-devtools-mcp --experimentalDataFormat=gcf\n' + '- For npm: npm install @blackwell-systems/gcf (add -g if installed globally)'); } } const response = []; if (this.#reconnectNotice) { structuredContent.reconnected = true; response.push(`Note: the browser was restarted or reconnected since the last call. Page ids have changed. Call ${listPages().name} to see open pages.`); } if (this.#textResponseLines.length) { structuredContent.message = this.#textResponseLines.join('\n'); response.push(...this.#textResponseLines); } if (this.#attachedWaitForResult) { if (this.#attachedWaitForResult.navigatedToUrl) { response.push(`Page navigated to ${this.#attachedWaitForResult.navigatedToUrl}.`); structuredContent.navigatedToUrl = this.#attachedWaitForResult.navigatedToUrl; } } const networkConditions = this.#page?.networkConditions; if (networkConditions) { const timeout = this.#page.pptrPage.getDefaultNavigationTimeout(); response.push(`Emulating network conditions: ${networkConditions}`); response.push(`Default navigation timeout set to ${timeout} ms`); structuredContent.networkConditions = networkConditions; structuredContent.navigationTimeout = timeout; } const geolocation = this.#page?.geolocation; if (geolocation) { response.push(`Emulating geolocation: latitude=${geolocation.latitude}, longitude=${geolocation.longitude}`); structuredContent.geolocation = geolocation; } const viewport = this.#page?.viewport; if (viewport) { response.push(`Emulating viewport: ${JSON.stringify(viewport)}`); structuredContent.viewport = viewport; } const userAgent = this.#page?.userAgent; if (userAgent) { response.push(`Emulating user agent: ${userAgent}`); structuredContent.userAgent = userAgent; } const cpuThrottlingRate = this.#page?.cpuThrottlingRate ?? 1; if (cpuThrottlingRate > 1) { response.push(`Emulating CPU throttling: ${cpuThrottlingRate}x slowdown`); structuredContent.cpuThrottlingRate = cpuThrottlingRate; } const colorScheme = this.#page?.colorScheme; if (colorScheme) { response.push(`Emulating color scheme: ${colorScheme}`); structuredContent.colorScheme = colorScheme; } const dialog = this.#page?.getDialog(); if (dialog) { const defaultValueIfNeeded = dialog.type() === 'prompt' ? ` (default value: "${dialog.defaultValue()}")` : ''; response.push(`# Open dialog ${dialog.type()}: ${dialog.message()}${defaultValueIfNeeded}. Call ${handleDialog.name} to handle it before continuing.`); structuredContent.dialog = { type: dialog.type(), message: dialog.message(), defaultValue: dialog.defaultValue(), }; } if (this.#includePages) { const allPages = context.getPages(); const { regularPages, extensionPages } = allPages.reduce((acc, mcpPage) => { if (mcpPage.pptrPage.url().startsWith('chrome-extension://')) { acc.extensionPages.push(mcpPage); } else { acc.regularPages.push(mcpPage); } return acc; }, { regularPages: [], extensionPages: [] }); const selectionFallback = context.getSelectedPageFallback(); if (selectionFallback) { let selectedPageId; try { selectedPageId = context.getSelectedMcpPage().id; } catch { selectedPageId = undefined; } response.push(`Note: the previously selected page ${selectionFallback.wasClosed ? 'was closed' : 'is no longer listed'}.${selectedPageId !== undefined ? ` Page ${selectedPageId} is now selected.` : ''}`); } if (regularPages.length) { const parts = [`## Pages`]; const structuredPages = []; for (const mcpPage of regularPages) { const isolatedContextName = mcpPage.isolatedContextName; const contextLabel = isolatedContextName ? ` isolatedContext=${isolatedContextName}` : ''; const title = await fetchPageTitle(mcpPage.pptrPage); const pageLabel = title ? `${truncateTitle(title)} (${mcpPage.pptrPage.url()})` : mcpPage.pptrPage.url(); parts.push(`${mcpPage.id}: ${pageLabel}${context.isPageSelected(mcpPage) ? ' [selected]' : ''}${contextLabel}`); structuredPages.push(createStructuredPage(mcpPage, context, title)); } response.push(...parts); structuredContent.pages = structuredPages; } if (this.#includeExtensionPages) { if (extensionPages.length) { response.push(`## Extension Pages`); const structuredExtensionPages = []; for (const mcpPage of extensionPages) { const isolatedContextName = mcpPage.isolatedContextName; const contextLabel = isolatedContextName ? ` isolatedContext=${isolatedContextName}` : ''; const title = await fetchPageTitle(mcpPage.pptrPage); const pageLabel = title ? `${truncateTitle(title)} (${mcpPage.pptrPage.url()})` : mcpPage.pptrPage.url(); response.push(`${mcpPage.id}: ${pageLabel}${context.isPageSelected(mcpPage) ? ' [selected]' : ''}${contextLabel}`); structuredExtensionPages.push(createStructuredPage(mcpPage, context, title)); } structuredContent.extensionPages = structuredExtensionPages; } } } if (this.#includeExtensionServiceWorkers) { if (context.getExtensionServiceWorkers().length) { response.push(`## Extension Service Workers`); } for (const extensionServiceWorker of context.getExtensionServiceWorkers()) { response.push(`${extensionServiceWorker.id}: ${extensionServiceWorker.url}`); } structuredContent.extensionServiceWorkers = context .getExtensionServiceWorkers() .map(extensionServiceWorker => { return { id: extensionServiceWorker.id, url: extensionServiceWorker.url, }; }); } if (this.#tabId) { structuredContent.tabId = this.#tabId; } if (data.traceSummary) { const summary = getTraceSummary(data.traceSummary, this.#deviceScope); response.push(summary); structuredContent.traceSummary = summary; structuredContent.traceInsights = []; for (const insightSet of data.traceSummary.insights?.values() ?? []) { for (const [insightName, model] of Object.entries(insightSet.model)) { structuredContent.traceInsights.push({ insightName, insightKey: typeof model === 'object' && model !== null && 'insightKey' in model ? model.insightKey : undefined, }); } } } if (data.traceInsight) { const insightOutput = getInsightOutput(data.traceInsight.trace, data.traceInsight.insightSetId, data.traceInsight.insightName, this.#deviceScope); if ('error' in insightOutput) { response.push(insightOutput.error); } else { response.push(insightOutput.output); } } if (data.lighthouseResult) { structuredContent.lighthouseResult = data.lighthouseResult; const { summary, reports } = data.lighthouseResult; response.push('## Lighthouse Audit Results'); response.push(`Mode: ${summary.mode}`); response.push(`Device: ${summary.device}`); response.push(`URL: ${summary.url}`); response.push('### Category Scores'); for (const score of summary.scores) { response.push(`- ${score.title}: ${(score.score ?? 0) * 100} (${score.id})`); } response.push('### Audit Summary'); response.push(`Passed: ${summary.audits.passed}`); response.push(`Failed: ${summary.audits.failed}`); response.push(`Total Timing: ${summary.timing.total}ms`); response.push('### Reports'); for (const report of reports) { response.push(`- ${report}`); } } if (data.snapshot) { if (typeof data.snapshot === 'string') { response.push(`Saved snapshot to ${data.snapshot}.`); structuredContent.snapshotFilePath = data.snapshot; } else { structuredContent.snapshot = data.snapshot.toJSON(); response.push('## Latest page snapshot'); response.push(compactEncode ? compactEncode(structuredContent.snapshot) : data.snapshot.toString()); } } if (this.#heapSnapshotOptions?.include) { response.push('## Heap Snapshot Data'); const stats = this.#heapSnapshotOptions.stats; const staticData = this.#heapSnapshotOptions.staticData; if (stats) { response.push(`Statistics: ${JSON.stringify(stats, null, 2)}`); structuredContent.heapSnapshot = structuredContent.heapSnapshot || {}; structuredContent.heapSnapshot.stats = stats; } if (staticData) { response.push(`Static Data: ${JSON.stringify(staticData, null, 2)}`); structuredContent.heapSnapshot = structuredContent.heapSnapshot || {}; structuredContent.heapSnapshot.staticData = staticData; } const nativeContextSizes = this.#heapSnapshotOptions.nativeContextSizes; if (nativeContextSizes) { response.push('### Native Contexts'); response.push(HeapSnapshotFormatter.formatNativeContextSizes(nativeContextSizes)); structuredContent.heapSnapshot = structuredContent.heapSnapshot || {}; structuredContent.heapSnapshot.nativeContextSizes = nativeContextSizes; } const retainedByContextSummary = this.#heapSnapshotOptions.retainedByContextSummary; if (retainedByContextSummary) { response.push('### Retained by Context Summary'); response.push(HeapSnapshotFormatter.formatRetainedByContextSummary(retainedByContextSummary)); structuredContent.heapSnapshot = structuredContent.heapSnapshot || {}; structuredContent.heapSnapshot.retainedByContextSummary = retainedByContextSummary; } const aggregateData = this.#heapSnapshotOptions.aggregateData; if (aggregateData) { const sortedEntries = HeapSnapshotFormatter.sort(aggregateData.aggregates); const paginationData = this.#dataWithPagination(sortedEntries, this.#heapSnapshotOptions.pagination); response.push(`Objects: ${aggregateData.objectCount}`); response.push(`Total shallow size: ${formatBytesToKb(aggregateData.totalSelfSize)}`); structuredContent.heapSnapshot = structuredContent.heapSnapshot || {}; structuredContent.heapSnapshot.aggregateStats = { objectCount: aggregateData.objectCount, totalSelfSize: aggregateData.totalSelfSize, }; structuredContent.pagination = paginationData.pagination; response.push(...paginationData.info); const paginatedRecord = Object.fromEntries(paginationData.items); const formatter = new HeapSnapshotFormatter(paginatedRecord); structuredContent.heapSnapshotData = formatter.toJSON(); response.push(compactEncode ? compactEncode(structuredContent.heapSnapshotData) : formatter.toString()); } const nodes = this.#heapSnapshotOptions.nodes; if (nodes) { let items = Array.from(nodes.items); const firstItem = nodes.items[0]; if (firstItem) { if (isNodeLike(firstItem)) { items = items .filter(isNodeLike) .sort((a, b) => b.retainedSize - a.retainedSize); } else if (isEdgeLike(firstItem)) { items = items.filter(isEdgeLike); } } const paginationData = this.#dataWithPagination(items, this.#heapSnapshotOptions.pagination); response.push(HeapSnapshotFormatter.formatNodes(paginationData.items)); structuredContent.pagination = paginationData.pagination; response.push(...paginationData.info); structuredContent.heapSnapshotNodes = paginationData.items; } const retainingPaths = this.#heapSnapshotOptions.retainingPaths; if (retainingPaths) { response.push('### Retaining Paths'); const { paths, limitsReached } = retainingPaths; if (paths.length === 0) { response.push('No retaining paths found.'); } else { response.push(HeapSnapshotFormatter.formatRetainingPaths(paths)); } const reached = Object.entries(limitsReached) .filter(([, hit]) => hit) .map(([limit]) => limit); if (reached.length > 0) { response.push(`Note: results are truncated, the following limits were reached: ${reached.join(', ')}.`); } structuredContent.heapSnapshotRetainingPaths = retainingPaths; } const dominators = this.#heapSnapshotOptions.dominators; if (dominators) { response.push('### Dominator Chain'); if (dominators.length === 0) { response.push('No dominators found.'); } else { response.push(HeapSnapshotFormatter.formatDominators(dominators)); } structuredContent.heapSnapshotDominators = dominators; } const classDiffs = this.#heapSnapshotOptions.classDiffs; if (classDiffs) { response.push('### Heap Snapshot Diff'); response.push(compactEncode ? compactEncode(classDiffs) : HeapSnapshotFormatter.formatDiffSummary(classDiffs)); structuredContent.heapSnapshotClassDiffs = classDiffs; } const detailedClassDiff = this.#heapSnapshotOptions.detailedClassDiff; if (detailedClassDiff) { response.push('### Heap Snapshot Detailed Diff'); response.push(compactEncode ? compactEncode(detailedClassDiff) : HeapSnapshotFormatter.formatDiffDetails(detailedClassDiff)); structuredContent.heapSnapshotDetailedClassDiff = detailedClassDiff; } const duplicateStrings = this.#heapSnapshotOptions.duplicateStrings; if (duplicateStrings) { response.push('### Duplicate Strings'); const paginationData = this.#dataWithPagination(duplicateStrings, this.#heapSnapshotOptions.pagination); structuredContent.pagination = paginationData.pagination; response.push(...paginationData.info); const formatted = HeapSnapshotFormatter.formatDuplicateStrings(paginationData.items); response.push(formatted); structuredContent.heapSnapshotDuplicateStrings = paginationData.items; } const objectInfo = this.#heapSnapshotOptions.objectInfo; if (objectInfo) { response.push('### Object Details'); response.push(compactEncode ? compactEncode(objectInfo) : HeapSnapshotFormatter.formatObjectInfo(objectInfo)); structuredContent.heapSnapshotObjectDetails = objectInfo; } } if (data.detailedNetworkRequest) { response.push(data.detailedNetworkRequest.toStringDetailed()); structuredContent.networkRequest = data.detailedNetworkRequest.toJSONDetailed(); } if (data.detailedConsoleMessage) { response.push(data.detailedConsoleMessage.toStringDetailed()); structuredContent.consoleMessage = data.detailedConsoleMessage.toJSONDetailed(); } if (data.extensions) { const extensionArray = Array.from(data.extensions.values()); structuredContent.extensions = extensionArray; response.push('## Extensions'); if (extensionArray.length === 0) { response.push('No extensions installed.'); } else { const extensionsMessage = extensionArray .map(extension => { return `id=${extension.id} "${extension.name}" v${extension.version} ${extension.enabled ? 'Enabled' : 'Disabled'}`; }) .join('\n'); response.push(extensionsMessage); } } const thirdPartyDeveloperTools = data.thirdPartyDeveloperTools; if (thirdPartyDeveloperTools?.length) { structuredContent.thirdPartyDeveloperTools = thirdPartyDeveloperTools; response.push('## Third-party developer tools'); for (const toolGroup of thirdPartyDeveloperTools) { response.push(`${toolGroup.name}: ${toolGroup.description}`); response.push('Available tools:'); const toolDefinitionsMessage = toolGroup.tools .map(tool => { return `name="${tool.name}", description="${tool.description}", inputSchema=${JSON.stringify(tool.inputSchema)}`; }) .join('\n'); response.push(toolDefinitionsMessage); } } if (this.#listWebMcpTools && data.webmcpTools) { structuredContent.webmcpTools = data.webmcpTools.map(({ name, description, inputSchema, annotations }) => ({ name, description, inputSchema, annotations, })); response.push('## WebMCP tools'); if (data.webmcpTools.length === 0) { response.push('No WebMCP tools available.'); } else { const webmcpToolsMessage = data.webmcpTools .map(tool => { return `name="${tool.name}", description="${tool.description}", inputSchema=${JSON.stringify(tool.inputSchema)}, annotations=${JSON.stringify(tool.annotations)}`; }) .join('\n'); response.push(webmcpToolsMessage); } } if (this.#networkRequestsOptions?.include && data.networkRequests) { const requests = data.networkRequests; response.push('## Network requests'); if (requests.length) { const paginationData = this.#dataWithPagination(requests, this.#networkRequestsOptions.pagination); structuredContent.pagination = paginationData.pagination; response.push(...paginationData.info); if (data.networkRequests) { structuredContent.networkRequests = paginationData.items.map(i => i.toJSON()); response.push(...(compactEncode ? [compactEncode(structuredContent.networkRequests)] : paginationData.items.map(i => i.toString()))); } } else { response.push('No requests found.'); } } if (this.#consoleDataOptions?.include) { const messages = data.consoleMessages ?? []; response.push('## Console messages'); if (messages.length) { const grouped = ConsoleFormatter.groupConsecutive(messages); const paginationData = this.#dataWithPagination(grouped, this.#consoleDataOptions.pagination); structuredContent.pagination = paginationData.pagination; structuredContent.consoleMessages = paginationData.items.map(item => item.toJSON()); response.push(...paginationData.info); if (compactEncode) { response.push(compactEncode(structuredContent.consoleMessages)); } else { response.push(...paginationData.items.map(item => item.toString())); } if (structuredContent.consoleMessages.some(message => 'stackTrace' in message)) { response.push('Note: stack trace line and column numbers use 1-based indexing'); } } else { response.push('<no console messages found>'); } } if (data.errorMessage) { response.push(`Error: ${data.errorMessage}`); structuredContent.errorMessage = data.errorMessage; } const text = { type: 'text', text: response.join('\n'), }; const images = this.#images.map(imageData => { return { type: 'image', ...imageData, }; }); return { content: [text, ...images], structuredContent, }; } #dataWithPagination(data, pagination) { const response = []; const paginationResult = paginate(data, pagination); if (paginationResult.invalidPage) { response.push('Invalid page number provided. Showing first page.'); } const { startIndex, endIndex, currentPage, totalPages } = paginationResult; response.push(`Showing ${startIndex + 1}-${endIndex} of ${data.length} (Page ${currentPage + 1} of ${totalPages}).`); if (pagination) { if (paginationResult.hasNextPage) { response.push(`Next page: ${currentPage + 1}`); } if (paginationResult.hasPreviousPage) { response.push(`Previous page: ${currentPage - 1}`); } } return { info: response, items: paginationResult.items, pagination: { currentPage: paginationResult.currentPage, totalPages: paginationResult.totalPages, hasNextPage: paginationResult.hasNextPage, hasPreviousPage: paginationResult.hasPreviousPage, startIndex: paginationResult.startIndex, endIndex: paginationResult.endIndex, invalidPage: paginationResult.invalidPage, }, }; } resetResponseLineForTesting() { this.#textResponseLines = []; } } function truncateTitle(title, maxLength = 50) { if (title.length <= maxLength) { return title; } return title.slice(0, maxLength - 3) + '...'; } async function fetchPageTitle(page) { return Promise.race([ page.title().catch(() => ''), new Promise(resolve => setTimeout(() => resolve(''), 1000)), ]); } function createStructuredPage(mcpPage, context, rawTitle) { const isolatedContextName = mcpPage.isolatedContextName; const title = truncateTitle(rawTitle); const entry = { id: mcpPage.id, url: mcpPage.pptrPage.url(), title, selected: context.isPageSelected(mcpPage), }; if (isolatedContextName) { entry.isolatedContext = isolatedContextName; } return entry; } //# sourceMappingURL=McpResponse.js.map