Skip to main content
Glama
extension.js14.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.activate = activate; exports.deactivate = deactivate; const vscode = __importStar(require("vscode")); const path = __importStar(require("path")); const fs = __importStar(require("fs")); const cp = __importStar(require("child_process")); const os = __importStar(require("os")); const SERVER_ID = "imagen"; const API_KEY_SECRET = "imagenMcp.apiKey"; const DEFAULT_MODEL = "gemini-3-pro-image-preview"; const VENV_DIR_NAME = ".imagen-venv"; // Python dependencies needed by the server const PYTHON_DEPENDENCIES = [ "fastmcp>=2.0.0", "Pillow>=10.4.0", "pillow-heif>=0.18.0", ]; function getWorkspaceRoot() { const folder = vscode.workspace.workspaceFolders?.[0]; return folder?.uri.fsPath; } async function getApiKey(secretStorage) { return secretStorage.get(API_KEY_SECRET); } function getConfiguredApiKey() { const config = vscode.workspace.getConfiguration(); const raw = config.get("imagenMcp.apiKey"); const trimmed = raw?.trim(); return trimmed ? trimmed : undefined; } /** * Get the path to the extension's bundled server directory. */ function getBundledServerPath(context) { return path.join(context.extensionPath, "server"); } /** * Get the path where we store the virtual environment. * Uses VS Code's global storage path so it persists across workspaces. */ function getVenvPath(context) { return path.join(context.globalStorageUri.fsPath, VENV_DIR_NAME); } /** * Get the Python executable path within the virtual environment. */ function getVenvPython(context) { const venvPath = getVenvPath(context); const isWindows = os.platform() === "win32"; return isWindows ? path.join(venvPath, "Scripts", "python.exe") : path.join(venvPath, "bin", "python"); } /** * Find a Python 3 interpreter on the system. */ async function findPython3() { const candidates = os.platform() === "win32" ? ["python", "python3", "py -3"] : ["python3", "python"]; for (const candidate of candidates) { try { const result = await execCommand(`${candidate} --version`); if (result.includes("Python 3")) { return candidate.split(" ")[0]; // Return just "python3" or "python", not "py -3" } } catch { // Try next candidate } } return undefined; } /** * Execute a command and return stdout. */ function execCommand(command, cwd) { return new Promise((resolve, reject) => { cp.exec(command, { cwd, timeout: 120000 }, (error, stdout, stderr) => { if (error) { reject(new Error(`${error.message}\n${stderr}`)); } else { resolve(stdout); } }); }); } /** * Check if the virtual environment exists and is valid. */ async function isVenvValid(context) { const pythonPath = getVenvPython(context); try { await fs.promises.access(pythonPath, fs.constants.X_OK); return true; } catch { return false; } } /** * Check if a marker file indicates dependencies are installed. */ async function areDependenciesInstalled(context) { const markerPath = path.join(getVenvPath(context), ".deps-installed"); try { await fs.promises.access(markerPath, fs.constants.F_OK); return true; } catch { return false; } } /** * Write a marker file to indicate dependencies are installed. */ async function markDependenciesInstalled(context) { const markerPath = path.join(getVenvPath(context), ".deps-installed"); await fs.promises.writeFile(markerPath, new Date().toISOString(), "utf8"); } /** * Ensure the Python environment is set up with all dependencies. */ async function ensurePythonEnvironment(context) { const venvPath = getVenvPath(context); const pythonPath = getVenvPython(context); // Ensure global storage directory exists await fs.promises.mkdir(context.globalStorageUri.fsPath, { recursive: true }); // Check if venv already exists and is valid if (await isVenvValid(context) && await areDependenciesInstalled(context)) { return true; } // Find Python 3 const systemPython = await findPython3(); if (!systemPython) { vscode.window.showErrorMessage("Python 3 is required but not found. Please install Python 3 and try again."); return false; } // Show progress while setting up return vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: "Imagen MCP: Setting up Python environment...", cancellable: false, }, async (progress) => { try { // Create virtual environment if needed if (!(await isVenvValid(context))) { progress.report({ message: "Creating virtual environment..." }); await execCommand(`${systemPython} -m venv "${venvPath}"`); } // Upgrade pip progress.report({ message: "Upgrading pip..." }); await execCommand(`"${pythonPath}" -m pip install --upgrade pip`); // Install dependencies progress.report({ message: "Installing dependencies..." }); for (const dep of PYTHON_DEPENDENCIES) { await execCommand(`"${pythonPath}" -m pip install "${dep}"`); } // Mark dependencies as installed await markDependenciesInstalled(context); vscode.window.showInformationMessage("Imagen MCP: Python environment ready."); return true; } catch (error) { vscode.window.showErrorMessage(`Imagen MCP: Failed to set up Python environment: ${error}`); return false; } }); } /** * Get the server command and args for the MCP config. * Uses the bundled server from the extension. */ function getServerConfig(context) { const pythonPath = getVenvPython(context); const serverPath = path.join(getBundledServerPath(context), "run_server.py"); return { command: pythonPath, args: [serverPath], }; } async function syncApiKeyFromSettings(context) { const configured = getConfiguredApiKey(); if (configured) { await context.secrets.store(API_KEY_SECRET, configured); return configured; } // Keep any previously stored secret so extension updates or missing settings // do not wipe the API key. Users can explicitly clear via the command or by // overwriting the setting. return getApiKey(context.secrets); } async function setApiKey(context) { const apiKey = await vscode.window.showInputBox({ prompt: "Enter your Google AI API key", password: true, ignoreFocusOut: true, placeHolder: "GOOGLE_AI_API_KEY" }); if (!apiKey) { return; } await context.secrets.store(API_KEY_SECRET, apiKey.trim()); await vscode.workspace.getConfiguration().update("imagenMcp.apiKey", apiKey.trim(), vscode.ConfigurationTarget.Workspace); vscode.window.showInformationMessage("Imagen MCP API key saved to VS Code secrets."); } async function promptForApiKeyIfMissing(context) { const existing = await getApiKey(context.secrets); if (existing) { return; } const choice = await vscode.window.showWarningMessage("Imagen MCP API key is not set. The server will fail without it.", "Set API Key"); if (choice === "Set API Key") { await setApiKey(context); await ensureMcpConfig(context, { force: true, notify: false }); } } function updateStatusBar(status) { const config = vscode.workspace.getConfiguration(); const current = config.get("imagenMcp.modelId") || DEFAULT_MODEL; status.text = `$(rocket) Imagen: ${current}`; } async function setModel(context, status) { const config = vscode.workspace.getConfiguration(); const current = config.get("imagenMcp.modelId") || DEFAULT_MODEL; const picks = [ "gemini-3-pro-image-preview", "gemini-2.5-flash-image", "gemini-2.0-flash-exp-image-generation", "imagen-4.0-generate-001", "imagen-4.0-ultra-generate-001" ]; const selection = await vscode.window.showQuickPick(picks, { placeHolder: "Select default image model", canPickMany: false }); if (!selection) { return; } await config.update("imagenMcp.modelId", selection, vscode.ConfigurationTarget.Workspace); vscode.window.showInformationMessage(`Imagen MCP model set to ${selection}.`); updateStatusBar(status); await ensureMcpConfig(context, { force: true, notify: false }); } async function writeMcpConfig(context) { await syncApiKeyFromSettings(context); await ensureMcpConfig(context, { force: true, notify: true }); } /** * Reinstall the Python environment from scratch. */ async function reinstallEnvironment(context) { const venvPath = getVenvPath(context); // Remove existing venv try { await fs.promises.rm(venvPath, { recursive: true, force: true }); } catch { // Ignore errors if directory doesn't exist } // Reinstall const success = await ensurePythonEnvironment(context); if (success) { // Regenerate MCP config with new paths await ensureMcpConfig(context, { force: true, notify: true }); } } async function ensureMcpConfig(context, options = {}) { const root = getWorkspaceRoot(); if (!root) { vscode.window.showErrorMessage("No workspace folder open."); return; } const vscodeDir = path.join(root, ".vscode"); const mcpPath = path.join(vscodeDir, "mcp.json"); // Check if we need to migrate from old workspace-based config let needsMigration = false; if (fs.existsSync(mcpPath)) { try { const existing = JSON.parse(await fs.promises.readFile(mcpPath, "utf8")); const imagenServer = existing?.servers?.[SERVER_ID]; if (imagenServer) { const cmd = imagenServer.command || ""; // Detect old workspace-based configs that need migration if (cmd.includes("${workspaceFolder}") || cmd.includes("run_with_venv.sh") || cmd.includes("run_server.py")) { needsMigration = true; } } } catch { // If we can't parse, let's regenerate needsMigration = true; } } if (!options.force && !needsMigration && fs.existsSync(mcpPath)) { return; // already present and doesn't need migration } // Ensure the Python environment is set up before writing the MCP config const envReady = await ensurePythonEnvironment(context); if (!envReady) { return; // Error already shown by ensurePythonEnvironment } const config = vscode.workspace.getConfiguration(); const apiKey = await getApiKey(context.secrets); const modelId = config.get("imagenMcp.modelId") || DEFAULT_MODEL; // Get the bundled server configuration const serverConfig = getServerConfig(context); const mcpConfig = { servers: { [SERVER_ID]: { command: serverConfig.command, args: serverConfig.args, env: { ...(apiKey ? { GOOGLE_AI_API_KEY: apiKey } : {}), IMAGEN_MODEL_ID: modelId, }, }, }, }; await fs.promises.mkdir(vscodeDir, { recursive: true }); await fs.promises.writeFile(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8"); if (options.notify || needsMigration) { vscode.window.showInformationMessage(needsMigration ? `Imagen MCP config migrated to use bundled server: ${mcpPath}` : `MCP config written to ${mcpPath}`); } } function activate(context) { const status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10); status.text = `$(rocket) Imagen MCP`; status.tooltip = "Imagen MCP Server"; status.command = "imagenMcp.setModel"; status.show(); context.subscriptions.push(vscode.commands.registerCommand("imagenMcp.setApiKey", () => setApiKey(context)), vscode.commands.registerCommand("imagenMcp.setModel", () => setModel(context, status)), vscode.commands.registerCommand("imagenMcp.writeMcpConfig", () => writeMcpConfig(context)), vscode.commands.registerCommand("imagenMcp.reinstallEnvironment", () => reinstallEnvironment(context)), status); updateStatusBar(status); context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(async (event) => { if (event.affectsConfiguration("imagenMcp.apiKey")) { await syncApiKeyFromSettings(context); await promptForApiKeyIfMissing(context); } if (event.affectsConfiguration("imagenMcp.modelId")) { updateStatusBar(status); } if (event.affectsConfiguration("imagenMcp")) { await ensureMcpConfig(context, { force: true, notify: false }); } })); // Auto-create MCP config if missing so the server works out of the box void (async () => { await syncApiKeyFromSettings(context); await ensureMcpConfig(context, { force: false, notify: false }); await promptForApiKeyIfMissing(context); })(); } function deactivate() { } //# sourceMappingURL=extension.js.map

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vipincr/imagen-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server