{"version":3,"sources":["../../../../../src/server/mcp/tools/get-page-metadata.ts"],"sourcesContent":["import type { McpServer } from 'next/dist/compiled/@modelcontextprotocol/sdk/server/mcp'\nimport {\n  HMR_MESSAGE_SENT_TO_BROWSER,\n  type HmrMessageSentToBrowser,\n} from '../../dev/hot-reloader-types'\nimport {\n  createBrowserRequest,\n  handleBrowserPageResponse,\n  DEFAULT_BROWSER_REQUEST_TIMEOUT_MS,\n} from './utils/browser-communication'\nimport type {\n  PageMetadata,\n  PageSegment,\n  SegmentTrieData,\n} from '../../../shared/lib/mcp-page-metadata-types'\nimport type { SegmentTrieNode } from '../../../next-devtools/dev-overlay/segment-explorer-trie'\nimport { mcpTelemetryTracker } from '../mcp-telemetry-tracker'\n\nexport function registerGetPageMetadataTool(\n  server: McpServer,\n  sendHmrMessage: (message: HmrMessageSentToBrowser) => void,\n  getActiveConnectionCount: () => number\n) {\n  server.registerTool(\n    'get_page_metadata',\n    {\n      description:\n        'Get runtime metadata about what contributes to the current page render from active browser sessions.',\n      inputSchema: {},\n    },\n    async (_request) => {\n      // Track telemetry\n      mcpTelemetryTracker.recordToolCall('mcp/get_page_metadata')\n\n      try {\n        const connectionCount = getActiveConnectionCount()\n        if (connectionCount === 0) {\n          return {\n            content: [\n              {\n                type: 'text',\n                text: JSON.stringify({\n                  error:\n                    'No browser sessions connected. Please open your application in a browser to retrieve page metadata.',\n                }),\n              },\n            ],\n          }\n        }\n\n        const responses = await createBrowserRequest<SegmentTrieData>(\n          HMR_MESSAGE_SENT_TO_BROWSER.REQUEST_PAGE_METADATA,\n          sendHmrMessage,\n          getActiveConnectionCount,\n          DEFAULT_BROWSER_REQUEST_TIMEOUT_MS\n        )\n\n        if (responses.length === 0) {\n          return {\n            content: [\n              {\n                type: 'text',\n                text: JSON.stringify({\n                  sessions: [],\n                }),\n              },\n            ],\n          }\n        }\n\n        const sessionMetadata: Array<{ url: string; metadata: PageMetadata }> =\n          []\n        for (const response of responses) {\n          if (response.data) {\n            // TODO: Add other metadata for the current page render here. Currently, we only have segment trie data.\n            const pageMetadata = convertSegmentTrieToPageMetadata(response.data)\n            sessionMetadata.push({ url: response.url, metadata: pageMetadata })\n          }\n        }\n\n        if (sessionMetadata.length === 0) {\n          return {\n            content: [\n              {\n                type: 'text',\n                text: JSON.stringify({\n                  sessions: [],\n                }),\n              },\n            ],\n          }\n        }\n\n        const output = formatPageMetadata(sessionMetadata)\n\n        return {\n          content: [\n            {\n              type: 'text',\n              text: JSON.stringify(output),\n            },\n          ],\n        }\n      } catch (error) {\n        return {\n          content: [\n            {\n              type: 'text',\n              text: JSON.stringify({\n                error: error instanceof Error ? error.message : String(error),\n              }),\n            },\n          ],\n        }\n      }\n    }\n  )\n}\n\nexport function handlePageMetadataResponse(\n  requestId: string,\n  segmentTrieData: SegmentTrieData | null,\n  url: string | undefined\n) {\n  handleBrowserPageResponse<SegmentTrieData | null>(\n    requestId,\n    segmentTrieData,\n    url || ''\n  )\n}\n\nfunction convertSegmentTrieToPageMetadata(data: SegmentTrieData): PageMetadata {\n  const segments: PageSegment[] = []\n\n  if (data.segmentTrie) {\n    // Traverse the trie and collect all segments\n    function traverseTrie(node: SegmentTrieNode): void {\n      if (node.value) {\n        segments.push({\n          type: node.value.type,\n          pagePath: node.value.pagePath,\n          boundaryType: node.value.boundaryType,\n        })\n      }\n\n      for (const childNode of Object.values(node.children)) {\n        if (childNode) {\n          traverseTrie(childNode)\n        }\n      }\n    }\n\n    traverseTrie(data.segmentTrie)\n  }\n\n  return {\n    segments,\n    routerType: data.routerType,\n  }\n}\n\ninterface FormattedSegment {\n  path: string\n  type: string\n  isBoundary: boolean\n  isBuiltin: boolean\n}\n\ninterface FormattedSession {\n  url: string\n  routerType: string\n  segments: FormattedSegment[]\n}\n\ninterface FormattedPageMetadataOutput {\n  sessions: FormattedSession[]\n}\n\nfunction formatPageMetadata(\n  sessionMetadata: Array<{ url: string; metadata: PageMetadata }>\n): FormattedPageMetadataOutput {\n  const sessions: FormattedSession[] = []\n\n  for (const { url, metadata } of sessionMetadata) {\n    let displayUrl = url\n    try {\n      const urlObj = new URL(url)\n      displayUrl = urlObj.pathname + urlObj.search + urlObj.hash\n    } catch {\n      // If URL parsing fails, use the original URL\n    }\n\n    // Ensure consistent output to avoid flaky tests\n    const sortedSegments = [...metadata.segments].sort((a, b) => {\n      const typeOrder = (segment: PageSegment): number => {\n        const type = segment.boundaryType || segment.type\n        if (type === 'layout') return 0\n        if (type.startsWith('boundary:')) return 1\n        if (type === 'page') return 2\n        return 3\n      }\n      const aOrder = typeOrder(a)\n      const bOrder = typeOrder(b)\n      if (aOrder !== bOrder) return aOrder - bOrder\n      return a.pagePath.localeCompare(b.pagePath)\n    })\n\n    const formattedSegments: FormattedSegment[] = []\n    for (const segment of sortedSegments) {\n      const path = segment.pagePath\n      const isBuiltin = path.startsWith('__next_builtin__')\n      const type = segment.boundaryType || segment.type\n      const isBoundary = type.startsWith('boundary:')\n\n      let displayPath = path\n        .replace(/@boundary$/, '')\n        .replace(/^__next_builtin__/, '')\n\n      if (!isBuiltin && !displayPath.startsWith('app/')) {\n        displayPath = `app/${displayPath}`\n      }\n\n      formattedSegments.push({\n        path: displayPath,\n        type,\n        isBoundary,\n        isBuiltin,\n      })\n    }\n\n    sessions.push({\n      url: displayUrl,\n      routerType: metadata.routerType,\n      segments: formattedSegments,\n    })\n  }\n\n  return { sessions }\n}\n"],"names":["HMR_MESSAGE_SENT_TO_BROWSER","createBrowserRequest","handleBrowserPageResponse","DEFAULT_BROWSER_REQUEST_TIMEOUT_MS","mcpTelemetryTracker","registerGetPageMetadataTool","server","sendHmrMessage","getActiveConnectionCount","registerTool","description","inputSchema","_request","recordToolCall","connectionCount","content","type","text","JSON","stringify","error","responses","REQUEST_PAGE_METADATA","length","sessions","sessionMetadata","response","data","pageMetadata","convertSegmentTrieToPageMetadata","push","url","metadata","output","formatPageMetadata","Error","message","String","handlePageMetadataResponse","requestId","segmentTrieData","segments","segmentTrie","traverseTrie","node","value","pagePath","boundaryType","childNode","Object","values","children","routerType","displayUrl","urlObj","URL","pathname","search","hash","sortedSegments","sort","a","b","typeOrder","segment","startsWith","aOrder","bOrder","localeCompare","formattedSegments","path","isBuiltin","isBoundary","displayPath","replace"],"mappings":"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","ignoreList":[0]}