generate_image
Generate images using AI with support for MeiGen, local ComfyUI, or OpenAI-compatible APIs. Accepts prompts and optional reference images for style guidance.
Instructions
Generate an image using AI. Supports MeiGen platform, local ComfyUI, or OpenAI-compatible APIs. Tip: get prompts from get_inspiration() or enhance_prompt(), and use gallery image URLs as referenceImages for style guidance. For Midjourney V8.1, an optional style reference can be passed by appending --sref <code> at the end of the prompt — only when the user provides a Midjourney style code (numeric or text). Do NOT pass URLs or local paths via --sref; for any image-based reference, use the referenceImages parameter instead.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| prompt | Yes | The image generation prompt | |
| model | No | Model name. For OpenAI-compatible providers: any model ID your endpoint supports. For MeiGen: use model IDs from list_models. | |
| size | No | Image size for OpenAI-compatible providers: "1024x1024", "1536x1024", "auto". MeiGen/ComfyUI: use aspectRatio instead. | |
| aspectRatio | No | Aspect ratio for MeiGen provider. Use "auto" (recommended, default when omitted) to let MeiGen infer the best ratio from the prompt content. Explicit values: "1:1", "3:4", "4:3", "16:9", "9:16", "21:9", "2:3", "3:2", "4:5", "5:4", etc. (model-dependent). ComfyUI: use comfyui_workflow modify to adjust dimensions before generating. | |
| resolution | No | Resolution tier. MeiGen: "1K" / "2K" / "3K" / "4K" — each model supports a subset (list_models reports resolutions when applicable). OpenAI: not used (use size instead). | |
| quality | No | Image quality. MeiGen gpt-image-2: "low" / "medium" / "high". OpenAI-compatible providers also accept "high". | |
| referenceImages | No | Image references for style/content guidance. Accepts both public URLs (http/https) and local file paths. Local files are automatically compressed and uploaded when needed. For ComfyUI: local files are passed directly to the workflow (requires LoadImage node). Sources: gallery URLs from search_gallery/get_inspiration, URLs from previous generate_image results, or local file paths. | |
| provider | No | Which provider to use. Auto-detected from configuration if not specified. | |
| workflow | No | ComfyUI workflow name to use (from comfyui_workflow list). Uses default workflow if not specified. | |
| negativePrompt | No | Negative prompt for OpenAI-compatible providers. ComfyUI: use comfyui_workflow modify to set negative prompt in the workflow before generating. |
Implementation Reference
- src/tools/generate-image.ts:121-207 (handler)Main handler: registerGenerateImage that registers the 'generate_image' tool on the MCP server. Dispatches to three providers (OpenAI, MeiGen, ComfyUI) based on config, handles reference image resolution, semaphore-based concurrency control, and error classification.
export function registerGenerateImage(server: McpServer, apiClient: MeiGenApiClient, config: MeiGenConfig) { server.tool( 'generate_image', 'Generate an image using AI. Supports MeiGen platform, local ComfyUI, or OpenAI-compatible APIs. Tip: get prompts from get_inspiration() or enhance_prompt(), and use gallery image URLs as referenceImages for style guidance. For Midjourney V8.1, an optional style reference can be passed by appending `--sref <code>` at the end of the prompt — only when the user provides a Midjourney style code (numeric or text). Do NOT pass URLs or local paths via --sref; for any image-based reference, use the referenceImages parameter instead.', generateImageSchema, { readOnlyHint: false, destructiveHint: true }, async ({ prompt, model, size, aspectRatio, resolution, quality, referenceImages, provider: requestedProvider, workflow, negativePrompt }, extra) => { const availableProviders = getAvailableProviders(config) if (availableProviders.length === 0) { return { content: [{ type: 'text' as const, text: 'No image generation providers configured. Get a MeiGen API token at https://www.meigen.ai (sign in → Settings → API Keys), then set MEIGEN_API_TOKEN in your environment or MCP config and restart the host. Claude Code users can run /meigen:setup for guided configuration. Alternative providers: OPENAI_API_KEY (any OpenAI-compatible API) or ComfyUI workflow import.', }], isError: true, } } // Determine which provider to use let providerType: ProviderType if (requestedProvider) { if (!availableProviders.includes(requestedProvider)) { return { content: [{ type: 'text' as const, text: `Provider "${requestedProvider}" is not configured. Available: ${availableProviders.join(', ')}`, }], isError: true, } } providerType = requestedProvider } else { providerType = getDefaultProvider(config)! } try { // Auto-upload local reference images for API providers (ComfyUI handles local files natively) const resolvedRefs = providerType !== 'comfyui' ? await resolveReferenceImages(referenceImages, config, (msg) => notify(extra, msg)) : referenceImages switch (providerType) { case 'openai': { await sharedApiSemaphore.acquire() try { return await generateWithOpenAI(config, prompt, model, size, quality, resolvedRefs) } finally { sharedApiSemaphore.release() } } case 'meigen': { await sharedApiSemaphore.acquire() try { return await generateWithMeiGen(apiClient, prompt, model, aspectRatio, resolution, quality, resolvedRefs, extra) } finally { sharedApiSemaphore.release() } } case 'comfyui': { await comfyuiSemaphore.acquire() try { return await generateWithComfyUI(config, prompt, workflow, referenceImages, extra) } finally { comfyuiSemaphore.release() } } default: return { content: [{ type: 'text' as const, text: `Unknown provider: ${providerType}` }], isError: true, } } } catch (error) { const message = error instanceof Error ? error.message : String(error) const guidance = classifyError(message) return { content: [{ type: 'text' as const, text: `Image generation failed: ${message}\n\n${guidance}`, }], isError: true, } } } ) } - src/tools/generate-image.ts:99-119 (schema)Input schema (generateImageSchema) defining all tool parameters: prompt, model, size, aspectRatio, resolution, quality, referenceImages, provider, workflow, negativePrompt — each with Zod validation and descriptions.
export const generateImageSchema = { prompt: z.string().trim().min(1, 'Prompt cannot be empty').describe('The image generation prompt'), model: z.string().optional() .describe('Model name. For OpenAI-compatible providers: any model ID your endpoint supports. For MeiGen: use model IDs from list_models.'), size: z.string().optional() .describe('Image size for OpenAI-compatible providers: "1024x1024", "1536x1024", "auto". MeiGen/ComfyUI: use aspectRatio instead.'), aspectRatio: z.string().optional() .describe('Aspect ratio for MeiGen provider. Use "auto" (recommended, default when omitted) to let MeiGen infer the best ratio from the prompt content. Explicit values: "1:1", "3:4", "4:3", "16:9", "9:16", "21:9", "2:3", "3:2", "4:5", "5:4", etc. (model-dependent). ComfyUI: use comfyui_workflow modify to adjust dimensions before generating.'), resolution: z.string().optional() .describe('Resolution tier. MeiGen: "1K" / "2K" / "3K" / "4K" — each model supports a subset (list_models reports resolutions when applicable). OpenAI: not used (use size instead).'), quality: z.string().optional() .describe('Image quality. MeiGen gpt-image-2: "low" / "medium" / "high". OpenAI-compatible providers also accept "high".'), referenceImages: z.array(z.string()).optional() .describe('Image references for style/content guidance. Accepts both public URLs (http/https) and local file paths. Local files are automatically compressed and uploaded when needed. For ComfyUI: local files are passed directly to the workflow (requires LoadImage node). Sources: gallery URLs from search_gallery/get_inspiration, URLs from previous generate_image results, or local file paths.'), provider: z.enum(['openai', 'meigen', 'comfyui']).optional() .describe('Which provider to use. Auto-detected from configuration if not specified.'), workflow: z.string().optional() .describe('ComfyUI workflow name to use (from comfyui_workflow list). Uses default workflow if not specified.'), negativePrompt: z.string().optional() .describe('Negative prompt for OpenAI-compatible providers. ComfyUI: use comfyui_workflow modify to set negative prompt in the workflow before generating.'), } - src/server.ts:274-275 (registration)Registration call in the main server setup that wires registerGenerateImage into the MCP server instance.
// Image generation (requires API Key, MeiGen Token, or ComfyUI workflow) registerGenerateImage(server, apiClient, config) - src/tools/generate-image.ts:213-236 (helper)Provider-specific handler: generateWithOpenAI — uses OpenAIProvider to generate, saves locally, and tracks recent generations.
async function generateWithOpenAI( config: MeiGenConfig, prompt: string, model?: string, size?: string, quality?: string, referenceImages?: string[], ) { const provider = new OpenAIProvider(config.openaiApiKey!, config.openaiBaseUrl, config.openaiModel) const result = await provider.generate({ prompt, model, size, quality, referenceImages }) const savedPath = saveImageLocally(result.imageBase64, result.mimeType) addRecentGeneration({ prompt, provider: 'openai', model: model || config.openaiModel }) const lines = [`Image generated successfully.`] lines.push(`- Provider: OpenAI-compatible (${model || config.openaiModel})`) if (referenceImages?.length) lines.push(`- Reference images: ${referenceImages.length} used`) if (savedPath) lines.push(`- Saved to: ${savedPath}`) return { content: [{ type: 'text' as const, text: lines.join('\n') }], } } - src/tools/generate-image.ts:238-324 (helper)Provider-specific handler: generateWithMeiGen — submits via apiClient.generateImage, polls for completion via waitForGeneration, downloads/saves result, and tracks recent generations. Handles multi-candidate results.
async function generateWithMeiGen( apiClient: MeiGenApiClient, prompt: string, model: string | undefined, aspectRatio: string | undefined, resolution: string | undefined, quality: string | undefined, referenceImages: string[] | undefined, extra: RequestHandlerExtra<ServerRequest, ServerNotification>, ) { // 1. Submit generation request // model / resolution / quality 不强制填充默认值;缺省时由 MeiGen 后端按 DB 决定 const genResponse = await apiClient.generateImage({ prompt, modelId: model, aspectRatio: aspectRatio || 'auto', resolution, quality, referenceImages, }) if (!genResponse.generationId) { throw new Error('No generation ID returned') } // Notify: generation submitted await notify(extra, 'Image generation submitted, waiting for result...') // 2. Poll until completed (with progress notifications) const status = await apiClient.waitForGeneration( genResponse.generationId, 300_000, async (elapsedMs) => { await notify(extra, `Still generating... (${Math.round(elapsedMs / 1000)}s elapsed)`) }, ) if (status.status === 'failed') { throw new Error(status.error || 'Generation failed') } // Detect video model misuse early — give a clear redirect instead of cryptic "no image URL" if (status.mediaType === 'video') { throw new Error('This model produces video. Use the generate_video tool with the same model id.') } // Use imageUrls array if available (e.g., V8.1 returns 4 candidates), fall back to imageUrl const allImageUrls = status.imageUrls?.length ? status.imageUrls : (status.imageUrl ? [status.imageUrl] : []) if (allImageUrls.length === 0) { throw new Error('No image URL in completed generation') } // Download first image for local save const imageRes = await fetch(allImageUrls[0]) if (!imageRes.ok) { throw new Error(`Failed to download generated image: ${imageRes.status}`) } const buffer = await imageRes.arrayBuffer() const base64 = Buffer.from(buffer).toString('base64') const mimeType = imageRes.headers.get('content-type') || 'image/jpeg' const savedPath = saveImageLocally(base64, mimeType) // 优先用后端返回的 modelId(反映真实使用的模型,包含 is_default 解析结果); // 若后端未回传(旧版 backend),用用户显式传入的 model,再 fallback 到占位 const actualModel = genResponse.modelId || model || 'meigen-default' addRecentGeneration({ prompt, provider: 'meigen', model: actualModel, aspectRatio }) const lines = [`Image generated successfully.`] lines.push(`- Provider: MeiGen (model: ${actualModel})`) if (allImageUrls.length > 1) { lines.push(`- ${allImageUrls.length} candidate images returned:`) allImageUrls.forEach((url, i) => lines.push(` ${i + 1}. ${url}`)) } else { lines.push(`- Image URL: ${allImageUrls[0]}`) } if (savedPath) lines.push(`- Saved to: ${savedPath}`) lines.push(`\nYou can use any Image URL as referenceImages for follow-up generation.`) return { content: [{ type: 'text' as const, text: lines.join('\n') }], } }