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

277 lines 11.5 kB
/** * 画像処理ユーティリティ */ import * as path from 'path'; import * as fs from 'fs/promises'; import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; import { resolveInputPath } from './path.js'; /** * Base64文字列を正規化 * Data URI形式の場合はBase64部分のみを抽出 * * @param input 入力文字列 * @returns 正規化されたBase64文字列 */ export function normalizeBase64String(input) { const trimmed = input.trim(); const commaIndex = trimmed.indexOf(','); if (trimmed.startsWith('data:') && commaIndex !== -1) { return trimmed.slice(commaIndex + 1).replace(/\s/g, ''); } return trimmed.replace(/\s/g, ''); } /** * ファイルパスからMIMEタイプを検出 * * @param filePath ファイルパス * @returns MIMEタイプ(不明な場合はundefined) */ export function detectMimeTypeFromPath(filePath) { const ext = path.extname(filePath).toLowerCase(); switch (ext) { case ".png": return "image/png"; case ".jpg": case ".jpeg": return "image/jpeg"; case ".webp": return "image/webp"; case ".bmp": return "image/bmp"; case ".gif": return "image/gif"; case ".tif": case ".tiff": return "image/tiff"; default: return undefined; } } /** * 画像ソースを解決(Base64文字列またはファイルパスから) * * @param options 解決オプション * @returns 解決された画像ソース * @throws 必須パラメータが不足している場合、またはファイルの読み込みに失敗した場合 */ export async function resolveImageSource({ base64Value, pathValue, label, required, }) { if (base64Value && typeof base64Value === 'string' && base64Value.trim().length > 0) { const trimmed = base64Value.trim(); if (trimmed.startsWith('data:')) { const commaIndex = trimmed.indexOf(','); if (commaIndex === -1) { throw new McpError(ErrorCode.InvalidParams, `${label} data URI is malformed`); } const header = trimmed.slice(5, commaIndex); // drop 'data:' const normalized = trimmed.slice(commaIndex + 1).replace(/\s/g, ''); if (!normalized) { throw new McpError(ErrorCode.InvalidParams, `${label} data URI contains no base64 data`); } const mimeType = header.split(';')[0] || undefined; return { base64: normalized, mimeType, source: "data-uri", }; } const normalized = normalizeBase64String(base64Value); if (!normalized) { throw new McpError(ErrorCode.InvalidParams, `${label} base64 string is empty`); } return { base64: normalized, source: "base64", }; } if (pathValue && typeof pathValue === 'string' && pathValue.trim().length > 0) { const fullPath = resolveInputPath(pathValue); try { const buffer = await fs.readFile(fullPath); return { base64: buffer.toString('base64'), mimeType: detectMimeTypeFromPath(fullPath), source: "file", filePath: fullPath, }; } catch (error) { throw new McpError(ErrorCode.InvalidParams, `${label} file could not be read from path: ${fullPath} (${error instanceof Error ? error.message : String(error)})`); } } if (required) { throw new McpError(ErrorCode.InvalidParams, `${label} is required as base64 string or file path`); } return undefined; } /** * 画像レスポンスを作成(MCPクライアント用) * * @param imageBuffer 画像バッファ * @param mimeType MIMEタイプ * @param filePath 保存先ファイルパス(省略可) * @param additionalInfo 追加情報テキスト(省略可) * @param options レスポンスオプション(省略可) * @returns MCPレスポンス形式 */ export function createImageResponse(imageBuffer, mimeType, filePath, additionalInfo, options) { const base64Data = imageBuffer.toString('base64'); const dataUrl = `data:${mimeType};base64,${base64Data}`; let responseText = additionalInfo || ''; if (filePath) { responseText += `\nSaved to: ${filePath}`; } responseText += `\nFile size: ${imageBuffer.length} bytes\nMIME type: ${mimeType}`; // デフォルトでは画像をLLMコンテキストから除外 const excludeFromContext = options?.excludeFromContext ?? true; // アノテーションの構築 const annotations = options?.annotations || { audience: excludeFromContext ? ["user"] : ["user", "assistant"], }; return { content: [ { type: "text", text: responseText }, { type: "image", data: dataUrl, mimeType: mimeType, annotations: annotations } ], }; } /** * URIベースの画像レスポンスを作成(Resources API用) * * @param uri file:// URI * @param mimeType MIMEタイプ * @param fileSize ファイルサイズ(バイト) * @param filePath 保存先の表示パス * @param absoluteFilePath 実際のファイルの絶対パス(サムネイル生成用) * @param additionalInfo 追加情報テキスト(省略可) * @param includeThumbnail サムネイルを含めるか(デフォルト: true) * @returns MCPレスポンス形式 */ export async function createUriImageResponse(uri, mimeType, fileSize, filePath, absoluteFilePath, additionalInfo, includeThumbnail = true) { let responseText = additionalInfo || ''; responseText += `\nSaved to: ${filePath}`; responseText += `\nFile size: ${fileSize} bytes`; responseText += `\nMIME type: ${mimeType}`; // Markdownリンク形式でURIを追加 const fileName = path.basename(absoluteFilePath); responseText += `\n\n📎 **Full-size image:** [${fileName}](${uri})`; responseText += `\n💡 *You can access the full-resolution image via MCP Resources API*`; // プレーンテキストでもURIを表示(コピー用) responseText += `\n\n🔗 Resource URI:\n\`\`\`\n${uri}\n\`\`\``; const content = [ { type: "text", text: responseText } ]; // サムネイル生成(オプション) // includeThumbnail パラメータで制御(各ツールで環境変数を考慮済み) const thumbnailEnabled = includeThumbnail; if (thumbnailEnabled) { try { if (process.env.DEBUG) { console.error(`[DEBUG] Thumbnail generation enabled, processing: ${absoluteFilePath}`); } const { generateThumbnailDataFromFile } = await import('./thumbnail.js'); const thumbnailData = await generateThumbnailDataFromFile(absoluteFilePath); content.push({ type: "image", data: thumbnailData.base64, mimeType: thumbnailData.mimeType, annotations: { audience: ["user", "assistant"], // LLMとユーザー両方に表示 priority: 0.8 } }); if (process.env.DEBUG) { const thumbnailSize = Math.round(thumbnailData.base64.length * 0.75); // Base64デコード後のサイズ概算 console.error(`[DEBUG] Thumbnail generated successfully: ~${thumbnailSize} bytes`); } } catch (error) { // サムネイル生成失敗はエラーとせず、警告のみ const errorMessage = error instanceof Error ? error.message : String(error); console.error(`[WARNING] Failed to generate thumbnail for ${absoluteFilePath}: ${errorMessage}`); if (process.env.DEBUG && error instanceof Error) { console.error(`[DEBUG] Thumbnail error stack: ${error.stack}`); } } } else if (process.env.DEBUG) { console.error(`[DEBUG] Thumbnail generation disabled (VERTEXAI_IMAGEN_THUMBNAIL=${process.env.VERTEXAI_IMAGEN_THUMBNAIL})`); } return { content }; } /** * 複数画像のURIベースレスポンスを作成(Resources API用、マルチサンプル生成) * * @param imageInfos 複数画像の情報配列 * @param additionalInfo 追加情報テキスト(省略可) * @param includeThumbnail サムネイルを含めるか(デフォルト: true) * @returns MCPレスポンス形式 */ export async function createMultiUriImageResponse(imageInfos, additionalInfo, includeThumbnail = true) { let responseText = additionalInfo || ''; responseText += `\n\nGenerated ${imageInfos.length} image(s):`; for (let i = 0; i < imageInfos.length; i++) { const info = imageInfos[i]; const fileName = path.basename(info.absoluteFilePath); responseText += `\n ${i + 1}. ${info.filePath} (${info.fileSize} bytes)`; // Markdownリンクを追加 responseText += `\n 📎 [${fileName}](${info.uri})`; } responseText += `\n\n💡 *Full-size images available via MCP Resources API*`; const content = [ { type: "text", text: responseText } ]; // サムネイル生成(オプション) const thumbnailEnabled = includeThumbnail; for (const info of imageInfos) { // サムネイルを生成 if (thumbnailEnabled) { try { if (process.env.DEBUG) { console.error(`[DEBUG] Generating thumbnail for: ${info.absoluteFilePath}`); } const { generateThumbnailDataFromFile } = await import('./thumbnail.js'); const thumbnailData = await generateThumbnailDataFromFile(info.absoluteFilePath); content.push({ type: "image", data: thumbnailData.base64, mimeType: thumbnailData.mimeType, annotations: { audience: ["user", "assistant"], // LLMとユーザー両方に表示 priority: 0.8 } }); if (process.env.DEBUG) { const thumbnailSize = Math.round(thumbnailData.base64.length * 0.75); console.error(`[DEBUG] Thumbnail generated: ~${thumbnailSize} bytes`); } } catch (error) { // サムネイル生成失敗はエラーとせず、警告のみ const errorMessage = error instanceof Error ? error.message : String(error); console.error(`[WARNING] Failed to generate thumbnail for ${info.absoluteFilePath}: ${errorMessage}`); if (process.env.DEBUG && error instanceof Error) { console.error(`[DEBUG] Thumbnail error stack: ${error.stack}`); } } } } if (!thumbnailEnabled && process.env.DEBUG) { console.error(`[DEBUG] Thumbnail generation disabled (VERTEXAI_IMAGEN_THUMBNAIL=${process.env.VERTEXAI_IMAGEN_THUMBNAIL})`); } return { content }; } //# sourceMappingURL=image.js.map