66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9+ import { glob } from 'node:fs/promises' ;
910import path from 'node:path' ;
1011import z from 'zod' ;
11- import { McpToolContext , declareTool } from './tool-registry' ;
12+ import { AngularWorkspace } from '../../../utilities/config' ;
13+ import { declareTool } from './tool-registry' ;
1214
1315export const LIST_PROJECTS_TOOL = declareTool ( {
1416 name : 'list_projects' ,
1517 title : 'List Angular Projects' ,
16- description :
17- 'Lists the names of all applications and libraries defined within an Angular workspace. ' +
18- 'It reads the `angular.json` configuration file to identify the projects. ' ,
18+ description : `
19+ <Purpose>
20+ Provides a comprehensive overview of all Angular workspaces and projects within a monorepo.
21+ It is essential to use this tool as a first step before performing any project-specific actions to understand the available projects,
22+ their types, and their locations.
23+ </Purpose>
24+ <Use Cases>
25+ * Finding the correct project name to use in other commands (e.g., \`ng generate component my-comp --project=my-app\`).
26+ * Identifying the \`root\` and \`sourceRoot\` of a project to read, analyze, or modify its files.
27+ * Determining if a project is an \`application\` or a \`library\`.
28+ * Getting the \`selectorPrefix\` for a project before generating a new component to ensure it follows conventions.
29+ </Use Cases>
30+ <Operational Notes>
31+ * **Working Directory:** Shell commands for a project (like \`ng generate\`) **MUST**
32+ be executed from the parent directory of the \`path\` field for the relevant workspace.
33+ * **Disambiguation:** A monorepo may contain multiple workspaces (e.g., for different applications or even in output directories).
34+ Use the \`path\` of each workspace to understand its context and choose the correct project.
35+ </Operational Notes>` ,
1936 outputSchema : {
20- projects : z . array (
37+ workspaces : z . array (
2138 z . object ( {
22- name : z
23- . string ( )
24- . describe ( 'The name of the project, as defined in the `angular.json` file.' ) ,
25- type : z
26- . enum ( [ 'application' , 'library' ] )
27- . optional ( )
28- . describe ( `The type of the project, either 'application' or 'library'.` ) ,
29- root : z
30- . string ( )
31- . describe ( 'The root directory of the project, relative to the workspace root.' ) ,
32- sourceRoot : z
33- . string ( )
34- . describe (
35- `The root directory of the project's source files, relative to the workspace root.` ,
36- ) ,
37- selectorPrefix : z
38- . string ( )
39- . optional ( )
40- . describe (
41- 'The prefix to use for component selectors.' +
42- ` For example, a prefix of 'app' would result in selectors like '<app-my-component>'.` ,
43- ) ,
39+ path : z . string ( ) . describe ( 'The path to the `angular.json` file for this workspace.' ) ,
40+ projects : z . array (
41+ z . object ( {
42+ name : z
43+ . string ( )
44+ . describe ( 'The name of the project, as defined in the `angular.json` file.' ) ,
45+ type : z
46+ . enum ( [ 'application' , 'library' ] )
47+ . optional ( )
48+ . describe ( `The type of the project, either 'application' or 'library'.` ) ,
49+ root : z
50+ . string ( )
51+ . describe ( 'The root directory of the project, relative to the workspace root.' ) ,
52+ sourceRoot : z
53+ . string ( )
54+ . describe (
55+ `The root directory of the project's source files, relative to the workspace root.` ,
56+ ) ,
57+ selectorPrefix : z
58+ . string ( )
59+ . optional ( )
60+ . describe (
61+ 'The prefix to use for component selectors.' +
62+ ` For example, a prefix of 'app' would result in selectors like '<app-my-component>'.` ,
63+ ) ,
64+ } ) ,
65+ ) ,
4466 } ) ,
4567 ) ,
4668 } ,
4769 isReadOnly : true ,
4870 isLocalOnly : true ,
49- shouldRegister : ( context ) => ! ! context . workspace ,
5071 factory : createListProjectsHandler ,
5172} ) ;
5273
53- function createListProjectsHandler ( { workspace } : McpToolContext ) {
74+ async function createListProjectsHandler ( ) {
5475 return async ( ) => {
55- if ( ! workspace ) {
76+ const workspaces = [ ] ;
77+ const seenPaths = new Set < string > ( ) ;
78+ for await ( const configFile of glob ( '**/angular.json' , { exclude : [ '**/node_modules/**' ] } ) ) {
79+ // A workspace may be found multiple times in a monorepo
80+ const resolvedPath = path . resolve ( configFile ) ;
81+ if ( seenPaths . has ( resolvedPath ) ) {
82+ continue ;
83+ }
84+ seenPaths . add ( resolvedPath ) ;
85+
86+ const ws = await AngularWorkspace . load ( configFile ) ;
87+
88+ const projects = [ ] ;
89+ for ( const [ name , project ] of ws . projects . entries ( ) ) {
90+ projects . push ( {
91+ name,
92+ type : project . extensions [ 'projectType' ] as 'application' | 'library' | undefined ,
93+ root : project . root ,
94+ sourceRoot : project . sourceRoot ?? path . posix . join ( project . root , 'src' ) ,
95+ selectorPrefix : project . extensions [ 'prefix' ] as string ,
96+ } ) ;
97+ }
98+
99+ workspaces . push ( {
100+ path : configFile ,
101+ projects,
102+ } ) ;
103+ }
104+
105+ if ( workspaces . length === 0 ) {
56106 return {
57107 content : [
58108 {
@@ -63,32 +113,20 @@ function createListProjectsHandler({ workspace }: McpToolContext) {
63113 ' could not be located in the current directory or any of its parent directories.' ,
64114 } ,
65115 ] ,
66- structuredContent : { projects : [ ] } ,
116+ structuredContent : { workspaces : [ ] } ,
67117 } ;
68118 }
69119
70- const projects = [ ] ;
71- // Convert to output format
72- for ( const [ name , project ] of workspace . projects . entries ( ) ) {
73- projects . push ( {
74- name,
75- type : project . extensions [ 'projectType' ] as 'application' | 'library' | undefined ,
76- root : project . root ,
77- sourceRoot : project . sourceRoot ?? path . posix . join ( project . root , 'src' ) ,
78- selectorPrefix : project . extensions [ 'prefix' ] as string ,
79- } ) ;
80- }
81-
82120 // The structuredContent field is newer and may not be supported by all hosts.
83121 // A text representation of the content is also provided for compatibility.
84122 return {
85123 content : [
86124 {
87125 type : 'text' as const ,
88- text : `Projects in the Angular workspace:\n${ JSON . stringify ( projects ) } ` ,
126+ text : `Projects in the Angular workspace:\n${ JSON . stringify ( { workspaces } ) } ` ,
89127 } ,
90128 ] ,
91- structuredContent : { projects } ,
129+ structuredContent : { workspaces } ,
92130 } ;
93131 } ;
94132}
0 commit comments