@insforge/install
Version:
CLI tool for installing Insforge MCP servers across different AI clients
318 lines (291 loc) • 12.8 kB
JavaScript
import {
clientNames,
logger,
readConfig,
writeConfig
} from "./utils.js";
import { execSync } from 'child_process';
import os from 'os';
import path from 'path';
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import pc from "picocolors";
var { green, red, yellow, cyan } = pc;
// Section header helper
const LINE = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━';
function printHeader(text) {
console.log();
console.log(LINE);
console.log(` ${text}`);
console.log(LINE);
console.log();
}
// ASCII art logo for InsForge
const INSFORGE_LOGO = `
██╗███╗ ██╗███████╗███████╗ ██████╗ ██████╗ ██████╗ ███████╗
██║████╗ ██║██╔════╝██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██╔════╝
██║██╔██╗ ██║███████╗█████╗ ██║ ██║██████╔╝██║ ███╗█████╗
██║██║╚██╗██║╚════██║██╔══╝ ██║ ██║██╔══██╗██║ ██║██╔══╝
██║██║ ╚████║███████║██║ ╚██████╔╝██║ ██║╚██████╔╝███████╗
╚═╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝
`;
function printPostInstallMessage() {
console.log(INSFORGE_LOGO);
console.log(green('✓ InsForge MCP is now configured!'));
console.log();
console.log('Next steps:');
console.log(' 1. Restart your coding agent to load InsForge');
console.log(' 2. Try these commands in your agent:');
console.log();
console.log(` ${yellow('"Create a posts table with title, content, and author"')}`);
console.log(' (Sets up your database schema)');
console.log();
console.log(` ${yellow('"Add image upload for user profiles"')}`);
console.log(' (Creates storage bucket and handles file uploads)');
console.log();
console.log('Learn more:');
console.log(' 📚 Documentation: https://docs.insforge.dev/introduction');
console.log(' 💬 Discord: https://discord.com/invite/MPxwj5xVvW');
console.log(' ⭐ GitHub: https://github.com/insforge/insforge');
console.log();
}
function builder(yargs2) {
return yargs2.option("client", {
type: "string",
description: "Client to use for installation",
demandOption: true,
choices: clientNames
}).option("env", {
type: "string",
description: "Environment variables as key=value pairs (can be used multiple times). API_KEY is required.",
array: true
}).option("dev", {
type: "boolean",
description: "Install dev version (@insforge/mcp@dev) instead of latest",
default: false
});
}
async function handler(argv) {
if (!argv.client || !clientNames.includes(argv.client)) {
logger.error(`Invalid client: ${argv.client}. Available clients: ${clientNames.join(", ")}`);
return;
}
const envVars = {};
if (argv.env && argv.env.length > 0) {
for (const envVar of argv.env) {
const [key, ...valueParts] = envVar.split("=");
if (key && valueParts.length > 0) {
envVars[key] = valueParts.join("=");
} else {
logger.warn(`Invalid environment variable format: ${envVar}. Expected KEY=VALUE format.`);
}
}
}
if (!envVars.API_KEY) {
logger.error("API_KEY environment variable is required. Use --env API_KEY=your_key");
return;
}
// Add default base URL if not provided
if (!envVars.API_BASE_URL) {
envVars.API_BASE_URL = "http://localhost:7130";
}
const name = "insforge";
const mcpVersion = argv.dev ? "@insforge/mcp@dev" : "@insforge/mcp@latest";
try {
printHeader('InsForge MCP Installer');
logger.info(`Setting up MCP for ${cyan(argv.client)}...`);
printHeader(`Configuring ${argv.client}`);
const config = readConfig(argv.client);
// Handle different config structures for different clients
if (argv.client === "claude-code") {
// Claude Code uses mcpServers at root level
if (!config.mcpServers) config.mcpServers = {};
const isWindows = process.platform === 'win32';
config.mcpServers[name] = isWindows ? {
command: "cmd",
args: ["/c", "npx", "-y", mcpVersion],
env: {
API_KEY: envVars.API_KEY,
API_BASE_URL: envVars.API_BASE_URL
}
} : {
command: "npx",
args: ["-y", mcpVersion],
env: {
API_KEY: envVars.API_KEY,
API_BASE_URL: envVars.API_BASE_URL
}
};
writeConfig(config, argv.client);
// Create or update .claude/settings.local.json with enableAllProjectMcpServers
const fs = await import('fs');
const claudeDir = path.join(process.cwd(), '.claude');
const settingsPath = path.join(claudeDir, 'settings.local.json');
if (!fs.existsSync(claudeDir)) {
fs.mkdirSync(claudeDir, { recursive: true });
}
let settings = {};
if (fs.existsSync(settingsPath)) {
try {
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
} catch (error) {
logger.warn(`Could not parse existing settings.local.json: ${error.message}`);
}
}
// Add "insforge" to enabledMcpjsonServers if not already present
if (!settings.enabledMcpjsonServers) {
settings.enabledMcpjsonServers = [];
}
if (!settings.enabledMcpjsonServers.includes("insforge")) {
settings.enabledMcpjsonServers.push("insforge");
}
// Remove "insforge" from disabledMcpjsonServers if present
if (settings.disabledMcpjsonServers && Array.isArray(settings.disabledMcpjsonServers)) {
settings.disabledMcpjsonServers = settings.disabledMcpjsonServers.filter(
(server) => server !== "insforge"
);
}
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
logger.info("Added \"insforge\" to enabledMcpjsonServers in .claude/settings.local.json");
} else if (argv.client === "codex") {
// Use Codex CLI for Codex
const homeDir = os.homedir();
const isWindows = process.platform === 'win32';
const codexPath = isWindows
? path.join(homeDir, 'AppData', 'Roaming', 'npm', 'codex.cmd')
: 'codex';
const envArgs = Object.entries(envVars)
.filter(([key]) => key !== 'CLIENT_NAME')
.map(([key, value]) => `--env ${key}=${value}`)
.join(' ');
try {
// First try to remove existing insforge MCP if it exists
try {
const removeCmd = isWindows ? `"${codexPath}" mcp remove ${name}` : `codex mcp remove ${name}`;
execSync(removeCmd, {
stdio: 'pipe',
shell: true
});
logger.info("Removed existing insforge MCP installation.");
} catch (removeError) {
// It's okay if remove fails - the MCP might not exist
logger.info("No existing insforge MCP found");
}
// Now add the MCP server using codex CLI with --env flags
const command = isWindows
? `"${codexPath}" mcp add ${name} ${envArgs} -- npx -y ${mcpVersion}`
: `codex mcp add ${name} ${envArgs} -- npx -y ${mcpVersion}`;
logger.info(`Adding insforge MCP server (${mcpVersion})...`);
execSync(command, {
stdio: 'inherit',
shell: true
});
} catch (error) {
throw new Error(`Failed to add MCP server via Codex CLI: ${error.message}`);
}
} else if (argv.client === "cursor") {
// Cursor uses mcpServers at root level
if (!config.mcpServers) config.mcpServers = {};
config.mcpServers[name] = {
command: "npx",
args: ["-y", mcpVersion],
env: {
API_KEY: envVars.API_KEY,
API_BASE_URL: envVars.API_BASE_URL
}
};
writeConfig(config, argv.client);
} else if (argv.client === "windsurf") {
if (!config.mcpServers) config.mcpServers = {};
config.mcpServers[name] = {
command: "npx",
args: ["-y", mcpVersion],
env: {
API_KEY: envVars.API_KEY,
API_BASE_URL: envVars.API_BASE_URL
}
};
writeConfig(config, argv.client);
} else if (argv.client === "cline" || argv.client === "roocode" || argv.client === "trae" || argv.client === "qoder") {
if (!config.mcpServers) config.mcpServers = {};
config.mcpServers[name] = {
command: "npx",
args: ["-y", mcpVersion],
env: {
API_KEY: envVars.API_KEY,
API_BASE_URL: envVars.API_BASE_URL
}
};
writeConfig(config, argv.client);
} else if (argv.client === "copilot") {
// Copilot uses "servers" instead of "mcpServers"
const fs = await import('fs');
const vscodeDir = path.join(process.cwd(), '.vscode');
const mcpConfigPath = path.join(vscodeDir, 'mcp.json');
if (!fs.existsSync(vscodeDir)) {
fs.mkdirSync(vscodeDir, { recursive: true });
}
let copilotConfig = { servers: {} };
if (fs.existsSync(mcpConfigPath)) {
try {
copilotConfig = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
if (!copilotConfig.servers) copilotConfig.servers = {};
} catch (error) {
logger.warn(`Could not parse existing mcp.json: ${error.message}`);
copilotConfig = { servers: {} };
}
}
copilotConfig.servers[name] = {
command: "npx",
args: ["-y", mcpVersion],
env: {
API_KEY: envVars.API_KEY,
API_BASE_URL: envVars.API_BASE_URL
}
};
fs.writeFileSync(mcpConfigPath, JSON.stringify(copilotConfig, null, 2));
logger.info(`Configured Copilot MCP at: ${mcpConfigPath}`);
}
// Fetch instructions documentation and save to appropriate files
let instructionsContent = null;
try {
const fetch = (await import('node-fetch')).default;
const fs = await import('fs');
const apiBaseUrl = envVars.API_BASE_URL || "http://localhost:7130";
const response = await fetch(`${apiBaseUrl}/api/docs/instructions`);
if (response.ok) {
const result = await response.json();
if (result && result.content) {
instructionsContent = result.content;
}
}
} catch (fetchError) {
logger.warn(`Could not download instructions: ${fetchError.message}`);
}
// Save instructions to agent.md for all clients
if (instructionsContent) {
const fs = await import('fs');
const frontmatter = `---
description: Instructions building apps with MCP
globs: *
alwaysApply: true
---
`;
const contentWithFrontmatter = frontmatter + instructionsContent;
const agentsMdPath = path.join(process.cwd(), 'AGENTS.md');
fs.writeFileSync(agentsMdPath, contentWithFrontmatter, 'utf-8');
logger.info(`Saved instructions to: ${agentsMdPath}`);
}
printHeader('Setup Complete!');
printPostInstallMessage();
} catch (e) {
logger.error(red(e.message));
}
}
var parser = yargs(hideBin(process.argv)).scriptName("@insforge/install").command("install", "Install Insforge MCP server", builder, handler).help().alias("h", "help").version().alias("v", "version");
if (!process.argv.slice(2).length || process.argv[2].startsWith("--")) {
parser.parse(["install", ...process.argv.slice(2)]);
} else {
parser.parse();
}