Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
25318e0
feat: enhance MCP configuration for multiple coding assistants and up…
Marina-L-Stoyanova May 8, 2026
322523e
feat: update MCP configuration to support multiple coding assistants …
Marina-L-Stoyanova May 8, 2026
5ae1e91
feat: enhance AI configuration by adding assistant labels and improvi…
Marina-L-Stoyanova May 11, 2026
11b80bb
Potential fix for pull request finding
Marina-L-Stoyanova May 11, 2026
8e8fb45
feat: update AI configuration to include assistant selection and modi…
Marina-L-Stoyanova May 11, 2026
6c408a8
fix: ai-config tests
Marina-L-Stoyanova May 12, 2026
39bba34
feat: update AI assistant configuration to include 'general' and modi…
Marina-L-Stoyanova May 12, 2026
d50be45
fix: adjust order of operations in new command to configure before pa…
Marina-L-Stoyanova May 12, 2026
2f7b429
fix: update launch configuration to change argument from 'ai-config' …
Marina-L-Stoyanova May 12, 2026
00758bb
fix: update AI assistant labels for clarity and consistency
Marina-L-Stoyanova May 12, 2026
b119049
fix: update addAIConfig function to correct assistants parameter and …
Marina-L-Stoyanova May 12, 2026
934ba2b
fix: rename 'assistant' to 'assistants' in AI config schema and updat…
Marina-L-Stoyanova May 12, 2026
a75b5f8
Merge branch 'master' into mstoyanova/update-mcp-config-agent-agnosti…
damyanpetev May 12, 2026
9af062f
chore: agent skills descriptions
damyanpetev May 12, 2026
1320544
fix: update configureMCP function to require assistants parameter and…
Marina-L-Stoyanova May 12, 2026
a8d5fd1
fix: update ai-config schematic tests to use correct assistants param…
Marina-L-Stoyanova May 12, 2026
67a679d
chore: formatting
damyanpetev May 12, 2026
80ba262
refactor(schematics): leftover default param
damyanpetev May 12, 2026
3968806
fix(schematics): mcp config none handling
damyanpetev May 12, 2026
487bb0a
refactor: move skill flag param at end
damyanpetev May 12, 2026
3a0962d
fix(ai-config): still config mcp w/ none agents selected
damyanpetev May 12, 2026
3bac0ac
Merge remote-tracking branch 'origin/master' into mstoyanova/update-m…
damyanpetev May 12, 2026
ca9899d
fix(ai-config): leftover change for config re-prompt call
damyanpetev May 12, 2026
d0fdb37
update: Update README.md
Marina-L-Stoyanova May 12, 2026
ee7bc1e
Update packages/core/util/mcp-config.ts
Marina-L-Stoyanova May 12, 2026
b15c512
fix(ai-config): update assistant terminology from "general" to "generic"
Marina-L-Stoyanova May 12, 2026
559e74b
fix(ai-config): update configure function to handle "none" option and…
Marina-L-Stoyanova May 12, 2026
f68207c
fix(new-command): reorder project directory change and configuration …
Marina-L-Stoyanova May 13, 2026
8d84906
fix(ai-config): simplify agent and assistant configuration prompts
Marina-L-Stoyanova May 13, 2026
063ddbf
Merge branch 'master' into mstoyanova/update-mcp-config-agent-agnosti…
damyanpetev May 13, 2026
df06219
chore(ai-configure: remove redundant prompt param
damyanpetev May 13, 2026
88ce66f
chore: alias repeated types
damyanpetev May 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,11 @@
"program": "${workspaceRoot}/packages/cli/bin/execute.js",
"console": "externalTerminal",
"preLaunchTask": "build",
"outFiles": [ "${workspaceRoot}/lib/**/*.js",
"${workspaceRoot}/spec/**/*.js" ],
"args": [
"new",
"angularproj",
"--framework=angular"
"--framework=angular",
"--skip-install"
]
},
{
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 15.1.0

## What's Changed
* feat(ai-config): The ai-config command now enables users to select and configure an AI coding assistant integration for their Ignite UI project. Based on the selected provider, the command adds the required skills, instruction files, and MCP configuration to streamline AI-assisted development workflows. The `new` command now accepts `--agents` (for adding skills and instruction files) and `--assistants`(for adding mcp configuration) options, allowing users to configure AI integration directly during project creation. When using the step-by-step wizard, users are automatically prompted to select their preferred AI agents and coding assistants after the project structure is generated. The Angular schematics package includes a dedicated `ai-config` schematic that runs automatically during `ng new` with Ignite UI and can also be invoked standalone via `ng generate @igniteui/angular-schematics:ai-config` to add AI configuration to existing Angular projects. by @Marina-L-Stoyanova https://github.com/IgniteUI/igniteui-cli/pull/1684

# 15.0.1 (2026-04-28)

## What's Changed
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,17 @@ To configure Ignite UI AI tooling — MCP servers and AI coding skills — run:
ig ai-config
```

You will be prompted to select which AI tools to configure (Claude and Generic are selected by default). You can also pass agents directly:
You will be prompted with two selections:
- **AI agents** — which tools to generate skill and instruction files for (Generic and Claude are selected by default)
- **Coding assistants** — which assistants to configure MCP servers for (general `.mcp.json` is selected by default, compatible with Claude Code, VS Code, and others)

You can also pass options directly:

```bash
ig ai-config --agents claude copilot generic
ig ai-config --agents claude copilot generic --assistants vscode cursor
```

This creates or updates `.vscode/mcp.json` with entries for the [Ignite UI MCP](#mcp-server) and `igniteui-theming` MCP servers (existing servers are preserved), copies AI coding skill files from installed Ignite UI packages, and generates agent-specific instruction files (e.g. `CLAUDE.md`, `AGENTS.md`).
This creates or updates the assistant-specific MCP config file (e.g. `.mcp.json`, `.vscode/mcp.json`, `.cursor/mcp.json`) with entries for the [Ignite UI MCP](#mcp-server) and `igniteui-theming` MCP servers (existing servers are preserved), copies AI coding skill files from installed Ignite UI packages, and generates agent-specific instruction files (e.g. `CLAUDE.md`, `AGENTS.md`).

The `ig new` command also prompts for AI tool configuration as part of project creation.

Expand Down
112 changes: 79 additions & 33 deletions packages/cli/lib/commands/ai-config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { addMcpServers, AI_AGENT_LABELS, AI_AGENT_CHOICES, AIAgentTarget, copyAgentInstructionFiles, copyAISkillsToProject, GoogleAnalytics, InquirerWrapper, Util, VS_CODE_MCP_PATH } from "@igniteui/cli-core";
import { addMcpServers, AI_AGENT_LABELS, AI_AGENT_CHOICES, AIAgentTarget, copyAgentInstructionFiles, copyAISkillsToProject, GoogleAnalytics, InquirerWrapper, Util, AiCodingAssistant, AI_ASSISTANT_MCP_CONFIGS, AI_ASSISTANT_CHOICES, AI_ASSISTANT_LABELS } from "@igniteui/cli-core";
import { ArgumentsCamelCase, CommandModule } from "yargs";

export function configureMCP(): void {
const modified = addMcpServers(VS_CODE_MCP_PATH);
export function configureMCP(assistants: AiCodingAssistant[]): void {
for (const assistant of assistants) {
const { mcpFilePath } = AI_ASSISTANT_MCP_CONFIGS[assistant];
const modified = addMcpServers(assistant);

if (!modified) {
Util.log(` Ignite UI MCP servers already configured in ${VS_CODE_MCP_PATH}`);
return;
if (!modified) {
Util.log(` Ignite UI MCP servers already configured in ${mcpFilePath}`);
} else {
Util.log(Util.greenCheck() + ` MCP servers configured in ${mcpFilePath}`);
}
}
Util.log(Util.greenCheck() + ` MCP servers configured in ${VS_CODE_MCP_PATH}`);
}

export function configureSkills(agents: AIAgentTarget[]): void {
Expand All @@ -26,36 +29,81 @@ export function configureSkills(agents: AIAgentTarget[]): void {
}
}

export async function configure(agents?: AIAgentTarget[], skills = true): Promise<void> {
if (!agents?.length) {
type AIAgentOption = AIAgentTarget | "none";
type AIAssistantOption = AiCodingAssistant | "none";

export async function configure(agents: AIAgentOption[] = [], assistants: AIAssistantOption[] = [], skills = true): Promise<{ agents: AIAgentTarget[], assistants: AiCodingAssistant[] }> {
if (!agents.length) {
agents = await promptForAgents();
}
if (!agents.length) return;
configureMCP();
if (skills) {
configureSkills(agents);

if (!assistants.length) {
assistants = await promptForAssistant();
}

const resolvedAgents: AIAgentTarget[] = agents.includes("none") ? [] : agents as AIAgentTarget[];
const resolvedAssistants: AiCodingAssistant[] = assistants.includes("none") ? [] : assistants as AiCodingAssistant[];

if (!resolvedAssistants.length) {
Util.log("No MCP configuration selected. Skipping.");
}
copyAgentInstructionFiles(agents);
configureMCP(resolvedAssistants);

if (!resolvedAgents.length) {
Util.log("No AI configuration selected. Skipping.");
} else {
if (skills) {
configureSkills(resolvedAgents);
}
copyAgentInstructionFiles(resolvedAgents);
}

return { agents: resolvedAgents, assistants: resolvedAssistants };
}

const AI_AGENT_CHECKBOX_DEFAULTS: AIAgentTarget[] = ["generic", "claude"];
const AI_ASSISTANT_CHECKBOX_DEFAULTS: AiCodingAssistant[] = ["generic"];

const AI_AGENT_CHECKBOX_CHOICES = [
{ value: "none", name: "None (skip AI configuration)" },
{ value: "none", name: "None (skip skills and instructions)" },
...AI_AGENT_CHOICES.map(agent => ({
value: agent,
name: AI_AGENT_LABELS[agent],
checked: AI_AGENT_CHECKBOX_DEFAULTS.includes(agent)
}))
];

export async function promptForAgents(): Promise<AIAgentTarget[]> {
let selected: AIAgentTarget[] = AI_AGENT_CHECKBOX_DEFAULTS;
const AI_ASSISTANT_CHECKBOX_CHOICES = [
{ value: "none", name: "None (skip MCP configuration)" },
...AI_ASSISTANT_CHOICES.map(a => ({
value: a,
name: AI_ASSISTANT_LABELS[a],
checked: AI_ASSISTANT_CHECKBOX_DEFAULTS.includes(a)
}))
];

export async function promptForAgents(): Promise<AIAgentOption[]> {
let selected: AIAgentOption[] = AI_AGENT_CHECKBOX_DEFAULTS;
if (Util.canPrompt()) {
const result = await InquirerWrapper.checkbox({
message: "Which AI tools do you want to generate configuration files for?",
message: "Which AI agents do you want to generate skills and instructions for?",
required: true,
choices: AI_AGENT_CHECKBOX_CHOICES
});
selected = result.includes("none") ? [] : result as AIAgentTarget[];
selected = result as AIAgentOption[];
}
return selected;
}

export async function promptForAssistant(): Promise<AIAssistantOption[]> {
let selected: AIAssistantOption[] = AI_ASSISTANT_CHECKBOX_DEFAULTS;
if (Util.canPrompt()) {
const result = await InquirerWrapper.checkbox({
message: "Which coding assistants should MCP servers be configured for?",
required: true,
choices: AI_ASSISTANT_CHECKBOX_CHOICES
});
selected = result as AIAssistantOption[];
}
return selected;
}
Expand All @@ -65,33 +113,31 @@ const command: CommandModule = {
describe: "Configures Ignite UI AI tooling (MCP servers, AI coding skills and instructions)",
builder: (yargs) => yargs
.usage("")
.option("agent", {
alias: "a",
.option("agents", {
describe: "AI agents/tools to generate configuration files for",
choices: AI_AGENT_CHOICES,
choices: [...AI_AGENT_CHOICES, "none"] as string[],
type: "array"
})
.option("assistants", {
describe: "Coding assistant(s) to configure MCP servers for",
choices: [...AI_ASSISTANT_CHOICES, "none"] as string[],
type: "array"
}),
async handler(argv: ArgumentsCamelCase) {
let agents = argv.agent as AIAgentTarget[] | undefined;
const agents = (argv.agents ?? []) as AIAgentOption[];
const assistants = (argv.assistants ?? []) as AIAssistantOption[];
GoogleAnalytics.post({
t: "screenview",
cd: "Ai Config"
});

if (!agents?.length) {
agents = await promptForAgents();
}
const result = await configure(agents, assistants);

GoogleAnalytics.post({
t: "event",
ec: "$ig ai-config",
ea: `agent: ${agents.join(", ")}`
ea: `agent: ${result.agents.join(", ") || "none"}; assistant: ${result.assistants.join(", ") || "none"}`
});

if (!agents.length) {
Util.log("No AI configuration selected. Skipping.");
return;
}
await configure(agents);
}
};

Expand Down
18 changes: 11 additions & 7 deletions packages/cli/lib/commands/new.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AI_AGENT_CHOICES, AIAgentTarget, App, type BaseTemplateManager, GoogleAnalytics, PackageManager, ProjectConfig, ProjectLibrary, TEMPLATE_MANAGER, Util } from "@igniteui/cli-core";
import { AI_AGENT_CHOICES, AI_ASSISTANT_CHOICES, AIAgentTarget, type AiCodingAssistant, App, type BaseTemplateManager, GoogleAnalytics, PackageManager, ProjectConfig, ProjectLibrary, TEMPLATE_MANAGER, Util } from "@igniteui/cli-core";
import * as path from "path";
import { PromptSession } from "./../PromptSession";
import { NewCommandType, PositionalArgs } from "./types";
Expand Down Expand Up @@ -60,9 +60,13 @@ const command: NewCommandType = {
type: "string"
})
.option("agents", {
alias: "a",
describe: "AI agents/tools to generate configuration files for",
choices: AI_AGENT_CHOICES,
choices: [...AI_AGENT_CHOICES, "none"] as string[],
type: "array"
})
.option("assistants", {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, have we considered naming this something else, like --mcp?

describe: "Coding assistant(s) to configure MCP servers for",
choices: [...AI_ASSISTANT_CHOICES, "none"] as string[],
type: "array"
})
.example("$0 new my-app", "Scaffold a new project interactively")
Expand Down Expand Up @@ -157,6 +161,10 @@ const command: NewCommandType = {
config, projTemplate.delimiters, false);
}

process.chdir(argv.name);
await configure(argv.agents as (AIAgentTarget | "none")[], argv.assistants as (AiCodingAssistant | "none")[]);
process.chdir("..");

Util.log(Util.greenCheck() + " Project Created");

if (!argv.skipInstall) {
Expand All @@ -165,10 +173,6 @@ const command: NewCommandType = {
process.chdir("..");
}

process.chdir(argv.name);
await configure(argv.agents as AIAgentTarget[] | undefined);
process.chdir("..");

if (!argv["skip-git"] && !ProjectConfig.getConfig().skipGit) {
Util.gitInit(process.cwd(), argv.name);
}
Expand Down
16 changes: 8 additions & 8 deletions packages/core/util/ai-skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ const AI_AGENT_INSTRUCTION_FILES: Record<AIAgentTarget, string> = {
};

export const AI_AGENT_LABELS: Record<AIAgentTarget, string> = {
generic: "Generic (Adding .agents/skills and AGENTS.md)",
claude: "Claude (Adding .claude/skills and CLAUDE.md)",
copilot: "Copilot (Adding .github/skills and copilot-instructions.md)",
cursor: "Cursor (Adding .cursor/skills and .cursor/rules/cursor.mdc)",
codex: "Codex (Adding .codex/skills and .codex/instructions.md)",
windsurf: "Windsurf (Adding .windsurf/skills and .windsurf/rules/guidelines.md)",
gemini: "Gemini (Adding .gemini/skills and .gemini/GEMINI.md)",
junie: "Junie (Adding .junie/skills and .junie/guidelines.md)"
generic: "Generic (Add .agents/skills and AGENTS.md general for most assistants)",
claude: "Claude (Add .claude/skills and CLAUDE.md)",
copilot: "Copilot (Add .github/skills and copilot-instructions.md)",
cursor: "Cursor (Add .cursor/skills and .cursor/rules/cursor.mdc)",
codex: "Codex (Add .codex/skills and .codex/instructions.md)",
windsurf: "Windsurf (Add .windsurf/skills and .windsurf/rules/guidelines.md)",
gemini: "Gemini (Add .gemini/skills and .gemini/GEMINI.md)",
junie: "Junie (Add .junie/skills and .junie/guidelines.md)"
};

/**
Expand Down
38 changes: 31 additions & 7 deletions packages/core/util/mcp-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@ export interface McpServerEntry {
args: string[];
}

export const AI_ASSISTANT_CHOICES = ["generic", "vscode", "cursor", "gemini", "junie"] as const;
export type AiCodingAssistant = typeof AI_ASSISTANT_CHOICES[number];

interface AssistantMcpConfig {
mcpFilePath: string;
rootKey: "servers" | "mcpServers";
}

export const AI_ASSISTANT_LABELS: Record<AiCodingAssistant, string> = {
"generic": ".mcp.json (generic for Claude Code, VS Code, and other assistants)",
"vscode": "VS Code (GitHub Copilot)",
"cursor": "Cursor",
"gemini": "Gemini",
"junie": "JetBrains Junie",
};

export const AI_ASSISTANT_MCP_CONFIGS: Record<AiCodingAssistant, AssistantMcpConfig> = {
"generic": { mcpFilePath: ".mcp.json", rootKey: "mcpServers" },
"vscode": { mcpFilePath: ".vscode/mcp.json", rootKey: "servers" },
"cursor": { mcpFilePath: ".cursor/mcp.json", rootKey: "mcpServers" },
"gemini": { mcpFilePath: ".gemini/settings.json", rootKey: "mcpServers" },
"junie": { mcpFilePath: ".junie/mcp/mcp.json", rootKey: "mcpServers" },
};

const IGNITEUI_MCP_SERVERS: Record<string, McpServerEntry> = {
"igniteui-cli": {
command: "npx",
Expand All @@ -18,18 +42,18 @@ const IGNITEUI_MCP_SERVERS: Record<string, McpServerEntry> = {
}
};

export const VS_CODE_MCP_PATH = ".vscode/mcp.json";

/**
* Reads .vscode/mcp.json, ensures all IgniteUI MCP servers are present,
* Reads the assistant-specific MCP config file, ensures all IgniteUI MCP servers are present,
* optionally adds additional servers. Creates the file if it doesn't exist.
* @param assistant target AI coding assistant (defaults to "vscode")
* @param additionalServers optional extra servers to include alongside the built-in ones
* @returns whether the file was modified
*/
export function addMcpServers(
mcpFilePath: string,
assistant: AiCodingAssistant,
additionalServers?: Record<string, McpServerEntry>
): boolean {
const { mcpFilePath, rootKey } = AI_ASSISTANT_MCP_CONFIGS[assistant];
const fileSystem = App.container.get<IFileSystem>(FS_TOKEN);
const servers = { ...additionalServers, ...IGNITEUI_MCP_SERVERS };

Expand All @@ -44,20 +68,20 @@ export function addMcpServers(
if (Object.keys(servers).length === 0) {
return false;
}
fileSystem.writeFile(mcpFilePath, JSON.stringify({ servers }, null, 2) + "\n");
fileSystem.writeFile(mcpFilePath, JSON.stringify({ [rootKey]: servers }, null, 2) + "\n");
return true;
}

const parsed = jsonc.parse(existingContent);
const existing = parsed.servers ?? {};
const existing = parsed[rootKey] ?? {};
const formattingOptions: jsonc.FormattingOptions = { tabSize: 2, insertSpaces: true };

let text = existingContent;
let modified = false;

for (const [key, value] of Object.entries(servers)) {
if (!existing[key]) {
const edits = jsonc.modify(text, ["servers", key], value, { formattingOptions });
const edits = jsonc.modify(text, [rootKey, key], value, { formattingOptions });
text = jsonc.applyEdits(text, edits);
modified = true;
}
Expand Down
Loading
Loading