"""Publish tools for safely publishing ComfyUI assets to web project directories
Thin MCP wrapper around PublishManager for asset publishing.
NO business logic - delegates to orchestrator.
"""
from fastmcp import FastMCP
from src.orchestrators.asset import AssetOrchestrator
from src.orchestrators.publish import PublishManager
from src.utils import get_global_logger
logger = get_global_logger("MCP_Server.tools.publish")
def register_publish_tools(
mcp: FastMCP, asset_orchestrator: AssetOrchestrator, publish_manager: PublishManager
):
"""Register publish tools with the MCP server
Args:
mcp: FastMCP server instance
asset_orchestrator: Orchestrator for asset operations
publish_manager: Orchestrator for publishing operations
"""
@mcp.tool()
def get_publish_info() -> dict:
"""Get publish configuration and status information.
This tool helps debug configuration issues and surfaces wrong assumptions
immediately. Call this first to verify publish setup before attempting to publish.
Returns:
Dict with:
- project_root: Detected project root (path and detection method: "cwd" | "auto-detected")
- publish_root: Publish directory (path, exists, writable)
- comfyui_output_root: ComfyUI output root (path or None, detection method, configured flag)
- comfyui_tried_paths: List of paths checked during detection with validation results
- config_file: Path to persistent config file
- status: "ready" | "needs_comfyui_root" | "error"
- message: Human-readable status/instructions
- warnings: List of warnings (e.g., "Using fallback project root detection")
- error_code: Machine-readable error code (if not ready)
"""
return publish_manager.get_publish_info()
@mcp.tool()
def set_comfyui_output_root(path: str) -> dict:
"""Set ComfyUI output root directory in persistent configuration.
This tool configures the ComfyUI output directory once, and it will be remembered
across server restarts. Use this when auto-detection fails or you want explicit control.
The path is validated to ensure:
- It exists and is a directory
- It appears to be a ComfyUI output directory (contains ComfyUI_*.png files or output/temp subdirs)
Configuration is stored in a platform-specific location:
- 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
Args:
path: Absolute or relative path to ComfyUI output directory
(e.g., "E:/comfyui-desktop/output" or "/opt/ComfyUI/output")
Returns:
Dict with:
- success: True if configured successfully
- path: Resolved absolute path
- config_file: Path to config file where setting was saved
- message: Human-readable status message
- error: Error code if configuration failed
"""
return publish_manager.set_comfyui_output_root(path)
@mcp.tool()
def publish_asset(
asset_id: str,
target_filename: str | None = None,
manifest_key: str | None = None,
web_optimize: bool = False,
max_bytes: int = 600_000,
overwrite: bool = True,
) -> dict:
"""Publish a ComfyUI-generated asset to a web project directory.
Supports two modes:
- **Demo mode**: Provide `target_filename` (e.g., "hero.png") - deterministic filename
- **Library mode**: Omit `target_filename`, provide `manifest_key` - auto-generates filename
**Default behavior (web_optimize=False):**
- Assets are copied as-is, preserving original format (typically PNG from ComfyUI)
- No compression or format conversion
- Original quality preserved
**Web optimization (web_optimize=True):**
- Images are converted to WebP format
- Compression ladder applied to meet size limits
- Useful for web deployment where smaller file sizes are important
Args:
asset_id: Asset ID from generation tools (e.g., from generate_image)
target_filename: Optional deterministic filename (e.g., "hero.png", "logo.webp")
manifest_key: Optional manifest key for library mode (e.g., "hero-image", "product-shot-1")
web_optimize: If True, convert to WebP and apply compression to meet size limit
max_bytes: Maximum file size in bytes for web optimization (default: 600000 = ~600KB)
overwrite: If True, overwrite existing files (default: True for demo mode)
Returns:
Dict with:
- success: True if published successfully
- publish_path: Absolute path to published file
- web_path: Relative web path (e.g., "/assets/hero.png")
- original_size: Original file size in bytes
- final_size: Final published file size in bytes
- compression_ratio: Compression ratio (if optimized)
- manifest_entry: Manifest entry (if manifest_key provided)
- error: Error message if publish failed
Examples:
# Demo mode: deterministic filename
publish_asset(asset_id="abc123", target_filename="hero.png")
# Library mode: auto-generated filename with manifest
publish_asset(asset_id="abc123", manifest_key="hero-image")
# Web optimization: compress to 600KB
publish_asset(asset_id="abc123", target_filename="hero.webp", web_optimize=True, max_bytes=600000)
# Prevent overwriting existing files
publish_asset(asset_id="abc123", target_filename="hero.png", overwrite=False)
"""
try:
# Get asset record from orchestrator
asset_record = asset_orchestrator.get_asset_record(asset_id)
if not asset_record:
return {
"error": f"Asset {asset_id} not found (registry is in-memory and resets on restart). "
"Generate a new asset first."
}
# Fetch asset bytes
asset_bytes = asset_orchestrator.fetch_asset_bytes(asset_record)
# Publish using publish manager
result = publish_manager.publish_asset(
asset_bytes=asset_bytes,
asset_record=asset_record,
target_filename=target_filename,
manifest_key=manifest_key,
web_optimize=web_optimize,
max_bytes=max_bytes,
overwrite=overwrite,
)
return result
except Exception as e:
logger.exception(f"Failed to publish asset {asset_id}")
return {"error": f"Failed to publish: {str(e)}"}