ImageGen-Studio / mcp_tools /mcp_gradio_integration.py
BlueSkyXN's picture
Deploy GitHub a51e6f6df2b2d5093fd2526a7953c2ee6a422e37
8a28a8d verified
Raw
History Blame Contribute Delete
7.54 kB
"""
MCP & Gradio Integration Module
Provides:
1. register_high_level_mcp_apis: Expose the canonical API plus Fluxus-compatible aliases
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 .run import handle_run
from .get_task_status import handle_get_task_status
from .get_chain_schema import handle_get_chain_schema
HIGH_LEVEL_MCP_API_NAMES = {
"get_task_list",
"get_model_architecture_list",
"get_model_list",
"get_feature_list",
"get_model_features",
"run",
"get_task_status",
"run_imagegen",
"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 7 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 7 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 directly (pass only required params) | Path 2: Call get_model_features to get official default hyperparams -> run]."""
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 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."""
arch = model_architecture.strip() if model_architecture else None
return sanitize_keys(handle_get_model_list(arch))
def get_feature_list(feature_name: str = "", compact: bool = False) -> list | dict:
"""Get supported advanced features. Empty input returns Fluxus-compatible full schemas; set compact=true for a token-saving summary. Pass feature_name for one or more complete schemas."""
return sanitize_keys(
handle_get_feature_list(
feature_name.strip() if isinstance(feature_name, str) else feature_name,
include_schema_on_empty=not compact,
)
)
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(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(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 run_imagegen(json_params: str = "{}") -> dict:
"""Backward-compatible alias for run, retained for Fluxus clients."""
return run(json_params)
def get_chain_schema(chain_type: str = "") -> dict:
"""Backward-compatible Fluxus endpoint for one chain/injector schema."""
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,
get_task_status,
run_imagegen,
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] Registered 7 canonical MCP APIs and 2 Fluxus aliases.")