UNPKG

@dondonudonjp/vertexai-imagen-mcp-server

Version:

[DEPRECATED] MCP Server for Vertex AI Imagen image generation. Imagen endpoints shut down 2026-06-30; migrate to the nanoBanana MCP Server (Gemini image models).

249 lines 9.81 kB
import * as os from 'os'; import * as path from 'path'; import * as fs from 'fs/promises'; /** * Get default output directory (cross-platform) * * Priority: * 1. VERTEXAI_IMAGEN_OUTPUT_DIR environment variable * 2. ~/Downloads/vertexai-imagen-files (default) */ export function getDefaultOutputDirectory() { // Check environment variable first const envDir = process.env.VERTEXAI_IMAGEN_OUTPUT_DIR; if (envDir) { return path.resolve(envDir); } // Use os.homedir() for cross-platform home directory const homeDir = os.homedir(); const defaultDir = path.join(homeDir, 'Downloads', 'vertexai-imagen-files'); return defaultDir; } /** * Generate unique file path by adding number suffix if file exists * * Examples: * - image.png → image.png (if not exists) * - image.png → image_1.png (if image.png exists) * - image.png → image_2.png (if image.png and image_1.png exist) * * @param filePath Original file path * @returns Unique file path (may be the same as input if no conflict) */ export async function generateUniqueFilePath(filePath) { try { // Check if file exists await fs.access(filePath); // File exists, generate unique name } catch { // File doesn't exist, use original path return filePath; } const parsedPath = path.parse(filePath); let counter = 1; let uniquePath; while (true) { // Generate path with counter: basename_1.ext, basename_2.ext, ... uniquePath = path.join(parsedPath.dir, `${parsedPath.name}_${counter}${parsedPath.ext}`); try { await fs.access(uniquePath); // File exists, try next number counter++; } catch { // File doesn't exist, use this path return uniquePath; } // Safety limit to prevent infinite loop if (counter > 10000) { throw new Error(`Cannot generate unique file name after 10000 attempts for: ${filePath}`); } } } /** * Normalize and validate output path (cross-platform) * * - Absolute paths: used as-is (must be within configured base directory) * - Relative paths: resolved relative to default output directory * - Creates parent directory if it doesn't exist * - Optionally generates unique file name if file already exists * - Validates path is within base directory (prevents path traversal attacks) * * @param outputPath Output file path * @param autoNumbering If true, automatically add number suffix to avoid overwriting (default: true) * @throws Error if path traversal is detected or directory creation fails */ export async function normalizeAndValidatePath(outputPath, autoNumbering = true) { const defaultDir = getDefaultOutputDirectory(); let absolutePath; // If already absolute, use as-is if (path.isAbsolute(outputPath)) { absolutePath = outputPath; } else { // Relative path: resolve from default output directory absolutePath = path.join(defaultDir, outputPath); } // Security: Validate path is within base directory (prevent path traversal) validatePathWithinBase(absolutePath, defaultDir); // Ensure parent directory exists (create if needed) const dir = path.dirname(absolutePath); try { await fs.mkdir(dir, { recursive: true }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Failed to create output directory: ${dir}\n` + `Error: ${errorMessage}\n` + `\n` + `Troubleshooting:\n` + ` - Check write permissions for the directory\n` + ` - Ensure the path is valid for your operating system\n` + ` - Try using VERTEXAI_IMAGEN_OUTPUT_DIR environment variable to set a custom output directory\n` + ` - Default output directory: ${getDefaultOutputDirectory()}`); } // Generate unique file path if auto-numbering is enabled if (autoNumbering) { absolutePath = await generateUniqueFilePath(absolutePath); } return absolutePath; } /** * Convert WSL path to Windows path * Examples: * - /mnt/c/Users/... → C:\Users\... * - /mnt/d/Data/... → D:\Data\... * * @param wslPath WSL-style path * @returns Windows path if WSL path detected, otherwise original path */ function convertWslPathToWindows(wslPath) { // Check if this is a WSL path: /mnt/{drive_letter}/... const wslPattern = /^\/mnt\/([a-z])\//i; const match = wslPath.match(wslPattern); if (match) { const driveLetter = match[1].toUpperCase(); // Replace /mnt/c/ with C:\ (and handle forward slashes) const windowsPath = wslPath.replace(wslPattern, `${driveLetter}:\\`).replace(/\//g, '\\'); return windowsPath; } return wslPath; } /** * Resolve input file path (for reading existing files) * * - Absolute paths: used as-is (with WSL path conversion if needed, must be within configured base directory) * - Relative paths: resolved relative to default output directory * (assuming the file was generated by this tool and saved to output directory) * - Validates path is within base directory (prevents path traversal attacks) * * @param inputPath Input file path * @returns Absolute path * @throws Error if path traversal is detected */ export function resolveInputPath(inputPath) { const defaultDir = getDefaultOutputDirectory(); // Convert WSL paths to Windows paths if needed const convertedPath = convertWslPathToWindows(inputPath); let absolutePath; // If already absolute, use as-is if (path.isAbsolute(convertedPath)) { absolutePath = convertedPath; } else { // Relative path: resolve from default output directory absolutePath = path.join(defaultDir, convertedPath); } // Security: Validate path is within base directory (prevent path traversal) validatePathWithinBase(absolutePath, defaultDir); return absolutePath; } /** * Get user-friendly display path * Converts absolute path to ~ notation for better readability */ export function getDisplayPath(absolutePath) { const homeDir = os.homedir(); if (absolutePath.startsWith(homeDir)) { return absolutePath.replace(homeDir, '~'); } return absolutePath; } /** * Generate multiple file paths with numbered suffixes for multi-sample generation * * Examples: * - generateMultipleFilePaths("image.png", 3) → ["image_1.png", "image_2.png", "image_3.png"] * - generateMultipleFilePaths("output.jpg", 1) → ["output_1.jpg"] * * Each path is checked for uniqueness using generateUniqueFilePath to avoid overwriting. * * @param baseOutputPath Base output file path * @param sampleCount Number of files to generate paths for * @param autoNumbering If true, automatically add number suffix to avoid overwriting (default: true) * @returns Array of unique file paths */ export async function generateMultipleFilePaths(baseOutputPath, sampleCount, autoNumbering = true) { if (sampleCount < 1) { throw new Error(`sampleCount must be at least 1, got: ${sampleCount}`); } // If only one sample, return single path (with optional auto-numbering) if (sampleCount === 1) { if (autoNumbering) { return [await generateUniqueFilePath(baseOutputPath)]; } return [baseOutputPath]; } // For multiple samples, add numbered suffixes const parsedPath = path.parse(baseOutputPath); const filePaths = []; for (let i = 1; i <= sampleCount; i++) { // Generate path with counter: basename_1.ext, basename_2.ext, ... const numberedPath = path.join(parsedPath.dir, `${parsedPath.name}_${i}${parsedPath.ext}`); // Apply auto-numbering if enabled (to avoid overwriting existing files) if (autoNumbering) { filePaths.push(await generateUniqueFilePath(numberedPath)); } else { filePaths.push(numberedPath); } } return filePaths; } /** * Validate that a resolved path is within a base directory * Prevents path traversal attacks (CWE-22) * * @param targetPath - The path to validate (will be resolved to absolute path) * @param basePath - The base directory path (will be resolved to absolute path) * @throws Error if targetPath is outside basePath * * @example * // Valid: path within base directory * validatePathWithinBase('/home/user/images/photo.png', '/home/user/images'); * * @example * // Invalid: path traversal detected * validatePathWithinBase('/home/user/images/../../../etc/passwd', '/home/user/images'); * // throws Error: Security Error: Path traversal detected */ export function validatePathWithinBase(targetPath, basePath) { // Resolve both paths to absolute paths to normalize them const resolvedTarget = path.resolve(targetPath); const resolvedBase = path.resolve(basePath); // Calculate relative path from base to target const relative = path.relative(resolvedBase, resolvedTarget); // If relative path starts with '..' or is absolute, target is outside base // Note: path.relative returns absolute path if paths are on different drives (Windows) if (relative.startsWith('..') || path.isAbsolute(relative)) { throw new Error(`Security Error: Path traversal detected.\n` + `Attempted path: ${targetPath}\n` + `Resolved to: ${resolvedTarget}\n` + `Must be within: ${resolvedBase}\n` + `\n` + `Path traversal (using ../) to access parent directories is not allowed for security reasons.\n` + `Please specify a path within the configured directory.`); } } //# sourceMappingURL=path.js.map