from typing import Annotated, Any, Literal
from fastmcp import Context
from mcp.types import ToolAnnotations
from services.registry import mcp_for_unity_tool
from services.tools import get_unity_instance_from_context
from transport.unity_transport import send_with_unity_instance
from transport.legacy.unity_connection import async_send_command_with_retry
# All possible actions grouped by component type
PARTICLE_ACTIONS = [
"particle_get_info", "particle_set_main", "particle_set_emission", "particle_set_shape",
"particle_set_color_over_lifetime", "particle_set_size_over_lifetime",
"particle_set_velocity_over_lifetime", "particle_set_noise", "particle_set_renderer",
"particle_enable_module", "particle_play", "particle_stop", "particle_pause",
"particle_restart", "particle_clear", "particle_add_burst", "particle_clear_bursts"
]
VFX_ACTIONS = [
# Asset management
"vfx_create_asset", "vfx_assign_asset", "vfx_list_templates", "vfx_list_assets",
# Runtime control
"vfx_get_info", "vfx_set_float", "vfx_set_int", "vfx_set_bool",
"vfx_set_vector2", "vfx_set_vector3", "vfx_set_vector4", "vfx_set_color",
"vfx_set_gradient", "vfx_set_texture", "vfx_set_mesh", "vfx_set_curve",
"vfx_send_event", "vfx_play", "vfx_stop", "vfx_pause", "vfx_reinit",
"vfx_set_playback_speed", "vfx_set_seed"
]
LINE_ACTIONS = [
"line_get_info", "line_set_positions", "line_add_position", "line_set_position",
"line_set_width", "line_set_color", "line_set_material", "line_set_properties",
"line_clear", "line_create_line", "line_create_circle", "line_create_arc", "line_create_bezier"
]
TRAIL_ACTIONS = [
"trail_get_info", "trail_set_time", "trail_set_width", "trail_set_color",
"trail_set_material", "trail_set_properties", "trail_clear", "trail_emit"
]
ALL_ACTIONS = ["ping"] + PARTICLE_ACTIONS + VFX_ACTIONS + LINE_ACTIONS + TRAIL_ACTIONS
@mcp_for_unity_tool(
description=(
"Manage Unity VFX components (ParticleSystem, VisualEffect, LineRenderer, TrailRenderer). "
"Action prefixes: particle_*, vfx_*, line_*, trail_*. "
"Action-specific parameters go in `properties` (keys match ManageVFX.cs)."
),
annotations=ToolAnnotations(
title="Manage VFX",
destructiveHint=True,
),
)
async def manage_vfx(
ctx: Context,
action: Annotated[str, "Action to perform (prefix: particle_, vfx_, line_, trail_)."],
target: Annotated[str | None, "Target GameObject (name/path/id)."] = None,
search_method: Annotated[
Literal["by_id", "by_name", "by_path", "by_tag", "by_layer"] | None,
"How to find the target GameObject.",
] = None,
properties: Annotated[
dict[str, Any] | str | None,
"Action-specific parameters (dict or JSON string).",
] = None,
) -> dict[str, Any]:
"""Unified VFX management tool."""
# Normalize action to lowercase to match Unity-side behavior
action_normalized = action.lower()
# Validate action against known actions using normalized value
if action_normalized not in ALL_ACTIONS:
# Provide helpful error with closest matches by prefix
prefix = action_normalized.split(
"_")[0] + "_" if "_" in action_normalized else ""
available_by_prefix = {
"particle_": PARTICLE_ACTIONS,
"vfx_": VFX_ACTIONS,
"line_": LINE_ACTIONS,
"trail_": TRAIL_ACTIONS,
}
suggestions = available_by_prefix.get(prefix, [])
if suggestions:
return {
"success": False,
"message": f"Unknown action '{action}'. Available {prefix}* actions: {', '.join(suggestions)}",
}
else:
return {
"success": False,
"message": (
f"Unknown action '{action}'. Use prefixes: "
"particle_*, vfx_*, line_*, trail_*. Run with action='ping' to test connection."
),
}
unity_instance = get_unity_instance_from_context(ctx)
params_dict: dict[str, Any] = {"action": action_normalized}
if properties is not None:
params_dict["properties"] = properties
if target is not None:
params_dict["target"] = target
if search_method is not None:
params_dict["searchMethod"] = search_method
params_dict = {k: v for k, v in params_dict.items() if v is not None}
# Send to Unity
result = await send_with_unity_instance(
async_send_command_with_retry,
unity_instance,
"manage_vfx",
params_dict,
)
return result if isinstance(result, dict) else {"success": False, "message": str(result)}