#
tokens: 27218/50000 20/20 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── .npmignore
├── .swcrc
├── build.mjs
├── CURSOR_INTEGRATION.md
├── cursor-mcp-bridge.js
├── cursor-setup.md
├── dist
│   └── index.js
├── package-lock.json
├── package.json
├── README.md
├── rollup.config.mjs
├── src
│   ├── index.ts
│   ├── interfaces
│   │   ├── http.ts
│   │   ├── imageGeneration.ts
│   │   ├── index.ts
│   │   └── jsonRpc.ts
│   └── services
│       ├── defaultParams.ts
│       ├── drawThingsService.ts
│       └── schemas.ts
├── start-cursor-bridge.sh
├── test-api-connection.js
├── test-mcp.js
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------

```
 1 | .git
 2 | .gitignore
 3 | .DS_Store
 4 | node_modules
 5 | *.log
 6 | .vscode
 7 | .idea
 8 | .env
 9 | *.swp
10 | *.swo 
```

--------------------------------------------------------------------------------
/.swcrc:
--------------------------------------------------------------------------------

```
 1 | {
 2 |   "$schema": "https://json.schemastore.org/swcrc",
 3 |   "jsc": {
 4 |     "parser": {
 5 |       "syntax": "typescript",
 6 |       "dynamicImport": true,
 7 |       "decorators": true
 8 |     },
 9 |     "transform": {
10 |       "legacyDecorator": true,
11 |       "decoratorMetadata": true
12 |     },
13 |     "target": "es2018",
14 |     "loose": false
15 |   },
16 |   "module": {
17 |     "type": "es6"
18 |   },
19 |   "sourceMaps": false,
20 |   "minify": true
21 | } 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
  1 | # Logs
  2 | 
  3 | logs
  4 | _.log
  5 | npm-debug.log_
  6 | yarn-debug.log*
  7 | yarn-error.log*
  8 | lerna-debug.log*
  9 | .pnpm-debug.log*
 10 | 
 11 | # Diagnostic reports (https://nodejs.org/api/report.html)
 12 | 
 13 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
 14 | 
 15 | # Runtime data
 16 | 
 17 | pids
 18 | _.pid
 19 | _.seed
 20 | \*.pid.lock
 21 | 
 22 | # Directory for instrumented libs generated by jscoverage/JSCover
 23 | 
 24 | lib-cov
 25 | 
 26 | # Coverage directory used by tools like istanbul
 27 | 
 28 | coverage
 29 | \*.lcov
 30 | 
 31 | # nyc test coverage
 32 | 
 33 | .nyc_output
 34 | 
 35 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
 36 | 
 37 | .grunt
 38 | 
 39 | # Bower dependency directory (https://bower.io/)
 40 | 
 41 | bower_components
 42 | 
 43 | # node-waf configuration
 44 | 
 45 | .lock-wscript
 46 | 
 47 | # Compiled binary addons (https://nodejs.org/api/addons.html)
 48 | 
 49 | build/Release
 50 | 
 51 | # Dependency directories
 52 | 
 53 | node_modules/
 54 | jspm_packages/
 55 | 
 56 | # Snowpack dependency directory (https://snowpack.dev/)
 57 | 
 58 | web_modules/
 59 | 
 60 | # TypeScript cache
 61 | 
 62 | \*.tsbuildinfo
 63 | 
 64 | # Optional npm cache directory
 65 | 
 66 | .npm
 67 | 
 68 | # Optional eslint cache
 69 | 
 70 | .eslintcache
 71 | 
 72 | # Optional stylelint cache
 73 | 
 74 | .stylelintcache
 75 | 
 76 | # Microbundle cache
 77 | 
 78 | .rpt2_cache/
 79 | .rts2_cache_cjs/
 80 | .rts2_cache_es/
 81 | .rts2_cache_umd/
 82 | 
 83 | # Optional REPL history
 84 | 
 85 | .node_repl_history
 86 | 
 87 | # Output of 'npm pack'
 88 | 
 89 | \*.tgz
 90 | 
 91 | # Yarn Integrity file
 92 | 
 93 | .yarn-integrity
 94 | 
 95 | # dotenv environment variable files
 96 | 
 97 | .env
 98 | .env.development.local
 99 | .env.test.local
100 | .env.production.local
101 | .env.local
102 | 
103 | # parcel-bundler cache (https://parceljs.org/)
104 | 
105 | .cache
106 | .parcel-cache
107 | 
108 | # Next.js build output
109 | 
110 | .next
111 | out
112 | 
113 | # Nuxt.js build / generate output
114 | 
115 | .nuxt
116 | 
117 | # Gatsby files
118 | 
119 | .cache/
120 | 
121 | # Comment in the public line in if your project uses Gatsby and not Next.js
122 | 
123 | # https://nextjs.org/blog/next-9-1#public-directory-support
124 | 
125 | # public
126 | 
127 | # vuepress build output
128 | 
129 | .vuepress/dist
130 | 
131 | # vuepress v2.x temp and cache directory
132 | 
133 | .temp
134 | .cache
135 | 
136 | # Docusaurus cache and generated files
137 | 
138 | .docusaurus
139 | 
140 | # Serverless directories
141 | 
142 | .serverless/
143 | 
144 | # FuseBox cache
145 | 
146 | .fusebox/
147 | 
148 | # DynamoDB Local files
149 | 
150 | .dynamodb/
151 | 
152 | # TernJS port file
153 | 
154 | .tern-port
155 | 
156 | # Stores VSCode versions used for testing VSCode extensions
157 | 
158 | .vscode-test
159 | 
160 | # yarn v2
161 | 
162 | .yarn/cache
163 | .yarn/unplugged
164 | .yarn/build-state.yml
165 | .yarn/install-state.gz
166 | .pnp.\*
167 | 
168 | # wrangler project
169 | 
170 | .dev.vars
171 | .wrangler/
172 | .specstory
173 | images/
174 | .cursor
175 | test-output
176 | cursor-mcp-bridge.log
177 | cursor-mcp-bridge.log.old
178 | draw-things-mcp.log
179 | draw-things-mcp.log.old
180 | *.log
181 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Draw Things MCP
  2 | 
  3 | Draw Things API integration for Cursor using Model Context Protocol (MCP).
  4 | 
  5 | ## Prerequisites
  6 | 
  7 | - Node.js >= 14.0.0
  8 | - Draw Things API running on http://127.0.0.1:7888
  9 | 
 10 | ## Installation
 11 | 
 12 | ```bash
 13 | # Install globally
 14 | npm install -g draw-things-mcp-cursor
 15 | 
 16 | # Or run directly
 17 | npx draw-things-mcp-cursor
 18 | ```
 19 | 
 20 | ## Cursor Integration
 21 | 
 22 | To set up this tool in Cursor, see the detailed guide in [cursor-setup.md](./cursor-setup.md).
 23 | 
 24 | Quick setup:
 25 | 
 26 | 1. Create or edit `~/.cursor/claude_desktop_config.json`:
 27 | ```json
 28 | {
 29 |   "mcpServers": {
 30 |     "draw-things": {
 31 |       "command": "draw-things-mcp-cursor",
 32 |       "args": []
 33 |     }
 34 |   }
 35 | }
 36 | ```
 37 | 
 38 | 2. Restart Cursor
 39 | 3. Use in Cursor: `generateImage({"prompt": "a cute cat"})`
 40 | 
 41 | ## CLI Usage
 42 | 
 43 | ### Generate Image
 44 | 
 45 | ```bash
 46 | echo '{"prompt": "your prompt here"}' | npx draw-things-mcp-cursor
 47 | ```
 48 | 
 49 | ### Parameters
 50 | 
 51 | - `prompt`: The text prompt for image generation (required)
 52 | - `negative_prompt`: The negative prompt for image generation
 53 | - `width`: Image width (default: 360)
 54 | - `height`: Image height (default: 360)
 55 | - `steps`: Number of steps for generation (default: 8)
 56 | - `model`: Model to use for generation (default: "flux_1_schnell_q5p.ckpt")
 57 | - `sampler`: Sampling method (default: "DPM++ 2M AYS")
 58 | 
 59 | Example:
 60 | 
 61 | ```bash
 62 | echo '{
 63 |   "prompt": "a happy smiling dog, professional photography",
 64 |   "negative_prompt": "ugly, deformed, blurry",
 65 |   "width": 360,
 66 |   "height": 360,
 67 |   "steps": 4
 68 | }' | npx draw-things-mcp-cursor
 69 | ```
 70 | 
 71 | ### MCP Tool Integration
 72 | 
 73 | When used as an MCP tool in Cursor, the tool will be registered as `generateImage` with the following parameters:
 74 | 
 75 | ```typescript
 76 | {
 77 |   prompt: string;       // Required - The prompt to generate the image from
 78 |   negative_prompt?: string;  // Optional - The negative prompt
 79 |   width?: number;       // Optional - Image width (default: 360)
 80 |   height?: number;      // Optional - Image height (default: 360)
 81 |   model?: string;       // Optional - Model name
 82 |   steps?: number;       // Optional - Number of steps (default: 8)
 83 | }
 84 | ```
 85 | 
 86 | The generated images will be saved in the `images` directory with a filename format of:
 87 | `<sanitized_prompt>_<timestamp>.png`
 88 | 
 89 | ## Response Format
 90 | 
 91 | Success:
 92 | ```json
 93 | {
 94 |   "type": "success",
 95 |   "content": [{
 96 |     "type": "image",
 97 |     "data": "base64 encoded image data",
 98 |     "mimeType": "image/png"
 99 |   }],
100 |   "metadata": {
101 |     "parameters": { ... }
102 |   }
103 | }
104 | ```
105 | 
106 | Error:
107 | ```json
108 | {
109 |   "type": "error",
110 |   "error": "error message",
111 |   "code": 500
112 | }
113 | ```
114 | 
115 | ## Troubleshooting
116 | 
117 | If you encounter issues:
118 | 
119 | - Ensure Draw Things API is running at http://127.0.0.1:7888
120 | - Check log files in `~/.cursor/logs` if using with Cursor
121 | - Make sure src/index.js has execution permissions: `chmod +x src/index.js`
122 | 
123 | ## License
124 | 
125 | MIT 
```

--------------------------------------------------------------------------------
/src/interfaces/jsonRpc.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * JSON-RPC 2.0 相關的介面定義
 3 |  */
 4 | 
 5 | /**
 6 |  * JSON-RPC 2.0 請求格式
 7 |  */
 8 | export interface JsonRpcRequest {
 9 |   jsonrpc: string;
10 |   id: string;
11 |   method: string;
12 |   params?: {
13 |     tool: string;
14 |     parameters: any;
15 |   };
16 |   prompt?: string;
17 |   parameters?: any;
18 | }
19 | 
20 | /**
21 |  * JSON-RPC 2.0 回應格式
22 |  */
23 | export interface JsonRpcResponse {
24 |   jsonrpc: string;
25 |   id: string;
26 |   result?: any;
27 |   error?: {
28 |     code: number;
29 |     message: string;
30 |     data?: any;
31 |   };
32 | } 
```

--------------------------------------------------------------------------------
/rollup.config.mjs:
--------------------------------------------------------------------------------

```
 1 | import { nodeResolve } from '@rollup/plugin-node-resolve';
 2 | 
 3 | export default {
 4 |   input: 'dist-temp/index.js', // SWC translated entry file
 5 |   output: {
 6 |     file: 'dist/index.js',
 7 |     format: 'es',  // keep ES Module format
 8 |     sourcemap: false,
 9 |   },
10 |   external: [
11 |     // external dependencies, not packaged into the final file
12 |     /@modelcontextprotocol\/.*/,
13 |     'axios',
14 |     'zod',
15 |     'path',
16 |     'fs',
17 |     'os',
18 |     'url',
19 |     'util',
20 |     'node:fs',
21 |     'node:path',
22 |     'node:os',
23 |     'node:url',
24 |     'node:util'
25 |   ],
26 |   plugins: [
27 |     nodeResolve({
28 |       exportConditions: ['node'],
29 |       preferBuiltins: true,
30 |     }),
31 |   ]
32 | }; 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2018",
 4 |     "module": "nodenext",
 5 |     "moduleResolution": "nodenext",
 6 |     "esModuleInterop": true,
 7 |     "allowSyntheticDefaultImports": true,
 8 |     "strict": true,
 9 |     "forceConsistentCasingInFileNames": true,
10 |     "skipLibCheck": true,
11 |     "outDir": "dist",
12 |     "sourceMap": false,
13 |     "rootDir": "src",
14 |     "declaration": false,
15 |     "resolveJsonModule": true,
16 |     "lib": ["ES2018"],
17 |     "types": ["node", "jsdom"],
18 |     "moduleSuffixes": ["js", "ts"],
19 |     "baseUrl": ".",
20 |     "paths": {
21 |       "@/*": ["src/*"]
22 |     },
23 |     "preserveSymlinks": true,
24 |     "allowJs": true,
25 |     "removeComments": true
26 | 
27 |   },
28 |   "include": ["src/**/*"],
29 |   "exclude": ["node_modules", "dist"]
30 | } 
```

--------------------------------------------------------------------------------
/build.mjs:
--------------------------------------------------------------------------------

```
 1 | #!/usr/bin/env node
 2 | 
 3 | import { execSync } from 'child_process';
 4 | import fs from 'fs';
 5 | import path from 'path';
 6 | 
 7 | // create temporary directory
 8 | const tempDir = 'dist-temp';
 9 | if (!fs.existsSync(tempDir)) {
10 |   fs.mkdirSync(tempDir);
11 | }
12 | 
13 | try {
14 |   // first step: use SWC to translate TypeScript to JavaScript (keep the original build command logic)
15 |   console.log('step 1: use SWC to translate TypeScript to JavaScript...');
16 |   execSync(`rimraf ${tempDir} && swc src -d ${tempDir} --strip-leading-paths`, { 
17 |     stdio: 'inherit' 
18 |   });
19 | 
20 |   // second step: use Rollup to package as a single file
21 |   console.log('step 2: use Rollup to package as a single file...');
22 |   execSync('rimraf dist && rollup -c', { 
23 |     stdio: 'inherit' 
24 |   });
25 | 
26 |   // third step: ensure dist/index.js has execution permission (because it is a bin file)
27 |   console.log('step 3: set execution permission...');
28 |   fs.chmodSync('dist/index.js', '755');
29 | 
30 |   // fourth step: clean temporary directory
31 |   console.log('step 4: clean temporary directory...');
32 |   execSync(`rimraf ${tempDir}`, { 
33 |     stdio: 'inherit' 
34 |   });
35 | 
36 |   console.log('build completed! output: dist/index.js');
37 | } catch (error) {
38 |   console.error('error occurred during the build process:', error);
39 |   process.exit(1);
40 | } 
```

--------------------------------------------------------------------------------
/src/interfaces/imageGeneration.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * images generation interfaces
 3 |  */
 4 | 
 5 | /**
 6 |  * image response format
 7 |  */
 8 | export interface ImageResponse {
 9 |   content: Array<{
10 |     base64: string;
11 |     path: string;
12 |     prompt: string;
13 |     negative_prompt?: string;
14 |     seed: number;
15 |     width: number;
16 |     height: number;
17 |     meta: Record<string, any>;
18 |   }>;
19 |   imageSavedPath?: string; // optional property, for storing image file path
20 | }
21 | 
22 | /**
23 |  * image generation parameters
24 |  */
25 | export interface ImageGenerationParameters {
26 |   prompt?: string;
27 |   negative_prompt?: string;
28 |   seed?: number;
29 |   width?: number;
30 |   height?: number;
31 |   num_inference_steps?: number;
32 |   guidance_scale?: number;
33 |   model?: string;
34 |   random_string?: string;
35 |   [key: string]: any;
36 | }
37 | 
38 | /**
39 |  * image generation result
40 |  */
41 | export interface ImageGenerationResult {
42 |   status?: number;  // changed to optional
43 |   error?: string;
44 |   images?: string[];
45 |   imageData?: string;
46 |   isError?: boolean;
47 |   errorMessage?: string;
48 | }
49 | 
50 | /**
51 |  * Draw Things service generation result
52 |  */
53 | export interface DrawThingsGenerationResult {
54 |   isError: boolean;
55 |   imageData?: string;
56 |   errorMessage?: string;
57 |   parameters?: Record<string, any>;
58 |   status?: number; // added property to compatible with ImageGenerationResult
59 |   images?: string[]; // added property to compatible with ImageGenerationResult
60 |   error?: string;    // added property to compatible with ImageGenerationResult
61 |   imagePath?: string; // added property to store the path of the generated image
62 |   metadata?: {
63 |     alt: string;
64 |     inference_time_ms: number;
65 |   }; // added metadata
66 | } 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 | 	"name": "draw-things-mcp-cursor",
 3 | 	"version": "1.4.3",
 4 | 	"description": "Draw Things API integration for Cursor using Model Context Protocol (MCP)",
 5 | 	"private": false,
 6 | 	"type": "module",
 7 | 	"main": "dist/index.js",
 8 | 	"bin": {
 9 | 		"draw-things-mcp-cursor": "./dist/index.js"
10 | 	},
11 | 	"scripts": {
12 | 		"start": "node --experimental-vm-modules --no-warnings dist/index.js",
13 | 		"dev": "NODE_OPTIONS='--loader ts-node/esm' ts-node src/index.ts",
14 | 		"build": "node build.mjs",
15 | 		"test": "node --experimental-vm-modules --no-warnings test-mcp.js",
16 | 		"prepare": "npm run build",
17 | 		"prepublishOnly": "npm run build",
18 | 		"typecheck": "tsc --noEmit"
19 | 	},
20 | 	"dependencies": {
21 | 		"@modelcontextprotocol/sdk": "^1.7.0",
22 | 		"axios": "^1.8.0",
23 | 		"zod": "^3.24.2"
24 | 	},
25 | 	"devDependencies": {
26 | 		"@rollup/plugin-node-resolve": "^16.0.0",
27 | 		"@swc/cli": "^0.3.8",
28 | 		"@swc/core": "^1.4.8",
29 | 		"@swc/helpers": "^0.5.6",
30 | 		"@types/node": "^20.17.19",
31 | 		"rimraf": "^5.0.10",
32 | 		"rollup": "^4.34.9",
33 | 		"ts-node": "^10.9.2",
34 | 		"typescript": "^5.3.3",
35 | 		"zod-to-json-schema": "3.20.3"
36 | 	},
37 | 	"overrides": {
38 | 		"zod-to-json-schema": "3.20.3"
39 | 	},
40 | 	"resolutions": {
41 | 		"zod-to-json-schema": "3.20.3"
42 | 	},
43 | 	"files": [
44 | 		"dist",
45 | 		"README.md",
46 | 		"cursor-setup.md"
47 | 	],
48 | 	"keywords": [
49 | 		"cursor",
50 | 		"mcp",
51 | 		"draw-things",
52 | 		"ai",
53 | 		"image-generation",
54 | 		"stable-diffusion"
55 | 	],
56 | 	"author": "jaokuohsuan",
57 | 	"license": "MIT",
58 | 	"repository": {
59 | 		"type": "git",
60 | 		"url": "https://github.com/jaokuohsuan/draw-things-mcp"
61 | 	},
62 | 	"bugs": {
63 | 		"url": "https://github.com/jaokuohsuan/draw-things-mcp/issues"
64 | 	},
65 | 	"homepage": "https://github.com/jaokuohsuan/draw-things-mcp#readme",
66 | 	"engines": {
67 | 		"node": ">=16.0.0"
68 | 	},
69 | 	"packageManager": "[email protected]+sha512.c8180b3fbe4e4bca02c94234717896b5529740a6cbadf19fa78254270403ea2f27d4e1d46a08a0f56c89b63dc8ebfd3ee53326da720273794e6200fcf0d184ab"
70 | }
71 | 
```

--------------------------------------------------------------------------------
/src/services/defaultParams.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ImageGenerationParams } from './schemas.js';
 2 | 
 3 | // Default parameters for image generation
 4 | export const defaultParams: ImageGenerationParams = {
 5 |   speed_up_with_guidance_embed: true,
 6 |   motion_scale: 127,
 7 |   image_guidance: 1.5,
 8 |   tiled_decoding: false,
 9 |   decoding_tile_height: 640,
10 |   negative_prompt_for_image_prior: true,
11 |   batch_size: 1,
12 |   decoding_tile_overlap: 128,
13 |   separate_open_clip_g: false,
14 |   hires_fix_height: 960,
15 |   decoding_tile_width: 640,
16 |   diffusion_tile_height: 1024,
17 |   num_frames: 14,
18 |   stage_2_guidance: 1,
19 |   t5_text_encoder_decoding: true,
20 |   mask_blur_outset: 0,
21 |   resolution_dependent_shift: true,
22 |   model: "flux_1_schnell_q5p.ckpt",
23 |   hires_fix: false,
24 |   strength: 1,
25 |   loras: [],
26 |   diffusion_tile_width: 1024,
27 |   diffusion_tile_overlap: 128,
28 |   original_width: 512,
29 |   seed: -1,
30 |   zero_negative_prompt: false,
31 |   upscaler_scale: 0,
32 |   steps: 8,
33 |   upscaler: null,
34 |   mask_blur: 1.5,
35 |   sampler: "DPM++ 2M AYS",
36 |   width: 320,
37 |   negative_original_width: 512,
38 |   batch_count: 1,
39 |   refiner_model: null,
40 |   shift: 1,
41 |   stage_2_shift: 1,
42 |   open_clip_g_text: null,
43 |   crop_left: 0,
44 |   controls: [],
45 |   start_frame_guidance: 1,
46 |   original_height: 512,
47 |   image_prior_steps: 5,
48 |   guiding_frame_noise: 0.019999999552965164,
49 |   clip_weight: 1,
50 |   clip_skip: 1,
51 |   crop_top: 0,
52 |   negative_original_height: 512,
53 |   preserve_original_after_inpaint: true,
54 |   separate_clip_l: false,
55 |   guidance_embed: 3.5,
56 |   negative_aesthetic_score: 2.5,
57 |   aesthetic_score: 6,
58 |   clip_l_text: null,
59 |   hires_fix_strength: 0.699999988079071,
60 |   guidance_scale: 7.5,
61 |   stochastic_sampling_gamma: 0.3,
62 |   seed_mode: "Scale Alike",
63 |   target_width: 512,
64 |   hires_fix_width: 960,
65 |   tiled_diffusion: false,
66 |   fps: 5,
67 |   refiner_start: 0.8500000238418579,
68 |   height: 512,
69 |   prompt: "A cute koala sitting on a eucalyptus tree, watercolor style, beautiful lighting, detailed",
70 |   negative_prompt: "deformed, distorted, unnatural pose, extra limbs, blurry, low quality, ugly, bad anatomy, poor details, mutated, text, watermark"
71 | }; 
```

--------------------------------------------------------------------------------
/cursor-setup.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Cursor MCP Tool Setup Guide
  2 | 
  3 | ## Setting Up Draw Things MCP Tool in Cursor
  4 | 
  5 | This guide will help you set up and use the Draw Things MCP tool in Cursor editor, allowing you to generate AI images directly within Cursor.
  6 | 
  7 | ### Prerequisites
  8 | 
  9 | - Ensure Draw Things API is running (http://127.0.0.1:7888)
 10 | - Node.js v14.0.0 or higher
 11 | 
 12 | ### 1. Install the Package
 13 | 
 14 | #### Local Development Mode
 15 | 
 16 | If you're developing or modifying this tool, you can use local linking:
 17 | 
 18 | ```bash
 19 | # In the project directory
 20 | npm link
 21 | ```
 22 | 
 23 | #### Publishing to NPM (if needed)
 24 | 
 25 | If you want to publish this tool for others to use:
 26 | 
 27 | ```bash
 28 | npm publish
 29 | ```
 30 | 
 31 | Then install globally via npm:
 32 | 
 33 | ```bash
 34 | npm install -g draw-things-mcp-cursor
 35 | ```
 36 | 
 37 | ### 2. Create Cursor MCP Configuration File
 38 | 
 39 | You need to create or edit the `~/.cursor/claude_desktop_config.json` file to register the MCP tool with Cursor:
 40 | 
 41 | ```json
 42 | {
 43 |   "mcpServers": {
 44 |     "draw-things": {
 45 |       "command": "draw-things-mcp-cursor",
 46 |       "args": []
 47 |     }
 48 |   }
 49 | }
 50 | ```
 51 | 
 52 | #### Local Development Configuration
 53 | 
 54 | If you're developing locally, it's recommended to use an absolute path to your JS file:
 55 | 
 56 | ```json
 57 | {
 58 |   "mcpServers": {
 59 |     "draw-things": {
 60 |       "command": "node",
 61 |       "args": [
 62 |         "/Users/james_jao/m800/my-mcp/src/index.js"
 63 |       ]
 64 |     }
 65 |   }
 66 | }
 67 | ```
 68 | 
 69 | ### 3. Restart Cursor
 70 | 
 71 | After configuration, completely close and restart the Cursor editor to ensure the new MCP configuration is properly loaded.
 72 | 
 73 | ### 4. Using the MCP Tool
 74 | 
 75 | In Cursor, you can call the image generation tool when chatting with the AI assistant using the following format:
 76 | 
 77 | #### Basic Usage
 78 | 
 79 | ```
 80 | generateImage({"prompt": "a cute cat"})
 81 | ```
 82 | 
 83 | #### Advanced Usage
 84 | 
 85 | You can specify additional parameters to fine-tune the generated image:
 86 | 
 87 | ```
 88 | generateImage({
 89 |   "prompt": "a cute cat",
 90 |   "negative_prompt": "ugly, deformed",
 91 |   "width": 512,
 92 |   "height": 512,
 93 |   "steps": 4,
 94 |   "model": "flux_1_schnell_q5p.ckpt"
 95 | })
 96 | ```
 97 | 
 98 | ### Available Parameters
 99 | 
100 | | Parameter Name | Description | Default Value |
101 | |----------------|-------------|---------------|
102 | | prompt | The image generation prompt | (Required) |
103 | | negative_prompt | Elements to avoid in the image | (Empty) |
104 | | width | Image width (pixels) | 360 |
105 | | height | Image height (pixels) | 360 |
106 | | steps | Number of generation steps (higher is more detailed but slower) | 8 |
107 | | model | Model name to use | "flux_1_schnell_q5p.ckpt" |
108 | 
109 | ### Troubleshooting
110 | 
111 | If you encounter issues when setting up or using the MCP tool, check:
112 | 
113 | - Log files in the `~/.cursor/logs` directory for detailed error information
114 | - Ensure Draw Things API is started and running at http://127.0.0.1:7888
115 | - Make sure the src/index.js file has execution permissions: `chmod +x src/index.js`
116 | - Check for error messages in the terminal: `draw-things-mcp-cursor`
117 | 
118 | ### Getting Help
119 | 
120 | If you have any questions, please open an issue on the project's GitHub page:
121 | https://github.com/james-jao/draw-things-mcp/issues 
```

--------------------------------------------------------------------------------
/src/services/schemas.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * image generation params interface
  3 |  */
  4 | 
  5 | // params interface
  6 | export interface ImageGenerationParams {
  7 |   // basic params
  8 |   prompt?: string;
  9 |   negative_prompt?: string;
 10 | 
 11 |   // size params
 12 |   width?: number;
 13 |   height?: number;
 14 | 
 15 |   // generate control params
 16 |   steps?: number;
 17 |   seed?: number;
 18 |   guidance_scale?: number;
 19 | 
 20 |   // model params
 21 |   model?: string;
 22 |   sampler?: string;
 23 | 
 24 |   // MCP special params
 25 |   random_string?: string;
 26 | 
 27 |   // allow other params
 28 |   [key: string]: any;
 29 | }
 30 | 
 31 | // function for validating ImageGenerationParams
 32 | export function validateImageGenerationParams(params: any): {
 33 |   valid: boolean;
 34 |   errors: string[];
 35 | } {
 36 |   const errors: string[] = [];
 37 | 
 38 |   // check params type
 39 |   if (params.prompt !== undefined && typeof params.prompt !== "string") {
 40 |     errors.push("prompt must be a string");
 41 |   }
 42 | 
 43 |   if (
 44 |     params.negative_prompt !== undefined &&
 45 |     typeof params.negative_prompt !== "string"
 46 |   ) {
 47 |     errors.push("negative_prompt must be a string");
 48 |   }
 49 | 
 50 |   if (
 51 |     params.width !== undefined &&
 52 |     (typeof params.width !== "number" ||
 53 |       params.width <= 0 ||
 54 |       !Number.isInteger(params.width))
 55 |   ) {
 56 |     errors.push("width must be a positive integer");
 57 |   }
 58 | 
 59 |   if (
 60 |     params.height !== undefined &&
 61 |     (typeof params.height !== "number" ||
 62 |       params.height <= 0 ||
 63 |       !Number.isInteger(params.height))
 64 |   ) {
 65 |     errors.push("height must be a positive integer");
 66 |   }
 67 | 
 68 |   if (
 69 |     params.steps !== undefined &&
 70 |     (typeof params.steps !== "number" ||
 71 |       params.steps <= 0 ||
 72 |       !Number.isInteger(params.steps))
 73 |   ) {
 74 |     errors.push("steps must be a positive integer");
 75 |   }
 76 | 
 77 |   if (
 78 |     params.seed !== undefined &&
 79 |     (typeof params.seed !== "number" || !Number.isInteger(params.seed))
 80 |   ) {
 81 |     errors.push("seed must be an integer");
 82 |   }
 83 | 
 84 |   if (
 85 |     params.guidance_scale !== undefined &&
 86 |     (typeof params.guidance_scale !== "number" || params.guidance_scale <= 0)
 87 |   ) {
 88 |     errors.push("guidance_scale must be a positive number");
 89 |   }
 90 | 
 91 |   if (params.model !== undefined && typeof params.model !== "string") {
 92 |     errors.push("model must be a string");
 93 |   }
 94 | 
 95 |   if (params.sampler !== undefined && typeof params.sampler !== "string") {
 96 |     errors.push("sampler must be a string");
 97 |   }
 98 | 
 99 |   if (
100 |     params.random_string !== undefined &&
101 |     typeof params.random_string !== "string"
102 |   ) {
103 |     errors.push("random_string must be a string");
104 |   }
105 | 
106 |   return { valid: errors.length === 0, errors };
107 | }
108 | 
109 | // response interface
110 | export interface ImageGenerationResult {
111 |   status?: number;
112 |   images?: string[];
113 |   parameters?: Record<string, any>;
114 |   error?: string;
115 | }
116 | 
117 | // success response interface
118 | export interface SuccessResponse {
119 |   content: Array<{
120 |     type: "image";
121 |     data: string;
122 |     mimeType: string;
123 |   }>;
124 | }
125 | 
126 | // error response interface
127 | export interface ErrorResponse {
128 |   content: Array<{
129 |     type: "text";
130 |     text: string;
131 |   }>;
132 |   isError: true;
133 | }
134 | 
135 | // MCP response interface
136 | export type McpResponse = SuccessResponse | ErrorResponse;
137 | 
```

--------------------------------------------------------------------------------
/CURSOR_INTEGRATION.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Complete Guide for Using Draw Things MCP in Cursor
  2 | 
  3 | This documentation provides steps for correctly using Draw Things MCP service to generate images in Cursor, following the [Model Context Protocol](https://modelcontextprotocol.io/docs/concepts/transports) specification.
  4 | 
  5 | ## Background
  6 | 
  7 | When directly using the `mcp_generateImage` tool in Cursor, the following issues occur:
  8 | 
  9 | 1. Cursor sends plain text prompts instead of the correct JSON-RPC 2.0 format
 10 | 2. MCP service cannot parse this input format, resulting in the error `Unexpected token A in JSON at position 0`
 11 | 3. According to the [MCP documentation](https://docs.cursor.com/context/model-context-protocol), communication must use a specific JSON-RPC 2.0 format
 12 | 
 13 | ## Solution: Bridge Service
 14 | 
 15 | We provide a bridge service that automatically converts Cursor's plain text prompts to the correct JSON-RPC 2.0 format.
 16 | 
 17 | ### Step 1: Environment Setup
 18 | 
 19 | First, ensure you have installed these prerequisites:
 20 | 
 21 | 1. Node.js and npm
 22 | 2. Draw Things application
 23 | 3. API enabled in Draw Things (port 7888)
 24 | 
 25 | ### Step 2: Start the Bridge Service
 26 | 
 27 | We provide a startup script that easily launches the bridge service:
 28 | 
 29 | ```bash
 30 | # Grant execution permission
 31 | chmod +x start-cursor-bridge.sh
 32 | 
 33 | # Basic usage
 34 | ./start-cursor-bridge.sh
 35 | 
 36 | # Use debug mode
 37 | ./start-cursor-bridge.sh --debug
 38 | 
 39 | # View help
 40 | ./start-cursor-bridge.sh --help
 41 | ```
 42 | 
 43 | This script will:
 44 | 1. Check if the Draw Things API is available
 45 | 2. Start the bridge service
 46 | 3. Start the MCP service
 47 | 4. Connect the two services so they can communicate with each other
 48 | 
 49 | ### Step 3: Using in Cursor
 50 | 
 51 | When the bridge service is running, the following two input methods are supported when using the `mcp_generateImage` tool in Cursor:
 52 | 
 53 | 1. **Directly send English prompts** (the bridge service will automatically convert to JSON-RPC format):
 54 |    ```
 55 |    A group of adorable kittens playing together, cute, fluffy, detailed fur, warm lighting, playful mood
 56 |    ```
 57 | 
 58 | 2. **Use JSON objects** (suitable when more custom parameters are needed):
 59 |    ```json
 60 |    {
 61 |      "prompt": "A group of adorable kittens playing together, cute, fluffy, detailed fur, warm lighting, playful mood",
 62 |      "negative_prompt": "blurry, distorted, low quality",
 63 |      "seed": 12345
 64 |    }
 65 |    ```
 66 | 
 67 | ### Step 4: View Results
 68 | 
 69 | Generated images will be saved in the `images` directory. You can also check the `cursor-mcp-bridge.log` file to understand the bridge service's operation.
 70 | 
 71 | ## JSON-RPC 2.0 Format Explanation
 72 | 
 73 | According to the MCP specification, the correct request format should be:
 74 | 
 75 | ```json
 76 | {
 77 |   "jsonrpc": "2.0",
 78 |   "id": "request-123",
 79 |   "method": "mcp.invoke",
 80 |   "params": {
 81 |     "tool": "generateImage",
 82 |     "parameters": {
 83 |       "prompt": "A group of adorable kittens playing together",
 84 |       "negative_prompt": "blurry, low quality",
 85 |       "seed": 12345
 86 |     }
 87 |   }
 88 | }
 89 | ```
 90 | 
 91 | Our bridge service automatically converts simple inputs to this format.
 92 | 
 93 | ## Custom Options
 94 | 
 95 | You can modify default parameters by editing the `src/services/drawThings/defaultParams.js` file, such as:
 96 | 
 97 | - Model selection
 98 | - Image dimensions
 99 | - Sampler type
100 | - Other generation parameters
101 | 
102 | ## Troubleshooting
103 | 
104 | ### Check Logs
105 | 
106 | If you encounter problems, first check these logs:
107 | 
108 | 1. `cursor-mcp-bridge.log` - Bridge service logs
109 | 2. `cursor-mcp-debug.log` - Detailed logs when debug mode is enabled
110 | 3. `error.log` - MCP service error logs
111 | 
112 | ### Common Issues
113 | 
114 | 1. **Connection Error**: Ensure the Draw Things application is running and API is enabled (127.0.0.1:7888).
115 | 
116 | 2. **Parsing Error**: Check the format of prompts sent from Cursor. The bridge service should handle most cases, but complex JSON structures may cause issues.
117 | 
118 | 3. **Service Not Started**: Make sure both the bridge service and MCP service are running. Please use the provided startup script, which will automatically handle both services.
119 | 
120 | ## Technical Details
121 | 
122 | How the bridge service works:
123 | 
124 | 1. Receives plain text or JSON input from Cursor
125 | 2. Converts it to JSON-RPC 2.0 format compliant with MCP specifications
126 | 3. Passes the converted request to the MCP service
127 | 4. MCP service communicates with the Draw Things API
128 | 5. Receives the response and saves the generated image to the file system
129 | 
130 | ### Transport Layer
131 | 
132 | According to the MCP specification, our bridge service implements the following functions:
133 | 
134 | - Uses stdin/stdout as the transport layer
135 | - Correctly handles JSON-RPC 2.0 request/response formats
136 | - Supports error handling and logging
137 | - Automatically saves generated images
138 | 
139 | If you need more customization, you can edit the `cursor-mcp-bridge.js` file. 
```

--------------------------------------------------------------------------------
/start-cursor-bridge.sh:
--------------------------------------------------------------------------------

```bash
  1 | #!/bin/bash
  2 | 
  3 | # Cursor MCP and Draw Things Bridge Service Startup Script
  4 | 
  5 | # Ensure the program aborts on error
  6 | set -e
  7 | 
  8 | echo "========================================================"
  9 | echo "  Cursor MCP and Draw Things Bridge Service Tool  "
 10 | echo "  Image Generation Service Compliant with Model Context Protocol  "
 11 | echo "========================================================"
 12 | echo
 13 | 
 14 | # Check dependencies
 15 | command -v node >/dev/null 2>&1 || { echo "Error: Node.js is required but not installed"; exit 1; }
 16 | command -v npm >/dev/null 2>&1 || { echo "Error: npm is required but not installed"; exit 1; }
 17 | 
 18 | # Ensure script has execution permissions
 19 | chmod +x cursor-mcp-bridge.js
 20 | 
 21 | # Check if help information is needed
 22 | if [ "$1" == "--help" ] || [ "$1" == "-h" ]; then
 23 |   echo "Usage: ./start-cursor-bridge.sh [options]"
 24 |   echo
 25 |   echo "Options:"
 26 |   echo "  --help, -h     Display this help information"
 27 |   echo "  --debug        Enable additional debug output"
 28 |   echo "  --no-cleanup   Keep old log files"
 29 |   echo "  --port PORT    Specify custom port for Draw Things API (default: 7888)"
 30 |   echo
 31 |   echo "This script is used to start the Cursor MCP and Draw Things bridge service."
 32 |   echo "It will start a service that allows Cursor to generate images using plain text prompts."
 33 |   echo
 34 |   echo "Dependencies:"
 35 |   echo "  - Node.js and npm"
 36 |   echo "  - Draw Things application (must be running with API enabled)"
 37 |   echo
 38 |   exit 0
 39 | fi
 40 | 
 41 | # Parse parameters
 42 | DEBUG_MODE=false
 43 | CLEANUP=true
 44 | API_PORT=7888
 45 | 
 46 | for arg in "$@"; do
 47 |   case $arg in
 48 |     --debug)
 49 |       DEBUG_MODE=true
 50 |       shift
 51 |       ;;
 52 |     --no-cleanup)
 53 |       CLEANUP=false
 54 |       shift
 55 |       ;;
 56 |     --port=*)
 57 |       API_PORT="${arg#*=}"
 58 |       shift
 59 |       ;;
 60 |   esac
 61 | done
 62 | 
 63 | # Install dependencies
 64 | echo "Checking and installing necessary dependencies..."
 65 | npm install --quiet
 66 | 
 67 | # Clean up old logs
 68 | if [ "$CLEANUP" = true ]; then
 69 |   echo "Cleaning up old log files..."
 70 |   if [ -f cursor-mcp-bridge.log ]; then
 71 |     mv cursor-mcp-bridge.log cursor-mcp-bridge.log.old
 72 |   fi
 73 |   if [ -f draw-things-mcp.log ]; then
 74 |     mv draw-things-mcp.log draw-things-mcp.log.old
 75 |   fi
 76 | fi
 77 | 
 78 | # Ensure images directory exists
 79 | mkdir -p images
 80 | # Ensure logs directory exists
 81 | mkdir -p logs
 82 | 
 83 | echo
 84 | echo "Step 1: Checking if Draw Things API is available..."
 85 | 
 86 | # Create a simple test script to check API connection
 87 | cat > test-api.js << EOL
 88 | import http from 'http';
 89 | 
 90 | const options = {
 91 |   host: '127.0.0.1',
 92 |   port: ${API_PORT},
 93 |   path: '/sdapi/v1/options',
 94 |   method: 'GET',
 95 |   timeout: 5000,
 96 |   headers: {
 97 |     'User-Agent': 'DrawThingsMCP/1.0',
 98 |     'Accept': 'application/json'
 99 |   }
100 | };
101 | 
102 | const req = http.request(options, (res) => {
103 |   console.log('Draw Things API connection successful! Status code:', res.statusCode);
104 |   process.exit(0);
105 | });
106 | 
107 | req.on('error', (e) => {
108 |   if (e.code === 'ECONNREFUSED') {
109 |     console.error('Error: Unable to connect to Draw Things API. Make sure Draw Things application is running and API is enabled.');
110 |   } else if (e.code === 'ETIMEDOUT') {
111 |     console.error('Error: Connection to Draw Things API timed out. Make sure Draw Things application is running normally.');
112 |   } else {
113 |     console.error('Error:', e.message);
114 |   }
115 |   process.exit(1);
116 | });
117 | 
118 | req.on('timeout', () => {
119 |   console.error('Error: Connection to Draw Things API timed out. Make sure Draw Things application is running normally.');
120 |   req.destroy();
121 |   process.exit(1);
122 | });
123 | 
124 | req.end();
125 | EOL
126 | 
127 | # Run API test
128 | if node test-api.js; then
129 |   echo "Draw Things API is available, continuing to start bridge service..."
130 | else
131 |   echo
132 |   echo "Warning: Draw Things API appears to be unavailable on port ${API_PORT}."
133 |   echo "Please ensure:"
134 |   echo "1. Draw Things application is running"
135 |   echo "2. API is enabled in Draw Things settings"
136 |   echo "3. API is listening on 127.0.0.1:${API_PORT}"
137 |   echo
138 |   read -p "Continue starting the bridge service anyway? (y/n) " -n 1 -r
139 |   echo
140 |   if [[ ! $REPLY =~ ^[Yy]$ ]]; then
141 |     echo "Canceling bridge service startup."
142 |     exit 1
143 |   fi
144 | fi
145 | 
146 | # Clean up temporary files
147 | rm -f test-api.js
148 | 
149 | echo
150 | echo "Step 2: Starting Services..."
151 | echo
152 | 
153 | # Set up environment variables
154 | export DRAW_THINGS_FORCE_STAY_ALIVE=true
155 | export MCP_BRIDGE_DEDUP=true
156 | export DEBUG_MODE=$DEBUG_MODE
157 | export DRAW_THINGS_API_PORT=$API_PORT
158 | export DRAW_THINGS_API_URL="http://127.0.0.1:${API_PORT}"
159 | 
160 | # Set up debug mode
161 | if [ "$DEBUG_MODE" = true ]; then
162 |   echo "Debug mode enabled, all log output will be displayed"
163 |   echo "Starting MCP bridge service in debug mode..."
164 |   
165 |   # Start both services in debug mode
166 |   node cursor-mcp-bridge.js 2>&1 | tee -a cursor-mcp-debug.log | node src/index.js
167 | else
168 |   # Start bridge service
169 |   echo "Starting bridge service in normal mode..."
170 |   echo "All logs will be saved to cursor-mcp-bridge.log and draw-things-mcp.log"
171 |   
172 |   # Start MCP bridge service and pipe output to MCP service
173 |   node cursor-mcp-bridge.js | node src/index.js
174 | fi
175 | 
176 | echo
177 | echo "Service has ended."
178 | echo "Log files:"
179 | echo " - cursor-mcp-bridge.log"
180 | echo " - draw-things-mcp.log"
181 | echo " - logs/error.log (if errors occurred)"
182 | echo "If generation was successful, images will be saved in the images directory."
183 | 
184 | # Display service status
185 | if [ -f "images/image_$(date +%Y%m%d)*.png" ] || [ -f "images/generated-image_*.png" ]; then
186 |   echo "Images were successfully generated today!"
187 |   ls -la images/ | grep "$(date +%Y-%m-%d)"
188 | else
189 |   echo "No images generated today were found. Please check the logs for more information."
190 | fi 
```

--------------------------------------------------------------------------------
/src/services/drawThingsService.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { defaultParams } from "./defaultParams.js";
  2 | import {
  3 |   ImageGenerationParams,
  4 |   validateImageGenerationParams,
  5 | } from "./schemas.js";
  6 | import axios, { AxiosInstance } from "axios";
  7 | import fs from "fs";
  8 | import path from "path";
  9 | import { fileURLToPath } from "url";
 10 | import { DrawThingsGenerationResult } from "../../interfaces/index.js";
 11 | 
 12 | /**
 13 |  * simplified DrawThingsService
 14 |  * focus on core functionality: connect to Draw Things API and generate image
 15 |  */
 16 | export class DrawThingsService {
 17 |   // make baseUrl public for compatibility with index.ts
 18 |   public baseUrl: string;
 19 |   // change to public axios for compatibility
 20 |   public axios: AxiosInstance;
 21 | 
 22 |   constructor(apiUrl = "http://127.0.0.1:7888") {
 23 |     this.baseUrl = apiUrl;
 24 | 
 25 |     // initialize axios
 26 |     this.axios = axios.create({
 27 |       baseURL: this.baseUrl,
 28 |       timeout: 300000, // 5 minutes timeout (image generation may take time)
 29 |       headers: {
 30 |         "Content-Type": "application/json",
 31 |         Accept: "application/json",
 32 |       },
 33 |     });
 34 | 
 35 |     // log initialization
 36 |     console.error(
 37 |       `DrawThingsService initialized, API location: ${this.baseUrl}`
 38 |     );
 39 |   }
 40 | 
 41 |   /**
 42 |    * Set new base URL and update axios instance
 43 |    * @param url new base URL
 44 |    */
 45 |   setBaseUrl(url: string): void {
 46 |     this.baseUrl = url;
 47 |     this.axios.defaults.baseURL = url;
 48 |     console.error(`Updated API base URL to: ${url}`);
 49 |   }
 50 | 
 51 |   /**
 52 |    * check API connection
 53 |    * simplified version that just checks if API is available
 54 |    */
 55 |   async checkApiConnection(): Promise<boolean> {
 56 |     try {
 57 |       console.error(`Checking API connection to: ${this.baseUrl}`);
 58 | 
 59 |       // Try simple endpoint with short timeout
 60 |       const response = await this.axios.get("/sdapi/v1/options", {
 61 |         timeout: 5000,
 62 |         validateStatus: (status) => status >= 200,
 63 |       });
 64 | 
 65 |       const isConnected = response.status >= 200;
 66 |       console.error(
 67 |         `API connection check: ${isConnected ? "Success" : "Failed"}`
 68 |       );
 69 |       return isConnected;
 70 |     } catch (error) {
 71 |       console.error(`API connection check failed: ${(error as Error).message}`);
 72 |       return false;
 73 |     }
 74 |   }
 75 | 
 76 |   // Helper function to save images to the file system
 77 |   async saveImage({
 78 |     base64Data,
 79 |     outputPath,
 80 |     fileName
 81 |   }: {
 82 |     base64Data: string;
 83 |     outputPath?: string;
 84 |     fileName?: string;
 85 |   }): Promise<string> {
 86 |     const __filename = fileURLToPath(import.meta.url);
 87 |     // Get directory name
 88 |     const __dirname = path.dirname(__filename);
 89 |     const projectRoot: string = path.resolve(__dirname, "..");
 90 |     
 91 |     try {
 92 |       // if no output path provided, use default path
 93 |       const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
 94 |       const defaultFileName = fileName || `generated-image-${timestamp}.png`;
 95 |       const defaultImagesDir = path.resolve(projectRoot, "..", "images");
 96 |       const finalOutputPath = outputPath || path.join(defaultImagesDir, defaultFileName);
 97 |       
 98 |       // ensure the images directory exists
 99 |       const imagesDir = path.dirname(finalOutputPath);
100 |       if (!fs.existsSync(imagesDir)) {
101 |         await fs.promises.mkdir(imagesDir, { recursive: true });
102 |       }
103 | 
104 |       const cleanBase64 = base64Data.replace(/^data:image\/\w+;base64,/, "");
105 |       const buffer = Buffer.from(cleanBase64, "base64");
106 | 
107 |       const absolutePath = path.resolve(finalOutputPath);
108 |       await fs.promises.writeFile(absolutePath, buffer);
109 |       return absolutePath;
110 |     } catch (error) {
111 |       console.error(
112 |         `Failed to save image: ${
113 |           error instanceof Error ? error.message : String(error)
114 |         }`
115 |       );
116 |       if (error instanceof Error) {
117 |         console.error(error.stack || "No stack trace available");
118 |       }
119 |       throw error;
120 |     }
121 |   }
122 | 
123 |   /**
124 |    * get default params
125 |    */
126 |   getDefaultParams(): ImageGenerationParams {
127 |     return defaultParams;
128 |   }
129 | 
130 |   /**
131 |    * generate image
132 |    * @param inputParams user provided params
133 |    */
134 |   async generateImage(
135 |     inputParams: Partial<ImageGenerationParams> = {}
136 |   ): Promise<DrawThingsGenerationResult> {
137 |     try {
138 |       // handle input params
139 |       let params: Partial<ImageGenerationParams> = {};
140 | 
141 |       // validate params
142 |       try {
143 |         const validationResult = validateImageGenerationParams(inputParams);
144 |         if (validationResult.valid) {
145 |           params = inputParams;
146 |         } else {
147 |           console.error("parameter validation failed, use default params");
148 |         }
149 |       } catch (error) {
150 |         console.error("parameter validation error:", error);
151 |       }
152 | 
153 |       // handle random_string special case
154 |       if (
155 |         params.random_string &&
156 |         (!params.prompt || Object.keys(params).length === 1)
157 |       ) {
158 |         params.prompt = params.random_string;
159 |         delete params.random_string;
160 |       }
161 | 
162 |       // ensure prompt
163 |       if (!params.prompt) {
164 |         params.prompt = inputParams.prompt || defaultParams.prompt;
165 |       }
166 | 
167 |       // merge params
168 |       const requestParams = {
169 |         ...defaultParams,
170 |         ...params,
171 |         seed: params.seed ?? Math.floor(Math.random() * 2147483647),
172 |       };
173 | 
174 |       console.error(`use prompt: "${requestParams.prompt}"`);
175 | 
176 |       // send request to Draw Things API
177 |       console.error("send request to Draw Things API...");
178 |       const response = await this.axios.post(
179 |         "/sdapi/v1/txt2img",
180 |         requestParams
181 |       );
182 | 
183 |       // handle response
184 |       if (
185 |         !response.data ||
186 |         !response.data.images ||
187 |         response.data.images.length === 0
188 |       ) {
189 |         throw new Error("API did not return image data");
190 |       }
191 | 
192 |       // handle image data
193 |       const imageData = response.data.images[0];
194 | 
195 |       // format image data
196 |       const formattedImageData = imageData.startsWith("data:image/")
197 |         ? imageData
198 |         : `data:image/png;base64,${imageData}`;
199 | 
200 |       console.error("image generation success");
201 |       
202 |       // record the start time of image generation
203 |       const startTime = Date.now() - 2000; // assume the image generation took 2 seconds
204 |       const endTime = Date.now();
205 |       
206 |       // automatically save the generated image
207 |       const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
208 |       const defaultFileName = `generated-image-${timestamp}.png`;
209 |       
210 |       // save the generated image
211 |       const imagePath = await this.saveImage({
212 |         base64Data: formattedImageData,
213 |         fileName: defaultFileName
214 |       });
215 |       
216 |       return {
217 |         isError: false,
218 |         imageData: formattedImageData,
219 |         imagePath: imagePath,
220 |         metadata: {
221 |           alt: `Image generated from prompt: ${requestParams.prompt}`,
222 |           inference_time_ms: endTime - startTime,
223 |         }
224 |       };
225 |     } catch (error) {
226 |       console.error("image generation error:", error);
227 | 
228 |       // error message
229 |       let errorMessage = "unknown error";
230 | 
231 |       if (error instanceof Error) {
232 |         errorMessage = error.message;
233 |       }
234 | 
235 |       // handle axios error
236 |       const axiosError = error as any;
237 |       if (axiosError.response) {
238 |         errorMessage = `API error: ${axiosError.response.status} - ${
239 |           axiosError.response.data?.error || axiosError.message
240 |         }`;
241 |       } else if (axiosError.code === "ECONNREFUSED") {
242 |         errorMessage =
243 |           "cannot connect to Draw Things API. please ensure Draw Things is running and API is enabled.";
244 |       } else if (axiosError.code === "ETIMEDOUT") {
245 |         errorMessage =
246 |           "connection to Draw Things API timeout. image generation may take longer, or API not responding.";
247 |       }
248 | 
249 |       return {
250 |         isError: true,
251 |         errorMessage,
252 |       };
253 |     }
254 |   }
255 | }
256 | 
```

--------------------------------------------------------------------------------
/test-api-connection.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Draw Things API Connection Test
  5 |  * 測試不同方式連接到 Draw Things API
  6 |  */
  7 | 
  8 | import http from 'http';
  9 | import https from 'https';
 10 | import axios from 'axios';
 11 | import fs from 'fs';
 12 | 
 13 | // 記錄檔
 14 | const logFile = 'api-connection-test.log';
 15 | function log(message) {
 16 |   const timestamp = new Date().toISOString();
 17 |   const logMessage = `${timestamp} - ${message}\n`;
 18 |   fs.appendFileSync(logFile, logMessage);
 19 |   console.log(message);
 20 | }
 21 | 
 22 | // 錯誤記錄
 23 | function logError(error, message = 'Error') {
 24 |   const timestamp = new Date().toISOString();
 25 |   const errorDetails = error instanceof Error ? 
 26 |     `${error.message}\n${error.stack}` : 
 27 |     String(error);
 28 |   const logMessage = `${timestamp} - [ERROR] ${message}: ${errorDetails}\n`;
 29 |   fs.appendFileSync(logFile, logMessage);
 30 |   console.error(`${message}: ${error.message}`);
 31 | }
 32 | 
 33 | // 讀取參數
 34 | const apiPort = process.env.DRAW_THINGS_API_PORT || 7888;
 35 | const apiProxyPort = process.env.PROXY_PORT || 7889;
 36 | 
 37 | log('Draw Things API Connection Test');
 38 | log('===========================');
 39 | log(`Testing API on port ${apiPort}`);
 40 | log(`Testing proxy on port ${apiProxyPort}`);
 41 | log('');
 42 | 
 43 | // 測試 1: 直接使用 HTTP 模組連接
 44 | async function testHttpModule() {
 45 |   log('Test 1: 使用 Node.js HTTP 模組連接');
 46 |   
 47 |   return new Promise((resolve) => {
 48 |     try {
 49 |       const urls = [
 50 |         { name: '直接 API 連接 (127.0.0.1)', host: '127.0.0.1', port: apiPort },
 51 |         { name: '直接 API 連接 (localhost)', host: 'localhost', port: apiPort },
 52 |         { name: '代理伺服器連接 (127.0.0.1)', host: '127.0.0.1', port: apiProxyPort },
 53 |         { name: '代理伺服器連接 (localhost)', host: 'localhost', port: apiProxyPort }
 54 |       ];
 55 |       
 56 |       let completedTests = 0;
 57 |       const results = [];
 58 | 
 59 |       for (const url of urls) {
 60 |         log(`測試連接: ${url.name}`);
 61 |         
 62 |         const options = {
 63 |           hostname: url.host,
 64 |           port: url.port,
 65 |           path: '/sdapi/v1/options',
 66 |           method: 'GET',
 67 |           timeout: 5000,
 68 |           headers: {
 69 |             'User-Agent': 'DrawThingsMCP/1.0',
 70 |             'Accept': 'application/json'
 71 |           }
 72 |         };
 73 |         
 74 |         const req = http.request(options, (res) => {
 75 |           log(`${url.name} 回應狀態碼: ${res.statusCode}`);
 76 |           
 77 |           let data = '';
 78 |           res.on('data', chunk => {
 79 |             data += chunk;
 80 |           });
 81 |           
 82 |           res.on('end', () => {
 83 |             const success = res.statusCode >= 200 && res.statusCode < 300;
 84 |             results.push({
 85 |               name: url.name,
 86 |               success,
 87 |               statusCode: res.statusCode,
 88 |               hasData: !!data
 89 |             });
 90 |             
 91 |             completedTests++;
 92 |             if (completedTests === urls.length) {
 93 |               resolve(results);
 94 |             }
 95 |           });
 96 |         });
 97 |         
 98 |         req.on('error', (e) => {
 99 |           log(`${url.name} 錯誤: ${e.message}`);
100 |           results.push({
101 |             name: url.name,
102 |             success: false,
103 |             error: e.message
104 |           });
105 |           
106 |           completedTests++;
107 |           if (completedTests === urls.length) {
108 |             resolve(results);
109 |           }
110 |         });
111 |         
112 |         req.on('timeout', () => {
113 |           log(`${url.name} 連接逾時`);
114 |           req.destroy();
115 |           
116 |           results.push({
117 |             name: url.name,
118 |             success: false,
119 |             error: 'Timeout'
120 |           });
121 |           
122 |           completedTests++;
123 |           if (completedTests === urls.length) {
124 |             resolve(results);
125 |           }
126 |         });
127 |         
128 |         req.end();
129 |       }
130 |     } catch (error) {
131 |       logError(error, 'HTTP 模組測試發生錯誤');
132 |       resolve([]);
133 |     }
134 |   });
135 | }
136 | 
137 | // 測試 2: 使用 Axios 連接
138 | async function testAxios() {
139 |   log('Test 2: 使用 Axios 連接');
140 |   
141 |   try {
142 |     const urls = [
143 |       { name: '直接 API 連接 (127.0.0.1)', url: `http://127.0.0.1:${apiPort}/sdapi/v1/options` },
144 |       { name: '直接 API 連接 (localhost)', url: `http://localhost:${apiPort}/sdapi/v1/options` },
145 |       { name: '代理伺服器連接 (127.0.0.1)', url: `http://127.0.0.1:${apiProxyPort}/sdapi/v1/options` },
146 |       { name: '代理伺服器連接 (localhost)', url: `http://localhost:${apiProxyPort}/sdapi/v1/options` },
147 |     ];
148 |     
149 |     const results = [];
150 |     
151 |     for (const url of urls) {
152 |       log(`測試連接: ${url.name}`);
153 |       
154 |       try {
155 |         const response = await axios.get(url.url, {
156 |           timeout: 5000,
157 |           headers: {
158 |             'User-Agent': 'DrawThingsMCP/1.0',
159 |             'Accept': 'application/json'
160 |           }
161 |         });
162 |         
163 |         log(`${url.name} 回應狀態碼: ${response.status}`);
164 |         
165 |         results.push({
166 |           name: url.name,
167 |           success: response.status >= 200 && response.status < 300,
168 |           statusCode: response.status,
169 |           hasData: !!response.data
170 |         });
171 |       } catch (error) {
172 |         log(`${url.name} 錯誤: ${error.message}`);
173 |         
174 |         results.push({
175 |           name: url.name,
176 |           success: false,
177 |           error: error.message
178 |         });
179 |       }
180 |     }
181 |     
182 |     return results;
183 |   } catch (error) {
184 |     logError(error, 'Axios 測試發生錯誤');
185 |     return [];
186 |   }
187 | }
188 | 
189 | // 測試 3: 嘗試不同的端點
190 | async function testDifferentEndpoints() {
191 |   log('Test 3: 測試不同的 API 端點');
192 |   
193 |   try {
194 |     // 使用工作正常的連接方式 (localhost 或 127.0.0.1)
195 |     const baseUrl = `http://127.0.0.1:${apiPort}`;
196 |     
197 |     const endpoints = [
198 |       '/sdapi/v1/options',
199 |       '/sdapi/v1/samplers',
200 |       '/sdapi/v1/sd-models',
201 |       '/sdapi/v1/prompt-styles',
202 |       '/'
203 |     ];
204 |     
205 |     const results = [];
206 |     
207 |     for (const endpoint of endpoints) {
208 |       log(`測試端點: ${endpoint}`);
209 |       
210 |       try {
211 |         const response = await axios.get(`${baseUrl}${endpoint}`, {
212 |           timeout: 5000,
213 |           headers: {
214 |             'User-Agent': 'DrawThingsMCP/1.0',
215 |             'Accept': 'application/json'
216 |           }
217 |         });
218 |         
219 |         log(`端點 ${endpoint} 回應狀態碼: ${response.status}`);
220 |         
221 |         results.push({
222 |           endpoint,
223 |           success: response.status >= 200 && response.status < 300,
224 |           statusCode: response.status,
225 |           hasData: !!response.data
226 |         });
227 |       } catch (error) {
228 |         log(`端點 ${endpoint} 錯誤: ${error.message}`);
229 |         
230 |         results.push({
231 |           endpoint,
232 |           success: false,
233 |           error: error.message
234 |         });
235 |       }
236 |     }
237 |     
238 |     return results;
239 |   } catch (error) {
240 |     logError(error, '端點測試發生錯誤');
241 |     return [];
242 |   }
243 | }
244 | 
245 | // 執行測試
246 | async function runTests() {
247 |   try {
248 |     // 測試 1: HTTP 模組
249 |     log('\n執行 HTTP 模組測試...');
250 |     const httpResults = await testHttpModule();
251 |     
252 |     log('\nHTTP 模組測試結果:');
253 |     httpResults.forEach(result => {
254 |       log(`${result.name}: ${result.success ? '成功' : '失敗'} ${result.statusCode ? `(狀態碼: ${result.statusCode})` : ''} ${result.error ? `(錯誤: ${result.error})` : ''}`);
255 |     });
256 |     
257 |     // 測試 2: Axios
258 |     log('\n執行 Axios 測試...');
259 |     const axiosResults = await testAxios();
260 |     
261 |     log('\nAxios 測試結果:');
262 |     axiosResults.forEach(result => {
263 |       log(`${result.name}: ${result.success ? '成功' : '失敗'} ${result.statusCode ? `(狀態碼: ${result.statusCode})` : ''} ${result.error ? `(錯誤: ${result.error})` : ''}`);
264 |     });
265 |     
266 |     // 測試 3: 不同端點
267 |     log('\n執行不同端點測試...');
268 |     const endpointResults = await testDifferentEndpoints();
269 |     
270 |     log('\n不同端點測試結果:');
271 |     endpointResults.forEach(result => {
272 |       log(`端點 ${result.endpoint}: ${result.success ? '成功' : '失敗'} ${result.statusCode ? `(狀態碼: ${result.statusCode})` : ''} ${result.error ? `(錯誤: ${result.error})` : ''}`);
273 |     });
274 |     
275 |     // 總結
276 |     const httpSuccess = httpResults.some(r => r.success);
277 |     const axiosSuccess = axiosResults.some(r => r.success);
278 |     const endpointSuccess = endpointResults.some(r => r.success);
279 |     
280 |     log('\n=== 測試總結 ===');
281 |     log(`HTTP 模組連接測試: ${httpSuccess ? '至少有一個成功' : '全部失敗'}`);
282 |     log(`Axios 連接測試: ${axiosSuccess ? '至少有一個成功' : '全部失敗'}`);
283 |     log(`端點測試: ${endpointSuccess ? '至少有一個成功' : '全部失敗'}`);
284 |     
285 |     if (httpSuccess || axiosSuccess) {
286 |       log('\nAPI 連接測試成功! 您的 Draw Things API 似乎可以正常工作。');
287 |       
288 |       // 建議最佳連接方式
289 |       const bestConnection = [...httpResults, ...axiosResults].find(r => r.success);
290 |       if (bestConnection) {
291 |         log(`建議使用連接方式: ${bestConnection.name}`);
292 |       }
293 |     } else {
294 |       log('\nAPI 連接測試失敗! 請確認:');
295 |       log('1. Draw Things 應用程式正在運行');
296 |       log('2. Draw Things 已啟用 API 功能');
297 |       log('3. API 在設定的端口上運行 (默認 7888)');
298 |       log('4. 沒有防火牆阻擋連接');
299 |     }
300 |     
301 |   } catch (error) {
302 |     logError(error, '測試執行過程中發生錯誤');
303 |   }
304 | }
305 | 
306 | // 執行測試
307 | runTests().catch(error => {
308 |   logError(error, '測試主程序發生錯誤');
309 | }); 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Draw Things MCP - A Model Context Protocol implementation for Draw Things API
  5 |  * Integrated with Cursor MCP Bridge functionality for multiple input formats
  6 |  *
  7 |  * NOTE: Requires Node.js version 14+ for optional chaining support in dependencies
  8 |  */
  9 | 
 10 | import path from "path";
 11 | import fs from "fs";
 12 | import { fileURLToPath } from "url";
 13 | import { z } from "zod";
 14 | 
 15 | // MCP SDK imports
 16 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 17 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 18 | import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
 19 | 
 20 | // Local service imports
 21 | import { DrawThingsService } from "./services/drawThingsService.js";
 22 | import {
 23 |   ImageGenerationParameters,
 24 |   ImageGenerationResult,
 25 | } from "./services/schemas.js";
 26 | 
 27 | // Constants and environment variables
 28 | const DEBUG_MODE: boolean = process.env.DEBUG_MODE === "true";
 29 | // Get current file path in ESM
 30 | const __filename = fileURLToPath(import.meta.url);
 31 | // Get directory name
 32 | const __dirname = path.dirname(__filename);
 33 | const projectRoot: string = path.resolve(__dirname, "..");
 34 | const logsDir: string = path.join(projectRoot, "logs");
 35 | 
 36 | // Create logs directory if it doesn't exist
 37 | try {
 38 |   if (!fs.existsSync(logsDir)) {
 39 |     fs.mkdirSync(logsDir, { recursive: true });
 40 |     console.error(`Created logs directory: ${logsDir}`);
 41 |   }
 42 | } catch (error) {
 43 |   console.error(
 44 |     `Failed to create logs directory: ${
 45 |       error instanceof Error ? error.message : String(error)
 46 |     }`
 47 |   );
 48 | }
 49 | 
 50 | const logFile: string = path.join(logsDir, "draw-things-mcp.log");
 51 | 
 52 | // Basic logging function
 53 | function log(message: string): void {
 54 |   const timestamp = new Date().toISOString();
 55 |   const logMessage = `${timestamp} - ${message}\n`;
 56 |   try {
 57 |     fs.appendFileSync(logFile, logMessage);
 58 |     // Only output to stderr to avoid polluting JSON-RPC communication
 59 |     console.error(logMessage);
 60 |   } catch (error) {
 61 |     console.error(
 62 |       `Failed to write to log file: ${
 63 |         error instanceof Error ? error.message : String(error)
 64 |       }`
 65 |     );
 66 |   }
 67 | }
 68 | 
 69 | // Enhanced error logging to dedicated error log file
 70 | async function logError(error: Error | unknown): Promise<void> {
 71 |   try {
 72 |     const errorLogFile = path.join(logsDir, "error.log");
 73 |     const timestamp = new Date().toISOString();
 74 |     const errorDetails =
 75 |       error instanceof Error
 76 |         ? `${error.message}\n${error.stack}`
 77 |         : String(error);
 78 | 
 79 |     const errorLog = `${timestamp} - ERROR: ${errorDetails}\n\n`;
 80 | 
 81 |     try {
 82 |       await fs.promises.appendFile(errorLogFile, errorLog);
 83 | 
 84 |       log(`Error logged to ${errorLogFile}`);
 85 | 
 86 |       if (DEBUG_MODE) {
 87 |         console.error(`\n[DEBUG] FULL ERROR DETAILS:\n${errorDetails}\n`);
 88 |       }
 89 |     } catch (writeError) {
 90 |       // Fallback to sync writing
 91 |       try {
 92 |         fs.appendFileSync(errorLogFile, errorLog);
 93 |       } catch (syncWriteError) {
 94 |         console.error(
 95 |           `Failed to write to error log: ${
 96 |             syncWriteError instanceof Error
 97 |               ? syncWriteError.message
 98 |               : String(syncWriteError)
 99 |           }`
100 |         );
101 |         console.error(`Original error: ${errorDetails}`);
102 |       }
103 |     }
104 |   } catch (logError) {
105 |     console.error("Critical error in error logging system:");
106 |     console.error(logError);
107 |     console.error("Original error:");
108 |     console.error(error);
109 |   }
110 | }
111 | 
112 | // Print connection information and help message on startup
113 | function printConnectionInfo(): void {
114 |   // Only print to stderr to avoid polluting JSON-RPC communication
115 |   const infoText = `
116 | ---------------------------------------------
117 | | Draw Things MCP - Image Generation Service |
118 | ---------------------------------------------
119 | 
120 | Attempting to connect to Draw Things API at:
121 |     http://127.0.0.1:7888
122 | 
123 | TROUBLESHOOTING TIPS:
124 | 1. Ensure Draw Things is running on your computer
125 | 2. Make sure the API is enabled in Draw Things settings
126 | 3. If you changed the default port in Draw Things, set the environment variable:
127 |    DRAW_THINGS_API_URL=http://127.0.0.1:YOUR_PORT 
128 | 
129 | Starting service...
130 | `;
131 | 
132 |   // Log to file and stderr
133 |   log(infoText);
134 | }
135 | 
136 | const drawThingsService = new DrawThingsService();
137 | 
138 | const server = new McpServer({
139 |   name: "draw-things-mcp",
140 |   version: "1.0.0",
141 | });
142 | 
143 | // Define the image generation tool schema
144 | const paramsSchema = {
145 |   prompt: z.string().optional(),
146 |   negative_prompt: z.string().optional(),
147 |   width: z.number().optional(),
148 |   height: z.number().optional(),
149 |   steps: z.number().optional(),
150 |   seed: z.number().optional(),
151 |   guidance_scale: z.number().optional(),
152 |   random_string: z.string().optional(),
153 | };
154 | 
155 | server.tool(
156 |   "generateImage",
157 |   "Generate an image based on a prompt",
158 |   paramsSchema,
159 |   async (mcpParams: any) => {
160 |     try {
161 |       log("Received image generation request");
162 |       log(`mcpParams====== ${JSON.stringify(mcpParams)}`);
163 |       // handle ai prompts
164 |       const parameters =
165 |         mcpParams?.params?.arguments || mcpParams?.arguments || mcpParams || {};
166 | 
167 |       if (parameters.prompt) {
168 |         log(`Using provided prompt: ${parameters.prompt}`);
169 |       } else {
170 |         log("No prompt provided, using default");
171 |         parameters.prompt = "A cute dog";
172 |       }
173 | 
174 |       // Generate image
175 |       const result: ImageGenerationResult =
176 |         await drawThingsService.generateImage(parameters);
177 | 
178 |       // Handle generation result
179 |       if (result.isError) {
180 |         log(`Error generating image: ${result.errorMessage}`);
181 |         throw new Error(result.errorMessage || "Unknown error");
182 |       }
183 | 
184 |       if (!result.imageData && (!result.images || result.images.length === 0)) {
185 |         log("No image data returned from generation");
186 |         throw new Error("No image data returned from generation");
187 |       }
188 | 
189 |       const imageData =
190 |         result.imageData ||
191 |         (result.images && result.images.length > 0
192 |           ? result.images[0]
193 |           : undefined);
194 |       if (!imageData) {
195 |         log("No valid image data available");
196 |         throw new Error("No valid image data available");
197 |       }
198 | 
199 |       log("Successfully generated image, returning directly via MCP");
200 | 
201 |       // calculate the difference between the start and end time (example value)
202 |       const startTime = Date.now() - 2000; // assume the image generation took 2 seconds
203 |       const endTime = Date.now();
204 | 
205 |       // build the response format
206 |       const responseData = {
207 |         image_paths: result.imagePath ? [result.imagePath] : [],
208 |         metadata: {
209 |           alt: `Image generated from prompt: ${parameters.prompt}`,
210 |           inference_time_ms:
211 |             result.metadata?.inference_time_ms || endTime - startTime,
212 |         },
213 |       };
214 | 
215 |       return {
216 |         content: [
217 |           {
218 |             type: "text",
219 |             text: JSON.stringify(responseData, null, 2),
220 |           },
221 |         ],
222 |       };
223 |     } catch (error) {
224 |       log(
225 |         `Error handling image generation: ${
226 |           error instanceof Error ? error.message : String(error)
227 |         }`
228 |       );
229 |       await logError(error);
230 |       throw error;
231 |     }
232 |   }
233 | );
234 | 
235 | // Main program
236 | async function main(): Promise<void> {
237 |   try {
238 |     log("Starting Draw Things MCP service...");
239 | 
240 |     // Print connection info to the console
241 |     printConnectionInfo();
242 | 
243 |     log("Initializing Draw Things MCP service");
244 | 
245 |     // Enhanced API connection verification with direct method
246 |     log("Checking Draw Things API connection before starting service...");
247 |     const apiPort = process.env.DRAW_THINGS_API_PORT || 7888;
248 | 
249 |     // Final drawThingsService connection check
250 |     const isApiConnected = await drawThingsService.checkApiConnection();
251 |     if (!isApiConnected) {
252 |       log("\nFAILED TO CONNECT TO DRAW THINGS API");
253 |       log("Please make sure Draw Things is running and the API is enabled.");
254 |       log(
255 |         "The service will continue running, but image generation will not work until the API is available.\n"
256 |       );
257 |     } else {
258 |       log("\nSUCCESSFULLY CONNECTED TO DRAW THINGS API");
259 |       log("The service is ready to generate images.\n");
260 |       drawThingsService.setBaseUrl(`http://127.0.0.1:${apiPort}`);
261 |     }
262 | 
263 |     // Create transport and connect server
264 |     log("Creating transport and connecting server...");
265 |     const transport = new StdioServerTransport();
266 | 
267 |     // Connect server to transport
268 |     log("Connecting server to transport...");
269 |     await server.connect(transport);
270 |     log("MCP Server started successfully!");
271 |   } catch (error) {
272 |     log(
273 |       `Error in main program: ${
274 |         error instanceof Error ? error.message : String(error)
275 |       }`
276 |     );
277 |     await logError(error);
278 |   }
279 | }
280 | 
281 | main().catch(async (error) => {
282 |   log("server.log", `${new Date().toISOString()} - ${error.stack || error}\n`);
283 |   console.error(error);
284 |   process.exit(1);
285 | });
286 | 
```

--------------------------------------------------------------------------------
/test-mcp.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Draw Things MCP Test Script
  5 |  * 
  6 |  * This script is used to test whether the Draw Things MCP service can start normally and process image generation requests.
  7 |  * It simulates MCP client behavior, sending requests to the MCP service and handling responses.
  8 |  */
  9 | 
 10 | import { spawn } from 'child_process';
 11 | import { writeFile, mkdir } from 'fs/promises';
 12 | import path from 'path';
 13 | import { fileURLToPath } from 'url';
 14 | 
 15 | // Get the directory path of the current file
 16 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
 17 | 
 18 | // Ensure test output directory exists
 19 | const testOutputDir = path.join(__dirname, 'test-output');
 20 | try {
 21 |   await mkdir(testOutputDir, { recursive: true });
 22 |   console.log(`Test output directory created: ${testOutputDir}`);
 23 | } catch (error) {
 24 |   if (error.code !== 'EEXIST') {
 25 |     console.error('Error creating test output directory:', error);
 26 |     process.exit(1);
 27 |   }
 28 | }
 29 | 
 30 | // MCP request example - format corrected to comply with MCP protocol
 31 | const mcpRequest = {
 32 |   jsonrpc: "2.0",
 33 |   id: "test-request-" + Date.now(),
 34 |   method: "mcp.invoke",
 35 |   params: {
 36 |     tool: "generateImage",
 37 |     parameters: {
 38 |       prompt: "Beautiful Taiwan landscape, mountain and water painting style"
 39 |     }
 40 |   }
 41 | };
 42 | 
 43 | // Save request to file
 44 | const requestFilePath = path.join(testOutputDir, 'mcp-request.json');
 45 | try {
 46 |   await writeFile(requestFilePath, JSON.stringify(mcpRequest, null, 2));
 47 |   console.log(`MCP request saved to: ${requestFilePath}`);
 48 | } catch (error) {
 49 |   console.error('Error saving MCP request:', error);
 50 |   process.exit(1);
 51 | }
 52 | 
 53 | console.log('Starting Draw Things MCP service for testing...');
 54 | 
 55 | // Start MCP service process
 56 | const mcpProcess = spawn('node', ['src/index.js'], {
 57 |   stdio: ['pipe', 'pipe', 'pipe']
 58 | });
 59 | 
 60 | // Add progress display timer
 61 | let waitTime = 0;
 62 | const progressInterval = setInterval(() => {
 63 |   waitTime += 30;
 64 |   console.log(`Waited ${waitTime} seconds... Image generation may take some time, please be patient`);
 65 | }, 30000);
 66 | 
 67 | // Cleanup function - called when terminating the service in any situation
 68 | function cleanup() {
 69 |   clearInterval(progressInterval);
 70 |   if (mcpProcess && !mcpProcess.killed) {
 71 |     mcpProcess.kill();
 72 |   }
 73 | }
 74 | 
 75 | // Record standard output
 76 | let stdoutData = '';
 77 | mcpProcess.stdout.on('data', (data) => {
 78 |   const dataStr = data.toString();
 79 |   console.log(`MCP standard output: ${dataStr}`);
 80 |   stdoutData += dataStr;
 81 |   
 82 |   try {
 83 |     // Try to parse output as JSON
 84 |     const lines = dataStr.trim().split('\n');
 85 |     for (const line of lines) {
 86 |       if (!line.trim()) continue;
 87 |       
 88 |       try {
 89 |         const jsonData = JSON.parse(line);
 90 |         console.log('Parsed JSON response:', JSON.stringify(jsonData).substring(0, 100) + '...');
 91 |         
 92 |         // If it's an MCP response, save and analyze
 93 |         if (jsonData.id && (jsonData.result || jsonData.error)) {
 94 |           console.log('Received MCP response ID:', jsonData.id);
 95 |           
 96 |           if (jsonData.error) {
 97 |             console.error('MCP error response:', jsonData.error);
 98 |             const errorFile = path.join(testOutputDir, 'mcp-error.json');
 99 |             writeFile(errorFile, JSON.stringify(jsonData, null, 2))
100 |               .catch(e => console.error('Failed to write error file:', e));
101 |           } else if (jsonData.result) {
102 |             console.log('MCP successful response type:', jsonData.result.content?.[0]?.type || 'unknown');
103 |             
104 |             // Determine if the response contains an error
105 |             if (jsonData.result.isError) {
106 |               console.error('Error response:', jsonData.result.content[0].text);
107 |               const errorResultFile = path.join(testOutputDir, 'mcp-error-result.json');
108 |               writeFile(errorResultFile, JSON.stringify(jsonData, null, 2))
109 |                 .catch(e => console.error('Failed to write error result file:', e));
110 |             } else {
111 |               // Successful response, should contain image data
112 |               console.log('Successfully generated image!');
113 |               if (jsonData.result.content && jsonData.result.content[0].type === 'image') {
114 |                 const imageData = jsonData.result.content[0].data;
115 |                 console.log(`Image data size: ${imageData.length} characters`);
116 |                 
117 |                 // Use immediately executed async function
118 |                 (async function() {
119 |                   try {
120 |                     const savedImagePath = await saveImage(imageData);
121 |                     console.log(`Image successfully saved to: ${savedImagePath}`);
122 |                     
123 |                     const successFile = path.join(testOutputDir, 'mcp-success.json');
124 |                     await writeFile(successFile, JSON.stringify(jsonData.result, null, 2));
125 |                     console.log('Successfully saved result information to JSON file');
126 |                     
127 |                     // Extend wait time to ensure all operations complete
128 |                     setTimeout(() => {
129 |                       console.log('Test completed, image processing successful, terminating MCP service...');
130 |                       cleanup();
131 |                       process.exit(0);
132 |                     }, 3000); // Increased to 3 seconds
133 |                   } catch (saveError) {
134 |                     console.error('Error saving image or results:', saveError);
135 |                     const errorFile = path.join(testOutputDir, 'mcp-save-error.json');
136 |                     writeFile(errorFile, JSON.stringify({ error: saveError.message }, null, 2))
137 |                       .catch(e => console.error('Failed to write error information:', e));
138 |                     
139 |                     // End test normally even if there's an error
140 |                     setTimeout(() => {
141 |                       console.log('Test completed, but errors occurred during image processing, terminating MCP service...');
142 |                       cleanup();
143 |                       process.exit(1);
144 |                     }, 3000);
145 |                   }
146 |                 })();
147 |               }
148 |             }
149 |           }
150 |         }
151 |       } catch (parseError) {
152 |         // Not valid JSON, might be regular log output
153 |         // console.log('Non-JSON data:', line);
154 |       }
155 |     }
156 |   } catch (error) {
157 |     console.error('Error processing MCP output:', error);
158 |   }
159 | });
160 | 
161 | // Record standard error
162 | mcpProcess.stderr.on('data', (data) => {
163 |   const logMsg = data.toString().trim();
164 |   console.log(`MCP service log: ${logMsg}`);
165 |   
166 |   // Monitor specific log messages to confirm service status
167 |   if (logMsg.includes('MCP service is ready')) {
168 |     console.log('Detected MCP service is ready, preparing to send request...');
169 |     // Delay sending request
170 |     setTimeout(() => {
171 |       sendRequest();
172 |     }, 1000);
173 |   }
174 | });
175 | 
176 | // Handle process exit
177 | mcpProcess.on('close', (code) => {
178 |   if (code !== 0 && code !== null) {
179 |     console.error(`MCP service exited with code ${code}`);
180 |     
181 |     // Save output for diagnosis
182 |     try {
183 |       writeFile(path.join(testOutputDir, 'mcp-stdout.log'), stdoutData);
184 |       console.log('MCP service standard output log saved');
185 |     } catch (error) {
186 |       console.error('Error saving output log:', error);
187 |     }
188 |     
189 |     cleanup();
190 |     process.exit(1);
191 |   }
192 | });
193 | 
194 | // Handle errors
195 | mcpProcess.on('error', (error) => {
196 |   console.error('Error starting MCP service:', error);
197 |   cleanup();
198 |   process.exit(1);
199 | });
200 | 
201 | // Function to send MCP request
202 | function sendRequest() {
203 |   console.log('Sending image generation request...');
204 |   console.log('Request content:', JSON.stringify(mcpRequest));
205 |   
206 |   // Ensure request string ends with newline
207 |   const requestString = JSON.stringify(mcpRequest) + '\n';
208 |   mcpProcess.stdin.write(requestString);
209 |   console.log(`Sent ${requestString.length} bytes of request data`);
210 |   console.log('\n========================================');
211 |   console.log('Image generation has started, this may take a few minutes...');
212 |   console.log('Wait progress will be displayed every 30 seconds');
213 |   console.log('Please be patient, do not interrupt the test');
214 |   console.log('========================================\n');
215 |   
216 |   // Save the raw request sent
217 |   writeFile(path.join(testOutputDir, 'mcp-raw-request.txt'), requestString)
218 |     .catch(e => console.error('Failed to save raw request:', e));
219 | }
220 | 
221 | // Helper function: Save image
222 | async function saveImage(base64Data) {
223 |   try {
224 |     // Create buffer from base64 string
225 |     const imageBuffer = Buffer.from(base64Data, 'base64');
226 |     
227 |     // Save image to file
228 |     const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
229 |     const imagePath = path.join(testOutputDir, `generated-image-${timestamp}.png`);
230 |     
231 |     await writeFile(imagePath, imageBuffer);
232 |     console.log(`Generated image saved to: ${imagePath}`);
233 |     return imagePath;  // Make sure to return the saved path
234 |   } catch (error) {
235 |     console.error('Error saving image:', error);
236 |     throw error;  // Throw error so the upper function can catch and handle it
237 |   }
238 | }
239 | 
240 | // Don't send request immediately, wait for service log to indicate readiness
241 | 
242 | // Timeout handling
243 | setTimeout(() => {
244 |   console.error('Test timeout, terminating MCP service...');
245 |   writeFile(path.join(testOutputDir, 'mcp-timeout.log'), 'Test timed out after 300 seconds')
246 |     .catch(e => console.error('Failed to save timeout log:', e));
247 |   cleanup();
248 |   process.exit(1);
249 | }, 300000); // 5 minute timeout, providing more time to complete image generation 
```

--------------------------------------------------------------------------------
/cursor-mcp-bridge.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Cursor MCP Bridge - Connect Cursor MCP and Draw Things API
  5 |  * Converts simple text prompts to proper JSON-RPC requests
  6 |  */
  7 | 
  8 | import fs from 'fs';
  9 | import path from 'path';
 10 | import { fileURLToPath } from 'url';
 11 | import readline from 'readline';
 12 | import http from 'http';
 13 | 
 14 | // Set up log file
 15 | const logFile = 'cursor-mcp-bridge.log';
 16 | function log(message) {
 17 |   const timestamp = new Date().toISOString();
 18 |   const logMessage = `${timestamp} - ${message}\n`;
 19 |   fs.appendFileSync(logFile, logMessage);
 20 |   console.error(logMessage); // Also output to stderr for debugging
 21 | }
 22 | 
 23 | // Enhanced error logging
 24 | function logError(error, message = 'Error') {
 25 |   const timestamp = new Date().toISOString();
 26 |   const errorDetails = error instanceof Error ? 
 27 |     `${error.message}\n${error.stack}` : 
 28 |     String(error);
 29 |   const logMessage = `${timestamp} - [ERROR] ${message}: ${errorDetails}\n`;
 30 |   fs.appendFileSync(logFile, logMessage);
 31 |   console.error(logMessage);
 32 | }
 33 | 
 34 | // Initialize log
 35 | log('Cursor MCP Bridge started');
 36 | log('Waiting for input...');
 37 | 
 38 | // Get current directory path
 39 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
 40 | 
 41 | // Ensure images directory exists
 42 | const imagesDir = path.join(__dirname, 'images');
 43 | if (!fs.existsSync(imagesDir)) {
 44 |   fs.mkdirSync(imagesDir, { recursive: true });
 45 |   log(`Created image storage directory: ${imagesDir}`);
 46 | }
 47 | 
 48 | // Verify API connection - direct connection check
 49 | async function verifyApiConnection() {
 50 |   // Read API port from environment or use default
 51 |   const apiPort = process.env.DRAW_THINGS_API_PORT || 7888;
 52 |   const apiUrl = process.env.DRAW_THINGS_API_URL || `http://127.0.0.1:${apiPort}`;
 53 |   
 54 |   log(`Verifying API connection to ${apiUrl}`);
 55 | 
 56 |   return new Promise((resolve) => {
 57 |     // Try multiple endpoints
 58 |     const endpoints = ['/sdapi/v1/options', '/sdapi/v1/samplers', '/'];
 59 |     let endpointIndex = 0;
 60 |     let retryCount = 0;
 61 |     const maxRetries = 2;
 62 | 
 63 |     const tryEndpoint = () => {
 64 |       if (endpointIndex >= endpoints.length) {
 65 |         log('All endpoints failed, API connection verification failed');
 66 |         resolve(false);
 67 |         return;
 68 |       }
 69 | 
 70 |       const endpoint = endpoints[endpointIndex];
 71 |       const url = new URL(endpoint, apiUrl);
 72 |       
 73 |       log(`Trying endpoint: ${url.toString()}`);
 74 | 
 75 |       const options = {
 76 |         hostname: url.hostname,
 77 |         port: url.port,
 78 |         path: url.pathname,
 79 |         method: 'GET',
 80 |         timeout: 5000,
 81 |         headers: {
 82 |           'User-Agent': 'DrawThingsMCP/1.0',
 83 |           'Accept': 'application/json'
 84 |         }
 85 |       };
 86 | 
 87 |       const req = http.request(options, (res) => {
 88 |         log(`API connection check response: ${res.statusCode}`);
 89 |         
 90 |         // Any 2xx response is good
 91 |         if (res.statusCode >= 200 && res.statusCode < 300) {
 92 |           log('API connection verified successfully');
 93 |           resolve(true);
 94 |           return;
 95 |         }
 96 |         
 97 |         // Try next endpoint
 98 |         endpointIndex++;
 99 |         retryCount = 0;
100 |         tryEndpoint();
101 |       });
102 | 
103 |       req.on('error', (e) => {
104 |         log(`API connection error (${endpoint}): ${e.message}`);
105 |         
106 |         // Retry same endpoint a few times
107 |         if (retryCount < maxRetries) {
108 |           retryCount++;
109 |           log(`Retrying ${endpoint} (attempt ${retryCount}/${maxRetries})...`);
110 |           setTimeout(tryEndpoint, 1000);
111 |           return;
112 |         }
113 |         
114 |         // Move to next endpoint
115 |         endpointIndex++;
116 |         retryCount = 0;
117 |         tryEndpoint();
118 |       });
119 | 
120 |       req.on('timeout', () => {
121 |         log(`API connection timeout (${endpoint})`);
122 |         req.destroy();
123 |         
124 |         // Retry same endpoint a few times
125 |         if (retryCount < maxRetries) {
126 |           retryCount++;
127 |           log(`Retrying ${endpoint} after timeout (attempt ${retryCount}/${maxRetries})...`);
128 |           setTimeout(tryEndpoint, 1000);
129 |           return;
130 |         }
131 |         
132 |         // Move to next endpoint
133 |         endpointIndex++;
134 |         retryCount = 0;
135 |         tryEndpoint();
136 |       });
137 | 
138 |       req.end();
139 |     };
140 | 
141 |     tryEndpoint();
142 |   });
143 | }
144 | 
145 | // Initial API verification
146 | let apiVerified = false;
147 | verifyApiConnection().then(result => {
148 |   apiVerified = result;
149 |   if (result) {
150 |     log('API connection verified on startup');
151 |   } else {
152 |     log('API connection verification failed on startup - will retry on requests');
153 |   }
154 | });
155 | 
156 | // Set up readline interface
157 | const rl = readline.createInterface({
158 |   input: process.stdin,
159 |   output: process.stdout,
160 |   terminal: false
161 | });
162 | 
163 | // Listen for line input
164 | rl.on('line', async (line) => {
165 |   log(`Received input: ${line.substring(0, 100)}${line.length > 100 ? '...' : ''}`);
166 |   
167 |   // If API connection hasn't been verified yet, try again
168 |   if (!apiVerified) {
169 |     apiVerified = await verifyApiConnection();
170 |     if (!apiVerified) {
171 |       log('API connection still not available');
172 |       // Return error response but continue processing the request
173 |       // This allows the MCP service to handle the error properly
174 |     }
175 |   }
176 |   
177 |   // Check if input is already in JSON format
178 |   try {
179 |     const jsonInput = JSON.parse(line);
180 |     log('Input is valid JSON, checking if it conforms to JSON-RPC 2.0 standard');
181 |     
182 |     // Check if it conforms to JSON-RPC 2.0 standard
183 |     if (jsonInput.jsonrpc === "2.0" && jsonInput.method && jsonInput.id) {
184 |       log('Input already conforms to JSON-RPC 2.0 standard, forwarding directly');
185 |       process.stdout.write(line + '\n');
186 |       return;
187 |     } else {
188 |       log('JSON format is valid but does not conform to JSON-RPC 2.0 standard, converting');
189 |       processRequest(jsonInput);
190 |     }
191 |     return;
192 |   } catch (e) {
193 |     log(`Input is not valid JSON: ${e.message}`);
194 |   }
195 |   
196 |   // Check if it's a plain text prompt from Cursor
197 |   if (line && typeof line === 'string' && !line.startsWith('{')) {
198 |     log('Detected plain text prompt, converting to JSON-RPC request');
199 |     
200 |     // Create request conforming to JSON-RPC 2.0 standard
201 |     const request = {
202 |       jsonrpc: "2.0",
203 |       id: Date.now().toString(),
204 |       method: "mcp.invoke",
205 |       params: {
206 |         tool: "generateImage",
207 |         parameters: {
208 |           prompt: line.trim()
209 |         }
210 |       }
211 |     };
212 |     
213 |     processRequest(request);
214 |   } else {
215 |     log('Unrecognized input format, cannot process');
216 |     sendErrorResponse('Unrecognized input format', "parse_error", -32700);
217 |   }
218 | });
219 | 
220 | // Process request
221 | function processRequest(request) {
222 |   log(`Processing request: ${JSON.stringify(request).substring(0, 100)}...`);
223 |   
224 |   try {
225 |     // Ensure request has the correct structure
226 |     if (!request.jsonrpc) request.jsonrpc = "2.0";
227 |     if (!request.id) request.id = Date.now().toString();
228 |     
229 |     // If no method, set to mcp.invoke
230 |     if (!request.method) {
231 |       request.method = "mcp.invoke";
232 |     }
233 |     
234 |     // Process params
235 |     if (!request.params) {
236 |       // Try to build params from different sources
237 |       if (request.prompt || request.parameters) {
238 |         request.params = {
239 |           tool: "generateImage",
240 |           parameters: request.prompt 
241 |             ? { prompt: request.prompt } 
242 |             : (request.parameters || {})
243 |         };
244 |       } else {
245 |         // No usable parameters found
246 |         log('No usable parameters found, using empty object');
247 |         request.params = {
248 |           tool: "generateImage",
249 |           parameters: {}
250 |         };
251 |       }
252 |     } else if (!request.params.tool) {
253 |       // Ensure there's a tool parameter
254 |       request.params.tool = "generateImage";
255 |     }
256 |     
257 |     // Ensure there are parameters
258 |     if (!request.params.parameters) {
259 |       request.params.parameters = {};
260 |     }
261 |     
262 |     // Add API verification status to the request for debugging
263 |     if (!apiVerified) {
264 |       log('Warning: Adding API status information to request');
265 |       request.params.parameters._apiVerified = apiVerified;
266 |     }
267 |     
268 |     log(`Final request: ${JSON.stringify(request).substring(0, 150)}...`);
269 |     process.stdout.write(JSON.stringify(request) + '\n');
270 |   } catch (error) {
271 |     logError(error, 'Error processing request');
272 |     sendErrorResponse(`Error processing request: ${error.message}`, "internal_error", -32603);
273 |   }
274 | }
275 | 
276 | // Send error response conforming to JSON-RPC 2.0
277 | function sendErrorResponse(message, errorType = "invalid_request", code = -32600) {
278 |   const errorResponse = {
279 |     jsonrpc: "2.0",
280 |     id: "error-" + Date.now(),
281 |     error: {
282 |       code: code,
283 |       message: errorType,
284 |       data: message
285 |     }
286 |   };
287 |   
288 |   log(`Sending error response: ${JSON.stringify(errorResponse)}`);
289 |   process.stdout.write(JSON.stringify(errorResponse) + '\n');
290 | }
291 | 
292 | // Buffer for assembling complete JSON responses
293 | let responseBuffer = '';
294 | 
295 | // Handle responses from the MCP service
296 | process.stdin.on('data', (data) => {
297 |   try {
298 |     const dataStr = data.toString();
299 |     log(`Received data chunk from MCP service: ${dataStr.substring(0, 100)}${dataStr.length > 100 ? '...' : ''}`);
300 |     
301 |     // Append to buffer to handle chunked responses
302 |     responseBuffer += dataStr;
303 |     
304 |     // Check if we have a complete JSON object by trying to find matching braces
305 |     if (isCompleteJson(responseBuffer)) {
306 |       log('Detected complete JSON response, processing');
307 |       processCompleteResponse(responseBuffer);
308 |       responseBuffer = ''; // Clear buffer after processing
309 |     } else {
310 |       log('Incomplete JSON detected, buffering for more data');
311 |     }
312 |   } catch (error) {
313 |     logError(error, 'Error handling MCP service data');
314 |     
315 |     // If there's an error, try to forward the original data as a fallback
316 |     try {
317 |       process.stdout.write(data);
318 |     } catch (writeError) {
319 |       logError(writeError, 'Error forwarding original data');
320 |     }
321 |   }
322 | });
323 | 
324 | // Check if a string contains a complete JSON object
325 | function isCompleteJson(str) {
326 |   try {
327 |     JSON.parse(str);
328 |     return true;
329 |   } catch (e) {
330 |     // Not complete or not valid JSON
331 |     // Try basic brace matching as a fallback
332 |     let openBraces = 0;
333 |     let insideString = false;
334 |     let escapeNext = false;
335 |     
336 |     for (let i = 0; i < str.length; i++) {
337 |       const char = str[i];
338 |       
339 |       if (escapeNext) {
340 |         escapeNext = false;
341 |         continue;
342 |       }
343 |       
344 |       if (char === '\\' && insideString) {
345 |         escapeNext = true;
346 |         continue;
347 |       }
348 |       
349 |       if (char === '"') {
350 |         insideString = !insideString;
351 |         continue;
352 |       }
353 |       
354 |       if (!insideString) {
355 |         if (char === '{') openBraces++;
356 |         if (char === '}') openBraces--;
357 |       }
358 |     }
359 |     
360 |     // Complete JSON object should have matching braces
361 |     return openBraces === 0 && str.trim().startsWith('{') && str.trim().endsWith('}');
362 |   }
363 | }
364 | 
365 | // Process a complete response
366 | function processCompleteResponse(responseStr) {
367 |   try {
368 |     log(`Processing complete response: ${responseStr.substring(0, 100)}${responseStr.length > 100 ? '...' : ''}`);
369 |     
370 |     // Check for API error messages and update connection status
371 |     if (responseStr.includes("Draw Things API is not running or cannot be connected")) {
372 |       log('Detected API connection error in response');
373 |       // Trigger a new API verification
374 |       verifyApiConnection().then(result => {
375 |         apiVerified = result;
376 |         log(`API verification after error message: ${apiVerified ? 'successful' : 'failed'}`);
377 |       });
378 |     }
379 |     
380 |     // Try to parse as JSON
381 |     const response = JSON.parse(responseStr);
382 |     log('Successfully parsed MCP service response as JSON');
383 |     
384 |     // Check if it's an image generation result
385 |     if (response.result && response.result.content) {
386 |       // Find image content
387 |       const imageContent = response.result.content.find(item => item.type === 'image');
388 |       if (imageContent && imageContent.data) {
389 |         // Save the image
390 |         const timestamp = Date.now();
391 |         const imagePath = path.join(imagesDir, `image_${timestamp}.png`);
392 |         
393 |         // Remove data:image/png;base64, prefix
394 |         const base64Data = imageContent.data.replace(/^data:image\/\w+;base64,/, '');
395 |         
396 |         fs.writeFileSync(imagePath, Buffer.from(base64Data, 'base64'));
397 |         log(`Image saved to: ${imagePath}`);
398 |         
399 |         // Add saved path info to the response
400 |         response.result.imageSavedPath = imagePath;
401 |         
402 |         // Successful image generation indicates API is working
403 |         apiVerified = true;
404 |       }
405 |     }
406 |     
407 |     // Forward the processed response
408 |     process.stdout.write(JSON.stringify(response) + '\n');
409 |   } catch (error) {
410 |     logError(error, 'Error processing complete response');
411 |     
412 |     // Try to convert non-JSON response to proper JSON-RPC
413 |     if (responseStr.trim() && !responseStr.trim().startsWith('{')) {
414 |       log('Converting non-JSON response to proper JSON-RPC response');
415 |       
416 |       // Create a JSON-RPC response with the text as content
417 |       const jsonResponse = {
418 |         jsonrpc: "2.0",
419 |         id: "response-" + Date.now(),
420 |         result: {
421 |           content: [{
422 |             type: 'text',
423 |             text: responseStr.trim()
424 |           }]
425 |         }
426 |       };
427 |       
428 |       process.stdout.write(JSON.stringify(jsonResponse) + '\n');
429 |     } else {
430 |       // Forward original response as fallback
431 |       log('Forwarding original response as fallback');
432 |       process.stdout.write(responseStr + '\n');
433 |     }
434 |   }
435 | }
436 | 
437 | // Handle end of input
438 | rl.on('close', () => {
439 |   log('Input stream closed, program ending');
440 |   process.exit(0);
441 | });
442 | 
443 | // Handle errors
444 | process.on('uncaughtException', (error) => {
445 |   logError(error, 'Uncaught exception');
446 |   sendErrorResponse(`Error processing request: ${error.message}`, "internal_error", -32603);
447 | });
448 | 
449 | log('Bridge service ready, waiting for Cursor input...');
450 | 
451 | // Periodically check API connection
452 | setInterval(async () => {
453 |   const prevStatus = apiVerified;
454 |   apiVerified = await verifyApiConnection();
455 |   
456 |   if (prevStatus !== apiVerified) {
457 |     log(`API connection status changed: ${prevStatus} -> ${apiVerified}`);
458 |   }
459 | }, 60000); // Check every minute 
```