From 9a2b642f98f1c32ab7b234802fe3fe7a77d5983b Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 29 Jul 2025 10:42:28 -0400 Subject: [PATCH 1/6] refactor(@angular/cli): add instructional text to MCP server Adds instructional text to the MCP server to guide users on its intended purpose. The instructions clarify that the server and its associated tools should be prioritized over direct shell commands for Angular development tasks, promoting adherence to best practices and efficient use of provided tooling. (cherry picked from commit ecea1e02fdd84e5bfeb18b246c58d4dbaec5694f) --- packages/angular/cli/src/commands/mcp/mcp-server.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/angular/cli/src/commands/mcp/mcp-server.ts b/packages/angular/cli/src/commands/mcp/mcp-server.ts index c9754e49e190..86338d1e26e6 100644 --- a/packages/angular/cli/src/commands/mcp/mcp-server.ts +++ b/packages/angular/cli/src/commands/mcp/mcp-server.ts @@ -25,6 +25,9 @@ export async function createMcpServer(context: { resources: {}, tools: {}, }, + instructions: + 'For Angular development, this server provides tools to adhere to best practices, search documentation, and find code examples. ' + + 'When writing or modifying Angular code, use the MCP server and its tools instead of direct shell commands where possible.', }); server.registerResource( From 2ed9c37466c07983e7a1aae31ef3b3910c55badc Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Wed, 30 Jul 2025 10:04:54 -0400 Subject: [PATCH 2/6] test(@angular/cli): add initial e2e test for MCP server tool registration Introduces the first end-to-end test for the MCP server. The new test, `registers-tools.ts`, verifies that the MCP server correctly initializes and registers its default set of tools. This provides a foundation for more comprehensive e2e testing of the MCP server and its features. (cherry picked from commit dd9fb2786511dabd40016df9dcb19f337a069f2a) --- .../cli/src/commands/mcp/mcp-server.ts | 3 +- .../e2e/tests/mcp/registers-tools.ts | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 tests/legacy-cli/e2e/tests/mcp/registers-tools.ts diff --git a/packages/angular/cli/src/commands/mcp/mcp-server.ts b/packages/angular/cli/src/commands/mcp/mcp-server.ts index 86338d1e26e6..214a0775187c 100644 --- a/packages/angular/cli/src/commands/mcp/mcp-server.ts +++ b/packages/angular/cli/src/commands/mcp/mcp-server.ts @@ -55,8 +55,7 @@ export async function createMcpServer(context: { registerBestPracticesTool(server); // If run outside an Angular workspace (e.g., globally) skip the workspace specific tools. - // Currently only the `list_projects` tool. - if (!context.workspace) { + if (context.workspace) { registerListProjectsTool(server, context); } diff --git a/tests/legacy-cli/e2e/tests/mcp/registers-tools.ts b/tests/legacy-cli/e2e/tests/mcp/registers-tools.ts new file mode 100644 index 000000000000..7943cb23bdab --- /dev/null +++ b/tests/legacy-cli/e2e/tests/mcp/registers-tools.ts @@ -0,0 +1,47 @@ +import { chdir } from 'process'; +import { exec, ProcessOutput, silentNpm } from '../../utils/process'; +import assert from 'node:assert/strict'; + +const MCP_INSPECTOR_PACKAGE_NAME = '@modelcontextprotocol/inspector-cli'; +const MCP_INSPECTOR_PACKAGE_VERSION = '0.16.2'; +const MCP_INSPECTOR_COMMAND_NAME = 'mcp-inspector-cli'; + +async function runInspector(...args: string[]): Promise { + const result = await exec( + MCP_INSPECTOR_COMMAND_NAME, + '--cli', + 'npx', + '--no', + '@angular/cli', + 'mcp', + ...args, + ); + + return result; +} + +export default async function () { + await silentNpm( + 'install', + '--ignore-scripts', + '-g', + `${MCP_INSPECTOR_PACKAGE_NAME}@${MCP_INSPECTOR_PACKAGE_VERSION}`, + ); + + // Ensure 'list_projects' is registered when inside an Angular workspace + const { stdout: stdoutInsideWorkspace } = await runInspector('--method', 'tools/list'); + + assert.match(stdoutInsideWorkspace, /"list_projects"/); + assert.match(stdoutInsideWorkspace, /"get_best_practices"/); + assert.match(stdoutInsideWorkspace, /"search_documentation"/); + + chdir('..'); + + const { stdout: stdoutOutsideWorkspace } = await runInspector('--method', 'tools/list'); + + assert.doesNotMatch(stdoutOutsideWorkspace, /"list_projects"/); + assert.match(stdoutOutsideWorkspace, /"get_best_practices"/); + assert.match(stdoutInsideWorkspace, /"search_documentation"/); + + silentNpm('uninstall', '-g', MCP_INSPECTOR_PACKAGE_NAME); +} From 8601f0648ae1469b791d6a51f9de61050e4b5a35 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 31 Jul 2025 16:45:48 -0400 Subject: [PATCH 3/6] refactor(@angular/cli): update suggested MCP server configuration output The suggested MCP server command options now use the `-y` option for `npx`. This provides better supported for global usage of the Angular CLI MCP server in addition to the workspace usage. (cherry picked from commit 8b4de57af14ffd35622ea6a4e15871bcf424098f) --- packages/angular/cli/src/commands/mcp/cli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/angular/cli/src/commands/mcp/cli.ts b/packages/angular/cli/src/commands/mcp/cli.ts index 81260f09f6b6..5b1107eeb73c 100644 --- a/packages/angular/cli/src/commands/mcp/cli.ts +++ b/packages/angular/cli/src/commands/mcp/cli.ts @@ -19,7 +19,7 @@ To start using the Angular CLI MCP Server, add this configuration to your host: "mcpServers": { "angular-cli": { "command": "npx", - "args": ["@angular/cli", "mcp"] + "args": ["-y", "@angular/cli", "mcp"] } } } From 48ca044745f49bc7fc365a621827294f4cc82c50 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Wed, 6 Aug 2025 09:58:52 -0400 Subject: [PATCH 4/6] fix(@angular/cli): cache MCP best practices content and add tool annotations Caches the content of the best practices instructions to avoid redundant file reads on subsequent uses of the tool. This can provide a minor performance improvement in cases where the tool is used multiple times during a single CLI process. Also, annotations have been added to the text to provide additional context for the assistant and allow for more tailored display of the information. (cherry picked from commit 51d56f770714a015aa7621d53c4a1634e8a01cc8) --- .../cli/src/commands/mcp/tools/best-practices.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/angular/cli/src/commands/mcp/tools/best-practices.ts b/packages/angular/cli/src/commands/mcp/tools/best-practices.ts index c6718a91e3ec..e8139ac3b794 100644 --- a/packages/angular/cli/src/commands/mcp/tools/best-practices.ts +++ b/packages/angular/cli/src/commands/mcp/tools/best-practices.ts @@ -11,6 +11,8 @@ import { readFile } from 'node:fs/promises'; import path from 'node:path'; export function registerBestPracticesTool(server: McpServer): void { + let bestPracticesText; + server.registerTool( 'get_best_practices', { @@ -27,7 +29,7 @@ export function registerBestPracticesTool(server: McpServer): void { }, }, async () => { - const text = await readFile( + bestPracticesText ??= await readFile( path.join(__dirname, '..', 'instructions', 'best-practices.md'), 'utf-8', ); @@ -36,7 +38,11 @@ export function registerBestPracticesTool(server: McpServer): void { content: [ { type: 'text', - text, + text: bestPracticesText, + annotations: { + audience: ['assistant'], + priority: 0.9, + }, }, ], }; From 761bc780829c0f5b6a79bac42fce5e964a2c5f55 Mon Sep 17 00:00:00 2001 From: Joey Perrott Date: Tue, 5 Aug 2025 19:01:20 +0000 Subject: [PATCH 5/6] build: update to latest dev-infra in the workspace Update to latest dev-infra in the workspace and rules_browsers browser toolchains --- WORKSPACE | 20 ++++++++++++++----- packages/angular/build/BUILD.bazel | 2 +- packages/angular/ssr/BUILD.bazel | 2 +- .../ssr/third_party/beasties/BUILD.bazel | 1 - packages/angular_devkit/architect/BUILD.bazel | 2 +- .../angular_devkit/build_angular/BUILD.bazel | 2 +- .../angular_devkit/build_webpack/BUILD.bazel | 2 +- packages/angular_devkit/core/BUILD.bazel | 2 +- .../angular_devkit/schematics/BUILD.bazel | 2 +- packages/ngtools/webpack/BUILD.bazel | 2 +- tests/legacy-cli/e2e.bzl | 8 ++++---- .../e2e/tests/mcp/registers-tools.ts | 2 +- 12 files changed, 28 insertions(+), 19 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index d6f15616179e..67bc38b6aed7 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -230,7 +230,7 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") git_repository( name = "devinfra", - commit = "dfe138678e4edb4789fbe40ae7792c046de3b4bd", + commit = "361ceb676a715e1aedf3d6cd64ecae5dee6a3e5f", remote = "https://github.com/angular/dev-infra.git", ) @@ -242,10 +242,6 @@ load("@devinfra//bazel:setup_dependencies_2.bzl", "setup_dependencies_2") setup_dependencies_2() -load("@devinfra//bazel/browsers:browser_repositories.bzl", "browser_repositories") - -browser_repositories() - register_toolchains( "@devinfra//bazel/git-toolchain:git_linux_toolchain", "@devinfra//bazel/git-toolchain:git_macos_x86_toolchain", @@ -298,3 +294,17 @@ http_archive( strip_prefix = "rules_rollup-2.0.1", url = "https://github.com/aspect-build/rules_rollup/releases/download/v2.0.1/rules_rollup-v2.0.1.tar.gz", ) + +git_repository( + name = "rules_browsers", + commit = "56ef8007ea07cd1916429bca8bb523433b0e9cdc", + remote = "https://github.com/devversion/rules_browsers.git", +) + +load("@rules_browsers//setup:step_1.bzl", "rules_browsers_setup_1") + +rules_browsers_setup_1() + +load("@rules_browsers//setup:step_2.bzl", "rules_browsers_setup_2") + +rules_browsers_setup_2() diff --git a/packages/angular/build/BUILD.bazel b/packages/angular/build/BUILD.bazel index 69d8ac3e2dd8..69f1cde4bab9 100644 --- a/packages/angular/build/BUILD.bazel +++ b/packages/angular/build/BUILD.bazel @@ -1,4 +1,4 @@ -load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index.bzl", "api_golden_test_npm_package") load("@npm//:defs.bzl", "npm_link_all_packages") load("//:constants.bzl", "BASELINE_DATE") load("//tools:defaults.bzl", "copy_to_bin", "jasmine_test", "npm_package", "ts_project") diff --git a/packages/angular/ssr/BUILD.bazel b/packages/angular/ssr/BUILD.bazel index 299f61401f7d..2694830e3ff4 100644 --- a/packages/angular/ssr/BUILD.bazel +++ b/packages/angular/ssr/BUILD.bazel @@ -1,4 +1,4 @@ -load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index.bzl", "api_golden_test_npm_package") load("@npm//:defs.bzl", "npm_link_all_packages") load("@rules_pkg//:pkg.bzl", "pkg_tar") load("//tools:defaults.bzl", "ng_package", "ts_project") diff --git a/packages/angular/ssr/third_party/beasties/BUILD.bazel b/packages/angular/ssr/third_party/beasties/BUILD.bazel index f570567cf596..c0b331caa798 100644 --- a/packages/angular/ssr/third_party/beasties/BUILD.bazel +++ b/packages/angular/ssr/third_party/beasties/BUILD.bazel @@ -44,5 +44,4 @@ rollup.rollup( "--dir=packages/angular/ssr/third_party/beasties", ], progress_message = "Bundling beasties", - silent_on_success = False, ) diff --git a/packages/angular_devkit/architect/BUILD.bazel b/packages/angular_devkit/architect/BUILD.bazel index f76e44f93f9d..1c550d124e84 100644 --- a/packages/angular_devkit/architect/BUILD.bazel +++ b/packages/angular_devkit/architect/BUILD.bazel @@ -3,7 +3,7 @@ # Use of this source code is governed by an MIT-style license that can be # found in the LICENSE file at https://angular.dev/license -load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index.bzl", "api_golden_test_npm_package") load("@npm//:defs.bzl", "npm_link_all_packages") load("//tools:defaults.bzl", "jasmine_test", "npm_package", "ts_project") load("//tools:ts_json_schema.bzl", "ts_json_schema") diff --git a/packages/angular_devkit/build_angular/BUILD.bazel b/packages/angular_devkit/build_angular/BUILD.bazel index c4c283676ec4..f7e0530a4105 100644 --- a/packages/angular_devkit/build_angular/BUILD.bazel +++ b/packages/angular_devkit/build_angular/BUILD.bazel @@ -3,7 +3,7 @@ # Use of this source code is governed by an MIT-style license that can be # found in the LICENSE file at https://angular.dev/license -load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index.bzl", "api_golden_test_npm_package") load("@npm//:defs.bzl", "npm_link_all_packages") load("//tools:defaults.bzl", "copy_to_bin", "jasmine_test", "npm_package", "ts_project") load("//tools:ts_json_schema.bzl", "ts_json_schema") diff --git a/packages/angular_devkit/build_webpack/BUILD.bazel b/packages/angular_devkit/build_webpack/BUILD.bazel index e01feffdd673..f66ea94f1919 100644 --- a/packages/angular_devkit/build_webpack/BUILD.bazel +++ b/packages/angular_devkit/build_webpack/BUILD.bazel @@ -3,7 +3,7 @@ # Use of this source code is governed by an MIT-style license that can be # found in the LICENSE file at https://angular.dev/license -load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index.bzl", "api_golden_test_npm_package") load("@npm//:defs.bzl", "npm_link_all_packages") load("//tools:defaults.bzl", "jasmine_test", "npm_package", "ts_project") load("//tools:ts_json_schema.bzl", "ts_json_schema") diff --git a/packages/angular_devkit/core/BUILD.bazel b/packages/angular_devkit/core/BUILD.bazel index 93892ecc3e2c..b59c5bd37987 100644 --- a/packages/angular_devkit/core/BUILD.bazel +++ b/packages/angular_devkit/core/BUILD.bazel @@ -1,4 +1,4 @@ -load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index.bzl", "api_golden_test_npm_package") load("@npm//:defs.bzl", "npm_link_all_packages") load("//tools:defaults.bzl", "jasmine_test", "npm_package", "ts_project") diff --git a/packages/angular_devkit/schematics/BUILD.bazel b/packages/angular_devkit/schematics/BUILD.bazel index 0b1d0e0f781b..e4c4a5d6bac4 100644 --- a/packages/angular_devkit/schematics/BUILD.bazel +++ b/packages/angular_devkit/schematics/BUILD.bazel @@ -1,4 +1,4 @@ -load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index.bzl", "api_golden_test_npm_package") load("@npm//:defs.bzl", "npm_link_all_packages") load("//tools:defaults.bzl", "jasmine_test", "npm_package", "ts_project") diff --git a/packages/ngtools/webpack/BUILD.bazel b/packages/ngtools/webpack/BUILD.bazel index 332e8a39cc1a..2dd79ca285e1 100644 --- a/packages/ngtools/webpack/BUILD.bazel +++ b/packages/ngtools/webpack/BUILD.bazel @@ -3,7 +3,7 @@ # Use of this source code is governed by an MIT-style license that can be # found in the LICENSE file at https://angular.dev/license -load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index.bzl", "api_golden_test_npm_package") load("@npm//:defs.bzl", "npm_link_all_packages") load("//tools:defaults.bzl", "jasmine_test", "npm_package", "ts_project") diff --git a/tests/legacy-cli/e2e.bzl b/tests/legacy-cli/e2e.bzl index 2152f6dcd229..e3fed2fc7de7 100644 --- a/tests/legacy-cli/e2e.bzl +++ b/tests/legacy-cli/e2e.bzl @@ -110,12 +110,12 @@ def _e2e_tests(name, runner, toolchain, **kwargs): # Chromium browser toolchain env.update({ - "CHROME_BIN": "$(CHROMIUM)", - "CHROME_PATH": "$(CHROMIUM)", + "CHROME_BIN": "$(CHROME-HEADLESS-SHELL)", + "CHROME_PATH": "$(CHROME-HEADLESS-SHELL)", "CHROMEDRIVER_BIN": "$(CHROMEDRIVER)", }) - toolchains = toolchains + ["@devinfra//bazel/browsers/chromium:toolchain_alias"] - data = data + ["@devinfra//bazel/browsers/chromium"] + toolchains = toolchains + ["@rules_browsers//src/browsers/chromium:toolchain_alias"] + data = data + ["@rules_browsers//src/browsers/chromium"] js_test( name = name, diff --git a/tests/legacy-cli/e2e/tests/mcp/registers-tools.ts b/tests/legacy-cli/e2e/tests/mcp/registers-tools.ts index 7943cb23bdab..e2c9461d6a26 100644 --- a/tests/legacy-cli/e2e/tests/mcp/registers-tools.ts +++ b/tests/legacy-cli/e2e/tests/mcp/registers-tools.ts @@ -1,4 +1,4 @@ -import { chdir } from 'process'; +import { chdir } from 'node:process'; import { exec, ProcessOutput, silentNpm } from '../../utils/process'; import assert from 'node:assert/strict'; From 2c0e973c6f01214b83391ffc13659744d4c9651f Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:26:35 -0400 Subject: [PATCH 6/6] release: cut the v20.1.5 release --- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce55e64c5a2f..01b9feda6743 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ + + +# 20.1.5 (2025-08-06) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [48ca04474](https://github.com/angular/angular-cli/commit/48ca044745f49bc7fc365a621827294f4cc82c50) | fix | cache MCP best practices content and add tool annotations | + + + # 20.1.4 (2025-07-30) diff --git a/package.json b/package.json index e0ae194329a8..c3fd53dc2d82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@angular/devkit-repo", - "version": "20.1.4", + "version": "20.1.5", "private": true, "description": "Software Development Kit for Angular", "keywords": [