← All articles

ComfyUI MCP: Complete Setup Guide & Best Practices

July 5, 2026·22 min read·MCPForge

What Is ComfyUI MCP?

ComfyUI MCP is an implementation of the Model Context Protocol (MCP) that wraps ComfyUI's image-generation engine as a set of callable tools, resources, and prompts — making it natively accessible to LLM clients like Claude Desktop, Cursor, and any other MCP-compatible host.

In practical terms: instead of writing Python scripts or REST calls to trigger a ComfyUI workflow, you describe what you want in natural language and let Claude or another AI model invoke ComfyUI on your behalf. The MCP server handles workflow construction, parameter injection, job submission, polling, and result retrieval.

Why this matters:

  • AI agents can generate, edit, and iterate on images autonomously inside a conversation
  • No manual workflow JSON editing for every generation task
  • ComfyUI's full node ecosystem becomes composable with LLM reasoning
  • Image generation becomes a first-class step in multi-tool AI pipelines

Architecture Overview

Understanding the data flow before you install anything saves hours of debugging.

┌─────────────────────────────────────────────────────────┐
│                    LLM Client Layer                      │

Want to analyze your API security?

Import your OpenAPI spec and generate a Security Report automatically.

│ Claude Desktop / Cursor / Custom Host │ └───────────────────────┬─────────────────────────────────┘ │ MCP Protocol (JSON-RPC 2.0) │ Transport: stdio | SSE | HTTP ┌───────────────────────▼─────────────────────────────────┐ │ ComfyUI MCP Server │ │ - Tool manifest (generate_image, img2img, upscale…) │ │ - Workflow templates │ │ - Parameter validation │ │ - Auth middleware │ │ - Result caching (optional) │ └───────────────────────┬─────────────────────────────────┘ │ HTTP REST / WebSocket ┌───────────────────────▼─────────────────────────────────┐ │ ComfyUI Backend │ │ - /prompt endpoint (job submission) │ │ - /history endpoint (result polling) │ │ - /upload/image endpoint (img2img input) │ │ - Custom nodes (ControlNet, IPAdapter, SDXL…) │ └───────────────────────┬─────────────────────────────────┘ │ GPU / Model Checkpoints


**Key architectural insight:** The MCP server is stateless with respect to inference. It translates tool calls into ComfyUI workflow JSON, submits them, and streams results back. This means you can scale the MCP server horizontally without touching ComfyUI.

---

## Prerequisites

Before installing anything, confirm you have:

| Requirement | Minimum Version | Notes |
|---|---|---|
| ComfyUI | Latest stable | Running and accessible at `http://localhost:8188` |
| Python | 3.10+ | For Python-based MCP servers |
| Node.js | 18+ | For TypeScript/Node-based implementations |
| Claude Desktop | Latest | For stdio integration |
| Cursor | 0.40+ | For Cursor MCP integration |
| Git | Any | To clone the MCP server repo |

ComfyUI must be running **before** you start the MCP server. The server performs a startup health check against the ComfyUI API. If ComfyUI is unreachable, the MCP server will exit.

---

## Installation

### Option A: comfyui-mcp-server (Python, recommended)

This is the most actively maintained open-source implementation. It supports stdio and SSE transports, workflow templating, and tool filtering.

```bash
# Clone the server
git clone https://github.com/your-org/comfyui-mcp-server
cd comfyui-mcp-server

# Create an isolated environment
python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt

# Copy and edit configuration
cp config.example.yaml config.yaml

Note: Always use a virtual environment. Installing into the global Python environment causes dependency conflicts with ComfyUI's own packages, especially torch and Pillow.

Option B: Using uvx (zero-install for quick testing)

If the server is published to PyPI:

bash
uvx comfyui-mcp-server --comfyui-url http://localhost:8188

This is useful for evaluating the server before committing to a full installation. Do not use uvx in production — pin the version explicitly.

Option C: npm/npx (TypeScript implementation)

bash
npx comfyui-mcp

Or install globally:

bash
npm install -g comfyui-mcp
comfyui-mcp --config ./config.json

Configuration

Core Configuration File

A complete config.yaml with every significant option explained:

yaml
# config.yaml

server:
  name: "comfyui-mcp"
  version: "1.0.0"
  transport: "stdio"          # stdio | sse | streamable-http
  host: "0.0.0.0"             # Only used for sse/http transport
  port: 3001                  # Only used for sse/http transport

comfyui:
  url: "http://localhost:8188"
  timeout_seconds: 120        # Max wait for a generation job
  poll_interval_ms: 500       # How often to check job status
  max_concurrent_jobs: 2      # Prevent overloading the GPU

authentication:
  enabled: false              # Set true for production
  type: "bearer"              # bearer | api_key | none
  token: "${MCP_AUTH_TOKEN}"  # Reference env var, never hardcode

tools:
  # Whitelist which tools to expose. Omit to expose all.
  enabled:
    - generate_image
    - image_to_image
    - inpaint
    - upscale
    - describe_image         # Uses a vision model in ComfyUI

workflows:
  directory: "./workflows"    # Directory of workflow template JSON files
  default_model: "dreamshaper_8.safetensors"
  default_steps: 25
  default_cfg: 7.0
  default_sampler: "dpmpp_2m"
  default_scheduler: "karras"
  default_width: 1024
  default_height: 1024

resources:
  expose_models: true         # Let clients list available checkpoints
  expose_loras: true
  expose_samplers: true
  models_refresh_interval: 300  # Seconds between model list refresh

caching:
  enabled: true
  backend: "memory"           # memory | redis
  ttl_seconds: 3600
  max_entries: 100

logging:
  level: "info"               # debug | info | warn | error
  format: "json"              # json | text
  file: "./logs/mcp-server.log"

Environment Variables

Never store secrets in config.yaml. Use environment variables:

bash
# .env (add to .gitignore immediately)
MCP_AUTH_TOKEN=your-secret-token-here
COMFYUI_URL=http://localhost:8188
COMFYUI_API_KEY=            # If your ComfyUI instance requires auth
REDIS_URL=redis://localhost:6379  # For distributed caching

Load with:

bash
export $(cat .env | xargs)
python -m comfyui_mcp_server --config config.yaml

Workflow Templates

Workflow templates are the core abstraction. Each template is a ComfyUI workflow JSON file with parameter placeholders. The MCP server injects tool call arguments into these placeholders before submission.

Text-to-Image Workflow Template

json
{
  "__metadata__": {
    "tool_name": "generate_image",
    "description": "Generate an image from a text prompt using Stable Diffusion",
    "parameters": {
      "prompt": { "type": "string", "required": true },
      "negative_prompt": { "type": "string", "default": "" },
      "model": { "type": "string", "default": "${default_model}" },
      "width": { "type": "integer", "default": 1024, "min": 64, "max": 2048 },
      "height": { "type": "integer", "default": 1024, "min": 64, "max": 2048 },
      "steps": { "type": "integer", "default": 25, "min": 1, "max": 100 },
      "cfg": { "type": "number", "default": 7.0 },
      "seed": { "type": "integer", "default": -1 }
    }
  },
  "4": {
    "inputs": {
      "ckpt_name": "{{model}}"
    },
    "class_type": "CheckpointLoaderSimple"
  },
  "6": {
    "inputs": {
      "text": "{{prompt}}",
      "clip": ["4", 1]
    },
    "class_type": "CLIPTextEncode"
  },
  "7": {
    "inputs": {
      "text": "{{negative_prompt}}",
      "clip": ["4", 1]
    },
    "class_type": "CLIPTextEncode"
  },
  "5": {
    "inputs": {
      "width": "{{width}}",
      "height": "{{height}}",
      "batch_size": 1
    },
    "class_type": "EmptyLatentImage"
  },
  "3": {
    "inputs": {
      "seed": "{{seed}}",
      "steps": "{{steps}}",
      "cfg": "{{cfg}}",
      "sampler_name": "dpmpp_2m",
      "scheduler": "karras",
      "denoise": 1.0,
      "model": ["4", 0],
      "positive": ["6", 0],
      "negative": ["7", 0],
      "latent_image": ["5", 0]
    },
    "class_type": "KSampler"
  },
  "8": {
    "inputs": {
      "samples": ["3", 0],
      "vae": ["4", 2]
    },
    "class_type": "VAEDecode"
  },
  "9": {
    "inputs": {
      "filename_prefix": "mcp_output",
      "images": ["8", 0]
    },
    "class_type": "SaveImage"
  }
}

The __metadata__ block is stripped before submission to ComfyUI — it exists only for the MCP server to build the tool's JSON Schema and validate incoming parameters.


Claude Desktop Integration

Claude Desktop reads MCP server configuration from its config file. This is the stdio transport path — no network port needed.

Step 1: Locate the Claude Desktop config file

bash
# macOS
~/Library/Application Support/Claude/claude_desktop_config.json

# Windows
%APPDATA%\Claude\claude_desktop_config.json

# Linux
~/.config/Claude/claude_desktop_config.json

Step 2: Add the ComfyUI MCP server entry

json
{
  "mcpServers": {
    "comfyui": {
      "command": "/absolute/path/to/.venv/bin/python",
      "args": [
        "-m",
        "comfyui_mcp_server",
        "--config",
        "/absolute/path/to/config.yaml"
      ],
      "env": {
        "MCP_AUTH_TOKEN": "your-token-here",
        "COMFYUI_URL": "http://localhost:8188"
      }
    }
  }
}

Critical: Use absolute paths everywhere. Claude Desktop spawns the server process from an unpredictable working directory, so relative paths will fail silently.

Step 3: Restart Claude Desktop

Claude Desktop does not hot-reload config. Fully quit (Cmd+Q on macOS, not just close the window) and reopen.

Step 4: Verify tool discovery

In a new Claude conversation, ask:

What tools do you have available?

You should see comfyui listed with tools like generate_image, image_to_image, etc. If you don't, check the Claude Desktop logs:

bash
# macOS
tail -f ~/Library/Logs/Claude/mcp-server-comfyui.log

Example Claude conversation with ComfyUI MCP

User: Generate a photorealistic image of a red fox sitting in a snowy forest 
at golden hour, high detail, 8k

Claude: I'll generate that image for you using ComfyUI.
[Calling generate_image with prompt="photorealistic red fox sitting in snowy 
forest, golden hour lighting, high detail, 8k, professional photography" 
width=1024 height=1024 steps=30 cfg=7]

Claude: Here's your image! The fox is rendered with warm golden-hour light 
contrasting against the cool blue snow. Would you like me to try a different 
angle or style?

Cursor Integration

Cursor supports MCP through its settings panel. You can use either stdio (local) or SSE (remote) transport.

stdio (local ComfyUI)

Open Cursor Settings → Features → MCP and add:

json
{
  "mcpServers": {
    "comfyui": {
      "command": "python",
      "args": ["-m", "comfyui_mcp_server", "--config", "/path/to/config.yaml"],
      "env": {
        "COMFYUI_URL": "http://localhost:8188"
      }
    }
  }
}

SSE (remote ComfyUI, e.g., RunPod or Lambda Labs)

First, run the MCP server in SSE mode on your GPU instance:

bash
python -m comfyui_mcp_server \
  --transport sse \
  --port 3001 \
  --comfyui-url http://localhost:8188

Then in Cursor:

json
{
  "mcpServers": {
    "comfyui-remote": {
      "url": "https://your-gpu-instance.example.com:3001/sse",
      "headers": {
        "Authorization": "Bearer your-token-here"
      }
    }
  }
}

Production note: The SSE endpoint must be behind TLS. Cursor and Claude Desktop both reject non-HTTPS SSE connections in their current versions.

Using ComfyUI tools inside Cursor

In Cursor's AI panel (Cmd+L), you can now prompt:

Generate a hero image for this landing page — clean, minimalist product 
photo of a wireless headphone on white background, studio lighting.

Cursor will call generate_image and embed the result path in your conversation. You can then reference it in your code with <img src="generated/mcp_output_00001_.png">.


Tool Reference

A well-implemented ComfyUI MCP server exposes these tools:

generate_image

json
{
  "name": "generate_image",
  "description": "Generate an image from a text prompt using Stable Diffusion",
  "inputSchema": {
    "type": "object",
    "properties": {
      "prompt": { "type": "string", "description": "Positive text prompt" },
      "negative_prompt": { "type": "string", "description": "What to avoid" },
      "model": { "type": "string", "description": "Checkpoint filename" },
      "width": { "type": "integer", "minimum": 64, "maximum": 2048 },
      "height": { "type": "integer", "minimum": 64, "maximum": 2048 },
      "steps": { "type": "integer", "minimum": 1, "maximum": 100 },
      "cfg": { "type": "number", "minimum": 1, "maximum": 30 },
      "seed": { "type": "integer", "description": "-1 for random" }
    },
    "required": ["prompt"]
  }
}

image_to_image

Takes a base64-encoded input image and applies SD img2img.

json
{
  "name": "image_to_image",
  "inputSchema": {
    "properties": {
      "image": { "type": "string", "description": "Base64-encoded input image" },
      "prompt": { "type": "string" },
      "strength": { "type": "number", "minimum": 0.0, "maximum": 1.0,
                    "description": "Denoise strength — lower preserves more of original" }
    },
    "required": ["image", "prompt"]
  }
}

inpaint

json
{
  "name": "inpaint",
  "inputSchema": {
    "properties": {
      "image": { "type": "string", "description": "Base64-encoded image" },
      "mask": { "type": "string", "description": "Base64-encoded mask (white = inpaint area)" },
      "prompt": { "type": "string" }
    },
    "required": ["image", "mask", "prompt"]
  }
}

list_models (Resource)

Resources in MCP are read-only data sources. list_models is better modeled as a resource than a tool:

python
# Server-side resource registration
@server.list_resources()
async def list_resources():
    return [
        Resource(
            uri="comfyui://models/checkpoints",
            name="Available Checkpoints",
            description="List of installed Stable Diffusion model checkpoints",
            mimeType="application/json"
        ),
        Resource(
            uri="comfyui://models/loras",
            name="Available LoRAs",
            mimeType="application/json"
        )
    ]

@server.read_resource()
async def read_resource(uri: AnyUrl):
    if str(uri) == "comfyui://models/checkpoints":
        models = await comfyui_client.get_models("checkpoints")
        return json.dumps(models)

This lets the LLM client fetch the model list and make informed decisions about which checkpoint to use without burning a tool call.


Authentication

Default ComfyUI has no authentication. Default MCP servers often inherit that lack of auth. This is a serious security issue if the server is accessible beyond localhost.

Bearer Token Middleware (Python)

python
from functools import wraps
import os

MCP_TOKEN = os.environ.get("MCP_AUTH_TOKEN")

def require_auth(func):
    @wraps(func)
    async def wrapper(request, *args, **kwargs):
        auth_header = request.headers.get("Authorization", "")
        if not auth_header.startswith("Bearer "):
            raise PermissionError("Missing Authorization header")
        token = auth_header.removeprefix("Bearer ").strip()
        if token != MCP_TOKEN:
            raise PermissionError("Invalid token")
        return await func(request, *args, **kwargs)
    return wrapper

Nginx Reverse Proxy with TLS (Production)

nginx
server {
    listen 443 ssl;
    server_name mcp.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/mcp.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mcp.yourdomain.com/privkey.pem;

    # Rate limiting
    limit_req_zone $binary_remote_addr zone=mcp:10m rate=10r/s;
    limit_req zone=mcp burst=20 nodelay;

    location /sse {
        proxy_pass http://127.0.0.1:3001;
        proxy_http_version 1.1;
        proxy_set_header Connection '';
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 300s;  # Long timeout for generation jobs
        chunked_transfer_encoding on;
    }
}

What authentication does NOT protect against

Even with token auth, a compromised token gives full access to all tools. Consider:

  • Tool-level authorization: Different tokens with different tool access scopes
  • Input sanitization: Reject prompts containing known jailbreak patterns
  • Output scanning: If generated images go into a user-facing product, scan for NSFW content
  • Resource limits: Cap steps at 50, resolution at 1024×1024 to prevent GPU exhaustion attacks

Security Checklist

✅ MCP server never runs as root
✅ ComfyUI only binds to localhost (not 0.0.0.0)
✅ MCP server is behind TLS-terminating reverse proxy
✅ Bearer token or API key authentication enabled
✅ Secrets loaded from environment variables, not config files
✅ Tool whitelist configured (only expose needed tools)
✅ Parameter bounds enforced (max steps, max resolution)
✅ Rate limiting at reverse proxy layer
✅ Output directory is outside web root
✅ ComfyUI's /upload endpoint is not publicly exposed
✅ Logs do not contain prompt content (privacy)
✅ MCP server version is pinned in requirements.txt

Common Mistakes and How to Fix Them

Mistake 1: Using relative paths in Claude Desktop config

Symptom: Server spawns but immediately exits. Claude shows "Server disconnected."

Cause: Claude Desktop spawns the process with a different working directory than you expect.

Fix: Use absolute paths for the command, all args, and the config file.

json
// WRONG
{ "command": "python", "args": ["server.py", "--config", "config.yaml"] }

// CORRECT
{ "command": "/home/user/comfyui-mcp/.venv/bin/python",
  "args": ["/home/user/comfyui-mcp/server.py",
           "--config", "/home/user/comfyui-mcp/config.yaml"] }

Mistake 2: ComfyUI not running when MCP server starts

Symptom: MCP server startup fails with ConnectionRefusedError or httpx.ConnectError.

Fix: Implement a startup retry loop, or use a process manager that enforces service ordering:

python
# startup health check with retry
async def wait_for_comfyui(url: str, max_attempts: int = 10):
    for attempt in range(max_attempts):
        try:
            async with httpx.AsyncClient() as client:
                resp = await client.get(f"{url}/system_stats", timeout=5)
                if resp.status_code == 200:
                    logger.info("ComfyUI is ready")
                    return
        except httpx.ConnectError:
            logger.warning(f"ComfyUI not ready, attempt {attempt + 1}/{max_attempts}")
            await asyncio.sleep(3)
    raise RuntimeError("ComfyUI did not become ready in time")

Mistake 3: Blocking the event loop during image polling

Symptom: The MCP server becomes unresponsive during long generation jobs. Other tool calls time out.

Cause: Synchronous time.sleep() in the polling loop blocks the asyncio event loop.

Fix: Use asyncio.sleep() inside an async polling loop:

python
# WRONG
def poll_job(prompt_id: str) -> dict:
    while True:
        result = requests.get(f"{COMFYUI_URL}/history/{prompt_id}")
        if result.json().get(prompt_id):
            return result.json()[prompt_id]
        time.sleep(0.5)  # Blocks the event loop!

# CORRECT
async def poll_job(prompt_id: str, timeout: int = 120) -> dict:
    deadline = asyncio.get_event_loop().time() + timeout
    async with httpx.AsyncClient() as client:
        while asyncio.get_event_loop().time() < deadline:
            resp = await client.get(f"{COMFYUI_URL}/history/{prompt_id}")
            data = resp.json()
            if prompt_id in data:
                return data[prompt_id]
            await asyncio.sleep(0.5)  # Non-blocking
    raise TimeoutError(f"Job {prompt_id} did not complete within {timeout}s")

Mistake 4: Exposing internal file paths in tool responses

Symptom: Tool returns something like {"image_path": "/home/ubuntu/ComfyUI/output/mcp_output_00001_.png"} — leaking server filesystem layout.

Fix: Return relative URLs or base64-encoded image data instead:

python
async def fetch_result_image(filename: str) -> str:
    image_url = f"{COMFYUI_URL}/view?filename={filename}&type=output"
    async with httpx.AsyncClient() as client:
        resp = await client.get(image_url)
        b64 = base64.b64encode(resp.content).decode()
        return f"data:image/png;base64,{b64}"

Mistake 5: Not setting a job concurrency limit

Symptom: Multiple Claude sessions hammer the GPU simultaneously, causing OOM crashes or 10-minute generation times.

Fix: Use an asyncio semaphore:

python
_generation_semaphore = asyncio.Semaphore(2)  # max 2 concurrent jobs

async def generate_image(params: dict) -> dict:
    async with _generation_semaphore:
        return await _run_workflow("text2img", params)

Performance Optimization

Workflow-level optimizations

  • Disable unnecessary preview nodes: ComfyUI's PreviewImage nodes save intermediate images to disk. Remove them from MCP-facing workflows — you only need the final SaveImage node.
  • Use the right sampler for speed: For draft generation (low steps, high throughput), use euler_a or lcm. Reserve dpmpp_2m karras for final outputs.
  • Share the model across requests: ComfyUI caches the loaded checkpoint in VRAM between jobs. If all requests use the same model, the first request loads it and subsequent requests skip the load time (~5–15s savings).

MCP server-level optimizations

yaml
caching:
  enabled: true
  backend: "redis"        # Use Redis for multi-instance deployments
  ttl_seconds: 3600
  # Cache key = hash(workflow_template + sorted_parameters)
  # Identical requests return cached images instantly

Prompt deduplication: If two concurrent requests have identical parameters, use a lock to make the second request wait for the first result instead of submitting two jobs:

python
_in_flight: dict[str, asyncio.Future] = {}

async def deduplicated_generate(cache_key: str, params: dict) -> dict:
    if cache_key in _in_flight:
        return await asyncio.shield(_in_flight[cache_key])
    
    future = asyncio.get_event_loop().create_future()
    _in_flight[cache_key] = future
    try:
        result = await _run_workflow("text2img", params)
        future.set_result(result)
        return result
    except Exception as e:
        future.set_exception(e)
        raise
    finally:
        _in_flight.pop(cache_key, None)

Resolution and step recommendations

Use CaseResolutionStepsApprox. Time (RTX 3090)
Thumbnail / draft512×51210–152–4s
Standard output1024×102420–308–15s
High quality1024×102440–5020–30s
SDXL + refiner1024×102430+1025–40s
Upscaled (4x)4096×40962045–90s

Expose these as named presets in your tool schema rather than raw numbers. LLMs make better decisions when they choose "high_quality" vs. micromanaging step counts.


Production Deployment

For a full production deployment guide, see Running MCP in Production on MCPForge. Below is the ComfyUI-specific overlay.

Docker Compose Stack

yaml
# docker-compose.yml
version: "3.9"

services:
  comfyui:
    image: yanwk/comfyui-boot:latest
    ports:
      - "127.0.0.1:8188:8188"  # Bind to localhost only
    volumes:
      - ./models:/root/ComfyUI/models
      - ./output:/root/ComfyUI/output
      - ./custom_nodes:/root/ComfyUI/custom_nodes
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8188/system_stats"]
      interval: 30s
      timeout: 10s
      retries: 3

  mcp-server:
    build: ./mcp-server
    ports:
      - "127.0.0.1:3001:3001"
    environment:
      - COMFYUI_URL=http://comfyui:8188
      - MCP_AUTH_TOKEN=${MCP_AUTH_TOKEN}
      - TRANSPORT=sse
    depends_on:
      comfyui:
        condition: service_healthy
    restart: unless-stopped
    volumes:
      - ./workflows:/app/workflows:ro
      - ./logs:/app/logs

  nginx:
    image: nginx:alpine
    ports:
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      - mcp-server
    restart: unless-stopped

Systemd Service (bare-metal)

ini
# /etc/systemd/system/comfyui-mcp.service
[Unit]
Description=ComfyUI MCP Server
After=network.target comfyui.service
Requires=comfyui.service

[Service]
Type=simple
User=mcpuser
WorkingDirectory=/opt/comfyui-mcp
EnvironmentFile=/opt/comfyui-mcp/.env
ExecStart=/opt/comfyui-mcp/.venv/bin/python -m comfyui_mcp_server --config /opt/comfyui-mcp/config.yaml
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/comfyui-mcp/logs

[Install]
WantedBy=multi-user.target

Health Monitoring

The MCP server should expose a /health endpoint for monitoring systems:

python
@app.get("/health")
async def health():
    # Check ComfyUI connectivity
    try:
        async with httpx.AsyncClient() as client:
            resp = await client.get(f"{COMFYUI_URL}/system_stats", timeout=5)
            comfyui_ok = resp.status_code == 200
            system_stats = resp.json()
    except Exception:
        comfyui_ok = False
        system_stats = {}
    
    return {
        "status": "healthy" if comfyui_ok else "degraded",
        "comfyui": {
            "reachable": comfyui_ok,
            "vram_free_mb": system_stats.get("devices", [{}])[0].get("vram_free", 0)
        },
        "active_jobs": _generation_semaphore._value,
        "cache_size": len(_cache)
    }

Validating Your Server with MCPForge Verify

Before connecting your ComfyUI MCP server to any LLM client or exposing it to users, run it through MCPForge Verify.

Verify performs:

  1. MCP handshake validation — Confirms the server correctly negotiates capabilities with a real MCP client
  2. Tool manifest inspection — Checks that all tools have valid JSON Schemas, required fields, and descriptions
  3. Authentication enforcement — Confirms that unauthenticated requests are rejected
  4. JSON-RPC compliance — Validates response shapes, error codes, and notification formats
  5. Transport compatibility — Tests both stdio and SSE transports if applicable
  6. Performance baseline — Measures tool call latency and flags slow responses

A server that passes Verify can be listed in the MCPForge Verified Directory, which is increasingly used by developers sourcing production-ready MCP integrations.

Run Verify against your staging environment, not production. Verify deliberately sends malformed inputs, out-of-bounds parameters, and authentication probes — treat it like a security scanner.


Troubleshooting

MCP server starts but tools don't appear in Claude

  1. Check that claude_desktop_config.json is valid JSON (python -m json.tool claude_desktop_config.json)
  2. Verify the command path resolves: which python inside the venv should match what's in the config
  3. Look at ~/Library/Logs/Claude/mcp-server-comfyui.log for the exact error
  4. Run the server command manually to confirm it starts without error

generate_image returns an error immediately

{"error": "ComfyUI returned status 400", "detail": "prompt invalid"}

This means the workflow JSON sent to ComfyUI failed validation. Common causes:

  • Model filename in default_model doesn't match any installed checkpoint
  • A node class in the workflow requires a custom node that isn't installed
  • A node input references a non-existent node ID

Debug by capturing the exact JSON sent to ComfyUI:

python
logging.debug("Submitting workflow: %s", json.dumps(workflow, indent=2))

Then paste it into ComfyUI's web UI manually to see the exact validation error.

SSE connection drops after 30–60 seconds

This is a proxy timeout. Your reverse proxy (Nginx, Caddy, Cloudflare) is closing idle SSE connections. Fix:

nginx
# Nginx
proxy_read_timeout 300s;
proxy_send_timeout 300s;
keepalive_timeout 300s;

Alternatively, implement SSE keepalive pings from the server:

python
async def keepalive(writer):
    while True:
        await asyncio.sleep(20)
        await writer.write(b": ping\n\n")  # SSE comment, ignored by clients

GPU runs out of VRAM mid-generation

This happens when concurrent jobs exceed GPU capacity. Symptoms: ComfyUI crashes or returns OOM error to the MCP server.

  • Set max_concurrent_jobs: 1 in config
  • Reduce default resolution to 768×768
  • Enable ComfyUI's --lowvram flag if you're on ≤8GB VRAM
  • Use SDXL-Turbo or LCM checkpoints which require fewer steps

Real-World Workflows

Workflow 1: AI-assisted design iteration

A frontend developer uses Cursor + ComfyUI MCP to generate UI mockup images:

Developer: Generate a hero banner image for a SaaS dashboard product — 
clean, modern, dark theme, showing abstract data visualization, 
1920x600 pixels

Claude (via Cursor): [Calls generate_image with custom dimensions]
Returns: hero_banner.png

Developer: The colors are too dark. Make it lighter, more purple gradient
Claude: [Calls image_to_image with hero_banner.png + style prompt, strength=0.6]
Returns: hero_banner_v2.png

Workflow 2: Automated product image generation

A backend service calls the MCP server programmatically to generate product images at scale:

python
import anthropic

client = anthropic.Anthropic()

def generate_product_image(product_name: str, description: str) -> str:
    message = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=1024,
        tools=[...],  # MCP tools injected by the client
        messages=[{
            "role": "user",
            "content": f"Generate a clean product photo for '{product_name}': {description}. "
                       f"White background, studio lighting, professional e-commerce style."
        }]
    )
    # Extract image URL from tool result
    for block in message.content:
        if block.type == "tool_result":
            return block.content[0]["url"]

Workflow 3: Multi-step image pipeline

Claude orchestrates a multi-step workflow without the developer writing any pipeline code:

User: Create a product visualization: start with the base render, 
upscale it 4x, then add a white studio background

Claude:
1. [generate_image] → base_product.png
2. [upscale] input=base_product.png, scale=4 → base_product_4k.png  
3. [inpaint] image=base_product_4k.png, mask=background_mask.png,
   prompt="clean white studio background" → final_product.png

The LLM reasons about the correct sequence and parameters. You never write orchestration code.


Best Practices Summary

Configuration:

  • Always use absolute paths in Claude Desktop and Cursor configs
  • Store all secrets in environment variables
  • Pin dependency versions in requirements.txt
  • Use named presets instead of raw numeric parameters where possible

Security:

  • Enable authentication for any non-localhost deployment
  • Restrict tools to only those genuinely needed
  • Enforce parameter bounds server-side (never trust the LLM's values)
  • Run behind TLS-terminating reverse proxy

Performance:

  • Limit concurrent jobs with a semaphore
  • Cache identical requests
  • Use async polling, never blocking sleep
  • Profile which workflow nodes are slowest and optimize them

Reliability:

  • Implement startup health checks with retry
  • Set job timeouts to avoid hanging
  • Return clear error messages with actionable context
  • Monitor VRAM utilization as a leading indicator of instability

Development:

  • Test with MCPForge Verify before connecting to any LLM client
  • Use JSON logging for production (structured logs are machine-parseable)
  • Version your workflow templates alongside your server code
  • Keep ComfyUI custom nodes pinned to known-good commits

Key Takeaways

  • ComfyUI MCP is not a thin wrapper — it's an architectural bridge that makes generative AI a composable tool in any MCP-compatible LLM workflow
  • stdio is the right transport for local development; SSE for remote/production deployments
  • The biggest production risks are no authentication, blocking async code, and no concurrency limits — address all three before going live
  • Workflow templates decouple your MCP tool API from ComfyUI's internal node structure, giving you versioning, validation, and flexibility
  • Run your server through MCPForge Verify before deployment — it catches the class of bugs that only manifest during real MCP handshakes
  • An LLM client that can call ComfyUI tools can orchestrate complex multi-step image pipelines without you writing orchestration code — that's the real value proposition

Frequently Asked Questions

What is the difference between ComfyUI's REST API and ComfyUI MCP?

ComfyUI's native REST API requires you to manually craft JSON workflow payloads and poll for results. ComfyUI MCP wraps that API in the Model Context Protocol, so LLM clients like Claude or Cursor can discover available tools, invoke them with structured parameters, and receive typed responses — all without you writing glue code. MCP also handles transport negotiation, authentication headers, and error normalization that the raw API leaves to you.

Do I need a GPU to run ComfyUI MCP?

No. The MCP server itself is a lightweight Node.js or Python process that has no GPU requirements. It delegates all inference to whichever ComfyUI instance you point it at. You can run the MCP server on a cheap VPS or even your laptop, while ComfyUI runs on a separate GPU machine, a cloud instance, or a service like RunPod.

Can ComfyUI MCP generate images automatically inside Claude conversations?

Yes. Once ComfyUI MCP is connected to Claude Desktop, Claude can call the generate_image tool directly within a conversation. Claude interprets your natural-language request, selects the appropriate workflow, fills in parameters like prompt, model, and resolution, and returns the generated image URL or base64 data inline in the chat.

Is ComfyUI MCP secure enough for production use?

It can be, but it requires deliberate hardening. By default, most ComfyUI MCP implementations expose all installed ComfyUI nodes as callable tools with no authentication. For production you must add an API key or OAuth layer, restrict which tools are exposed, run the server behind a reverse proxy with TLS, and sandbox the ComfyUI process. See the Security section of this guide for a complete checklist.

Which ComfyUI custom nodes are exposed as MCP tools?

This depends on the MCP server implementation you install. Most expose a curated set of core generation tools (text-to-image, image-to-image, inpainting, upscaling) rather than every installed node. Some implementations support dynamic tool registration so you can declaratively add tools that map to specific custom-node workflows. Check the server's README for the tool manifest.

Can I run multiple ComfyUI instances behind one MCP server?

Yes. You can configure the MCP server to load-balance across multiple ComfyUI endpoints using a round-robin or least-busy strategy. This is the recommended pattern for high-throughput production deployments. Each ComfyUI instance should share the same model checkpoint directory so outputs are consistent regardless of which instance handles a request.

What transport should I use — stdio or SSE?

Use stdio when the MCP server and the LLM client run on the same machine (e.g., Claude Desktop on your laptop). Use SSE or Streamable HTTP when the MCP server is remote, such as a GPU cloud instance. SSE adds latency but enables remote access, authentication headers, and horizontal scaling.

How do I validate that my ComfyUI MCP server is production-ready?

Run your server endpoint through MCPForge Verify (mcpforge.dev/verify). It performs a full MCP handshake, enumerates your tool manifest, checks authentication enforcement, validates JSON-RPC response shapes, and flags common misconfigurations. You should do this before pointing any LLM client at a production server.

Check your MCP security posture

Generate a Security Score, detect risky tools, and review permissions before exposing APIs to AI agents.

Related Articles

What Is Model Context Protocol (MCP)?

OpenAPI to MCP: Complete Guide

How to Connect Claude to Any API Using MCP

Coming soon

GitHub MCP Server Explained

Coming soon