added object existance validation

main
Justin Barnett 2025-03-18 09:52:41 -04:00
parent 276ac8166f
commit 36b5b224b3
7 changed files with 516 additions and 36 deletions

View File

@ -108,31 +108,38 @@ def asset_creation_strategy() -> str:
## Best Practices ## Best Practices
1. **Error Handling**: 1. **Existence Checking**:
- ALWAYS check if objects, scripts, assets, or materials exist before creating or updating them
- Use appropriate search tools (`find_objects_by_name`, `list_scripts`, `get_asset_list`) to verify existence
- Handle both cases: creation when it doesn't exist and updating when it does
- Implement proper error handling when an expected resource is not found
2. **Error Handling**:
- Always include try-catch blocks in Python tools - Always include try-catch blocks in Python tools
- Validate parameters in C# handlers - Validate parameters in C# handlers
- Return meaningful error messages - Return meaningful error messages
2. **Documentation**: 3. **Documentation**:
- Add XML documentation to C# handlers - Add XML documentation to C# handlers
- Include detailed docstrings in Python tools - Include detailed docstrings in Python tools
- Update the prompt with clear usage instructions - Update the prompt with clear usage instructions
3. **Parameter Validation**: 4. **Parameter Validation**:
- Validate parameters on both Python and C# sides - Validate parameters on both Python and C# sides
- Use appropriate types (str, int, float, List, etc.) - Use appropriate types (str, int, float, List, etc.)
- Provide default values when appropriate - Provide default values when appropriate
4. **Testing**: 5. **Testing**:
- Test the tool in both Unity Editor and Python environments - Test the tool in both Unity Editor and Python environments
- Verify error handling works as expected - Verify error handling works as expected
- Check that the tool integrates well with existing functionality - Check that the tool integrates well with existing functionality
5. **Code Organization**: 6. **Code Organization**:
- Group related tools in appropriate handler classes - Group related tools in appropriate handler classes
- Keep tools focused and single-purpose - Keep tools focused and single-purpose
- Follow existing naming conventions - Follow existing naming conventions
@ -150,14 +157,29 @@ public static class ExampleHandler
{ {
string prefabName = (string)@params["prefab_name"]; string prefabName = (string)@params["prefab_name"];
string template = (string)@params["template"]; string template = (string)@params["template"];
bool overwrite = @params["overwrite"] != null ? (bool)@params["overwrite"] : false;
// Check if the prefab already exists
string prefabPath = $"Assets/Prefabs/{prefabName}.prefab";
bool prefabExists = System.IO.File.Exists(prefabPath);
if (prefabExists && !overwrite)
{
return new {
message = $"Prefab already exists: {prefabName}. Use overwrite=true to replace it.",
exists = true,
path = prefabPath
};
}
// Implementation // Implementation
GameObject prefab = new GameObject(prefabName); GameObject prefab = new GameObject(prefabName);
// ... setup prefab ... // ... setup prefab ...
return new { return new {
message = $"Created prefab: {prefabName}", message = prefabExists ? $"Updated prefab: {prefabName}" : $"Created prefab: {prefabName}",
path = $"Assets/Prefabs/{prefabName}.prefab" exists = prefabExists,
path = prefabPath
}; };
} }
} }
@ -170,33 +192,51 @@ public static class ExampleHandler
def create_prefab( def create_prefab(
ctx: Context, ctx: Context,
prefab_name: str, prefab_name: str,
template: str = "default" template: str = "default",
overwrite: bool = False
) -> str: ) -> str:
"""Create a new prefab in the project. """Create a new prefab in the project or update if it exists.
Args: Args:
ctx: The MCP context ctx: The MCP context
prefab_name: Name for the new prefab prefab_name: Name for the new prefab
template: Template to use (default: "default") template: Template to use (default: "default")
overwrite: Whether to overwrite an existing prefab (default: False)
Returns: Returns:
str: Success message or error details str: Success message or error details
""" """
try: try:
# First check if the prefab already exists
assets = get_unity_connection().send_command("GET_ASSET_LIST", {
"type": "Prefab",
"search_pattern": prefab_name,
"folder": "Assets/Prefabs"
}).get("assets", [])
prefab_exists = any(asset.get("name") == prefab_name for asset in assets)
if prefab_exists and not overwrite:
return f"Prefab '{prefab_name}' already exists. Use overwrite=True to replace it."
# Create or update the prefab
response = get_unity_connection().send_command("CREATE_PREFAB", { response = get_unity_connection().send_command("CREATE_PREFAB", {
"prefab_name": prefab_name, "prefab_name": prefab_name,
"template": template "template": template,
"overwrite": overwrite
}) })
return response.get("message", "Prefab created successfully")
return response.get("message", "Prefab operation completed successfully")
except Exception as e: except Exception as e:
return f"Error creating prefab: {str(e)}" return f"Error with prefab operation: {str(e)}"
``` ```
3. **Update Prompt**: 3. **Update Prompt**:
```python ```python
"1. **Prefab Management**:\n" "1. **Prefab Management**:\n"
" - Create prefabs with `create_prefab(prefab_name, template)`\n" " - ALWAYS check if a prefab exists before creating it\n"
" - Create or update prefabs with `create_prefab(prefab_name, template, overwrite=False)`\n"
``` ```
## Troubleshooting ## Troubleshooting

View File

@ -56,6 +56,7 @@ def asset_creation_strategy() -> str:
" - `open_scene(path)`, `save_scene(path)` - Open/save scenes\n" " - `open_scene(path)`, `save_scene(path)` - Open/save scenes\n"
" - `new_scene(path)`, `change_scene(path, save_current)` - Create/switch scenes\n\n" " - `new_scene(path)`, `change_scene(path, save_current)` - Create/switch scenes\n\n"
"3. **Object Management**\n" "3. **Object Management**\n"
" - ALWAYS use `find_objects_by_name(name)` to check if an object exists before creating or modifying it\n"
" - `create_object(name, type)` - Create objects (e.g. `CUBE`, `SPHERE`, `EMPTY`, `CAMERA`)\n" " - `create_object(name, type)` - Create objects (e.g. `CUBE`, `SPHERE`, `EMPTY`, `CAMERA`)\n"
" - `delete_object(name)` - Remove objects\n" " - `delete_object(name)` - Remove objects\n"
" - `set_object_transform(name, location, rotation, scale)` - Modify object position, rotation, and scale\n" " - `set_object_transform(name, location, rotation, scale)` - Modify object position, rotation, and scale\n"
@ -65,11 +66,13 @@ def asset_creation_strategy() -> str:
" - `find_objects_by_name(name)` - Find objects by name\n" " - `find_objects_by_name(name)` - Find objects by name\n"
" - `get_hierarchy()` - Get object hierarchy\n" " - `get_hierarchy()` - Get object hierarchy\n"
"4. **Script Management**\n" "4. **Script Management**\n"
" - ALWAYS use `list_scripts(folder_path)` or `view_script(path)` to check if a script exists before creating or updating it\n"
" - `create_script(name, type, namespace, template)` - Create scripts\n" " - `create_script(name, type, namespace, template)` - Create scripts\n"
" - `view_script(path)`, `update_script(path, content)` - View/modify scripts\n" " - `view_script(path)`, `update_script(path, content)` - View/modify scripts\n"
" - `attach_script(object_name, script_name)` - Add scripts to objects\n" " - `attach_script(object_name, script_name)` - Add scripts to objects\n"
" - `list_scripts(folder_path)` - List scripts in folder\n\n" " - `list_scripts(folder_path)` - List scripts in folder\n\n"
"5. **Asset Management**\n" "5. **Asset Management**\n"
" - ALWAYS use `get_asset_list(type, search_pattern, folder)` to check if an asset exists before creating or importing it\n"
" - `import_asset(source_path, target_path)` - Import external assets\n" " - `import_asset(source_path, target_path)` - Import external assets\n"
" - `instantiate_prefab(path, pos_x, pos_y, pos_z, rot_x, rot_y, rot_z)` - Create prefab instances\n" " - `instantiate_prefab(path, pos_x, pos_y, pos_z, rot_x, rot_y, rot_z)` - Create prefab instances\n"
" - `create_prefab(object_name, path)`, `apply_prefab(object_name, path)` - Manage prefabs\n" " - `create_prefab(object_name, path)`, `apply_prefab(object_name, path)` - Manage prefabs\n"
@ -77,9 +80,11 @@ def asset_creation_strategy() -> str:
" - Use relative paths for Unity assets (e.g., 'Assets/Models/MyModel.fbx')\n" " - Use relative paths for Unity assets (e.g., 'Assets/Models/MyModel.fbx')\n"
" - Use absolute paths for external files\n\n" " - Use absolute paths for external files\n\n"
"6. **Material Management**\n" "6. **Material Management**\n"
" - ALWAYS check if a material exists before creating or modifying it\n"
" - `set_material(object_name, material_name, color)` - Apply/create materials\n" " - `set_material(object_name, material_name, color)` - Apply/create materials\n"
" - Use RGB colors (0.0-1.0 range)\n\n" " - Use RGB colors (0.0-1.0 range)\n\n"
"7. **Best Practices**\n" "7. **Best Practices**\n"
" - ALWAYS verify existence before creating or updating any objects, scripts, assets, or materials\n"
" - Use meaningful names for objects and scripts\n" " - Use meaningful names for objects and scripts\n"
" - Keep scripts organized in folders with namespaces\n" " - Keep scripts organized in folders with namespaces\n"
" - Verify changes after modifications\n" " - Verify changes after modifications\n"

View File

@ -9,7 +9,8 @@ def register_asset_tools(mcp: FastMCP):
def import_asset( def import_asset(
ctx: Context, ctx: Context,
source_path: str, source_path: str,
target_path: str target_path: str,
overwrite: bool = False
) -> str: ) -> str:
"""Import an asset (e.g., 3D model, texture) into the Unity project. """Import an asset (e.g., 3D model, texture) into the Unity project.
@ -17,11 +18,14 @@ def register_asset_tools(mcp: FastMCP):
ctx: The MCP context ctx: The MCP context
source_path: Path to the source file on disk 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) 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: Returns:
str: Success message or error details str: Success message or error details
""" """
try: try:
unity = get_unity_connection()
# Parameter validation # Parameter validation
if not source_path or not isinstance(source_path, str): if not source_path or not isinstance(source_path, str):
return f"Error importing asset: source_path must be a valid string" return f"Error importing asset: source_path must be a valid string"
@ -29,9 +33,30 @@ def register_asset_tools(mcp: FastMCP):
if not target_path or not isinstance(target_path, str): if not target_path or not isinstance(target_path, str):
return f"Error importing asset: target_path must be a valid string" return f"Error importing asset: target_path must be a valid string"
response = get_unity_connection().send_command("IMPORT_ASSET", { # 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, "source_path": source_path,
"target_path": target_path "target_path": target_path,
"overwrite": overwrite
}) })
if not response.get("success", False): if not response.get("success", False):
@ -68,6 +93,8 @@ def register_asset_tools(mcp: FastMCP):
str: Success message or error details str: Success message or error details
""" """
try: try:
unity = get_unity_connection()
# Parameter validation # Parameter validation
if not prefab_path or not isinstance(prefab_path, str): if not prefab_path or not isinstance(prefab_path, str):
return f"Error instantiating prefab: prefab_path must be a valid string" return f"Error instantiating prefab: prefab_path must be a valid string"
@ -86,7 +113,26 @@ def register_asset_tools(mcp: FastMCP):
if not isinstance(param_value, (int, float)): if not isinstance(param_value, (int, float)):
return f"Error instantiating prefab: {param_name} must be a number" return f"Error instantiating prefab: {param_name} must be a number"
response = get_unity_connection().send_command("INSTANTIATE_PREFAB", { # 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, "prefab_path": prefab_path,
"position_x": position_x, "position_x": position_x,
"position_y": position_y, "position_y": position_y,
@ -107,7 +153,8 @@ def register_asset_tools(mcp: FastMCP):
def create_prefab( def create_prefab(
ctx: Context, ctx: Context,
object_name: str, object_name: str,
prefab_path: str prefab_path: str,
overwrite: bool = False
) -> str: ) -> str:
"""Create a new prefab asset from a GameObject in the scene. """Create a new prefab asset from a GameObject in the scene.
@ -115,11 +162,14 @@ def register_asset_tools(mcp: FastMCP):
ctx: The MCP context ctx: The MCP context
object_name: Name of the GameObject in the scene to create prefab from 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) 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: Returns:
str: Success message or error details str: Success message or error details
""" """
try: try:
unity = get_unity_connection()
# Parameter validation # Parameter validation
if not object_name or not isinstance(object_name, str): if not object_name or not isinstance(object_name, str):
return f"Error creating prefab: object_name must be a valid string" return f"Error creating prefab: object_name must be a valid string"
@ -127,13 +177,36 @@ def register_asset_tools(mcp: FastMCP):
if not prefab_path or not isinstance(prefab_path, str): if not prefab_path or not isinstance(prefab_path, str):
return f"Error creating prefab: prefab_path must be a valid string" 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 # Verify prefab path has proper extension
if not prefab_path.lower().endswith('.prefab'): if not prefab_path.lower().endswith('.prefab'):
prefab_path = f"{prefab_path}.prefab" prefab_path = f"{prefab_path}.prefab"
response = get_unity_connection().send_command("CREATE_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, "object_name": object_name,
"prefab_path": prefab_path "prefab_path": prefab_path,
"overwrite": overwrite
}) })
if not response.get("success", False): if not response.get("success", False):
@ -158,7 +231,27 @@ def register_asset_tools(mcp: FastMCP):
str: Success message or error details str: Success message or error details
""" """
try: try:
response = get_unity_connection().send_command("APPLY_PREFAB", { 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 "object_name": object_name
}) })
return response.get("message", "Prefab changes applied successfully") return response.get("message", "Prefab changes applied successfully")

View File

@ -92,6 +92,34 @@ def register_editor_tools(mcp: FastMCP):
str: Success message or error details str: Success message or error details
""" """
try: 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", { response = get_unity_connection().send_command("EDITOR_CONTROL", {
"command": "BUILD", "command": "BUILD",
"params": { "params": {
@ -104,17 +132,41 @@ def register_editor_tools(mcp: FastMCP):
return f"Error building project: {str(e)}" return f"Error building project: {str(e)}"
@mcp.tool() @mcp.tool()
def execute_command(ctx: Context, command_name: str) -> str: 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. """Execute a specific editor command or custom script within the Unity editor.
Args: Args:
command_name: Name of the editor command to execute (e.g., "Edit/Preferences") 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: Returns:
str: Success message or error details str: Success message or error details
""" """
try: try:
response = get_unity_connection().send_command("EDITOR_CONTROL", { 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", "command": "EXECUTE_COMMAND",
"params": { "params": {
"commandName": command_name "commandName": command_name

View File

@ -10,7 +10,8 @@ def register_material_tools(mcp: FastMCP):
ctx: Context, ctx: Context,
object_name: str, object_name: str,
material_name: str = None, material_name: str = None,
color: List[float] = None color: List[float] = None,
create_if_missing: bool = True
) -> str: ) -> str:
""" """
Apply or create a material for a game object. Apply or create a material for a game object.
@ -19,14 +20,56 @@ def register_material_tools(mcp: FastMCP):
object_name: Target game object. object_name: Target game object.
material_name: Optional material name. material_name: Optional material name.
color: Optional [R, G, B] values (0.0-1.0). color: Optional [R, G, B] values (0.0-1.0).
create_if_missing: Whether to create the material if it doesn't exist (default: True).
""" """
try: try:
unity = get_unity_connection() unity = get_unity_connection()
# Check if the object exists
object_response = unity.send_command("FIND_OBJECTS_BY_NAME", {
"name": object_name
})
objects = object_response.get("objects", [])
if not objects:
return f"GameObject '{object_name}' not found in the scene."
# If a material name is specified, check if it exists
if material_name:
material_assets = unity.send_command("GET_ASSET_LIST", {
"type": "Material",
"search_pattern": material_name,
"folder": "Assets/Materials"
}).get("assets", [])
material_exists = any(asset.get("name") == material_name for asset in material_assets)
if not material_exists and not create_if_missing:
return f"Material '{material_name}' not found. Use create_if_missing=True to create it."
# Validate color values if provided
if color:
# Check if color has the right number of components (RGB or RGBA)
if not (len(color) == 3 or len(color) == 4):
return f"Error: Color must have 3 (RGB) or 4 (RGBA) components, but got {len(color)}."
# Check if all color values are in the 0-1 range
for i, value in enumerate(color):
if not isinstance(value, (int, float)):
return f"Error: Color component at index {i} is not a number."
if value < 0.0 or value > 1.0:
channel = "RGBA"[i] if i < 4 else f"component {i}"
return f"Error: Color {channel} value must be in the range 0.0-1.0, but got {value}."
# Set up parameters for the command
params = {"object_name": object_name} params = {"object_name": object_name}
if material_name: if material_name:
params["material_name"] = material_name params["material_name"] = material_name
params["create_if_missing"] = create_if_missing
if color: if color:
params["color"] = color params["color"] = color
result = unity.send_command("SET_MATERIAL", params) result = unity.send_command("SET_MATERIAL", params)
return f"Applied material to {object_name}: {result.get('material_name', 'unknown')}" return f"Applied material to {object_name}: {result.get('material_name', 'unknown')}"
except Exception as e: except Exception as e:

View File

@ -28,6 +28,19 @@ def register_scene_tools(mcp: FastMCP):
""" """
try: try:
unity = get_unity_connection() 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}) result = unity.send_command("OPEN_SCENE", {"scene_path": scene_path})
return result.get("message", "Scene opened successfully") return result.get("message", "Scene opened successfully")
except Exception as e: except Exception as e:
@ -48,19 +61,36 @@ def register_scene_tools(mcp: FastMCP):
return f"Error saving scene: {str(e)}" return f"Error saving scene: {str(e)}"
@mcp.tool() @mcp.tool()
def new_scene(ctx: Context, scene_path: str) -> str: def new_scene(ctx: Context, scene_path: str, overwrite: bool = False) -> str:
"""Create a new empty scene in the Unity editor. """Create a new empty scene in the Unity editor.
Args: Args:
scene_path: Full path where the new scene should be saved (e.g., "Assets/Scenes/NewScene.unity") 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: Returns:
str: Success message or error details str: Success message or error details
""" """
try: try:
unity = get_unity_connection() 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 # Create new scene
result = unity.send_command("NEW_SCENE", {"scene_path": scene_path}) result = unity.send_command("NEW_SCENE", {
"scene_path": scene_path,
"overwrite": overwrite
})
# Save the scene to ensure it's properly created # Save the scene to ensure it's properly created
unity.send_command("SAVE_SCENE") unity.send_command("SAVE_SCENE")
@ -115,7 +145,8 @@ def register_scene_tools(mcp: FastMCP):
name: str = None, name: str = None,
location: List[float] = None, location: List[float] = None,
rotation: List[float] = None, rotation: List[float] = None,
scale: List[float] = None scale: List[float] = None,
replace_if_exists: bool = False
) -> str: ) -> str:
""" """
Create a game object in the Unity scene. Create a game object in the Unity scene.
@ -126,12 +157,27 @@ def register_scene_tools(mcp: FastMCP):
location: [x, y, z] position (defaults to [0, 0, 0]). location: [x, y, z] position (defaults to [0, 0, 0]).
rotation: [x, y, z] rotation in degrees (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]). 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: Returns:
Confirmation message with the created object's name. Confirmation message with the created object's name.
""" """
try: try:
unity = get_unity_connection() 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 = { params = {
"type": type.upper(), "type": type.upper(),
"location": location or [0, 0, 0], "location": location or [0, 0, 0],
@ -140,6 +186,7 @@ def register_scene_tools(mcp: FastMCP):
} }
if name: if name:
params["name"] = name params["name"] = name
result = unity.send_command("CREATE_OBJECT", params) result = unity.send_command("CREATE_OBJECT", params)
return f"Created {type} game object: {result['name']}" return f"Created {type} game object: {result['name']}"
except Exception as e: except Exception as e:
@ -180,6 +227,48 @@ def register_scene_tools(mcp: FastMCP):
""" """
try: try:
unity = get_unity_connection() 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} params = {"name": name}
# Add basic transform properties # Add basic transform properties
@ -212,15 +301,31 @@ def register_scene_tools(mcp: FastMCP):
return f"Error modifying game object: {str(e)}" return f"Error modifying game object: {str(e)}"
@mcp.tool() @mcp.tool()
def delete_object(ctx: Context, name: str) -> str: def delete_object(ctx: Context, name: str, ignore_missing: bool = False) -> str:
""" """
Remove a game object from the scene. Remove a game object from the scene.
Args: Args:
name: Name of the game object to delete. 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: try:
unity = get_unity_connection() 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}) result = unity.send_command("DELETE_OBJECT", {"name": name})
return f"Deleted game object: {name}" return f"Deleted game object: {name}"
except Exception as e: except Exception as e:

View File

@ -31,7 +31,8 @@ def register_script_tools(mcp: FastMCP):
script_name: str, script_name: str,
script_type: str = "MonoBehaviour", script_type: str = "MonoBehaviour",
namespace: str = None, namespace: str = None,
template: str = None template: str = None,
overwrite: bool = False
) -> str: ) -> str:
"""Create a new Unity script file. """Create a new Unity script file.
@ -41,17 +42,32 @@ def register_script_tools(mcp: FastMCP):
script_type: Type of script (e.g., MonoBehaviour, ScriptableObject) script_type: Type of script (e.g., MonoBehaviour, ScriptableObject)
namespace: Optional namespace for the script namespace: Optional namespace for the script
template: Optional custom template to use template: Optional custom template to use
overwrite: Whether to overwrite if script already exists (default: False)
Returns: Returns:
str: Success message or error details str: Success message or error details
""" """
try: try:
# First check if a script with this name already exists
unity = get_unity_connection()
script_path = f"Assets/Scripts/{script_name}.cs"
# Try to view the script to check if it exists
existing_script_response = unity.send_command("VIEW_SCRIPT", {
"script_path": script_path
})
# If the script exists and overwrite is False, return a message
if "content" in existing_script_response and not overwrite:
return f"Script '{script_name}.cs' already exists. Use overwrite=True to replace it."
# Send command to Unity to create the script # Send command to Unity to create the script
response = get_unity_connection().send_command("CREATE_SCRIPT", { response = unity.send_command("CREATE_SCRIPT", {
"script_name": script_name, "script_name": script_name,
"script_type": script_type, "script_type": script_type,
"namespace": namespace, "namespace": namespace,
"template": template "template": template,
"overwrite": overwrite
}) })
return response.get("message", "Script created successfully") return response.get("message", "Script created successfully")
except Exception as e: except Exception as e:
@ -61,7 +77,9 @@ def register_script_tools(mcp: FastMCP):
def update_script( def update_script(
ctx: Context, ctx: Context,
script_path: str, script_path: str,
content: str content: str,
create_if_missing: bool = False,
create_folder_if_missing: bool = False
) -> str: ) -> str:
"""Update the contents of an existing Unity script. """Update the contents of an existing Unity script.
@ -69,13 +87,66 @@ def register_script_tools(mcp: FastMCP):
ctx: The MCP context ctx: The MCP context
script_path: Path to the script file relative to the Assets folder script_path: Path to the script file relative to the Assets folder
content: New content for the script content: New content for the script
create_if_missing: Whether to create the script if it doesn't exist (default: False)
create_folder_if_missing: Whether to create the parent directory if it doesn't exist (default: False)
Returns: Returns:
str: Success message or error details str: Success message or error details
""" """
try: try:
unity = get_unity_connection()
# Check if the script exists first
existing_script_response = unity.send_command("VIEW_SCRIPT", {
"script_path": script_path
})
# If the script doesn't exist
if "content" not in existing_script_response:
if not create_if_missing:
return f"Script at '{script_path}' not found. Use create_if_missing=True to create it."
# If we should create the missing script
script_name = script_path.split("/")[-1].replace(".cs", "")
script_folder = "/".join(script_path.split("/")[:-1])
# Check if the parent directory exists
if script_folder:
folder_exists = False
try:
# Try to list scripts in the folder to see if it exists
folder_response = unity.send_command("LIST_SCRIPTS", {
"folder_path": script_folder
})
# If we didn't get an error, the folder exists
folder_exists = "error" not in folder_response
except:
folder_exists = False
if not folder_exists:
if not create_folder_if_missing:
return f"Parent directory '{script_folder}' does not exist. Use create_folder_if_missing=True to create it."
# Create the directory structure
try:
response = unity.send_command("CREATE_FOLDER", {
"folder_path": script_folder
})
if "error" in response:
return f"Error creating directory '{script_folder}': {response.get('error')}"
except Exception as folder_error:
return f"Error creating directory '{script_folder}': {str(folder_error)}"
# Create the script with the provided content
response = unity.send_command("CREATE_SCRIPT", {
"script_name": script_name,
"script_folder": script_folder,
"content": content
})
return response.get("message", "Script created successfully")
# Send command to Unity to update the script # Send command to Unity to update the script
response = get_unity_connection().send_command("UPDATE_SCRIPT", { response = unity.send_command("UPDATE_SCRIPT", {
"script_path": script_path, "script_path": script_path,
"content": content "content": content
}) })
@ -110,7 +181,8 @@ def register_script_tools(mcp: FastMCP):
def attach_script( def attach_script(
ctx: Context, ctx: Context,
object_name: str, object_name: str,
script_name: str script_name: str,
script_path: str = None
) -> str: ) -> str:
"""Attach a script component to a GameObject. """Attach a script component to a GameObject.
@ -118,15 +190,85 @@ def register_script_tools(mcp: FastMCP):
ctx: The MCP context ctx: The MCP context
object_name: Name of the target GameObject in the scene object_name: Name of the target GameObject in the scene
script_name: Name of the script to attach (with or without .cs extension) script_name: Name of the script to attach (with or without .cs extension)
script_path: Optional full path to the script (if not in the default Scripts folder)
Returns: Returns:
str: Success message or error details str: Success message or error details
""" """
try: try:
unity = get_unity_connection()
# Check if the object exists
object_response = unity.send_command("FIND_OBJECTS_BY_NAME", {
"name": object_name
})
objects = object_response.get("objects", [])
if not objects:
return f"GameObject '{object_name}' not found in the scene."
# Ensure script_name has .cs extension
if not script_name.lower().endswith(".cs"):
script_name = f"{script_name}.cs"
# Determine the full script path
if script_path is None:
# Use default Scripts folder if no path provided
script_path = f"Assets/Scripts/{script_name}"
elif not script_path.endswith(script_name):
# If path is just a directory, append the script name
if script_path.endswith("/"):
script_path = f"{script_path}{script_name}"
else:
script_path = f"{script_path}/{script_name}"
# Check if the script exists by trying to view it
existing_script_response = unity.send_command("VIEW_SCRIPT", {
"script_path": script_path
})
if "content" not in existing_script_response:
# If not found at the specific path, try to search for it in the project
script_found = False
try:
# Search in the entire Assets folder
script_assets = unity.send_command("LIST_SCRIPTS", {
"folder_path": "Assets"
}).get("scripts", [])
# Look for matching script name in any folder
matching_scripts = [path for path in script_assets if path.endswith(f"/{script_name}") or path == script_name]
if matching_scripts:
script_path = matching_scripts[0]
script_found = True
if len(matching_scripts) > 1:
return f"Multiple scripts named '{script_name}' found in the project. Please specify script_path parameter."
except:
pass
if not script_found:
return f"Script '{script_name}' not found in the project."
# Check if the script is already attached
object_props = unity.send_command("GET_OBJECT_PROPERTIES", {
"name": object_name
})
# Extract script name without .cs and without path
script_class_name = script_name.replace(".cs", "")
# Check if component is already attached
components = object_props.get("components", [])
for component in components:
if component.get("type") == script_class_name:
return f"Script '{script_class_name}' is already attached to '{object_name}'."
# Send command to Unity to attach the script # Send command to Unity to attach the script
response = get_unity_connection().send_command("ATTACH_SCRIPT", { response = unity.send_command("ATTACH_SCRIPT", {
"object_name": object_name, "object_name": object_name,
"script_name": script_name "script_name": script_name,
"script_path": script_path
}) })
return response.get("message", "Script attached successfully") return response.get("message", "Script attached successfully")
except Exception as e: except Exception as e: