Skip to content

Commit 7327970

Browse files
clydinalan-agius4
authored andcommitted
refactor(@angular/cli): separate MCP tool declaration assembly into helper function
The logic to assemble the list of active tools for the MCP server has been extracted into a new `assembleToolDeclarations` helper function. This improves the structure and readability of the `createMcpServer` function. The tool lists have also been moved to module-level, `readonly` variables with JSDoc comments to improve clarity and prevent accidental modifications.
1 parent 7c37eba commit 7327970

File tree

2 files changed

+53
-29
lines changed

2 files changed

+53
-29
lines changed

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

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,22 @@ import { DOC_SEARCH_TOOL } from './tools/doc-search';
1616
import { FIND_EXAMPLE_TOOL } from './tools/examples';
1717
import { MODERNIZE_TOOL } from './tools/modernize';
1818
import { LIST_PROJECTS_TOOL } from './tools/projects';
19-
import { McpToolDeclaration, registerTools } from './tools/tool-registry';
19+
import { AnyMcpToolDeclaration, registerTools } from './tools/tool-registry';
20+
21+
/**
22+
* The set of tools that are enabled by default for the MCP server.
23+
* These tools are considered stable and suitable for general use.
24+
*/
25+
const STABLE_TOOLS = [BEST_PRACTICES_TOOL, DOC_SEARCH_TOOL, LIST_PROJECTS_TOOL] as const;
26+
27+
/**
28+
* The set of tools that are available but not enabled by default.
29+
* These tools are considered experimental and may have limitations.
30+
*/
31+
const EXPERIMENTAL_TOOLS = [FIND_EXAMPLE_TOOL, MODERNIZE_TOOL] as const;
2032

2133
export async function createMcpServer(
22-
context: {
34+
options: {
2335
workspace?: AngularWorkspace;
2436
readOnly?: boolean;
2537
localOnly?: boolean;
@@ -46,51 +58,61 @@ export async function createMcpServer(
4658

4759
registerInstructionsResource(server);
4860

49-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
50-
let toolDeclarations: McpToolDeclaration<any, any>[] = [
51-
BEST_PRACTICES_TOOL,
52-
DOC_SEARCH_TOOL,
53-
LIST_PROJECTS_TOOL,
54-
];
55-
const experimentalToolDeclarations = [FIND_EXAMPLE_TOOL, MODERNIZE_TOOL];
61+
const toolDeclarations = assembleToolDeclarations(STABLE_TOOLS, EXPERIMENTAL_TOOLS, {
62+
...options,
63+
logger,
64+
});
5665

57-
if (context.readOnly) {
66+
await registerTools(
67+
server,
68+
{
69+
workspace: options.workspace,
70+
logger,
71+
exampleDatabasePath: path.join(__dirname, '../../../lib/code-examples.db'),
72+
},
73+
toolDeclarations,
74+
);
75+
76+
return server;
77+
}
78+
79+
export function assembleToolDeclarations(
80+
stableDeclarations: readonly AnyMcpToolDeclaration[],
81+
experimentalDeclarations: readonly AnyMcpToolDeclaration[],
82+
options: {
83+
readOnly?: boolean;
84+
localOnly?: boolean;
85+
experimentalTools?: string[];
86+
logger: { warn(text: string): void };
87+
},
88+
): AnyMcpToolDeclaration[] {
89+
let toolDeclarations = [...stableDeclarations];
90+
91+
if (options.readOnly) {
5892
toolDeclarations = toolDeclarations.filter((tool) => tool.isReadOnly);
5993
}
6094

61-
if (context.localOnly) {
95+
if (options.localOnly) {
6296
toolDeclarations = toolDeclarations.filter((tool) => tool.isLocalOnly);
6397
}
6498

65-
const enabledExperimentalTools = new Set(context.experimentalTools);
99+
const enabledExperimentalTools = new Set(options.experimentalTools);
66100
if (process.env['NG_MCP_CODE_EXAMPLES'] === '1') {
67101
enabledExperimentalTools.add('find_examples');
68102
}
69103

70104
if (enabledExperimentalTools.size > 0) {
71-
const experimentalToolsMap = new Map(
72-
experimentalToolDeclarations.map((tool) => [tool.name, tool]),
73-
);
105+
const experimentalToolsMap = new Map(experimentalDeclarations.map((tool) => [tool.name, tool]));
74106

75107
for (const toolName of enabledExperimentalTools) {
76108
const tool = experimentalToolsMap.get(toolName);
77109
if (tool) {
78110
toolDeclarations.push(tool);
79111
} else {
80-
logger.warn(`Unknown experimental tool: ${toolName}`);
112+
options.logger.warn(`Unknown experimental tool: ${toolName}`);
81113
}
82114
}
83115
}
84116

85-
await registerTools(
86-
server,
87-
{
88-
workspace: context.workspace,
89-
logger,
90-
exampleDatabasePath: path.join(__dirname, '../../../lib/code-examples.db'),
91-
},
92-
toolDeclarations,
93-
);
94-
95-
return server;
117+
return toolDeclarations;
96118
}

packages/angular/cli/src/commands/mcp/tools/tool-registry.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export interface McpToolDeclaration<TInput extends ZodRawShape, TOutput extends
3535
isLocalOnly?: boolean;
3636
}
3737

38+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
39+
export type AnyMcpToolDeclaration = McpToolDeclaration<any, any>;
40+
3841
export function declareTool<TInput extends ZodRawShape, TOutput extends ZodRawShape>(
3942
declaration: McpToolDeclaration<TInput, TOutput>,
4043
): McpToolDeclaration<TInput, TOutput> {
@@ -44,8 +47,7 @@ export function declareTool<TInput extends ZodRawShape, TOutput extends ZodRawSh
4447
export async function registerTools(
4548
server: McpServer,
4649
context: McpToolContext,
47-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
48-
declarations: McpToolDeclaration<any, any>[],
50+
declarations: AnyMcpToolDeclaration[],
4951
): Promise<void> {
5052
for (const declaration of declarations) {
5153
if (declaration.shouldRegister && !(await declaration.shouldRegister(context))) {

0 commit comments

Comments
 (0)