Unity MCP Server

by justinpbarnett
Verified
from typing import Optional from mcp.server.fastmcp import FastMCP, Context from unity_connection import get_unity_connection def register_asset_tools(mcp: FastMCP): """Register all asset management tools with the MCP server.""" @mcp.tool() def import_asset( ctx: Context, source_path: str, target_path: str, overwrite: bool = False ) -> str: """Import an asset (e.g., 3D model, texture) into the Unity project. Args: ctx: The MCP context source_path: Path to the source file on disk target_path: Path where the asset should be imported in the Unity project (relative to Assets folder) overwrite: Whether to overwrite if an asset already exists at the target path (default: False) Returns: str: Success message or error details """ try: unity = get_unity_connection() # Parameter validation if not source_path or not isinstance(source_path, str): return f"Error importing asset: source_path must be a valid string" if not target_path or not isinstance(target_path, str): return f"Error importing asset: target_path must be a valid string" # Check if the source file exists (on local disk) import os if not os.path.exists(source_path): return f"Error importing asset: Source file '{source_path}' does not exist" # Extract the target directory and filename target_dir = '/'.join(target_path.split('/')[:-1]) target_filename = target_path.split('/')[-1] # Check if an asset already exists at the target path existing_assets = unity.send_command("GET_ASSET_LIST", { "search_pattern": target_filename, "folder": target_dir or "Assets" }).get("assets", []) # Check if any asset matches the exact path asset_exists = any(asset.get("path") == target_path for asset in existing_assets) if asset_exists and not overwrite: return f"Asset already exists at '{target_path}'. Use overwrite=True to replace it." response = unity.send_command("IMPORT_ASSET", { "source_path": source_path, "target_path": target_path, "overwrite": overwrite }) if not response.get("success", False): return f"Error importing asset: {response.get('error', 'Unknown error')} (Source: {source_path}, Target: {target_path})" return response.get("message", "Asset imported successfully") except Exception as e: return f"Error importing asset: {str(e)} (Source: {source_path}, Target: {target_path})" @mcp.tool() def instantiate_prefab( ctx: Context, prefab_path: str, position_x: float = 0.0, position_y: float = 0.0, position_z: float = 0.0, rotation_x: float = 0.0, rotation_y: float = 0.0, rotation_z: float = 0.0 ) -> str: """Instantiate a prefab into the current scene at a specified location. Args: ctx: The MCP context prefab_path: Path to the prefab asset (relative to Assets folder) position_x: X position in world space (default: 0.0) position_y: Y position in world space (default: 0.0) position_z: Z position in world space (default: 0.0) rotation_x: X rotation in degrees (default: 0.0) rotation_y: Y rotation in degrees (default: 0.0) rotation_z: Z rotation in degrees (default: 0.0) Returns: str: Success message or error details """ try: unity = get_unity_connection() # Parameter validation if not prefab_path or not isinstance(prefab_path, str): return f"Error instantiating prefab: prefab_path must be a valid string" # Validate numeric parameters position_params = { "position_x": position_x, "position_y": position_y, "position_z": position_z, "rotation_x": rotation_x, "rotation_y": rotation_y, "rotation_z": rotation_z } for param_name, param_value in position_params.items(): if not isinstance(param_value, (int, float)): return f"Error instantiating prefab: {param_name} must be a number" # Check if the prefab exists prefab_dir = '/'.join(prefab_path.split('/')[:-1]) or "Assets" prefab_name = prefab_path.split('/')[-1] # Ensure prefab has .prefab extension for searching if not prefab_name.lower().endswith('.prefab'): prefab_name = f"{prefab_name}.prefab" prefab_path = f"{prefab_path}.prefab" prefab_assets = unity.send_command("GET_ASSET_LIST", { "type": "Prefab", "search_pattern": prefab_name, "folder": prefab_dir }).get("assets", []) prefab_exists = any(asset.get("path") == prefab_path for asset in prefab_assets) if not prefab_exists: return f"Prefab '{prefab_path}' not found in the project." response = unity.send_command("INSTANTIATE_PREFAB", { "prefab_path": prefab_path, "position_x": position_x, "position_y": position_y, "position_z": position_z, "rotation_x": rotation_x, "rotation_y": rotation_y, "rotation_z": rotation_z }) if not response.get("success", False): return f"Error instantiating prefab: {response.get('error', 'Unknown error')} (Path: {prefab_path})" return f"Prefab instantiated successfully as '{response.get('instance_name', 'unknown')}'" except Exception as e: return f"Error instantiating prefab: {str(e)} (Path: {prefab_path})" @mcp.tool() def create_prefab( ctx: Context, object_name: str, prefab_path: str, overwrite: bool = False ) -> str: """Create a new prefab asset from a GameObject in the scene. Args: ctx: The MCP context object_name: Name of the GameObject in the scene to create prefab from prefab_path: Path where the prefab should be saved (relative to Assets folder) overwrite: Whether to overwrite if a prefab already exists at the path (default: False) Returns: str: Success message or error details """ try: unity = get_unity_connection() # Parameter validation if not object_name or not isinstance(object_name, str): return f"Error creating prefab: object_name must be a valid string" if not prefab_path or not isinstance(prefab_path, str): return f"Error creating prefab: prefab_path must be a valid string" # Check if the GameObject exists found_objects = unity.send_command("FIND_OBJECTS_BY_NAME", { "name": object_name }).get("objects", []) if not found_objects: return f"GameObject '{object_name}' not found in the scene." # Verify prefab path has proper extension if not prefab_path.lower().endswith('.prefab'): prefab_path = f"{prefab_path}.prefab" # Check if a prefab already exists at this path prefab_dir = '/'.join(prefab_path.split('/')[:-1]) or "Assets" prefab_name = prefab_path.split('/')[-1] prefab_assets = unity.send_command("GET_ASSET_LIST", { "type": "Prefab", "search_pattern": prefab_name, "folder": prefab_dir }).get("assets", []) prefab_exists = any(asset.get("path") == prefab_path for asset in prefab_assets) if prefab_exists and not overwrite: return f"Prefab already exists at '{prefab_path}'. Use overwrite=True to replace it." response = unity.send_command("CREATE_PREFAB", { "object_name": object_name, "prefab_path": prefab_path, "overwrite": overwrite }) if not response.get("success", False): return f"Error creating prefab: {response.get('error', 'Unknown error')} (Object: {object_name}, Path: {prefab_path})" return f"Prefab created successfully at {response.get('path', prefab_path)}" except Exception as e: return f"Error creating prefab: {str(e)} (Object: {object_name}, Path: {prefab_path})" @mcp.tool() def apply_prefab( ctx: Context, object_name: str ) -> str: """Apply changes made to a prefab instance back to the original prefab asset. Args: ctx: The MCP context object_name: Name of the prefab instance in the scene Returns: str: Success message or error details """ try: unity = get_unity_connection() # Check if the GameObject exists found_objects = unity.send_command("FIND_OBJECTS_BY_NAME", { "name": object_name }).get("objects", []) if not found_objects: return f"GameObject '{object_name}' not found in the scene." # Check if the object is a prefab instance object_props = unity.send_command("GET_OBJECT_PROPERTIES", { "name": object_name }) # Try to extract prefab status from properties is_prefab_instance = object_props.get("isPrefabInstance", False) if not is_prefab_instance: return f"GameObject '{object_name}' is not a prefab instance." response = unity.send_command("APPLY_PREFAB", { "object_name": object_name }) return response.get("message", "Prefab changes applied successfully") except Exception as e: return f"Error applying prefab changes: {str(e)}"