Skip to content

Commit 2a18b31

Browse files
committed
feat(@angular/cli): Add a "serve" MCP tool
1 parent 50caa1d commit 2a18b31

File tree

2 files changed

+126
-1
lines changed

2 files changed

+126
-1
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { BUILD_TOOL } from './tools/build';
1717
import { DOC_SEARCH_TOOL } from './tools/doc-search';
1818
import { FIND_EXAMPLE_TOOL } from './tools/examples';
1919
import { MODERNIZE_TOOL } from './tools/modernize';
20+
import { SERVE_TOOL } from './tools/serve';
2021
import { ZONELESS_MIGRATION_TOOL } from './tools/onpush-zoneless-migration/zoneless-migration';
2122
import { LIST_PROJECTS_TOOL } from './tools/projects';
2223
import { AnyMcpToolDeclaration, registerTools } from './tools/tool-registry';
@@ -38,7 +39,7 @@ const STABLE_TOOLS = [
3839
* The set of tools that are available but not enabled by default.
3940
* These tools are considered experimental and may have limitations.
4041
*/
41-
export const EXPERIMENTAL_TOOLS = [MODERNIZE_TOOL, ZONELESS_MIGRATION_TOOL] as const;
42+
export const EXPERIMENTAL_TOOLS = [MODERNIZE_TOOL, ZONELESS_MIGRATION_TOOL, SERVE_TOOL] as const;
4243

4344
export async function createMcpServer(
4445
options: {
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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 { ChildProcess, spawn } from 'child_process';
10+
import { z } from 'zod';
11+
import { McpToolContext, declareTool } from './tool-registry';
12+
13+
let devServerProcess: ChildProcess | null = null;
14+
const serverLogs: string[] = [];
15+
16+
const serveToolInputSchema = z.object({
17+
command: z.enum(['start_devserver', 'stop_devserver']).describe('The subcommand to execute.'),
18+
project: z
19+
.string()
20+
.optional()
21+
.describe(
22+
'Which project to serve in a monorepo context. If not provided, serves the top-level project.',
23+
),
24+
configuration: z
25+
.string()
26+
.optional()
27+
.default('development')
28+
.describe('Which build configuration to use. Defaults to "development".'),
29+
});
30+
31+
export type ServeToolInput = z.infer<typeof serveToolInputSchema>;
32+
33+
const serveToolOutputSchema = z.object({
34+
message: z.string().describe('A message indicating the result of the operation.'),
35+
logs: z.array(z.string()).optional().describe('The logs from the dev server.'),
36+
});
37+
38+
export type ServeToolOutput = z.infer<typeof serveToolOutputSchema>;
39+
40+
function serveToolFactory(context: McpToolContext) {
41+
return (input: ServeToolInput) => {
42+
switch (input.command) {
43+
case 'start_devserver':
44+
if (devServerProcess) {
45+
return {
46+
structuredContent: {
47+
message: 'Development server is already running.',
48+
},
49+
};
50+
}
51+
52+
const args = ['serve'];
53+
if (input.project) {
54+
args.push(input.project);
55+
}
56+
if (input.configuration) {
57+
args.push(`--configuration=${input.configuration}`);
58+
}
59+
60+
serverLogs.length = 0; // Clear previous logs
61+
devServerProcess = spawn('ng', args, { stdio: 'pipe' });
62+
63+
devServerProcess.stdout?.on('data', (data) => {
64+
serverLogs.push(data.toString());
65+
});
66+
devServerProcess.stderr?.on('data', (data) => {
67+
serverLogs.push(data.toString());
68+
});
69+
70+
devServerProcess.on('close', () => {
71+
devServerProcess = null;
72+
});
73+
74+
return {
75+
structuredContent: {
76+
message: 'Development server started.',
77+
},
78+
};
79+
80+
case 'stop_devserver':
81+
if (!devServerProcess) {
82+
return {
83+
structuredContent: {
84+
message: 'Development server is not running.',
85+
},
86+
};
87+
}
88+
89+
devServerProcess.kill('SIGTERM');
90+
devServerProcess = null;
91+
92+
return {
93+
structuredContent: {
94+
message: 'Development server stopped.',
95+
logs: serverLogs,
96+
},
97+
};
98+
}
99+
};
100+
}
101+
102+
export const SERVE_TOOL = declareTool({
103+
name: 'serve',
104+
title: 'Serve Tool',
105+
description: `
106+
<Purpose>
107+
Manages the Angular development server ("ng serve"). It allows you to start and stop the server as a background process.
108+
</Purpose>
109+
<Use Cases>
110+
* **Starting the Server:** Use the 'start_devserver' command to begin serving the application. The tool will return immediately while the server runs in the background.
111+
* **Stopping the Server:** Use the 'stop_devserver' command to terminate the running development server and retrieve the logs.
112+
</Use Cases>
113+
<Operational Notes>
114+
* This tool manages a single, shared development server instance.
115+
* 'start_devserver' is asynchronous. Subsequent commands can be run while the server is active.
116+
* 'stop_devserver' should be called to gracefully shut down the server and access the full log output.
117+
</Operational Notes>
118+
`,
119+
isReadOnly: false,
120+
isLocalOnly: true,
121+
inputSchema: serveToolInputSchema.shape,
122+
outputSchema: serveToolOutputSchema.shape,
123+
factory: serveToolFactory,
124+
});

0 commit comments

Comments
 (0)