Unity MCP Server
by justinpbarnett
Verified
# Unity MCP Server
This directory contains the Unity MCP Server implementation, which provides a bridge between Python and Unity Editor functionality.
## Adding New Tools
To add a new tool to the MCP Server, follow these steps:
### 1. Create the C# Command Handler
First, create or modify a command handler in the `Editor/Commands` directory:
```csharp
// Example: NewCommandHandler.cs
public static class NewCommandHandler
{
public static object HandleNewCommand(JObject @params)
{
// Extract parameters
string param1 = (string)@params["param1"];
int param2 = (int)@params["param2"];
// Implement the Unity-side functionality
// ...
// Return results
return new {
message = "Operation successful",
result = someResult
};
}
}
```
### 2. Register the Command Handler
Add your command handler to the `CommandRegistry.cs` in the `Editor/Commands` directory:
```csharp
public static class CommandRegistry
{
private static readonly Dictionary<string, Func<JObject, object>> _handlers = new()
{
// ... existing handlers ...
{ "NEW_COMMAND", NewCommandHandler.HandleNewCommand }
};
}
```
### 3. Create the Python Tool
Add your tool to the appropriate Python module in the `Python/tools` directory:
```python
@mcp.tool()
def new_tool(
ctx: Context,
param1: str,
param2: int
) -> str:
"""Description of what the tool does.
Args:
ctx: The MCP context
param1: Description of param1
param2: Description of param2
Returns:
str: Success message or error details
"""
try:
response = get_unity_connection().send_command("NEW_COMMAND", {
"param1": param1,
"param2": param2
})
return response.get("message", "Operation successful")
except Exception as e:
return f"Error executing operation: {str(e)}"
```
### 4. Register the Tool
Ensure your tool is registered in the appropriate registration function:
```python
# In Python/tools/__init__.py
def register_all_tools(mcp):
register_scene_tools(mcp)
register_script_tools(mcp)
register_material_tools(mcp)
# Add your new tool registration if needed
```
### 5. Update the Prompt
If your tool should be exposed to users, update the prompt in `Python/server.py`:
```python
@mcp.prompt()
def asset_creation_strategy() -> str:
return (
"Follow these Unity best practices:\n\n"
"1. **Your Category**:\n"
" - Use `new_tool(param1, param2)` to do something\n"
# ... rest of the prompt ...
)
```
## Best Practices
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
- Validate parameters in C# handlers
- Return meaningful error messages
3. **Documentation**:
- Add XML documentation to C# handlers
- Include detailed docstrings in Python tools
- Update the prompt with clear usage instructions
4. **Parameter Validation**:
- Validate parameters on both Python and C# sides
- Use appropriate types (str, int, float, List, etc.)
- Provide default values when appropriate
5. **Testing**:
- Test the tool in both Unity Editor and Python environments
- Verify error handling works as expected
- Check that the tool integrates well with existing functionality
6. **Code Organization**:
- Group related tools in appropriate handler classes
- Keep tools focused and single-purpose
- Follow existing naming conventions
## Example Implementation
Here's a complete example of adding a new tool:
1. **C# Handler** (`Editor/Commands/ExampleHandler.cs`):
```csharp
public static class ExampleHandler
{
public static object CreatePrefab(JObject @params)
{
string prefabName = (string)@params["prefab_name"];
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
GameObject prefab = new GameObject(prefabName);
// ... setup prefab ...
return new {
message = prefabExists ? $"Updated prefab: {prefabName}" : $"Created prefab: {prefabName}",
exists = prefabExists,
path = prefabPath
};
}
}
```
2. **Python Tool** (`Python/tools/example_tools.py`):
```python
@mcp.tool()
def create_prefab(
ctx: Context,
prefab_name: str,
template: str = "default",
overwrite: bool = False
) -> str:
"""Create a new prefab in the project or update if it exists.
Args:
ctx: The MCP context
prefab_name: Name for the new prefab
template: Template to use (default: "default")
overwrite: Whether to overwrite an existing prefab (default: False)
Returns:
str: Success message or error details
"""
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", {
"prefab_name": prefab_name,
"template": template,
"overwrite": overwrite
})
return response.get("message", "Prefab operation completed successfully")
except Exception as e:
return f"Error with prefab operation: {str(e)}"
```
3. **Update Prompt**:
```python
"1. **Prefab Management**:\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
If you encounter issues:
1. Check the Unity Console for C# errors
2. Verify the command name matches between Python and C#
3. Ensure all parameters are properly serialized
4. Check the Python logs for connection issues
5. Verify the tool is properly registered in both environments