Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 32 additions & 0 deletions packages/cli/lib/PromptSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ProjectLibrary, PromptTaskContext, Task, Util
} from "@igniteui/cli-core";
import * as path from "path";
import * as fs from "fs";
import { default as add } from "./commands/add";
import { default as start } from "./commands/start";
import { default as upgrade } from "./commands/upgrade";
Expand Down Expand Up @@ -103,6 +104,37 @@ export class PromptSession extends BasePromptSession {
await upgrade.upgrade({ skipInstall: true, _: ["upgrade"], $0: "upgrade" });
}

protected async configureMcp(): Promise<void> {
const MCP_SERVER_KEY = "igniteui-mcp-server";
let command: string;
let args: string[];
try {
const pkgEntry = require.resolve("igniteui-mcp-server");
command = "node";
args = [pkgEntry];
} catch {
command = "npx";
args = ["-y", "igniteui-mcp-server"];
Comment on lines +109 to +117
}
const configPath = path.join(process.cwd(), ".vscode", "mcp.json");
let config: { servers: Record<string, { command: string; args: string[] }> } = { servers: {} };
try {
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
} catch { /* file doesn't exist yet */ }
config.servers = config.servers || {};

if (config.servers[MCP_SERVER_KEY]) {
Util.log(Util.greenCheck() + ` Ignite UI MCP server already configured in ${configPath}`);
return;
}

// Preserve existing MCP entries and add ours
config.servers[MCP_SERVER_KEY] = { command, args };
fs.mkdirSync(path.dirname(configPath), { recursive: true });
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
Util.log(Util.greenCheck() + ` MCP server configured in ${configPath}`);
Comment on lines +119 to +135
}

/**
* Get user name and set template's extra configurations if any
* @param projectLibrary to add component to
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
add,
ADD_COMMAND_NAME,
ALL_COMMANDS,
aiConfig,
build,
config,
doc,
Expand Down Expand Up @@ -56,6 +57,7 @@ export async function run(args = null) {
.command(list)
.command(upgrade)
.command(mcp)
.command(aiConfig)
.version(false) // disable built-in `yargs.version` to override it with our custom option
.options({
version: {
Expand Down
88 changes: 88 additions & 0 deletions packages/cli/lib/commands/ai-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { GoogleAnalytics, Util } from "@igniteui/cli-core";
import { ArgumentsCamelCase, CommandModule } from "yargs";
import * as path from "path";
import * as fs from "fs";

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<string, McpServerEntry>;
}

function getConfigPath(): string {
return path.join(process.cwd(), ".vscode", "mcp.json");
}

function readJson<T>(filePath: string, fallback: T): T {
try {
return JSON.parse(fs.readFileSync(filePath, "utf8")) as T;
} catch {
return fallback;
}
}

function writeJson(filePath: string, data: unknown): void {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf8");
}

function configureVsCode(): void {
const configPath = getConfigPath();
const config = readJson<VsCodeMcpConfig>(configPath, { servers: {} });
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;
}

if (!modified) {
Util.log(` Ignite UI MCP servers already configured in ${configPath}`);
return;
}
writeJson(configPath, config);
Util.log(Util.greenCheck() + ` MCP servers configured in ${configPath}`);
}
Comment on lines +45 to +66

const command: CommandModule = {
command: "ai-config",
describe: "Configure the Ignite UI MCP server for an AI client",
builder: (yargs) => yargs.usage(""),
async handler(_argv: ArgumentsCamelCase) {
GoogleAnalytics.post({
t: "screenview",
cd: "MCP"
});

GoogleAnalytics.post({
t: "event",
ec: "$ig ai-config",
ea: "client: vscode"
});

configureVsCode();
}
};

export default command;
1 change: 1 addition & 0 deletions packages/cli/lib/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as add } from "./add";
export { default as aiConfig } from "./ai-config";
export { default as build } from "./build";
export { default as config } from "./config";
export { default as doc } from "./doc";
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/lib/commands/mcp.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as fs from "fs";
import * as fs from "fs";
import * as path from "path";
import { spawn } from "child_process";
import { CommandType, PositionalArgs } from "./types";
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/lib/commands/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const TEST_COMMAND_NAME = "test";
export const LIST_COMMAND_NAME = "list";
export const UPGRADE_COMMAND_NAME = "upgrade-packages";
export const MCP_COMMAND_NAME = "mcp";
export const AI_CONFIG_COMMAND_NAME = "ai-config";

export const ALL_COMMANDS = new Set([
ADD_COMMAND_NAME,
Expand All @@ -25,7 +26,8 @@ export const ALL_COMMANDS = new Set([
TEST_COMMAND_NAME,
LIST_COMMAND_NAME,
UPGRADE_COMMAND_NAME,
MCP_COMMAND_NAME
MCP_COMMAND_NAME,
AI_CONFIG_COMMAND_NAME
]);

export interface PositionalArgs {
Expand Down
5 changes: 5 additions & 0 deletions packages/core/prompt/BasePromptSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ export abstract class BasePromptSession {
/** Upgrade packages to use private Infragistics feed */
protected abstract upgradePackages();

/** Configure the Ignite UI MCP server for the project */
protected abstract configureMcp(): Promise<void>;

/**
* Get user name and set template's extra configurations if any
* @param projectLibrary to add component to
Expand Down Expand Up @@ -418,6 +421,8 @@ export abstract class BasePromptSession {
await this.upgradePackages();
}
}

await this.configureMcp();
Comment on lines +424 to +425

const defaultPort = config.project.defaultPort;
const port = await this.getUserInput({
Expand Down
4 changes: 4 additions & 0 deletions packages/ng-schematics/src/prompt/SchematicsPromptSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ export class SchematicsPromptSession extends BasePromptSession {
// TODO?
}

protected async configureMcp(): Promise<void> {
// No-op in schematics context
}

protected async upgradePackages() {
this.userAnswers.set("upgradePackages", true);
}
Expand Down
1 change: 1 addition & 0 deletions spec/acceptance/help-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe("Help command", () => {
upgrade-packages upgrades Ignite UI Packages
mcp Starts the Ignite UI MCP server for AI assistant
integration
ai-config Configure the Ignite UI MCP server for an AI client
Options:
-v, --version Show current Ignite UI CLI version [boolean]
-h, --help Show help [boolean]`.replace(/\s/g, "");
Expand Down
6 changes: 3 additions & 3 deletions spec/unit/PromptSession-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ describe("Unit - PromptSession", () => {
await mockSession.chooseActionLoop(mockProjectLibrary);
expect(mockSession.chooseActionLoop).toHaveBeenCalledTimes(1);
expect(InquirerWrapper.select).toHaveBeenCalledTimes(9);
expect(Util.log).toHaveBeenCalledTimes(3);
expect(Util.log).toHaveBeenCalledTimes(4);
expect(PackageManager.flushQueue).toHaveBeenCalledWith(true);
expect(start.start).toHaveBeenCalledTimes(1);
expect(add.addTemplate).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -568,7 +568,7 @@ describe("Unit - PromptSession", () => {
expect(mockSession.chooseActionLoop).toHaveBeenCalledTimes(1);
expect(InquirerWrapper.select).toHaveBeenCalledTimes(5);
expect(InquirerWrapper.input).toHaveBeenCalledTimes(2);
expect(Util.log).toHaveBeenCalledTimes(3);
expect(Util.log).toHaveBeenCalledTimes(4);
expect(PackageManager.flushQueue).toHaveBeenCalledWith(true);
expect(start.start).toHaveBeenCalledTimes(1);
expect(Util.getAvailableName).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -691,7 +691,7 @@ describe("Unit - PromptSession", () => {
expect(InquirerWrapper.select).toHaveBeenCalledTimes(10);
expect(InquirerWrapper.input).toHaveBeenCalledTimes(2);
expect(InquirerWrapper.checkbox).toHaveBeenCalledTimes(1);
expect(Util.log).toHaveBeenCalledTimes(3);
expect(Util.log).toHaveBeenCalledTimes(4);
expect(PackageManager.flushQueue).toHaveBeenCalledWith(true);
expect(start.start).toHaveBeenCalledTimes(1);
expect(add.addTemplate).toHaveBeenCalledTimes(1);
Expand Down
Loading