#!/usr/bin/env python3 """ Qwen Image Generation MCP Server Provides image generation via Qwen (DashScope) API """ import os import requests from urllib.parse import urlparse from mcp.server.fastmcp import FastMCP # Initialize MCP server mcp = FastMCP("qwen-image-generator") @mcp.tool() def generate_image( prompt: str, negative_prompt: str | None = None, prompt_extend: bool = True, size: str = "1024*1024", n: int = 1, image_url: str | None = None, output_path: str | None = None ) -> str: """ Generate images using Qwen (通义千问) image generation API. Args: prompt: The image generation prompt describing what to generate (max 800 chars) negative_prompt: Negative prompt to avoid certain elements (max 500 chars) prompt_extend: Enable prompt extend to enhance the prompt (default: true) watermark: Add watermark to generated image (default: false) size: Image resolution. Options: 1024*1024 (1:1), 1344*768 (16:9), 768*1344 (9:16), 1184*864 (4:3), 864*1184 (3:4) n: Number of images to generate, 1-6 (default: 1) image_url: Optional reference image URL for image-to-image generation output_path: Optional local path to save the generated image Returns: Image URLs and generation status """ api_key = os.environ.get("DASHSCOPE_API_KEY", "") api_base = "https://dashscope.aliyuncs.com" if not api_key: return "Error: DASHSCOPE_API_KEY environment variable is not set" # Build content array content = [{"text": prompt}] # Add reference image if provided if image_url: content.append({"image_url": {"url": image_url}}) # Build parameters dict parameters = { "prompt_extend": prompt_extend, "size": size, "n": n } # Only add negative_prompt if provided if negative_prompt: parameters["negative_prompt"] = negative_prompt # Build request payload payload = { "model": "qwen-image-2.0-pro", "input": { "messages": [ { "role": "user", "content": content } ] }, "parameters": parameters } # Make API request url = f"{api_base}/api/v1/services/aigc/multimodal-generation/generation" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } try: response = requests.post(url, headers=headers, json=payload, timeout=180) response.raise_for_status() result = response.json() # Check for API errors if "error" in result: error_msg = result.get("error", {}).get("message", "Unknown error") return f"API Error: {error_msg}" # Parse response choices = result.get("output", {}).get("choices", []) usage = result.get("usage", {}) # Extract image URLs image_urls = [] for choice in choices: message = choice.get("message", {}) content_items = message.get("content", []) for item in content_items: if "image" in item: image_urls.append(item["image"]) width = usage.get("width", 1024) height = usage.get("height", 1024) request_id = result.get("request_id", "N/A") # Save image if output_path provided saved_path = None if output_path and image_urls: try: img_response = requests.get(image_urls[0], timeout=30) img_response.raise_for_status() # Determine file extension parsed = urlparse(image_urls[0]) ext = os.path.splitext(parsed.path)[1] if "." in parsed.path else ".png" if not ext or len(ext) > 5: ext = ".png" # Ensure directory exists os.makedirs(os.path.dirname(output_path) if os.path.dirname(output_path) else ".", exist_ok=True) # Add extension if not present if not output_path.endswith(ext): output_path = output_path + ext with open(output_path, "wb") as f: f.write(img_response.content) saved_path = os.path.abspath(output_path) except Exception as e: return f"Failed to save image: {str(e)}" # Build response response_text = f"Successfully generated {n} image(s) ({width}x{height})\n\n" response_text += f"Request ID: {request_id}\n\n" if saved_path: response_text += f"Saved to: {saved_path}\n\n" response_text += "Image URLs:\n" for i, img_url in enumerate(image_urls, 1): response_text += f" {i}. {img_url}\n" return response_text except requests.exceptions.RequestException as e: return f"Request Error: {str(e)}" except Exception as e: return f"Unexpected Error: {str(e)}" if __name__ == "__main__": mcp.run()