From 24a695cbc697518ae749f0966f40d6d55b9a085e Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:03:26 -0400 Subject: [PATCH 1/2] refactor(@angular/cli): expand MCP tool/resource descriptions The descriptions for both the initial best practices resource and list projects tool have been expanded to provide more context regarding their use and results. --- .../cli/src/commands/mcp/mcp-server.ts | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/angular/cli/src/commands/mcp/mcp-server.ts b/packages/angular/cli/src/commands/mcp/mcp-server.ts index 33df33f71ba9..1339e8f2c0d9 100644 --- a/packages/angular/cli/src/commands/mcp/mcp-server.ts +++ b/packages/angular/cli/src/commands/mcp/mcp-server.ts @@ -28,9 +28,12 @@ export async function createMcpServer(context: { 'instructions', 'instructions://best-practices', { - title: 'Angular System Instructions', + title: 'Angular Best Practices and Code Generation Guide', description: - 'A set of instructions to help LLMs generate correct code that follows Angular best practices.', + "A comprehensive guide detailing Angular's best practices for code generation and development. " + + 'This guide should be used as a reference by an LLM to ensure any generated code ' + + 'adheres to modern Angular standards, including the use of standalone components, ' + + 'typed forms, modern control flow syntax, and other current conventions.', mimeType: 'text/markdown', }, async () => { @@ -46,18 +49,26 @@ export async function createMcpServer(context: { server.registerTool( 'list_projects', { - title: 'List projects', + title: 'List Angular Projects', description: - 'List projects within an Angular workspace.' + - ' This information is read from the `angular.json` file at the root path of the Angular workspace', + 'Lists the names of all applications and libraries defined within an Angular workspace. ' + + 'It reads the `angular.json` configuration file to identify the projects. ', + annotations: { + readOnlyHint: true, + }, }, - () => { - if (!context.workspace) { + async () => { + const { workspace } = context; + + if (!workspace) { return { content: [ { - type: 'text', - text: 'Not within an Angular project.', + type: 'text' as const, + text: + 'No Angular workspace found.' + + ' An `angular.json` file, which marks the root of a workspace,' + + ' could not be located in the current directory or any of its parent directories.', }, ], }; @@ -66,10 +77,8 @@ export async function createMcpServer(context: { return { content: [ { - type: 'text', - text: - 'Projects in the Angular workspace: ' + - [...context.workspace.projects.keys()].join(','), + type: 'text' as const, + text: 'Projects in the Angular workspace: ' + [...workspace.projects.keys()].join(','), }, ], }; From 72b3e28a918c7bd4282cbfcf88d8329c5a8f7182 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Mon, 7 Jul 2025 09:24:36 -0400 Subject: [PATCH 2/2] refactor(@angular/cli): provide additional project details for MCP list projects tool The Angular CLI's MCP server will now provide additional information regarding each project when the list projects tool is used. This includes: * name * type (application/library) * root (project root path relative to the Angular workspace root) * sourceRoot (source code root path relative to the Angular workspace root) * selectorPrefix (default selector prefix used for component generation) --- packages/angular/cli/BUILD.bazel | 1 + packages/angular/cli/package.json | 3 +- .../cli/src/commands/mcp/mcp-server.ts | 54 +++++++++++++++++-- pnpm-lock.yaml | 23 ++++---- 4 files changed, 65 insertions(+), 16 deletions(-) diff --git a/packages/angular/cli/BUILD.bazel b/packages/angular/cli/BUILD.bazel index 41201feb3c1f..a25061997acf 100644 --- a/packages/angular/cli/BUILD.bazel +++ b/packages/angular/cli/BUILD.bazel @@ -58,6 +58,7 @@ ts_project( ":node_modules/pacote", ":node_modules/resolve", ":node_modules/yargs", + ":node_modules/zod", "//:node_modules/@angular/core", "//:node_modules/@types/ini", "//:node_modules/@types/node", diff --git a/packages/angular/cli/package.json b/packages/angular/cli/package.json index 950db34a956e..6b58a139b964 100644 --- a/packages/angular/cli/package.json +++ b/packages/angular/cli/package.json @@ -38,7 +38,8 @@ "pacote": "21.0.0", "resolve": "1.22.10", "semver": "7.7.2", - "yargs": "18.0.0" + "yargs": "18.0.0", + "zod": "3.25.75" }, "ng-update": { "migrations": "@schematics/angular/migrations/migration-collection.json", diff --git a/packages/angular/cli/src/commands/mcp/mcp-server.ts b/packages/angular/cli/src/commands/mcp/mcp-server.ts index 1339e8f2c0d9..81a11ac6c94a 100644 --- a/packages/angular/cli/src/commands/mcp/mcp-server.ts +++ b/packages/angular/cli/src/commands/mcp/mcp-server.ts @@ -9,6 +9,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { readFile } from 'node:fs/promises'; import path from 'node:path'; +import { z } from 'zod'; import type { AngularWorkspace } from '../../utilities/config'; import { VERSION } from '../../utilities/version'; @@ -30,10 +31,10 @@ export async function createMcpServer(context: { { title: 'Angular Best Practices and Code Generation Guide', description: - "A comprehensive guide detailing Angular's best practices for code generation and development. " + - 'This guide should be used as a reference by an LLM to ensure any generated code ' + - 'adheres to modern Angular standards, including the use of standalone components, ' + - 'typed forms, modern control flow syntax, and other current conventions.', + "A comprehensive guide detailing Angular's best practices for code generation and development." + + ' This guide should be used as a reference by an LLM to ensure any generated code' + + ' adheres to modern Angular standards, including the use of standalone components,' + + ' typed forms, modern control flow syntax, and other current conventions.', mimeType: 'text/markdown', }, async () => { @@ -56,6 +57,34 @@ export async function createMcpServer(context: { annotations: { readOnlyHint: true, }, + outputSchema: { + projects: z.array( + z.object({ + name: z + .string() + .describe('The name of the project, as defined in the `angular.json` file.'), + type: z + .enum(['application', 'library']) + .optional() + .describe(`The type of the project, either 'application' or 'library'.`), + root: z + .string() + .describe('The root directory of the project, relative to the workspace root.'), + sourceRoot: z + .string() + .describe( + `The root directory of the project's source files, relative to the workspace root.`, + ), + selectorPrefix: z + .string() + .optional() + .describe( + 'The prefix to use for component selectors.' + + ` For example, a prefix of 'app' would result in selectors like ''.`, + ), + }), + ), + }, }, async () => { const { workspace } = context; @@ -74,13 +103,28 @@ export async function createMcpServer(context: { }; } + const projects = []; + // Convert to output format + for (const [name, project] of workspace.projects.entries()) { + projects.push({ + name, + type: project.extensions['projectType'] as 'application' | 'library' | undefined, + root: project.root, + sourceRoot: project.sourceRoot ?? path.posix.join(project.root, 'src'), + selectorPrefix: project.extensions['prefix'] as string, + }); + } + + // The structuredContent field is newer and may not be supported by all hosts. + // A text representation of the content is also provided for compatibility. return { content: [ { type: 'text' as const, - text: 'Projects in the Angular workspace: ' + [...workspace.projects.keys()].join(','), + text: `Projects in the Angular workspace:\n${JSON.stringify(projects)}`, }, ], + structuredContent: { projects }, }; }, ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 494137b8d861..a5a00e7b18fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -507,6 +507,9 @@ importers: yargs: specifier: 18.0.0 version: 18.0.0 + zod: + specifier: 3.25.75 + version: 3.25.75 packages/angular/pwa: dependencies: @@ -8334,8 +8337,8 @@ packages: peerDependencies: zod: ^3.24.1 - zod@3.25.67: - resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} + zod@3.25.75: + resolution: {integrity: sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg==} zone.js@0.15.1: resolution: {integrity: sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==} @@ -9433,8 +9436,8 @@ snapshots: dependencies: google-auth-library: 9.15.1(encoding@0.1.13)(supports-color@10.0.0) ws: 8.18.2 - zod: 3.25.67 - zod-to-json-schema: 3.24.5(zod@3.25.67) + zod: 3.25.75 + zod-to-json-schema: 3.24.5(zod@3.25.75) optionalDependencies: '@modelcontextprotocol/sdk': 1.13.3 transitivePeerDependencies: @@ -9692,8 +9695,8 @@ snapshots: express-rate-limit: 7.5.0(express@5.1.0) pkce-challenge: 5.0.0 raw-body: 3.0.0 - zod: 3.25.67 - zod-to-json-schema: 3.24.5(zod@3.25.67) + zod: 3.25.75 + zod-to-json-schema: 3.24.5(zod@3.25.75) transitivePeerDependencies: - supports-color @@ -11738,7 +11741,7 @@ snapshots: dependencies: devtools-protocol: 0.0.1452169 mitt: 3.0.1 - zod: 3.25.67 + zod: 3.25.75 cli-cursor@3.1.0: dependencies: @@ -16880,10 +16883,10 @@ snapshots: yoctocolors-cjs@2.1.2: {} - zod-to-json-schema@3.24.5(zod@3.25.67): + zod-to-json-schema@3.24.5(zod@3.25.75): dependencies: - zod: 3.25.67 + zod: 3.25.75 - zod@3.25.67: {} + zod@3.25.75: {} zone.js@0.15.1: {}