siliconflow-image-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts", "../src/services/siliconflow.ts", "../src/types/index.ts", "../src/utils/file.ts", "../src/tools/generate.ts", "../src/tools/edit.ts", "../src/tools/list-models.ts"],
4
+ "sourcesContent": ["#!/usr/bin/env node\n/**\n * SiliconFlow Image MCP Server\n *\n * A Model Context Protocol server for image generation and editing using SiliconFlow\n * Optimized for China users\n */\n\n// Handle build flag for compilation\nif (process.argv.includes('--build')) {\n console.log('\u2705 Project structure is ready. Use \"npm start\" to run the server.');\n console.log('\uD83D\uDCDD Note: This project uses tsx for runtime execution.');\n process.exit(0);\n}\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { SiliconFlowService } from \"./services/siliconflow.js\";\nimport { createGenerateImageTool } from \"./tools/generate.js\";\nimport { createEditImageTool } from \"./tools/edit.js\";\nimport { createListModelsTool } from \"./tools/list-models.js\";\n\n// Environment variable check\nconst SILICONFLOW_API_KEY = process.env.SILICONFLOW_API_KEY;\nconst MOCK_MODE = process.env.SILICONFLOW_MOCK === \"true\";\n\nif (!SILICONFLOW_API_KEY && !MOCK_MODE) {\n console.error(\"\u274C Error: SILICONFLOW_API_KEY environment variable is required\");\n console.error(\"\\nTo use this MCP server:\");\n console.error(\"1. Get your API key from https://siliconflow.cn\");\n console.error(\"2. Set it as an environment variable:\");\n console.error(\" export SILICONFLOW_API_KEY=your-api-key-here\");\n console.error(\"\\nFor Claude Desktop, add to your config.json:\");\n console.error(`{\n \"mcpServers\": {\n \"siliconflow-image-mcp\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"siliconflow-image-mcp\"],\n \"env\": {\n \"SILICONFLOW_API_KEY\": \"your-api-key-here\"\n }\n }\n }\n}`);\n console.error(\"\\nOptional configurations:\");\n console.error(\" export SILICONFLOW_MOCK=true # Use mock mode for testing\");\n console.error(\" export SILICONFLOW_IMAGE_DIR=/path # Custom directory for saved images\");\n process.exit(1);\n}\n\n// Initialize services and server\nasync function main() {\n console.error(\"\uD83D\uDE80 Starting SiliconFlow Image MCP Server...\");\n\n try {\n // Initialize SiliconFlow service\n let siliconFlowService;\n if (MOCK_MODE) {\n console.error(\"\u26A0\uFE0F Running in MOCK mode - API calls will be simulated\");\n // Create a mock service that returns fake data\n siliconFlowService = {\n generateImage: async () => [{ data: \"mock-base64-data\", mimeType: \"image/png\" }],\n editImage: async () => ({ data: \"mock-base64-data\", mimeType: \"image/png\" }),\n listImageModels: async () => [\n { id: \"black-forest-labs/FLUX.1-dev\", name: \"FLUX.1-dev\", description: \"Mock model\", output_modalities: [\"image\"] }\n ],\n testConnection: async () => true\n };\n } else {\n siliconFlowService = new SiliconFlowService(SILICONFLOW_API_KEY);\n\n // Test connection\n const isConnected = await siliconFlowService.testConnection();\n if (!isConnected) {\n console.error(\"\u274C Failed to connect to SiliconFlow. Please check your API key.\");\n process.exit(1);\n }\n console.error(\"\u2705 Connected to SiliconFlow successfully\");\n }\n\n // Create MCP server\n const server = new McpServer({\n name: \"siliconflow-image-mcp\",\n version: \"1.0.0\",\n capabilities: {\n tools: {\n listChanged: true,\n },\n },\n });\n\n // Register tools\n const generateTool = createGenerateImageTool(siliconFlowService);\n const editTool = createEditImageTool(siliconFlowService);\n const listModelsTool = createListModelsTool(siliconFlowService);\n\n server.registerTool(\n generateTool.name,\n {\n description: generateTool.description,\n inputSchema: generateTool.inputSchema,\n },\n generateTool.handler\n );\n\n server.registerTool(\n editTool.name,\n {\n description: editTool.description,\n inputSchema: editTool.inputSchema,\n },\n editTool.handler\n );\n\n server.registerTool(\n listModelsTool.name,\n {\n description: listModelsTool.description,\n inputSchema: listModelsTool.inputSchema,\n },\n listModelsTool.handler\n );\n\n // Connect to stdio transport\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n console.error(\"\u2705 SiliconFlow Image MCP Server is running\");\n console.error(\"\uD83D\uDCCB Available tools: generate_image, edit_image, list_image_models\");\n console.error(\"\uD83D\uDCDD Waiting for requests...\");\n\n // Handle graceful shutdown\n process.on(\"SIGINT\", async () => {\n console.error(\"\\n\uD83D\uDED1 Shutting down gracefully...\");\n await server.close();\n process.exit(0);\n });\n\n } catch (error) {\n console.error(\"\u274C Failed to start server:\", error instanceof Error ? error.message : \"Unknown error\");\n process.exit(1);\n }\n}\n\n// Run the server\nmain().catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n});", "/**\n * SiliconFlow service wrapper for image generation and editing\n * Optimized for China users\n */\n\nimport { ImageResult, ModelInfo } from \"../types/index.js\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\nexport class SiliconFlowService {\n private apiKey: string;\n private baseUrl: string;\n\n constructor(apiKey: string) {\n if (!apiKey || apiKey.trim() === \"\") {\n throw new Error(\"SiliconFlow API key is required\");\n }\n this.apiKey = apiKey;\n this.baseUrl = \"https://api.siliconflow.cn/v1\";\n }\n\n /**\n * Make API call to SiliconFlow\n */\n private async makeApiCall<T = any>(endpoint: string, body: any): Promise<T> {\n const response = await fetch(`${this.baseUrl}${endpoint}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`API error (${response.status}): ${errorText}`);\n }\n\n return response.json() as Promise<T>;\n }\n\n /**\n * Make GET API call to SiliconFlow\n */\n private async makeGetCall<T = any>(endpoint: string, params?: Record<string, any>): Promise<T> {\n const url = new URL(`${this.baseUrl}${endpoint}`);\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.append(key, String(value));\n }\n });\n }\n\n const response = await fetch(url.toString(), {\n method: \"GET\",\n headers: {\n \"Authorization\": `Bearer ${this.apiKey}`,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`API error (${response.status}): ${errorText}`);\n }\n\n return response.json() as Promise<T>;\n }\n\n /**\n * Generate images using SiliconFlow's image generation models\n */\n async generateImage(\n prompt: string,\n model: string = \"black-forest-labs/FLUX.1-dev\",\n aspectRatio?: string,\n imageSize?: string,\n count: number = 1,\n negativePrompt?: string,\n seed?: number\n ): Promise<ImageResult[]> {\n try {\n // Build request body\n const requestBody: any = {\n model,\n prompt,\n batch_size: Math.min(count, 4), // SiliconFlow max is 4\n };\n\n // Handle image size mapping from aspect ratio\n if (aspectRatio || imageSize) {\n requestBody.image_size = this.mapAspectRatioToSize(aspectRatio, imageSize, model);\n }\n\n if (negativePrompt) {\n requestBody.negative_prompt = negativePrompt;\n }\n\n if (seed !== undefined) {\n requestBody.seed = seed;\n }\n\n // Use SiliconFlow's image generation endpoint\n const result = await this.makeApiCall(\"/images/generations\", requestBody);\n\n if (!result.images || result.images.length === 0) {\n throw new Error(\"No images were generated\");\n }\n\n // Download images and convert to base64\n const images: ImageResult[] = [];\n for (const img of result.images) {\n const imageUrl = img.url;\n const imageResponse = await fetch(imageUrl);\n if (!imageResponse.ok) {\n throw new Error(`Failed to download image: ${imageResponse.status}`);\n }\n\n const imageBuffer = await imageResponse.arrayBuffer();\n const base64Data = Buffer.from(imageBuffer).toString('base64');\n\n // Determine mime type from URL or default to PNG\n const mimeType = imageUrl.includes('.png') ? 'image/png' :\n imageUrl.includes('.jpg') || imageUrl.includes('.jpeg') ? 'image/jpeg' :\n 'image/png';\n\n images.push({\n data: base64Data,\n mimeType,\n });\n }\n\n return images;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Image generation failed: ${error.message}`);\n }\n throw new Error(\"Image generation failed with unknown error\");\n }\n }\n\n /**\n * Edit an existing image using SiliconFlow\n */\n async editImage(\n image: string,\n prompt: string,\n model: string = \"Qwen/Qwen-Image-Edit-2509\"\n ): Promise<ImageResult> {\n try {\n // Determine if image is base64, URL, or local file path\n let imageContent: string;\n\n if (image.startsWith(\"data:image/\")) {\n // Already a data URL\n imageContent = image;\n } else if (image.startsWith(\"http://\") || image.startsWith(\"https://\")) {\n // URL - SiliconFlow accepts URLs directly\n imageContent = image;\n } else if (fs.existsSync(image) && fs.statSync(image).isFile()) {\n // Local file path - read and convert to base64 data URL\n const imageBuffer = fs.readFileSync(image);\n const base64Data = imageBuffer.toString('base64');\n\n // Determine mime type from file extension\n const ext = path.extname(image).toLowerCase();\n const mimeType = ext === '.png' ? 'image/png' :\n ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg' :\n ext === '.webp' ? 'image/webp' :\n 'image/png';\n\n imageContent = `data:${mimeType};base64,${base64Data}`;\n } else {\n // Assume raw base64 string, convert to data URL\n imageContent = `data:image/png;base64,${image}`;\n }\n\n // Build request body for image editing\n const requestBody: any = {\n model,\n prompt,\n image: imageContent,\n };\n\n const result = await this.makeApiCall(\"/images/generations\", requestBody);\n\n if (!result.images || result.images.length === 0) {\n throw new Error(\"No edited image was returned\");\n }\n\n // Download the edited image and convert to base64\n const img = result.images[0];\n const imageUrl = img.url;\n\n const imageResponse = await fetch(imageUrl);\n if (!imageResponse.ok) {\n throw new Error(`Failed to download edited image: ${imageResponse.status}`);\n }\n\n const imageBuffer = await imageResponse.arrayBuffer();\n const base64Data = Buffer.from(imageBuffer).toString('base64');\n\n const mimeType = imageUrl.includes('.png') ? 'image/png' :\n imageUrl.includes('.jpg') || imageUrl.includes('.jpeg') ? 'image/jpeg' :\n 'image/png';\n\n return {\n data: base64Data,\n mimeType,\n };\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Image editing failed: ${error.message}`);\n }\n throw new Error(\"Image editing failed with unknown error\");\n }\n }\n\n /**\n * List all available image generation models\n */\n async listImageModels(): Promise<ModelInfo[]> {\n try {\n // Get all models, then filter for image generation models\n const result = await this.makeGetCall(\"/models\", { type: \"image\" });\n\n if (!result.data || result.data.length === 0) {\n return [];\n }\n\n // Filter for text-to-image and image-to-image models\n const imageModels = result.data\n .filter((model: any) => {\n // SiliconFlow doesn't always provide detailed modality info in the basic list\n // We'll include models that are commonly known for image generation\n const modelId = model.id.toLowerCase();\n return (\n modelId.includes(\"flux\") ||\n modelId.includes(\"sd\") ||\n modelId.includes(\"stable-diffusion\") ||\n modelId.includes(\"qwen-image\") ||\n modelId.includes(\"kolors\") ||\n modelId.includes(\"dall\") ||\n modelId.includes(\"painting\")\n );\n })\n .map((model: any) => ({\n id: model.id,\n name: model.id,\n description: `Image generation model: ${model.id}`,\n output_modalities: [\"image\"],\n }));\n\n return imageModels;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Failed to list models: ${error.message}`);\n }\n throw new Error(\"Failed to list models with unknown error\");\n }\n }\n\n /**\n * Test the API key validity\n */\n async testConnection(): Promise<boolean> {\n try {\n await this.makeGetCall(\"/models\");\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Map aspect ratio and image size to SiliconFlow's image_size format\n */\n private mapAspectRatioToSize(aspectRatio?: string, imageSize?: string, model?: string): string {\n // SiliconFlow uses \"widthxheight\" format\n // Based on their documentation, we'll map common aspect ratios\n\n // For Qwen models\n if (model && model.includes(\"qwen\")) {\n const qwenSizes: { [key: string]: string } = {\n \"1:1\": \"1328x1328\",\n \"16:9\": \"1664x928\",\n \"9:16\": \"928x1664\",\n \"4:3\": \"1472x1140\",\n \"3:4\": \"1140x1472\",\n \"3:2\": \"1584x1056\",\n \"2:3\": \"1056x1584\",\n };\n if (aspectRatio && qwenSizes[aspectRatio]) {\n return qwenSizes[aspectRatio];\n }\n }\n\n // For Kolors models\n const kolorsSizes: { [key: string]: string } = {\n \"1:1\": \"1024x1024\",\n \"3:4\": \"960x1280\",\n \"4:3\": \"1280x960\",\n \"1:2\": \"720x1440\",\n \"9:16\": \"720x1280\",\n \"16:9\": \"1280x720\",\n };\n\n if (aspectRatio && kolorsSizes[aspectRatio]) {\n return kolorsSizes[aspectRatio];\n }\n\n // Default sizes\n if (imageSize === \"4K\") return \"2048x2048\";\n if (imageSize === \"2K\") return \"1536x1536\";\n\n // Default to 1024x1024\n return \"1024x1024\";\n }\n}", "/**\n * Type definitions for SiliconFlow Image MCP\n */\n\nimport { z } from \"zod\";\n\n// Image generation input schema\nexport const GenerateImageInputSchema = z.object({\n prompt: z\n .string()\n .min(1, \"Prompt is required\")\n .max(2000, \"Prompt must be 2000 characters or less\")\n .describe(\"Detailed description of the image to generate\"),\n\n model: z\n .string()\n .optional()\n .describe(\"Model to use for generation (defaults to black-forest-labs/FLUX.1-dev)\"),\n\n aspectRatio: z\n .enum([\"1:1\", \"2:3\", \"3:2\", \"3:4\", \"4:3\", \"4:5\", \"5:4\", \"9:16\", \"16:9\", \"21:9\"])\n .optional()\n .describe(\"Aspect ratio for generated images\"),\n\n imageSize: z\n .enum([\"1K\", \"2K\", \"4K\"])\n .optional()\n .describe(\"Image size (higher resolution)\"),\n\n count: z\n .number()\n .int()\n .min(1)\n .max(4)\n .optional()\n .default(1)\n .describe(\"Number of images to generate (1-4)\"),\n\n negativePrompt: z\n .string()\n .optional()\n .describe(\"Negative prompt - what to avoid in the image\"),\n\n seed: z\n .number()\n .int()\n .min(0)\n .max(9999999999)\n .optional()\n .describe(\"Seed for reproducible results\"),\n});\n\nexport type GenerateImageInput = z.infer<typeof GenerateImageInputSchema>;\n\n// Image editing input schema\nexport const EditImageInputSchema = z.object({\n image: z\n .string()\n .min(1, \"Image data is required\")\n .describe(\"Base64 encoded image data, image URL, or local file path to edit\"),\n\n prompt: z\n .string()\n .min(1, \"Edit prompt is required\")\n .max(2000, \"Edit prompt must be 2000 characters or less\")\n .describe(\"Instructions for editing the image\"),\n\n model: z\n .string()\n .optional()\n .describe(\"Model to use for editing (defaults to Qwen/Qwen-Image-Edit-2509)\"),\n});\n\nexport type EditImageInput = z.infer<typeof EditImageInputSchema>;\n\n// List models input schema (empty)\nexport const ListModelsInputSchema = z.object({});\n\nexport type ListModelsInput = z.infer<typeof ListModelsInputSchema>;\n\n// Image result type\nexport interface ImageResult {\n data: string; // Base64 encoded image data\n mimeType: string; // e.g., \"image/png\"\n size?: number; // File size in bytes\n width?: number; // Image width in pixels\n height?: number; // Image height in pixels\n}\n\n// Model information type\nexport interface ModelInfo {\n id: string;\n name: string;\n description?: string;\n output_modalities?: string[];\n pricing?: {\n prompt?: number;\n completion?: number;\n image?: number;\n };\n context_length?: number;\n}\n\n// Tool response type\nexport interface ToolResponse {\n content: Array<{\n type: \"text\" | \"image\" | \"resource\";\n text?: string;\n data?: string;\n mimeType?: string;\n uri?: string;\n name?: string;\n }>;\n isError?: boolean;\n}", "/**\n * File utility functions for saving images\n */\n\nimport { promises as fs } from \"fs\";\nimport path from \"path\";\nimport os from \"os\";\n\n/**\n * Get the base directory for storing images\n * Uses SILICONFLOW_IMAGE_DIR env var if set, otherwise defaults to system temp dir\n */\nfunction getImageBaseDir(): string {\n const customDir = process.env.SILICONFLOW_IMAGE_DIR;\n if (customDir) {\n return customDir;\n }\n return path.join(os.tmpdir(), \"siliconflow-images\");\n}\n\n/**\n * Save base64 image data to a temporary file\n * @param base64Data - Base64 encoded image data\n * @param prefix - Prefix for the filename\n * @param mimeType - MIME type to determine file extension\n * @returns Path to the saved file\n */\nexport async function saveImageToFile(\n base64Data: string,\n prefix: string,\n mimeType: string\n): Promise<string> {\n const tempDir = getImageBaseDir();\n await fs.mkdir(tempDir, { recursive: true });\n\n const extension = mimeType === \"image/jpeg\" ? \"jpg\" : \"png\";\n const timestamp = Date.now();\n const filename = `${prefix}_${timestamp}.${extension}`;\n const filepath = path.join(tempDir, filename);\n\n const buffer = Buffer.from(base64Data, 'base64');\n await fs.writeFile(filepath, buffer);\n\n return filepath;\n}\n\n/**\n * Get the temporary directory path for siliconflow images\n * Uses SILICONFLOW_IMAGE_DIR env var if set, otherwise defaults to system temp dir\n * @returns The temporary directory path\n */\nexport function getTempDir(): string {\n return getImageBaseDir();\n}\n", "/**\n * Image generation tool implementation for SiliconFlow\n */\n\nimport { z } from \"zod\";\nimport { SiliconFlowService } from \"../services/siliconflow.js\";\nimport { GenerateImageInputSchema, ToolResponse } from \"../types/index.js\";\nimport { saveImageToFile, getTempDir } from \"../utils/file.js\";\n\nexport function createGenerateImageTool(service: SiliconFlowService) {\n return {\n name: \"generate_image\",\n description: \"Generate images using SiliconFlow's AI models. Supports various aspect ratios, image sizes, negative prompts, and seeds for reproducible results. Images are saved to temporary files and paths are returned.\",\n inputSchema: GenerateImageInputSchema,\n\n handler: async (input: unknown): Promise<ToolResponse> => {\n const parsed = GenerateImageInputSchema.safeParse(input);\n\n if (!parsed.success) {\n return {\n content: [\n {\n type: \"text\",\n text: `Invalid input: ${parsed.error.errors.map(e => e.message).join(\", \")}`,\n },\n ],\n isError: true,\n };\n }\n\n const { prompt, model, aspectRatio, imageSize, count, negativePrompt, seed } = parsed.data;\n\n try {\n const images = await service.generateImage(\n prompt,\n model || \"black-forest-labs/FLUX.1-dev\",\n aspectRatio,\n imageSize,\n count,\n negativePrompt,\n seed\n );\n\n if (images.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No images were generated. Please try a different prompt.\",\n },\n ],\n isError: true,\n };\n }\n\n // Save images to files and return file paths\n const savedFiles: string[] = [];\n\n for (let i = 0; i < images.length; i++) {\n const img = images[i];\n const filepath = await saveImageToFile(img.data, `generated_${i + 1}`, img.mimeType);\n savedFiles.push(filepath);\n }\n\n // Create response with file paths\n const tempDir = getTempDir();\n const response: ToolResponse = {\n content: [\n {\n type: \"text\",\n text: `Successfully generated ${images.length} image${images.length > 1 ? \"s\" : \"\"} for prompt: \"${prompt}\"\\n` +\n `Saved to:\\n${savedFiles.map(f => `- ${f}`).join(\"\\n\")}\\n\\n` +\n `Temporary directory: ${tempDir}\\n` +\n `Note: These are temporary files. Use the file paths to access the images.`,\n },\n ],\n };\n\n return response;\n } catch (error) {\n return {\n content: [\n {\n type: \"text\",\n text: `Image generation failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n },\n ],\n isError: true,\n };\n }\n },\n };\n}", "/**\n * Image editing tool implementation for SiliconFlow\n */\n\nimport { z } from \"zod\";\nimport { SiliconFlowService } from \"../services/siliconflow.js\";\nimport { EditImageInputSchema, ToolResponse } from \"../types/index.js\";\nimport { saveImageToFile, getTempDir } from \"../utils/file.js\";\n\nexport function createEditImageTool(service: SiliconFlowService) {\n return {\n name: \"edit_image\",\n description: \"Edit existing images using SiliconFlow's AI models. Accepts base64 encoded image data, image URLs, or local file paths. Provide instructions for modifications. Uses Qwen/Qwen-Image-Edit-2509 by default. Images are saved to temporary files and paths are returned.\",\n inputSchema: EditImageInputSchema,\n\n handler: async (input: unknown): Promise<ToolResponse> => {\n const parsed = EditImageInputSchema.safeParse(input);\n\n if (!parsed.success) {\n return {\n content: [\n {\n type: \"text\",\n text: `Invalid input: ${parsed.error.errors.map(e => e.message).join(\", \")}`,\n },\n ],\n isError: true,\n };\n }\n\n const { image, prompt, model } = parsed.data;\n\n try {\n const editedImage = await service.editImage(\n image,\n prompt,\n model || \"Qwen/Qwen-Image-Edit-2509\"\n );\n\n // Save edited image to file\n const filepath = await saveImageToFile(editedImage.data, \"edited\", editedImage.mimeType);\n\n // Create response with file path\n const tempDir = getTempDir();\n const response: ToolResponse = {\n content: [\n {\n type: \"text\",\n text: `Successfully edited image with prompt: \"${prompt}\"\\n` +\n `Saved to: ${filepath}\\n\\n` +\n `Temporary directory: ${tempDir}\\n` +\n `Note: This is a temporary file. Use the file path to access the image.`,\n },\n ],\n };\n\n return response;\n } catch (error) {\n return {\n content: [\n {\n type: \"text\",\n text: `Image editing failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n },\n ],\n isError: true,\n };\n }\n },\n };\n}", "/**\n * List image models tool implementation for SiliconFlow\n */\n\nimport { z } from \"zod\";\nimport { SiliconFlowService } from \"../services/siliconflow.js\";\nimport { ListModelsInputSchema, ToolResponse } from \"../types/index.js\";\n\nexport function createListModelsTool(service: SiliconFlowService) {\n return {\n name: \"list_image_models\",\n description: \"List all available image generation models from SiliconFlow. Shows model IDs and capabilities.\",\n inputSchema: ListModelsInputSchema,\n\n handler: async (input: unknown): Promise<ToolResponse> => {\n const parsed = ListModelsInputSchema.safeParse(input);\n\n if (!parsed.success) {\n return {\n content: [\n {\n type: \"text\",\n text: `Invalid input: ${parsed.error.errors.map(e => e.message).join(\", \")}`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n const models = await service.listImageModels();\n\n if (models.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No image generation models found. This might be a temporary issue with the API.\",\n },\n ],\n isError: true,\n };\n }\n\n // Format the response for readability\n const modelList = models\n .map((model, index) => {\n const parts = [\n `${index + 1}. **${model.name}** (\\`${model.id}\\`)`,\n model.description ? ` - ${model.description}` : null,\n model.output_modalities ? ` - Capabilities: ${model.output_modalities.join(\", \")}` : null,\n ].filter(Boolean);\n\n return parts.join(\"\\n\");\n })\n .join(\"\\n\\n\");\n\n // Add SiliconFlow-specific usage tips\n const usageTips = `\\n\\n---\\n\\n**Usage:** Use the \\`generate_image\\` tool with the model ID to generate images.\\n\\n**SiliconFlow Tips:**\\n- Recommended models: \\`black-forest-labs/FLUX.1-dev\\` (generation), \\`Qwen/Qwen-Image-Edit-2509\\` (editing)\\n- Supports advanced options: negative prompts, seeds, CFG values\\n- Image sizes: Use aspect ratios like \"1:1\", \"16:9\", \"9:16\"\\n- Optimized for China users with fast local network access`;\n\n return {\n content: [\n {\n type: \"text\",\n text: `## Available Image Generation Models (SiliconFlow)\\n\\nFound ${models.length} model${models.length > 1 ? \"s\" : \"\"} that support image generation:\\n\\n${modelList}${usageTips}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: \"text\",\n text: `Failed to list models: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n },\n ],\n isError: true,\n };\n }\n },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["imageBuffer", "base64Data", "mimeType", "fs", "path", "path", "fs"]
7
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "siliconflow-image-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for image generation and editing using SiliconFlow - Optimized for China users",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": "index.js",
8
+ "dependencies": {
9
+ "@modelcontextprotocol/sdk": "^1.0.3",
10
+ "tsx": "^4.7.0",
11
+ "zod": "^3.22.4"
12
+ },
13
+ "engines": {
14
+ "node": ">=18.0.0"
15
+ },
16
+ "keywords": [
17
+ "mcp",
18
+ "siliconflow",
19
+ "image-generation",
20
+ "ai",
21
+ "claude",
22
+ "model-context-protocol",
23
+ "china",
24
+ "image-editing",
25
+ "flux",
26
+ "qwen"
27
+ ],
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/martianzhang/siliconflow-image-mcp"
32
+ }
33
+ }
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "siliconflow-image-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for image generation and editing using SiliconFlow - Optimized for China users",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "siliconflow-image-mcp": "bin/siliconflow-image-mcp"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "src",
13
+ "bin",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "build": "node scripts/build.mjs",
19
+ "dev": "tsx src/index.ts",
20
+ "start": "tsx src/index.ts",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "test:coverage": "vitest run --coverage",
24
+ "lint": "eslint src/**/*.ts",
25
+ "lint:fix": "eslint src/**/*.ts --fix",
26
+ "format": "prettier --write src/**/*.ts",
27
+ "format:check": "prettier --check src/**/*.ts",
28
+ "prepublishOnly": "npm test && npm run build"
29
+ },
30
+ "keywords": [
31
+ "mcp",
32
+ "siliconflow",
33
+ "image-generation",
34
+ "ai",
35
+ "claude",
36
+ "model-context-protocol",
37
+ "china",
38
+ "image-editing",
39
+ "flux",
40
+ "qwen"
41
+ ],
42
+ "author": "martianzhang",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/martianzhang/siliconflow-image-mcp"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/martianzhang/siliconflow-image-mcp/issues"
50
+ },
51
+ "homepage": "https://github.com/martianzhang/siliconflow-image-mcp#readme",
52
+ "dependencies": {
53
+ "@modelcontextprotocol/sdk": "^1.0.3",
54
+ "tsx": "^4.7.0",
55
+ "zod": "^3.22.4"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^20.11.5",
59
+ "@typescript-eslint/eslint-plugin": "^6.19.0",
60
+ "@typescript-eslint/parser": "^6.19.0",
61
+ "eslint": "^8.56.0",
62
+ "prettier": "^3.2.4",
63
+ "tsx": "^4.7.0",
64
+ "typescript": "^5.3.3",
65
+ "vitest": "^1.2.1"
66
+ },
67
+ "engines": {
68
+ "node": ">=18.0.0"
69
+ }
70
+ }
package/src/index.ts ADDED
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SiliconFlow Image MCP Server
4
+ *
5
+ * A Model Context Protocol server for image generation and editing using SiliconFlow
6
+ * Optimized for China users
7
+ */
8
+
9
+ // Handle build flag for compilation
10
+ if (process.argv.includes('--build')) {
11
+ console.log('✅ Project structure is ready. Use "npm start" to run the server.');
12
+ console.log('📝 Note: This project uses tsx for runtime execution.');
13
+ process.exit(0);
14
+ }
15
+
16
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18
+ import { SiliconFlowService } from "./services/siliconflow.js";
19
+ import { createGenerateImageTool } from "./tools/generate.js";
20
+ import { createEditImageTool } from "./tools/edit.js";
21
+ import { createListModelsTool } from "./tools/list-models.js";
22
+
23
+ // Environment variable check
24
+ const SILICONFLOW_API_KEY = process.env.SILICONFLOW_API_KEY;
25
+ const MOCK_MODE = process.env.SILICONFLOW_MOCK === "true";
26
+
27
+ if (!SILICONFLOW_API_KEY && !MOCK_MODE) {
28
+ console.error("❌ Error: SILICONFLOW_API_KEY environment variable is required");
29
+ console.error("\nTo use this MCP server:");
30
+ console.error("1. Get your API key from https://siliconflow.cn");
31
+ console.error("2. Set it as an environment variable:");
32
+ console.error(" export SILICONFLOW_API_KEY=your-api-key-here");
33
+ console.error("\nFor Claude Desktop, add to your config.json:");
34
+ console.error(`{
35
+ "mcpServers": {
36
+ "siliconflow-image-mcp": {
37
+ "command": "npx",
38
+ "args": ["-y", "siliconflow-image-mcp"],
39
+ "env": {
40
+ "SILICONFLOW_API_KEY": "your-api-key-here"
41
+ }
42
+ }
43
+ }
44
+ }`);
45
+ console.error("\nOptional configurations:");
46
+ console.error(" export SILICONFLOW_MOCK=true # Use mock mode for testing");
47
+ console.error(" export SILICONFLOW_IMAGE_DIR=/path # Custom directory for saved images");
48
+ process.exit(1);
49
+ }
50
+
51
+ // Initialize services and server
52
+ async function main() {
53
+ console.error("🚀 Starting SiliconFlow Image MCP Server...");
54
+
55
+ try {
56
+ // Initialize SiliconFlow service
57
+ let siliconFlowService;
58
+ if (MOCK_MODE) {
59
+ console.error("⚠️ Running in MOCK mode - API calls will be simulated");
60
+ // Create a mock service that returns fake data
61
+ siliconFlowService = {
62
+ generateImage: async () => [{ data: "mock-base64-data", mimeType: "image/png" }],
63
+ editImage: async () => ({ data: "mock-base64-data", mimeType: "image/png" }),
64
+ listImageModels: async () => [
65
+ { id: "black-forest-labs/FLUX.1-dev", name: "FLUX.1-dev", description: "Mock model", output_modalities: ["image"] }
66
+ ],
67
+ testConnection: async () => true
68
+ };
69
+ } else {
70
+ siliconFlowService = new SiliconFlowService(SILICONFLOW_API_KEY);
71
+
72
+ // Test connection
73
+ const isConnected = await siliconFlowService.testConnection();
74
+ if (!isConnected) {
75
+ console.error("❌ Failed to connect to SiliconFlow. Please check your API key.");
76
+ process.exit(1);
77
+ }
78
+ console.error("✅ Connected to SiliconFlow successfully");
79
+ }
80
+
81
+ // Create MCP server
82
+ const server = new McpServer({
83
+ name: "siliconflow-image-mcp",
84
+ version: "1.0.0",
85
+ capabilities: {
86
+ tools: {
87
+ listChanged: true,
88
+ },
89
+ },
90
+ });
91
+
92
+ // Register tools
93
+ const generateTool = createGenerateImageTool(siliconFlowService);
94
+ const editTool = createEditImageTool(siliconFlowService);
95
+ const listModelsTool = createListModelsTool(siliconFlowService);
96
+
97
+ server.registerTool(
98
+ generateTool.name,
99
+ {
100
+ description: generateTool.description,
101
+ inputSchema: generateTool.inputSchema,
102
+ },
103
+ generateTool.handler
104
+ );
105
+
106
+ server.registerTool(
107
+ editTool.name,
108
+ {
109
+ description: editTool.description,
110
+ inputSchema: editTool.inputSchema,
111
+ },
112
+ editTool.handler
113
+ );
114
+
115
+ server.registerTool(
116
+ listModelsTool.name,
117
+ {
118
+ description: listModelsTool.description,
119
+ inputSchema: listModelsTool.inputSchema,
120
+ },
121
+ listModelsTool.handler
122
+ );
123
+
124
+ // Connect to stdio transport
125
+ const transport = new StdioServerTransport();
126
+ await server.connect(transport);
127
+
128
+ console.error("✅ SiliconFlow Image MCP Server is running");
129
+ console.error("📋 Available tools: generate_image, edit_image, list_image_models");
130
+ console.error("📝 Waiting for requests...");
131
+
132
+ // Handle graceful shutdown
133
+ process.on("SIGINT", async () => {
134
+ console.error("\n🛑 Shutting down gracefully...");
135
+ await server.close();
136
+ process.exit(0);
137
+ });
138
+
139
+ } catch (error) {
140
+ console.error("❌ Failed to start server:", error instanceof Error ? error.message : "Unknown error");
141
+ process.exit(1);
142
+ }
143
+ }
144
+
145
+ // Run the server
146
+ main().catch((error) => {
147
+ console.error("Fatal error:", error);
148
+ process.exit(1);
149
+ });
@@ -0,0 +1,320 @@
1
+ /**
2
+ * SiliconFlow service wrapper for image generation and editing
3
+ * Optimized for China users
4
+ */
5
+
6
+ import { ImageResult, ModelInfo } from "../types/index.js";
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+
10
+ export class SiliconFlowService {
11
+ private apiKey: string;
12
+ private baseUrl: string;
13
+
14
+ constructor(apiKey: string) {
15
+ if (!apiKey || apiKey.trim() === "") {
16
+ throw new Error("SiliconFlow API key is required");
17
+ }
18
+ this.apiKey = apiKey;
19
+ this.baseUrl = "https://api.siliconflow.cn/v1";
20
+ }
21
+
22
+ /**
23
+ * Make API call to SiliconFlow
24
+ */
25
+ private async makeApiCall<T = any>(endpoint: string, body: any): Promise<T> {
26
+ const response = await fetch(`${this.baseUrl}${endpoint}`, {
27
+ method: "POST",
28
+ headers: {
29
+ "Content-Type": "application/json",
30
+ "Authorization": `Bearer ${this.apiKey}`,
31
+ },
32
+ body: JSON.stringify(body),
33
+ });
34
+
35
+ if (!response.ok) {
36
+ const errorText = await response.text();
37
+ throw new Error(`API error (${response.status}): ${errorText}`);
38
+ }
39
+
40
+ return response.json() as Promise<T>;
41
+ }
42
+
43
+ /**
44
+ * Make GET API call to SiliconFlow
45
+ */
46
+ private async makeGetCall<T = any>(endpoint: string, params?: Record<string, any>): Promise<T> {
47
+ const url = new URL(`${this.baseUrl}${endpoint}`);
48
+ if (params) {
49
+ Object.entries(params).forEach(([key, value]) => {
50
+ if (value !== undefined && value !== null) {
51
+ url.searchParams.append(key, String(value));
52
+ }
53
+ });
54
+ }
55
+
56
+ const response = await fetch(url.toString(), {
57
+ method: "GET",
58
+ headers: {
59
+ "Authorization": `Bearer ${this.apiKey}`,
60
+ },
61
+ });
62
+
63
+ if (!response.ok) {
64
+ const errorText = await response.text();
65
+ throw new Error(`API error (${response.status}): ${errorText}`);
66
+ }
67
+
68
+ return response.json() as Promise<T>;
69
+ }
70
+
71
+ /**
72
+ * Generate images using SiliconFlow's image generation models
73
+ */
74
+ async generateImage(
75
+ prompt: string,
76
+ model: string = "black-forest-labs/FLUX.1-dev",
77
+ aspectRatio?: string,
78
+ imageSize?: string,
79
+ count: number = 1,
80
+ negativePrompt?: string,
81
+ seed?: number
82
+ ): Promise<ImageResult[]> {
83
+ try {
84
+ // Build request body
85
+ const requestBody: any = {
86
+ model,
87
+ prompt,
88
+ batch_size: Math.min(count, 4), // SiliconFlow max is 4
89
+ };
90
+
91
+ // Handle image size mapping from aspect ratio
92
+ if (aspectRatio || imageSize) {
93
+ requestBody.image_size = this.mapAspectRatioToSize(aspectRatio, imageSize, model);
94
+ }
95
+
96
+ if (negativePrompt) {
97
+ requestBody.negative_prompt = negativePrompt;
98
+ }
99
+
100
+ if (seed !== undefined) {
101
+ requestBody.seed = seed;
102
+ }
103
+
104
+ // Use SiliconFlow's image generation endpoint
105
+ const result = await this.makeApiCall("/images/generations", requestBody);
106
+
107
+ if (!result.images || result.images.length === 0) {
108
+ throw new Error("No images were generated");
109
+ }
110
+
111
+ // Download images and convert to base64
112
+ const images: ImageResult[] = [];
113
+ for (const img of result.images) {
114
+ const imageUrl = img.url;
115
+ const imageResponse = await fetch(imageUrl);
116
+ if (!imageResponse.ok) {
117
+ throw new Error(`Failed to download image: ${imageResponse.status}`);
118
+ }
119
+
120
+ const imageBuffer = await imageResponse.arrayBuffer();
121
+ const base64Data = Buffer.from(imageBuffer).toString('base64');
122
+
123
+ // Determine mime type from URL or default to PNG
124
+ const mimeType = imageUrl.includes('.png') ? 'image/png' :
125
+ imageUrl.includes('.jpg') || imageUrl.includes('.jpeg') ? 'image/jpeg' :
126
+ 'image/png';
127
+
128
+ images.push({
129
+ data: base64Data,
130
+ mimeType,
131
+ });
132
+ }
133
+
134
+ return images;
135
+ } catch (error) {
136
+ if (error instanceof Error) {
137
+ throw new Error(`Image generation failed: ${error.message}`);
138
+ }
139
+ throw new Error("Image generation failed with unknown error");
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Edit an existing image using SiliconFlow
145
+ */
146
+ async editImage(
147
+ image: string,
148
+ prompt: string,
149
+ model: string = "Qwen/Qwen-Image-Edit-2509"
150
+ ): Promise<ImageResult> {
151
+ try {
152
+ // Determine if image is base64, URL, or local file path
153
+ let imageContent: string;
154
+
155
+ if (image.startsWith("data:image/")) {
156
+ // Already a data URL
157
+ imageContent = image;
158
+ } else if (image.startsWith("http://") || image.startsWith("https://")) {
159
+ // URL - SiliconFlow accepts URLs directly
160
+ imageContent = image;
161
+ } else if (fs.existsSync(image) && fs.statSync(image).isFile()) {
162
+ // Local file path - read and convert to base64 data URL
163
+ const imageBuffer = fs.readFileSync(image);
164
+ const base64Data = imageBuffer.toString('base64');
165
+
166
+ // Determine mime type from file extension
167
+ const ext = path.extname(image).toLowerCase();
168
+ const mimeType = ext === '.png' ? 'image/png' :
169
+ ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg' :
170
+ ext === '.webp' ? 'image/webp' :
171
+ 'image/png';
172
+
173
+ imageContent = `data:${mimeType};base64,${base64Data}`;
174
+ } else {
175
+ // Assume raw base64 string, convert to data URL
176
+ imageContent = `data:image/png;base64,${image}`;
177
+ }
178
+
179
+ // Build request body for image editing
180
+ const requestBody: any = {
181
+ model,
182
+ prompt,
183
+ image: imageContent,
184
+ };
185
+
186
+ const result = await this.makeApiCall("/images/generations", requestBody);
187
+
188
+ if (!result.images || result.images.length === 0) {
189
+ throw new Error("No edited image was returned");
190
+ }
191
+
192
+ // Download the edited image and convert to base64
193
+ const img = result.images[0];
194
+ const imageUrl = img.url;
195
+
196
+ const imageResponse = await fetch(imageUrl);
197
+ if (!imageResponse.ok) {
198
+ throw new Error(`Failed to download edited image: ${imageResponse.status}`);
199
+ }
200
+
201
+ const imageBuffer = await imageResponse.arrayBuffer();
202
+ const base64Data = Buffer.from(imageBuffer).toString('base64');
203
+
204
+ const mimeType = imageUrl.includes('.png') ? 'image/png' :
205
+ imageUrl.includes('.jpg') || imageUrl.includes('.jpeg') ? 'image/jpeg' :
206
+ 'image/png';
207
+
208
+ return {
209
+ data: base64Data,
210
+ mimeType,
211
+ };
212
+ } catch (error) {
213
+ if (error instanceof Error) {
214
+ throw new Error(`Image editing failed: ${error.message}`);
215
+ }
216
+ throw new Error("Image editing failed with unknown error");
217
+ }
218
+ }
219
+
220
+ /**
221
+ * List all available image generation models
222
+ */
223
+ async listImageModels(): Promise<ModelInfo[]> {
224
+ try {
225
+ // Get all models, then filter for image generation models
226
+ const result = await this.makeGetCall("/models", { type: "image" });
227
+
228
+ if (!result.data || result.data.length === 0) {
229
+ return [];
230
+ }
231
+
232
+ // Filter for text-to-image and image-to-image models
233
+ const imageModels = result.data
234
+ .filter((model: any) => {
235
+ // SiliconFlow doesn't always provide detailed modality info in the basic list
236
+ // We'll include models that are commonly known for image generation
237
+ const modelId = model.id.toLowerCase();
238
+ return (
239
+ modelId.includes("flux") ||
240
+ modelId.includes("sd") ||
241
+ modelId.includes("stable-diffusion") ||
242
+ modelId.includes("qwen-image") ||
243
+ modelId.includes("kolors") ||
244
+ modelId.includes("dall") ||
245
+ modelId.includes("painting")
246
+ );
247
+ })
248
+ .map((model: any) => ({
249
+ id: model.id,
250
+ name: model.id,
251
+ description: `Image generation model: ${model.id}`,
252
+ output_modalities: ["image"],
253
+ }));
254
+
255
+ return imageModels;
256
+ } catch (error) {
257
+ if (error instanceof Error) {
258
+ throw new Error(`Failed to list models: ${error.message}`);
259
+ }
260
+ throw new Error("Failed to list models with unknown error");
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Test the API key validity
266
+ */
267
+ async testConnection(): Promise<boolean> {
268
+ try {
269
+ await this.makeGetCall("/models");
270
+ return true;
271
+ } catch {
272
+ return false;
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Map aspect ratio and image size to SiliconFlow's image_size format
278
+ */
279
+ private mapAspectRatioToSize(aspectRatio?: string, imageSize?: string, model?: string): string {
280
+ // SiliconFlow uses "widthxheight" format
281
+ // Based on their documentation, we'll map common aspect ratios
282
+
283
+ // For Qwen models
284
+ if (model && model.includes("qwen")) {
285
+ const qwenSizes: { [key: string]: string } = {
286
+ "1:1": "1328x1328",
287
+ "16:9": "1664x928",
288
+ "9:16": "928x1664",
289
+ "4:3": "1472x1140",
290
+ "3:4": "1140x1472",
291
+ "3:2": "1584x1056",
292
+ "2:3": "1056x1584",
293
+ };
294
+ if (aspectRatio && qwenSizes[aspectRatio]) {
295
+ return qwenSizes[aspectRatio];
296
+ }
297
+ }
298
+
299
+ // For Kolors models
300
+ const kolorsSizes: { [key: string]: string } = {
301
+ "1:1": "1024x1024",
302
+ "3:4": "960x1280",
303
+ "4:3": "1280x960",
304
+ "1:2": "720x1440",
305
+ "9:16": "720x1280",
306
+ "16:9": "1280x720",
307
+ };
308
+
309
+ if (aspectRatio && kolorsSizes[aspectRatio]) {
310
+ return kolorsSizes[aspectRatio];
311
+ }
312
+
313
+ // Default sizes
314
+ if (imageSize === "4K") return "2048x2048";
315
+ if (imageSize === "2K") return "1536x1536";
316
+
317
+ // Default to 1024x1024
318
+ return "1024x1024";
319
+ }
320
+ }