#
tokens: 19812/50000 20/20 files
lines: off (toggle) GitHub
raw markdown copy
# 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:
--------------------------------------------------------------------------------

```
.git
.gitignore
.DS_Store
node_modules
*.log
.vscode
.idea
.env
*.swp
*.swo 
```

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

```
{
  "$schema": "https://json.schemastore.org/swcrc",
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "dynamicImport": true,
      "decorators": true
    },
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": true
    },
    "target": "es2018",
    "loose": false
  },
  "module": {
    "type": "es6"
  },
  "sourceMaps": false,
  "minify": true
} 
```

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

```
# Logs

logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)

report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# Runtime data

pids
_.pid
_.seed
\*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover

lib-cov

# Coverage directory used by tools like istanbul

coverage
\*.lcov

# nyc test coverage

.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)

.grunt

# Bower dependency directory (https://bower.io/)

bower_components

# node-waf configuration

.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)

build/Release

# Dependency directories

node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)

web_modules/

# TypeScript cache

\*.tsbuildinfo

# Optional npm cache directory

.npm

# Optional eslint cache

.eslintcache

# Optional stylelint cache

.stylelintcache

# Microbundle cache

.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history

.node_repl_history

# Output of 'npm pack'

\*.tgz

# Yarn Integrity file

.yarn-integrity

# dotenv environment variable files

.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)

.cache
.parcel-cache

# Next.js build output

.next
out

# Nuxt.js build / generate output

.nuxt

# Gatsby files

.cache/

# Comment in the public line in if your project uses Gatsby and not Next.js

# https://nextjs.org/blog/next-9-1#public-directory-support

# public

# vuepress build output

.vuepress/dist

# vuepress v2.x temp and cache directory

.temp
.cache

# Docusaurus cache and generated files

.docusaurus

# Serverless directories

.serverless/

# FuseBox cache

.fusebox/

# DynamoDB Local files

.dynamodb/

# TernJS port file

.tern-port

# Stores VSCode versions used for testing VSCode extensions

.vscode-test

# yarn v2

.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*

# wrangler project

.dev.vars
.wrangler/
.specstory
images/
.cursor
test-output
cursor-mcp-bridge.log
cursor-mcp-bridge.log.old
draw-things-mcp.log
draw-things-mcp.log.old
*.log

```

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

```markdown
# Draw Things MCP

Draw Things API integration for Cursor using Model Context Protocol (MCP).

## Prerequisites

- Node.js >= 14.0.0
- Draw Things API running on http://127.0.0.1:7888

## Installation

```bash
# Install globally
npm install -g draw-things-mcp-cursor

# Or run directly
npx draw-things-mcp-cursor
```

## Cursor Integration

To set up this tool in Cursor, see the detailed guide in [cursor-setup.md](./cursor-setup.md).

Quick setup:

1. Create or edit `~/.cursor/claude_desktop_config.json`:
```json
{
  "mcpServers": {
    "draw-things": {
      "command": "draw-things-mcp-cursor",
      "args": []
    }
  }
}
```

2. Restart Cursor
3. Use in Cursor: `generateImage({"prompt": "a cute cat"})`

## CLI Usage

### Generate Image

```bash
echo '{"prompt": "your prompt here"}' | npx draw-things-mcp-cursor
```

### Parameters

- `prompt`: The text prompt for image generation (required)
- `negative_prompt`: The negative prompt for image generation
- `width`: Image width (default: 360)
- `height`: Image height (default: 360)
- `steps`: Number of steps for generation (default: 8)
- `model`: Model to use for generation (default: "flux_1_schnell_q5p.ckpt")
- `sampler`: Sampling method (default: "DPM++ 2M AYS")

Example:

```bash
echo '{
  "prompt": "a happy smiling dog, professional photography",
  "negative_prompt": "ugly, deformed, blurry",
  "width": 360,
  "height": 360,
  "steps": 4
}' | npx draw-things-mcp-cursor
```

### MCP Tool Integration

When used as an MCP tool in Cursor, the tool will be registered as `generateImage` with the following parameters:

```typescript
{
  prompt: string;       // Required - The prompt to generate the image from
  negative_prompt?: string;  // Optional - The negative prompt
  width?: number;       // Optional - Image width (default: 360)
  height?: number;      // Optional - Image height (default: 360)
  model?: string;       // Optional - Model name
  steps?: number;       // Optional - Number of steps (default: 8)
}
```

The generated images will be saved in the `images` directory with a filename format of:
`<sanitized_prompt>_<timestamp>.png`

## Response Format

Success:
```json
{
  "type": "success",
  "content": [{
    "type": "image",
    "data": "base64 encoded image data",
    "mimeType": "image/png"
  }],
  "metadata": {
    "parameters": { ... }
  }
}
```

Error:
```json
{
  "type": "error",
  "error": "error message",
  "code": 500
}
```

## Troubleshooting

If you encounter issues:

- Ensure Draw Things API is running at http://127.0.0.1:7888
- Check log files in `~/.cursor/logs` if using with Cursor
- Make sure src/index.js has execution permissions: `chmod +x src/index.js`

## License

MIT 
```

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

```typescript
/**
 * JSON-RPC 2.0 相關的介面定義
 */

/**
 * JSON-RPC 2.0 請求格式
 */
export interface JsonRpcRequest {
  jsonrpc: string;
  id: string;
  method: string;
  params?: {
    tool: string;
    parameters: any;
  };
  prompt?: string;
  parameters?: any;
}

/**
 * JSON-RPC 2.0 回應格式
 */
export interface JsonRpcResponse {
  jsonrpc: string;
  id: string;
  result?: any;
  error?: {
    code: number;
    message: string;
    data?: any;
  };
} 
```

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

```
import { nodeResolve } from '@rollup/plugin-node-resolve';

export default {
  input: 'dist-temp/index.js', // SWC translated entry file
  output: {
    file: 'dist/index.js',
    format: 'es',  // keep ES Module format
    sourcemap: false,
  },
  external: [
    // external dependencies, not packaged into the final file
    /@modelcontextprotocol\/.*/,
    'axios',
    'zod',
    'path',
    'fs',
    'os',
    'url',
    'util',
    'node:fs',
    'node:path',
    'node:os',
    'node:url',
    'node:util'
  ],
  plugins: [
    nodeResolve({
      exportConditions: ['node'],
      preferBuiltins: true,
    }),
  ]
}; 
```

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

```json
{
  "compilerOptions": {
    "target": "ES2018",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "sourceMap": false,
    "rootDir": "src",
    "declaration": false,
    "resolveJsonModule": true,
    "lib": ["ES2018"],
    "types": ["node", "jsdom"],
    "moduleSuffixes": ["js", "ts"],
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
    "preserveSymlinks": true,
    "allowJs": true,
    "removeComments": true

  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
} 
```

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

```
#!/usr/bin/env node

import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';

// create temporary directory
const tempDir = 'dist-temp';
if (!fs.existsSync(tempDir)) {
  fs.mkdirSync(tempDir);
}

try {
  // first step: use SWC to translate TypeScript to JavaScript (keep the original build command logic)
  console.log('step 1: use SWC to translate TypeScript to JavaScript...');
  execSync(`rimraf ${tempDir} && swc src -d ${tempDir} --strip-leading-paths`, { 
    stdio: 'inherit' 
  });

  // second step: use Rollup to package as a single file
  console.log('step 2: use Rollup to package as a single file...');
  execSync('rimraf dist && rollup -c', { 
    stdio: 'inherit' 
  });

  // third step: ensure dist/index.js has execution permission (because it is a bin file)
  console.log('step 3: set execution permission...');
  fs.chmodSync('dist/index.js', '755');

  // fourth step: clean temporary directory
  console.log('step 4: clean temporary directory...');
  execSync(`rimraf ${tempDir}`, { 
    stdio: 'inherit' 
  });

  console.log('build completed! output: dist/index.js');
} catch (error) {
  console.error('error occurred during the build process:', error);
  process.exit(1);
} 
```

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

```typescript
/**
 * images generation interfaces
 */

/**
 * image response format
 */
export interface ImageResponse {
  content: Array<{
    base64: string;
    path: string;
    prompt: string;
    negative_prompt?: string;
    seed: number;
    width: number;
    height: number;
    meta: Record<string, any>;
  }>;
  imageSavedPath?: string; // optional property, for storing image file path
}

/**
 * image generation parameters
 */
export interface ImageGenerationParameters {
  prompt?: string;
  negative_prompt?: string;
  seed?: number;
  width?: number;
  height?: number;
  num_inference_steps?: number;
  guidance_scale?: number;
  model?: string;
  random_string?: string;
  [key: string]: any;
}

/**
 * image generation result
 */
export interface ImageGenerationResult {
  status?: number;  // changed to optional
  error?: string;
  images?: string[];
  imageData?: string;
  isError?: boolean;
  errorMessage?: string;
}

/**
 * Draw Things service generation result
 */
export interface DrawThingsGenerationResult {
  isError: boolean;
  imageData?: string;
  errorMessage?: string;
  parameters?: Record<string, any>;
  status?: number; // added property to compatible with ImageGenerationResult
  images?: string[]; // added property to compatible with ImageGenerationResult
  error?: string;    // added property to compatible with ImageGenerationResult
  imagePath?: string; // added property to store the path of the generated image
  metadata?: {
    alt: string;
    inference_time_ms: number;
  }; // added metadata
} 
```

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

```json
{
	"name": "draw-things-mcp-cursor",
	"version": "1.4.3",
	"description": "Draw Things API integration for Cursor using Model Context Protocol (MCP)",
	"private": false,
	"type": "module",
	"main": "dist/index.js",
	"bin": {
		"draw-things-mcp-cursor": "./dist/index.js"
	},
	"scripts": {
		"start": "node --experimental-vm-modules --no-warnings dist/index.js",
		"dev": "NODE_OPTIONS='--loader ts-node/esm' ts-node src/index.ts",
		"build": "node build.mjs",
		"test": "node --experimental-vm-modules --no-warnings test-mcp.js",
		"prepare": "npm run build",
		"prepublishOnly": "npm run build",
		"typecheck": "tsc --noEmit"
	},
	"dependencies": {
		"@modelcontextprotocol/sdk": "^1.7.0",
		"axios": "^1.8.0",
		"zod": "^3.24.2"
	},
	"devDependencies": {
		"@rollup/plugin-node-resolve": "^16.0.0",
		"@swc/cli": "^0.3.8",
		"@swc/core": "^1.4.8",
		"@swc/helpers": "^0.5.6",
		"@types/node": "^20.17.19",
		"rimraf": "^5.0.10",
		"rollup": "^4.34.9",
		"ts-node": "^10.9.2",
		"typescript": "^5.3.3",
		"zod-to-json-schema": "3.20.3"
	},
	"overrides": {
		"zod-to-json-schema": "3.20.3"
	},
	"resolutions": {
		"zod-to-json-schema": "3.20.3"
	},
	"files": [
		"dist",
		"README.md",
		"cursor-setup.md"
	],
	"keywords": [
		"cursor",
		"mcp",
		"draw-things",
		"ai",
		"image-generation",
		"stable-diffusion"
	],
	"author": "jaokuohsuan",
	"license": "MIT",
	"repository": {
		"type": "git",
		"url": "https://github.com/jaokuohsuan/draw-things-mcp"
	},
	"bugs": {
		"url": "https://github.com/jaokuohsuan/draw-things-mcp/issues"
	},
	"homepage": "https://github.com/jaokuohsuan/draw-things-mcp#readme",
	"engines": {
		"node": ">=16.0.0"
	},
	"packageManager": "[email protected]+sha512.c8180b3fbe4e4bca02c94234717896b5529740a6cbadf19fa78254270403ea2f27d4e1d46a08a0f56c89b63dc8ebfd3ee53326da720273794e6200fcf0d184ab"
}

```

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

```typescript
import { ImageGenerationParams } from './schemas.js';

// Default parameters for image generation
export const defaultParams: ImageGenerationParams = {
  speed_up_with_guidance_embed: true,
  motion_scale: 127,
  image_guidance: 1.5,
  tiled_decoding: false,
  decoding_tile_height: 640,
  negative_prompt_for_image_prior: true,
  batch_size: 1,
  decoding_tile_overlap: 128,
  separate_open_clip_g: false,
  hires_fix_height: 960,
  decoding_tile_width: 640,
  diffusion_tile_height: 1024,
  num_frames: 14,
  stage_2_guidance: 1,
  t5_text_encoder_decoding: true,
  mask_blur_outset: 0,
  resolution_dependent_shift: true,
  model: "flux_1_schnell_q5p.ckpt",
  hires_fix: false,
  strength: 1,
  loras: [],
  diffusion_tile_width: 1024,
  diffusion_tile_overlap: 128,
  original_width: 512,
  seed: -1,
  zero_negative_prompt: false,
  upscaler_scale: 0,
  steps: 8,
  upscaler: null,
  mask_blur: 1.5,
  sampler: "DPM++ 2M AYS",
  width: 320,
  negative_original_width: 512,
  batch_count: 1,
  refiner_model: null,
  shift: 1,
  stage_2_shift: 1,
  open_clip_g_text: null,
  crop_left: 0,
  controls: [],
  start_frame_guidance: 1,
  original_height: 512,
  image_prior_steps: 5,
  guiding_frame_noise: 0.019999999552965164,
  clip_weight: 1,
  clip_skip: 1,
  crop_top: 0,
  negative_original_height: 512,
  preserve_original_after_inpaint: true,
  separate_clip_l: false,
  guidance_embed: 3.5,
  negative_aesthetic_score: 2.5,
  aesthetic_score: 6,
  clip_l_text: null,
  hires_fix_strength: 0.699999988079071,
  guidance_scale: 7.5,
  stochastic_sampling_gamma: 0.3,
  seed_mode: "Scale Alike",
  target_width: 512,
  hires_fix_width: 960,
  tiled_diffusion: false,
  fps: 5,
  refiner_start: 0.8500000238418579,
  height: 512,
  prompt: "A cute koala sitting on a eucalyptus tree, watercolor style, beautiful lighting, detailed",
  negative_prompt: "deformed, distorted, unnatural pose, extra limbs, blurry, low quality, ugly, bad anatomy, poor details, mutated, text, watermark"
}; 
```

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

```markdown
# Cursor MCP Tool Setup Guide

## Setting Up Draw Things MCP Tool in Cursor

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.

### Prerequisites

- Ensure Draw Things API is running (http://127.0.0.1:7888)
- Node.js v14.0.0 or higher

### 1. Install the Package

#### Local Development Mode

If you're developing or modifying this tool, you can use local linking:

```bash
# In the project directory
npm link
```

#### Publishing to NPM (if needed)

If you want to publish this tool for others to use:

```bash
npm publish
```

Then install globally via npm:

```bash
npm install -g draw-things-mcp-cursor
```

### 2. Create Cursor MCP Configuration File

You need to create or edit the `~/.cursor/claude_desktop_config.json` file to register the MCP tool with Cursor:

```json
{
  "mcpServers": {
    "draw-things": {
      "command": "draw-things-mcp-cursor",
      "args": []
    }
  }
}
```

#### Local Development Configuration

If you're developing locally, it's recommended to use an absolute path to your JS file:

```json
{
  "mcpServers": {
    "draw-things": {
      "command": "node",
      "args": [
        "/Users/james_jao/m800/my-mcp/src/index.js"
      ]
    }
  }
}
```

### 3. Restart Cursor

After configuration, completely close and restart the Cursor editor to ensure the new MCP configuration is properly loaded.

### 4. Using the MCP Tool

In Cursor, you can call the image generation tool when chatting with the AI assistant using the following format:

#### Basic Usage

```
generateImage({"prompt": "a cute cat"})
```

#### Advanced Usage

You can specify additional parameters to fine-tune the generated image:

```
generateImage({
  "prompt": "a cute cat",
  "negative_prompt": "ugly, deformed",
  "width": 512,
  "height": 512,
  "steps": 4,
  "model": "flux_1_schnell_q5p.ckpt"
})
```

### Available Parameters

| Parameter Name | Description | Default Value |
|----------------|-------------|---------------|
| prompt | The image generation prompt | (Required) |
| negative_prompt | Elements to avoid in the image | (Empty) |
| width | Image width (pixels) | 360 |
| height | Image height (pixels) | 360 |
| steps | Number of generation steps (higher is more detailed but slower) | 8 |
| model | Model name to use | "flux_1_schnell_q5p.ckpt" |

### Troubleshooting

If you encounter issues when setting up or using the MCP tool, check:

- Log files in the `~/.cursor/logs` directory for detailed error information
- Ensure Draw Things API is started and running at http://127.0.0.1:7888
- Make sure the src/index.js file has execution permissions: `chmod +x src/index.js`
- Check for error messages in the terminal: `draw-things-mcp-cursor`

### Getting Help

If you have any questions, please open an issue on the project's GitHub page:
https://github.com/james-jao/draw-things-mcp/issues 
```

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

```typescript
/**
 * image generation params interface
 */

// params interface
export interface ImageGenerationParams {
  // basic params
  prompt?: string;
  negative_prompt?: string;

  // size params
  width?: number;
  height?: number;

  // generate control params
  steps?: number;
  seed?: number;
  guidance_scale?: number;

  // model params
  model?: string;
  sampler?: string;

  // MCP special params
  random_string?: string;

  // allow other params
  [key: string]: any;
}

// function for validating ImageGenerationParams
export function validateImageGenerationParams(params: any): {
  valid: boolean;
  errors: string[];
} {
  const errors: string[] = [];

  // check params type
  if (params.prompt !== undefined && typeof params.prompt !== "string") {
    errors.push("prompt must be a string");
  }

  if (
    params.negative_prompt !== undefined &&
    typeof params.negative_prompt !== "string"
  ) {
    errors.push("negative_prompt must be a string");
  }

  if (
    params.width !== undefined &&
    (typeof params.width !== "number" ||
      params.width <= 0 ||
      !Number.isInteger(params.width))
  ) {
    errors.push("width must be a positive integer");
  }

  if (
    params.height !== undefined &&
    (typeof params.height !== "number" ||
      params.height <= 0 ||
      !Number.isInteger(params.height))
  ) {
    errors.push("height must be a positive integer");
  }

  if (
    params.steps !== undefined &&
    (typeof params.steps !== "number" ||
      params.steps <= 0 ||
      !Number.isInteger(params.steps))
  ) {
    errors.push("steps must be a positive integer");
  }

  if (
    params.seed !== undefined &&
    (typeof params.seed !== "number" || !Number.isInteger(params.seed))
  ) {
    errors.push("seed must be an integer");
  }

  if (
    params.guidance_scale !== undefined &&
    (typeof params.guidance_scale !== "number" || params.guidance_scale <= 0)
  ) {
    errors.push("guidance_scale must be a positive number");
  }

  if (params.model !== undefined && typeof params.model !== "string") {
    errors.push("model must be a string");
  }

  if (params.sampler !== undefined && typeof params.sampler !== "string") {
    errors.push("sampler must be a string");
  }

  if (
    params.random_string !== undefined &&
    typeof params.random_string !== "string"
  ) {
    errors.push("random_string must be a string");
  }

  return { valid: errors.length === 0, errors };
}

// response interface
export interface ImageGenerationResult {
  status?: number;
  images?: string[];
  parameters?: Record<string, any>;
  error?: string;
}

// success response interface
export interface SuccessResponse {
  content: Array<{
    type: "image";
    data: string;
    mimeType: string;
  }>;
}

// error response interface
export interface ErrorResponse {
  content: Array<{
    type: "text";
    text: string;
  }>;
  isError: true;
}

// MCP response interface
export type McpResponse = SuccessResponse | ErrorResponse;

```

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

```markdown
# Complete Guide for Using Draw Things MCP in Cursor

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.

## Background

When directly using the `mcp_generateImage` tool in Cursor, the following issues occur:

1. Cursor sends plain text prompts instead of the correct JSON-RPC 2.0 format
2. MCP service cannot parse this input format, resulting in the error `Unexpected token A in JSON at position 0`
3. According to the [MCP documentation](https://docs.cursor.com/context/model-context-protocol), communication must use a specific JSON-RPC 2.0 format

## Solution: Bridge Service

We provide a bridge service that automatically converts Cursor's plain text prompts to the correct JSON-RPC 2.0 format.

### Step 1: Environment Setup

First, ensure you have installed these prerequisites:

1. Node.js and npm
2. Draw Things application
3. API enabled in Draw Things (port 7888)

### Step 2: Start the Bridge Service

We provide a startup script that easily launches the bridge service:

```bash
# Grant execution permission
chmod +x start-cursor-bridge.sh

# Basic usage
./start-cursor-bridge.sh

# Use debug mode
./start-cursor-bridge.sh --debug

# View help
./start-cursor-bridge.sh --help
```

This script will:
1. Check if the Draw Things API is available
2. Start the bridge service
3. Start the MCP service
4. Connect the two services so they can communicate with each other

### Step 3: Using in Cursor

When the bridge service is running, the following two input methods are supported when using the `mcp_generateImage` tool in Cursor:

1. **Directly send English prompts** (the bridge service will automatically convert to JSON-RPC format):
   ```
   A group of adorable kittens playing together, cute, fluffy, detailed fur, warm lighting, playful mood
   ```

2. **Use JSON objects** (suitable when more custom parameters are needed):
   ```json
   {
     "prompt": "A group of adorable kittens playing together, cute, fluffy, detailed fur, warm lighting, playful mood",
     "negative_prompt": "blurry, distorted, low quality",
     "seed": 12345
   }
   ```

### Step 4: View Results

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.

## JSON-RPC 2.0 Format Explanation

According to the MCP specification, the correct request format should be:

```json
{
  "jsonrpc": "2.0",
  "id": "request-123",
  "method": "mcp.invoke",
  "params": {
    "tool": "generateImage",
    "parameters": {
      "prompt": "A group of adorable kittens playing together",
      "negative_prompt": "blurry, low quality",
      "seed": 12345
    }
  }
}
```

Our bridge service automatically converts simple inputs to this format.

## Custom Options

You can modify default parameters by editing the `src/services/drawThings/defaultParams.js` file, such as:

- Model selection
- Image dimensions
- Sampler type
- Other generation parameters

## Troubleshooting

### Check Logs

If you encounter problems, first check these logs:

1. `cursor-mcp-bridge.log` - Bridge service logs
2. `cursor-mcp-debug.log` - Detailed logs when debug mode is enabled
3. `error.log` - MCP service error logs

### Common Issues

1. **Connection Error**: Ensure the Draw Things application is running and API is enabled (127.0.0.1:7888).

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.

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.

## Technical Details

How the bridge service works:

1. Receives plain text or JSON input from Cursor
2. Converts it to JSON-RPC 2.0 format compliant with MCP specifications
3. Passes the converted request to the MCP service
4. MCP service communicates with the Draw Things API
5. Receives the response and saves the generated image to the file system

### Transport Layer

According to the MCP specification, our bridge service implements the following functions:

- Uses stdin/stdout as the transport layer
- Correctly handles JSON-RPC 2.0 request/response formats
- Supports error handling and logging
- Automatically saves generated images

If you need more customization, you can edit the `cursor-mcp-bridge.js` file. 
```

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

```bash
#!/bin/bash

# Cursor MCP and Draw Things Bridge Service Startup Script

# Ensure the program aborts on error
set -e

echo "========================================================"
echo "  Cursor MCP and Draw Things Bridge Service Tool  "
echo "  Image Generation Service Compliant with Model Context Protocol  "
echo "========================================================"
echo

# Check dependencies
command -v node >/dev/null 2>&1 || { echo "Error: Node.js is required but not installed"; exit 1; }
command -v npm >/dev/null 2>&1 || { echo "Error: npm is required but not installed"; exit 1; }

# Ensure script has execution permissions
chmod +x cursor-mcp-bridge.js

# Check if help information is needed
if [ "$1" == "--help" ] || [ "$1" == "-h" ]; then
  echo "Usage: ./start-cursor-bridge.sh [options]"
  echo
  echo "Options:"
  echo "  --help, -h     Display this help information"
  echo "  --debug        Enable additional debug output"
  echo "  --no-cleanup   Keep old log files"
  echo "  --port PORT    Specify custom port for Draw Things API (default: 7888)"
  echo
  echo "This script is used to start the Cursor MCP and Draw Things bridge service."
  echo "It will start a service that allows Cursor to generate images using plain text prompts."
  echo
  echo "Dependencies:"
  echo "  - Node.js and npm"
  echo "  - Draw Things application (must be running with API enabled)"
  echo
  exit 0
fi

# Parse parameters
DEBUG_MODE=false
CLEANUP=true
API_PORT=7888

for arg in "$@"; do
  case $arg in
    --debug)
      DEBUG_MODE=true
      shift
      ;;
    --no-cleanup)
      CLEANUP=false
      shift
      ;;
    --port=*)
      API_PORT="${arg#*=}"
      shift
      ;;
  esac
done

# Install dependencies
echo "Checking and installing necessary dependencies..."
npm install --quiet

# Clean up old logs
if [ "$CLEANUP" = true ]; then
  echo "Cleaning up old log files..."
  if [ -f cursor-mcp-bridge.log ]; then
    mv cursor-mcp-bridge.log cursor-mcp-bridge.log.old
  fi
  if [ -f draw-things-mcp.log ]; then
    mv draw-things-mcp.log draw-things-mcp.log.old
  fi
fi

# Ensure images directory exists
mkdir -p images
# Ensure logs directory exists
mkdir -p logs

echo
echo "Step 1: Checking if Draw Things API is available..."

# Create a simple test script to check API connection
cat > test-api.js << EOL
import http from 'http';

const options = {
  host: '127.0.0.1',
  port: ${API_PORT},
  path: '/sdapi/v1/options',
  method: 'GET',
  timeout: 5000,
  headers: {
    'User-Agent': 'DrawThingsMCP/1.0',
    'Accept': 'application/json'
  }
};

const req = http.request(options, (res) => {
  console.log('Draw Things API connection successful! Status code:', res.statusCode);
  process.exit(0);
});

req.on('error', (e) => {
  if (e.code === 'ECONNREFUSED') {
    console.error('Error: Unable to connect to Draw Things API. Make sure Draw Things application is running and API is enabled.');
  } else if (e.code === 'ETIMEDOUT') {
    console.error('Error: Connection to Draw Things API timed out. Make sure Draw Things application is running normally.');
  } else {
    console.error('Error:', e.message);
  }
  process.exit(1);
});

req.on('timeout', () => {
  console.error('Error: Connection to Draw Things API timed out. Make sure Draw Things application is running normally.');
  req.destroy();
  process.exit(1);
});

req.end();
EOL

# Run API test
if node test-api.js; then
  echo "Draw Things API is available, continuing to start bridge service..."
else
  echo
  echo "Warning: Draw Things API appears to be unavailable on port ${API_PORT}."
  echo "Please ensure:"
  echo "1. Draw Things application is running"
  echo "2. API is enabled in Draw Things settings"
  echo "3. API is listening on 127.0.0.1:${API_PORT}"
  echo
  read -p "Continue starting the bridge service anyway? (y/n) " -n 1 -r
  echo
  if [[ ! $REPLY =~ ^[Yy]$ ]]; then
    echo "Canceling bridge service startup."
    exit 1
  fi
fi

# Clean up temporary files
rm -f test-api.js

echo
echo "Step 2: Starting Services..."
echo

# Set up environment variables
export DRAW_THINGS_FORCE_STAY_ALIVE=true
export MCP_BRIDGE_DEDUP=true
export DEBUG_MODE=$DEBUG_MODE
export DRAW_THINGS_API_PORT=$API_PORT
export DRAW_THINGS_API_URL="http://127.0.0.1:${API_PORT}"

# Set up debug mode
if [ "$DEBUG_MODE" = true ]; then
  echo "Debug mode enabled, all log output will be displayed"
  echo "Starting MCP bridge service in debug mode..."
  
  # Start both services in debug mode
  node cursor-mcp-bridge.js 2>&1 | tee -a cursor-mcp-debug.log | node src/index.js
else
  # Start bridge service
  echo "Starting bridge service in normal mode..."
  echo "All logs will be saved to cursor-mcp-bridge.log and draw-things-mcp.log"
  
  # Start MCP bridge service and pipe output to MCP service
  node cursor-mcp-bridge.js | node src/index.js
fi

echo
echo "Service has ended."
echo "Log files:"
echo " - cursor-mcp-bridge.log"
echo " - draw-things-mcp.log"
echo " - logs/error.log (if errors occurred)"
echo "If generation was successful, images will be saved in the images directory."

# Display service status
if [ -f "images/image_$(date +%Y%m%d)*.png" ] || [ -f "images/generated-image_*.png" ]; then
  echo "Images were successfully generated today!"
  ls -la images/ | grep "$(date +%Y-%m-%d)"
else
  echo "No images generated today were found. Please check the logs for more information."
fi 
```

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

```typescript
import { defaultParams } from "./defaultParams.js";
import {
  ImageGenerationParams,
  validateImageGenerationParams,
} from "./schemas.js";
import axios, { AxiosInstance } from "axios";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import { DrawThingsGenerationResult } from "../../interfaces/index.js";

/**
 * simplified DrawThingsService
 * focus on core functionality: connect to Draw Things API and generate image
 */
export class DrawThingsService {
  // make baseUrl public for compatibility with index.ts
  public baseUrl: string;
  // change to public axios for compatibility
  public axios: AxiosInstance;

  constructor(apiUrl = "http://127.0.0.1:7888") {
    this.baseUrl = apiUrl;

    // initialize axios
    this.axios = axios.create({
      baseURL: this.baseUrl,
      timeout: 300000, // 5 minutes timeout (image generation may take time)
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
    });

    // log initialization
    console.error(
      `DrawThingsService initialized, API location: ${this.baseUrl}`
    );
  }

  /**
   * Set new base URL and update axios instance
   * @param url new base URL
   */
  setBaseUrl(url: string): void {
    this.baseUrl = url;
    this.axios.defaults.baseURL = url;
    console.error(`Updated API base URL to: ${url}`);
  }

  /**
   * check API connection
   * simplified version that just checks if API is available
   */
  async checkApiConnection(): Promise<boolean> {
    try {
      console.error(`Checking API connection to: ${this.baseUrl}`);

      // Try simple endpoint with short timeout
      const response = await this.axios.get("/sdapi/v1/options", {
        timeout: 5000,
        validateStatus: (status) => status >= 200,
      });

      const isConnected = response.status >= 200;
      console.error(
        `API connection check: ${isConnected ? "Success" : "Failed"}`
      );
      return isConnected;
    } catch (error) {
      console.error(`API connection check failed: ${(error as Error).message}`);
      return false;
    }
  }

  // Helper function to save images to the file system
  async saveImage({
    base64Data,
    outputPath,
    fileName
  }: {
    base64Data: string;
    outputPath?: string;
    fileName?: string;
  }): Promise<string> {
    const __filename = fileURLToPath(import.meta.url);
    // Get directory name
    const __dirname = path.dirname(__filename);
    const projectRoot: string = path.resolve(__dirname, "..");
    
    try {
      // if no output path provided, use default path
      const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
      const defaultFileName = fileName || `generated-image-${timestamp}.png`;
      const defaultImagesDir = path.resolve(projectRoot, "..", "images");
      const finalOutputPath = outputPath || path.join(defaultImagesDir, defaultFileName);
      
      // ensure the images directory exists
      const imagesDir = path.dirname(finalOutputPath);
      if (!fs.existsSync(imagesDir)) {
        await fs.promises.mkdir(imagesDir, { recursive: true });
      }

      const cleanBase64 = base64Data.replace(/^data:image\/\w+;base64,/, "");
      const buffer = Buffer.from(cleanBase64, "base64");

      const absolutePath = path.resolve(finalOutputPath);
      await fs.promises.writeFile(absolutePath, buffer);
      return absolutePath;
    } catch (error) {
      console.error(
        `Failed to save image: ${
          error instanceof Error ? error.message : String(error)
        }`
      );
      if (error instanceof Error) {
        console.error(error.stack || "No stack trace available");
      }
      throw error;
    }
  }

  /**
   * get default params
   */
  getDefaultParams(): ImageGenerationParams {
    return defaultParams;
  }

  /**
   * generate image
   * @param inputParams user provided params
   */
  async generateImage(
    inputParams: Partial<ImageGenerationParams> = {}
  ): Promise<DrawThingsGenerationResult> {
    try {
      // handle input params
      let params: Partial<ImageGenerationParams> = {};

      // validate params
      try {
        const validationResult = validateImageGenerationParams(inputParams);
        if (validationResult.valid) {
          params = inputParams;
        } else {
          console.error("parameter validation failed, use default params");
        }
      } catch (error) {
        console.error("parameter validation error:", error);
      }

      // handle random_string special case
      if (
        params.random_string &&
        (!params.prompt || Object.keys(params).length === 1)
      ) {
        params.prompt = params.random_string;
        delete params.random_string;
      }

      // ensure prompt
      if (!params.prompt) {
        params.prompt = inputParams.prompt || defaultParams.prompt;
      }

      // merge params
      const requestParams = {
        ...defaultParams,
        ...params,
        seed: params.seed ?? Math.floor(Math.random() * 2147483647),
      };

      console.error(`use prompt: "${requestParams.prompt}"`);

      // send request to Draw Things API
      console.error("send request to Draw Things API...");
      const response = await this.axios.post(
        "/sdapi/v1/txt2img",
        requestParams
      );

      // handle response
      if (
        !response.data ||
        !response.data.images ||
        response.data.images.length === 0
      ) {
        throw new Error("API did not return image data");
      }

      // handle image data
      const imageData = response.data.images[0];

      // format image data
      const formattedImageData = imageData.startsWith("data:image/")
        ? imageData
        : `data:image/png;base64,${imageData}`;

      console.error("image generation success");
      
      // record the start time of image generation
      const startTime = Date.now() - 2000; // assume the image generation took 2 seconds
      const endTime = Date.now();
      
      // automatically save the generated image
      const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
      const defaultFileName = `generated-image-${timestamp}.png`;
      
      // save the generated image
      const imagePath = await this.saveImage({
        base64Data: formattedImageData,
        fileName: defaultFileName
      });
      
      return {
        isError: false,
        imageData: formattedImageData,
        imagePath: imagePath,
        metadata: {
          alt: `Image generated from prompt: ${requestParams.prompt}`,
          inference_time_ms: endTime - startTime,
        }
      };
    } catch (error) {
      console.error("image generation error:", error);

      // error message
      let errorMessage = "unknown error";

      if (error instanceof Error) {
        errorMessage = error.message;
      }

      // handle axios error
      const axiosError = error as any;
      if (axiosError.response) {
        errorMessage = `API error: ${axiosError.response.status} - ${
          axiosError.response.data?.error || axiosError.message
        }`;
      } else if (axiosError.code === "ECONNREFUSED") {
        errorMessage =
          "cannot connect to Draw Things API. please ensure Draw Things is running and API is enabled.";
      } else if (axiosError.code === "ETIMEDOUT") {
        errorMessage =
          "connection to Draw Things API timeout. image generation may take longer, or API not responding.";
      }

      return {
        isError: true,
        errorMessage,
      };
    }
  }
}

```

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

```javascript
#!/usr/bin/env node

/**
 * Draw Things API Connection Test
 * 測試不同方式連接到 Draw Things API
 */

import http from 'http';
import https from 'https';
import axios from 'axios';
import fs from 'fs';

// 記錄檔
const logFile = 'api-connection-test.log';
function log(message) {
  const timestamp = new Date().toISOString();
  const logMessage = `${timestamp} - ${message}\n`;
  fs.appendFileSync(logFile, logMessage);
  console.log(message);
}

// 錯誤記錄
function logError(error, message = 'Error') {
  const timestamp = new Date().toISOString();
  const errorDetails = error instanceof Error ? 
    `${error.message}\n${error.stack}` : 
    String(error);
  const logMessage = `${timestamp} - [ERROR] ${message}: ${errorDetails}\n`;
  fs.appendFileSync(logFile, logMessage);
  console.error(`${message}: ${error.message}`);
}

// 讀取參數
const apiPort = process.env.DRAW_THINGS_API_PORT || 7888;
const apiProxyPort = process.env.PROXY_PORT || 7889;

log('Draw Things API Connection Test');
log('===========================');
log(`Testing API on port ${apiPort}`);
log(`Testing proxy on port ${apiProxyPort}`);
log('');

// 測試 1: 直接使用 HTTP 模組連接
async function testHttpModule() {
  log('Test 1: 使用 Node.js HTTP 模組連接');
  
  return new Promise((resolve) => {
    try {
      const urls = [
        { name: '直接 API 連接 (127.0.0.1)', host: '127.0.0.1', port: apiPort },
        { name: '直接 API 連接 (localhost)', host: 'localhost', port: apiPort },
        { name: '代理伺服器連接 (127.0.0.1)', host: '127.0.0.1', port: apiProxyPort },
        { name: '代理伺服器連接 (localhost)', host: 'localhost', port: apiProxyPort }
      ];
      
      let completedTests = 0;
      const results = [];

      for (const url of urls) {
        log(`測試連接: ${url.name}`);
        
        const options = {
          hostname: url.host,
          port: url.port,
          path: '/sdapi/v1/options',
          method: 'GET',
          timeout: 5000,
          headers: {
            'User-Agent': 'DrawThingsMCP/1.0',
            'Accept': 'application/json'
          }
        };
        
        const req = http.request(options, (res) => {
          log(`${url.name} 回應狀態碼: ${res.statusCode}`);
          
          let data = '';
          res.on('data', chunk => {
            data += chunk;
          });
          
          res.on('end', () => {
            const success = res.statusCode >= 200 && res.statusCode < 300;
            results.push({
              name: url.name,
              success,
              statusCode: res.statusCode,
              hasData: !!data
            });
            
            completedTests++;
            if (completedTests === urls.length) {
              resolve(results);
            }
          });
        });
        
        req.on('error', (e) => {
          log(`${url.name} 錯誤: ${e.message}`);
          results.push({
            name: url.name,
            success: false,
            error: e.message
          });
          
          completedTests++;
          if (completedTests === urls.length) {
            resolve(results);
          }
        });
        
        req.on('timeout', () => {
          log(`${url.name} 連接逾時`);
          req.destroy();
          
          results.push({
            name: url.name,
            success: false,
            error: 'Timeout'
          });
          
          completedTests++;
          if (completedTests === urls.length) {
            resolve(results);
          }
        });
        
        req.end();
      }
    } catch (error) {
      logError(error, 'HTTP 模組測試發生錯誤');
      resolve([]);
    }
  });
}

// 測試 2: 使用 Axios 連接
async function testAxios() {
  log('Test 2: 使用 Axios 連接');
  
  try {
    const urls = [
      { name: '直接 API 連接 (127.0.0.1)', url: `http://127.0.0.1:${apiPort}/sdapi/v1/options` },
      { name: '直接 API 連接 (localhost)', url: `http://localhost:${apiPort}/sdapi/v1/options` },
      { name: '代理伺服器連接 (127.0.0.1)', url: `http://127.0.0.1:${apiProxyPort}/sdapi/v1/options` },
      { name: '代理伺服器連接 (localhost)', url: `http://localhost:${apiProxyPort}/sdapi/v1/options` },
    ];
    
    const results = [];
    
    for (const url of urls) {
      log(`測試連接: ${url.name}`);
      
      try {
        const response = await axios.get(url.url, {
          timeout: 5000,
          headers: {
            'User-Agent': 'DrawThingsMCP/1.0',
            'Accept': 'application/json'
          }
        });
        
        log(`${url.name} 回應狀態碼: ${response.status}`);
        
        results.push({
          name: url.name,
          success: response.status >= 200 && response.status < 300,
          statusCode: response.status,
          hasData: !!response.data
        });
      } catch (error) {
        log(`${url.name} 錯誤: ${error.message}`);
        
        results.push({
          name: url.name,
          success: false,
          error: error.message
        });
      }
    }
    
    return results;
  } catch (error) {
    logError(error, 'Axios 測試發生錯誤');
    return [];
  }
}

// 測試 3: 嘗試不同的端點
async function testDifferentEndpoints() {
  log('Test 3: 測試不同的 API 端點');
  
  try {
    // 使用工作正常的連接方式 (localhost 或 127.0.0.1)
    const baseUrl = `http://127.0.0.1:${apiPort}`;
    
    const endpoints = [
      '/sdapi/v1/options',
      '/sdapi/v1/samplers',
      '/sdapi/v1/sd-models',
      '/sdapi/v1/prompt-styles',
      '/'
    ];
    
    const results = [];
    
    for (const endpoint of endpoints) {
      log(`測試端點: ${endpoint}`);
      
      try {
        const response = await axios.get(`${baseUrl}${endpoint}`, {
          timeout: 5000,
          headers: {
            'User-Agent': 'DrawThingsMCP/1.0',
            'Accept': 'application/json'
          }
        });
        
        log(`端點 ${endpoint} 回應狀態碼: ${response.status}`);
        
        results.push({
          endpoint,
          success: response.status >= 200 && response.status < 300,
          statusCode: response.status,
          hasData: !!response.data
        });
      } catch (error) {
        log(`端點 ${endpoint} 錯誤: ${error.message}`);
        
        results.push({
          endpoint,
          success: false,
          error: error.message
        });
      }
    }
    
    return results;
  } catch (error) {
    logError(error, '端點測試發生錯誤');
    return [];
  }
}

// 執行測試
async function runTests() {
  try {
    // 測試 1: HTTP 模組
    log('\n執行 HTTP 模組測試...');
    const httpResults = await testHttpModule();
    
    log('\nHTTP 模組測試結果:');
    httpResults.forEach(result => {
      log(`${result.name}: ${result.success ? '成功' : '失敗'} ${result.statusCode ? `(狀態碼: ${result.statusCode})` : ''} ${result.error ? `(錯誤: ${result.error})` : ''}`);
    });
    
    // 測試 2: Axios
    log('\n執行 Axios 測試...');
    const axiosResults = await testAxios();
    
    log('\nAxios 測試結果:');
    axiosResults.forEach(result => {
      log(`${result.name}: ${result.success ? '成功' : '失敗'} ${result.statusCode ? `(狀態碼: ${result.statusCode})` : ''} ${result.error ? `(錯誤: ${result.error})` : ''}`);
    });
    
    // 測試 3: 不同端點
    log('\n執行不同端點測試...');
    const endpointResults = await testDifferentEndpoints();
    
    log('\n不同端點測試結果:');
    endpointResults.forEach(result => {
      log(`端點 ${result.endpoint}: ${result.success ? '成功' : '失敗'} ${result.statusCode ? `(狀態碼: ${result.statusCode})` : ''} ${result.error ? `(錯誤: ${result.error})` : ''}`);
    });
    
    // 總結
    const httpSuccess = httpResults.some(r => r.success);
    const axiosSuccess = axiosResults.some(r => r.success);
    const endpointSuccess = endpointResults.some(r => r.success);
    
    log('\n=== 測試總結 ===');
    log(`HTTP 模組連接測試: ${httpSuccess ? '至少有一個成功' : '全部失敗'}`);
    log(`Axios 連接測試: ${axiosSuccess ? '至少有一個成功' : '全部失敗'}`);
    log(`端點測試: ${endpointSuccess ? '至少有一個成功' : '全部失敗'}`);
    
    if (httpSuccess || axiosSuccess) {
      log('\nAPI 連接測試成功! 您的 Draw Things API 似乎可以正常工作。');
      
      // 建議最佳連接方式
      const bestConnection = [...httpResults, ...axiosResults].find(r => r.success);
      if (bestConnection) {
        log(`建議使用連接方式: ${bestConnection.name}`);
      }
    } else {
      log('\nAPI 連接測試失敗! 請確認:');
      log('1. Draw Things 應用程式正在運行');
      log('2. Draw Things 已啟用 API 功能');
      log('3. API 在設定的端口上運行 (默認 7888)');
      log('4. 沒有防火牆阻擋連接');
    }
    
  } catch (error) {
    logError(error, '測試執行過程中發生錯誤');
  }
}

// 執行測試
runTests().catch(error => {
  logError(error, '測試主程序發生錯誤');
}); 
```

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

```typescript
#!/usr/bin/env node

/**
 * Draw Things MCP - A Model Context Protocol implementation for Draw Things API
 * Integrated with Cursor MCP Bridge functionality for multiple input formats
 *
 * NOTE: Requires Node.js version 14+ for optional chaining support in dependencies
 */

import path from "path";
import fs from "fs";
import { fileURLToPath } from "url";
import { z } from "zod";

// MCP SDK imports
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";

// Local service imports
import { DrawThingsService } from "./services/drawThingsService.js";
import {
  ImageGenerationParameters,
  ImageGenerationResult,
} from "./services/schemas.js";

// Constants and environment variables
const DEBUG_MODE: boolean = process.env.DEBUG_MODE === "true";
// Get current file path in ESM
const __filename = fileURLToPath(import.meta.url);
// Get directory name
const __dirname = path.dirname(__filename);
const projectRoot: string = path.resolve(__dirname, "..");
const logsDir: string = path.join(projectRoot, "logs");

// Create logs directory if it doesn't exist
try {
  if (!fs.existsSync(logsDir)) {
    fs.mkdirSync(logsDir, { recursive: true });
    console.error(`Created logs directory: ${logsDir}`);
  }
} catch (error) {
  console.error(
    `Failed to create logs directory: ${
      error instanceof Error ? error.message : String(error)
    }`
  );
}

const logFile: string = path.join(logsDir, "draw-things-mcp.log");

// Basic logging function
function log(message: string): void {
  const timestamp = new Date().toISOString();
  const logMessage = `${timestamp} - ${message}\n`;
  try {
    fs.appendFileSync(logFile, logMessage);
    // Only output to stderr to avoid polluting JSON-RPC communication
    console.error(logMessage);
  } catch (error) {
    console.error(
      `Failed to write to log file: ${
        error instanceof Error ? error.message : String(error)
      }`
    );
  }
}

// Enhanced error logging to dedicated error log file
async function logError(error: Error | unknown): Promise<void> {
  try {
    const errorLogFile = path.join(logsDir, "error.log");
    const timestamp = new Date().toISOString();
    const errorDetails =
      error instanceof Error
        ? `${error.message}\n${error.stack}`
        : String(error);

    const errorLog = `${timestamp} - ERROR: ${errorDetails}\n\n`;

    try {
      await fs.promises.appendFile(errorLogFile, errorLog);

      log(`Error logged to ${errorLogFile}`);

      if (DEBUG_MODE) {
        console.error(`\n[DEBUG] FULL ERROR DETAILS:\n${errorDetails}\n`);
      }
    } catch (writeError) {
      // Fallback to sync writing
      try {
        fs.appendFileSync(errorLogFile, errorLog);
      } catch (syncWriteError) {
        console.error(
          `Failed to write to error log: ${
            syncWriteError instanceof Error
              ? syncWriteError.message
              : String(syncWriteError)
          }`
        );
        console.error(`Original error: ${errorDetails}`);
      }
    }
  } catch (logError) {
    console.error("Critical error in error logging system:");
    console.error(logError);
    console.error("Original error:");
    console.error(error);
  }
}

// Print connection information and help message on startup
function printConnectionInfo(): void {
  // Only print to stderr to avoid polluting JSON-RPC communication
  const infoText = `
---------------------------------------------
| Draw Things MCP - Image Generation Service |
---------------------------------------------

Attempting to connect to Draw Things API at:
    http://127.0.0.1:7888

TROUBLESHOOTING TIPS:
1. Ensure Draw Things is running on your computer
2. Make sure the API is enabled in Draw Things settings
3. If you changed the default port in Draw Things, set the environment variable:
   DRAW_THINGS_API_URL=http://127.0.0.1:YOUR_PORT 

Starting service...
`;

  // Log to file and stderr
  log(infoText);
}

const drawThingsService = new DrawThingsService();

const server = new McpServer({
  name: "draw-things-mcp",
  version: "1.0.0",
});

// Define the image generation tool schema
const paramsSchema = {
  prompt: z.string().optional(),
  negative_prompt: z.string().optional(),
  width: z.number().optional(),
  height: z.number().optional(),
  steps: z.number().optional(),
  seed: z.number().optional(),
  guidance_scale: z.number().optional(),
  random_string: z.string().optional(),
};

server.tool(
  "generateImage",
  "Generate an image based on a prompt",
  paramsSchema,
  async (mcpParams: any) => {
    try {
      log("Received image generation request");
      log(`mcpParams====== ${JSON.stringify(mcpParams)}`);
      // handle ai prompts
      const parameters =
        mcpParams?.params?.arguments || mcpParams?.arguments || mcpParams || {};

      if (parameters.prompt) {
        log(`Using provided prompt: ${parameters.prompt}`);
      } else {
        log("No prompt provided, using default");
        parameters.prompt = "A cute dog";
      }

      // Generate image
      const result: ImageGenerationResult =
        await drawThingsService.generateImage(parameters);

      // Handle generation result
      if (result.isError) {
        log(`Error generating image: ${result.errorMessage}`);
        throw new Error(result.errorMessage || "Unknown error");
      }

      if (!result.imageData && (!result.images || result.images.length === 0)) {
        log("No image data returned from generation");
        throw new Error("No image data returned from generation");
      }

      const imageData =
        result.imageData ||
        (result.images && result.images.length > 0
          ? result.images[0]
          : undefined);
      if (!imageData) {
        log("No valid image data available");
        throw new Error("No valid image data available");
      }

      log("Successfully generated image, returning directly via MCP");

      // calculate the difference between the start and end time (example value)
      const startTime = Date.now() - 2000; // assume the image generation took 2 seconds
      const endTime = Date.now();

      // build the response format
      const responseData = {
        image_paths: result.imagePath ? [result.imagePath] : [],
        metadata: {
          alt: `Image generated from prompt: ${parameters.prompt}`,
          inference_time_ms:
            result.metadata?.inference_time_ms || endTime - startTime,
        },
      };

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(responseData, null, 2),
          },
        ],
      };
    } catch (error) {
      log(
        `Error handling image generation: ${
          error instanceof Error ? error.message : String(error)
        }`
      );
      await logError(error);
      throw error;
    }
  }
);

// Main program
async function main(): Promise<void> {
  try {
    log("Starting Draw Things MCP service...");

    // Print connection info to the console
    printConnectionInfo();

    log("Initializing Draw Things MCP service");

    // Enhanced API connection verification with direct method
    log("Checking Draw Things API connection before starting service...");
    const apiPort = process.env.DRAW_THINGS_API_PORT || 7888;

    // Final drawThingsService connection check
    const isApiConnected = await drawThingsService.checkApiConnection();
    if (!isApiConnected) {
      log("\nFAILED TO CONNECT TO DRAW THINGS API");
      log("Please make sure Draw Things is running and the API is enabled.");
      log(
        "The service will continue running, but image generation will not work until the API is available.\n"
      );
    } else {
      log("\nSUCCESSFULLY CONNECTED TO DRAW THINGS API");
      log("The service is ready to generate images.\n");
      drawThingsService.setBaseUrl(`http://127.0.0.1:${apiPort}`);
    }

    // Create transport and connect server
    log("Creating transport and connecting server...");
    const transport = new StdioServerTransport();

    // Connect server to transport
    log("Connecting server to transport...");
    await server.connect(transport);
    log("MCP Server started successfully!");
  } catch (error) {
    log(
      `Error in main program: ${
        error instanceof Error ? error.message : String(error)
      }`
    );
    await logError(error);
  }
}

main().catch(async (error) => {
  log("server.log", `${new Date().toISOString()} - ${error.stack || error}\n`);
  console.error(error);
  process.exit(1);
});

```

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

```javascript
#!/usr/bin/env node

/**
 * Draw Things MCP Test Script
 * 
 * This script is used to test whether the Draw Things MCP service can start normally and process image generation requests.
 * It simulates MCP client behavior, sending requests to the MCP service and handling responses.
 */

import { spawn } from 'child_process';
import { writeFile, mkdir } from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';

// Get the directory path of the current file
const __dirname = path.dirname(fileURLToPath(import.meta.url));

// Ensure test output directory exists
const testOutputDir = path.join(__dirname, 'test-output');
try {
  await mkdir(testOutputDir, { recursive: true });
  console.log(`Test output directory created: ${testOutputDir}`);
} catch (error) {
  if (error.code !== 'EEXIST') {
    console.error('Error creating test output directory:', error);
    process.exit(1);
  }
}

// MCP request example - format corrected to comply with MCP protocol
const mcpRequest = {
  jsonrpc: "2.0",
  id: "test-request-" + Date.now(),
  method: "mcp.invoke",
  params: {
    tool: "generateImage",
    parameters: {
      prompt: "Beautiful Taiwan landscape, mountain and water painting style"
    }
  }
};

// Save request to file
const requestFilePath = path.join(testOutputDir, 'mcp-request.json');
try {
  await writeFile(requestFilePath, JSON.stringify(mcpRequest, null, 2));
  console.log(`MCP request saved to: ${requestFilePath}`);
} catch (error) {
  console.error('Error saving MCP request:', error);
  process.exit(1);
}

console.log('Starting Draw Things MCP service for testing...');

// Start MCP service process
const mcpProcess = spawn('node', ['src/index.js'], {
  stdio: ['pipe', 'pipe', 'pipe']
});

// Add progress display timer
let waitTime = 0;
const progressInterval = setInterval(() => {
  waitTime += 30;
  console.log(`Waited ${waitTime} seconds... Image generation may take some time, please be patient`);
}, 30000);

// Cleanup function - called when terminating the service in any situation
function cleanup() {
  clearInterval(progressInterval);
  if (mcpProcess && !mcpProcess.killed) {
    mcpProcess.kill();
  }
}

// Record standard output
let stdoutData = '';
mcpProcess.stdout.on('data', (data) => {
  const dataStr = data.toString();
  console.log(`MCP standard output: ${dataStr}`);
  stdoutData += dataStr;
  
  try {
    // Try to parse output as JSON
    const lines = dataStr.trim().split('\n');
    for (const line of lines) {
      if (!line.trim()) continue;
      
      try {
        const jsonData = JSON.parse(line);
        console.log('Parsed JSON response:', JSON.stringify(jsonData).substring(0, 100) + '...');
        
        // If it's an MCP response, save and analyze
        if (jsonData.id && (jsonData.result || jsonData.error)) {
          console.log('Received MCP response ID:', jsonData.id);
          
          if (jsonData.error) {
            console.error('MCP error response:', jsonData.error);
            const errorFile = path.join(testOutputDir, 'mcp-error.json');
            writeFile(errorFile, JSON.stringify(jsonData, null, 2))
              .catch(e => console.error('Failed to write error file:', e));
          } else if (jsonData.result) {
            console.log('MCP successful response type:', jsonData.result.content?.[0]?.type || 'unknown');
            
            // Determine if the response contains an error
            if (jsonData.result.isError) {
              console.error('Error response:', jsonData.result.content[0].text);
              const errorResultFile = path.join(testOutputDir, 'mcp-error-result.json');
              writeFile(errorResultFile, JSON.stringify(jsonData, null, 2))
                .catch(e => console.error('Failed to write error result file:', e));
            } else {
              // Successful response, should contain image data
              console.log('Successfully generated image!');
              if (jsonData.result.content && jsonData.result.content[0].type === 'image') {
                const imageData = jsonData.result.content[0].data;
                console.log(`Image data size: ${imageData.length} characters`);
                
                // Use immediately executed async function
                (async function() {
                  try {
                    const savedImagePath = await saveImage(imageData);
                    console.log(`Image successfully saved to: ${savedImagePath}`);
                    
                    const successFile = path.join(testOutputDir, 'mcp-success.json');
                    await writeFile(successFile, JSON.stringify(jsonData.result, null, 2));
                    console.log('Successfully saved result information to JSON file');
                    
                    // Extend wait time to ensure all operations complete
                    setTimeout(() => {
                      console.log('Test completed, image processing successful, terminating MCP service...');
                      cleanup();
                      process.exit(0);
                    }, 3000); // Increased to 3 seconds
                  } catch (saveError) {
                    console.error('Error saving image or results:', saveError);
                    const errorFile = path.join(testOutputDir, 'mcp-save-error.json');
                    writeFile(errorFile, JSON.stringify({ error: saveError.message }, null, 2))
                      .catch(e => console.error('Failed to write error information:', e));
                    
                    // End test normally even if there's an error
                    setTimeout(() => {
                      console.log('Test completed, but errors occurred during image processing, terminating MCP service...');
                      cleanup();
                      process.exit(1);
                    }, 3000);
                  }
                })();
              }
            }
          }
        }
      } catch (parseError) {
        // Not valid JSON, might be regular log output
        // console.log('Non-JSON data:', line);
      }
    }
  } catch (error) {
    console.error('Error processing MCP output:', error);
  }
});

// Record standard error
mcpProcess.stderr.on('data', (data) => {
  const logMsg = data.toString().trim();
  console.log(`MCP service log: ${logMsg}`);
  
  // Monitor specific log messages to confirm service status
  if (logMsg.includes('MCP service is ready')) {
    console.log('Detected MCP service is ready, preparing to send request...');
    // Delay sending request
    setTimeout(() => {
      sendRequest();
    }, 1000);
  }
});

// Handle process exit
mcpProcess.on('close', (code) => {
  if (code !== 0 && code !== null) {
    console.error(`MCP service exited with code ${code}`);
    
    // Save output for diagnosis
    try {
      writeFile(path.join(testOutputDir, 'mcp-stdout.log'), stdoutData);
      console.log('MCP service standard output log saved');
    } catch (error) {
      console.error('Error saving output log:', error);
    }
    
    cleanup();
    process.exit(1);
  }
});

// Handle errors
mcpProcess.on('error', (error) => {
  console.error('Error starting MCP service:', error);
  cleanup();
  process.exit(1);
});

// Function to send MCP request
function sendRequest() {
  console.log('Sending image generation request...');
  console.log('Request content:', JSON.stringify(mcpRequest));
  
  // Ensure request string ends with newline
  const requestString = JSON.stringify(mcpRequest) + '\n';
  mcpProcess.stdin.write(requestString);
  console.log(`Sent ${requestString.length} bytes of request data`);
  console.log('\n========================================');
  console.log('Image generation has started, this may take a few minutes...');
  console.log('Wait progress will be displayed every 30 seconds');
  console.log('Please be patient, do not interrupt the test');
  console.log('========================================\n');
  
  // Save the raw request sent
  writeFile(path.join(testOutputDir, 'mcp-raw-request.txt'), requestString)
    .catch(e => console.error('Failed to save raw request:', e));
}

// Helper function: Save image
async function saveImage(base64Data) {
  try {
    // Create buffer from base64 string
    const imageBuffer = Buffer.from(base64Data, 'base64');
    
    // Save image to file
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const imagePath = path.join(testOutputDir, `generated-image-${timestamp}.png`);
    
    await writeFile(imagePath, imageBuffer);
    console.log(`Generated image saved to: ${imagePath}`);
    return imagePath;  // Make sure to return the saved path
  } catch (error) {
    console.error('Error saving image:', error);
    throw error;  // Throw error so the upper function can catch and handle it
  }
}

// Don't send request immediately, wait for service log to indicate readiness

// Timeout handling
setTimeout(() => {
  console.error('Test timeout, terminating MCP service...');
  writeFile(path.join(testOutputDir, 'mcp-timeout.log'), 'Test timed out after 300 seconds')
    .catch(e => console.error('Failed to save timeout log:', e));
  cleanup();
  process.exit(1);
}, 300000); // 5 minute timeout, providing more time to complete image generation 
```

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

```javascript
#!/usr/bin/env node

/**
 * Cursor MCP Bridge - Connect Cursor MCP and Draw Things API
 * Converts simple text prompts to proper JSON-RPC requests
 */

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import readline from 'readline';
import http from 'http';

// Set up log file
const logFile = 'cursor-mcp-bridge.log';
function log(message) {
  const timestamp = new Date().toISOString();
  const logMessage = `${timestamp} - ${message}\n`;
  fs.appendFileSync(logFile, logMessage);
  console.error(logMessage); // Also output to stderr for debugging
}

// Enhanced error logging
function logError(error, message = 'Error') {
  const timestamp = new Date().toISOString();
  const errorDetails = error instanceof Error ? 
    `${error.message}\n${error.stack}` : 
    String(error);
  const logMessage = `${timestamp} - [ERROR] ${message}: ${errorDetails}\n`;
  fs.appendFileSync(logFile, logMessage);
  console.error(logMessage);
}

// Initialize log
log('Cursor MCP Bridge started');
log('Waiting for input...');

// Get current directory path
const __dirname = path.dirname(fileURLToPath(import.meta.url));

// Ensure images directory exists
const imagesDir = path.join(__dirname, 'images');
if (!fs.existsSync(imagesDir)) {
  fs.mkdirSync(imagesDir, { recursive: true });
  log(`Created image storage directory: ${imagesDir}`);
}

// Verify API connection - direct connection check
async function verifyApiConnection() {
  // Read API port from environment or use default
  const apiPort = process.env.DRAW_THINGS_API_PORT || 7888;
  const apiUrl = process.env.DRAW_THINGS_API_URL || `http://127.0.0.1:${apiPort}`;
  
  log(`Verifying API connection to ${apiUrl}`);

  return new Promise((resolve) => {
    // Try multiple endpoints
    const endpoints = ['/sdapi/v1/options', '/sdapi/v1/samplers', '/'];
    let endpointIndex = 0;
    let retryCount = 0;
    const maxRetries = 2;

    const tryEndpoint = () => {
      if (endpointIndex >= endpoints.length) {
        log('All endpoints failed, API connection verification failed');
        resolve(false);
        return;
      }

      const endpoint = endpoints[endpointIndex];
      const url = new URL(endpoint, apiUrl);
      
      log(`Trying endpoint: ${url.toString()}`);

      const options = {
        hostname: url.hostname,
        port: url.port,
        path: url.pathname,
        method: 'GET',
        timeout: 5000,
        headers: {
          'User-Agent': 'DrawThingsMCP/1.0',
          'Accept': 'application/json'
        }
      };

      const req = http.request(options, (res) => {
        log(`API connection check response: ${res.statusCode}`);
        
        // Any 2xx response is good
        if (res.statusCode >= 200 && res.statusCode < 300) {
          log('API connection verified successfully');
          resolve(true);
          return;
        }
        
        // Try next endpoint
        endpointIndex++;
        retryCount = 0;
        tryEndpoint();
      });

      req.on('error', (e) => {
        log(`API connection error (${endpoint}): ${e.message}`);
        
        // Retry same endpoint a few times
        if (retryCount < maxRetries) {
          retryCount++;
          log(`Retrying ${endpoint} (attempt ${retryCount}/${maxRetries})...`);
          setTimeout(tryEndpoint, 1000);
          return;
        }
        
        // Move to next endpoint
        endpointIndex++;
        retryCount = 0;
        tryEndpoint();
      });

      req.on('timeout', () => {
        log(`API connection timeout (${endpoint})`);
        req.destroy();
        
        // Retry same endpoint a few times
        if (retryCount < maxRetries) {
          retryCount++;
          log(`Retrying ${endpoint} after timeout (attempt ${retryCount}/${maxRetries})...`);
          setTimeout(tryEndpoint, 1000);
          return;
        }
        
        // Move to next endpoint
        endpointIndex++;
        retryCount = 0;
        tryEndpoint();
      });

      req.end();
    };

    tryEndpoint();
  });
}

// Initial API verification
let apiVerified = false;
verifyApiConnection().then(result => {
  apiVerified = result;
  if (result) {
    log('API connection verified on startup');
  } else {
    log('API connection verification failed on startup - will retry on requests');
  }
});

// Set up readline interface
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: false
});

// Listen for line input
rl.on('line', async (line) => {
  log(`Received input: ${line.substring(0, 100)}${line.length > 100 ? '...' : ''}`);
  
  // If API connection hasn't been verified yet, try again
  if (!apiVerified) {
    apiVerified = await verifyApiConnection();
    if (!apiVerified) {
      log('API connection still not available');
      // Return error response but continue processing the request
      // This allows the MCP service to handle the error properly
    }
  }
  
  // Check if input is already in JSON format
  try {
    const jsonInput = JSON.parse(line);
    log('Input is valid JSON, checking if it conforms to JSON-RPC 2.0 standard');
    
    // Check if it conforms to JSON-RPC 2.0 standard
    if (jsonInput.jsonrpc === "2.0" && jsonInput.method && jsonInput.id) {
      log('Input already conforms to JSON-RPC 2.0 standard, forwarding directly');
      process.stdout.write(line + '\n');
      return;
    } else {
      log('JSON format is valid but does not conform to JSON-RPC 2.0 standard, converting');
      processRequest(jsonInput);
    }
    return;
  } catch (e) {
    log(`Input is not valid JSON: ${e.message}`);
  }
  
  // Check if it's a plain text prompt from Cursor
  if (line && typeof line === 'string' && !line.startsWith('{')) {
    log('Detected plain text prompt, converting to JSON-RPC request');
    
    // Create request conforming to JSON-RPC 2.0 standard
    const request = {
      jsonrpc: "2.0",
      id: Date.now().toString(),
      method: "mcp.invoke",
      params: {
        tool: "generateImage",
        parameters: {
          prompt: line.trim()
        }
      }
    };
    
    processRequest(request);
  } else {
    log('Unrecognized input format, cannot process');
    sendErrorResponse('Unrecognized input format', "parse_error", -32700);
  }
});

// Process request
function processRequest(request) {
  log(`Processing request: ${JSON.stringify(request).substring(0, 100)}...`);
  
  try {
    // Ensure request has the correct structure
    if (!request.jsonrpc) request.jsonrpc = "2.0";
    if (!request.id) request.id = Date.now().toString();
    
    // If no method, set to mcp.invoke
    if (!request.method) {
      request.method = "mcp.invoke";
    }
    
    // Process params
    if (!request.params) {
      // Try to build params from different sources
      if (request.prompt || request.parameters) {
        request.params = {
          tool: "generateImage",
          parameters: request.prompt 
            ? { prompt: request.prompt } 
            : (request.parameters || {})
        };
      } else {
        // No usable parameters found
        log('No usable parameters found, using empty object');
        request.params = {
          tool: "generateImage",
          parameters: {}
        };
      }
    } else if (!request.params.tool) {
      // Ensure there's a tool parameter
      request.params.tool = "generateImage";
    }
    
    // Ensure there are parameters
    if (!request.params.parameters) {
      request.params.parameters = {};
    }
    
    // Add API verification status to the request for debugging
    if (!apiVerified) {
      log('Warning: Adding API status information to request');
      request.params.parameters._apiVerified = apiVerified;
    }
    
    log(`Final request: ${JSON.stringify(request).substring(0, 150)}...`);
    process.stdout.write(JSON.stringify(request) + '\n');
  } catch (error) {
    logError(error, 'Error processing request');
    sendErrorResponse(`Error processing request: ${error.message}`, "internal_error", -32603);
  }
}

// Send error response conforming to JSON-RPC 2.0
function sendErrorResponse(message, errorType = "invalid_request", code = -32600) {
  const errorResponse = {
    jsonrpc: "2.0",
    id: "error-" + Date.now(),
    error: {
      code: code,
      message: errorType,
      data: message
    }
  };
  
  log(`Sending error response: ${JSON.stringify(errorResponse)}`);
  process.stdout.write(JSON.stringify(errorResponse) + '\n');
}

// Buffer for assembling complete JSON responses
let responseBuffer = '';

// Handle responses from the MCP service
process.stdin.on('data', (data) => {
  try {
    const dataStr = data.toString();
    log(`Received data chunk from MCP service: ${dataStr.substring(0, 100)}${dataStr.length > 100 ? '...' : ''}`);
    
    // Append to buffer to handle chunked responses
    responseBuffer += dataStr;
    
    // Check if we have a complete JSON object by trying to find matching braces
    if (isCompleteJson(responseBuffer)) {
      log('Detected complete JSON response, processing');
      processCompleteResponse(responseBuffer);
      responseBuffer = ''; // Clear buffer after processing
    } else {
      log('Incomplete JSON detected, buffering for more data');
    }
  } catch (error) {
    logError(error, 'Error handling MCP service data');
    
    // If there's an error, try to forward the original data as a fallback
    try {
      process.stdout.write(data);
    } catch (writeError) {
      logError(writeError, 'Error forwarding original data');
    }
  }
});

// Check if a string contains a complete JSON object
function isCompleteJson(str) {
  try {
    JSON.parse(str);
    return true;
  } catch (e) {
    // Not complete or not valid JSON
    // Try basic brace matching as a fallback
    let openBraces = 0;
    let insideString = false;
    let escapeNext = false;
    
    for (let i = 0; i < str.length; i++) {
      const char = str[i];
      
      if (escapeNext) {
        escapeNext = false;
        continue;
      }
      
      if (char === '\\' && insideString) {
        escapeNext = true;
        continue;
      }
      
      if (char === '"') {
        insideString = !insideString;
        continue;
      }
      
      if (!insideString) {
        if (char === '{') openBraces++;
        if (char === '}') openBraces--;
      }
    }
    
    // Complete JSON object should have matching braces
    return openBraces === 0 && str.trim().startsWith('{') && str.trim().endsWith('}');
  }
}

// Process a complete response
function processCompleteResponse(responseStr) {
  try {
    log(`Processing complete response: ${responseStr.substring(0, 100)}${responseStr.length > 100 ? '...' : ''}`);
    
    // Check for API error messages and update connection status
    if (responseStr.includes("Draw Things API is not running or cannot be connected")) {
      log('Detected API connection error in response');
      // Trigger a new API verification
      verifyApiConnection().then(result => {
        apiVerified = result;
        log(`API verification after error message: ${apiVerified ? 'successful' : 'failed'}`);
      });
    }
    
    // Try to parse as JSON
    const response = JSON.parse(responseStr);
    log('Successfully parsed MCP service response as JSON');
    
    // Check if it's an image generation result
    if (response.result && response.result.content) {
      // Find image content
      const imageContent = response.result.content.find(item => item.type === 'image');
      if (imageContent && imageContent.data) {
        // Save the image
        const timestamp = Date.now();
        const imagePath = path.join(imagesDir, `image_${timestamp}.png`);
        
        // Remove data:image/png;base64, prefix
        const base64Data = imageContent.data.replace(/^data:image\/\w+;base64,/, '');
        
        fs.writeFileSync(imagePath, Buffer.from(base64Data, 'base64'));
        log(`Image saved to: ${imagePath}`);
        
        // Add saved path info to the response
        response.result.imageSavedPath = imagePath;
        
        // Successful image generation indicates API is working
        apiVerified = true;
      }
    }
    
    // Forward the processed response
    process.stdout.write(JSON.stringify(response) + '\n');
  } catch (error) {
    logError(error, 'Error processing complete response');
    
    // Try to convert non-JSON response to proper JSON-RPC
    if (responseStr.trim() && !responseStr.trim().startsWith('{')) {
      log('Converting non-JSON response to proper JSON-RPC response');
      
      // Create a JSON-RPC response with the text as content
      const jsonResponse = {
        jsonrpc: "2.0",
        id: "response-" + Date.now(),
        result: {
          content: [{
            type: 'text',
            text: responseStr.trim()
          }]
        }
      };
      
      process.stdout.write(JSON.stringify(jsonResponse) + '\n');
    } else {
      // Forward original response as fallback
      log('Forwarding original response as fallback');
      process.stdout.write(responseStr + '\n');
    }
  }
}

// Handle end of input
rl.on('close', () => {
  log('Input stream closed, program ending');
  process.exit(0);
});

// Handle errors
process.on('uncaughtException', (error) => {
  logError(error, 'Uncaught exception');
  sendErrorResponse(`Error processing request: ${error.message}`, "internal_error", -32603);
});

log('Bridge service ready, waiting for Cursor input...');

// Periodically check API connection
setInterval(async () => {
  const prevStatus = apiVerified;
  apiVerified = await verifyApiConnection();
  
  if (prevStatus !== apiVerified) {
    log(`API connection status changed: ${prevStatus} -> ${apiVerified}`);
  }
}, 60000); // Check every minute 
```