Skip to content

Commit 42a0a7f

Browse files
committed
feat(@angular/cli): add a modernize MCP tool.
1 parent e94dd1c commit 42a0a7f

1 file changed

Lines changed: 5 additions & 118 deletions

File tree

Lines changed: 5 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
3+
import { Tree } from '@angular-devkit/schematics';
24
import { z } from 'zod';
5+
import path from 'node:path';
36

47
interface Transformation {
58
name: string;
@@ -55,121 +58,5 @@ const TRANSFORMATIONS: Array<Transformation> = [
5558
'Converts the application to use standalone components, directives, and pipes. This is a three-step process. After each step, you should verify that your application builds and runs correctly.',
5659
instructions: `This migration requires running a cli schematic multiple times. Run the commands in the order listed below, verifying that your code builds and runs between each step:
5760
58-
1. Run \`ng g @angular/core:standalone\` and select "Convert all components, directives and pipes to standalone"
59-
2. Run \`ng g @angular/core:standalone\` and select "Remove unnecessary NgModule classes"
60-
3. Run \`ng g @angular/core:standalone\` and select "Bootstrap the project using standalone APIs"
61-
`,
62-
documentationUrl: 'https://angular.dev/reference/migrations/standalone',
63-
},
64-
{
65-
name: 'zoneless',
66-
description: 'Migrates the application to be zoneless.',
67-
documentationUrl: 'https://angular.dev/guide/zoneless',
68-
},
69-
];
70-
71-
const modernizeInputSchema = z.object({
72-
// Casting to [string, ...string[]] since the enum definition requires a nonempty array.
73-
transformations: z
74-
.array(z.enum(TRANSFORMATIONS.map((t) => t.name) as [string, ...string[]]))
75-
.optional(),
76-
});
77-
78-
export type ModernizeInput = z.infer<typeof modernizeInputSchema>;
79-
80-
export async function runModernization(input: ModernizeInput) {
81-
try {
82-
if (!input.transformations || input.transformations.length === 0) {
83-
const instructions = [
84-
'See https://angular.dev/best-practices for Angular best practices. You can call this tool if you have specific transformation you want to run.',
85-
];
86-
return {
87-
content: [
88-
{
89-
type: 'text' as const,
90-
text: JSON.stringify({
91-
instructions,
92-
}),
93-
},
94-
],
95-
structuredContent: {
96-
instructions,
97-
},
98-
};
99-
}
100-
101-
const transformationsToRun = TRANSFORMATIONS.filter((t) =>
102-
input.transformations!.includes(t.name),
103-
);
104-
105-
const allInstructions: string[] = [];
106-
107-
for (const transformation of transformationsToRun) {
108-
let transformationInstructions = '';
109-
if (transformation.instructions) {
110-
transformationInstructions = transformation.instructions;
111-
} else {
112-
// If no instructions are included, default to running a cli schematic with the transformation name.
113-
const command = `ng generate @angular/core:${transformation.name}`;
114-
transformationInstructions = `To run the ${transformation.name} migration, execute the following command: \`${command}\`.`;
115-
}
116-
if (transformation.documentationUrl) {
117-
transformationInstructions += `\nFor more information, see ${transformation.documentationUrl}.`;
118-
}
119-
allInstructions.push(transformationInstructions);
120-
}
121-
122-
const structuredContent = {
123-
instructions: allInstructions.length ? allInstructions : undefined,
124-
};
125-
126-
return {
127-
content: [{ type: 'text' as const, text: JSON.stringify(structuredContent) }],
128-
structuredContent,
129-
};
130-
} catch (e) {
131-
const message = e instanceof Error ? e.message : 'An unknown error occurred.';
132-
return {
133-
content: [
134-
{
135-
type: 'text' as const,
136-
text: `Failed to run modernization migrations: ${message}`,
137-
},
138-
],
139-
structuredContent: {},
140-
isError: true,
141-
};
142-
}
143-
}
144-
145-
export function registerModernizeTool(server: McpServer): void {
146-
server.registerTool(
147-
'modernize',
148-
{
149-
title: 'Modernize Angular Code',
150-
description:
151-
'<Purpose>\n' +
152-
'This tool modernizes Angular code by applying the latest best practices and syntax improvements, ensuring it is idiomatic, readable, and maintainable.\n\n' +
153-
'</Purpose>\n' +
154-
'<Use Cases>\n' +
155-
'* After generating new code: Run this tool immediately after creating new Angular components, directives, or services to ensure they adhere to modern standards.\n' +
156-
'* On existing code: Apply to existing TypeScript files (.ts) and Angular templates (.ng.html) to update them with the latest features, such as the new built-in control flow syntax.\n\n' +
157-
'* When the user asks for a specific transformation: When the transformation list is populated, these specific ones will be ran on the inputs.\n' +
158-
'</Use Cases>\n' +
159-
'<Transformations>\n' +
160-
TRANSFORMATIONS.map((t) => `* ${t.name}: ${t.description}`).join('\n') +
161-
'\n</Transformations>\n',
162-
annotations: {
163-
readOnlyHint: true,
164-
},
165-
inputSchema: modernizeInputSchema.shape,
166-
outputSchema: {
167-
instructions: z
168-
.array(z.string())
169-
.optional()
170-
.describe('A list of instructions on how to run the migrations.'),
171-
},
172-
},
173-
(input) => runModernization(input as ModernizeInput),
174-
);
175-
}
61+
1. Run
62+
`ng g @angular/core:standalone` and select

0 commit comments

Comments
 (0)