forked from angular/angular-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmcp-server.ts
More file actions
191 lines (170 loc) · 6.2 KB
/
mcp-server.ts
File metadata and controls
191 lines (170 loc) · 6.2 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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/**
* @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 { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { join } from 'node:path';
import type { AngularWorkspace } from '../../utilities/config';
import { VERSION } from '../../utilities/version';
import type { Devserver } from './devserver';
import { LocalWorkspaceHost } from './host';
import { registerInstructionsResource } from './resources/instructions';
import { AI_TUTOR_TOOL } from './tools/ai-tutor';
import { BEST_PRACTICES_TOOL } from './tools/best-practices';
import { BUILD_TOOL } from './tools/build';
import { DEVSERVER_START_TOOL } from './tools/devserver/devserver-start';
import { DEVSERVER_STOP_TOOL } from './tools/devserver/devserver-stop';
import { DEVSERVER_WAIT_FOR_BUILD_TOOL } from './tools/devserver/devserver-wait-for-build';
import { DOC_SEARCH_TOOL } from './tools/doc-search';
import { E2E_TOOL } from './tools/e2e';
import { FIND_EXAMPLE_TOOL } from './tools/examples/index';
import { MODERNIZE_TOOL } from './tools/modernize';
import { ZONELESS_MIGRATION_TOOL } from './tools/onpush-zoneless-migration/zoneless-migration';
import { LIST_PROJECTS_TOOL } from './tools/projects';
import { TEST_TOOL } from './tools/test';
import { type AnyMcpToolDeclaration, registerTools } from './tools/tool-registry';
/**
* Tools to manage devservers. Should be bundled together, then added to experimental or stable as a group.
*/
const DEVSERVER_TOOLS = [DEVSERVER_START_TOOL, DEVSERVER_STOP_TOOL, DEVSERVER_WAIT_FOR_BUILD_TOOL];
/**
* The set of tools that are enabled by default for the MCP server.
* These tools are considered stable and suitable for general use.
*/
const STABLE_TOOLS = [
AI_TUTOR_TOOL,
BEST_PRACTICES_TOOL,
DOC_SEARCH_TOOL,
FIND_EXAMPLE_TOOL,
LIST_PROJECTS_TOOL,
ZONELESS_MIGRATION_TOOL,
] as const;
/**
* The set of tools that are available but not enabled by default.
* These tools are considered experimental and may have limitations.
*/
export const EXPERIMENTAL_TOOLS = [
BUILD_TOOL,
E2E_TOOL,
MODERNIZE_TOOL,
TEST_TOOL,
...DEVSERVER_TOOLS,
] as const;
/**
* Experimental tools that are grouped together under a single name.
*
* Used for enabling them as a group.
*/
export const EXPERIMENTAL_TOOL_GROUPS = {
'all': EXPERIMENTAL_TOOLS,
'devserver': DEVSERVER_TOOLS,
};
export async function createMcpServer(
options: {
workspace?: AngularWorkspace;
readOnly?: boolean;
localOnly?: boolean;
experimentalTools?: string[];
},
logger: { warn(text: string): void },
): Promise<McpServer> {
const server = new McpServer(
{
name: 'angular-cli-server',
version: VERSION.full,
},
{
capabilities: {
resources: {},
tools: {},
logging: {},
},
instructions: `
<General Purpose>
This server provides a safe, programmatic interface to the Angular CLI for an AI assistant.
Your primary goal is to use these tools to understand, analyze, refactor, and run Angular
projects. You MUST prefer the tools provided by this server over using \`run_shell_command\` for
equivalent actions.
</General Purpose>
<Core Workflows & Tool Guide>
* **1. Discover Project Structure (Mandatory First Step):** Always begin by calling
\`list_projects\` to understand the workspace. The \`path\` property for a workspace
is a required input for other tools.
* **2. Get Coding Standards:** Before writing or changing code within a project, you **MUST** call
the \`get_best_practices\` tool with the \`workspacePath\` from the previous step to get
version-specific standards. For general knowledge, you can call the tool without this path.
* **3. Answer User Questions:**
- For conceptual questions ("what is..."), use \`search_documentation\`.
- For code examples ("show me how to..."), use \`find_examples\`.
</Core Workflows & Tool Guide>
<Key Concepts>
* **Workspace vs. Project:** A 'workspace' contains an \`angular.json\` file and defines 'projects'
(applications or libraries). A monorepo can have multiple workspaces.
* **Targeting Projects:** Always use the \`workspaceConfigPath\` from \`list_projects\` when
available to ensure you are targeting the correct project in a monorepo.
</Key Concepts>
`,
},
);
registerInstructionsResource(server);
const toolDeclarations = assembleToolDeclarations(STABLE_TOOLS, EXPERIMENTAL_TOOLS, {
...options,
logger,
});
await registerTools(
server,
{
workspace: options.workspace,
logger,
exampleDatabasePath: join(__dirname, '../../../lib/code-examples.db'),
devservers: new Map<string, Devserver>(),
host: LocalWorkspaceHost,
},
toolDeclarations,
);
return server;
}
export function assembleToolDeclarations(
stableDeclarations: readonly AnyMcpToolDeclaration[],
experimentalDeclarations: readonly AnyMcpToolDeclaration[],
options: {
readOnly?: boolean;
localOnly?: boolean;
experimentalTools?: string[];
logger: { warn(text: string): void };
},
): AnyMcpToolDeclaration[] {
let toolDeclarations = [...stableDeclarations];
if (options.readOnly) {
toolDeclarations = toolDeclarations.filter((tool) => tool.isReadOnly);
}
if (options.localOnly) {
toolDeclarations = toolDeclarations.filter((tool) => tool.isLocalOnly);
}
const enabledExperimentalTools = new Set(options.experimentalTools);
if (process.env['NG_MCP_CODE_EXAMPLES'] === '1') {
enabledExperimentalTools.add('find_examples');
}
for (const [toolGroupName, toolGroup] of Object.entries(EXPERIMENTAL_TOOL_GROUPS)) {
if (enabledExperimentalTools.delete(toolGroupName)) {
for (const tool of toolGroup) {
enabledExperimentalTools.add(tool.name);
}
}
}
if (enabledExperimentalTools.size > 0) {
const experimentalToolsMap = new Map(experimentalDeclarations.map((tool) => [tool.name, tool]));
for (const toolName of enabledExperimentalTools) {
const tool = experimentalToolsMap.get(toolName);
if (tool) {
toolDeclarations.push(tool);
} else {
options.logger.warn(`Unknown experimental tool: ${toolName}`);
}
}
}
return toolDeclarations;
}