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
torchandPillow.
Option B: Using uvx (zero-install for quick testing)
If the server is published to PyPI:
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)
npx comfyui-mcp
Or install globally:
npm install -g comfyui-mcp
comfyui-mcp --config ./config.json
Configuration
Core Configuration File
A complete config.yaml with every significant option explained:
# 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:
# .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:
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
{
"__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
# 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
{
"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:
# 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:
{
"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:
python -m comfyui_mcp_server \
--transport sse \
--port 3001 \
--comfyui-url http://localhost:8188
Then in Cursor:
{
"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
{
"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.
{
"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
{
"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:
# 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)
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)
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
stepsat 50,resolutionat 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.
// 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:
# 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:
# 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:
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:
_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
PreviewImagenodes save intermediate images to disk. Remove them from MCP-facing workflows — you only need the finalSaveImagenode. - Use the right sampler for speed: For draft generation (low steps, high throughput), use
euler_aorlcm. Reservedpmpp_2m karrasfor 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
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:
_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 Case | Resolution | Steps | Approx. Time (RTX 3090) |
|---|---|---|---|
| Thumbnail / draft | 512×512 | 10–15 | 2–4s |
| Standard output | 1024×1024 | 20–30 | 8–15s |
| High quality | 1024×1024 | 40–50 | 20–30s |
| SDXL + refiner | 1024×1024 | 30+10 | 25–40s |
| Upscaled (4x) | 4096×4096 | 20 | 45–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
# 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)
# /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:
@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:
- MCP handshake validation — Confirms the server correctly negotiates capabilities with a real MCP client
- Tool manifest inspection — Checks that all tools have valid JSON Schemas, required fields, and descriptions
- Authentication enforcement — Confirms that unauthenticated requests are rejected
- JSON-RPC compliance — Validates response shapes, error codes, and notification formats
- Transport compatibility — Tests both stdio and SSE transports if applicable
- 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
- Check that
claude_desktop_config.jsonis valid JSON (python -m json.tool claude_desktop_config.json) - Verify the command path resolves:
which pythoninside the venv should match what's in the config - Look at
~/Library/Logs/Claude/mcp-server-comfyui.logfor the exact error - 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_modeldoesn'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:
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
proxy_read_timeout 300s;
proxy_send_timeout 300s;
keepalive_timeout 300s;
Alternatively, implement SSE keepalive pings from the server:
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: 1in config - Reduce default resolution to 768×768
- Enable ComfyUI's
--lowvramflag 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:
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