Skip to main content
Glama
shimizu

ModelsLab Text2Image MCP Server

by shimizu
modelslab-image-handler.js9.51 kB
import fs from 'fs/promises'; import path from 'path'; import axios from 'axios'; /** * ModelsLab生成画像をダウンロード・保存するユーティリティ */ export class ModelsLabImageHandler { /** * 画像URLから画像をダウンロードしてファイルに保存 * @param {string} imageUrl - 画像URL * @param {string} outputPath - 出力ファイルパス * @param {Object} options - オプション設定 * @returns {Object} 保存結果 */ static async downloadAndSaveImage(imageUrl, outputPath, options = {}) { try { const { timeout = 30000, format = 'original' } = options; // ディレクトリが存在しない場合は作成 const dir = path.dirname(outputPath); await fs.mkdir(dir, { recursive: true }); // 画像をダウンロード const response = await axios({ method: 'GET', url: imageUrl, responseType: 'arraybuffer', timeout: timeout, headers: { 'User-Agent': 'ModelsLab-MCP/1.0', 'Accept': 'image/*' } }); if (!response.data) { throw new Error('Empty response data'); } // ファイル形式の検証と変換 const finalPath = await this._processAndSaveImage( response.data, outputPath, format, response.headers['content-type'] ); const stats = await fs.stat(finalPath); return { success: true, path: finalPath, size: this.formatFileSize(stats.size), bytes: stats.size, contentType: response.headers['content-type'] || 'image/jpeg', originalUrl: imageUrl, downloadTime: new Date().toISOString() }; } catch (error) { throw new Error(`Failed to download image: ${error.message}`); } } /** * 複数の画像を一括ダウンロード * @param {Array} imageUrls - 画像URLの配列 * @param {string} basePath - ベースパス * @param {Object} options - オプション設定 * @returns {Object} ダウンロード結果 */ static async downloadMultipleImages(imageUrls, basePath, options = {}) { const { namePrefix = 'image', format = 'original', concurrent = 3 } = options; if (!Array.isArray(imageUrls) || imageUrls.length === 0) { throw new Error('imageUrls must be a non-empty array'); } const results = []; const errors = []; // 並行ダウンロード数を制限 for (let i = 0; i < imageUrls.length; i += concurrent) { const batch = imageUrls.slice(i, i + concurrent); const promises = batch.map(async (url, batchIndex) => { const index = i + batchIndex; const extension = this._getExtensionFromFormat(format) || '.jpg'; const fileName = `${namePrefix}_${index + 1}${extension}`; const outputPath = path.join(basePath, fileName); try { const result = await this.downloadAndSaveImage(url, outputPath, { format }); return { index, ...result }; } catch (error) { const errorInfo = { index, url, error: error.message }; errors.push(errorInfo); return null; } }); const batchResults = await Promise.all(promises); results.push(...batchResults.filter(r => r !== null)); } return { success: results.length > 0, downloaded: results.length, failed: errors.length, total: imageUrls.length, results: results, errors: errors }; } /** * 画像データを処理して保存 * @private */ static async _processAndSaveImage(imageData, outputPath, format, contentType) { // 元の拡張子を取得 const originalExt = this._getExtensionFromContentType(contentType); const requestedExt = path.extname(outputPath).toLowerCase(); let finalPath = outputPath; let dataToSave = imageData; // フォーマット変換の処理 if (format === 'original') { // 元の形式を保持、拡張子が指定されていない場合は推定して追加 if (!requestedExt && originalExt) { finalPath = outputPath + originalExt; } } else { // 指定されたフォーマットに変換(実際の変換は簡略化、必要に応じて画像処理ライブラリを使用) const targetExt = this._getExtensionFromFormat(format); if (targetExt) { finalPath = outputPath.replace(/\.[^.]*$/, '') + targetExt; } // 注意: 実際の画像形式変換はここでは実装せず、元データをそのまま保存 // 必要に応じてSharp等の画像処理ライブラリを使用 } // ファイルパス検証 if (!this.validateImageFilePath(finalPath)) { throw new Error('Invalid image file path'); } // ファイルに保存 await fs.writeFile(finalPath, dataToSave); return finalPath; } /** * Content-Typeから拡張子を推定 * @private */ static _getExtensionFromContentType(contentType) { if (!contentType) return '.jpg'; const mimeToExt = { 'image/jpeg': '.jpg', 'image/jpg': '.jpg', 'image/png': '.png', 'image/gif': '.gif', 'image/webp': '.webp', 'image/bmp': '.bmp', 'image/tiff': '.tiff' }; return mimeToExt[contentType.toLowerCase()] || '.jpg'; } /** * フォーマット指定から拡張子を取得 * @private */ static _getExtensionFromFormat(format) { const formatToExt = { 'jpg': '.jpg', 'jpeg': '.jpg', 'png': '.png', 'gif': '.gif', 'webp': '.webp', 'bmp': '.bmp', 'tiff': '.tiff', 'original': null }; return formatToExt[format.toLowerCase()]; } /** * 画像ファイルパスの妥当性をチェック * @param {string} filePath - ファイルパス * @returns {boolean} 妥当性 */ static validateImageFilePath(filePath) { if (!filePath || typeof filePath !== 'string') { return false; } // 危険なパスをチェック const normalizedPath = path.normalize(filePath); if (normalizedPath.includes('..')) { return false; } // 拡張子チェック const ext = path.extname(filePath).toLowerCase(); const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.tiff']; return allowedExtensions.includes(ext); } /** * ファイルサイズを人間が読みやすい形式に変換 * @param {number} bytes - バイト数 * @returns {string} フォーマットされたサイズ */ static formatFileSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } /** * 統合的な画像処理(MCP応答付き) * @param {Object} params - パラメータ * @returns {Object} MCP形式のレスポンス */ static async handleImageDownloadWithOptionalFile(params) { const { imageUrls, outputPath, format = 'original', metadata = {}, toolName, namePrefix = 'generated_image' } = params; if (!Array.isArray(imageUrls) || imageUrls.length === 0) { throw new Error('imageUrls must be a non-empty array'); } // ファイル出力が指定されている場合 if (outputPath) { let result; if (imageUrls.length === 1) { // 単一画像の場合 result = await this.downloadAndSaveImage(imageUrls[0], outputPath, { format }); return { content: [{ type: 'text', text: JSON.stringify({ status: 'success', message: '画像をファイルに保存しました', file: result.path, size: result.size, contentType: result.contentType, format: format, ...metadata }, null, 2) }] }; } else { // 複数画像の場合 const basePath = path.dirname(outputPath); result = await this.downloadMultipleImages(imageUrls, basePath, { namePrefix, format }); return { content: [{ type: 'text', text: JSON.stringify({ status: 'success', message: `${result.downloaded}枚の画像をファイルに保存しました`, downloaded: result.downloaded, failed: result.failed, total: result.total, files: result.results.map(r => ({ path: r.path, size: r.size })), errors: result.errors, format: format, ...metadata }, null, 2) }] }; } } // ファイル出力が指定されていない場合は通常のMCP応答 return { content: [{ type: 'text', text: JSON.stringify({ success: true, imageUrls: imageUrls, metadata: { tool: toolName, timestamp: new Date().toISOString(), imageCount: imageUrls.length, ...metadata } }, null, 2) }] }; } }

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/shimizu/modelslab-text2img-mcp-server'

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