|
| 1 | +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; |
| 2 | +import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; |
| 3 | +import { Tree } from '@angular-devkit/schematics'; |
| 4 | +import { z } from 'zod'; |
| 5 | +import path from 'node:path'; |
| 6 | + |
| 7 | +export function registerModernizeTool(server: McpServer): void { |
| 8 | + server.registerTool( |
| 9 | + 'modernize', |
| 10 | + { |
| 11 | + title: 'Modernize Angular Code', |
| 12 | + description: |
| 13 | + 'Runs migrations on Angular code to make it more modern and idiomatic. ' + |
| 14 | + 'This tool should be run when creating new Angular code, or when existing Angular code needs to be updated. ' + |
| 15 | + 'It can apply transformations for things like control flow, self-closing tags, and dependency injection.', |
| 16 | + inputSchema: { |
| 17 | + files: z.array( |
| 18 | + z.object({ |
| 19 | + name: z.string().describe('The name of the file.'), |
| 20 | + content: z.string().describe('The content of the file.'), |
| 21 | + }), |
| 22 | + ), |
| 23 | + }, |
| 24 | + outputSchema: { |
| 25 | + files: z.array( |
| 26 | + z.object({ |
| 27 | + name: z.string().describe('The name of the file.'), |
| 28 | + content: z.string().describe('The updated content of the file.'), |
| 29 | + }), |
| 30 | + ), |
| 31 | + }, |
| 32 | + }, |
| 33 | + async (input) => { |
| 34 | + try { |
| 35 | + const migrationRunner = new SchematicTestRunner( |
| 36 | + '@angular/core', |
| 37 | + path.join( |
| 38 | + __dirname, |
| 39 | + '../../../../../../node_modules/@angular/core/schematics/migrations.json', |
| 40 | + ), |
| 41 | + ); |
| 42 | + |
| 43 | + const collectionRunner = new SchematicTestRunner( |
| 44 | + '@angular/core', |
| 45 | + path.join( |
| 46 | + __dirname, |
| 47 | + '../../../../../../node_modules/@angular/core/schematics/collection.json', |
| 48 | + ), |
| 49 | + ); |
| 50 | + |
| 51 | + let tree: Tree = new UnitTestTree(Tree.empty()); |
| 52 | + for (const file of input.files) { |
| 53 | + tree.create(file.name, file.content); |
| 54 | + } |
| 55 | + |
| 56 | + for (const file of input.files) { |
| 57 | + if (file.name.endsWith('.ts')) { |
| 58 | + tree = await migrationRunner.runSchematic('test-bed-get', {}, tree); |
| 59 | + tree = await migrationRunner.runSchematic('inject-flags', {}, tree); |
| 60 | + } else if (file.name.endsWith('.html') || file.name.endsWith('.ng.html')) { |
| 61 | + tree = await migrationRunner.runSchematic('control-flow-migration', {}, tree); |
| 62 | + tree = await collectionRunner.runSchematic('self-closing-tags-migration', {}, tree); |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + const updatedFiles = input.files.map((file) => { |
| 67 | + const updatedContent = tree.read(file.name)?.toString() ?? file.content; |
| 68 | + return { |
| 69 | + name: file.name, |
| 70 | + content: updatedContent, |
| 71 | + }; |
| 72 | + }); |
| 73 | + |
| 74 | + return { |
| 75 | + content: [ |
| 76 | + { |
| 77 | + type: 'text' as const, |
| 78 | + text: 'Modernization migrations applied successfully.', |
| 79 | + }, |
| 80 | + ], |
| 81 | + structuredContent: { |
| 82 | + files: updatedFiles, |
| 83 | + }, |
| 84 | + }; |
| 85 | + } catch (e) { |
| 86 | + const message = e instanceof Error ? e.message : 'An unknown error occurred.'; |
| 87 | + return { |
| 88 | + content: [ |
| 89 | + { |
| 90 | + type: 'text' as const, |
| 91 | + text: `Failed to run modernization migrations: ${message}`, |
| 92 | + }, |
| 93 | + ], |
| 94 | + }; |
| 95 | + } |
| 96 | + }, |
| 97 | + ); |
| 98 | +} |
0 commit comments