# How to Extend Unity MCP Functionality: A Complete Developer Guide

> Discover how to extend Unity MCP functionality with our complete guide. Learn to implement a three-layer architecture using Python, C#, and a transport bridge for seamless integration. Get started today.

- Repository: [Coplay/unity-mcp](https://github.com/CoplayDev/unity-mcp)
- Tags: how-to-guide
- Published: 2026-07-07

---

**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:

1. **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_tool` and handle parameter validation before sending commands to Unity.

2. **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 the `HandleCommand` method to execute operations within the Unity process.

3. **Transport layer** – Located in `MCPForUnity/Editor/Services/Transport/`, this bridges the Python and C# sides using WebSocket or Stdio protocols. The Python side uses `send_with_unity_instance()` from [`Server/src/services/tools/utils.py`](https://github.com/CoplayDev/unity-mcp/blob/main/Server/src/services/tools/utils.py) to 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`](https://github.com/CoplayDev/unity-mcp/blob/main/manage_material.py).

Your tool function must:
- Use the `@mcp_for_unity_tool` decorator with a `description` and `group` parameter
- Accept a `Context` parameter and any specific arguments
- Use `get_unity_instance_from_context()` to retrieve the Unity connection
- Call `send_with_unity_instance()` with `async_send_command_with_retry` to dispatch commands

```python

# 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`](https://github.com/CoplayDev/unity-mcp/blob/main/Server/src/services/tools/utils.py) for color, property, and JSON payload normalization, as demonstrated in [`manage_material.py`](https://github.com/CoplayDev/unity-mcp/blob/main/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.

```csharp
// 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`](https://github.com/CoplayDev/unity-mcp/blob/main/MCPForUnity/Editor/Helpers/ToolParams.cs) provides automatic validation and conversion, as seen in its usage within [`ManageVFX.cs`](https://github.com/CoplayDev/unity-mcp/blob/main/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`](https://github.com/CoplayDev/unity-mcp/blob/main/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`](https://github.com/CoplayDev/unity-mcp/blob/main/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`](https://github.com/CoplayDev/unity-mcp/blob/main/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`](https://github.com/CoplayDev/unity-mcp/blob/main/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:

```bash
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`](https://github.com/CoplayDev/unity-mcp/blob/main/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_feature` in 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`](https://github.com/CoplayDev/unity-mcp/blob/main/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):**

```python
await mcp_client.call_tool(
    "manage_my_feature",
    {"action": "create", "name": "EnemyShip"}
)

```

**From another C# tool within Unity:**

```csharp
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 in `MCPForUnity/Editor/Tools/`, and transport calls using `send_with_unity_instance()`

- Use the `@mcp_for_unity_tool` decorator 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.cs`](https://github.com/CoplayDev/unity-mcp/blob/main/ToolParams.cs) for parameter validation and [`utils.py`](https://github.com/CoplayDev/unity-mcp/blob/main/utils.py) for 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`](https://github.com/CoplayDev/unity-mcp/blob/main/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.