66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9+ import { createServer } from 'net' ;
910import { z } from 'zod' ;
10- import { DevServer , LocalDevServer } from '../dev-server' ;
11+ import { LocalDevServer , devServerKey } from '../dev-server' ;
1112import { Host , LocalWorkspaceHost } from '../host' ;
1213import { createStructureContentOutput } from '../utils' ;
1314import { McpToolContext , McpToolDeclaration , declareTool } from './tool-registry' ;
@@ -19,37 +20,76 @@ const startDevserverToolInputSchema = z.object({
1920 . describe (
2021 'Which project to serve in a monorepo context. If not provided, serves the default project.' ,
2122 ) ,
22- port : z . number ( ) . optional ( ) . describe ( 'Port to listen on.' ) ,
2323} ) ;
2424
2525export type StartDevserverToolInput = z . infer < typeof startDevserverToolInputSchema > ;
2626
2727const startDevserverToolOutputSchema = z . object ( {
2828 message : z . string ( ) . describe ( 'A message indicating the result of the operation.' ) ,
29+ address : z
30+ . string ( )
31+ . optional ( )
32+ . describe (
33+ 'If the operation was successful, this is the HTTP address that the server can be found at.' ,
34+ ) ,
2935} ) ;
3036
3137export type StartDevserverToolOutput = z . infer < typeof startDevserverToolOutputSchema > ;
3238
33- export const DEFAULT_PROJECT_KEY = '<default>' ;
39+ /**
40+ * Finds an available TCP port on the system.
41+ */
42+ function getAvailablePort ( ) : Promise < number > {
43+ return new Promise ( ( resolve , reject ) => {
44+ // Create a new temporary server from Node's net library.
45+ const server = createServer ( ) ;
46+
47+ server . once ( 'error' , ( err : unknown ) => {
48+ reject ( err ) ;
49+ } ) ;
50+
51+ // Listen on port 0 to let the OS assign an available port.
52+ server . listen ( 0 , ( ) => {
53+ const address = server . address ( ) ;
54+
55+ // Ensure address is an object with a port property.
56+ if ( address && typeof address === 'object' ) {
57+ const port = address . port ;
3458
35- function startDevserver ( input : StartDevserverToolInput , context : McpToolContext , host : Host ) {
36- const projectKey = input . project ?? DEFAULT_PROJECT_KEY ;
37- if ( context . devServers ?. has ( projectKey ) ) {
59+ server . close ( ) ;
60+ resolve ( port ) ;
61+ } else {
62+ reject ( new Error ( 'Unable to retrieve address information from server.' ) ) ;
63+ }
64+ } ) ;
65+ } ) ;
66+ }
67+
68+ function localhostAddress ( port : number ) {
69+ return `http://localhost:${ port } /` ;
70+ }
71+
72+ async function startDevserver ( input : StartDevserverToolInput , context : McpToolContext , host : Host ) {
73+ const projectKey = devServerKey ( input . project ) ;
74+
75+ let devServer = context . devServers . get ( projectKey ) ;
76+ if ( devServer ) {
3877 return createStructureContentOutput ( {
3978 message : `Development server for project '${ projectKey } ' is already running.` ,
79+ address : localhostAddress ( devServer . port ) ,
4080 } ) ;
4181 }
4282
43- const devServer = new LocalDevServer ( { host, project : input . project , port : input . port } ) ;
83+ const port = await getAvailablePort ( ) ;
84+
85+ devServer = new LocalDevServer ( { host, project : input . project , port } ) ;
4486 devServer . start ( ) ;
4587
46- if ( ! context . devServers ) {
47- context . devServers = new Map < string , DevServer > ( ) ;
48- }
4988 context . devServers . set ( projectKey , devServer ) ;
5089
5190 return createStructureContentOutput ( {
52- message : `Development server for project '${ projectKey } ' started.` ,
91+ message : `Development server for project '${ projectKey } ' started and watching for workspace changes.` ,
92+ address : localhostAddress ( port ) ,
5393 } ) ;
5494}
5595
@@ -64,12 +104,14 @@ export const START_DEVSERVER_TOOL: McpToolDeclaration<
64104Starts the Angular development server ("ng serve") as a background process.
65105</Purpose>
66106<Use Cases>
67- * **Starting the Server:** Use this tool to begin serving the application. The tool will return immediately
68- while the server runs in the background.
107+ * **Starting the Server:** Use this tool to begin serving the application. The tool will return immediately while the server runs in the
108+ background.
109+ * **Get Build Logs:** Once a dev server has started, use the "wait_for_devserver_build" tool to ensure it's alive. If there are any build
110+ errors, "wait_for_devserver_build" would provide them back and you can give them to the user or rely on them to propose a fix.
69111</Use Cases>
70112<Operational Notes>
71- * This tool manages a development server instance for each project.
72- * This is an asynchronous operation. Subsequent commands can be run while the server is active.
113+ * This tool manages development servers by itself. It maintains at most a single dev server instance for each project in the monorepo .
114+ * This is an asynchronous operation. Subsequent commands can be ran while the server is active.
73115* Use 'stop_devserver' to gracefully shut down the server and access the full log output.
74116</Operational Notes>
75117` ,
0 commit comments