UNPKG

@napolab/gpt-image-1-mcp

Version:

OpenAI gpt-image-1 MCP server with advanced image generation and editing capabilities

64 lines (52 loc) 33.7 kB
#!/usr/bin/env node import{Server as Re}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Oe}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as ke,CallToolRequestSchema as Ae}from"@modelcontextprotocol/sdk/types.js";import{z as r}from"zod";var C=r.enum(["inpaint","outpaint","variation","style_transfer","object_removal","background_change"]),ie=r.enum(["gpt-image-1"]),G=r.number().min(0).max(1),H=r.discriminatedUnion("type",[r.object({type:r.literal("url"),value:r.string().url("Valid URL is required")}),r.object({type:r.literal("base64"),value:r.string().min(1,"Base64 data is required")}),r.object({type:r.literal("local"),value:r.string().min(1,"File path is required")})]),V=r.object({source_image:H,edit_prompt:r.string().min(1,"Edit prompt is required"),edit_type:C,model:ie.optional().default("gpt-image-1"),mask_area:r.string().optional(),strength:G.optional().default(.8),preserve_composition:r.boolean().optional().default(!0),output_format:r.enum(["png","jpeg","webp"]).optional().default("png"),background:r.enum(["transparent","opaque","auto"]).optional().default("auto"),quality:r.enum(["auto","high","medium","low"]).optional().default("auto"),save_to_file:r.boolean().optional().default(!0),output_directory:r.string().optional(),filename_prefix:r.string().optional().default("edited_"),naming_strategy:r.enum(["timestamp","prompt","custom","hash"]).optional().default("timestamp"),organize_by:r.enum(["none","date","aspect_ratio","quality"]).optional().default("none")}),Te=r.object({original_image:r.object({url:r.string(),format:r.string(),dimensions:r.object({width:r.number(),height:r.number()})}),edited_image:r.object({image_url:r.string().url().optional(),local_file_path:r.string().optional(),file_size:r.number().optional(),revised_prompt:r.string(),original_prompt:r.string(),edit_type:C,strength:G,local_path:r.string().optional(),filename:r.string().optional(),directory:r.string().optional(),size_bytes:r.number().optional(),format:r.string().optional(),saved_at:r.string().optional()}),metadata:r.object({edit_time_ms:r.number(),model_used:r.string(),composition_preserved:r.boolean(),mask_applied:r.boolean()})}),W=r.object({images:r.array(H).min(1,"At least one image is required"),image_urls:r.array(r.string().url()).min(1,"At least one image URL is required").optional(),edit_prompt:r.string().min(1,"Edit prompt is required"),edit_type:r.enum(["style_transfer","background_change","color_adjustment","enhancement"]),batch_settings:r.object({parallel_processing:r.boolean().optional().default(!0),progress_callback:r.boolean().optional().default(!0),error_handling:r.enum(["fail_fast","continue_on_error","retry_failed"]).optional().default("continue_on_error"),max_concurrent:r.number().min(1).max(10).optional().default(3)}).optional(),save_to_file:r.boolean().optional().default(!0),output_directory:r.string().optional(),filename_prefix:r.string().optional().default("batch_"),naming_strategy:r.enum(["timestamp","prompt","custom","hash"]).optional().default("timestamp"),organize_by:r.enum(["none","date","aspect_ratio","quality"]).optional().default("none")}),je=r.object({total_images:r.number(),processed_images:r.number(),failed_images:r.number(),results:r.array(r.object({original_url:r.string(),success:r.boolean(),edited_image:r.object({image_url:r.string().url().optional(),local_file_path:r.string().optional(),file_size:r.number().optional(),local_path:r.string().optional(),filename:r.string().optional(),directory:r.string().optional(),size_bytes:r.number().optional(),format:r.string().optional(),saved_at:r.string().optional()}).optional(),error:r.string().optional()})),metadata:r.object({total_time_ms:r.number(),average_time_per_image_ms:r.number(),model_used:r.string(),parallel_processing:r.boolean()})}),Be=r.object({image_url:r.string().url("Valid image URL is required"),mask_type:r.enum(["object_detection","semantic_segmentation","manual","auto"]).optional().default("auto"),target_object:r.string().optional(),precision:r.enum(["low","medium","high"]).optional().default("medium")}),Me=r.object({original_image:r.string(),mask_image:r.string(),mask_type:r.string(),confidence:r.number().min(0).max(1),detected_objects:r.array(r.string()).optional(),metadata:r.object({generation_time_ms:r.number(),model_used:r.string(),precision_level:r.string()})}),x=class extends Error{constructor(a,t,s){super(a);this.code=t;this.details=s;this.name="ImageEditError"}},$=class extends x{constructor(e,a){super(e,"MASK_ERROR",a),this.name="MaskError"}},R=class extends x{constructor(a,t,s,o){super(a,"BATCH_PROCESSING_ERROR",o);this.failedImages=t;this.successfulImages=s;this.name="BatchProcessingError"}};import{z as c}from"zod";var ne=c.enum(["square","landscape","portrait","1:1","16:9","9:16"]),oe=c.enum(["high","medium","low"]),se=c.enum(["vivid","natural"]),le=c.enum(["1024x1024","1536x1024","1024x1536"]),me=c.enum(["transparent","opaque","auto"]),pe=c.enum(["png","jpeg","webp"]),ce=c.enum(["auto","low"]),K=c.object({prompt:c.string().min(1,"Prompt is required"),aspect_ratio:ne.optional().default("square"),quality:oe.optional(),style:se.optional(),background:me.optional().default("auto"),output_format:pe.optional().default("png"),moderation:ce.optional().default("auto"),analyze_after_generation:c.boolean().optional().default(!1),remove_background:c.boolean().optional().default(!1),include_base64:c.boolean().optional().default(!1),save_to_file:c.boolean().optional().default(!0),output_directory:c.string().optional(),filename:c.string().optional(),naming_strategy:c.enum(["timestamp","prompt","custom","hash"]).optional().default("timestamp"),organize_by:c.enum(["none","date","aspect_ratio","quality"]).optional().default("none")}),Ue=c.object({image:c.object({url:c.string().url(),revised_prompt:c.string(),original_prompt:c.string(),was_translated:c.boolean(),aspect_ratio:c.string(),size:le,local_path:c.string().optional(),filename:c.string().optional(),directory:c.string().optional(),size_bytes:c.number().optional(),format:c.string().optional(),saved_at:c.string().optional()}),analysis:c.object({description:c.string(),detected_elements:c.array(c.string()),colors:c.array(c.string()),style_analysis:c.string()}).optional(),metadata:c.object({generation_time_ms:c.number(),model_used:c.string(),background_removed:c.boolean()})}),Q=i=>{switch(i){case"square":case"1:1":return"1024x1024";case"landscape":case"16:9":return"1536x1024";case"portrait":case"9:16":return"1024x1536";default:return"1024x1024"}},X=i=>{switch(i){case"standard":return"medium";case"hd":return"high";case"high":case"medium":case"low":return i;default:return"medium"}};import{promises as Y}from"fs";import{resolve as ue,normalize as ge}from"path";var E=10*1024*1024,Z=[".png",".jpg",".jpeg",".webp",".gif"];async function J(i){switch(i.type){case"url":return await fe(i.value);case"base64":return _e(i.value);case"local":return await de(i.value);default:throw new Error(`Unsupported image input type: ${i.type}`)}}async function de(i){let e=ge(i),a=ue(e);if(e.includes(".."))throw new Error("Invalid file path: directory traversal not allowed");let t=ye(a);if(!Z.includes(t))throw new Error(`Unsupported file extension: ${t}. Allowed: ${Z.join(", ")}`);try{let s=await Y.stat(a);if(!s.isFile())throw new Error(`Path is not a file: ${a}`);if(s.size>E)throw new Error(`File too large: ${s.size} bytes (max: ${E} bytes)`);let o=await Y.readFile(a);return o.buffer.slice(o.byteOffset,o.byteOffset+o.byteLength)}catch(s){throw s instanceof Error?new Error(`Failed to load local file: ${s.message}`):new Error(`Failed to load local file: ${String(s)}`)}}async function fe(i){try{let e=await fetch(i);if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);let a=e.headers.get("content-type");if(a==null||a.length===0||!a.startsWith("image/"))throw new Error(`Invalid content type: ${a??"null"}. Expected image/*`);let t=await e.arrayBuffer();if(t.byteLength>E)throw new Error(`Image too large: ${t.byteLength} bytes (max: ${E} bytes)`);return t}catch(e){throw e instanceof Error?new Error(`Failed to download image from URL: ${e.message}`):new Error(`Failed to download image from URL: ${String(e)}`)}}function _e(i){try{let e=i;if(i.startsWith("data:")){let t=i.indexOf(",");if(t===-1)throw new Error("Invalid base64 data URL format");e=i.substring(t+1)}if(!/^[\d+/A-Za-z]*={0,2}$/.test(e))throw new Error("Invalid base64 format");let a=Buffer.from(e,"base64");if(a.length>E)throw new Error(`Base64 data too large: ${a.length} bytes (max: ${E} bytes)`);return a.buffer.slice(a.byteOffset,a.byteOffset+a.byteLength)}catch(e){throw e instanceof Error?new Error(`Failed to convert base64 to buffer: ${e.message}`):new Error(`Failed to convert base64 to buffer: ${String(e)}`)}}function ye(i){let e=i.lastIndexOf(".");return e===-1?"":i.substring(e).toLowerCase()}function O(i){return typeof i=="object"?i:i.startsWith("data:")?{type:"base64",value:i}:i.startsWith("http://")||i.startsWith("https://")?{type:"url",value:i}:{type:"local",value:i}}import{OpenAI as $e}from"openai";import{createHash as we}from"crypto";import{createWriteStream as Ie,existsSync as q,statSync as xe,unlinkSync as Ee,readdirSync as ve}from"fs";import{join as S,dirname as T,extname as j,basename as B}from"path";import{pipeline as Se}from"stream/promises";import{ensureDir as ze}from"fs-extra";import{z as _}from"zod";var he=_.enum(["timestamp","prompt","custom","hash"]),be=_.enum(["none","date","aspect_ratio","quality"]),Ke=_.object({save_to_file:_.boolean().default(!0),output_directory:_.string().optional(),filename:_.string().optional(),naming_strategy:he.default("timestamp"),organize_by:be.default("none")}),Qe=_.object({url:_.string().url(),local_path:_.string(),filename:_.string(),directory:_.string(),size_bytes:_.number(),format:_.string(),saved_at:_.string().datetime()}),f=class extends Error{constructor(a,t,s,o){super(a);this.code=t;this.path=s;this.details=o;this.name="FileError"}},h=class extends f{constructor(a,t,s,o){super(a,"DOWNLOAD_ERROR",void 0,o);this.url=t;this.statusCode=s;this.name="DownloadError"}},v=class extends f{constructor(a,t,s,o){super(a,"DISK_SPACE_ERROR",o,{requiredBytes:t,availableBytes:s});this.requiredBytes=t;this.availableBytes=s;this.name="DiskSpaceError"}};var k=class{options;customFileCounter=new Map;constructor(e={}){this.options={defaultOutputDir:e.defaultOutputDir??"./generated_images",maxFileSizeBytes:e.maxFileSizeBytes??50*1024*1024,enableFileOutput:e.enableFileOutput??!0,autoCleanupDays:e.autoCleanupDays??30}}async downloadImage(e){let{url:a,outputPath:t,timeout:s=12e4,retryCount:o=3,retryDelay:n=1e3}=e,l=Date.now();await this.ensureDirectory(T(t));let g=null;for(let u=0;u<=o;u++)try{let m=new AbortController,d=setTimeout(()=>m.abort(),s),p=await fetch(a,{signal:m.signal,headers:{"User-Agent":"Image-Gen-MCP/1.0"}});if(clearTimeout(d),!p.ok)throw new h(`HTTP ${p.status}: ${p.statusText}`,a,p.status);let b=parseInt(p.headers.get("content-length")??"0",10),w=p.headers.get("content-type")??"";if(b>this.options.maxFileSizeBytes)throw new v(`File size (${b} bytes) exceeds maximum allowed size (${this.options.maxFileSizeBytes} bytes)`,b,this.options.maxFileSizeBytes,t);if(!w.startsWith("image/"))throw new h(`Invalid content type: ${w}. Expected image content.`,a,p.status);if(!p.body)throw new h("No response body",a,p.status);let y=Ie(t),I=0,L=this;try{await Se(p.body,async function*(ae){for await(let U of ae){if(I+=U.length,I>L.options.maxFileSizeBytes)throw new v("Download size exceeded limit during transfer",I,L.options.maxFileSizeBytes,t);yield U}},y);let z=Date.now()-l,re=this.getImageFormat(w,t);return{path:t,size:I,format:re,downloadTime:z}}catch(z){throw new f(`Failed to write file: ${z instanceof Error?z.message:"Unknown error"}`,"WRITE_ERROR",t,{originalError:z})}}catch(m){if(g=this.handleDownloadError(m,a,u,o),u<o){await this.delay(n*(u+1));continue}throw g}throw g||new h("Download failed after all retries",a)}generateFileName(e){let{strategy:a,prompt:t,customPrefix:s,maxLength:o=100}=e,n=new Date,l=n.toISOString().slice(0,10).replace(/-/g,"")+"_"+n.toISOString().slice(11,19).replace(/:/g,""),g=".png";switch(a){case"timestamp":{let u=this.generateHash(t??"").slice(0,6);return`image_${l}_${u}${g}`}case"prompt":{if(t==null)throw new f("Prompt is required for prompt-based naming","INVALID_OPTION");return`${this.sanitizeFilename(t).slice(0,o-l.length-g.length-1)}_${l}${g}`}case"custom":{let u=s??"generated",m=this.getNextCounter(u);return`${u}_${m.toString().padStart(3,"0")}${g}`}case"hash":return`${this.generateHash(t??l).slice(0,8)}${g}`;default:throw new f(`Unknown naming strategy: ${a}`,"INVALID_OPTION")}}async ensureDirectory(e){if(!e||e.trim()==="")throw new f("Directory path cannot be empty","INVALID_PATH");try{await ze(e)}catch(a){throw new f(`Failed to create directory: ${a instanceof Error?a.message:"Unknown error"}`,"DIRECTORY_ERROR",e,{originalError:a})}}getOrganizedDirectory(e){let{baseDir:a,organizeBy:t,aspectRatio:s,quality:o,date:n}=e;switch(t){case"none":return a;case"date":{let g=(n||new Date).toISOString().slice(0,10);return S(a,g)}case"aspect_ratio":{if(s==null)throw new f("Aspect ratio is required for aspect_ratio organization","INVALID_OPTION");return S(a,s)}case"quality":{if(o==null)throw new f("Quality is required for quality organization","INVALID_OPTION");return S(a,o)}default:throw new f(`Unknown organization strategy: ${t}`,"INVALID_OPTION")}}async saveImage(e,a,t){if(!a.save_to_file)return null;let o={baseDir:a.output_directory??this.options.defaultOutputDir,organizeBy:a.organize_by,date:new Date};t.aspectRatio!=null&&t.aspectRatio.length>0&&(o.aspectRatio=t.aspectRatio),t.quality!=null&&t.quality.length>0&&(o.quality=t.quality);let n=this.getOrganizedDirectory(o);await this.ensureDirectory(n);let l=a.filename!=null?this.resolveCustomFilename(a.filename,n):this.generateFileName({strategy:a.naming_strategy,...t.prompt!=null&&t.prompt.length>0&&{prompt:t.prompt},...t.aspectRatio!=null&&t.aspectRatio.length>0&&{aspectRatio:t.aspectRatio},...t.quality!=null&&t.quality.length>0&&{quality:t.quality}}),g=S(n,l),u=await this.resolveFileConflict(g,{strategy:"auto_rename"}),m=await this.downloadImage({url:e,outputPath:u});return{url:e,local_path:u,filename:B(u),directory:T(u),size_bytes:m.size,format:m.format,saved_at:new Date().toISOString()}}async cleanupOldFiles(e){if(!q(e))return;let a=new Date(Date.now()-this.options.autoCleanupDays*24*60*60*1e3);try{let t=ve(e);for(let s of t){let o=S(e,s),n=xe(o);n.isFile()&&n.mtime<a&&Ee(o)}}catch(t){console.warn(`Failed to cleanup old files in ${e}:`,t)}}async resolveFileConflict(e,a){if(!q(e))return e;switch(a.strategy){case"overwrite":return e;case"skip":throw new f("File already exists","FILE_EXISTS",e);case"auto_rename":default:{let t=j(e),s=B(e,t),o=T(e),n=1,l;do l=S(o,`${s}_${n.toString().padStart(3,"0")}_${Date.now()}${t}`),n++;while(q(l));return l}}}resolveCustomFilename(e,a){let t=this.sanitizeFilename(e),s=j(t)||".png";return`${B(t,s)}${s}`}sanitizeFilename(e){return e.replace(/[^\s\w-]/g,"").replace(/\s+/g,"_").toLowerCase().slice(0,100)}generateHash(e){return we("sha256").update(e).digest("hex")}getNextCounter(e){let t=(this.customFileCounter.get(e)??0)+1;return this.customFileCounter.set(e,t),t}getImageFormat(e,a){return e.includes("png")?"png":e.includes("jpeg")||e.includes("jpg")?"jpg":e.includes("gif")?"gif":e.includes("webp")?"webp":j(a).slice(1).toLowerCase()||"png"}handleDownloadError(e,a,t,s){return e instanceof v||e instanceof f?e:e instanceof Error?e.name==="AbortError"?new h("Download timeout",a,void 0,{attempt:t,maxAttempts:s}):new h(`Network error: ${e.message}`,a,void 0,{attempt:t,maxAttempts:s}):new h("Unknown download error",a,void 0,{attempt:t,maxAttempts:s})}delay(e){return new Promise(a=>setTimeout(a,e))}};var A=class{client;fileManager;MAX_RESPONSE_TOKENS=2e4;WARNING_THRESHOLD=15e3;constructor(){let e=process.env.OPENAI_API_KEY;if(!e)throw new Error("OPENAI_API_KEY environment variable is required");this.client=new $e({apiKey:e,maxRetries:parseInt(process.env.OPENAI_MAX_RETRIES??"3"),timeout:parseInt(process.env.OPENAI_API_TIMEOUT??"120000")}),this.fileManager=new k({defaultOutputDir:process.env.DEFAULT_OUTPUT_DIR??"./generated_images",maxFileSizeBytes:parseInt(process.env.MAX_FILE_SIZE_MB??"50")*1024*1024,enableFileOutput:process.env.ENABLE_FILE_OUTPUT!=="false",autoCleanupDays:parseInt(process.env.KEEP_FILES_DAYS??"30")})}estimateResponseTokens(e,a){if(!a)return 200;let s=Math.ceil(e*4/3),o=Math.ceil(s/4);return Math.ceil((200+o)*1.2)}async generateImage(e){try{let a=Q(e.aspect_ratio),t={model:"gpt-image-1",prompt:e.prompt,size:a,...e.quality&&{quality:e.quality},n:1};e.output_format&&(t.output_format=e.output_format),e.moderation&&(t.moderation=e.moderation);let s=await this.client.images.generate(t);if(s.data==null||s.data.length===0)throw new Error("No image data returned from OpenAI");let o=s.data[0];if(o==null||o.url==null&&o.b64_json==null)throw new Error("No image data returned from OpenAI");let n=o.url,l=o.b64_json,g={metadata:{width:0,height:0,format:"",size_bytes:0,created_at:""}},u=null;try{let d=n??`data:image/png;base64,${l}`;u=await this.fileManager.saveImage(d,{save_to_file:e.save_to_file,output_directory:e.output_directory,filename:e.filename,naming_strategy:e.naming_strategy,organize_by:e.organize_by},{prompt:e.prompt,aspectRatio:e.aspect_ratio,quality:e.quality??"medium"})}catch(d){console.warn("Failed to save image to file:",d)}e.save_to_file&&u&&(g.file_path=u.local_path),g.metadata={width:parseInt(a.split("x")[0]??"1024"),height:parseInt(a.split("x")[1]??"1024"),format:u?.format??e.output_format??"png",size_bytes:u?.size_bytes??0,created_at:u?.saved_at??new Date().toISOString()};let m=[];if(e.include_base64){let d=u?.size_bytes??0,p=this.estimateResponseTokens(d,!0);p>this.MAX_RESPONSE_TOKENS?m.push(`Image too large for base64 response (${p} tokens > ${this.MAX_RESPONSE_TOKENS} limit). Use file_path instead.`):(p>this.WARNING_THRESHOLD&&m.push(`Large response size warning: ${p} tokens. Consider using file_path for better performance.`),l!=null&&l.length>0?g.base64_image=l:n!=null&&n.length>0&&m.push("Base64 requested but only URL available. Use file_path instead."))}return m.length>0&&(g.warnings=m),g}catch(a){throw this.handleOpenAIError(a)}}async editImage(e){let a=Date.now();try{let t=O(e.source_image),s=await J(t),n={image:new File([new Uint8Array(s)],"source.png",{type:"image/png"}),prompt:e.edit_prompt,model:e.model,size:"1024x1024",n:1},l={};e.quality&&(l.quality=e.quality),e.background&&(l.background=e.background);let g=await this.client.images.edit({...n,...l}),u=Date.now()-a;if(!g.data?.[0])throw new Error("No image returned from OpenAI");let m=g.data[0],d,p;if(m.b64_json!=null&&m.b64_json.length>0){let w=m.b64_json,y=`data:image/${e.output_format??"png"};base64,${w}`;try{p=await this.fileManager.saveImage(y,{save_to_file:!0,output_directory:e.output_directory,filename:e.filename_prefix!==""&&e.filename_prefix.length>0?`${e.filename_prefix}${Date.now()}`:void 0,naming_strategy:e.naming_strategy,organize_by:e.organize_by},{prompt:e.edit_prompt,aspectRatio:"square",quality:"medium"})}catch(I){console.warn("Failed to save base64 image to file:",I)}}else if(m.url!==void 0&&m.url.length>0){if(d=m.url,e.save_to_file)try{p=await this.fileManager.saveImage(m.url,{save_to_file:e.save_to_file,output_directory:e.output_directory,filename:e.filename_prefix!==""&&e.filename_prefix.length>0?`${e.filename_prefix}${Date.now()}`:void 0,naming_strategy:e.naming_strategy,organize_by:e.organize_by},{prompt:e.edit_prompt,aspectRatio:"square",quality:"medium"})}catch(w){console.warn("Failed to save edited image to file:",w)}}else throw new Error("No image data in response");let b={image_url:d,local_file_path:p?.local_path,file_size:p?.size_bytes,revised_prompt:m.revised_prompt??e.edit_prompt,original_prompt:e.edit_prompt,edit_type:e.edit_type,strength:e.strength,model_used:e.model,local_path:p?.local_path,filename:p?.filename,directory:p?.directory,size_bytes:p?.size_bytes,format:p?.format,saved_at:p?.saved_at};return{original_image:{url:t.type==="url"?t.value:`${t.type}:${t.value.substring(0,50)}...`,format:"unknown",dimensions:{width:1024,height:1024}},edited_image:b,metadata:{edit_time_ms:u,model_used:e.model,composition_preserved:e.preserve_composition,mask_applied:e.mask_area!==void 0}}}catch(t){throw this.handleImageEditError(t)}}async batchEdit(e){let a=Date.now(),t=[],s=e.batch_settings||{parallel_processing:!0,max_concurrent:3},o=0,n=0,l=e.images.length>0?e.images:e.image_urls?e.image_urls.map(g=>({type:"url",value:g})):[];try{let g=async(m,d)=>{try{let p={style_transfer:"style_transfer",background_change:"background_change",color_adjustment:"variation",enhancement:"variation"},b=await this.editImage({source_image:m,edit_prompt:e.edit_prompt,edit_type:p[e.edit_type],model:"gpt-image-1",background:"auto",quality:"auto",save_to_file:e.save_to_file,output_directory:e.output_directory,filename_prefix:e.filename_prefix?`${e.filename_prefix}${d+1}_`:"edited_",naming_strategy:e.naming_strategy,organize_by:e.organize_by,strength:.8,preserve_composition:!0,output_format:"png"});return o++,{original_url:m.type==="url"?m.value:`${m.type}:${m.value.substring(0,50)}...`,success:!0,edited_image:b.edited_image}}catch(p){return n++,{original_url:m.type==="url"?m.value:`${m.type}:${m.value.substring(0,50)}...`,success:!1,error:p instanceof Error?p.message:"Unknown error"}}};if(s.parallel_processing){let m=s.max_concurrent||3,d=[];for(let p=0;p<l.length;p+=m)d.push(l.slice(p,p+m));for(let p of d){let b=p.map((y,I)=>g(y,I)),w=await Promise.allSettled(b);for(let y of w)y.status==="fulfilled"?t.push(y.value):(n++,t.push({original_url:"unknown",success:!1,error:y.reason instanceof Error?y.reason.message:"Unknown error"}))}}else for(let m=0;m<l.length;m++){let d=l[m];if(d!=null){let p=await g(d,m);t.push(p)}}let u=Date.now()-a;return{total_images:l.length,processed_images:o,failed_images:n,results:t,metadata:{total_time_ms:u,average_time_per_image_ms:u/l.length,model_used:"gpt-image-1",parallel_processing:s.parallel_processing||!1}}}catch(g){let u=l.filter((d,p)=>!(t[p]?.success??!1)).map(d=>d.type==="url"?d.value:`${d.type}:${d.value.substring(0,50)}...`),m=l.filter((d,p)=>t[p]?.success??!1).map(d=>d.type==="url"?d.value:`${d.type}:${d.value.substring(0,50)}...`);throw new R("Batch processing failed",u,m,{originalError:g})}}async generateMask(e){try{return await this.client.chat.completions.create({model:"gpt-4o",messages:[{role:"user",content:[{type:"text",text:`Generate a mask for the following image${e.target_object!==void 0?` focusing on: ${e.target_object}`:""}. Describe the mask areas in detail.`},{type:"image_url",image_url:{url:e.image_url}}]}],max_tokens:500}),{original_image:e.image_url,mask_image:e.image_url,mask_type:e.mask_type,confidence:.8,detected_objects:e.target_object!==void 0?[e.target_object]:[],metadata:{generation_time_ms:1e3,model_used:"gpt-4o",precision_level:e.precision??"medium"}}}catch(a){throw new $("Mask generation failed",{originalError:a})}}handleImageEditError(e){return e instanceof x||e instanceof $||e instanceof R?e:this.handleOpenAIError(e)}handleOpenAIError(e){if(e!=null&&typeof e=="object"&&"error"in e){let a=e;if(a.error===null)return new Error("OpenAI API error: Unknown error");let t=a.error;if(t.type==="insufficient_quota")return new Error("OpenAI API quota exceeded. Please check your billing.");if(t.type==="rate_limit_exceeded")return new Error("OpenAI API rate limit exceeded. Please try again later.");if(t.type==="invalid_request_error")return new Error(`Invalid request: ${t.message||"Unknown error"}`);if(t.type==="authentication_error")return new Error("OpenAI API authentication failed. Please check your API key.")}return e instanceof Error?new Error(`OpenAI API error: ${e.message||"Unknown error"}`):new Error("OpenAI API error: Unknown error")}};var D=class extends Error{constructor(a,t,s){super(a);this.code=t;this.suggestion=s;this.name="ValidationError"}};function F(i,e="text"){if(!i||typeof i!="string"||i.trim().length===0)throw new D(`${e} cannot be empty.`,"EMPTY_TEXT","Please provide a descriptive prompt.")}function P(i){return i instanceof D?{content:[{type:"text",text:`\u274C Validation Error: ${i.message} \u{1F4A1} Suggestion: ${i.suggestion??"Please check your input and try again."} \u{1F4DD} Example of correct usage: - "A beautiful sunset over a mountain landscape" - "Modern minimalist logo design with blue accents" - "Portrait of a person reading a book by a window" Please check your input and try again.`}],isError:!0}:{content:[{type:"text",text:`Error: ${i.message}`}],isError:!0}}function De(i){return Array.isArray(i)&&i.every(e=>typeof e=="string")}function ee(i){return typeof i=="object"&&i!==null&&"image_urls"in i&&De(i.image_urls)}function te(i){return typeof i=="object"&&i!==null&&"images"in i&&Array.isArray(i.images)}function Fe(i){return typeof i=="object"&&i!==null&&"source_image"in i&&typeof i.source_image=="string"}var M=new A,N=new Re({name:"gpt-image-1-mcp",version:"1.0.1"},{capabilities:{tools:{}}});N.setRequestHandler(ke,async()=>({tools:[{name:"generate-image",description:"Generate images using gpt-image-1 with advanced text rendering and instruction following",inputSchema:{type:"object",properties:{prompt:{type:"string",description:"Image description"},aspect_ratio:{type:"string",enum:["square","landscape","portrait","1:1","16:9","9:16"],default:"square",description:"Aspect ratio (square=1024x1024, landscape=1792x1024, portrait=1024x1792)"},quality:{type:"string",enum:["standard","hd","high","medium","low"],description:"Image quality"},output_format:{type:"string",enum:["png","jpeg","webp"],default:"png",description:"Output image format"},moderation:{type:"string",enum:["auto","low"],default:"auto",description:"Content moderation level"},analyze_after_generation:{type:"boolean",default:!1,description:"Analyze the generated image and return description"},remove_background:{type:"boolean",default:!1,description:"Attempt to remove background (experimental, requires post-processing)"},include_base64:{type:"boolean",default:!1,description:"Include base64 in response if size permits"},save_to_file:{type:"boolean",default:!0,description:"Save the generated image to local file"},output_directory:{type:"string",description:"Directory to save the image (defaults to ./generated_images)"},filename:{type:"string",description:"Custom filename (without extension)"},naming_strategy:{type:"string",enum:["timestamp","prompt","custom","hash"],default:"timestamp",description:"Strategy for generating filenames"},organize_by:{type:"string",enum:["none","date","aspect_ratio","quality"],default:"none",description:"Subdirectory organization strategy"}},required:["prompt"]}},{name:"edit-image",description:"Edit existing images with gpt-image-1 powered modifications including inpainting, outpainting, and style transfer",inputSchema:{type:"object",properties:{source_image:{type:"object",properties:{type:{type:"string",enum:["url","base64","local"],description:"Type of image input"},value:{type:"string",description:"Image URL, base64 data, or local file path"}},required:["type","value"],description:"Image input as discriminated union (URL, base64, or local file)"},edit_prompt:{type:"string",description:"Description of desired changes"},edit_type:{type:"string",enum:["inpaint","outpaint","variation","style_transfer","object_removal","background_change"],description:"Type of edit to perform"},mask_area:{type:"string",description:"Mask specification for targeted editing (optional)"},strength:{type:"number",minimum:0,maximum:1,default:.8,description:"Edit strength (0.0 = minimal, 1.0 = maximum)"},preserve_composition:{type:"boolean",default:!0,description:"Maintain original image composition"},output_format:{type:"string",enum:["png","jpeg","webp"],default:"png",description:"Output image format"},save_to_file:{type:"boolean",default:!0,description:"Save the edited image to local file"},output_directory:{type:"string",description:"Directory to save edited image"},filename_prefix:{type:"string",default:"edited_",description:"Prefix for edited image filename"},naming_strategy:{type:"string",enum:["timestamp","prompt","custom","hash"],default:"timestamp",description:"Strategy for generating filenames"},organize_by:{type:"string",enum:["none","date","aspect_ratio","quality"],default:"none",description:"Subdirectory organization strategy"}},required:["source_image","edit_prompt","edit_type"]}},{name:"batch-edit",description:"Apply same edit to multiple images",inputSchema:{type:"object",properties:{images:{type:"array",items:{type:"object",properties:{type:{type:"string",enum:["url","base64","local"],description:"Type of image input"},value:{type:"string",description:"Image URL, base64 data, or local file path"}},required:["type","value"]},description:"Array of image inputs as discriminated union (URL, base64, or local file)"},image_urls:{type:"array",items:{type:"string"},description:"[DEPRECATED] Array of image URLs to edit - use 'images' instead"},edit_prompt:{type:"string",description:"Edit description to apply to all images"},edit_type:{type:"string",enum:["style_transfer","background_change","color_adjustment","enhancement"],description:"Type of edit to apply"},batch_settings:{type:"object",properties:{parallel_processing:{type:"boolean",default:!0,description:"Process images in parallel"},progress_callback:{type:"boolean",default:!0,description:"Enable progress tracking"},error_handling:{type:"string",enum:["fail_fast","continue_on_error","retry_failed"],default:"continue_on_error",description:"How to handle errors during batch processing"},max_concurrent:{type:"number",minimum:1,maximum:10,default:3,description:"Maximum concurrent operations"}}},save_to_file:{type:"boolean",default:!0,description:"Save the edited images to local files"},output_directory:{type:"string",description:"Directory to save edited images"},filename_prefix:{type:"string",default:"batch_",description:"Prefix for edited image filenames"},naming_strategy:{type:"string",enum:["timestamp","prompt","custom","hash"],default:"timestamp",description:"Strategy for generating filenames"},organize_by:{type:"string",enum:["none","date","aspect_ratio","quality"],default:"none",description:"Subdirectory organization strategy"}},required:["edit_prompt","edit_type"]}}]}));N.setRequestHandler(Ae,async i=>{let{name:e,arguments:a}=i.params;switch(e){case"generate-image":try{let t=a||{},s={...t,...t.quality!=null&&typeof t.quality=="string"?{quality:X(t.quality)}:{}},o=K.parse(s);F(o.prompt,"prompt");let n=await M.generateImage(o);return{content:[{type:"text",text:`Generated image successfully! Prompt: "${o.prompt}" Size: ${n.metadata.width}x${n.metadata.height} Aspect ratio: ${o.aspect_ratio} Quality: ${o.quality||"medium"} Model: gpt-image-1 ${n.file_path!=null&&n.file_path.length>0?`Local file: ${n.file_path} File size: ${n.metadata.size_bytes} bytes Format: ${n.metadata.format} Created at: ${n.metadata.created_at} `:""}${n.base64_image!=null&&n.base64_image.length>0?`Base64 data included in response (${Math.ceil(n.base64_image.length*.75)} bytes)`:""}${n.warnings&&n.warnings.length>0?` Warnings: ${n.warnings.map(l=>`- ${l}`).join(` `)}`:""}`}]}}catch(t){return P(t)}case"edit-image":try{let t=a||{},s={...t,source_image:Fe(t)?O(t.source_image):t.source_image},o=V.parse(s);F(o.edit_prompt,"edit_prompt");let n=await M.editImage(o);return{content:[{type:"text",text:`Image editing completed successfully! Original Image: ${n.original_image.url} Edit Type: ${n.edited_image.edit_type} Edit Strength: ${n.edited_image.strength} Edit prompt: "${n.edited_image.original_prompt}" Edited Image URL: ${n.edited_image.image_url??n.edited_image.local_file_path??"N/A"} ${n.edited_image.local_path!=null?`Local file: ${n.edited_image.local_path} File size: ${n.edited_image.size_bytes} bytes Format: ${n.edited_image.format} Saved at: ${n.edited_image.saved_at}`:""} Edit time: ${n.metadata.edit_time_ms}ms Model used: ${n.metadata.model_used} Composition preserved: ${n.metadata.composition_preserved} Mask applied: ${n.metadata.mask_applied}`}]}}catch(t){return P(t)}case"batch-edit":try{let t=a||{};if(!te(t)&&!ee(t))throw new Error("Either 'images' or 'image_urls' parameter must be provided");let s={...t,...ee(t)&&!te(t)?{images:t.image_urls.map(l=>O(l))}:{}},o=W.parse(s);F(o.edit_prompt,"edit_prompt");let n=await M.batchEdit(o);return{content:[{type:"text",text:`Batch editing completed! Total Images: ${n.total_images} Successfully Processed: ${n.processed_images} Failed: ${n.failed_images} Edit Type: ${o.edit_type} Edit Prompt: "${o.edit_prompt}" Results Summary: ${n.results.map((l,g)=>` Image ${g+1}: ${l.success?"\u2713 Success":"\u2717 Failed"} - Original: ${l.original_url} ${l.success&&l.edited_image?`- Edited: ${l.edited_image.image_url??l.edited_image.local_file_path??"N/A"} ${l.edited_image.local_path!=null?`- Local file: ${l.edited_image.local_path}`:""}`:""} ${!l.success&&l.error!=null?`- Error: ${l.error}`:""} `).join("")} Processing time: ${n.metadata.total_time_ms}ms Average time per image: ${n.metadata.average_time_per_image_ms}ms Model used: ${n.metadata.model_used} Parallel processing: ${n.metadata.parallel_processing}`}]}}catch(t){return P(t)}default:return{content:[{type:"text",text:`Unknown tool: ${e}`}],isError:!0}}});async function Pe(){let i=new Oe;await N.connect(i),console.error("MCP Image Generation Server started")}Pe().catch(console.error);export{N as server}; //# sourceMappingURL=index.js.map