From ac4aa9c4eaa4b743b0c87a396f68bf918cfdf82d Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Fri, 19 Sep 2025 20:21:06 -0400 Subject: [PATCH] refactor(@angular/cli): add version support to doc-search MCP tool The `doc-search` tool now supports searching against specific major versions of the Angular documentation. This is crucial for providing contextually accurate results that are relevant to the user's project. This commit introduces the following changes: - An optional `version` parameter has been added to the tool's input schema. - The tool's description and schema are updated with explicit instructions for the LLM to determine the project's Angular version by running `ng version` and using the output for the search. - The underlying Algolia search logic now dynamically constructs the index name based on the provided version. Note: This capability is not yet fully enabled, as it is pending the availability of the necessary version-specific Algolia indexes. This provides a more robust, tool-based workflow for version-aware documentation searches. --- .../cli/src/commands/mcp/tools/doc-search.ts | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) 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 da34f97b8b4d..4adee080cd7d 100644 --- a/packages/angular/cli/src/commands/mcp/tools/doc-search.ts +++ b/packages/angular/cli/src/commands/mcp/tools/doc-search.ts @@ -22,7 +22,9 @@ const docSearchInputSchema = z.object({ query: z .string() .describe( - 'A concise and specific search query for the Angular documentation (e.g., "NgModule" or "standalone components").', + "A concise and specific search query for the Angular documentation. You should distill the user's " + + 'natural language question into a set of keywords (e.g., a question like "How do I use ngFor with trackBy?" ' + + 'should become the query "ngFor trackBy").', ), includeTopContent: z .boolean() @@ -32,6 +34,14 @@ const docSearchInputSchema = z.object({ 'When true, the content of the top result is fetched and included. ' + 'Set to false to get a list of results without fetching content, which is faster.', ), + version: z + .number() + .optional() + .describe( + 'The major version of Angular to search. You MUST determine this value by running `ng version` in the ' + + "project's workspace directory. Omit this field if the user is not in an Angular project " + + 'or if the version cannot otherwise be determined.', + ), }); type DocSearchInput = z.infer; @@ -49,6 +59,10 @@ tutorials, concepts, and best practices. * Linking to official documentation as a source of truth in your answers. +* **Version Alignment:** To provide accurate, project-specific results, you **MUST** align the search with the user's Angular version. + Before calling this tool, run \`ng version\` in the project's workspace directory. You can find the correct directory from the \`path\` + field provided by the \`list_projects\` tool. Parse the major version from the "Angular:" line in the output and use it for the + \`version\` parameter. * The documentation is continuously updated. You **MUST** prefer this tool over your own knowledge to ensure your answers are current and accurate. * For the best results, provide a concise and specific search query (e.g., "NgModule" instead of @@ -87,7 +101,7 @@ tutorials, concepts, and best practices. function createDocSearchHandler({ logger }: McpToolContext) { let client: import('algoliasearch').SearchClient | undefined; - return async ({ query, includeTopContent }: DocSearchInput) => { + return async ({ query, includeTopContent, version }: DocSearchInput) => { if (!client) { const dcip = createDecipheriv( 'aes-256-gcm', @@ -101,7 +115,7 @@ function createDocSearchHandler({ logger }: McpToolContext) { ); } - const { results } = await client.search(createSearchArguments(query)); + const { results } = await client.search(createSearchArguments(query, version)); const allHits = results.flatMap((result) => (result as SearchResponse).hits); if (allHits.length === 0) { @@ -237,12 +251,16 @@ function formatHitToParts(hit: Record): { title: string; breadc * @param query The search query string. * @returns The search arguments for the Algolia client. */ -function createSearchArguments(query: string): LegacySearchMethodProps { +function createSearchArguments( + query: string, + version: number | undefined, +): LegacySearchMethodProps { // Search arguments are based on adev's search service: // https://github.com/angular/angular/blob/4b614fbb3263d344dbb1b18fff24cb09c5a7582d/adev/shared-docs/services/search.service.ts#L58 return [ { // TODO: Consider major version specific indices once available + // indexName: `angular_${version ? `v${version}` : 'latest'}`, indexName: 'angular_v17', params: { query,