Skip to content

Commit 14da042

Browse files
committed
refactor(@angular/cli): move MCP list projects tool to separate file
Move the list projects MCP tool for the Angular CLI's MCP server to a separate file within the tools subdirectory. This provides a more consistent structure that matches the other tools in the server. (cherry picked from commit c07fdd2)
1 parent 731d1a6 commit 14da042

File tree

2 files changed

+105
-83
lines changed

2 files changed

+105
-83
lines changed

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

Lines changed: 2 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1010
import { readFile } from 'node:fs/promises';
1111
import path from 'node:path';
12-
import { z } from 'zod';
1312
import type { AngularWorkspace } from '../../utilities/config';
1413
import { VERSION } from '../../utilities/version';
1514
import { registerBestPracticesTool } from './tools/best-practices';
1615
import { registerDocSearchTool } from './tools/doc-search';
16+
import { registerListProjectsTool } from './tools/projects';
1717

1818
export async function createMcpServer(context: {
1919
workspace?: AngularWorkspace;
@@ -50,88 +50,7 @@ export async function createMcpServer(context: {
5050
);
5151

5252
registerBestPracticesTool(server);
53-
54-
server.registerTool(
55-
'list_projects',
56-
{
57-
title: 'List Angular Projects',
58-
description:
59-
'Lists the names of all applications and libraries defined within an Angular workspace. ' +
60-
'It reads the `angular.json` configuration file to identify the projects. ',
61-
annotations: {
62-
readOnlyHint: true,
63-
},
64-
outputSchema: {
65-
projects: z.array(
66-
z.object({
67-
name: z
68-
.string()
69-
.describe('The name of the project, as defined in the `angular.json` file.'),
70-
type: z
71-
.enum(['application', 'library'])
72-
.optional()
73-
.describe(`The type of the project, either 'application' or 'library'.`),
74-
root: z
75-
.string()
76-
.describe('The root directory of the project, relative to the workspace root.'),
77-
sourceRoot: z
78-
.string()
79-
.describe(
80-
`The root directory of the project's source files, relative to the workspace root.`,
81-
),
82-
selectorPrefix: z
83-
.string()
84-
.optional()
85-
.describe(
86-
'The prefix to use for component selectors.' +
87-
` For example, a prefix of 'app' would result in selectors like '<app-my-component>'.`,
88-
),
89-
}),
90-
),
91-
},
92-
},
93-
async () => {
94-
const { workspace } = context;
95-
96-
if (!workspace) {
97-
return {
98-
content: [
99-
{
100-
type: 'text' as const,
101-
text:
102-
'No Angular workspace found.' +
103-
' An `angular.json` file, which marks the root of a workspace,' +
104-
' could not be located in the current directory or any of its parent directories.',
105-
},
106-
],
107-
};
108-
}
109-
110-
const projects = [];
111-
// Convert to output format
112-
for (const [name, project] of workspace.projects.entries()) {
113-
projects.push({
114-
name,
115-
type: project.extensions['projectType'] as 'application' | 'library' | undefined,
116-
root: project.root,
117-
sourceRoot: project.sourceRoot ?? path.posix.join(project.root, 'src'),
118-
selectorPrefix: project.extensions['prefix'] as string,
119-
});
120-
}
121-
122-
// The structuredContent field is newer and may not be supported by all hosts.
123-
// A text representation of the content is also provided for compatibility.
124-
return {
125-
content: [
126-
{
127-
type: 'text' as const,
128-
text: `Projects in the Angular workspace:\n${JSON.stringify(projects)}`,
129-
},
130-
],
131-
structuredContent: { projects },
132-
};
133-
},
134-
);
53+
registerListProjectsTool(server, context);
13554

13655
await registerDocSearchTool(server);
13756

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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 type { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
10+
import path from 'node:path';
11+
import z from 'zod';
12+
import type { AngularWorkspace } from '../../../utilities/config';
13+
14+
export function registerListProjectsTool(
15+
server: McpServer,
16+
context: {
17+
workspace?: AngularWorkspace;
18+
},
19+
): void {
20+
server.registerTool(
21+
'list_projects',
22+
{
23+
title: 'List Angular Projects',
24+
description:
25+
'Lists the names of all applications and libraries defined within an Angular workspace. ' +
26+
'It reads the `angular.json` configuration file to identify the projects. ',
27+
annotations: {
28+
readOnlyHint: true,
29+
openWorldHint: false,
30+
},
31+
outputSchema: {
32+
projects: z.array(
33+
z.object({
34+
name: z
35+
.string()
36+
.describe('The name of the project, as defined in the `angular.json` file.'),
37+
type: z
38+
.enum(['application', 'library'])
39+
.optional()
40+
.describe(`The type of the project, either 'application' or 'library'.`),
41+
root: z
42+
.string()
43+
.describe('The root directory of the project, relative to the workspace root.'),
44+
sourceRoot: z
45+
.string()
46+
.describe(
47+
`The root directory of the project's source files, relative to the workspace root.`,
48+
),
49+
selectorPrefix: z
50+
.string()
51+
.optional()
52+
.describe(
53+
'The prefix to use for component selectors.' +
54+
` For example, a prefix of 'app' would result in selectors like '<app-my-component>'.`,
55+
),
56+
}),
57+
),
58+
},
59+
},
60+
async () => {
61+
const { workspace } = context;
62+
63+
if (!workspace) {
64+
return {
65+
content: [
66+
{
67+
type: 'text' as const,
68+
text:
69+
'No Angular workspace found.' +
70+
' An `angular.json` file, which marks the root of a workspace,' +
71+
' could not be located in the current directory or any of its parent directories.',
72+
},
73+
],
74+
structuredContent: { projects: [] },
75+
};
76+
}
77+
78+
const projects = [];
79+
// Convert to output format
80+
for (const [name, project] of workspace.projects.entries()) {
81+
projects.push({
82+
name,
83+
type: project.extensions['projectType'] as 'application' | 'library' | undefined,
84+
root: project.root,
85+
sourceRoot: project.sourceRoot ?? path.posix.join(project.root, 'src'),
86+
selectorPrefix: project.extensions['prefix'] as string,
87+
});
88+
}
89+
90+
// The structuredContent field is newer and may not be supported by all hosts.
91+
// A text representation of the content is also provided for compatibility.
92+
return {
93+
content: [
94+
{
95+
type: 'text' as const,
96+
text: `Projects in the Angular workspace:\n${JSON.stringify(projects)}`,
97+
},
98+
],
99+
structuredContent: { projects },
100+
};
101+
},
102+
);
103+
}

0 commit comments

Comments
 (0)