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

202 lines 7.54 kB
/** * MCP Resources API 用の画像リソース管理クラス * * 出力ディレクトリ内の画像ファイルをリソースとして管理し、 * file:// URI経由でのアクセスを提供します。 */ import * as path from 'path'; import * as fs from 'fs/promises'; import { fileURLToPath, pathToFileURL } from 'url'; import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; import { detectMimeTypeFromPath } from './image.js'; /** * 画像リソースマネージャー */ export class ImageResourceManager { outputDir; /** * @param outputDir 出力ディレクトリの絶対パス */ constructor(outputDir) { this.outputDir = path.resolve(outputDir); } /** * 出力ディレクトリを取得 */ getOutputDir() { return this.outputDir; } /** * ファイルパスからfile:// URIを生成 * * @param filePath ファイルの絶対パス * @returns file:// URI * @throws パスが出力ディレクトリ外の場合 */ getFileUri(filePath) { const resolvedPath = path.resolve(filePath); // セキュリティチェック: 出力ディレクトリ内かを確認 if (!this.isPathInOutputDir(resolvedPath)) { throw new McpError(ErrorCode.InvalidRequest, `File path is outside the output directory: ${filePath}`); } // file:// URI に変換 return pathToFileURL(resolvedPath).href; } /** * file:// URI からファイルパスを解決 * * @param uri file:// URI * @returns ファイルの絶対パス、またはnull(無効なURIの場合) * @throws URIが出力ディレクトリ外を指す場合 */ resolveUri(uri) { // file:// スキームのチェック if (!uri.startsWith('file://')) { return null; } try { const filePath = fileURLToPath(uri); const resolvedPath = path.resolve(filePath); // セキュリティチェック: 出力ディレクトリ内かを確認 if (!this.isPathInOutputDir(resolvedPath)) { throw new McpError(ErrorCode.InvalidRequest, `URI points to a file outside the output directory: ${uri}`); } return resolvedPath; } catch (error) { if (error instanceof McpError) { throw error; } // URI解析エラー return null; } } /** * パスが出力ディレクトリ内かをチェック * * @param targetPath チェック対象のパス * @returns true: ディレクトリ内、false: ディレクトリ外 */ isPathInOutputDir(targetPath) { const resolvedTarget = path.resolve(targetPath); const resolvedOutput = path.resolve(this.outputDir); // パストラバーサル攻撃を防ぐため、正規化されたパスで比較 return resolvedTarget.startsWith(resolvedOutput + path.sep) || resolvedTarget === resolvedOutput; } /** * 出力ディレクトリ内のすべての画像リソースを一覧取得 * * @returns リソース情報の配列 */ async listResources() { const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.bmp', '.tiff', '.tif']; const resources = []; try { // 出力ディレクトリが存在するか確認 await fs.access(this.outputDir); } catch (error) { // ディレクトリが存在しない場合は空配列を返す if (process.env.DEBUG) { console.error(`[DEBUG] Output directory does not exist: ${this.outputDir}`); } return resources; } try { const files = await fs.readdir(this.outputDir); for (const file of files) { const ext = path.extname(file).toLowerCase(); // 画像ファイルのみを対象 if (!imageExtensions.includes(ext)) { continue; } const filePath = path.join(this.outputDir, file); try { const stats = await fs.stat(filePath); // ディレクトリはスキップ if (!stats.isFile()) { continue; } const uri = this.getFileUri(filePath); const mimeType = detectMimeTypeFromPath(filePath) || 'application/octet-stream'; resources.push({ uri, name: file, mimeType, description: `Generated image: ${file}`, size: stats.size }); } catch (error) { // 個別ファイルのエラーは無視して続行 if (process.env.DEBUG) { console.error(`[DEBUG] Failed to process file ${file}: ${error}`); } continue; } } if (process.env.DEBUG) { console.error(`[DEBUG] Found ${resources.length} image resources in ${this.outputDir}`); } return resources; } catch (error) { throw new McpError(ErrorCode.InternalError, `Failed to list resources: ${error instanceof Error ? error.message : String(error)}`); } } /** * 指定されたURIのリソースを読み込み * * @param uri file:// URI * @returns 画像データのバッファ * @throws ファイルが見つからない、または読み込みに失敗した場合 */ async readResource(uri) { const filePath = this.resolveUri(uri); if (!filePath) { throw new McpError(ErrorCode.InvalidRequest, `Invalid resource URI: ${uri}`); } try { const buffer = await fs.readFile(filePath); if (process.env.DEBUG) { console.error(`[DEBUG] Read resource: ${uri} (${buffer.length} bytes)`); } return buffer; } catch (error) { if (error instanceof Error && 'code' in error && error.code === 'ENOENT') { throw new McpError(ErrorCode.InvalidRequest, `Resource not found: ${uri}`); } throw new McpError(ErrorCode.InternalError, `Failed to read resource: ${error instanceof Error ? error.message : String(error)}`); } } /** * MCP Resource形式でリソース情報を返す * * @param uri file:// URI * @returns MCP Resource形式のオブジェクト */ async getResourceMetadata(uri) { const filePath = this.resolveUri(uri); if (!filePath) { return null; } try { const stats = await fs.stat(filePath); const fileName = path.basename(filePath); const mimeType = detectMimeTypeFromPath(filePath) || 'application/octet-stream'; return { uri, name: fileName, mimeType, description: `Generated image: ${fileName}`, size: stats.size }; } catch (error) { return null; } } } //# sourceMappingURL=resources.js.map