Skip to content

Commit e5a69f0

Browse files
authored
Merge pull request #1588 from IgniteUI/mstoyanova/ng-add-mcp-config
feat(ng-schematics): add AI configuration to mcp.json for igniteui an…
2 parents 9e940df + d4bdba0 commit e5a69f0

File tree

4 files changed

+157
-3
lines changed

4 files changed

+157
-3
lines changed

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

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,46 @@ function importStyles(): Rule {
117117
};
118118
}
119119

120+
export function addAIConfig(): Rule {
121+
return (tree: Tree) => {
122+
const mcpFilePath = "/.vscode/mcp.json";
123+
const igniteuiServer = {
124+
command: "npx",
125+
args: ["-y", "igniteui-cli@next", "mcp"]
126+
};
127+
const igniteuiThemingServer = {
128+
command: "npx",
129+
args: ["-y", "igniteui-theming", "igniteui-theming-mcp"]
130+
};
131+
132+
if (tree.exists(mcpFilePath)) {
133+
const content = JSON.parse(tree.read(mcpFilePath)!.toString());
134+
const servers = content.servers ?? {};
135+
let modified = false;
136+
if (!servers["igniteui"]) {
137+
servers["igniteui"] = igniteuiServer;
138+
modified = true;
139+
}
140+
if (!servers["igniteui-theming"]) {
141+
servers["igniteui-theming"] = igniteuiThemingServer;
142+
modified = true;
143+
}
144+
if (modified) {
145+
content.servers = servers;
146+
tree.overwrite(mcpFilePath, JSON.stringify(content, null, 2));
147+
}
148+
} else {
149+
const mcpConfig = {
150+
servers: {
151+
"igniteui": igniteuiServer,
152+
"igniteui-theming": igniteuiThemingServer
153+
}
154+
};
155+
tree.create(mcpFilePath, JSON.stringify(mcpConfig, null, 2));
156+
}
157+
};
158+
}
159+
120160
export default function (): Rule {
121161
return (tree: Tree) => {
122162
setVirtual(tree);
@@ -125,7 +165,8 @@ export default function (): Rule {
125165
addTypographyToProj(),
126166
importBrowserAnimations(),
127167
createCliConfig(),
128-
displayVersionMismatch()
168+
displayVersionMismatch(),
169+
addAIConfig()
129170
]);
130171
};
131172
}

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

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,4 +309,85 @@ export const appConfig: ApplicationConfig = {
309309
await runner.runSchematic("cli-config", {}, tree);
310310
expect(warns).toContain(jasmine.stringMatching(pattern));
311311
});
312+
313+
describe("addAIConfig", () => {
314+
const mcpFilePath = "/.vscode/mcp.json";
315+
316+
it("should create .vscode/mcp.json with both servers when file does not exist", async () => {
317+
await runner.runSchematic("cli-config", {}, tree);
318+
319+
expect(tree.exists(mcpFilePath)).toBeTruthy();
320+
const content = JSON.parse(tree.readContent(mcpFilePath));
321+
expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] });
322+
expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] });
323+
});
324+
325+
it("should add both servers to existing .vscode/mcp.json that has no servers", async () => {
326+
tree.create(mcpFilePath, JSON.stringify({ servers: {} }));
327+
328+
await runner.runSchematic("cli-config", {}, tree);
329+
330+
const content = JSON.parse(tree.readContent(mcpFilePath));
331+
expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] });
332+
expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] });
333+
});
334+
335+
it("should add missing igniteui-theming server if only igniteui is already present", async () => {
336+
tree.create(mcpFilePath, JSON.stringify({
337+
servers: {
338+
"igniteui": { command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] }
339+
}
340+
}));
341+
342+
await runner.runSchematic("cli-config", {}, tree);
343+
344+
const content = JSON.parse(tree.readContent(mcpFilePath));
345+
expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] });
346+
expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] });
347+
});
348+
349+
it("should add missing igniteui server if only igniteui-theming is already present", async () => {
350+
tree.create(mcpFilePath, JSON.stringify({
351+
servers: {
352+
"igniteui-theming": { command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] }
353+
}
354+
}));
355+
356+
await runner.runSchematic("cli-config", {}, tree);
357+
358+
const content = JSON.parse(tree.readContent(mcpFilePath));
359+
expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] });
360+
expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] });
361+
});
362+
363+
it("should not modify .vscode/mcp.json if both servers are already present", async () => {
364+
const existing = {
365+
servers: {
366+
"igniteui": { command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] },
367+
"igniteui-theming": { command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] }
368+
}
369+
};
370+
tree.create(mcpFilePath, JSON.stringify(existing));
371+
372+
await runner.runSchematic("cli-config", {}, tree);
373+
374+
const content = JSON.parse(tree.readContent(mcpFilePath));
375+
expect(content).toEqual(existing);
376+
});
377+
378+
it("should preserve existing servers when adding igniteui servers", async () => {
379+
tree.create(mcpFilePath, JSON.stringify({
380+
servers: {
381+
"other-server": { command: "node", args: ["server.js"] }
382+
}
383+
}));
384+
385+
await runner.runSchematic("cli-config", {}, tree);
386+
387+
const content = JSON.parse(tree.readContent(mcpFilePath));
388+
expect(content.servers["other-server"]).toEqual({ command: "node", args: ["server.js"] });
389+
expect(content.servers["igniteui"]).toBeDefined();
390+
expect(content.servers["igniteui-theming"]).toBeDefined();
391+
});
392+
});
312393
});

packages/ng-schematics/src/ng-new/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { defer, Observable } from "rxjs";
1717
import { NewProjectOptions } from "../app-projects/schema";
1818
import { SchematicsPromptSession } from "../prompt/SchematicsPromptSession";
1919
import { SchematicsTemplateManager } from "../SchematicsTemplateManager";
20+
import { addAIConfig } from "../cli-config/index";
2021
import { setVirtual } from "../utils/NgFileSystem";
2122
import { OptionsSchema } from "./schema";
2223

@@ -150,6 +151,7 @@ export function newProject(options: OptionsSchema): Rule {
150151
});
151152
}
152153
},
154+
addAIConfig(),
153155
(_tree: Tree, _context: IgxSchematicContext) => {
154156
return move(options.name!);
155157
}

packages/ng-schematics/src/ng-new/index_spec.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ describe("Schematics ng-new", () => {
136136
expect(mockFunc[1]).toHaveBeenCalled();
137137
}
138138
expect(AppProjectSchematic.default).toHaveBeenCalled();
139-
expect(e.files.length).toEqual(1);
139+
expect(e.files.length).toEqual(2);
140140
expect(e.exists(`${workingDirectory}/.gitignore`)).toBeTruthy();
141141
const taskOptions = runner.tasks.map(task => task.options);
142142
const expectedInstall: NodePackageTaskOptions = {
@@ -189,7 +189,7 @@ describe("Schematics ng-new", () => {
189189
runner.runSchematic("ng-new", { version: "8.0.3", name: workingDirectory }, myTree)
190190
.then((e: UnitTestTree) => {
191191
expect(AppProjectSchematic.default).toHaveBeenCalled();
192-
expect(e.files.length).toEqual(1);
192+
expect(e.files.length).toEqual(2);
193193
expect(e.exists(`${workingDirectory}/.gitignore`)).toBeTruthy();
194194
const taskOptions = runner.tasks.map(task => task.options);
195195
const expectedInstall: NodePackageTaskOptions = {
@@ -212,4 +212,34 @@ describe("Schematics ng-new", () => {
212212
expect(taskOptions).toContain(expectedInit);
213213
});
214214
});
215+
216+
describe("addAIConfig via ng-new", () => {
217+
const workingDirectory = "my-test-project";
218+
const mcpFilePath = `${workingDirectory}/.vscode/mcp.json`;
219+
220+
function setupAndRun(runner: SchematicTestRunner, myTree: Tree): Promise<UnitTestTree> {
221+
spyOn(AppProjectSchematic, "default").and.returnValue((currentTree: Tree, _context: SchematicContext) => {
222+
currentTree.create("gitignore", "");
223+
return currentTree;
224+
});
225+
226+
const userAnswers = new Map<string, any>();
227+
userAnswers.set("upgradePackages", false);
228+
spyOnProperty(SchematicsPromptSession.prototype, "userAnswers", "get").and.returnValue(userAnswers);
229+
230+
return runner.runSchematic("ng-new", { version: "8.0.3", name: workingDirectory, skipInstall: true, skipGit: true }, myTree);
231+
}
232+
233+
it("should create .vscode/mcp.json with both servers during ng-new", async () => {
234+
const runner = new SchematicTestRunner("schematics", collectionPath);
235+
const myTree = Tree.empty();
236+
237+
const e = await setupAndRun(runner, myTree);
238+
239+
expect(e.exists(mcpFilePath)).toBeTruthy();
240+
const content = JSON.parse(e.readContent(mcpFilePath));
241+
expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] });
242+
expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] });
243+
});
244+
});
215245
});

0 commit comments

Comments
 (0)