Draw Things MCP
by jaokuohsuan
- dist
#!/usr/bin/env node
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import axios from 'axios';
const defaultParams={speed_up_with_guidance_embed:true,motion_scale:127,image_guidance:1.5,tiled_decoding:false,decoding_tile_height:640,negative_prompt_for_image_prior:true,batch_size:1,decoding_tile_overlap:128,separate_open_clip_g:false,hires_fix_height:960,decoding_tile_width:640,diffusion_tile_height:1024,num_frames:14,stage_2_guidance:1,t5_text_encoder_decoding:true,mask_blur_outset:0,resolution_dependent_shift:true,model:"flux_1_schnell_q5p.ckpt",hires_fix:false,strength:1,loras:[],diffusion_tile_width:1024,diffusion_tile_overlap:128,original_width:512,seed:-1,zero_negative_prompt:false,upscaler_scale:0,steps:8,upscaler:null,mask_blur:1.5,sampler:"DPM++ 2M AYS",width:320,negative_original_width:512,batch_count:1,refiner_model:null,shift:1,stage_2_shift:1,open_clip_g_text:null,crop_left:0,controls:[],start_frame_guidance:1,original_height:512,image_prior_steps:5,guiding_frame_noise:.019999999552965164,clip_weight:1,clip_skip:1,crop_top:0,negative_original_height:512,preserve_original_after_inpaint:true,separate_clip_l:false,guidance_embed:3.5,negative_aesthetic_score:2.5,aesthetic_score:6,clip_l_text:null,hires_fix_strength:.699999988079071,guidance_scale:7.5,stochastic_sampling_gamma:.3,seed_mode:"Scale Alike",target_width:512,hires_fix_width:960,tiled_diffusion:false,fps:5,refiner_start:.8500000238418579,height:512,prompt:"A cute koala sitting on a eucalyptus tree, watercolor style, beautiful lighting, detailed",negative_prompt:"deformed, distorted, unnatural pose, extra limbs, blurry, low quality, ugly, bad anatomy, poor details, mutated, text, watermark"};
function validateImageGenerationParams(params){const errors=[];if(params.prompt!==undefined&&typeof params.prompt!=="string"){errors.push("prompt must be a string");}if(params.negative_prompt!==undefined&&typeof params.negative_prompt!=="string"){errors.push("negative_prompt must be a string");}if(params.width!==undefined&&(typeof params.width!=="number"||params.width<=0||!Number.isInteger(params.width))){errors.push("width must be a positive integer");}if(params.height!==undefined&&(typeof params.height!=="number"||params.height<=0||!Number.isInteger(params.height))){errors.push("height must be a positive integer");}if(params.steps!==undefined&&(typeof params.steps!=="number"||params.steps<=0||!Number.isInteger(params.steps))){errors.push("steps must be a positive integer");}if(params.seed!==undefined&&(typeof params.seed!=="number"||!Number.isInteger(params.seed))){errors.push("seed must be an integer");}if(params.guidance_scale!==undefined&&(typeof params.guidance_scale!=="number"||params.guidance_scale<=0)){errors.push("guidance_scale must be a positive number");}if(params.model!==undefined&&typeof params.model!=="string"){errors.push("model must be a string");}if(params.sampler!==undefined&&typeof params.sampler!=="string"){errors.push("sampler must be a string");}if(params.random_string!==undefined&&typeof params.random_string!=="string"){errors.push("random_string must be a string");}return {valid:errors.length===0,errors}}
function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true});}else {obj[key]=value;}return obj}class DrawThingsService{setBaseUrl(url){this.baseUrl=url;this.axios.defaults.baseURL=url;console.error(`Updated API base URL to: ${url}`);}async checkApiConnection(){try{console.error(`Checking API connection to: ${this.baseUrl}`);const response=await this.axios.get("/sdapi/v1/options",{timeout:5e3,validateStatus:status=>status>=200});const isConnected=response.status>=200;console.error(`API connection check: ${isConnected?"Success":"Failed"}`);return isConnected}catch(error){console.error(`API connection check failed: ${error.message}`);return false}}async saveImage({base64Data,outputPath,fileName}){const __filename=fileURLToPath(import.meta.url);const __dirname=path.dirname(__filename);const projectRoot=path.resolve(__dirname,"..");try{const timestamp=new Date().toISOString().replace(/[:.]/g,"-");const defaultFileName=fileName||`generated-image-${timestamp}.png`;const defaultImagesDir=path.resolve(projectRoot,"..","images");const finalOutputPath=outputPath||path.join(defaultImagesDir,defaultFileName);const imagesDir=path.dirname(finalOutputPath);if(!fs.existsSync(imagesDir)){await fs.promises.mkdir(imagesDir,{recursive:true});}const cleanBase64=base64Data.replace(/^data:image\/\w+;base64,/,"");const buffer=Buffer.from(cleanBase64,"base64");const absolutePath=path.resolve(finalOutputPath);await fs.promises.writeFile(absolutePath,buffer);return absolutePath}catch(error){console.error(`Failed to save image: ${error instanceof Error?error.message:String(error)}`);if(error instanceof Error){console.error(error.stack||"No stack trace available");}throw error}}getDefaultParams(){return defaultParams}async generateImage(inputParams={}){try{let params={};try{const validationResult=validateImageGenerationParams(inputParams);if(validationResult.valid){params=inputParams;}else {console.error("parameter validation failed, use default params");}}catch(error){console.error("parameter validation error:",error);}if(params.random_string&&(!params.prompt||Object.keys(params).length===1)){params.prompt=params.random_string;delete params.random_string;}if(!params.prompt){params.prompt=inputParams.prompt||defaultParams.prompt;}var _params_seed;const requestParams={...defaultParams,...params,seed:(_params_seed=params.seed)!==null&&_params_seed!==void 0?_params_seed:Math.floor(Math.random()*0x7fffffff)};console.error(`use prompt: "${requestParams.prompt}"`);console.error("send request to Draw Things API...");const response=await this.axios.post("/sdapi/v1/txt2img",requestParams);if(!response.data||!response.data.images||response.data.images.length===0){throw new Error("API did not return image data")}const imageData=response.data.images[0];const formattedImageData=imageData.startsWith("data:image/")?imageData:`data:image/png;base64,${imageData}`;console.error("image generation success");const startTime=Date.now()-2e3;const endTime=Date.now();const timestamp=new Date().toISOString().replace(/[:.]/g,"-");const defaultFileName=`generated-image-${timestamp}.png`;const imagePath=await this.saveImage({base64Data:formattedImageData,fileName:defaultFileName});return {isError:false,imageData:formattedImageData,imagePath:imagePath,metadata:{alt:`Image generated from prompt: ${requestParams.prompt}`,inference_time_ms:endTime-startTime}}}catch(error){console.error("image generation error:",error);let errorMessage="unknown error";if(error instanceof Error){errorMessage=error.message;}const axiosError=error;if(axiosError.response){var _axiosError_response_data;errorMessage=`API error: ${axiosError.response.status} - ${((_axiosError_response_data=axiosError.response.data)===null||_axiosError_response_data===void 0?void 0:_axiosError_response_data.error)||axiosError.message}`;}else if(axiosError.code==="ECONNREFUSED"){errorMessage="cannot connect to Draw Things API. please ensure Draw Things is running and API is enabled.";}else if(axiosError.code==="ETIMEDOUT"){errorMessage="connection to Draw Things API timeout. image generation may take longer, or API not responding.";}return {isError:true,errorMessage}}}constructor(apiUrl="http://127.0.0.1:7888"){_define_property(this,"baseUrl",void 0);_define_property(this,"axios",void 0);this.baseUrl=apiUrl;this.axios=axios.create({baseURL:this.baseUrl,timeout:3e5,headers:{"Content-Type":"application/json",Accept:"application/json"}});console.error(`DrawThingsService initialized, API location: ${this.baseUrl}`);}}
const DEBUG_MODE=process.env.DEBUG_MODE==="true";const __filename=fileURLToPath(import.meta.url);const __dirname=path.dirname(__filename);const projectRoot=path.resolve(__dirname,"..");const logsDir=path.join(projectRoot,"logs");try{if(!fs.existsSync(logsDir)){fs.mkdirSync(logsDir,{recursive:true});console.error(`Created logs directory: ${logsDir}`);}}catch(error){console.error(`Failed to create logs directory: ${error instanceof Error?error.message:String(error)}`);}const logFile=path.join(logsDir,"draw-things-mcp.log");function log(message){const timestamp=new Date().toISOString();const logMessage=`${timestamp} - ${message}
`;try{fs.appendFileSync(logFile,logMessage);console.error(logMessage);}catch(error){console.error(`Failed to write to log file: ${error instanceof Error?error.message:String(error)}`);}}async function logError(error){try{const errorLogFile=path.join(logsDir,"error.log");const timestamp=new Date().toISOString();const errorDetails=error instanceof Error?`${error.message}
${error.stack}`:String(error);const errorLog=`${timestamp} - ERROR: ${errorDetails}
`;try{await fs.promises.appendFile(errorLogFile,errorLog);log(`Error logged to ${errorLogFile}`);if(DEBUG_MODE){console.error(`
[DEBUG] FULL ERROR DETAILS:
${errorDetails}
`);}}catch(writeError){try{fs.appendFileSync(errorLogFile,errorLog);}catch(syncWriteError){console.error(`Failed to write to error log: ${syncWriteError instanceof Error?syncWriteError.message:String(syncWriteError)}`);console.error(`Original error: ${errorDetails}`);}}}catch(logError){console.error("Critical error in error logging system:");console.error(logError);console.error("Original error:");console.error(error);}}function printConnectionInfo(){const infoText=`
---------------------------------------------
| Draw Things MCP - Image Generation Service |
---------------------------------------------
Attempting to connect to Draw Things API at:
http://127.0.0.1:7888
TROUBLESHOOTING TIPS:
1. Ensure Draw Things is running on your computer
2. Make sure the API is enabled in Draw Things settings
3. If you changed the default port in Draw Things, set the environment variable:
DRAW_THINGS_API_URL=http://127.0.0.1:YOUR_PORT
Starting service...
`;log(infoText);}const drawThingsService=new DrawThingsService;const server=new McpServer({name:"draw-things-mcp",version:"1.0.0"});const paramsSchema={prompt:z.string().optional(),negative_prompt:z.string().optional(),width:z.number().optional(),height:z.number().optional(),steps:z.number().optional(),seed:z.number().optional(),guidance_scale:z.number().optional(),random_string:z.string().optional()};server.tool("generateImage","Generate an image based on a prompt",paramsSchema,async mcpParams=>{try{var _mcpParams_params,_result_metadata;log("Received image generation request");log(`mcpParams====== ${JSON.stringify(mcpParams)}`);const parameters=(mcpParams===null||mcpParams===void 0?void 0:(_mcpParams_params=mcpParams.params)===null||_mcpParams_params===void 0?void 0:_mcpParams_params.arguments)||(mcpParams===null||mcpParams===void 0?void 0:mcpParams.arguments)||mcpParams||{};if(parameters.prompt){log(`Using provided prompt: ${parameters.prompt}`);}else {log("No prompt provided, using default");parameters.prompt="A cute dog";}const result=await drawThingsService.generateImage(parameters);if(result.isError){log(`Error generating image: ${result.errorMessage}`);throw new Error(result.errorMessage||"Unknown error")}if(!result.imageData&&(!result.images||result.images.length===0)){log("No image data returned from generation");throw new Error("No image data returned from generation")}const imageData=result.imageData||(result.images&&result.images.length>0?result.images[0]:undefined);if(!imageData){log("No valid image data available");throw new Error("No valid image data available")}log("Successfully generated image, returning directly via MCP");const startTime=Date.now()-2e3;const endTime=Date.now();const responseData={image_paths:result.imagePath?[result.imagePath]:[],metadata:{alt:`Image generated from prompt: ${parameters.prompt}`,inference_time_ms:((_result_metadata=result.metadata)===null||_result_metadata===void 0?void 0:_result_metadata.inference_time_ms)||endTime-startTime}};return {content:[{type:"text",text:JSON.stringify(responseData,null,2)}]}}catch(error){log(`Error handling image generation: ${error instanceof Error?error.message:String(error)}`);await logError(error);throw error}});async function main(){try{log("Starting Draw Things MCP service...");printConnectionInfo();log("Initializing Draw Things MCP service");log("Checking Draw Things API connection before starting service...");const apiPort=process.env.DRAW_THINGS_API_PORT||7888;const isApiConnected=await drawThingsService.checkApiConnection();if(!isApiConnected){log("\nFAILED TO CONNECT TO DRAW THINGS API");log("Please make sure Draw Things is running and the API is enabled.");log("The service will continue running, but image generation will not work until the API is available.\n");}else {log("\nSUCCESSFULLY CONNECTED TO DRAW THINGS API");log("The service is ready to generate images.\n");drawThingsService.setBaseUrl(`http://127.0.0.1:${apiPort}`);}log("Creating transport and connecting server...");const transport=new StdioServerTransport;log("Connecting server to transport...");await server.connect(transport);log("MCP Server started successfully!");}catch(error){log(`Error in main program: ${error instanceof Error?error.message:String(error)}`);await logError(error);}}main().catch(async error=>{log("server.log",`${new Date().toISOString()} - ${error.stack||error}
`);console.error(error);process.exit(1);});