forked from angular/angular-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdevserver-start.ts
More file actions
128 lines (114 loc) · 5.07 KB
/
devserver-start.ts
File metadata and controls
128 lines (114 loc) · 5.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { z } from 'zod';
import { LocalDevserver, getDevserverKey } from '../../devserver';
import { workspaceAndProjectOptions } from '../../shared-options';
import { createStructuredContentOutput } from '../../utils';
import { resolveWorkspaceAndProject } from '../../workspace-utils';
import { type McpToolContext, type McpToolDeclaration, declareTool } from '../tool-registry';
const devserverStartToolInputSchema = z.object({
...workspaceAndProjectOptions,
port: z
.number()
.optional()
.describe(
'The port number to run the server on. If not provided, a random available port will be chosen. ' +
'It is recommended to reuse port numbers across calls within the same workspace to maintain consistency.',
),
});
export type DevserverStartToolInput = z.infer<typeof devserverStartToolInputSchema>;
const devserverStartToolOutputSchema = z.object({
message: z.string().describe('A message indicating the result of the operation.'),
address: z
.string()
.optional()
.describe(
'If the operation was successful, this is the HTTP address that the server can be found at.',
),
});
export type DevserverStartToolOutput = z.infer<typeof devserverStartToolOutputSchema>;
function localhostAddress(port: number) {
return `http://localhost:${port}/`;
}
export async function startDevserver(input: DevserverStartToolInput, context: McpToolContext) {
const { workspacePath, projectName } = await resolveWorkspaceAndProject({
host: context.host,
workspacePathInput: input.workspace,
projectNameInput: input.project,
mcpWorkspace: context.workspace,
});
const key = getDevserverKey(workspacePath, projectName);
let devserver = context.devservers.get(key);
if (devserver) {
return createStructuredContentOutput({
message: `Development server for project '${projectName}' is already running.`,
address: localhostAddress(devserver.port),
});
}
let port: number;
if (input.port) {
if (!(await context.host.isPortAvailable(input.port))) {
throw new Error(
`Port ${input.port} is unavailable. Try calling this tool again without the 'port' parameter to auto-assign a free port.`,
);
}
port = input.port;
} else {
port = await context.host.getAvailablePort();
}
devserver = new LocalDevserver({
host: context.host,
project: projectName,
port,
workspacePath,
});
devserver.start();
context.devservers.set(key, devserver);
return createStructuredContentOutput({
message: `Development server for project '${projectName}' started and watching for workspace changes.`,
address: localhostAddress(port),
});
}
export const DEVSERVER_START_TOOL: McpToolDeclaration<
typeof devserverStartToolInputSchema.shape,
typeof devserverStartToolOutputSchema.shape
> = declareTool({
name: 'devserver.start',
title: 'Start Development Server',
description: `
<Purpose>
Starts the Angular development server ("ng serve") as a background process. Follow this up with "devserver.wait_for_build" to wait until
the first build completes.
</Purpose>
<Use Cases>
* **Starting the Server:** Use this tool to begin serving the application. The tool will return immediately while the server runs in the
background.
* **Get Initial Build Logs:** Once a dev server has started, use the "devserver.wait_for_build" tool to ensure it's alive. If there are any
build errors, "devserver.wait_for_build" would provide them back and you can give them to the user or rely on them to propose a fix.
* **Get Updated Build Logs:** Important: as long as a devserver is alive (i.e. "devserver.stop" wasn't called), after every time you
make a change to the workspace, re-run "devserver.wait_for_build" to see whether the change was successfully built and wait for the
devserver to be updated.
</Use Cases>
<Operational Notes>
* This tool manages development servers by itself. It maintains at most a single dev server instance for each project in the monorepo.
* This is an asynchronous operation. Subsequent commands can be ran while the server is active.
* Use 'devserver.stop' to gracefully shut down the server and access the full log output.
* **Keeping the Server Alive**: It is often better to keep the server alive between tool calls if you expect the user to request more
changes or run more tests, as it saves time on restarts and maintains the file watcher state. You must still call
'devserver.wait_for_build' after every change to see whether the change was successfully built and be sure that that app was updated.
* **Consistent Ports**: If making multiple calls, it is recommended to reuse the port you got from the first call for subsequent ones.
</Operational Notes>
`,
isReadOnly: true,
isLocalOnly: true,
inputSchema: devserverStartToolInputSchema.shape,
outputSchema: devserverStartToolOutputSchema.shape,
factory: (context) => (input) => {
return startDevserver(input, context);
},
});