UNPKG

@graisol/gpt-image-mcp

Version:

A Model Context Protocol (MCP) server for OpenAI GPT-Image-1 image generation and editing

399 lines (395 loc) 17.4 kB
#!/usr/bin/env node "use strict"; /** * MCP Server for OpenAI GPT-Image-1 API Integration * * This server provides image generation, editing, and management capabilities * using OpenAI's GPT-Image-1 model through the Model Context Protocol. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js"); const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const path_1 = __importDefault(require("path")); const os_1 = __importDefault(require("os")); const fs_1 = __importDefault(require("fs")); const openai_client_js_1 = require("./utils/openai-client.js"); const image_manager_js_1 = require("./utils/image-manager.js"); const index_js_2 = require("./tools/index.js"); const logger_js_1 = require("./utils/logger.js"); // Determine the best storage path for images function getDefaultStoragePath() { // First try the current working directory const localPath = path_1.default.join(process.cwd(), 'mcp-images'); try { // Check if we can write to the current directory const testDir = path_1.default.join(process.cwd(), '.gpt-image-mcp-test'); fs_1.default.mkdirSync(testDir, { recursive: true }); fs_1.default.rmdirSync(testDir); // If we get here, we have write permissions in the current directory return localPath; } catch (error) { // Fall back to user's home directory if we can't write to current directory return path_1.default.join(os_1.default.homedir(), '.gpt-image-mcp', 'mcp-images'); } } // Parse command line arguments function parseArgs() { const args = process.argv.slice(2); const result = {}; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === '--help' || arg === '-h') { result.help = true; } else if (arg === '--api-key' || arg === '-k') { if (i + 1 < args.length) { result.apiKey = args[i + 1]; i++; // Skip the next argument as it's the value } } } return result; } function showHelp() { console.log(` GPT-Image-1 MCP Server Usage: gpt-image-mcp [options] Options: --api-key, -k <key> OpenAI API key (required) --help, -h Show this help message Environment Variables: OPENAI_API_KEY OpenAI API key (alternative to --api-key) OPENAI_ORG_ID OpenAI organization ID (optional) DEFAULT_IMAGE_SIZE Default image size (default: 1024x1024) DEFAULT_IMAGE_QUALITY Default image quality (default: high) DEFAULT_MODERATION Default moderation level (default: auto) IMAGE_STORAGE_PATH Path to store images (default: ./mcp-images in current working directory) MAX_STORED_IMAGES Maximum number of stored images (default: 100) Example: gpt-image-mcp --api-key your_openai_api_key_here npx @graisol/gpt-image-mcp --api-key your_openai_api_key_here `); } class GPTImageMCPServer { server; openaiClient; imageManager; config; tools; constructor(apiKey) { this.config = this.loadConfig(apiKey); this.server = new index_js_1.Server({ name: 'gpt-image-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, }); this.openaiClient = new openai_client_js_1.OpenAIClient(this.config); this.imageManager = new image_manager_js_1.ImageManager(this.config); this.tools = (0, index_js_2.setupTools)({ openaiClient: this.openaiClient, imageManager: this.imageManager, }); this.setupHandlers(); } loadConfig(apiKey) { const config = { openai_api_key: apiKey || process.env.OPENAI_API_KEY || '', openai_org_id: process.env.OPENAI_ORG_ID, default_size: process.env.DEFAULT_IMAGE_SIZE || '1024x1024', default_quality: process.env.DEFAULT_IMAGE_QUALITY || 'high', default_moderation: process.env.DEFAULT_MODERATION || 'auto', image_storage_path: process.env.IMAGE_STORAGE_PATH || getDefaultStoragePath(), max_stored_images: parseInt(process.env.MAX_STORED_IMAGES || '100'), }; if (!config.openai_api_key) { throw new Error('OpenAI API key is required. Provide it via --api-key argument or OPENAI_API_KEY environment variable'); } return config; } setupHandlers() { this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => { return { tools: [ { name: 'generate_image', description: 'Generate images from text prompts using OpenAI GPT-Image-1', inputSchema: { type: 'object', properties: { prompt: { type: 'string', description: 'Text description of the image to generate', }, n: { type: 'number', minimum: 1, maximum: 10, description: 'Number of images to generate', default: 1, }, size: { type: 'string', enum: ['1024x1024', '1024x1536', '1536x1024', 'auto'], description: 'Size of the generated image', default: this.config.default_size, }, quality: { type: 'string', enum: ['high', 'medium', 'low'], description: 'Quality of the generated image', default: this.config.default_quality, }, background: { type: 'string', enum: ['transparent', 'opaque', 'auto'], description: 'Background type for the image', default: 'auto', }, output_compression: { type: 'number', minimum: 0, maximum: 100, description: 'Compression level for output image', }, output_format: { type: 'string', enum: ['png', 'jpeg', 'webp'], description: 'Output format for the image', default: 'png', }, moderation: { type: 'string', enum: ['auto', 'low'], description: 'Moderation level for content filtering', default: this.config.default_moderation, }, }, required: ['prompt'], }, }, { name: 'edit_image', description: 'Edit existing images with new prompts using OpenAI GPT-Image-1', inputSchema: { type: 'object', properties: { image: { type: 'string', description: 'Base64 encoded image or image URL to edit', }, prompt: { type: 'string', description: 'Text description of the desired changes', }, mask: { type: 'string', description: 'Mask PNG for inpainting edits', }, n: { type: 'number', minimum: 1, maximum: 10, description: 'Number of images to generate', default: 1, }, size: { type: 'string', enum: ['1024x1024', '1024x1536', '1536x1024', 'auto'], description: 'Size of the edited image', default: this.config.default_size, }, quality: { type: 'string', enum: ['high', 'medium', 'low'], description: 'Quality of the edited image', default: this.config.default_quality, }, background: { type: 'string', enum: ['transparent', 'opaque', 'auto'], description: 'Background type for the image', default: 'auto', }, output_compression: { type: 'number', minimum: 0, maximum: 100, description: 'Compression level for output image', }, output_format: { type: 'string', enum: ['png', 'jpeg', 'webp'], description: 'Output format for the image', default: 'png', }, moderation: { type: 'string', enum: ['auto', 'low'], description: 'Moderation level for content filtering', default: this.config.default_moderation, }, }, required: ['image', 'prompt'], }, }, { name: 'get_image_info', description: 'Get information about generated images', inputSchema: { type: 'object', properties: { image_id: { type: 'string', description: 'ID of the image to get information about', }, }, required: ['image_id'], }, }, { name: 'list_generations', description: 'List recent image generations with optional filtering', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Maximum number of generations to return', default: 10, }, offset: { type: 'number', description: 'Number of generations to skip', default: 0, }, filter: { type: 'string', description: 'Filter generations by prompt content', }, }, }, }, ], }; }); this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { await logger_js_1.logger.info(`Executing tool: ${name}`, { args }); switch (name) { case 'generate_image': return await this.handleGenerateImage(args); case 'edit_image': return await this.handleEditImage(args); case 'get_image_info': return await this.handleGetImageInfo(args); case 'list_generations': return await this.handleListGenerations(args); default: throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { await logger_js_1.logger.error(`Error executing tool ${name}`, { args }, error); if (error instanceof types_js_1.McpError) { throw error; } throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Error executing tool ${name}: ${error}`); } }); } async handleGenerateImage(args) { const result = await this.tools.generateImage(args); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } async handleEditImage(args) { const result = await this.tools.editImage(args); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } async handleGetImageInfo(args) { const info = await this.tools.getImageInfo(args); return { content: [ { type: 'text', text: JSON.stringify(info, null, 2), }, ], }; } async handleListGenerations(args) { const generations = await this.tools.listGenerations(args); return { content: [ { type: 'text', text: JSON.stringify(generations, null, 2), }, ], }; } async run() { try { await logger_js_1.logger.info('Starting GPT-Image-1 MCP Server'); // Test OpenAI connection const connectionOk = await this.openaiClient.testConnection(); if (!connectionOk) { throw new Error('Failed to connect to OpenAI API'); } const transport = new stdio_js_1.StdioServerTransport(); await this.server.connect(transport); await logger_js_1.logger.info('GPT-Image-1 MCP Server running on stdio'); console.error('GPT-Image-1 MCP Server running on stdio'); } catch (error) { await logger_js_1.logger.error('Failed to start server', {}, error); throw error; } } } // Handle process termination gracefully process.on('SIGINT', async () => { await logger_js_1.logger.info('Received SIGINT, shutting down gracefully...'); console.error('Received SIGINT, shutting down gracefully...'); process.exit(0); }); process.on('SIGTERM', async () => { await logger_js_1.logger.info('Received SIGTERM, shutting down gracefully...'); console.error('Received SIGTERM, shutting down gracefully...'); process.exit(0); }); // Start the server const args = parseArgs(); if (args.help) { showHelp(); process.exit(0); } const server = new GPTImageMCPServer(args.apiKey); server.run().catch(async (error) => { await logger_js_1.logger.error('Failed to start server', {}, error); console.error('Failed to start server:', error); process.exit(1); }); //# sourceMappingURL=index.js.map