How to Extend Unity MCP Functionality: A Complete Developer Guide
To extend Unity MCP functionality, you must implement a mirrored three-layer architecture consisting of a Python MCP tool in Server/src/services/tools/, a C# handler in MCPForUnity/Editor/Tools/ decorated with [McpForUnityTool], and a transport bridge using send_with_unity_instance() in the Python utilities.
The CoplayDev/unity-mcp repository provides a Model Context Protocol (MCP) implementation that bridges Python-based AI assistants with the Unity Editor. Extending this system requires understanding its tightly coupled trio of components: Python MCP tools, C# editor tools, and the transport layer that bridges them.
Unity MCP Architecture Overview
Unity MCP is built as a tightly coupled trio that enables bidirectional communication between AI assistants and the Unity Editor.
The Three-Layer Pattern
When you add new capabilities to extend Unity MCP functionality, you typically implement three mirrored pieces:
-
Python MCP tools – Located in
Server/src/services/tools/, these define the tool interface that AI assistants call. They are decorated with@mcp_for_unity_tooland handle parameter validation before sending commands to Unity. -
C# editor tools – Located in
MCPForUnity/Editor/Tools/, these contain the actual Unity Editor logic. They are discovered at runtime via the[McpForUnityTool]attribute and implement theHandleCommandmethod to execute operations within the Unity process. -
Transport layer – Located in
MCPForUnity/Editor/Services/Transport/, this bridges the Python and C# sides using WebSocket or Stdio protocols. The Python side usessend_with_unity_instance()fromServer/src/services/tools/utils.pyto communicate across this boundary.
Step-by-Step Guide to Extend Unity MCP Functionality
Step 1: Create the Python Tool Definition
Begin by defining your tool in the Python layer. Create a new file in Server/src/services/tools/ following the pattern established in manage_material.py.
Your tool function must:
- Use the
@mcp_for_unity_tooldecorator with adescriptionandgroupparameter - Accept a
Contextparameter and any specific arguments - Use
get_unity_instance_from_context()to retrieve the Unity connection - Call
send_with_unity_instance()withasync_send_command_with_retryto dispatch commands
# Server/src/services/tools/manage_my_feature.py
from fastmcp import Context
from services.registry import mcp_for_unity_tool
from services.tools import get_unity_instance_from_context
from transport.unity_transport import send_with_unity_instance
from transport.legacy.unity_connection import async_send_command_with_retry
@mcp_for_unity_tool(
description="Demo feature – creates a custom GameObject and attaches a component.",
group="graphics",
)
async def manage_my_feature(ctx: Context, action: str, name: str | None = None) -> dict:
unity = await get_unity_instance_from_context(ctx)
# Normalise / validate inputs here (similar to manage_material.py)
params = {"action": action.lower()}
if name:
params["name"] = name
result = await send_with_unity_instance(
async_send_command_with_retry, unity, "manage_my_feature", params
)
return result if isinstance(result, dict) else {"success": False, "message": str(result)}
Key patterns: Use normalize_* helpers from Server/src/services/tools/utils.py for color, property, and JSON payload normalization, as demonstrated in manage_material.py.
Step 2: Implement the C# Handler
Next, create the corresponding C# handler in MCPForUnity/Editor/Tools/<Domain>/Manage<Domain>.cs. This class must use the [McpForUnityTool] attribute to register with the system.
// MCPForUnity/Editor/Tools/Graphics/ManageMyFeature.cs
using Newtonsoft.Json.Linq;
using MCPForUnity.Editor.Helpers;
namespace MCPForUnity.Editor.Tools.Graphics
{
[McpForUnityTool("manage_my_feature", AutoRegister = false, Group = "graphics")]
public static class ManageMyFeature
{
public static object HandleCommand(JObject @params)
{
string action = @params["action"]?.ToString();
if (action == null) return new { success = false, message = "Missing action" };
switch (action.ToLowerInvariant())
{
case "create":
return CreateGameObject(@params);
default:
return new { success = false, message = $"Unknown action {action}" };
}
}
private static object CreateGameObject(JObject @params)
{
string name = @params["name"]?.ToString() ?? "NewObject";
var go = new GameObject(name);
go.AddComponent<MyCustomComponent>();
return new { success = true, objectId = go.GetInstanceID(), name };
}
}
}
Key utilities: The ToolParams helper in MCPForUnity/Editor/Helpers/ToolParams.cs provides automatic validation and conversion, as seen in its usage within ManageVFX.cs.
Step 3: Register and Expose the Tool Group
While most tools are auto-discovered via reflection, you must explicitly expose your tool group to the Unity Editor UI.
Edit MCPForUnity/Editor/Windows/Components/Tools/McpToolsSection.cs to add a toggle for your new group, allowing users to enable or disable the functionality in the editor interface.
If you require explicit ordering rather than auto-discovery, add an entry in Server/src/services/tools/__init__.py to register the tool in the Python registry.
Step 4: Write Cross-Platform Tests
Comprehensive testing requires validating both sides of the bridge:
Python side: Add a test file Server/tests/test_manage_my_feature.py that calls the tool via a mocked Unity instance, following the pattern in tests/test_manage_material.py.
Unity side: Add an editor test under TestProjects/UnityMCPTests/Assets/Tests/ that invokes CommandRegistry.InvokeCommandAsync("manage_my_feature", …).
Run the full test harness to verify cross-process compatibility:
cd Server && uv run pytest -v # Python unit tests
python tools/local_harness.py # End-to-end Unity bridge tests
Why the Three-Layer Pattern Matters
Understanding why Unity MCP uses this architecture helps you extend Unity MCP functionality without breaking existing integrations:
-
Isolation: Python tools run in a separate process and communicate only via the transport layer. Adding a new tool does not require changes to the core server logic in
MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs. -
Symmetry: The C# side mirrors the Python side, making it easy for LLM assistants to discover capabilities. The tool name remains identical on both sides—
manage_my_featurein Python corresponds to[McpForUnityTool("manage_my_feature")]in C#. -
Extensibility: Groups let you ship experimental features without breaking existing clients. They are toggled via the "Tool groups" UI in
MCPForUnity/Editor/Windows/Components/Tools/McpToolsSection.cs.
Practical Implementation Examples
You can call your extended functionality from multiple contexts:
From an MCP client (e.g., Claude):
await mcp_client.call_tool(
"manage_my_feature",
{"action": "create", "name": "EnemyShip"}
)
From another C# tool within Unity:
var result = await CommandRegistry.InvokeCommandAsync(
"manage_my_feature",
new JObject { ["action"] = "create", ["name"] = "EnemyShip" }
);
Summary
To successfully extend Unity MCP functionality in the CoplayDev/unity-mcp repository:
-
Implement three mirrored components: a Python tool definition in
Server/src/services/tools/, a C# handler inMCPForUnity/Editor/Tools/, and transport calls usingsend_with_unity_instance() -
Use the
@mcp_for_unity_tooldecorator on Python functions and the[McpForUnityTool]attribute on C# classes to register capabilities -
Organize tools into groups (e.g., "graphics", "vfx", "ui") to enable selective enabling via the Unity Editor UI
-
Leverage helper utilities like
ToolParams.csfor parameter validation andutils.pyfor normalization -
Write cross-platform tests covering both Python and Unity C# implementations
-
Maintain naming symmetry between Python tool names and C# tool attributes to ensure LLM discoverability
Frequently Asked Questions
What is the minimum code required to extend Unity MCP functionality?
You need three essential pieces: a Python async function decorated with @mcp_for_unity_tool in Server/src/services/tools/, a static C# class with the [McpForUnityTool] attribute in MCPForUnity/Editor/Tools/, and a HandleCommand method that accepts JObject @params. The Python side must call send_with_unity_instance() with async_send_command_with_retry to dispatch commands to the C# handler.
How do I organize new tools into feature groups?
Specify the group parameter in both the Python decorator @mcp_for_unity_tool(group="graphics") and the C# attribute [McpForUnityTool("tool_name", Group = "graphics")]. Then expose the group in the Unity Editor by modifying MCPForUnity/Editor/Windows/Components/Tools/McpToolsSection.cs to add a toggle for your new group.
Can I extend Unity MCP functionality without modifying the core transport layer?
Yes. The transport layer in MCPForUnity/Editor/Services/Transport/ is designed to be agnostic to specific tools. You only need to use the existing send_with_unity_instance() helper in Python and implement the HandleCommand method in C#. The WebSocket and Stdio implementations handle all cross-process communication automatically.
How do I test my extended Unity MCP tools?
Write tests for both sides of the bridge. For Python, create tests in Server/tests/ that mock the Unity instance and verify parameter handling. For Unity, create editor tests in TestProjects/UnityMCPTests/Assets/Tests/ that call CommandRegistry.InvokeCommandAsync() directly. Run uv run pytest -v for Python tests and use the Unity Test Runner for C# tests.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →