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:

  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 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.

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

# 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_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.

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 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 for parameter validation and 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 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →