Unity MCP Server

by justinpbarnett
Verified
from mcp.server.fastmcp import FastMCP, Context from typing import List, Dict, Any, Optional import json from unity_connection import get_unity_connection def register_scene_tools(mcp: FastMCP): """Register all scene-related tools with the MCP server.""" @mcp.tool() def get_scene_info(ctx: Context) -> str: """Retrieve detailed info about the current Unity scene. Returns: str: JSON string containing scene information including: - sceneName: Name of the current scene - rootObjects: List of root GameObject names in the scene """ try: unity = get_unity_connection() result = unity.send_command("GET_SCENE_INFO") return json.dumps(result, indent=2) except Exception as e: return f"Error getting scene info: {str(e)}" @mcp.tool() def open_scene(ctx: Context, scene_path: str) -> str: """Open a specified scene in the Unity editor. Args: scene_path: Full path to the scene file (e.g., "Assets/Scenes/MyScene.unity") Returns: str: Success message or error details """ try: unity = get_unity_connection() # Check if the scene exists in the project scenes = unity.send_command("GET_ASSET_LIST", { "type": "Scene", "search_pattern": scene_path.split('/')[-1], "folder": '/'.join(scene_path.split('/')[:-1]) or "Assets" }).get("assets", []) # Check if any scene matches the exact path scene_exists = any(scene.get("path") == scene_path for scene in scenes) if not scene_exists: return f"Scene at '{scene_path}' not found in the project." result = unity.send_command("OPEN_SCENE", {"scene_path": scene_path}) return result.get("message", "Scene opened successfully") except Exception as e: return f"Error opening scene: {str(e)}" @mcp.tool() def save_scene(ctx: Context) -> str: """Save the current scene to its file. Returns: str: Success message or error details """ try: unity = get_unity_connection() result = unity.send_command("SAVE_SCENE") return result.get("message", "Scene saved successfully") except Exception as e: return f"Error saving scene: {str(e)}" @mcp.tool() def new_scene(ctx: Context, scene_path: str, overwrite: bool = False) -> str: """Create a new empty scene in the Unity editor. Args: scene_path: Full path where the new scene should be saved (e.g., "Assets/Scenes/NewScene.unity") overwrite: Whether to overwrite if scene already exists (default: False) Returns: str: Success message or error details """ try: unity = get_unity_connection() # Check if a scene with this path already exists scenes = unity.send_command("GET_ASSET_LIST", { "type": "Scene", "search_pattern": scene_path.split('/')[-1], "folder": '/'.join(scene_path.split('/')[:-1]) or "Assets" }).get("assets", []) # Check if any scene matches the exact path scene_exists = any(scene.get("path") == scene_path for scene in scenes) if scene_exists and not overwrite: return f"Scene at '{scene_path}' already exists. Use overwrite=True to replace it." # Create new scene result = unity.send_command("NEW_SCENE", { "scene_path": scene_path, "overwrite": overwrite }) # Save the scene to ensure it's properly created unity.send_command("SAVE_SCENE") # Get scene info to verify it's loaded scene_info = unity.send_command("GET_SCENE_INFO") return result.get("message", "New scene created successfully") except Exception as e: return f"Error creating new scene: {str(e)}" @mcp.tool() def change_scene(ctx: Context, scene_path: str, save_current: bool = False) -> str: """Change to a different scene, optionally saving the current one. Args: scene_path: Full path to the target scene file (e.g., "Assets/Scenes/TargetScene.unity") save_current: Whether to save the current scene before changing (default: False) Returns: str: Success message or error details """ try: unity = get_unity_connection() result = unity.send_command("CHANGE_SCENE", { "scene_path": scene_path, "save_current": save_current }) return result.get("message", "Scene changed successfully") except Exception as e: return f"Error changing scene: {str(e)}" @mcp.tool() def get_object_info(ctx: Context, object_name: str) -> str: """ Get info about a specific game object. Args: object_name: Name of the game object. """ try: unity = get_unity_connection() result = unity.send_command("GET_OBJECT_INFO", {"name": object_name}) return json.dumps(result, indent=2) except Exception as e: return f"Error getting object info: {str(e)}" @mcp.tool() def create_object( ctx: Context, type: str = "CUBE", name: str = None, location: List[float] = None, rotation: List[float] = None, scale: List[float] = None, replace_if_exists: bool = False ) -> str: """ Create a game object in the Unity scene. Args: type: Object type (CUBE, SPHERE, CYLINDER, CAPSULE, PLANE, EMPTY, CAMERA, LIGHT). name: Optional name for the game object. location: [x, y, z] position (defaults to [0, 0, 0]). rotation: [x, y, z] rotation in degrees (defaults to [0, 0, 0]). scale: [x, y, z] scale factors (defaults to [1, 1, 1]). replace_if_exists: Whether to replace if an object with the same name exists (default: False) Returns: Confirmation message with the created object's name. """ try: unity = get_unity_connection() # Check if an object with the specified name already exists (if name is provided) if name: found_objects = unity.send_command("FIND_OBJECTS_BY_NAME", { "name": name }).get("objects", []) if found_objects and not replace_if_exists: return f"Object with name '{name}' already exists. Use replace_if_exists=True to replace it." elif found_objects and replace_if_exists: # Delete the existing object unity.send_command("DELETE_OBJECT", {"name": name}) # Create the new object params = { "type": type.upper(), "location": location or [0, 0, 0], "rotation": rotation or [0, 0, 0], "scale": scale or [1, 1, 1] } if name: params["name"] = name result = unity.send_command("CREATE_OBJECT", params) return f"Created {type} game object: {result['name']}" except Exception as e: return f"Error creating game object: {str(e)}" @mcp.tool() def modify_object( ctx: Context, name: str, location: Optional[List[float]] = None, rotation: Optional[List[float]] = None, scale: Optional[List[float]] = None, visible: Optional[bool] = None, set_parent: Optional[str] = None, add_component: Optional[str] = None, remove_component: Optional[str] = None, set_property: Optional[Dict[str, Any]] = None ) -> str: """ Modify a game object's properties and components. Args: name: Name of the game object to modify. location: Optional [x, y, z] position. rotation: Optional [x, y, z] rotation in degrees. scale: Optional [x, y, z] scale factors. visible: Optional visibility toggle. set_parent: Optional name of the parent object to set. add_component: Optional name of the component type to add (e.g., "Rigidbody", "BoxCollider"). remove_component: Optional name of the component type to remove. set_property: Optional dict with keys: - component: Name of the component type - property: Name of the property to set - value: Value to set the property to Returns: str: Success message or error details """ try: unity = get_unity_connection() # Check if the object exists found_objects = unity.send_command("FIND_OBJECTS_BY_NAME", { "name": name }).get("objects", []) if not found_objects: return f"Object with name '{name}' not found in the scene." # If set_parent is provided, check if parent object exists if set_parent is not None: parent_objects = unity.send_command("FIND_OBJECTS_BY_NAME", { "name": set_parent }).get("objects", []) if not parent_objects: return f"Parent object '{set_parent}' not found in the scene." # If we're adding a component, we could also check if it's already attached if add_component is not None: object_props = unity.send_command("GET_OBJECT_PROPERTIES", { "name": name }) components = object_props.get("components", []) component_exists = any(comp.get("type") == add_component for comp in components) if component_exists: return f"Component '{add_component}' is already attached to '{name}'." # If we're removing a component, check if it exists if remove_component is not None: object_props = unity.send_command("GET_OBJECT_PROPERTIES", { "name": name }) components = object_props.get("components", []) component_exists = any(comp.get("type") == remove_component for comp in components) if not component_exists: return f"Component '{remove_component}' is not attached to '{name}'." params = {"name": name} # Add basic transform properties if location is not None: params["location"] = location if rotation is not None: params["rotation"] = rotation if scale is not None: params["scale"] = scale if visible is not None: params["visible"] = visible # Add parent setting if set_parent is not None: params["set_parent"] = set_parent # Add component operations if add_component is not None: params["add_component"] = add_component if remove_component is not None: params["remove_component"] = remove_component # Add property setting if set_property is not None: params["set_property"] = set_property result = unity.send_command("MODIFY_OBJECT", params) return f"Modified game object: {result['name']}" except Exception as e: return f"Error modifying game object: {str(e)}" @mcp.tool() def delete_object(ctx: Context, name: str, ignore_missing: bool = False) -> str: """ Remove a game object from the scene. Args: name: Name of the game object to delete. ignore_missing: Whether to silently ignore if the object doesn't exist (default: False) Returns: str: Success message or error details """ try: unity = get_unity_connection() # Check if the object exists found_objects = unity.send_command("FIND_OBJECTS_BY_NAME", { "name": name }).get("objects", []) if not found_objects: if ignore_missing: return f"No object named '{name}' found to delete. Ignoring." else: return f"Error: Object '{name}' not found in the scene." result = unity.send_command("DELETE_OBJECT", {"name": name}) return f"Deleted game object: {name}" except Exception as e: return f"Error deleting game object: {str(e)}"