Skip to content

Commit 25318e0

Browse files
Marina-L-StoyanovaCopilot
authored andcommitted
feat: enhance MCP configuration for multiple coding assistants and update related prompts
Co-authored-by: Copilot <copilot@github.com>
1 parent 26d958b commit 25318e0

6 files changed

Lines changed: 191 additions & 29 deletions

File tree

packages/cli/lib/commands/ai-config.ts

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import { addMcpServers, AI_AGENT_LABELS, AI_AGENT_CHOICES, AIAgentTarget, copyAgentInstructionFiles, copyAISkillsToProject, GoogleAnalytics, InquirerWrapper, Util, VS_CODE_MCP_PATH } from "@igniteui/cli-core";
1+
import { addMcpServers, AI_AGENT_LABELS, AI_AGENT_CHOICES, AIAgentTarget, copyAgentInstructionFiles, copyAISkillsToProject, GoogleAnalytics, InquirerWrapper, Util, AiCodingAssistant, AI_ASSISTANT_MCP_CONFIGS } from "@igniteui/cli-core";
22
import { ArgumentsCamelCase, CommandModule } from "yargs";
33

4-
export function configureMCP(): void {
5-
const modified = addMcpServers(VS_CODE_MCP_PATH);
4+
export function configureMCP(assistant: AiCodingAssistant = "vscode"): void {
5+
const { mcpFilePath } = AI_ASSISTANT_MCP_CONFIGS[assistant];
6+
const modified = addMcpServers(assistant);
67

78
if (!modified) {
8-
Util.log(` Ignite UI MCP servers already configured in ${VS_CODE_MCP_PATH}`);
9+
Util.log(` Ignite UI MCP servers already configured in ${mcpFilePath}`);
910
return;
1011
}
11-
Util.log(Util.greenCheck() + ` MCP servers configured in ${VS_CODE_MCP_PATH}`);
12+
Util.log(Util.greenCheck() + ` MCP servers configured in ${mcpFilePath}`);
1213
}
1314

1415
export function configureSkills(agents: AIAgentTarget[]): void {
@@ -26,20 +27,31 @@ export function configureSkills(agents: AIAgentTarget[]): void {
2627
}
2728
}
2829

29-
export async function configure(agents?: AIAgentTarget[], skills = true): Promise<void> {
30+
export async function configure(agents?: AIAgentTarget[], skills = true, assistant: AiCodingAssistant = "vscode"): Promise<void> {
3031
if (!agents?.length) {
3132
agents = await promptForAgents();
3233
}
3334
if (!agents.length) return;
34-
configureMCP();
35+
configureMCP(assistant);
3536
if (skills) {
3637
configureSkills(agents);
3738
}
3839
copyAgentInstructionFiles(agents);
3940
}
4041
const AI_AGENT_CHECKBOX_DEFAULTS: AIAgentTarget[] = ["generic", "claude"];
42+
43+
const AI_ASSISTANT_CHOICES = Object.keys(AI_ASSISTANT_MCP_CONFIGS) as AiCodingAssistant[];
44+
45+
const AI_ASSISTANT_LABELS: Record<AiCodingAssistant, string> = {
46+
"vscode": "VS Code (GitHub Copilot)",
47+
"cursor": "Cursor",
48+
"claude-code": "Claude Code",
49+
"gemini": "Gemini",
50+
"junie": "JetBrains Junie",
51+
};
52+
4153
const AI_AGENT_CHECKBOX_CHOICES = [
42-
{ value: "none", name: "None (skip AI configuration)" },
54+
{ value: "none", name: "None (skip skills and instructions)" },
4355
...AI_AGENT_CHOICES.map(agent => ({
4456
value: agent,
4557
name: AI_AGENT_LABELS[agent],
@@ -51,7 +63,7 @@ export async function promptForAgents(): Promise<AIAgentTarget[]> {
5163
let selected: AIAgentTarget[] = AI_AGENT_CHECKBOX_DEFAULTS;
5264
if (Util.canPrompt()) {
5365
const result = await InquirerWrapper.checkbox({
54-
message: "Which AI tools do you want to generate configuration files for?",
66+
message: "Which AI agents do you want to generate skills and instructions for?",
5567
required: true,
5668
choices: AI_AGENT_CHECKBOX_CHOICES
5769
});
@@ -60,6 +72,14 @@ export async function promptForAgents(): Promise<AIAgentTarget[]> {
6072
return selected;
6173
}
6274

75+
export async function promptForAssistant(): Promise<AiCodingAssistant> {
76+
const selected = await InquirerWrapper.select({
77+
message: "Which coding assistant should MCP servers be configured for?",
78+
choices: AI_ASSISTANT_CHOICES.map(a => ({ value: a, name: AI_ASSISTANT_LABELS[a] }))
79+
});
80+
return selected as AiCodingAssistant;
81+
}
82+
6383
const command: CommandModule = {
6484
command: "ai-config",
6585
describe: "Configures Ignite UI AI tooling (MCP servers, AI coding skills and instructions)",
@@ -70,23 +90,36 @@ const command: CommandModule = {
7090
describe: "AI agents/tools to generate configuration files for",
7191
choices: AI_AGENT_CHOICES,
7292
type: "array"
93+
})
94+
.option("assistant", {
95+
describe: "Coding assistant to configure MCP servers for",
96+
choices: AI_ASSISTANT_CHOICES,
97+
type: "string"
7398
}),
7499
async handler(argv: ArgumentsCamelCase) {
75100
let agents = argv.agent as AIAgentTarget[] | undefined;
101+
let assistant = argv.assistant as AiCodingAssistant | undefined;
102+
76103
GoogleAnalytics.post({
77104
t: "screenview",
78105
cd: "Ai Config"
79106
});
80107

108+
if (!assistant) {
109+
assistant = await promptForAssistant();
110+
}
81111
if (!agents?.length) {
82112
agents = await promptForAgents();
83113
}
114+
84115
GoogleAnalytics.post({
85116
t: "event",
86117
ec: "$ig ai-config",
87-
ea: `agent: ${agents.join(", ")}`
118+
ea: `agent: ${agents.join(", ") || "none"}`
88119
});
89120

121+
configureMCP(assistant);
122+
90123
if (!agents.length) {
91124
Util.log("No AI configuration selected. Skipping.");
92125
return;

packages/core/util/mcp-config.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ export interface McpServerEntry {
77
args: string[];
88
}
99

10+
export type AiCodingAssistant = "vscode" | "cursor" | "claude-code" | "gemini" | "junie";
11+
12+
interface AssistantMcpConfig {
13+
mcpFilePath: string;
14+
rootKey: "servers" | "mcpServers";
15+
}
16+
17+
export const AI_ASSISTANT_MCP_CONFIGS: Record<AiCodingAssistant, AssistantMcpConfig> = {
18+
"vscode": { mcpFilePath: ".vscode/mcp.json", rootKey: "servers" },
19+
"cursor": { mcpFilePath: ".cursor/mcp.json", rootKey: "mcpServers" },
20+
"claude-code": { mcpFilePath: ".mcp.json", rootKey: "mcpServers" },
21+
"gemini": { mcpFilePath: ".gemini/settings.json", rootKey: "mcpServers" },
22+
"junie": { mcpFilePath: ".junie/mcp/mcp.json", rootKey: "mcpServers" },
23+
};
24+
1025
const IGNITEUI_MCP_SERVERS: Record<string, McpServerEntry> = {
1126
"igniteui-cli": {
1227
command: "npx",
@@ -18,18 +33,18 @@ const IGNITEUI_MCP_SERVERS: Record<string, McpServerEntry> = {
1833
}
1934
};
2035

21-
export const VS_CODE_MCP_PATH = ".vscode/mcp.json";
22-
2336
/**
24-
* Reads .vscode/mcp.json, ensures all IgniteUI MCP servers are present,
37+
* Reads the assistant-specific MCP config file, ensures all IgniteUI MCP servers are present,
2538
* optionally adds additional servers. Creates the file if it doesn't exist.
39+
* @param assistant target AI coding assistant (defaults to "vscode")
2640
* @param additionalServers optional extra servers to include alongside the built-in ones
2741
* @returns whether the file was modified
2842
*/
2943
export function addMcpServers(
30-
mcpFilePath: string,
44+
assistant: AiCodingAssistant = "vscode",
3145
additionalServers?: Record<string, McpServerEntry>
3246
): boolean {
47+
const { mcpFilePath, rootKey } = AI_ASSISTANT_MCP_CONFIGS[assistant];
3348
const fileSystem = App.container.get<IFileSystem>(FS_TOKEN);
3449
const servers = { ...additionalServers, ...IGNITEUI_MCP_SERVERS };
3550

@@ -44,20 +59,20 @@ export function addMcpServers(
4459
if (Object.keys(servers).length === 0) {
4560
return false;
4661
}
47-
fileSystem.writeFile(mcpFilePath, JSON.stringify({ servers }, null, 2) + "\n");
62+
fileSystem.writeFile(mcpFilePath, JSON.stringify({ [rootKey]: servers }, null, 2) + "\n");
4863
return true;
4964
}
5065

5166
const parsed = jsonc.parse(existingContent);
52-
const existing = parsed.servers ?? {};
67+
const existing = parsed[rootKey] ?? {};
5368
const formattingOptions: jsonc.FormattingOptions = { tabSize: 2, insertSpaces: true };
5469

5570
let text = existingContent;
5671
let modified = false;
5772

5873
for (const [key, value] of Object.entries(servers)) {
5974
if (!existing[key]) {
60-
const edits = jsonc.modify(text, ["servers", key], value, { formattingOptions });
75+
const edits = jsonc.modify(text, [rootKey, key], value, { formattingOptions });
6176
text = jsonc.applyEdits(text, edits);
6277
modified = true;
6378
}
@@ -69,3 +84,4 @@ export function addMcpServers(
6984

7085
return modified;
7186
}
87+

packages/ng-schematics/src/cli-config/ai-config-schema.json

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
"enum": ["none", "claude", "copilot", "cursor", "codex", "windsurf", "gemini", "junie", "generic"]
1616
},
1717
"x-prompt": {
18-
"message": "Which AI tools do you want to generate configuration files for?",
18+
"message": "Which AI agents do you want to generate skills and instructions for?",
1919
"type": "list",
2020
"multiselect": true,
2121
"items": [
22-
{ "value": "none", "label": "None (skip AI configuration)" },
22+
{ "value": "none", "label": "None (skip skills and instructions)" },
2323
{ "value": "generic", "label": "Generic (Adding .agents/skills and AGENTS.md)" },
2424
{ "value": "claude", "label": "Claude (Adding .claude/skills and CLAUDE.md)" },
2525
{ "value": "copilot", "label": "Copilot (Adding .github/skills and copilot-instructions.md)" },
@@ -30,6 +30,23 @@
3030
{ "value": "junie", "label": "Junie (Adding .junie/skills and .junie/guidelines.md)" }
3131
]
3232
}
33+
},
34+
"assistant": {
35+
"type": "string",
36+
"description": "Coding assistant to configure MCP servers for.",
37+
"default": "vscode",
38+
"enum": ["vscode", "cursor", "claude-code", "gemini", "junie"],
39+
"x-prompt": {
40+
"message": "Which coding assistant should MCP servers be configured for?",
41+
"type": "list",
42+
"items": [
43+
{ "value": "vscode", "label": "VS Code (GitHub Copilot)" },
44+
{ "value": "cursor", "label": "Cursor" },
45+
{ "value": "claude-code", "label": "Claude Code" },
46+
{ "value": "gemini", "label": "Gemini" },
47+
{ "value": "junie", "label": "JetBrains Junie" }
48+
]
49+
}
3350
}
3451
}
3552
}

packages/ng-schematics/src/cli-config/index.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as ts from "typescript";
22
import { DependencyNotFoundException } from "@angular-devkit/core";
33
import { chain, FileDoesNotExistException, Rule, SchematicContext, Tree } from "@angular-devkit/schematics";
44
import { RunSchematicTask } from "@angular-devkit/schematics/tasks";
5-
import { addClassToBody, addMcpServers, AIAgentTarget, App, copyAgentInstructionFiles, copyAISkillsToProject, FormatSettings, McpServerEntry, NPM_ANGULAR, resolvePackage, TEMPLATE_MANAGER, TypeScriptAstTransformer, TypeScriptUtils, VS_CODE_MCP_PATH } from "@igniteui/cli-core";
5+
import { addClassToBody, addMcpServers, AIAgentTarget, AiCodingAssistant, App, copyAgentInstructionFiles, copyAISkillsToProject, FormatSettings, McpServerEntry, NPM_ANGULAR, resolvePackage, TEMPLATE_MANAGER, TypeScriptAstTransformer, TypeScriptUtils, VS_CODE_MCP_PATH } from "@igniteui/cli-core";
66
import { AngularTypeScriptFileUpdate } from "@igniteui/angular-templates";
77
import { createCliConfig } from "../utils/cli-config";
88
import { setVirtual } from "../utils/NgFileSystem";
@@ -127,7 +127,7 @@ function appInit(tree: Tree) {
127127
setVirtual(tree);
128128
}
129129

130-
function aiConfig({ init, agents }: { init: boolean; agents: AIAgentTarget[] }): Rule {
130+
function aiConfig({ init, agents, assistant = "vscode" }: { init: boolean; agents: AIAgentTarget[]; assistant?: AiCodingAssistant }): Rule {
131131
return (tree: Tree) => {
132132
if (init) {
133133
appInit(tree);
@@ -142,18 +142,15 @@ function aiConfig({ init, agents }: { init: boolean; agents: AIAgentTarget[] }):
142142
}
143143
};
144144

145-
addMcpServers(VS_CODE_MCP_PATH, angularCliServer);
145+
addMcpServers(assistant, angularCliServer);
146146
};
147147
}
148148

149149
/** Standalone `ai-config` schematic entry */
150-
export function addAIConfig(options: { agents?: AIAgentTarget[] } = {}): Rule {
150+
export function addAIConfig(options: { agents?: AIAgentTarget[]; assistant?: AiCodingAssistant } = {}): Rule {
151151
const selected = options.agents?.length ? options.agents : [] as AIAgentTarget[];
152152
const agents = selected.includes("none" as any) ? [] : selected;
153-
if (!agents.length) {
154-
return (tree: Tree) => tree;
155-
}
156-
return aiConfig({ init: true, agents });
153+
return aiConfig({ init: true, agents, assistant: options.assistant });
157154
}
158155

159156
export default function (): Rule {

packages/ng-schematics/src/cli-config/index_spec.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,5 +432,37 @@ export const appConfig: ApplicationConfig = {
432432
expect(aiSkillsModule.copyAISkillsToProject).toHaveBeenCalledWith(["claude", "cursor"]);
433433
expect(aiSkillsModule.copyAgentInstructionFiles).toHaveBeenCalledWith(["claude", "cursor"]);
434434
});
435+
436+
it("should default MCP config to .vscode/mcp.json with servers key", async () => {
437+
await runner.runSchematic("ai-config", {}, tree);
438+
439+
const mcpFilePath = "/.vscode/mcp.json";
440+
expect(tree.exists(mcpFilePath)).toBeTruthy();
441+
const content = JSON.parse(tree.readContent(mcpFilePath));
442+
expect(content.servers).toBeDefined();
443+
expect(content.servers["igniteui-cli"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli", "mcp"] });
444+
});
445+
446+
it("should write to .cursor/mcp.json with mcpServers key when assistant is cursor", async () => {
447+
await runner.runSchematic("ai-config", { assistant: "cursor" }, tree);
448+
449+
const mcpFilePath = "/.cursor/mcp.json";
450+
expect(tree.exists(mcpFilePath)).toBeTruthy();
451+
const content = JSON.parse(tree.readContent(mcpFilePath));
452+
expect(content.mcpServers).toBeDefined();
453+
expect(content.mcpServers["igniteui-cli"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli", "mcp"] });
454+
expect(content.mcpServers["angular-cli"]).toEqual({ command: "npx", args: ["-y", "@angular/cli", "mcp"] });
455+
expect(content.servers).toBeUndefined();
456+
});
457+
458+
it("should write to .mcp.json when assistant is claude-code", async () => {
459+
await runner.runSchematic("ai-config", { assistant: "claude-code" }, tree);
460+
461+
const mcpFilePath = "/.mcp.json";
462+
expect(tree.exists(mcpFilePath)).toBeTruthy();
463+
const content = JSON.parse(tree.readContent(mcpFilePath));
464+
expect(content.mcpServers["igniteui-cli"]).toBeDefined();
465+
expect(content.mcpServers["angular-cli"]).toBeDefined();
466+
});
435467
});
436468
});

0 commit comments

Comments
 (0)