Skip to content

Commit b287b6e

Browse files
committed
refactor(@angular/cli): split MCP server into multiple files.
1 parent 935e309 commit b287b6e

4 files changed

Lines changed: 118 additions & 23 deletions

File tree

packages/angular/cli/src/commands/mcp/mcp-server.ts

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { registerBestPracticesTool } from './tools/best-practices';
1515
import { registerDocSearchTool } from './tools/doc-search';
1616
import { registerFindExampleTool } from './tools/examples';
1717
import { registerListProjectsTool } from './tools/projects';
18+
import { registerInstructionsResource } from './resources/instructions';
1819

1920
export async function createMcpServer(
2021
context: {
@@ -34,28 +35,7 @@ export async function createMcpServer(
3435
'When writing or modifying Angular code, use the MCP server and its tools instead of direct shell commands where possible.',
3536
});
3637

37-
server.registerResource(
38-
'instructions',
39-
'instructions://best-practices',
40-
{
41-
title: 'Angular Best Practices and Code Generation Guide',
42-
description:
43-
"A comprehensive guide detailing Angular's best practices for code generation and development." +
44-
' This guide should be used as a reference by an LLM to ensure any generated code' +
45-
' adheres to modern Angular standards, including the use of standalone components,' +
46-
' typed forms, modern control flow syntax, and other current conventions.',
47-
mimeType: 'text/markdown',
48-
},
49-
async () => {
50-
const text = await readFile(
51-
path.join(__dirname, 'instructions', 'best-practices.md'),
52-
'utf-8',
53-
);
54-
55-
return { contents: [{ uri: 'instructions://best-practices', text }] };
56-
},
57-
);
58-
38+
registerInstructionsResource(server);
5939
registerBestPracticesTool(server);
6040

6141
// If run outside an Angular workspace (e.g., globally) skip the workspace specific tools.
@@ -79,4 +59,4 @@ export async function createMcpServer(
7959
}
8060

8161
return server;
82-
}
62+
}

packages/angular/cli/src/commands/mcp/instructions/best-practices.md renamed to packages/angular/cli/src/commands/mcp/resources/best-practices.md

File renamed without changes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2+
import { readFile } from 'node:fs/promises';
3+
import path from 'node:path';
4+
5+
export function registerInstructionsResource(server: McpServer): void {
6+
server.registerResource(
7+
'instructions',
8+
'instructions://best-practices',
9+
{
10+
title: 'Angular Best Practices and Code Generation Guide',
11+
description:
12+
"A comprehensive guide detailing Angular's best practices for code generation and development." +
13+
' This guide should be used as a reference by an LLM to ensure any generated code' +
14+
' adheres to modern Angular standards, including the use of standalone components,' +
15+
' typed forms, modern control flow syntax, and other current conventions.',
16+
mimeType: 'text/markdown',
17+
},
18+
async () => {
19+
const text = await readFile(path.join(__dirname, 'best-practices.md'), 'utf-8');
20+
21+
return { contents: [{ uri: 'instructions://best-practices', text }] };
22+
},
23+
);
24+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2+
import path from 'node:path';
3+
import { z } from 'zod';
4+
import type { AngularWorkspace } from '../../../utilities/config';
5+
6+
export function registerListProjectsTool(
7+
server: McpServer,
8+
context: { workspace?: AngularWorkspace },
9+
): void {
10+
server.registerTool(
11+
'list_projects',
12+
{
13+
title: 'List Angular Projects',
14+
description:
15+
'Lists the names of all applications and libraries defined within an Angular workspace. ' +
16+
'It reads the `angular.json` configuration file to identify the projects. ',
17+
annotations: {
18+
readOnlyHint: true,
19+
},
20+
outputSchema: {
21+
projects: z.array(
22+
z.object({
23+
name: z
24+
.string()
25+
.describe('The name of the project, as defined in the `angular.json` file.'),
26+
type: z
27+
.enum(['application', 'library'])
28+
.optional()
29+
.describe(`The type of the project, either 'application' or 'library'.`),
30+
root: z
31+
.string()
32+
.describe('The root directory of the project, relative to the workspace root.'),
33+
sourceRoot: z
34+
.string()
35+
.describe(
36+
`The root directory of the project's source files, relative to the workspace root.`,
37+
),
38+
selectorPrefix: z
39+
.string()
40+
.optional()
41+
.describe(
42+
'The prefix to use for component selectors.' +
43+
` For example, a prefix of 'app' would result in selectors like '<app-my-component>'.`,
44+
),
45+
}),
46+
),
47+
},
48+
},
49+
async () => {
50+
const { workspace } = context;
51+
52+
if (!workspace) {
53+
return {
54+
content: [
55+
{
56+
type: 'text' as const,
57+
text:
58+
'No Angular workspace found.' +
59+
' An `angular.json` file, which marks the root of a workspace,' +
60+
' could not be located in the current directory or any of its parent directories.',
61+
},
62+
],
63+
};
64+
}
65+
66+
const projects = [];
67+
// Convert to output format
68+
for (const [name, project] of workspace.projects.entries()) {
69+
projects.push({
70+
name,
71+
type: project.extensions['projectType'] as 'application' | 'library' | undefined,
72+
root: project.root,
73+
sourceRoot: project.sourceRoot ?? path.posix.join(project.root, 'src'),
74+
selectorPrefix: project.extensions['prefix'] as string,
75+
});
76+
}
77+
78+
// The structuredContent field is newer and may not be supported by all hosts.
79+
// A text representation of the content is also provided for compatibility.
80+
return {
81+
content: [
82+
{
83+
type: 'text' as const,
84+
text: `Projects in the Angular workspace:\n${JSON.stringify(projects)}`,
85+
},
86+
],
87+
structuredContent: { projects },
88+
};
89+
},
90+
);
91+
}

0 commit comments

Comments
 (0)