diff --git a/packages/angular/cli/src/commands/mcp/tools/projects.ts b/packages/angular/cli/src/commands/mcp/tools/projects.ts index bba995a4fd2a..c6fb60417fa5 100644 --- a/packages/angular/cli/src/commands/mcp/tools/projects.ts +++ b/packages/angular/cli/src/commands/mcp/tools/projects.ts @@ -34,6 +34,10 @@ const listProjectsOutputSchema = { .enum(['application', 'library']) .optional() .describe(`The type of the project, either 'application' or 'library'.`), + builder: z + .string() + .optional() + .describe('The primary builder for the project, typically from the "build" target.'), root: z .string() .describe('The root directory of the project, relative to the workspace root.'), @@ -90,6 +94,7 @@ their types, and their locations. * Determining if a project is an \`application\` or a \`library\`. * Getting the \`selectorPrefix\` for a project before generating a new component to ensure it follows conventions. * Identifying the major version of the Angular framework for each workspace, which is crucial for monorepos. +* Determining a project's primary function by inspecting its builder (e.g., '@angular-devkit/build-angular:browser' for an application). * **Working Directory:** Shell commands for a project (like \`ng generate\`) **MUST** @@ -253,6 +258,7 @@ async function loadAndParseWorkspace( projects.push({ name, type: project.extensions['projectType'] as 'application' | 'library' | undefined, + builder: project.targets.get('build')?.builder, root: project.root, sourceRoot: project.sourceRoot ?? path.posix.join(project.root, 'src'), selectorPrefix: project.extensions['prefix'] as string, @@ -272,6 +278,56 @@ async function loadAndParseWorkspace( } } +// Types for the structured output of the helper function. +type VersioningError = z.infer[number]; + +/** + * Processes a single `angular.json` file to extract workspace and framework version information. + * @param configFile The path to the `angular.json` file. + * @param searchRoot The directory at which to stop the upward search for `package.json`. + * @param seenPaths A Set of absolute paths that have already been processed to avoid duplicates. + * @param versionCache A Map to cache framework version lookups for performance. + * @returns A promise resolving to an object containing the processed data and any errors. + */ +async function processConfigFile( + configFile: string, + searchRoot: string, + seenPaths: Set, + versionCache: Map, +): Promise<{ + workspace?: WorkspaceData; + parsingError?: ParsingError; + versioningError?: VersioningError; +}> { + const { workspace, error } = await loadAndParseWorkspace(configFile, seenPaths); + if (error) { + return { parsingError: error }; + } + + if (!workspace) { + return {}; // Skipped as it was already seen. + } + + try { + const workspaceDir = path.dirname(configFile); + workspace.frameworkVersion = await findAngularCoreVersion( + workspaceDir, + versionCache, + searchRoot, + ); + + return { workspace }; + } catch (e) { + return { + workspace, + versioningError: { + filePath: workspace.path, + message: e instanceof Error ? e.message : 'An unknown error occurred.', + }, + }; + } +} + async function createListProjectsHandler({ server }: McpToolContext) { return async () => { const workspaces: WorkspaceData[] = []; @@ -292,27 +348,22 @@ async function createListProjectsHandler({ server }: McpToolContext) { for (const root of searchRoots) { for await (const configFile of findAngularJsonFiles(root)) { - const { workspace, error } = await loadAndParseWorkspace(configFile, seenPaths); - if (error) { - parsingErrors.push(error); - } + const { workspace, parsingError, versioningError } = await processConfigFile( + configFile, + root, + seenPaths, + versionCache, + ); if (workspace) { - try { - const workspaceDir = path.dirname(configFile); - workspace.frameworkVersion = await findAngularCoreVersion( - workspaceDir, - versionCache, - root, - ); - } catch (e) { - versioningErrors.push({ - filePath: workspace.path, - message: e instanceof Error ? e.message : 'An unknown error occurred.', - }); - } workspaces.push(workspace); } + if (parsingError) { + parsingErrors.push(parsingError); + } + if (versioningError) { + versioningErrors.push(versioningError); + } } }