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).

152 lines 7.36 kB
import axios from 'axios'; import fs from 'fs/promises'; import path from 'path'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { normalizeAndValidatePath, getDisplayPath, resolveInputPath, } from '../utils/path.js'; import { getProjectId, getUpscaleApiUrl, getAuthHeaders } from '../utils/auth.js'; import { createImageResponse, createUriImageResponse, } from '../utils/image.js'; import { generateImageUUID, calculateParamsHash, embedMetadata, isMetadataEmbeddingEnabled, } from '../utils/metadata.js'; import { vertexAIRateLimiter } from '../utils/rateLimiter.js'; export async function upscaleImage(context, args) { const { input_path, output_path, scale_factor = '2', return_base64 = false, include_thumbnail, region, } = args; const { auth, resourceManager, historyDb } = context; if (!input_path || typeof input_path !== 'string') { throw new McpError(ErrorCode.InvalidParams, 'input_path is required and must be a string'); } if (process.env.DEBUG) { console.error(`[DEBUG] upscale_image: scale=${scale_factor}`); } // パラメータハッシュの計算(履歴管理用) const params = { input_path, scale_factor, tool: 'upscale_image', }; const paramsHash = calculateParamsHash(params); // UUID発行 const uuid = generateImageUUID(); const metadataEmbeddingEnabled = isMetadataEmbeddingEnabled(); if (process.env.DEBUG && metadataEmbeddingEnabled) { console.error(`[DEBUG] Metadata embedding enabled. UUID: ${uuid}`); } try { const resolvedInputPath = resolveInputPath(input_path); const inputImageBuffer = await fs.readFile(resolvedInputPath); const inputImageBase64 = inputImageBuffer.toString('base64'); const parsedPath = path.parse(input_path); const defaultOutputPath = path.join(parsedPath.dir, `upscaled_${scale_factor}x_${parsedPath.base}`); const finalOutputPath = output_path || defaultOutputPath; const normalizedPath = return_base64 ? undefined : await normalizeAndValidatePath(finalOutputPath); const requestBody = { instances: [ { prompt: '', image: { bytesBase64Encoded: inputImageBase64, }, }, ], parameters: { mode: 'upscale', upscaleConfig: { upscaleFactor: `x${scale_factor}`, }, sampleCount: 1, }, }; const projectId = await getProjectId(auth); const apiUrl = getUpscaleApiUrl(projectId, region); const authHeaders = await getAuthHeaders(auth); const response = await vertexAIRateLimiter.execute(() => axios.post(apiUrl, requestBody, { headers: { 'Content-Type': 'application/json', ...authHeaders, }, timeout: 60000, })); if (!response.data.predictions || response.data.predictions.length === 0) { throw new Error('Upscaling failed - no output received'); } const upscaledImage = response.data.predictions[0]; let imageBuffer = Buffer.from(upscaledImage.bytesBase64Encoded, 'base64'); if (return_base64) { console.error('[WARNING] return_base64=true is deprecated and consumes ~1,500 tokens. Use file save mode (default) instead.'); return createImageResponse(imageBuffer, upscaledImage.mimeType, undefined, `Image upscaled successfully!\n\nInput: ${input_path}\nScale factor: ${scale_factor}`); } if (!normalizedPath) { throw new Error('Normalized path is required for file save mode.\n' + 'This is an internal error - please report this issue.'); } // メタデータ埋め込み if (metadataEmbeddingEnabled) { const metadata = { vertexai_imagen_uuid: uuid, params_hash: paramsHash, tool_name: 'upscale_image', model: 'imagen-upscale', created_at: new Date().toISOString(), }; try { imageBuffer = (await embedMetadata(imageBuffer, metadata)); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); console.error(`[WARNING] Failed to embed metadata for ${uuid}: ${errorMsg}`); } } await fs.writeFile(normalizedPath, imageBuffer); const displayPath = getDisplayPath(normalizedPath); const fileUri = resourceManager.getFileUri(normalizedPath); const shouldIncludeThumbnail = include_thumbnail !== undefined ? include_thumbnail : process.env.VERTEXAI_IMAGEN_THUMBNAIL === 'true'; // データベースに履歴記録 try { historyDb.createImageHistory({ uuid, filePath: normalizedPath, toolName: 'upscale_image', prompt: `Upscale ${scale_factor}x: ${input_path}`, model: 'imagen-upscale', sampleCount: 1, parameters: JSON.stringify(params), paramsHash, success: true, fileSize: imageBuffer.length, mimeType: upscaledImage.mimeType, }); if (process.env.DEBUG) { console.error(`[DEBUG] Image history recorded: ${uuid}`); } } catch (dbError) { const errorMsg = dbError instanceof Error ? dbError.message : String(dbError); console.error(`[WARNING] Failed to record image history for ${uuid}: ${errorMsg}`); } return await createUriImageResponse(fileUri, upscaledImage.mimeType, imageBuffer.length, displayPath, normalizedPath, `Image upscaled successfully!\n\nInput: ${input_path}\nScale factor: ${scale_factor}`, shouldIncludeThumbnail); } catch (error) { if (axios.isAxiosError(error)) { const errorMessage = error.response?.data?.error?.message || error.message; const errorCode = error.response?.status; if (process.env.DEBUG) { console.error(`[DEBUG] API Error ${errorCode}: ${errorMessage}`); } if (errorCode === 401 || errorCode === 403) { throw new McpError(ErrorCode.InvalidRequest, `Google Imagen API authentication error: ${errorMessage}`); } if (errorCode === 400) { throw new McpError(ErrorCode.InvalidParams, `Google Imagen API invalid parameter error: ${errorMessage}`); } if (errorCode && errorCode >= 500) { throw new McpError(ErrorCode.InternalError, `Google Imagen API server error: ${errorMessage}`); } throw new McpError(ErrorCode.InternalError, `Google Imagen API error: ${errorMessage}`); } if (error instanceof Error && 'code' in error && error.code === 'ENOENT') { throw new McpError(ErrorCode.InvalidParams, `Input image file not found: ${input_path}`); } throw error; } } //# sourceMappingURL=upscaleImage.js.map