@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
JavaScript
#!/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