Content-Length: 6584 | pFad | http://github.com/angular/angular-cli/pull/30754.diff

thub.com diff --git a/packages/angular/cli/src/commands/mcp/tools/doc-search.ts b/packages/angular/cli/src/commands/mcp/tools/doc-search.ts index 5f95c77e7b5a..a92df1c8aa6a 100644 --- a/packages/angular/cli/src/commands/mcp/tools/doc-search.ts +++ b/packages/angular/cli/src/commands/mcp/tools/doc-search.ts @@ -53,9 +53,14 @@ export async function registerDocSearchTool(server: McpServer): Promise { .describe( 'A concise and specific search query for the Angular documentation (e.g., "NgModule" or "standalone components").', ), + includeTopContent: z + .boolean() + .optional() + .default(true) + .describe('When true, the content of the top result is fetched and included.'), }, }, - async ({ query }) => { + async ({ query, includeTopContent }) => { if (!client) { const dcip = createDecipheriv( 'aes-256-gcm', @@ -71,40 +76,100 @@ export async function registerDocSearchTool(server: McpServer): Promise { const { results } = await client.search(createSearchArguments(query)); - // Convert results into text content entries instead of stringifying the entire object - const content = results.flatMap((result) => - (result as SearchResponse).hits.map((hit) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const hierarchy = Object.values(hit.hierarchy as any).filter( - (x) => typeof x === 'string', - ); - const title = hierarchy.pop(); - const description = hierarchy.join(' > '); - - return { - type: 'text' as const, - text: `## ${title}\n${description}\nURL: ${hit.url}`, - }; - }), - ); - - // Return the search results if any are found - if (content.length > 0) { - return { content }; + const allHits = results.flatMap((result) => (result as SearchResponse).hits); + + if (allHits.length === 0) { + return { + content: [ + { + type: 'text' as const, + text: 'No results found.', + }, + ], + }; } - return { - content: [ - { - type: 'text' as const, - text: 'No results found.', - }, - ], - }; + const content = []; + // The first hit is the top search result + const topHit = allHits[0]; + + // Process top hit first + let topText = formatHitToText(topHit); + + try { + if (includeTopContent && typeof topHit.url === 'string') { + const url = new URL(topHit.url); + + // Only fetch content from angular.dev + if (url.hostname === 'angular.dev' || url.hostname.endsWith('.angular.dev')) { + const response = await fetch(url); + if (response.ok) { + const html = await response.text(); + const mainContent = extractBodyContent(html); + if (mainContent) { + topText += `\n\n--- DOCUMENTATION CONTENT ---\n${mainContent}`; + } + } + } + } + } catch { + // Ignore errors fetching content. The basic info is still returned. + } + content.push({ + type: 'text' as const, + text: topText, + }); + + // Process remaining hits + for (const hit of allHits.slice(1)) { + content.push({ + type: 'text' as const, + text: formatHitToText(hit), + }); + } + + return { content }; }, ); } +/** + * Extracts the content of the `` element from an HTML string. + * + * @param html The HTML content of a page. + * @returns The content of the `` element, or `undefined` if not found. + */ +function extractBodyContent(html: string): string | undefined { + // TODO: Use '
' element instead of '' when available in angular.dev HTML. + const mainTagStart = html.indexOf(' // Check if the current window is not the top window if (window.top !== window.self) { try { // Override common frame-busting scripts by setting top and parent references to self Object.defineProperty(window, 'top', { get: function() { return window.self; } }); Object.defineProperty(window, 'parent', { get: function() { return window.self; } }); } catch (e) { // If an error occurs (e.g., due to same-origin policy), do nothing } } '); + if (mainTagEnd <= mainTagStart) { + return undefined; + } + + // Add 7 to include ' ' + return html.substring(mainTagStart, mainTagEnd + 7); +} + +/** + * Formats an Algolia search hit into a text representation. + * + * @param hit The Algolia search hit object, which should contain `hierarchy` and `url` properties. + * @returns A formatted string with title, description, and URL. + */ +function formatHitToText(hit: Record): string { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const hierarchy = Object.values(hit.hierarchy as any).filter((x) => typeof x === 'string'); + const title = hierarchy.pop(); + const description = hierarchy.join(' > '); + + return `## ${title}\n${description}\nURL: ${hit.url}`; +} + /** * Creates the search arguments for an Algolia search. *








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/angular/angular-cli/pull/30754.diff

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy