#!/usr/bin/env node /** * godot-mcp-setup — Setup and management CLI for Godot MCP Pro * * Commands: * install Install dependencies and build the server * check-update Check if a newer version is available on GitHub * configure Auto-detect AI client and generate MCP config * doctor Diagnose environment (Node.js, npm, build status) */ import { execSync } from "child_process"; import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs"; import { resolve, dirname, join } from "path"; import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Server root is one level up from build/ const SERVER_DIR = resolve(__dirname, ".."); const PACKAGE_JSON = join(SERVER_DIR, "package.json"); const BUILD_INDEX = join(SERVER_DIR, "build", "index.js"); const GITHUB_REPO = "youichi-uda/godot-mcp-pro"; // ─── Utilities ──────────────────────────────────────────────── function getVersion() { try { const pkg = JSON.parse(readFileSync(PACKAGE_JSON, "utf-8")); return pkg.version || "unknown"; } catch { return "unknown"; } } function run(cmd, cwd) { try { return execSync(cmd, { cwd: cwd || SERVER_DIR, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], }).trim(); } catch (err) { return err.stderr?.trim() || err.message || "command failed"; } } function check(label, ok, detail) { const icon = ok ? "✓" : "✗"; const line = detail ? `${label}: ${detail}` : label; console.log(` ${icon} ${line}`); } /** Compare semver strings. Returns >0 if a > b, <0 if a < b, 0 if equal. */ function compareSemver(a, b) { const pa = a.split(".").map(Number); const pb = b.split(".").map(Number); for (let i = 0; i < 3; i++) { const diff = (pa[i] || 0) - (pb[i] || 0); if (diff !== 0) return diff; } return 0; } // ─── Commands ───────────────────────────────────────────────── async function cmdInstall() { console.log("Installing Godot MCP Pro server...\n"); console.log("[1/2] Installing dependencies..."); try { execSync("npm install", { cwd: SERVER_DIR, stdio: "inherit" }); } catch { console.error("\nFailed to install dependencies. Make sure npm is available."); process.exit(1); } console.log("\n[2/2] Building server..."); try { execSync("npm run build", { cwd: SERVER_DIR, stdio: "inherit" }); } catch { console.error("\nBuild failed. Check for TypeScript errors above."); process.exit(1); } console.log(`\nDone! Server built at: ${BUILD_INDEX}`); console.log(`Version: ${getVersion()}`); console.log("\nNext step: Run 'node build/setup.js configure' to set up your AI client."); } async function cmdCheckUpdate() { const current = getVersion(); console.log(`Current version: ${current}\n`); console.log(`Checking GitHub releases for ${GITHUB_REPO}...`); try { const res = await fetch(`https://api.github.com/repos/${GITHUB_REPO}/releases/latest`, { headers: { "User-Agent": "godot-mcp-pro-setup" } }); if (!res.ok) { if (res.status === 404) { console.log("No releases found on GitHub."); return; } console.error(`GitHub API error: ${res.status} ${res.statusText}`); return; } const data = (await res.json()); const latest = data.tag_name.replace(/^v/, ""); if (compareSemver(latest, current) > 0) { console.log(`\nUpdate available: v${latest} (current: v${current})`); console.log(`Download: ${data.html_url}`); console.log("\nTo update: download the new version, replace server/src/, and run 'node build/setup.js install'"); } else { console.log(`\nUp to date! (${current})`); } } catch (err) { console.error(`Failed to check for updates: ${err.message}`); } } async function cmdConfigure() { const serverPath = resolve(BUILD_INDEX).replace(/\\/g, "/"); if (!existsSync(BUILD_INDEX)) { console.error("Server not built yet. Run 'node build/setup.js install' first."); process.exit(1); } console.log("Detecting AI clients...\n"); // Detect available clients by checking config file locations const home = process.env.HOME || process.env.USERPROFILE || ""; const cwd = process.cwd(); const candidates = [ { name: "Claude Code (project)", configPath: join(cwd, ".mcp.json"), configKey: "godot-mcp-pro", }, { name: "Cursor (project)", configPath: join(cwd, ".cursor", "mcp.json"), configKey: "godot-mcp-pro", }, { name: "Windsurf (project)", configPath: join(cwd, ".windsurf", "mcp.json"), configKey: "godot-mcp-pro", }, { name: "Claude Desktop", configPath: join(home, process.platform === "win32" ? "AppData/Roaming/Claude/claude_desktop_config.json" : process.platform === "darwin" ? "Library/Application Support/Claude/claude_desktop_config.json" : ".config/claude/claude_desktop_config.json"), configKey: "godot-mcp-pro", }, ]; // Find existing configs const existing = candidates.filter((c) => existsSync(c.configPath)); const missing = candidates.filter((c) => !existsSync(c.configPath)); if (existing.length > 0) { console.log("Found existing configs:"); for (const c of existing) { console.log(` ✓ ${c.name}: ${c.configPath}`); } } // Default: create .mcp.json in cwd (Claude Code) const target = candidates[0]; // Claude Code project-level // No GODOT_MCP_PORT env: lets the server auto-scan 6505-6509 so multiple // Claude Code sessions can each grab a free port. Pinning a single port // here would force every session to collide on 6505. const entry = { command: "node", args: [serverPath], }; let config; if (existsSync(target.configPath)) { try { config = JSON.parse(readFileSync(target.configPath, "utf-8")); if (!config.mcpServers) config.mcpServers = {}; } catch { config = { mcpServers: {} }; } } else { config = { mcpServers: {} }; } if (config.mcpServers[target.configKey]) { console.log(`\n${target.name} already configured in ${target.configPath}`); console.log("Updating server path..."); } config.mcpServers[target.configKey] = entry; const dir = dirname(target.configPath); if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); writeFileSync(target.configPath, JSON.stringify(config, null, 2) + "\n"); console.log(`\nWrote config to: ${target.configPath}`); console.log(`Server path: ${serverPath}`); console.log("\nYou're all set! Start your AI assistant to begin using Godot MCP Pro."); } function cmdDoctor() { console.log("Godot MCP Pro — Environment Check\n"); // Node.js const nodeVer = run("node --version"); const nodeOk = nodeVer.startsWith("v") && parseInt(nodeVer.slice(1)) >= 18; check("Node.js", nodeOk, nodeVer); // npm const npmVer = run("npm --version"); const npmOk = !npmVer.includes("not found") && !npmVer.includes("failed"); check("npm", npmOk, npmVer); // Dependencies installed const nodeModules = existsSync(join(SERVER_DIR, "node_modules")); check("Dependencies installed", nodeModules); // Server built const built = existsSync(BUILD_INDEX); check("Server built", built, built ? BUILD_INDEX : "run 'node build/setup.js install'"); // Version console.log(`\n Version: ${getVersion()}`); // Overall const allOk = nodeOk && npmOk && nodeModules && built; console.log(allOk ? "\nAll good!" : "\nSome issues found. Fix them above."); if (!allOk) process.exit(1); } // ─── Main ───────────────────────────────────────────────────── function showHelp() { console.log(`godot-mcp-setup — Setup and management for Godot MCP Pro Usage: node build/setup.js Commands: install Install dependencies and build the server check-update Check if a newer version is available on GitHub configure Auto-detect AI client and generate .mcp.json config doctor Check Node.js, npm, and build status Options: --help Show this help --version Show current version Examples: node build/setup.js install node build/setup.js doctor node build/setup.js configure node build/setup.js check-update`); } async function main() { const args = process.argv.slice(2); const cmd = args[0]; if (!cmd || cmd === "--help" || cmd === "-h") { showHelp(); process.exit(0); } if (cmd === "--version" || cmd === "-v") { console.log(getVersion()); process.exit(0); } switch (cmd) { case "install": await cmdInstall(); break; case "check-update": await cmdCheckUpdate(); break; case "configure": await cmdConfigure(); break; case "doctor": cmdDoctor(); break; default: console.error(`Unknown command: ${cmd}`); showHelp(); process.exit(1); } } main().catch((err) => { console.error("Fatal:", err.message); process.exit(1); }); //# sourceMappingURL=setup.js.map