Skip to content

Latest commit

 

History

History
862 lines (650 loc) · 31.2 KB

File metadata and controls

862 lines (650 loc) · 31.2 KB

Architecture

High-level architecture and design decisions for ComfyUI MCP Server.

Overview

The server bridges MCP (Model Context Protocol) and ComfyUI, providing a standardized interface for AI agents to generate media through ComfyUI workflows.

Design Decisions

These foundational decisions shape the entire architecture and should be understood before diving into implementation details.

Why Streamable-HTTP?

  • Better scalability than WebSocket
  • Cloud-ready (works behind load balancers)
  • Standard HTTP tooling
  • Stateless (easier to scale horizontally)

Why Stable Asset Identity?

Problem: URL-based identity breaks with hostname/port changes.

Solution: Use (filename, subfolder, type) tuple as stable identity:

  • Works across different ComfyUI instances (localhost, 127.0.0.1, different ports)
  • Resilient to ComfyUI restarts for already-known output identities (URL computation)
  • URLs computed on-the-fly from base_url
  • O(1) lookups via dual-index structure

Benefits:

  • Robust to configuration changes
  • No "thor:8188" hostname bugs
  • Portable across deployments

Why UUID Asset IDs?

  • Globally unique external reference
  • Opaque (doesn't leak internal structure)
  • Standard format
  • Separate from stable identity (internal lookup)

Why Full Provenance Storage?

Stored Data:

  • comfy_history: Full /history/{prompt_id} response snapshot
  • submitted_workflow: Original workflow JSON submitted to ComfyUI

Benefits:

  • Free reproducibility (can regenerate with exact parameters)
  • Debugging becomes trivial (see exactly what was submitted)
  • Enables regenerate() tool without re-specifying everything
  • Complete audit trail

Trade-offs:

  • History snapshots can be large for complex workflows
  • TTL-based expiration (24h) limits growth
  • Future: Consider compression or selective field storage

Why TTL Instead of Manual Deletion?

  • Automatic cleanup reduces memory usage
  • No manual management needed
  • Predictable behavior
  • Configurable per deployment

Why Separate view_image Tool?

  • Lazy loading: Only fetch/process when needed
  • Size control: Enforce limits at viewing time
  • Format conversion: Optimize for display
  • Separation of concerns: Generation vs. viewing

Why Thin Adapter Architecture?

The server delegates execution to ComfyUI rather than reimplementing:

  • Queue logic: Direct passthrough to /queue endpoint
  • History tracking: Direct passthrough to /history endpoint
  • Job cancellation: Direct passthrough to /queue with delete action

Benefits:

  • No sync issues (ComfyUI is source of truth)
  • Minimal code surface
  • Leverages ComfyUI's native capabilities
  • Easier to maintain (changes in ComfyUI automatically reflected)

Core Components

WorkflowManager

Purpose: Discovers, loads, and processes ComfyUI workflow JSON files.

Responsibilities:

  • Scan workflows/ directory for JSON files
  • Extract parameters from PARAM_* placeholders
  • Build WorkflowToolDefinition objects
  • Render workflows with provided parameters
  • Apply constrained overrides (for run_workflow)

Key Methods:

  • _load_workflows(): Discovery and loading
  • _extract_parameters(): Placeholder parsing
  • render_workflow(): Parameter substitution
  • apply_workflow_overrides(): Constrained parameter updates

DefaultsManager

Purpose: Manages default values with configurable precedence.

Precedence Order:

  1. Per-call values (explicit parameters)
  2. Runtime defaults (set_defaults tool)
  3. Config file (~/.config/comfy-mcp/config.json)
  4. Environment variables
  5. Hardcoded defaults

Key Methods:

  • get_default(): Resolve value with precedence
  • set_defaults(): Set runtime defaults
  • persist_defaults(): Write to config file

AssetRegistry

Purpose: Track generated assets for viewing and management.

Features:

  • UUID-based asset IDs for external reference
  • Stable identity using (filename, subfolder, type) tuple (robust to URL changes)
  • TTL-based expiration (default 24 hours)
  • O(1) lookups via dual-index structure (_assets and _asset_key_to_id)
  • Full provenance storage (comfy_history, submitted_workflow)
  • Session tracking for conversation isolation
  • Automatic cleanup of expired assets

Key Methods:

  • register_asset(): Register new asset with stable identity, return AssetRecord
  • get_asset(): Retrieve by ID (checks expiration)
  • list_assets(): List assets with optional filtering (workflow_id, session_id)
  • cleanup_expired(): Remove expired assets

Stable Identity Design: Assets are identified by (filename, subfolder, folder_type) instead of URLs, making the system robust to:

  • Hostname/port/base-url changes
  • Resilient to ComfyUI restarts for already-known output identities

URLs are computed on-the-fly from the stable identity when needed.

Note: Stable identity prevents URL/base changes from breaking computed URLs, but does not imply persistence of the asset registry across MCP server restarts.

ComfyUIClient

Purpose: Interface with ComfyUI API as a thin adapter.

Responsibilities:

  • Queue workflows via /prompt endpoint
  • Poll for completion via /history/{prompt_id}
  • Extract asset info (filename, subfolder, type) from outputs (stable identity)
  • Fetch asset metadata (size, dimensions, mime type)
  • Direct passthrough to ComfyUI queue and history endpoints
  • Cancel queued/running jobs

Key Methods:

  • run_custom_workflow(): Execute workflow and wait for completion, returns stable identity + provenance
  • _queue_workflow(): Submit workflow to ComfyUI
  • _wait_for_prompt(): Poll until completion
  • _extract_first_asset_info(): Extract (filename, subfolder, type) from outputs
  • get_queue(): Direct passthrough to /queue endpoint
  • get_history(prompt_id): Direct passthrough to /history endpoint
  • cancel_prompt(prompt_id): Cancel queued or running jobs

Thin Adapter Philosophy: The client delegates execution to ComfyUI rather than reimplementing queue logic. ComfyUI is the source of truth for job state.

Workflow System

Discovery

  1. WorkflowManager scans workflows/ directory
  2. Loads JSON files (skips .meta.json files)
  3. Extracts PARAM_* placeholders
  4. Builds parameter definitions with types and bindings
  5. Creates WorkflowToolDefinition objects

Parameter Extraction

Placeholder Format: PARAM_<TYPE?>_<NAME>

Examples:

  • PARAM_PROMPTprompt: str (required)
  • PARAM_INT_STEPSsteps: int (optional)
  • PARAM_FLOAT_CFGcfg: float (optional)

Binding: Maps to [node_id, input_name] in workflow JSON

Tool Registration

  1. register_workflow_generation_tools() iterates over definitions
  2. Creates dynamic tool functions with proper signatures
  3. Handles type coercion (JSON-RPC strings → Python types)
  4. Registers with FastMCP via @mcp.tool() decorator

Execution Flow

  1. Tool called with parameters
  2. render_workflow() substitutes placeholders with values
  3. Defaults applied for missing optional parameters
  4. Workflow queued to ComfyUI via /prompt endpoint
  5. Server polls for completion via /history/{prompt_id}
  6. Asset info extracted from outputs: (filename, subfolder, type) (stable identity)
  7. Full history snapshot fetched from ComfyUI
  8. Asset registered in AssetRegistry with:
    • Stable identity (filename, subfolder, folder_type)
    • Provenance data (comfy_history, submitted_workflow)
    • Session ID (if provided)
  9. Asset URL computed from stable identity
  10. Response returned with asset_id, asset_url, and metadata

Asset Lifecycle

Generation

  1. Workflow executes in ComfyUI
  2. Asset saved to ComfyUI output directory
  3. ComfyUI returns output metadata

Registration

  1. AssetRegistry.register_asset() called with (filename, subfolder, type) stable identity
  2. UUID generated for asset_id (external reference)
  3. Stable identity key created: f"{folder_type}:{subfolder}:{filename}"
  4. Deduplication check: if asset with same identity exists and not expired, return existing
  5. Expiration time calculated (now + TTL)
  6. AssetRecord created with:
    • Stable identity fields (filename, subfolder, folder_type)
    • Provenance data (comfy_history, submitted_workflow)
    • Session ID (for conversation isolation)
  7. Dual-index storage:
    • _assets[asset_id]AssetRecord (UUID lookup)
    • _asset_key_to_id[asset_key]asset_id (identity lookup)

Viewing

  1. view_image called with asset_id
  2. AssetRegistry.get_asset() retrieves record by UUID
  3. Expiration checked (returns None if expired)
  4. Asset URL computed from stable identity: get_asset_url(base_url)
  5. Asset bytes fetched from ComfyUI /view endpoint (URL-encoded for special characters)
  6. Image processed (downscale, re-encode as WebP)
  7. Base64-encoded thumbnail returned

URL Computation: URLs are computed on-the-fly from stable identity, ensuring they work even if ComfyUI base URL changes:

asset_url = f"{base_url}/view?filename={quote(filename)}&subfolder={quote(subfolder)}&type={folder_type}"

Expiration

  1. Assets expire after TTL (default 24 hours)
  2. cleanup_expired() removes expired records
  3. Called periodically during view_image operations

Image Processing Pipeline

Purpose

Convert large images to small, context-friendly thumbnails for inline display in chat (i.e. for image injection into AI context).

Constraints

  • Size limit: 100KB base64 payload (configurable)
  • Dimension limit: 512px max dimension (configurable)
  • Format: WebP (efficient compression)

Process

  1. Fetch: Download image bytes from ComfyUI
  2. Load: Open with Pillow, apply EXIF orientation
  3. Downscale: Resize to fit within max_dim (maintain aspect ratio)
  4. Optimize: Quality ladder (70 → 55 → 40) to fit budget
  5. Encode: Save as WebP, base64 encode
  6. Validate: Check total payload size (base64 + data URI prefix)
  7. Cache: Store result (LRU cache, max 100 entries)

Why WebP?

  • Better compression than PNG/JPEG
  • Supports transparency
  • Widely supported in modern clients
  • Good quality/size tradeoff

Why Thumbnails Only?

  • Context window limits in AI chat interfaces
  • Base64 encoding adds ~33% overhead
  • Large images cause context bloat and crashes
  • Thumbnails provide visual feedback without cost

Default Value System

Why Multiple Sources?

Different use cases need different defaults:

  • Hardcoded: Sensible defaults for new users
  • Config file: Persistent preferences
  • Runtime: Session-specific overrides
  • Environment: Deployment-specific settings

Resolution Algorithm

def get_default(namespace, key, provided_value):
    if provided_value is not None:
        return provided_value  # Explicit wins
    
    # Check in order of precedence
    if key in runtime_defaults[namespace]:
        return runtime_defaults[namespace][key]
    if key in config_defaults[namespace]:
        return config_defaults[namespace][key]
    if key in env_defaults[namespace]:
        return env_defaults[namespace][key]
    if key in hardcoded_defaults[namespace]:
        return hardcoded_defaults[namespace][key]
    
    return None  # No default found

Security Considerations

Path Traversal Protection

Workflow IDs are sanitized before file access:

safe_id = workflow_id.replace("/", "_").replace("\\", "_").replace("..", "_")
safe_id = "".join(c for c in safe_id if c.isalnum() or c in ("_", "-"))

Resolved paths are validated to be within workflows/ directory.

URL Encoding

Special characters in filenames are properly URL-encoded when constructing asset URLs:

from urllib.parse import quote
encoded_filename = quote(filename, safe='')
encoded_subfolder = quote(subfolder, safe='') if subfolder else ''

Prevents injection attacks and ensures valid URLs for all filenames.

Asset Access Control

  • Only assets generated by this server can be viewed
  • asset_id must exist in registry (UUID lookup)
  • Expired assets are automatically removed
  • No direct file system access from tools
  • Asset URLs computed from stable identity (validated)

Parameter Validation

  • Overrides constrained to declared parameters
  • Type coercion with validation
  • Constraints enforced (min/max/enum) if metadata provided
  • Workflow metadata (.meta.json) provides additional validation

Performance Considerations

Caching

  • Workflows: Cached after first load (in-memory)
  • Image previews: LRU cache (max 100 entries)
  • Model list: Cached in ComfyUIClient (refreshed on init)

Polling Strategy

  • 1-second intervals
  • Maximum 30 attempts (30 seconds)
  • Exponential backoff considered but not implemented

Memory Management

  • Asset registry: In-memory dict with dual-index structure
    • _assets: UUID → AssetRecord (O(1) lookup)
    • _asset_key_to_id: Stable identity → UUID (O(1) lookup)
  • Image cache: Limited to 100 entries (LRU)
  • Expired assets cleaned up automatically
  • Provenance data: Stored as-is (no compression), TTL limits growth

Lookup Performance

  • Asset by ID: O(1) via _assets dict
  • Asset by identity: O(1) via _asset_key_to_id dict
  • List assets: O(n log n) for sorting (n = total assets, typically small)
  • URL encoding: Applied only when computing URLs (not stored)

History Snapshot Size

comfy_history can be large for complex workflows:

  • Stored as-is (no compression in v1)
  • TTL-based expiration (24h) limits growth
  • Future: Consider compression or selective field storage

Job Management

The server provides tools for monitoring and controlling ComfyUI job execution, enabling AI agents to work asynchronously.

Queue Status

get_queue_status() provides async awareness:

  • Check if ComfyUI is busy before submitting new jobs
  • Monitor queue depth
  • Determine if a job is queued vs running

Implementation: Direct passthrough to ComfyUI /queue endpoint - no reimplementation of queue logic.

Job Polling

get_job(prompt_id) polls job completion:

  • Checks queue first (running/queued status)
  • Falls back to history endpoint for completed jobs
  • Returns structured status: completed, running, queued, error, not_found
  • Includes full history snapshot when available

Error Handling:

  • Gracefully handles missing prompt_ids
  • Distinguishes between "not found" and "error" states
  • Handles ComfyUI unavailability

Asset Browsing

list_assets() enables AI memory and iteration:

  • Lists recently generated assets
  • Filterable by workflow_id (e.g., only images)
  • Filterable by session_id (conversation isolation)
  • Returns stable identity for reliable follow-ups

Asset Metadata

get_asset_metadata(asset_id) provides full provenance:

  • Complete asset details (dimensions, size, type)
  • Full ComfyUI history snapshot
  • Original submitted workflow (enables regeneration)
  • Creation and expiration timestamps

Regeneration

regenerate(asset_id, param_overrides) enables iteration:

  • Retrieves original workflow from submitted_workflow
  • Applies parameter overrides (e.g., {"steps": 30, "cfg": 10.0})
  • Re-submits to ComfyUI with modifications
  • Preserves session ID for conversation continuity

Implementation: Uses deep copy of stored workflow, applies overrides via _update_workflow_params(), handles seed separately.

Cancellation

cancel_job(prompt_id) cancels queued or running jobs:

  • Direct passthrough to ComfyUI /queue with delete action
  • Provides user control and resource management

Publish System

The publish system enables AI agents to safely copy ComfyUI-generated assets into web project directories with deterministic filenames, optional WebP compression, and manifest management.

Purpose

Bridge the gap between ComfyUI's output directory and web project asset directories:

  • Copy generated assets to project's web directory (e.g., public/gen/)
  • Enforce provenance (only assets from current session)
  • Preserve original format by default (typically PNG from ComfyUI)
  • Optional WebP compression when web_optimize=True (deterministic compression ladder)
  • Support two modes: explicit filenames (demo) and manifest-based (library)
  • Auto-detect configuration with conservative fallbacks

Core Components

PublishConfig

Purpose: Manages publish configuration with auto-detection and persistent storage.

Responsibilities:

  • Auto-detect project root (primary: cwd, fallback: search for markers)
  • Auto-detect publish root (public/gen, static/gen, or assets/gen)
  • Auto-detect ComfyUI output root (tight candidate list, validated)
  • Load/save persistent configuration (platform-specific)
  • Track detection methods and tried paths for debugging

Key Methods:

  • detect_project_root(): Find project root with conservative fallback
  • get_default_publish_root(): Infer publish directory from project structure
  • detect_comfyui_output_root(): Best-effort detection with validation
  • load_publish_config() / save_publish_config(): Persistent config management

Configuration Priority:

  1. Explicit parameters (constructor args)
  2. Persistent config file (platform-specific)
  3. Auto-detection (best-effort)

PublishManager

Purpose: Manages safe asset publishing with path validation, compression, and manifest updates.

Responsibilities:

  • Validate source paths (must be within ComfyUI output root)
  • Validate target paths (must be within publish root)
  • Compress images using deterministic ladder
  • Update manifest.json atomically
  • Provide status reporting via get_publish_info()

Key Methods:

  • resolve_source_path(): Validate and canonicalize source path
  • resolve_target_path(): Validate and canonicalize target path
  • _compress_image(): Deterministic compression ladder
  • copy_asset(): Copy with optional compression and atomic write
  • update_manifest(): Atomic manifest update with locking
  • ensure_ready(): Centralized configuration validation
  • get_publish_info(): Comprehensive status reporting
  • set_comfyui_output_root(): Configure and persist ComfyUI output root

Publishing Flow

  1. Configuration Check: ensure_ready() validates publish root and ComfyUI output root
  2. Asset Lookup: Retrieve asset from AssetRegistry by asset_id (session-scoped)
  3. Source Resolution: resolve_source_path() validates source is within ComfyUI output root
  4. Target Resolution: resolve_target_path() validates target filename and resolves path
  5. Compression (if web_optimize=True): Deterministic compression ladder to meet max_bytes limit, otherwise copy as-is
  6. Atomic Copy: Write to temporary file, then atomic rename
  7. Manifest Update (if manifest_key provided): Atomic read/modify/write with lock

Two Publishing Modes

Demo Mode (explicit filename):

  • Agent provides target_filename (e.g., "hero.webp")
  • Deterministic output location: <publish_root>/<target_filename>
  • Manifest not updated unless manifest_key also provided
  • Use case: Direct file references, simple deployments

Library Mode (auto-generated with manifest):

  • Agent provides manifest_key, omits target_filename
  • Filename auto-generated: asset_<shortid>.webp
  • Manifest automatically updated: {"manifest_key": "filename"}
  • Use case: Hot-swappable assets, dynamic loading via manifest

Key Design Decisions

Path Safety and Canonicalization

Problem: Path traversal attacks, symlink issues, relative path confusion.

Solution: All paths are canonicalized before use:

def canonicalize_path(path: Union[str, Path], must_exist: bool = True) -> Path:
    """Resolve to absolute real path, handling symlinks."""
    resolved = Path(path).resolve(strict=must_exist)
    return resolved

Containment Checks: is_within() validates child paths are within parent directories:

def is_within(child_path: Path, parent_path: Path, child_must_exist: bool = True) -> bool:
    """Check if child_path is within parent_path."""
    child_real = canonicalize_path(child_path, must_exist=child_must_exist)
    parent_real = canonicalize_path(parent_path, must_exist=True)
    try:
        child_real.relative_to(parent_real)
        return True
    except ValueError:
        return False

Non-existent Paths: must_exist=False parameter allows validation of target paths before creation, preventing path traversal even for files that don't exist yet.

Benefits:

  • Prevents ../../../etc/passwd style attacks
  • Handles symlinks correctly (resolves to real path)
  • Works for both existing and non-existent paths
  • Clear error messages when containment fails

Provenance Enforcement

Problem: Prevent arbitrary filesystem copying, ensure assets come from ComfyUI.

Solution: Multi-layer validation:

  1. Session-scoped asset_id: Only assets registered in current session can be published
  2. Source Validation: Source paths must be within configured ComfyUI output root
  3. Asset Registry Integration: Uses existing AssetRegistry to verify asset identity

Implementation:

  • asset_id lookup fails if asset not in current session (expired or from different server instance)
  • Source path canonicalized and checked against comfyui_output_root (real path comparison)
  • No direct file system access from tools - all paths validated through manager

Benefits:

  • Prevents copying arbitrary files
  • Ensures assets come from ComfyUI runs
  • Session isolation prevents cross-contamination
  • Clear error messages when provenance fails

Persistent Configuration

Problem: Environment variables are ephemeral and not user-friendly for one-time setup.

Solution: Platform-specific config files with tool-based configuration:

  • Windows: %APPDATA%/comfyui-mcp-server/publish_config.json
  • Mac: ~/Library/Application Support/comfyui-mcp-server/publish_config.json
  • Linux: ~/.config/comfyui-mcp-server/publish_config.json

Priority Order:

  1. Explicit parameter (constructor args)
  2. Persistent config file (via set_comfyui_output_root() tool)
  3. Auto-detection (best-effort, tight candidate list)

Tool-based Configuration: set_comfyui_output_root(path) tool:

  • Validates path exists and looks like ComfyUI output directory
  • Saves to persistent config (merged with existing)
  • Provides clear error messages if validation fails

Benefits:

  • One-time setup persists across restarts
  • User-friendly (no environment variable management)
  • Platform-appropriate storage locations
  • Clear error messages guide configuration

Auto-detection with Conservative Fallbacks

Project Root Detection:

  • Primary contract: Server should be started from repo root (cwd)
  • Conservative fallback: Search upward (max 10 levels) for project markers:
    • .git, package.json, pyproject.toml, Cargo.toml
  • Ambiguity handling: If multiple markers found at different levels, raise error with guidance
  • Best guess: If no markers found, use cwd with warning

Publish Root Detection:

  • Tries in order: public/genstatic/genassets/gen
  • Uses first parent directory that exists
  • Creates directory if needed
  • Defaults to public/gen if none exist

ComfyUI Output Root Detection:

  • Tight candidate list: 2-5 specific paths (no broad scanning)
  • Priority: Persistent config → specific candidates (e.g., comfyui-desktop/output)
  • Validation: Pattern matching (ComfyUI_*.png files, image files, output/temp subdirs)
  • Reporting: All tried paths returned with validation results

Benefits:

  • Zero-config in common cases
  • Conservative (doesn't scan entire filesystem)
  • Clear error messages with tried paths
  • User can override with set_comfyui_output_root() tool

Optional WebP Compression

Problem: Need optional compression for web optimization while preserving original quality by default.

Solution: Compression is opt-in via web_optimize parameter:

  • Default (web_optimize=False): Assets copied as-is, preserving original format (typically PNG)
  • Web optimization (web_optimize=True): Convert to WebP and apply deterministic compression ladder

Deterministic Compression Ladder (when web_optimize=True):

  1. Quality progression: [85, 75, 65, 55, 45, 35]
  2. Downscale factors: [1.0, 0.9, 0.75, 0.6, 0.5] (if needed)
  3. Format conversion: PNG/JPEG → WebP

Algorithm:

for downscale_factor in [1.0, 0.9, 0.75, 0.6, 0.5]:
    im_resized = im.resize(new_size) if downscale_factor < 1.0 else im
    for quality in [85, 75, 65, 55, 45, 35]:
        compressed_bytes = save_with_quality(im_resized, quality)
        if len(compressed_bytes) <= max_bytes:
            return compressed_bytes, compression_info

Compression Info Returned:

  • compressed: Whether compression was applied
  • original_size: Original file size
  • final_size: Compressed file size
  • quality: Quality level used
  • original_dimensions: Original image dimensions
  • final_dimensions: Final image dimensions (if downscaled)
  • downscaled: Whether downscaling was applied

Benefits:

  • Default preserves original quality (no compression unless requested)
  • Reproducible results when compression is enabled (same input → same output)
  • Clear failure modes (can't compress below limit)
  • Detailed feedback for debugging
  • Predictable behavior for agents
  • Users can choose between quality preservation and file size optimization

Atomic Manifest Updates

Problem: Race conditions when multiple processes update manifest.json.

Solution: Process-level locking with atomic writes:

_manifest_lock = threading.Lock()  # Process-level lock

def update_manifest(self, manifest_key: str, filename: str):
    with self._manifest_lock:
        # Read existing manifest
        manifest = json.load(manifest_path) if manifest_path.exists() else {}
        # Update entry
        manifest[manifest_key] = filename
        # Atomic write: temp file + rename
        temp_path = manifest_path.with_suffix(".tmp")
        json.dump(manifest, temp_path)
        temp_path.replace(manifest_path)  # Atomic on most filesystems

Benefits:

  • Prevents corruption from concurrent updates
  • Atomic writes prevent partial updates
  • Simple key-value format (no arrays in v1)
  • Fast (in-memory lock, minimal I/O)

Strict Validation

Filename Validation:

  • Regex: ^[a-z0-9][a-z0-9._-]{0,63}\.(webp|png|jpg|jpeg)$
  • Prevents path traversal (../, /, \)
  • Enforces safe naming (lowercase, alphanumeric, dots, dashes, underscores)
  • Length limit: 64 characters (including extension)

Manifest Key Validation:

  • Regex: ^[a-z0-9][a-z0-9._-]{0,63}$
  • Same pattern as filename but without extension
  • Prevents injection and ensures safe keys

Early Validation: All validation happens before any file operations:

  • Filename validated before path resolution
  • Manifest key validated before manifest update
  • Source path validated before file access
  • Target path validated before write

Benefits:

  • Prevents path traversal attacks
  • Clear error messages before operations
  • Consistent validation across all entry points

Operational Honesty

Status Reporting: get_publish_info() provides comprehensive configuration status:

  • Project root (path and detection method)
  • Publish root (path and writability)
  • ComfyUI output root (path, method, validation status)
  • All tried paths with validation results
  • Status: "ready" | "needs_comfyui_root" | "error"
  • Human-readable messages and warnings

Machine-readable Errors: Standardized error codes:

  • ASSET_NOT_FOUND_OR_EXPIRED: Asset not in current session
  • COMFYUI_OUTPUT_ROOT_NOT_FOUND: ComfyUI output root not configured
  • INVALID_TARGET_FILENAME: Filename doesn't match regex
  • SOURCE_PATH_OUTSIDE_ROOT: Source file outside ComfyUI output root
  • PATH_TRAVERSAL_DETECTED: Path traversal attempt detected
  • PUBLISH_ROOT_NOT_WRITABLE: Publish directory not writable

Tried Paths Reporting: Shows all attempted detection paths with validation results:

{
  "comfyui_tried_paths": [
    {"path": "E:/comfyui-desktop/output", "exists": true, "is_valid": true, "source": "persistent_config"},
    {"path": "E:/ComfyUI/output", "exists": false, "is_valid": false, "source": "candidate"}
  ]
}

Benefits:

  • Agents can diagnose configuration issues
  • Clear guidance for fixing problems
  • No silent failures
  • Debugging information available

Security Considerations

Path Traversal Prevention

Multi-layer Defense:

  1. Filename Validation: Regex prevents ../, /, \ in filenames
  2. Path Canonicalization: All paths resolved to absolute real paths
  3. Containment Checks: is_within() validates child within parent
  4. Source Validation: Source must be within ComfyUI output root
  5. Target Validation: Target must be within publish root

Example Attack Prevention:

# Attack attempt: "../../../etc/passwd"
target_filename = "../../../etc/passwd"  # Blocked by regex validation

# Attack attempt: symlink to sensitive file
source_path = canonicalize_path(symlink)  # Resolves to real path
is_within(source_path, comfyui_output_root)  # Validates containment

Provenance Enforcement

  • Only assets from current session can be published (asset_id lookup)
  • Source paths validated against ComfyUI output root
  • No arbitrary file copying
  • Session isolation prevents cross-contamination

Atomic Operations

  • Manifest updates use process-level locking
  • File writes use temporary file + atomic rename
  • Prevents corruption from concurrent access
  • Prevents partial updates

Performance Considerations

Compression Performance

  • Default: No compression overhead when web_optimize=False (simple file copy)
  • Deterministic Ladder: Fixed sequence means predictable performance when enabled
  • Early Exit: Stops at first successful compression attempt
  • Quality vs. Size: Tries quality first (faster), then downscaling (slower)
  • Format Conversion: WebP encoding is CPU-intensive but provides best compression

Path Resolution

  • Canonicalization: Path.resolve() is O(1) for most cases (cached by filesystem)
  • Containment Check: relative_to() is O(1) for valid paths
  • Validation: Regex validation is O(n) where n is filename length (typically < 64 chars)

Manifest Updates

  • Lock Contention: Process-level lock prevents concurrent updates (acceptable for low-frequency operations)
  • Atomic Writes: Temporary file + rename is atomic on most filesystems
  • JSON Parsing: Small manifest files (< 1KB typically) parse quickly

Auto-detection

  • Project Root: Upward search limited to 10 levels (O(10) file checks)
  • Publish Root: 3 candidate checks (O(3) directory checks)
  • ComfyUI Output Root: 2-5 candidate checks with validation (O(5) file system checks)
  • Caching: Detection results cached in PublishConfig instance

Future Considerations

Potential Improvements

  • Persistent asset registry: Database backend for production (SQLite option)
  • History compression: Compress large comfy_history snapshots
  • Rate limiting: Prevent spam polling in get_job()
  • Health checks: ComfyUI connectivity monitoring
  • Metrics: Generation time, success rates
  • Batch operations: Generate multiple assets
  • Streaming: Real-time progress updates
  • Session filtering enhancements: More granular conversation isolation

Scalability

Current design is single-instance. For scale:

  • Add database backend for asset registry
  • Implement distributed locking for workflow execution
  • Add queue system for high-volume scenarios
  • Consider caching layer for ComfyUI responses