From c0043e6fd3a6fb13862acd96d733890f3a07b9e5 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Wed, 1 Apr 2026 13:31:32 +0300 Subject: [PATCH 1/4] feat(ng-schematics): add AI configuration to mcp.json for igniteui and igniteui-theming servers --- .../ng-schematics/src/cli-config/index.ts | 43 +++++++++- .../src/cli-config/index_spec.ts | 81 +++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index 0cc2265fb..b17727662 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -117,6 +117,46 @@ function importStyles(): Rule { }; } +function addAIConfig(): Rule { + return (tree: Tree) => { + const mcpFilePath = "/.vscode/mcp.json"; + const igniteuiServer = { + command: "npx", + args: ["-y", "igniteui-cli@next", "mcp"] + }; + const igniteuiThemingServer = { + command: "npx", + args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] + }; + + if (tree.exists(mcpFilePath)) { + const content = JSON.parse(tree.read(mcpFilePath)!.toString()); + const servers = content.servers ?? {}; + let modified = false; + if (!servers["igniteui"]) { + servers["igniteui"] = igniteuiServer; + modified = true; + } + if (!servers["igniteui-theming"]) { + servers["igniteui-theming"] = igniteuiThemingServer; + modified = true; + } + if (modified) { + content.servers = servers; + tree.overwrite(mcpFilePath, JSON.stringify(content, null, 2)); + } + } else { + const mcpConfig = { + servers: { + "igniteui": igniteuiServer, + "igniteui-theming": igniteuiThemingServer + } + }; + tree.create(mcpFilePath, JSON.stringify(mcpConfig, null, 2)); + } + }; +} + export default function (): Rule { return (tree: Tree) => { setVirtual(tree); @@ -125,7 +165,8 @@ export default function (): Rule { addTypographyToProj(), importBrowserAnimations(), createCliConfig(), - displayVersionMismatch() + displayVersionMismatch(), + addAIConfig() ]); }; } diff --git a/packages/ng-schematics/src/cli-config/index_spec.ts b/packages/ng-schematics/src/cli-config/index_spec.ts index afdc825db..128a22c32 100644 --- a/packages/ng-schematics/src/cli-config/index_spec.ts +++ b/packages/ng-schematics/src/cli-config/index_spec.ts @@ -309,4 +309,85 @@ export const appConfig: ApplicationConfig = { await runner.runSchematic("cli-config", {}, tree); expect(warns).toContain(jasmine.stringMatching(pattern)); }); + + describe("addAIConfig", () => { + const mcpFilePath = "/.vscode/mcp.json"; + + it("should create .vscode/mcp.json with both servers when file does not exist", async () => { + await runner.runSchematic("cli-config", {}, tree); + + expect(tree.exists(mcpFilePath)).toBeTruthy(); + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] }); + expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] }); + }); + + it("should add both servers to existing .vscode/mcp.json that has no servers", async () => { + tree.create(mcpFilePath, JSON.stringify({ servers: {} })); + + await runner.runSchematic("cli-config", {}, tree); + + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] }); + expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] }); + }); + + it("should add missing igniteui-theming server if only igniteui is already present", async () => { + tree.create(mcpFilePath, JSON.stringify({ + servers: { + "igniteui": { command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] } + } + })); + + await runner.runSchematic("cli-config", {}, tree); + + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] }); + expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] }); + }); + + it("should add missing igniteui server if only igniteui-theming is already present", async () => { + tree.create(mcpFilePath, JSON.stringify({ + servers: { + "igniteui-theming": { command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] } + } + })); + + await runner.runSchematic("cli-config", {}, tree); + + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] }); + expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] }); + }); + + it("should not modify .vscode/mcp.json if both servers are already present", async () => { + const existing = { + servers: { + "igniteui": { command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] }, + "igniteui-theming": { command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] } + } + }; + tree.create(mcpFilePath, JSON.stringify(existing)); + + await runner.runSchematic("cli-config", {}, tree); + + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content).toEqual(existing); + }); + + it("should preserve existing servers when adding igniteui servers", async () => { + tree.create(mcpFilePath, JSON.stringify({ + servers: { + "other-server": { command: "node", args: ["server.js"] } + } + })); + + await runner.runSchematic("cli-config", {}, tree); + + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content.servers["other-server"]).toEqual({ command: "node", args: ["server.js"] }); + expect(content.servers["igniteui"]).toBeDefined(); + expect(content.servers["igniteui-theming"]).toBeDefined(); + }); + }); }); From e8e435cedc5aa642f29e6f691b4bbc85efac15d1 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Wed, 1 Apr 2026 13:51:17 +0300 Subject: [PATCH 2/4] feat(ng-schematics): integrate AI configuration setup in new project creation --- packages/ng-schematics/src/ng-new/index.ts | 2 ++ .../ng-schematics/src/ng-new/index_spec.ts | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/packages/ng-schematics/src/ng-new/index.ts b/packages/ng-schematics/src/ng-new/index.ts index 01f844173..bb4deb766 100644 --- a/packages/ng-schematics/src/ng-new/index.ts +++ b/packages/ng-schematics/src/ng-new/index.ts @@ -17,6 +17,7 @@ import { defer, Observable } from "rxjs"; import { NewProjectOptions } from "../app-projects/schema"; import { SchematicsPromptSession } from "../prompt/SchematicsPromptSession"; import { SchematicsTemplateManager } from "../SchematicsTemplateManager"; +import { addAIConfig } from "../cli-config/index"; import { setVirtual } from "../utils/NgFileSystem"; import { OptionsSchema } from "./schema"; @@ -150,6 +151,7 @@ export function newProject(options: OptionsSchema): Rule { }); } }, + addAIConfig(), (_tree: Tree, _context: IgxSchematicContext) => { return move(options.name!); } diff --git a/packages/ng-schematics/src/ng-new/index_spec.ts b/packages/ng-schematics/src/ng-new/index_spec.ts index 64e41ff27..c767d41f5 100644 --- a/packages/ng-schematics/src/ng-new/index_spec.ts +++ b/packages/ng-schematics/src/ng-new/index_spec.ts @@ -212,4 +212,34 @@ describe("Schematics ng-new", () => { expect(taskOptions).toContain(expectedInit); }); }); + + describe("addAIConfig via ng-new", () => { + const workingDirectory = "my-test-project"; + const mcpFilePath = `${workingDirectory}/.vscode/mcp.json`; + + function setupAndRun(runner: SchematicTestRunner, myTree: Tree): Promise { + spyOn(AppProjectSchematic, "default").and.returnValue((currentTree: Tree, _context: SchematicContext) => { + currentTree.create("gitignore", ""); + return currentTree; + }); + + const userAnswers = new Map(); + userAnswers.set("upgradePackages", false); + spyOnProperty(SchematicsPromptSession.prototype, "userAnswers", "get").and.returnValue(userAnswers); + + return runner.runSchematic("ng-new", { version: "8.0.3", name: workingDirectory, skipInstall: true, skipGit: true }, myTree); + } + + it("should create .vscode/mcp.json with both servers during ng-new", async () => { + const runner = new SchematicTestRunner("schematics", collectionPath); + const myTree = Tree.empty(); + + const e = await setupAndRun(runner, myTree); + + expect(e.exists(mcpFilePath)).toBeTruthy(); + const content = JSON.parse(e.readContent(mcpFilePath)); + expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] }); + expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] }); + }); + }); }); From 5720d539985b6e12d6c54b0543313556fd5a4b07 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Wed, 1 Apr 2026 14:08:28 +0300 Subject: [PATCH 3/4] feat(ng-schematics): export addAIConfig function for external usage --- packages/ng-schematics/src/cli-config/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index b17727662..eea65ef86 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -117,7 +117,7 @@ function importStyles(): Rule { }; } -function addAIConfig(): Rule { +export function addAIConfig(): Rule { return (tree: Tree) => { const mcpFilePath = "/.vscode/mcp.json"; const igniteuiServer = { From c30ca48fd27ac8e82c6c2e275f401035c1a8db2c Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Wed, 1 Apr 2026 14:13:28 +0300 Subject: [PATCH 4/4] test(ng-schematics): update file count expectations in ng-new tests --- packages/ng-schematics/src/ng-new/index_spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ng-schematics/src/ng-new/index_spec.ts b/packages/ng-schematics/src/ng-new/index_spec.ts index c767d41f5..702651cb7 100644 --- a/packages/ng-schematics/src/ng-new/index_spec.ts +++ b/packages/ng-schematics/src/ng-new/index_spec.ts @@ -136,7 +136,7 @@ describe("Schematics ng-new", () => { expect(mockFunc[1]).toHaveBeenCalled(); } expect(AppProjectSchematic.default).toHaveBeenCalled(); - expect(e.files.length).toEqual(1); + expect(e.files.length).toEqual(2); expect(e.exists(`${workingDirectory}/.gitignore`)).toBeTruthy(); const taskOptions = runner.tasks.map(task => task.options); const expectedInstall: NodePackageTaskOptions = { @@ -189,7 +189,7 @@ describe("Schematics ng-new", () => { runner.runSchematic("ng-new", { version: "8.0.3", name: workingDirectory }, myTree) .then((e: UnitTestTree) => { expect(AppProjectSchematic.default).toHaveBeenCalled(); - expect(e.files.length).toEqual(1); + expect(e.files.length).toEqual(2); expect(e.exists(`${workingDirectory}/.gitignore`)).toBeTruthy(); const taskOptions = runner.tasks.map(task => task.options); const expectedInstall: NodePackageTaskOptions = {