from mcp.server.fastmcp import FastMCP, Context from typing import Optional, List, Dict, Any from unity_connection import get_unity_connection def register_editor_tools(mcp: FastMCP): """Register all editor control tools with the MCP server.""" @mcp.tool() def undo(ctx: Context) -> str: """Undo the last action performed in the Unity editor. Returns: str: Success message or error details """ try: response = get_unity_connection().send_command("EDITOR_CONTROL", { "command": "UNDO" }) return response.get("message", "Undo performed successfully") except Exception as e: return f"Error performing undo: {str(e)}" @mcp.tool() def redo(ctx: Context) -> str: """Redo the last undone action in the Unity editor. Returns: str: Success message or error details """ try: response = get_unity_connection().send_command("EDITOR_CONTROL", { "command": "REDO" }) return response.get("message", "Redo performed successfully") except Exception as e: return f"Error performing redo: {str(e)}" @mcp.tool() def play(ctx: Context) -> str: """Start the game in play mode within the Unity editor. Returns: str: Success message or error details """ try: response = get_unity_connection().send_command("EDITOR_CONTROL", { "command": "PLAY" }) return response.get("message", "Entered play mode") except Exception as e: return f"Error entering play mode: {str(e)}" @mcp.tool() def pause(ctx: Context) -> str: """Pause the game while in play mode. Returns: str: Success message or error details """ try: response = get_unity_connection().send_command("EDITOR_CONTROL", { "command": "PAUSE" }) return response.get("message", "Game paused") except Exception as e: return f"Error pausing game: {str(e)}" @mcp.tool() def stop(ctx: Context) -> str: """Stop the game and exit play mode. Returns: str: Success message or error details """ try: response = get_unity_connection().send_command("EDITOR_CONTROL", { "command": "STOP" }) return response.get("message", "Exited play mode") except Exception as e: return f"Error stopping game: {str(e)}" @mcp.tool() def build(ctx: Context, platform: str, build_path: str) -> str: """Build the project for a specified platform. Args: platform: Target platform (windows, mac, linux, android, ios, webgl) build_path: Path where the build should be saved Returns: str: Success message or error details """ try: # Validate platform valid_platforms = ["windows", "mac", "linux", "android", "ios", "webgl"] if platform.lower() not in valid_platforms: return f"Error: '{platform}' is not a valid platform. Valid platforms are: {', '.join(valid_platforms)}" # Check if build_path exists and is writable import os # Check if the directory exists build_dir = os.path.dirname(build_path) if not os.path.exists(build_dir): return f"Error: Build directory '{build_dir}' does not exist. Please create it first." # Check if the directory is writable if not os.access(build_dir, os.W_OK): return f"Error: Build directory '{build_dir}' is not writable." # If the build path itself exists, check if it's a file or directory if os.path.exists(build_path): if os.path.isfile(build_path): # If it's a file, check if it's writable if not os.access(build_path, os.W_OK): return f"Error: Existing build file '{build_path}' is not writable." elif os.path.isdir(build_path): # If it's a directory, check if it's writable if not os.access(build_path, os.W_OK): return f"Error: Existing build directory '{build_path}' is not writable." response = get_unity_connection().send_command("EDITOR_CONTROL", { "command": "BUILD", "params": { "platform": platform, "buildPath": build_path } }) return response.get("message", "Build completed successfully") except Exception as e: return f"Error building project: {str(e)}" @mcp.tool() def execute_command(ctx: Context, command_name: str, validate_command: bool = True) -> str: """Execute a specific editor command or custom script within the Unity editor. Args: command_name: Name of the editor command to execute (e.g., "Edit/Preferences") validate_command: Whether to validate the command existence before executing (default: True) Returns: str: Success message or error details """ try: unity = get_unity_connection() # Optionally validate if the command exists if validate_command: # Get a list of available commands from Unity available_commands = unity.send_command("EDITOR_CONTROL", { "command": "GET_AVAILABLE_COMMANDS" }).get("commands", []) # Check if the command exists in the list if available_commands and command_name not in available_commands: # If command doesn't exist, try to find similar commands as suggestions similar_commands = [cmd for cmd in available_commands if command_name.lower() in cmd.lower()] suggestion_msg = "" if similar_commands: suggestion_msg = f" Did you mean one of these: {', '.join(similar_commands[:5])}" if len(similar_commands) > 5: suggestion_msg += " or others?" else: suggestion_msg += "?" return f"Error: Command '{command_name}' not found.{suggestion_msg}" response = unity.send_command("EDITOR_CONTROL", { "command": "EXECUTE_COMMAND", "params": { "commandName": command_name } }) return response.get("message", f"Executed command: {command_name}") except Exception as e: return f"Error executing command: {str(e)}" @mcp.tool() def read_console( ctx: Context, show_logs: bool = True, show_warnings: bool = True, show_errors: bool = True, search_term: Optional[str] = None ) -> List[Dict[str, Any]]: """Read log messages from the Unity Console. Args: ctx: The MCP context show_logs: Whether to include regular log messages (default: True) show_warnings: Whether to include warning messages (default: True) show_errors: Whether to include error messages (default: True) search_term: Optional text to filter logs by content. If multiple words are provided, entries must contain all words (not necessarily in order) to be included. (default: None) Returns: List[Dict[str, Any]]: A list of console log entries, each containing 'type', 'message', and 'stackTrace' fields """ try: # Prepare params with only the provided values params = { "show_logs": show_logs, "show_warnings": show_warnings, "show_errors": show_errors } # Only add search_term if it's provided if search_term is not None: params["search_term"] = search_term response = get_unity_connection().send_command("EDITOR_CONTROL", { "command": "READ_CONSOLE", "params": params }) if "error" in response: return [{ "type": "Error", "message": f"Failed to read console: {response['error']}", "stackTrace": response.get("stackTrace", "") }] entries = response.get("entries", []) total_entries = response.get("total_entries", 0) filtered_count = response.get("filtered_count", 0) filter_applied = response.get("filter_applied", False) # Add summary info summary = [] if total_entries > 0: summary.append(f"Total console entries: {total_entries}") if filter_applied: summary.append(f"Filtered entries: {filtered_count}") if filtered_count == 0: summary.append(f"No entries matched the search term: '{search_term}'") else: summary.append(f"Showing all entries") else: summary.append("No entries in console") # Add filter info filter_types = [] if show_logs: filter_types.append("logs") if show_warnings: filter_types.append("warnings") if show_errors: filter_types.append("errors") if filter_types: summary.append(f"Showing: {', '.join(filter_types)}") # Add summary as first entry if summary: entries.insert(0, { "type": "Info", "message": " | ".join(summary), "stackTrace": "" }) return entries if entries else [{ "type": "Info", "message": "No logs found in console", "stackTrace": "" }] except Exception as e: return [{ "type": "Error", "message": f"Error reading console: {str(e)}", "stackTrace": "" }]