
{"id":5327,"date":"2025-04-11T07:17:11","date_gmt":"2025-04-10T23:17:11","guid":{"rendered":"https:\/\/infernews.com\/?page_id=5327"},"modified":"2025-04-11T07:17:11","modified_gmt":"2025-04-10T23:17:11","slug":"comfyui-mcp-gemini-2-5-pro","status":"publish","type":"page","link":"https:\/\/infernews.com\/blog\/comfyui-mcp-gemini-2-5-pro\/","title":{"rendered":"Comfyui MCP &#8211; Gemini 2.5 Pro"},"content":{"rendered":"\n<p>Okay, let&#8217;s create a Python script using Flask to act as a server that interacts with the ComfyUI API. This script will:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Run an HTTP Server:<\/strong> Listen for incoming requests (using Flask).<\/li>\n\n\n\n<li><strong>Define an Endpoint (<code>\/generate<\/code>):<\/strong> Accept POST requests with JSON data containing a <code>prompt<\/code> (text) and optionally an <code>image<\/code> (base64 encoded).<\/li>\n\n\n\n<li><strong>Interact with ComfyUI API:<\/strong>\n<ul class=\"wp-block-list\">\n<li>Upload the input image (if provided).<\/li>\n\n\n\n<li>Load a predefined ComfyUI workflow (in API JSON format).<\/li>\n\n\n\n<li>Modify the workflow to include the user&#8217;s prompt and the uploaded image filename.<\/li>\n\n\n\n<li>Queue the prompt for execution.<\/li>\n\n\n\n<li>Monitor the execution progress via WebSocket.<\/li>\n\n\n\n<li>Retrieve the resulting image\/video.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Return the Output:<\/strong> Send the generated image\/video back to the client.<\/li>\n<\/ol>\n\n\n\n<p><strong>Prerequisites:<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>ComfyUI Running:<\/strong> You need a ComfyUI instance running and accessible. Make sure it&#8217;s started with the <code>--enable-cors<\/code> flag if your script runs on a different origin, although for server-to-server communication from the same machine or network, it might not be strictly necessary, but it&#8217;s good practice. Ensure the API is accessible (usually at <code>http:\/\/127.0.0.1:8188<\/code>).<\/li>\n\n\n\n<li><strong>Python Environment:<\/strong> Python 3.6+ installed.<\/li>\n\n\n\n<li><strong>Required Libraries:<\/strong> Install them using pip:<br><code>bash pip install Flask requests Pillow websocket-client<\/code><\/li>\n\n\n\n<li><strong>ComfyUI Workflow (API Format):<\/strong>\n<ul class=\"wp-block-list\">\n<li>Create a workflow in the ComfyUI interface that takes a text prompt and optionally an image input (e.g., using <code>LoadImage<\/code>) and produces an output (e.g., using <code>SaveImage<\/code> or a video node).<\/li>\n\n\n\n<li><strong>Crucially:<\/strong> Click &#8220;Save (API Format)&#8221; in ComfyUI and save this JSON file (e.g., <code>workflow_api.json<\/code>).<\/li>\n\n\n\n<li><strong>Identify Node IDs\/Titles:<\/strong> You need to know which nodes in your workflow correspond to:\n<ul class=\"wp-block-list\">\n<li>The positive text prompt input (e.g., a <code>CLIPTextEncode<\/code> node). Let&#8217;s assume its title is &#8220;Positive Prompt&#8221;.<\/li>\n\n\n\n<li>The input image loader (e.g., a <code>LoadImage<\/code> node). Let&#8217;s assume its title is &#8220;Input Image&#8221;.<\/li>\n\n\n\n<li>The final output node (e.g., a <code>SaveImage<\/code> or <code>SaveAnimatedWEBP<\/code> node). Let&#8217;s assume its title is &#8220;Final Output&#8221;. You&#8217;ll need its <code>class_type<\/code> too.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>Python Script (<code>mcp_server.py<\/code>):<\/strong><\/p>\n\n\n\n<div class=\"codecopy-container\">\n                <div class=\"codecopy-header\">\n                    \n                    <button class=\"codecopy-btn\" data-target=\"codecopy-69f8c252bad66\" title=\"Copy code to clipboard\">\n                <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                    <path d=\"M16 1H4C2.9 1 2 1.9 2 3V17H4V3H16V1ZM19 5H8C6.9 5 6 5.9 6 7V21C6 22.1 6.9 23 8 23H19C20.1 23 21 22.1 21 21V7C21 5.9 20.1 5 19 5ZM19 21H8V7H19V21Z\" fill=\"currentColor\"\/>\n                <\/svg>\n                <span class=\"codecopy-text\">Copy<\/span>\n            <\/button>\n                <\/div>\n                <pre class=\"line-numbers\" id=\"codecopy-69f8c252bad66\"><code class=\"language-text\">import websocket # websocket-client library\nimport uuid\nimport json\nimport urllib.request\nimport urllib.parse\nimport random\nimport os\nimport time\nfrom PIL import Image\nimport io\nimport requests\nfrom flask import Flask, request, jsonify, send_file\n\n# --- Configuration ---\nCOMFYUI_ADDRESS = \"127.0.0.1:8188\" # Replace with your ComfyUI address if different\nSERVER_ADDRESS = \"0.0.0.0\"         # Listen on all network interfaces\nSERVER_PORT = 9090                 # Port for this MCP server\nCLIENT_ID = str(uuid.uuid4())      # Unique ID for this client\nWORKSPACE_DIR = \"mcp_workspace\"    # Directory to store temp files and outputs\nWORKFLOW_API_JSON = \"workflow_api.json\" # Path to your ComfyUI workflow in API JSON format\n\n# Node titles\/types in your workflow_api.json to modify (ADJUST THESE)\nNODE_TITLE_POSITIVE_PROMPT = \"Positive Prompt\"  # Title of the node receiving the positive text prompt\nNODE_TITLE_LOAD_IMAGE = \"Input Image\"          # Title of the node loading the input image (if used)\nNODE_TITLE_NEGATIVE_PROMPT = \"Negative Prompt\"  # Optional: Title for negative prompt\n# NODE_TITLE_OUTPUT = \"Final Output\"         # Title of the final output node (useful for finding output)\nOUTPUT_NODE_CLASS_TYPE = \"SaveImage\" # Class type of the *final* output node (e.g., SaveImage, SaveAnimatedWEBP, etc.)\n\n\n# --- ComfyUI API Interaction Functions ---\n\ndef queue_prompt(prompt_workflow, client_id, comfyui_address):\n    \"\"\"Sends the workflow to ComfyUI for queuing.\"\"\"\n    p = {\"prompt\": prompt_workflow, \"client_id\": client_id}\n    data = json.dumps(p).encode('utf-8')\n    req = urllib.request.Request(f\"http:\/\/{comfyui_address}\/prompt\", data=data)\n    try:\n        response = urllib.request.urlopen(req)\n        return json.loads(response.read())\n    except urllib.error.URLError as e:\n        print(f\"Error queuing prompt: {e}\")\n        print(f\"Response body (if any): {e.read().decode() if hasattr(e, 'read') else 'N\/A'}\")\n        return None\n    except Exception as e:\n        print(f\"An unexpected error occurred during queueing: {e}\")\n        return None\n\n\ndef get_image(filename, subfolder, folder_type, comfyui_address):\n    \"\"\"Fetches an image from ComfyUI's \/view endpoint.\"\"\"\n    data = {\"filename\": filename, \"subfolder\": subfolder, \"type\": folder_type}\n    url_values = urllib.parse.urlencode(data)\n    url = f\"http:\/\/{comfyui_address}\/view?{url_values}\"\n    print(f\"Fetching image from: {url}\")\n    try:\n        with urllib.request.urlopen(url) as response:\n            return response.read()\n    except Exception as e:\n        print(f\"Error fetching image {filename}: {e}\")\n        return None\n\ndef get_history(prompt_id, comfyui_address):\n    \"\"\"Retrieves the execution history for a given prompt ID.\"\"\"\n    try:\n        with urllib.request.urlopen(f\"http:\/\/{comfyui_address}\/history\/{prompt_id}\") as response:\n            return json.loads(response.read())\n    except Exception as e:\n        print(f\"Error getting history for {prompt_id}: {e}\")\n        return None\n\ndef upload_image(image_data, comfyui_address, filename_prefix=\"input_image\"):\n    \"\"\"Uploads an image to ComfyUI's \/upload\/image endpoint.\"\"\"\n    try:\n        # Use PIL to determine format and potentially convert if needed\n        img = Image.open(io.BytesIO(image_data))\n        image_format = img.format.lower() if img.format else 'png' # Default to png if format unknown\n\n        # Ensure filename has a valid extension\n        filename = f\"{filename_prefix}_{uuid.uuid4()}.{image_format}\"\n\n        # Prepare multipart\/form-data\n        files = {'image': (filename, image_data, f'image\/{image_format}')}\n        data = {'overwrite': 'true'} # Overwrite if filename exists (UUID makes this unlikely)\n\n        response = requests.post(f\"http:\/\/{comfyui_address}\/upload\/image\", files=files, data=data)\n        response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx)\n\n        upload_data = response.json()\n        print(f\"Image uploaded: {upload_data}\")\n        # Expected response format: {\"name\": \"input_image_uuid.png\", \"subfolder\": \"\", \"type\": \"input\"}\n        return upload_data&#91;'name'], upload_data.get('subfolder', ''), upload_data.get('type', 'input')\n\n    except requests.exceptions.RequestException as e:\n        print(f\"Error uploading image via requests: {e}\")\n        if e.response is not None:\n            print(f\"Response status: {e.response.status_code}\")\n            print(f\"Response body: {e.response.text}\")\n        return None, None, None\n    except Exception as e:\n        print(f\"An unexpected error occurred during image upload: {e}\")\n        return None, None, None\n\n\ndef find_node_id_by_title(workflow, title):\n    \"\"\"Finds the node ID in the workflow based on its title.\"\"\"\n    for node_id, node_data in workflow.items():\n        if node_data.get(\"_meta\", {}).get(\"title\") == title:\n            return node_id\n    return None\n\ndef find_node_id_by_class_type(workflow, class_type):\n    \"\"\"Finds the node ID in the workflow based on its class type.\"\"\"\n    for node_id, node_data in workflow.items():\n        if node_data.get(\"class_type\") == class_type:\n            return node_id\n    return None\n\ndef get_final_outputs(prompt_id, comfyui_address, output_node_class_type):\n    \"\"\"\n    Gets the final output image\/video data by monitoring WebSocket\n    or falling back to history if WebSocket fails.\n    \"\"\"\n    output_data = None\n    start_time = time.time()\n    ws_failed = False\n\n    # --- WebSocket Monitoring ---\n    ws = websocket.WebSocket()\n    ws_url = f\"ws:\/\/{comfyui_address}\/ws?clientId={CLIENT_ID}\"\n    print(f\"Connecting to WebSocket: {ws_url}\")\n    try:\n        ws.connect(ws_url)\n        print(\"WebSocket connected.\")\n        while True:\n            out = ws.recv()\n            if isinstance(out, str):\n                message = json.loads(out)\n                # print(f\"WS Received: {message}\") # Debug: print all messages\n                if message&#91;'type'] == 'status':\n                    status_data = message&#91;'data']&#91;'status']\n                    execinfo = status_data.get('execinfo')\n                    if execinfo and execinfo.get('queue_remaining') is not None:\n                        print(f\"Queue remaining: {execinfo&#91;'queue_remaining']}\")\n\n                elif message&#91;'type'] == 'progress':\n                    progress_data = message&#91;'data']\n                    print(f\"Progress: {progress_data&#91;'value']}\/{progress_data&#91;'max']}\")\n\n                elif message&#91;'type'] == 'executing':\n                    data = message&#91;'data']\n                    if data&#91;'node'] is None and data&#91;'prompt_id'] == prompt_id:\n                        print(f\"Execution finished for prompt {prompt_id}\")\n                        break # Execution is finished\n                    elif data&#91;'prompt_id'] == prompt_id:\n                         print(f\"Executing node: {data&#91;'node']}\")\n\n                elif message&#91;'type'] == 'executed':\n                     data = message&#91;'data']\n                     if data&#91;'prompt_id'] == prompt_id and 'outputs' in data:\n                         # Check if this is the output from the final node\n                         node_info = get_history(prompt_id, comfyui_address).get(prompt_id, {}).get('outputs', {}).get(data&#91;'node'], {})\n                         if node_info.get('class_type') == output_node_class_type:\n                            print(f\"Detected output from final node {data&#91;'node']} ({output_node_class_type}): {data&#91;'outputs']}\")\n                            output_data = data&#91;'outputs'] # Store the last relevant output\n                            # Don't break here yet, wait for 'executing' with node=None\n            else:\n                # Handle binary messages if necessary (less common for status)\n                print(\"Received binary message (unhandled)\")\n                # If the final output is expected as binary via WS, handle it here\n\n            # Timeout check\n            if time.time() - start_time &gt; 120: # 2-minute timeout\n                print(\"WebSocket timeout waiting for execution finish.\")\n                ws_failed = True\n                break\n\n    except websocket.WebSocketException as e:\n        print(f\"WebSocket Error: {e}\")\n        ws_failed = True\n    except Exception as e:\n        print(f\"Error processing WebSocket message: {e}\")\n        ws_failed = True\n    finally:\n        if ws.connected:\n            ws.close()\n            print(\"WebSocket closed.\")\n\n    # --- Fallback to History API if WebSocket failed or didn't find output ---\n    if output_data is None:\n        print(\"WebSocket did not yield output or failed, trying History API...\")\n        history = get_history(prompt_id, comfyui_address)\n        if not history or prompt_id not in history:\n            print(f\"Error: Could not retrieve history for prompt {prompt_id}\")\n            return None\n\n        prompt_history = history&#91;prompt_id]\n        if 'outputs' not in prompt_history:\n             print(f\"Error: 'outputs' not found in history for prompt {prompt_id}\")\n             return None\n\n        # Find the output node in the history\n        for node_id, node_output in prompt_history&#91;'outputs'].items():\n             # Check if this node's class type matches the expected output type\n            if node_output.get('class_type') == output_node_class_type:\n                print(f\"Found output in history from node {node_id} ({output_node_class_type}): {node_output}\")\n                output_data = node_output # Use the first match found in history\n                break # Assuming one main output node of this type\n\n        if output_data is None:\n             print(f\"Error: Output node with class type '{output_node_class_type}' not found in history outputs.\")\n             print(f\"Available history outputs: {prompt_history&#91;'outputs']}\")\n             return None\n\n\n    # --- Process the identified outputs ---\n    results = &#91;]\n    if output_data:\n        # Output data structure varies (e.g., {'images': &#91;...]}, {'gifs': &#91;...]})\n        key_found = None\n        for key in &#91;'images', 'gifs', 'videos']: # Add other possible keys if needed\n             if key in output_data:\n                 key_found = key\n                 break\n\n        if not key_found:\n             print(f\"Error: Could not find expected output key (images, gifs, videos) in node output: {output_data}\")\n             return None\n\n        for output_item in output_data&#91;key_found]:\n            image_data = get_image(output_item&#91;'filename'], output_item.get('subfolder',''), output_item&#91;'type'], comfyui_address)\n            if image_data:\n                results.append({\n                    \"data\": image_data,\n                    \"filename\": output_item&#91;'filename'],\n                    \"content_type\": f\"image\/{output_item&#91;'format']}\" if 'format' in output_item else 'application\/octet-stream' # Adjust mime type if needed\n                })\n            else:\n                 print(f\"Warning: Failed to retrieve data for output item {output_item&#91;'filename']}\")\n\n    return results\n\n# --- Flask App ---\n\napp = Flask(__name__)\n\n@app.route('\/generate', methods=&#91;'POST'])\ndef generate():\n    if not request.is_json:\n        return jsonify({\"error\": \"Request must be JSON\"}), 400\n\n    data = request.get_json()\n    prompt_text = data.get('prompt')\n    negative_prompt_text = data.get('negative_prompt', '') # Optional negative prompt\n    base64_image = data.get('image') # Optional base64 encoded image\n\n    if not prompt_text:\n        return jsonify({\"error\": \"Missing 'prompt' in request data\"}), 400\n\n    # --- 1. Load Workflow ---\n    try:\n        with open(WORKFLOW_API_JSON, 'r') as f:\n            prompt_workflow = json.load(f)\n    except FileNotFoundError:\n        return jsonify({\"error\": f\"Workflow file '{WORKFLOW_API_JSON}' not found.\"}), 500\n    except json.JSONDecodeError:\n        return jsonify({\"error\": f\"Invalid JSON in workflow file '{WORKFLOW_API_JSON}'.\"}), 500\n    except Exception as e:\n         return jsonify({\"error\": f\"Error loading workflow: {e}\"}), 500\n\n    # --- 2. Handle Image Upload (if provided) ---\n    uploaded_filename = None\n    if base64_image:\n        try:\n            import base64\n            image_data = base64.b64decode(base64_image)\n            filename, subfolder, img_type = upload_image(image_data, COMFYUI_ADDRESS)\n            if not filename:\n                return jsonify({\"error\": \"Failed to upload image to ComfyUI\"}), 500\n            uploaded_filename = filename\n            print(f\"Uploaded image filename: {uploaded_filename}\")\n        except base64.binascii.Error:\n            return jsonify({\"error\": \"Invalid base64 image data\"}), 400\n        except Exception as e:\n            return jsonify({\"error\": f\"Error processing image upload: {e}\"}), 500\n\n    # --- 3. Modify Workflow ---\n    try:\n        # Find Positive Prompt Node\n        pos_prompt_node_id = find_node_id_by_title(prompt_workflow, NODE_TITLE_POSITIVE_PROMPT)\n        if not pos_prompt_node_id:\n            print(f\"Warning: Node with title '{NODE_TITLE_POSITIVE_PROMPT}' not found in workflow.\")\n            # If not found by title, try finding a common text input node type\n            possible_types = &#91;\"CLIPTextEncode\", \"CLIPTextEncodeSDXL\"]\n            for node_id, node_data in prompt_workflow.items():\n                if node_data&#91;\"class_type\"] in possible_types and \"Positive\" in node_data.get(\"_meta\", {}).get(\"title\", \"\"):\n                     pos_prompt_node_id = node_id\n                     print(f\"Found potential positive prompt node by type\/partial title: {node_id}\")\n                     break\n            if not pos_prompt_node_id:\n                 return jsonify({\"error\": f\"Could not find Positive Prompt node ('{NODE_TITLE_POSITIVE_PROMPT}') in workflow.\"}), 500\n\n        # Update Positive Prompt Text\n        # The prompt text is usually within inputs -&gt; text\n        if 'text' in prompt_workflow&#91;pos_prompt_node_id]&#91;'inputs']:\n             prompt_workflow&#91;pos_prompt_node_id]&#91;'inputs']&#91;'text'] = prompt_text\n        # Sometimes it might be in widgets_values (older ComfyUI versions?)\n        elif 'widgets_values' in prompt_workflow&#91;pos_prompt_node_id] and isinstance(prompt_workflow&#91;pos_prompt_node_id]&#91;'widgets_values'], list):\n             # Find the widget by name (usually 'text' or similar)\n             widget_index = next((i for i, w_name in enumerate(prompt_workflow&#91;pos_prompt_node_id].get('widgets_info', &#91;])) if w_name == 'text'), -1)\n             if widget_index != -1 and widget_index &lt; len(prompt_workflow&#91;pos_prompt_node_id]&#91;'widgets_values']):\n                 prompt_workflow&#91;pos_prompt_node_id]&#91;'widgets_values']&#91;widget_index] = prompt_text\n             else:\n                  print(f\"Warning: Could not find 'text' widget input for node {pos_prompt_node_id}. Prompt may not be set.\")\n        else:\n            print(f\"Warning: Could not find 'text' input or 'widgets_values' for node {pos_prompt_node_id}. Prompt may not be set.\")\n\n        print(f\"Updated Positive Prompt Node '{pos_prompt_node_id}' with text: '{prompt_text&#91;:50]}...'\")\n\n        # Find and Update Negative Prompt Node (Optional)\n        if negative_prompt_text and NODE_TITLE_NEGATIVE_PROMPT:\n            neg_prompt_node_id = find_node_id_by_title(prompt_workflow, NODE_TITLE_NEGATIVE_PROMPT)\n            if neg_prompt_node_id:\n                 if 'text' in prompt_workflow&#91;neg_prompt_node_id]&#91;'inputs']:\n                    prompt_workflow&#91;neg_prompt_node_id]&#91;'inputs']&#91;'text'] = negative_prompt_text\n                    print(f\"Updated Negative Prompt Node '{neg_prompt_node_id}' with text: '{negative_prompt_text&#91;:50]}...'\")\n                 else:\n                     print(f\"Warning: Could not find 'text' input for negative prompt node {neg_prompt_node_id}.\")\n            else:\n                 print(f\"Warning: Negative prompt provided, but node '{NODE_TITLE_NEGATIVE_PROMPT}' not found.\")\n\n\n        # Find Load Image Node and Update Filename (if image was uploaded)\n        if uploaded_filename:\n            load_image_node_id = find_node_id_by_title(prompt_workflow, NODE_TITLE_LOAD_IMAGE)\n            if not load_image_node_id:\n                # Fallback: Try finding by class type if title doesn't match\n                load_image_node_id = find_node_id_by_class_type(prompt_workflow, \"LoadImage\")\n                if load_image_node_id:\n                     print(f\"Found LoadImage node by class type: {load_image_node_id}\")\n                else:\n                     return jsonify({\"error\": f\"Input image provided, but Load Image node ('{NODE_TITLE_LOAD_IMAGE}' or type 'LoadImage') not found in workflow.\"}), 500\n\n            # Update the 'image' input field\n            if 'image' in prompt_workflow&#91;load_image_node_id]&#91;'inputs']:\n                prompt_workflow&#91;load_image_node_id]&#91;'inputs']&#91;'image'] = uploaded_filename\n                print(f\"Updated Load Image Node '{load_image_node_id}' with filename: {uploaded_filename}\")\n            else:\n                 return jsonify({\"error\": f\"Could not find 'image' input field for Load Image node '{load_image_node_id}'.\"}), 500\n\n    except Exception as e:\n        print(f\"Error modifying workflow: {e}\")\n        import traceback\n        traceback.print_exc()\n        return jsonify({\"error\": f\"Internal server error during workflow modification: {e}\"}), 500\n\n    # --- 4. Queue Prompt ---\n    print(\"\\nModified Workflow Snippet (Prompt Nodes):\")\n    if pos_prompt_node_id in prompt_workflow: print(f\"  {pos_prompt_node_id}: {prompt_workflow&#91;pos_prompt_node_id]&#91;'inputs']}\")\n    if 'neg_prompt_node_id' in locals() and neg_prompt_node_id in prompt_workflow: print(f\"  {neg_prompt_node_id}: {prompt_workflow&#91;neg_prompt_node_id]&#91;'inputs']}\")\n    if uploaded_filename and load_image_node_id in prompt_workflow: print(f\"  {load_image_node_id}: {prompt_workflow&#91;load_image_node_id]&#91;'inputs']}\")\n    print(\"\\nQueuing prompt...\")\n\n    queued_data = queue_prompt(prompt_workflow, CLIENT_ID, COMFYUI_ADDRESS)\n    if not queued_data or 'prompt_id' not in queued_data:\n        return jsonify({\"error\": \"Failed to queue prompt in ComfyUI\"}), 500\n\n    prompt_id = queued_data&#91;'prompt_id']\n    print(f\"Prompt queued successfully. Prompt ID: {prompt_id}\")\n\n    # --- 5. Get Output ---\n    print(f\"Waiting for output for prompt {prompt_id}...\")\n    output_results = get_final_outputs(prompt_id, COMFYUI_ADDRESS, OUTPUT_NODE_CLASS_TYPE)\n\n    if not output_results:\n        return jsonify({\"error\": \"Failed to retrieve output from ComfyUI execution\"}), 500\n\n    # --- 6. Return Output ---\n    if len(output_results) == 1:\n        # Return single image\/video directly\n        result = output_results&#91;0]\n        # Clean up temp workspace file (optional)\n        # local_filepath = os.path.join(WORKSPACE_DIR, result&#91;\"filename\"])\n        # if os.path.exists(local_filepath):\n        #     os.remove(local_filepath)\n\n        return send_file(\n            io.BytesIO(result&#91;\"data\"]),\n            mimetype=result&#91;\"content_type\"],\n            as_attachment=False, # Display inline if possible\n            download_name=result&#91;\"filename\"]\n        )\n    else:\n        # Handle multiple outputs (e.g., return JSON with list of base64 or links?)\n        # For simplicity, let's just return the first one for now\n        print(f\"Warning: Workflow produced multiple outputs ({len(output_results)}). Returning the first one.\")\n        result = output_results&#91;0]\n        return send_file(\n            io.BytesIO(result&#91;\"data\"]),\n            mimetype=result&#91;\"content_type\"],\n            as_attachment=False,\n            download_name=result&#91;\"filename\"]\n        )\n\n# --- Main Execution ---\n\nif __name__ == '__main__':\n    # Create workspace directory if it doesn't exist\n    if not os.path.exists(WORKSPACE_DIR):\n        os.makedirs(WORKSPACE_DIR)\n\n    print(f\"MCP Server starting...\")\n    print(f\" - ComfyUI API: http:\/\/{COMFYUI_ADDRESS}\")\n    print(f\" - Listening on: http:\/\/{SERVER_ADDRESS}:{SERVER_PORT}\")\n    print(f\" - Using Workflow: {WORKFLOW_API_JSON}\")\n    print(f\"   - Positive Prompt Node Title: '{NODE_TITLE_POSITIVE_PROMPT}'\")\n    print(f\"   - Negative Prompt Node Title: '{NODE_TITLE_NEGATIVE_PROMPT}' (Optional)\")\n    print(f\"   - Load Image Node Title: '{NODE_TITLE_LOAD_IMAGE}' (Optional)\")\n    print(f\"   - Final Output Node Type: '{OUTPUT_NODE_CLASS_TYPE}'\")\n\n    app.run(host=SERVER_ADDRESS, port=SERVER_PORT, debug=False) # Turn debug=False for production<\/code><\/pre>\n            <\/div>\n\n\n\n<p><strong>How to Use:<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Save the script:<\/strong> Save the code above as <code>mcp_server.py<\/code>.<\/li>\n\n\n\n<li><strong>Get Workflow JSON:<\/strong>\n<ul class=\"wp-block-list\">\n<li>Design your ComfyUI workflow (e.g., LoadImage -> CLIPTextEncode (Prompt) -> KSampler -> SaveImage).<\/li>\n\n\n\n<li>Make sure the nodes you want to modify have recognizable titles (e.g., &#8220;Positive Prompt&#8221;, &#8220;Input Image&#8221;). You can set titles by right-clicking a node -> Title.<\/li>\n\n\n\n<li>Click &#8220;Save (API Format)&#8221; in ComfyUI and save the file as <code>workflow_api.json<\/code> in the same directory as the script.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Configure the Script:<\/strong>\n<ul class=\"wp-block-list\">\n<li>Edit the <code>COMFYUI_ADDRESS<\/code> if your ComfyUI is not running on <code>127.0.0.1:8188<\/code>.<\/li>\n\n\n\n<li><strong>Crucially:<\/strong> Adjust <code>NODE_TITLE_POSITIVE_PROMPT<\/code>, <code>NODE_TITLE_LOAD_IMAGE<\/code>, <code>NODE_TITLE_NEGATIVE_PROMPT<\/code> (if used), and <code>OUTPUT_NODE_CLASS_TYPE<\/code> to match the exact titles\/type you used in your ComfyUI workflow. If titles don&#8217;t work reliably, you might need to hardcode the node IDs from the <code>workflow_api.json<\/code> file instead.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Run ComfyUI:<\/strong> Start your ComfyUI instance (remember <code>--enable-cors<\/code> if needed, though maybe not essential for this server-to-server setup).<\/li>\n\n\n\n<li><strong>Run the Script:<\/strong><br><code>bash python mcp_server.py<\/code><\/li>\n\n\n\n<li><strong>Send Requests:<\/strong> Use a tool like <code>curl<\/code>, Postman, or another script to send POST requests to <code>http:\/\/&lt;your_server_ip>:9090\/generate<\/code>.<\/li>\n<\/ol>\n\n\n\n<p><strong>Example <code>curl<\/code> requests:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Text-only:<\/strong> <code>curl -X POST -H \"Content-Type: application\/json\" \\ -d '{\"prompt\": \"A photo of an astronaut riding a horse on the moon\"}' \\ http:\/\/127.0.0.1:9090\/generate --output output.png<\/code><\/li>\n\n\n\n<li><strong>Text and Image:<\/strong>\n<ul class=\"wp-block-list\">\n<li>First, base64 encode your image (e.g., <code>input.jpg<\/code>):<br><code>bash # On Linux\/macOS: IMAGE_B64=$(base64 -w 0 input.jpg) # On Windows (PowerShell): # $IMAGE_B64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes(\"input.jpg\"))<\/code><\/li>\n\n\n\n<li>Then send the request:<br><code>bash curl -X POST -H \"Content-Type: application\/json\" \\ -d '{ \"prompt\": \"Make this image look like a watercolor painting\", \"image\": \"'\"$IMAGE_B64\"'\" }' \\ http:\/\/127.0.0.1:9090\/generate --output output_watercolor.png<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>This script provides a solid foundation. You might need to adapt the node finding logic, error handling, or output processing depending on the specifics of your workflows and desired behavior. Remember to check the console output of both this script and ComfyUI for debugging information.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Okay, let&#8217;s create a Python script using Flask to act as a server that interacts with the ComfyUI API. This script will: Prerequisites: Python Script (mcp_server.py): How to Use: Example curl requests: This script provides a solid foundation. You might need to adapt the node finding logic, error handling, or output processing depending on the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"googlesitekit_rrm_CAowvqSiDA:productID":"","footnotes":""},"class_list":["post-5327","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/infernews.com\/blog\/wp-json\/wp\/v2\/pages\/5327","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/infernews.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/infernews.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/infernews.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/infernews.com\/blog\/wp-json\/wp\/v2\/comments?post=5327"}],"version-history":[{"count":0,"href":"https:\/\/infernews.com\/blog\/wp-json\/wp\/v2\/pages\/5327\/revisions"}],"wp:attachment":[{"href":"https:\/\/infernews.com\/blog\/wp-json\/wp\/v2\/media?parent=5327"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}