Anatomie d'un workflow ComfyUI JSON
Un workflow ComfyUI est un graphe orienté stocké en JSON. Chaque entrée du dictionnaire racine est un noeud identifié par un id numérique (sous forme de chaîne : "1", "2", etc.). Un noeud a deux champs obligatoires : class_type (le type de noeud, qui détermine son comportement) et inputs (les paramètres du noeud).
Les connexions entre noeuds sont exprimées à l'intérieur des inputs : quand un paramètre attend la sortie d'un autre noeud, sa valeur est un tableau [node_id, output_index] au lieu d'une valeur scalaire. output_index correspond à la position de la sortie dans l'ordre de déclaration du noeud.
Exemple : CheckpointLoaderSimple déclare trois sorties dans l'ordre MODEL (indice 0), CLIP (indice 1), VAE (indice 2). Pour brancher le CLIP vers un CLIPTextEncode, on écrit "clip": ["2", 1] si le loader est le noeud "2".
Voici un workflow minimal représentant la chaîne classique de génération texte vers image :
{"1": {"class_type": "CheckpointLoaderSimple","inputs": {"ckpt_name": "flux1-schnell-fp8.safetensors"}},"2": {"class_type": "CLIPTextEncode","inputs": {"text": "a robot astronaut on the moon","clip": ["1", 1]}},"3": {"class_type": "CLIPTextEncode","inputs": {"text": "","clip": ["1", 1]}},"4": {"class_type": "EmptyLatentImage","inputs": {"width": 1024,"height": 1024,"batch_size": 1}},"5": {"class_type": "KSampler","inputs": {"seed": 42,"steps": 4,"cfg": 1.0,"sampler_name": "euler","scheduler": "simple","denoise": 1.0,"model": ["1", 0],"positive": ["2", 0],"negative": ["3", 0],"latent_image": ["4", 0]}},"6": {"class_type": "VAEDecode","inputs": {"samples": ["5", 0],"vae": ["1", 2]}},"7": {"class_type": "SaveImage","inputs": {"filename_prefix": "claude_run","images": ["6", 0]}}}
Les quatre types de noeuds présents ici sont suffisants pour comprendre 90 % des workflows :
- Loaders (
CheckpointLoaderSimple,VAELoader,UNETLoader) : chargent les fichiers modèle depuis le disque - Samplers (
KSampler,KSamplerAdvanced) : pilotent le processus de débruitage itératif - Encodeurs de texte (
CLIPTextEncode) : transforment le prompt en embedding latent - Noeuds de sortie (
VAEDecode,SaveImage) : convertissent le résultat en pixels et l'écrivent sur disque
sampler_name et scheduler : deux champs séparés
Dans ComfyUI, le sampler (algorithme de débruitage) et le scheduler (stratégie d'évolution du bruit par étape) sont deux paramètres distincts du KSampler. On écrit sampler_name: "dpmpp_2m" combiné avec scheduler: "karras", pas "dpmpp_2m_karras" en un seul champ.
Exposer le workflow via un outil MCP custom
L'architecture est directe : Claude Code appelle un tool MCP, le serveur MCP charge le fichier JSON du workflow, applique les paramètres dynamiques reçus, puis POST sur http://127.0.0.1:8188/prompt. ComfyUI exécute le workflow et écrit l'image dans son dossier output/.
Le wrapper ci-dessous utilise le même pattern McpServer + server.tool() que les autres exemples du site, présenté dans Créer un MCP en TypeScript.
Créer le projet et installer les dépendances
mkdir comfyui-workflow-mcp && cd comfyui-workflow-mcpnpm init -ynpm install @modelcontextprotocol/sdk zodnpm install -D typescript tsx @types/node
Préparer le fichier de workflow JSON
Exporte ton workflow depuis l'interface ComfyUI via le bouton "Save (API Format)" (pas le bouton "Save" classique : celui-ci produit un format étendu non compatible avec l'API). Place le fichier dans workflow.json à la racine du projet.
Tu peux partir du workflow minimal de la section précédente.
Écrire le wrapper MCP
Crée src/index.ts :
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";import { z } from "zod";import { readFileSync } from "fs";import { join } from "path";const COMFYUI_URL = "http://127.0.0.1:8188";const server = new McpServer({name: "comfyui-workflow-pilot",version: "0.1.0",});server.tool("run_workflow","Exécute un workflow ComfyUI JSON avec des paramètres dynamiques",{positive_prompt: z.string().describe("Prompt positif (description de l'image)"),negative_prompt: z.string().default("").describe("Prompt négatif (éléments à éviter)"),seed: z.number().int().default(-1).describe("Seed (-1 = aléatoire)"),sampler_name: z.enum(["euler", "dpmpp_2m", "dpmpp_3m_sde", "ddim", "uni_pc"]).default("euler").describe("Algorithme de sampling"),scheduler: z.enum(["simple", "karras", "exponential", "sgm_uniform", "beta"]).default("simple").describe("Stratégie de scheduling du bruit"),steps: z.number().int().min(1).max(150).default(4).describe("Nombre d'étapes"),},async ({ positive_prompt, negative_prompt, seed, sampler_name, scheduler, steps }) => {const workflowPath = join(process.cwd(), "workflow.json");const workflow = JSON.parse(readFileSync(workflowPath, "utf-8")) as Record<string,{ class_type: string; inputs: Record<string, unknown> }>;const resolvedSeed = seed === -1 ? Math.floor(Math.random() * 1e9) : seed;for (const node of Object.values(workflow)) {if (node.class_type === "CLIPTextEncode") {const isNegative =typeof node.inputs["text"] === "string" && node.inputs["text"] === "";node.inputs["text"] = isNegative ? negative_prompt : positive_prompt;}if (node.class_type === "KSampler") {node.inputs["seed"] = resolvedSeed;node.inputs["sampler_name"] = sampler_name;node.inputs["scheduler"] = scheduler;node.inputs["steps"] = steps;}}const res = await fetch(`${COMFYUI_URL}/prompt`, {method: "POST",headers: { "Content-Type": "application/json" },body: JSON.stringify({ prompt: workflow }),});if (!res.ok) {const text = await res.text();throw new Error(`ComfyUI /prompt a répondu ${res.status}: ${text}`);}const data = (await res.json()) as { prompt_id: string; number: number };return {content: [{type: "text" as const,text: `Workflow en file. prompt_id: ${data.prompt_id}, position: ${data.number}`,},],};});async function main(): Promise<void> {const transport = new StdioServerTransport();await server.connect(transport);}main().catch((err: unknown) => {console.error("Erreur MCP:", err);process.exit(1);});
Configurer Claude Code pour utiliser le MCP
Ajoute (ou crée) .mcp.json à la racine de ton projet Claude Code :
{"mcpServers": {"comfyui-workflow": {"command": "npx","args": ["tsx", "/chemin/absolu/comfyui-workflow-mcp/src/index.ts"]}}}
Redémarre Claude Code pour qu'il charge la nouvelle config.
Ordre de démarrage
ComfyUI doit tourner sur le port 8188 avant de lancer Claude Code. Le MCP tente de contacter l'API au premier appel d'outil : si ComfyUI n'est pas prêt, la requête échoue avec une erreur de connexion.
Faire éditer le workflow par Claude
Une fois le MCP connecté, Claude Code peut modifier les paramètres du workflow à la demande. Il n'a pas besoin de voir le JSON brut : il reçoit une description du tool et de ses paramètres via le protocole MCP.
Exemple de prompt système
Tu as accès à l'outil run_workflow qui lance des générations d'images via ComfyUI.Les paramètres disponibles sont : positive_prompt, negative_prompt, seed,sampler_name (euler | dpmpp_2m | dpmpp_3m_sde | ddim | uni_pc),scheduler (simple | karras | exponential | sgm_uniform | beta), steps.Pour les générations rapides (tests, aperçu), utilise sampler_name=euler avecscheduler=simple et steps=4.Pour les générations de qualité, utilise sampler_name=dpmpp_2m avecscheduler=karras et steps=20.
Exemple d'interaction
Message utilisateur :
Génère une illustration d'une ville futuriste sous la pluie, style cyberpunk,teintes bleues et orange. Utilise un sampler de qualité supérieure.
Appel de tool que Claude va effectuer :
{"tool": "run_workflow","arguments": {"positive_prompt": "futuristic city in the rain, cyberpunk style, blue and orange tones, neon reflections on wet streets, highly detailed","negative_prompt": "blurry, low quality, watermark, text","seed": -1,"sampler_name": "dpmpp_2m","scheduler": "karras","steps": 20}}
Réponse retournée au modèle :
Workflow en file. prompt_id: a3f7c2d1-..., position: 1
Vérifier la progression
L'API ComfyUI expose GET /queue pour consulter les jobs en attente et GET /history/{prompt_id} pour récupérer le statut et le nom du fichier généré. Tu peux ajouter un second tool get_generation_status qui poll ces endpoints si tu veux que Claude attende la fin de la génération avant de continuer.
Cas d'usage
Changer le sampler pour ajuster qualité vs vitesse
Le KSampler est le noeud le plus influent sur le rendu final. Passer de euler/simple/4 steps à dpmpp_2m/karras/20 steps change significativement la cohérence et la précision des détails, au prix d'un temps de génération 4 à 5 fois plus long. C'est utile en deux temps : générer une dizaine d'aperçus rapides pour valider la direction, puis lancer une génération longue sur le meilleur candidat. Le wrapper MCP le rend trivial depuis Claude Code : un seul message pour chaque phase.
Ajouter un ControlNet à un workflow existant
Un ControlNet s'insère dans le graphe entre le loader de modèle et le KSampler. Tu charges une image de référence (contours Canny, pose OpenPose ou carte de profondeur), tu la passes à un node ControlNetApply, et tu branches la sortie conditioning sur l'entrée positive du KSampler à la place du CLIPTextEncode direct.
Pour piloter ça via MCP, ajoute au tool un paramètre controlnet_image_path et controlnet_strength. Le wrapper modifie dynamiquement le workflow en injectant les noeuds ControlNetLoader et ControlNetApply si le paramètre est fourni, sinon il passe directement le CLIPTextEncode. Claude peut ainsi activer ou désactiver le ControlNet sur une génération sans toucher au fichier JSON.
A/B test sur N prompts en boucle
Le cas le plus productif : tu veux comparer 10 formulations de prompt sur le même seed et les mêmes paramètres de sampling. Donne à Claude une liste de prompts et demande-lui de les lancer séquentiellement avec seed fixe. Il appelle run_workflow dix fois, chacune avec un positive_prompt différent et le même seed, ce qui garantit des variations contrôlées. Tu récupères les dix images dans ComfyUI/output/ et tu choisis la meilleure formulation avant de lancer une génération finale en haute qualité.
Prochaines étapes
- MCP ComfyUI local, tutoriel complet : mise en place de ComfyUI depuis zéro, installation de Flux Schnell, et connexion initiale à Claude Code.
- Panorama Claude Code + IA générative : les quatre patterns d'intégration et la matrice de décision pour choisir entre local et cloud.