""" MCP & Gradio Integration Module Provides: 1. register_high_level_mcp_apis: Expose only 8 high-level abstract API/MCP endpoints (using gr.api without polluting the visual UI structure) 2. cleanup_dependencies_api_names: Force cleanup of show_api attribute for non-high-level APIs in dependencies 3. patch_gradio_api_suppression: No-op implementation retained for backward compatibility """ import json import gradio as gr from .get_task_list import handle_get_task_list from .get_model_architecture_list import handle_get_model_architecture_list from .get_model_list import handle_get_model_list from .get_feature_list import handle_get_feature_list from .get_model_features import handle_get_model_features from .get_chain_schema import handle_get_chain_schema from .run_imagegen import handle_run_imagegen from .get_task_status import handle_get_task_status HIGH_LEVEL_MCP_API_NAMES = { "get_task_list", "get_model_architecture_list", "get_model_list", "get_feature_list", "get_model_features", "run_imagegen", "get_task_status", "get_chain_schema", } def sanitize_keys(obj): """Recursively ensure all dictionary keys are converted to str type to avoid Gradio 5 orjson TypeError: Dict key must be str.""" if isinstance(obj, dict): return {str(k): sanitize_keys(v) for k, v in obj.items()} elif isinstance(obj, list): return [sanitize_keys(x) for x in obj] elif isinstance(obj, tuple): return tuple(sanitize_keys(x) for x in obj) return obj def patch_gradio_api_suppression(): """Retained for backward compatibility (no-op).""" pass def cleanup_dependencies_api_names(demo): """ Clean up residual auto-generated API names in demo.fns and demo.dependencies. Force only the 8 high-level abstract MCP APIs to be exposed as public endpoints. """ for fn in demo.fns.values(): api_name = getattr(fn, "api_name", None) if api_name not in HIGH_LEVEL_MCP_API_NAMES: fn.show_api = False deps = getattr(demo, "dependencies", None) if deps is None and hasattr(demo, "config") and isinstance(demo.config, dict): deps = demo.config.get("dependencies", []) if deps: for dep in deps: if isinstance(dep, dict): api_name = dep.get("api_name") if api_name not in HIGH_LEVEL_MCP_API_NAMES: dep["show_api"] = False print("[MCP Protection] Cleaned up demo dependencies. Suppressed atomic API endpoints.") def register_high_level_mcp_apis(demo): """ Explicitly register 8 high-level abstract MCP API endpoints on the Gradio demo using gr.api. Using gr.api() never adds any visual UI components (such as Row, Textbox, Button, etc.), avoiding duplicate interface rendering. """ def get_task_list() -> list: """[Recommended Discovery Flow Step 1] Get a list of all supported image generation task types (txt2img, img2img, inpaint, outpaint, hires_fix) along with their required and optional parameter lists. Recommended flow: get_task_list -> get_model_architecture_list -> get_model_list -> [Path 1: Call run_imagegen directly (pass only required params) | Path 2: Call get_model_features to get official default hyperparams -> run_imagegen].""" return sanitize_keys(handle_get_task_list()) def get_model_architecture_list() -> list: """[Recommended Discovery Flow Step 2] Get a list of all supported model architectures (e.g., SD1.5, SDXL, FLUX, etc.) along with their default resolutions. It is recommended to call this tool before get_model_list to obtain valid model_architecture parameters for precise model filtering.""" return sanitize_keys(handle_get_model_architecture_list()) def get_model_list(model_architecture: str = "") -> list | dict: """[Recommended Discovery Flow Step 3] Query the list of available image generation models. After obtaining models, choose one of two paths: 1. [Path 1 (Recommended - Minimal Mode)] Call run_imagegen directly with only required parameters. Do NOT guess steps/cfg/sampler/scheduler from experience; the server will automatically apply the model's optimal default hyperparameters. 2. [Path 2 (Explicit Alignment Mode)] First call get_model_features to query the model's officially recommended hyperparameters, then pass them to run_imagegen.""" arch = model_architecture.strip() if model_architecture else None return sanitize_keys(handle_get_model_list(arch)) def get_feature_list() -> list: """Get the list of supported advanced features along with their usage constraints and parameter schemas.""" return sanitize_keys(handle_get_feature_list()) def get_model_features(model: str = "") -> dict: """Query metadata for the specified model, including supported task types, extended features, and official default inference parameters (steps, cfg, sampler, scheduler). This tool MUST be called when explicitly obtaining a model's optimal default hyperparameters (Path 2). Guessing or fabricating hyperparameters without querying is strictly prohibited.""" return sanitize_keys(handle_get_model_features(model.strip())) def run_imagegen(json_params: str = "{}") -> dict: """[Recommended Discovery Flow Step 4] Unified image generation task execution interface. Supports txt2img, img2img, and other tasks with chainable extended features. [IMPORTANT PARAMETER RULES] Do NOT guess or fabricate inference hyperparameters such as steps, cfg, sampler, scheduler! Path 1 (Recommended): Pass only required parameters (task_type, model, prompt, width, height), leave optional hyperparams empty (server uses optimal defaults). Path 2: If explicit hyperparams are needed, you MUST first call get_model_features to obtain official defaults before passing them.""" try: if isinstance(json_params, dict): params = json_params else: params = json.loads(json_params or "{}") except Exception as e: return {"error": {"code": "INVALID_JSON", "message": f"Failed to parse JSON params: {e}"}} return sanitize_keys(handle_run_imagegen(params)) def get_task_status(task_id: str = "") -> dict: """Query the progress, status, and final generated results of an async image generation task.""" return sanitize_keys(handle_get_task_status(task_id.strip())) def get_chain_schema(chain_type: str = "") -> dict: """Get the complete parameter schema and usage examples for a specified chain/injector type.""" return sanitize_keys(handle_get_chain_schema(chain_type.strip())) funcs = [ get_task_list, get_model_architecture_list, get_model_list, get_feature_list, get_model_features, run_imagegen, get_task_status, get_chain_schema, ] for func in funcs: gr.api(func) for fn in demo.fns.values(): if getattr(fn, "api_name", None) in HIGH_LEVEL_MCP_API_NAMES: fn.show_api = True print("[MCP Integration] Successfully registered 8 High-Level Abstract MCP APIs via gr.api().")