Skip to content

Commit dc45c18

Browse files
clydinjosephperrott
authored andcommitted
feat(@angular/cli): add initial MCP server implementation
An initial experimental implementation of a Model Context Protocol (MCP) server is now available as a command within the Angular CLI. The server is a `stdio` based server intended to run locally by a host via the `ng mcp` command. Currently, the server provides one resource and one tool. The resource is for the best practices system instructions found https://angular.dev/ai/develop-with-ai. The tool allows for listing the Angular projects within the Angular workspace. These are preliminary and subject to change. Additional functionality may be added in the future to provide additional capabilities for IDE host integrations.
1 parent 5b179d3 commit dc45c18

File tree

7 files changed

+239
-5
lines changed

7 files changed

+239
-5
lines changed

packages/angular/cli/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ ts_project(
4949
":node_modules/@angular-devkit/schematics",
5050
":node_modules/@inquirer/prompts",
5151
":node_modules/@listr2/prompt-adapter-inquirer",
52+
":node_modules/@modelcontextprotocol/sdk",
5253
":node_modules/@yarnpkg/lockfile",
5354
":node_modules/ini",
5455
":node_modules/jsonc-parser",

packages/angular/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@angular-devkit/schematics": "workspace:0.0.0-PLACEHOLDER",
2828
"@inquirer/prompts": "7.5.3",
2929
"@listr2/prompt-adapter-inquirer": "2.0.22",
30+
"@modelcontextprotocol/sdk": "1.13.1",
3031
"@schematics/angular": "workspace:0.0.0-PLACEHOLDER",
3132
"@yarnpkg/lockfile": "1.1.0",
3233
"ini": "5.0.0",

packages/angular/cli/src/commands/command-config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type CommandNames =
2121
| 'generate'
2222
| 'lint'
2323
| 'make-this-awesome'
24+
| 'mcp'
2425
| 'new'
2526
| 'run'
2627
| 'serve'
@@ -77,6 +78,9 @@ export const RootCommands: Record<
7778
'make-this-awesome': {
7879
factory: () => import('./make-this-awesome/cli'),
7980
},
81+
'mcp': {
82+
factory: () => import('./mcp/cli'),
83+
},
8084
'new': {
8185
factory: () => import('./new/cli'),
8286
aliases: ['n'],
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10+
import { Argv } from 'yargs';
11+
import { CommandModule, CommandModuleImplementation } from '../../command-builder/command-module';
12+
import { isTTY } from '../../utilities/tty';
13+
import { createMcpServer } from './mcp-server';
14+
15+
const INTERACTIVE_MESSAGE = `
16+
To start using the Angular CLI MCP Server, add this configuration to your host:
17+
18+
{
19+
"mcpServers": {
20+
"angular-cli": {
21+
"command": "npx",
22+
"args": ["@angular/cli", "mcp"]
23+
}
24+
}
25+
}
26+
27+
Exact configuration may differ depending on the host.
28+
`;
29+
30+
export default class McpCommandModule extends CommandModule implements CommandModuleImplementation {
31+
command = 'mcp';
32+
describe = false as const;
33+
longDescriptionPath = undefined;
34+
35+
builder(localYargs: Argv): Argv {
36+
return localYargs;
37+
}
38+
39+
async run(): Promise<void> {
40+
if (isTTY()) {
41+
this.context.logger.info(INTERACTIVE_MESSAGE);
42+
43+
return;
44+
}
45+
46+
const server = await createMcpServer({ workspace: this.context.workspace });
47+
const transport = new StdioServerTransport();
48+
await server.connect(transport);
49+
}
50+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
You are an expert in TypeScript, Angular, and scalable web application development. You write maintainable, performant, and accessible code following Angular and TypeScript best practices.
2+
3+
## TypeScript Best Practices
4+
5+
- Use strict type checking
6+
- Prefer type inference when the type is obvious
7+
- Avoid the `any` type; use `unknown` when type is uncertain
8+
9+
## Angular Best Practices
10+
11+
- Always use standalone components over NgModules
12+
- Don't use explicit `standalone: true` (it is implied by default)
13+
- Use signals for state management
14+
- Implement lazy loading for feature routes
15+
- Use `NgOptimizedImage` for all static images.
16+
17+
## Components
18+
19+
- Keep components small and focused on a single responsibility
20+
- Use `input()` and `output()` functions instead of decorators
21+
- Use `computed()` for derived state
22+
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
23+
- Prefer inline templates for small components
24+
- Prefer Reactive forms instead of Template-driven ones
25+
- Do NOT use `ngClass`, use `class` bindings instead
26+
- DO NOT use `ngStyle`, use `style` bindings instead
27+
28+
## State Management
29+
30+
- Use signals for local component state
31+
- Use `computed()` for derived state
32+
- Keep state transformations pure and predictable
33+
34+
## Templates
35+
36+
- Keep templates simple and avoid complex logic
37+
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
38+
- Use the async pipe to handle observables
39+
40+
## Services
41+
42+
- Design services around a single responsibility
43+
- Use the `providedIn: 'root'` option for singleton services
44+
- Use the `inject()` function instead of constructor injection
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10+
import { readFile } from 'node:fs/promises';
11+
import path from 'node:path';
12+
import type { AngularWorkspace } from '../../utilities/config';
13+
import { VERSION } from '../../utilities/version';
14+
15+
export async function createMcpServer(context: {
16+
workspace?: AngularWorkspace;
17+
}): Promise<McpServer> {
18+
const server = new McpServer({
19+
name: 'angular-cli-server',
20+
version: VERSION.full,
21+
capabilities: {
22+
resources: {},
23+
tools: {},
24+
},
25+
});
26+
27+
server.registerResource(
28+
'instructions',
29+
'instructions://best-practices',
30+
{
31+
title: 'Angular System Instructions',
32+
description:
33+
'A set of instructions to help LLMs generate correct code that follows Angular best practices.',
34+
mimeType: 'text/markdown',
35+
},
36+
async () => {
37+
const text = await readFile(
38+
path.join(__dirname, 'instructions', 'best-practices.md'),
39+
'utf-8',
40+
);
41+
42+
return { contents: [{ uri: 'instructions://best-practices', text }] };
43+
},
44+
);
45+
46+
server.registerTool(
47+
'list_projects',
48+
{
49+
title: 'List projects',
50+
description:
51+
'List projects within an Angular workspace.' +
52+
' This information is read from the `angular.json` file at the root path of the Angular workspace',
53+
},
54+
() => {
55+
if (!context.workspace) {
56+
return {
57+
content: [
58+
{
59+
type: 'text',
60+
text: 'Not within an Angular project.',
61+
},
62+
],
63+
};
64+
}
65+
66+
return {
67+
content: [
68+
{
69+
type: 'text',
70+
text:
71+
'Projects in the Angular workspace: ' +
72+
[...context.workspace.projects.keys()].join(','),
73+
},
74+
],
75+
};
76+
},
77+
);
78+
79+
return server;
80+
}

pnpm-lock.yaml

Lines changed: 59 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)