From c5c46fb6dafd9bf0585e03ff59efc0e6cb65ceeb Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 21 Apr 2026 13:26:13 +0300 Subject: [PATCH 1/5] feat: integrate MCP server configuration with jsonc-parser and refactor AI config handling --- packages/cli/lib/commands/ai-config.ts | 55 +++--------------- packages/core/package.json | 1 + packages/core/util/McpConfig.ts | 52 +++++++++++++++++ packages/core/util/index.ts | 1 + packages/ng-schematics/package.json | 1 - .../ng-schematics/src/cli-config/index.ts | 56 +++++-------------- 6 files changed, 76 insertions(+), 90 deletions(-) create mode 100644 packages/core/util/McpConfig.ts diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index 9f4616521..d64adba78 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -1,65 +1,26 @@ -import { copyAISkillsToProject, FsFileSystem, GoogleAnalytics, IFileSystem, Util } from "@igniteui/cli-core"; +import { addMcpServers, copyAISkillsToProject, FsFileSystem, GoogleAnalytics, IGNITEUI_MCP_SERVERS, IFileSystem, Util } from "@igniteui/cli-core"; import { ArgumentsCamelCase, CommandModule } from "yargs"; import * as path from "path"; -const IGNITEUI_SERVER_KEY = "igniteui-cli"; -const IGNITEUI_THEMING_SERVER_KEY = "igniteui-theming"; - -const igniteuiServer = { - command: "npx", - args: ["-y", "igniteui-cli@next", "mcp"] -}; - -const igniteuiThemingServer = { - command: "npx", - args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] -}; - -interface McpServerEntry { - command: string; - args: string[]; -} - -interface VsCodeMcpConfig { - servers: Record; -} - function getConfigPath(): string { return path.join(process.cwd(), ".vscode", "mcp.json"); } -function readJson(filePath: string, fallback: T, fileSystem: IFileSystem): T { - try { - return JSON.parse(fileSystem.readFile(filePath)) as T; - } catch { - return fallback; - } -} - -function writeJson(filePath: string, data: unknown, fileSystem: IFileSystem): void { - fileSystem.writeFile(filePath, JSON.stringify(data, null, 2) + "\n"); -} - export function configureMCP(fileSystem: IFileSystem = new FsFileSystem()): void { const configPath = getConfigPath(); - const config = readJson(configPath, { servers: {} }, fileSystem); - config.servers = config.servers || {}; - - let modified = false; - if (!config.servers[IGNITEUI_SERVER_KEY]) { - config.servers[IGNITEUI_SERVER_KEY] = igniteuiServer; - modified = true; - } - if (!config.servers[IGNITEUI_THEMING_SERVER_KEY]) { - config.servers[IGNITEUI_THEMING_SERVER_KEY] = igniteuiThemingServer; - modified = true; + let existingContent: string | undefined; + try { + existingContent = fileSystem.readFile(configPath); + } catch { + // file doesn't exist } + const { text, modified } = addMcpServers(existingContent, IGNITEUI_MCP_SERVERS); if (!modified) { Util.log(` Ignite UI MCP servers already configured in ${configPath}`); return; } - writeJson(configPath, config, fileSystem); + fileSystem.writeFile(configPath, text + "\n"); Util.log(Util.greenCheck() + ` MCP servers configured in ${configPath}`); } diff --git a/packages/core/package.json b/packages/core/package.json index 33f4a617e..a94e0d2ae 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -15,6 +15,7 @@ "@inquirer/prompts": "^7.9.0", "chalk": "^2.3.2", "glob": "^11.0.0", + "jsonc-parser": "3.3.1", "through2": "^2.0.3", "typescript": "~5.5.4" }, diff --git a/packages/core/util/McpConfig.ts b/packages/core/util/McpConfig.ts new file mode 100644 index 000000000..a8f526423 --- /dev/null +++ b/packages/core/util/McpConfig.ts @@ -0,0 +1,52 @@ +import * as jsonc from "jsonc-parser"; + +export interface McpServerEntry { + command: string; + args: string[]; +} + +export const IGNITEUI_MCP_SERVERS: Record = { + "igniteui-cli": { + command: "npx", + args: ["-y", "igniteui-cli@next", "mcp"] + }, + "igniteui-theming": { + command: "npx", + args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] + } +}; + +/** + * Adds MCP servers to mcp.json content using jsonc-parser to preserve comments. + * @param existingContent existing file content, or undefined/empty if file doesn't exist + * @param servers servers to add (skips keys already present) + * @returns the resulting text and whether any changes were made + */ +export function addMcpServers( + existingContent: string | undefined, + servers: Record +): { text: string; modified: boolean } { + if (!existingContent) { + return { + text: JSON.stringify({ servers: { ...servers } }, null, 2), + modified: Object.keys(servers).length > 0 + }; + } + + const parsed = jsonc.parse(existingContent); + const existing = parsed.servers ?? {}; + 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 }); + text = jsonc.applyEdits(text, edits); + modified = true; + } + } + + return { text, modified }; +} diff --git a/packages/core/util/index.ts b/packages/core/util/index.ts index b9b7ada55..a6b3cad4f 100644 --- a/packages/core/util/index.ts +++ b/packages/core/util/index.ts @@ -1,6 +1,7 @@ export * from './ai-skills'; export * from './detect-framework'; export * from './GoogleAnalytics'; +export * from './McpConfig'; export * from './Util'; export * from './ProjectConfig'; export * from './Schematics'; diff --git a/packages/ng-schematics/package.json b/packages/ng-schematics/package.json index d7b18ae29..9f750cde3 100644 --- a/packages/ng-schematics/package.json +++ b/packages/ng-schematics/package.json @@ -23,7 +23,6 @@ "@igniteui/angular-templates": "~21.1.1500-rc.2", "@igniteui/cli-core": "~15.0.0-rc.2", "@schematics/angular": "^21.0.0", - "jsonc-parser": "3.3.1", "minimatch": "^10.0.1", "rxjs": "~7.8.1" }, diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index 147fd331d..d54955122 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -1,8 +1,7 @@ import * as ts from "typescript"; import { DependencyNotFoundException } from "@angular-devkit/core"; import { chain, FileDoesNotExistException, Rule, SchematicContext, Tree } from "@angular-devkit/schematics"; -import * as jsonc from "jsonc-parser"; -import { addClassToBody, App, copyAISkillsToProject, FormatSettings, NPM_ANGULAR, resolvePackage, TEMPLATE_MANAGER, TypeScriptAstTransformer, TypeScriptUtils } from "@igniteui/cli-core"; +import { addClassToBody, addMcpServers, App, copyAISkillsToProject, FormatSettings, IGNITEUI_MCP_SERVERS, McpServerEntry, NPM_ANGULAR, resolvePackage, TEMPLATE_MANAGER, TypeScriptAstTransformer, TypeScriptUtils } from "@igniteui/cli-core"; import { AngularTypeScriptFileUpdate } from "@igniteui/angular-templates"; import { createCliConfig } from "../utils/cli-config"; import { setVirtual } from "../utils/NgFileSystem"; @@ -124,50 +123,23 @@ export function addAIConfig(): Rule { copyAISkillsToProject(); const mcpFilePath = "/.vscode/mcp.json"; - const angularCliServer = { - command: "npx", - args: ["-y", "@angular/cli", "mcp"] - }; - const igniteuiServer = { - command: "npx", - args: ["-y", "igniteui-cli@next", "mcp"] - }; - const igniteuiThemingServer = { - command: "npx", - args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] + const servers: Record = { + "angular-cli": { + command: "npx", + args: ["-y", "@angular/cli", "mcp"] + }, + ...IGNITEUI_MCP_SERVERS }; - if (tree.exists(mcpFilePath)) { - let text = tree.read(mcpFilePath)!.toString(); - const content = jsonc.parse(text); - const servers = content.servers ?? {}; - const formattingOptions: jsonc.FormattingOptions = { tabSize: 2, insertSpaces: true }; - const newServers: Record = {}; - if (!servers["angular-cli"]) { - newServers["angular-cli"] = angularCliServer; - } - if (!servers["igniteui-cli"]) { - newServers["igniteui-cli"] = igniteuiServer; - } - if (!servers["igniteui-theming"]) { - newServers["igniteui-theming"] = igniteuiThemingServer; - } - for (const [key, value] of Object.entries(newServers)) { - const edits = jsonc.modify(text, ["servers", key], value, { formattingOptions }); - text = jsonc.applyEdits(text, edits); - } - if (Object.keys(newServers).length > 0) { + const existingContent = tree.exists(mcpFilePath) ? tree.read(mcpFilePath)!.toString() : undefined; + const { text, modified } = addMcpServers(existingContent, servers); + + if (modified) { + if (tree.exists(mcpFilePath)) { tree.overwrite(mcpFilePath, text); + } else { + tree.create(mcpFilePath, text); } - } else { - const mcpConfig = { - servers: { - "angular-cli": angularCliServer, - "igniteui-cli": igniteuiServer, - "igniteui-theming": igniteuiThemingServer - } - }; - tree.create(mcpFilePath, JSON.stringify(mcpConfig, null, 2)); } }; } From bccb9483dfe6f6253a559cc39251fb8b5a5f5cb4 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 21 Apr 2026 16:14:57 +0300 Subject: [PATCH 2/5] feat: refactor MCP server configuration and enhance jsonc handling --- packages/cli/lib/commands/ai-config.ts | 14 ++++------- packages/core/util/index.ts | 2 +- .../core/util/{McpConfig.ts => mcp-config.ts} | 23 ++++++++++++++++++- .../ng-schematics/src/cli-config/index.ts | 22 +++++++----------- 4 files changed, 36 insertions(+), 25 deletions(-) rename packages/core/util/{McpConfig.ts => mcp-config.ts} (62%) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index d64adba78..cbb52b844 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -1,4 +1,4 @@ -import { addMcpServers, copyAISkillsToProject, FsFileSystem, GoogleAnalytics, IGNITEUI_MCP_SERVERS, IFileSystem, Util } from "@igniteui/cli-core"; +import { configureMcpFile, copyAISkillsToProject, FsFileSystem, GoogleAnalytics, IFileSystem, Util } from "@igniteui/cli-core"; import { ArgumentsCamelCase, CommandModule } from "yargs"; import * as path from "path"; @@ -8,19 +8,15 @@ function getConfigPath(): string { export function configureMCP(fileSystem: IFileSystem = new FsFileSystem()): void { const configPath = getConfigPath(); - let existingContent: string | undefined; - try { - existingContent = fileSystem.readFile(configPath); - } catch { - // file doesn't exist - } - const { text, modified } = addMcpServers(existingContent, IGNITEUI_MCP_SERVERS); + const modified = configureMcpFile( + () => { try { return fileSystem.readFile(configPath); } catch { return undefined; } }, + (text) => fileSystem.writeFile(configPath, text + "\n") + ); if (!modified) { Util.log(` Ignite UI MCP servers already configured in ${configPath}`); return; } - fileSystem.writeFile(configPath, text + "\n"); Util.log(Util.greenCheck() + ` MCP servers configured in ${configPath}`); } diff --git a/packages/core/util/index.ts b/packages/core/util/index.ts index a6b3cad4f..045234447 100644 --- a/packages/core/util/index.ts +++ b/packages/core/util/index.ts @@ -1,7 +1,7 @@ export * from './ai-skills'; export * from './detect-framework'; export * from './GoogleAnalytics'; -export * from './McpConfig'; +export * from './mcp-config'; export * from './Util'; export * from './ProjectConfig'; export * from './Schematics'; diff --git a/packages/core/util/McpConfig.ts b/packages/core/util/mcp-config.ts similarity index 62% rename from packages/core/util/McpConfig.ts rename to packages/core/util/mcp-config.ts index a8f526423..06329f0a9 100644 --- a/packages/core/util/McpConfig.ts +++ b/packages/core/util/mcp-config.ts @@ -5,7 +5,7 @@ export interface McpServerEntry { args: string[]; } -export const IGNITEUI_MCP_SERVERS: Record = { +const IGNITEUI_MCP_SERVERS: Record = { "igniteui-cli": { command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] @@ -50,3 +50,24 @@ export function addMcpServers( return { text, modified }; } + +/** + * Reads existing mcp.json content, merges servers, and writes back if changed. + * @param readFile callback to read existing content (return undefined if file doesn't exist) + * @param writeFile callback to write the resulting content + * @param extraServers additional servers to include alongside the built-in ones + * @returns whether the file was modified + */ +export function configureMcpFile( + readFile: () => string | undefined, + writeFile: (content: string) => void, + extraServers?: Record +): boolean { + const servers = { ...extraServers, ...IGNITEUI_MCP_SERVERS }; + const existingContent = readFile(); + const { text, modified } = addMcpServers(existingContent, servers); + if (modified) { + writeFile(text); + } + return modified; +} diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index d54955122..2856dcc58 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -1,7 +1,7 @@ import * as ts from "typescript"; import { DependencyNotFoundException } from "@angular-devkit/core"; import { chain, FileDoesNotExistException, Rule, SchematicContext, Tree } from "@angular-devkit/schematics"; -import { addClassToBody, addMcpServers, App, copyAISkillsToProject, FormatSettings, IGNITEUI_MCP_SERVERS, McpServerEntry, NPM_ANGULAR, resolvePackage, TEMPLATE_MANAGER, TypeScriptAstTransformer, TypeScriptUtils } from "@igniteui/cli-core"; +import { addClassToBody, configureMcpFile, App, copyAISkillsToProject, FormatSettings, McpServerEntry, NPM_ANGULAR, resolvePackage, TEMPLATE_MANAGER, TypeScriptAstTransformer, TypeScriptUtils } from "@igniteui/cli-core"; import { AngularTypeScriptFileUpdate } from "@igniteui/angular-templates"; import { createCliConfig } from "../utils/cli-config"; import { setVirtual } from "../utils/NgFileSystem"; @@ -123,24 +123,18 @@ export function addAIConfig(): Rule { copyAISkillsToProject(); const mcpFilePath = "/.vscode/mcp.json"; - const servers: Record = { + const angularCliServer: Record = { "angular-cli": { command: "npx", args: ["-y", "@angular/cli", "mcp"] - }, - ...IGNITEUI_MCP_SERVERS + } }; - const existingContent = tree.exists(mcpFilePath) ? tree.read(mcpFilePath)!.toString() : undefined; - const { text, modified } = addMcpServers(existingContent, servers); - - if (modified) { - if (tree.exists(mcpFilePath)) { - tree.overwrite(mcpFilePath, text); - } else { - tree.create(mcpFilePath, text); - } - } + configureMcpFile( + () => tree.exists(mcpFilePath) ? tree.read(mcpFilePath)!.toString() : undefined, + (text) => tree.exists(mcpFilePath) ? tree.overwrite(mcpFilePath, text) : tree.create(mcpFilePath, text), + angularCliServer + ); }; } From 34aef78cf530120b89cb46d1f2962fc85a92321a Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 21 Apr 2026 19:05:44 +0300 Subject: [PATCH 3/5] feat: refactor MCP server configuration to use addMcpServers and simplify file handling --- packages/cli/lib/commands/ai-config.ts | 19 ++----- packages/core/util/mcp-config.ts | 57 +++++++++---------- .../ng-schematics/src/cli-config/index.ts | 10 +--- spec/unit/ai-config-spec.ts | 23 ++++---- 4 files changed, 47 insertions(+), 62 deletions(-) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index cbb52b844..66cc47518 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -1,23 +1,14 @@ -import { configureMcpFile, copyAISkillsToProject, FsFileSystem, GoogleAnalytics, IFileSystem, Util } from "@igniteui/cli-core"; +import { addMcpServers, copyAISkillsToProject, GoogleAnalytics, Util } from "@igniteui/cli-core"; import { ArgumentsCamelCase, CommandModule } from "yargs"; -import * as path from "path"; -function getConfigPath(): string { - return path.join(process.cwd(), ".vscode", "mcp.json"); -} - -export function configureMCP(fileSystem: IFileSystem = new FsFileSystem()): void { - const configPath = getConfigPath(); - const modified = configureMcpFile( - () => { try { return fileSystem.readFile(configPath); } catch { return undefined; } }, - (text) => fileSystem.writeFile(configPath, text + "\n") - ); +export function configureMCP(): void { + const modified = addMcpServers(); if (!modified) { - Util.log(` Ignite UI MCP servers already configured in ${configPath}`); + Util.log(` Ignite UI MCP servers already configured`); return; } - Util.log(Util.greenCheck() + ` MCP servers configured in ${configPath}`); + Util.log(Util.greenCheck() + ` MCP servers configured`); } export function configureSkills(): void { diff --git a/packages/core/util/mcp-config.ts b/packages/core/util/mcp-config.ts index 06329f0a9..db1856cb0 100644 --- a/packages/core/util/mcp-config.ts +++ b/packages/core/util/mcp-config.ts @@ -1,4 +1,6 @@ +import { FS_TOKEN, IFileSystem } from "../types/FileSystem"; import * as jsonc from "jsonc-parser"; +import { App } from "./App"; export interface McpServerEntry { command: string; @@ -16,21 +18,33 @@ const IGNITEUI_MCP_SERVERS: Record = { } }; +const mcpFilePath = ".vscode/mcp.json"; + /** - * Adds MCP servers to mcp.json content using jsonc-parser to preserve comments. - * @param existingContent existing file content, or undefined/empty if file doesn't exist - * @param servers servers to add (skips keys already present) - * @returns the resulting text and whether any changes were made + * Reads .vscode/mcp.json, ensures all IgniteUI MCP servers are present, + * optionally adds additional servers. Creates the file if it doesn't exist. + * @param additionalServers optional extra servers to include alongside the built-in ones + * @returns whether the file was modified */ export function addMcpServers( - existingContent: string | undefined, - servers: Record -): { text: string; modified: boolean } { + additionalServers?: Record +): boolean { + const fileSystem = App.container.get(FS_TOKEN); + const servers = { ...additionalServers, ...IGNITEUI_MCP_SERVERS }; + + let existingContent: string | undefined; + try { + existingContent = fileSystem.readFile(mcpFilePath); + } catch { + existingContent = undefined; + } + if (!existingContent) { - return { - text: JSON.stringify({ servers: { ...servers } }, null, 2), - modified: Object.keys(servers).length > 0 - }; + if (Object.keys(servers).length === 0) { + return false; + } + fileSystem.writeFile(mcpFilePath, JSON.stringify({ servers }, null, 2) + "\n"); + return true; } const parsed = jsonc.parse(existingContent); @@ -48,26 +62,9 @@ export function addMcpServers( } } - return { text, modified }; -} - -/** - * Reads existing mcp.json content, merges servers, and writes back if changed. - * @param readFile callback to read existing content (return undefined if file doesn't exist) - * @param writeFile callback to write the resulting content - * @param extraServers additional servers to include alongside the built-in ones - * @returns whether the file was modified - */ -export function configureMcpFile( - readFile: () => string | undefined, - writeFile: (content: string) => void, - extraServers?: Record -): boolean { - const servers = { ...extraServers, ...IGNITEUI_MCP_SERVERS }; - const existingContent = readFile(); - const { text, modified } = addMcpServers(existingContent, servers); if (modified) { - writeFile(text); + fileSystem.writeFile(mcpFilePath, text); } + return modified; } diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index 2856dcc58..428ec39fd 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -1,7 +1,7 @@ import * as ts from "typescript"; import { DependencyNotFoundException } from "@angular-devkit/core"; import { chain, FileDoesNotExistException, Rule, SchematicContext, Tree } from "@angular-devkit/schematics"; -import { addClassToBody, configureMcpFile, App, copyAISkillsToProject, FormatSettings, McpServerEntry, NPM_ANGULAR, resolvePackage, TEMPLATE_MANAGER, TypeScriptAstTransformer, TypeScriptUtils } from "@igniteui/cli-core"; +import { addClassToBody, addMcpServers, App, copyAISkillsToProject, FormatSettings, McpServerEntry, NPM_ANGULAR, resolvePackage, TEMPLATE_MANAGER, TypeScriptAstTransformer, TypeScriptUtils } from "@igniteui/cli-core"; import { AngularTypeScriptFileUpdate } from "@igniteui/angular-templates"; import { createCliConfig } from "../utils/cli-config"; import { setVirtual } from "../utils/NgFileSystem"; @@ -120,9 +120,9 @@ function importStyles(): Rule { export function addAIConfig(): Rule { return (tree: Tree) => { + setVirtual(tree); copyAISkillsToProject(); - const mcpFilePath = "/.vscode/mcp.json"; const angularCliServer: Record = { "angular-cli": { command: "npx", @@ -130,11 +130,7 @@ export function addAIConfig(): Rule { } }; - configureMcpFile( - () => tree.exists(mcpFilePath) ? tree.read(mcpFilePath)!.toString() : undefined, - (text) => tree.exists(mcpFilePath) ? tree.overwrite(mcpFilePath, text) : tree.create(mcpFilePath, text), - angularCliServer - ); + addMcpServers(angularCliServer); }; } diff --git a/spec/unit/ai-config-spec.ts b/spec/unit/ai-config-spec.ts index 40724feca..c3bddebfe 100644 --- a/spec/unit/ai-config-spec.ts +++ b/spec/unit/ai-config-spec.ts @@ -40,8 +40,9 @@ describe("Unit - ai-config command", () => { describe("configureMCP", () => { it("creates .vscode/mcp.json with both servers when file does not exist", () => { const mockFs = createMockFs(); + App.container.set(FS_TOKEN, mockFs); - configureMCP(mockFs); + configureMCP(); expect(mockFs.writeFile).toHaveBeenCalled(); const config = writtenConfig(mockFs); @@ -51,8 +52,9 @@ describe("Unit - ai-config command", () => { it("adds both servers when file exists but servers object is empty", () => { const mockFs = createMockFs(JSON.stringify({ servers: {} })); + App.container.set(FS_TOKEN, mockFs); - configureMCP(mockFs); + configureMCP(); expect(mockFs.writeFile).toHaveBeenCalled(); const config = writtenConfig(mockFs); @@ -64,8 +66,9 @@ describe("Unit - ai-config command", () => { const mockFs = createMockFs(JSON.stringify({ servers: { [IGNITEUI_SERVER_KEY]: igniteuiServer } })); + App.container.set(FS_TOKEN, mockFs); - configureMCP(mockFs); + configureMCP(); expect(mockFs.writeFile).toHaveBeenCalled(); const config = writtenConfig(mockFs); @@ -77,8 +80,9 @@ describe("Unit - ai-config command", () => { const mockFs = createMockFs(JSON.stringify({ servers: { [IGNITEUI_THEMING_SERVER_KEY]: igniteuiThemingServer } })); + App.container.set(FS_TOKEN, mockFs); - configureMCP(mockFs); + configureMCP(); expect(mockFs.writeFile).toHaveBeenCalled(); const config = writtenConfig(mockFs); @@ -93,8 +97,9 @@ describe("Unit - ai-config command", () => { [IGNITEUI_THEMING_SERVER_KEY]: igniteuiThemingServer } })); + App.container.set(FS_TOKEN, mockFs); - configureMCP(mockFs); + configureMCP(); expect(mockFs.writeFile).not.toHaveBeenCalled(); expect(Util.log).toHaveBeenCalledWith(jasmine.stringContaining("already configured")); @@ -105,8 +110,9 @@ describe("Unit - ai-config command", () => { const mockFs = createMockFs(JSON.stringify({ servers: { "other-server": thirdPartyServer } })); + App.container.set(FS_TOKEN, mockFs); - configureMCP(mockFs); + configureMCP(); expect(mockFs.writeFile).toHaveBeenCalled(); const config = writtenConfig(mockFs); @@ -238,11 +244,6 @@ describe("Unit - ai-config command", () => { describe("handler", () => { it("posts analytics and calls configure", async () => { App.container.set(FS_TOKEN, createMockFs()); - const fs = require("fs"); - spyOn(fs, "readFileSync").and.throwError(new Error("ENOENT")); - spyOn(fs, "existsSync").and.returnValue(false); - spyOn(fs, "mkdirSync"); - spyOn(fs, "writeFileSync"); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); From 5b8c52aaa05820fff7c99437325c913f91ef68b7 Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Tue, 21 Apr 2026 20:04:24 +0300 Subject: [PATCH 4/5] refactor: revert back path param and in command message --- packages/cli/lib/commands/ai-config.ts | 8 ++++---- packages/core/util/mcp-config.ts | 3 ++- packages/ng-schematics/src/cli-config/index.ts | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index 66cc47518..8ce72b28a 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -1,14 +1,14 @@ -import { addMcpServers, copyAISkillsToProject, GoogleAnalytics, Util } from "@igniteui/cli-core"; +import { addMcpServers, copyAISkillsToProject, GoogleAnalytics, Util, VS_CODE_MCP_PATH } from "@igniteui/cli-core"; import { ArgumentsCamelCase, CommandModule } from "yargs"; export function configureMCP(): void { - const modified = addMcpServers(); + const modified = addMcpServers(VS_CODE_MCP_PATH); if (!modified) { - Util.log(` Ignite UI MCP servers already configured`); + Util.log(` Ignite UI MCP servers already configured in ${VS_CODE_MCP_PATH}`); return; } - Util.log(Util.greenCheck() + ` MCP servers configured`); + Util.log(Util.greenCheck() + ` MCP servers configured in ${VS_CODE_MCP_PATH}`); } export function configureSkills(): void { diff --git a/packages/core/util/mcp-config.ts b/packages/core/util/mcp-config.ts index db1856cb0..ce500d99c 100644 --- a/packages/core/util/mcp-config.ts +++ b/packages/core/util/mcp-config.ts @@ -18,7 +18,7 @@ const IGNITEUI_MCP_SERVERS: Record = { } }; -const mcpFilePath = ".vscode/mcp.json"; +export const VS_CODE_MCP_PATH = ".vscode/mcp.json"; /** * Reads .vscode/mcp.json, ensures all IgniteUI MCP servers are present, @@ -27,6 +27,7 @@ const mcpFilePath = ".vscode/mcp.json"; * @returns whether the file was modified */ export function addMcpServers( + mcpFilePath: string, additionalServers?: Record ): boolean { const fileSystem = App.container.get(FS_TOKEN); diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index 428ec39fd..8dc7132a9 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -1,7 +1,7 @@ import * as ts from "typescript"; import { DependencyNotFoundException } from "@angular-devkit/core"; import { chain, FileDoesNotExistException, Rule, SchematicContext, Tree } from "@angular-devkit/schematics"; -import { addClassToBody, addMcpServers, App, copyAISkillsToProject, FormatSettings, McpServerEntry, NPM_ANGULAR, resolvePackage, TEMPLATE_MANAGER, TypeScriptAstTransformer, TypeScriptUtils } from "@igniteui/cli-core"; +import { addClassToBody, addMcpServers, App, copyAISkillsToProject, FormatSettings, McpServerEntry, NPM_ANGULAR, resolvePackage, TEMPLATE_MANAGER, TypeScriptAstTransformer, TypeScriptUtils, VS_CODE_MCP_PATH } from "@igniteui/cli-core"; import { AngularTypeScriptFileUpdate } from "@igniteui/angular-templates"; import { createCliConfig } from "../utils/cli-config"; import { setVirtual } from "../utils/NgFileSystem"; @@ -130,7 +130,7 @@ export function addAIConfig(): Rule { } }; - addMcpServers(angularCliServer); + addMcpServers(VS_CODE_MCP_PATH,angularCliServer); }; } From b3bcd7da7a248bc69c4daf271686c68da017df0a Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Tue, 21 Apr 2026 20:36:17 +0300 Subject: [PATCH 5/5] refactor(schematics): split aiConfig rule from standalone ai-config entry --- .../ng-schematics/src/cli-config/index.ts | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index 8dc7132a9..35bdb322b 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -118,9 +118,19 @@ function importStyles(): Rule { }; } -export function addAIConfig(): Rule { +/** Initialize the App container with TemplateManager and virtual FS */ +function appInit(tree: Tree) { + App.initialize("angular-cli"); + // must be initialized with physical fs first: + App.container.set(TEMPLATE_MANAGER, new SchematicsTemplateManager()); + setVirtual(tree); +} + +function aiConfig({ init } = { init: true }): Rule { return (tree: Tree) => { - setVirtual(tree); + if (init) { + appInit(tree); + } copyAISkillsToProject(); const angularCliServer: Record = { @@ -130,23 +140,25 @@ export function addAIConfig(): Rule { } }; - addMcpServers(VS_CODE_MCP_PATH,angularCliServer); + addMcpServers(VS_CODE_MCP_PATH, angularCliServer); }; } +/** Standalone `ai-config` schematic entry */ +export function addAIConfig(): Rule { + return aiConfig(); +} + export default function (): Rule { return (tree: Tree) => { - App.initialize("angular-cli"); - // must be initialized with physical fs first: - App.container.set(TEMPLATE_MANAGER, new SchematicsTemplateManager()); - setVirtual(tree); + appInit(tree); return chain([ importStyles(), addTypographyToProj(), importBrowserAnimations(), createCliConfig(), displayVersionMismatch(), - addAIConfig() + aiConfig({ init: false }) ]); }; }