Unity MCP PluginRegistry and PluginHub Architecture: How the Bridge Connects AI Clients to Unity

The Unity MCP PluginRegistry auto-discovers AI client configurators via reflection, while the PluginHub (StdioBridgeHost) hosts a TCP bridge that frames JSON commands between Python MCP servers and the Unity Editor, enabling bidirectional communication via a heartbeat file and queued command processing.

The CoplayDev/unity-mcp repository implements a two-layer architecture that enables AI assistants like Claude, Cursor, and VS Code to control the Unity Editor through the Model Context Protocol. Central to this system are the PluginRegistry, which manages client discovery through McpClientRegistry, and the PluginHub, which handles transport and command execution via StdioBridgeHost. Understanding how these components interact reveals how external MCP clients establish and maintain connections with Unity.

Client Discovery via PluginRegistry (McpClientRegistry)

The McpClientRegistry class in MCPForUnity/Editor/Clients/McpClientRegistry.cs serves as the discovery mechanism for AI client integrations. It maintains a static All property that caches available client configurators to optimize UI performance and prevent redundant reflection calls during domain reloads.

Reflection-Based Discovery of IMcpClientConfigurator

The registry uses Unity's TypeCache system to locate all implementations of IMcpClientConfigurator without manual registration. During initialization, it calls TypeCache.GetTypesDerivedFrom<IMcpClientConfigurator>() to retrieve every concrete configurator class found in the assembly, eliminating the need for hardcoded client lists.

Validation and Caching Mechanism

Only public, non-abstract classes with parameterless constructors are instantiated via Activator.CreateInstance. The registry sorts these instances alphabetically by DisplayName and stores them in the static All collection, making the list available immediately after domain reload for UI components like MCPSetupWindow and MCPForUnityEditorWindow.

The Bridge Hub Architecture (StdioBridgeHost)

The StdioBridgeHost class in MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs functions as the PluginHub, hosting a TCP listener that bridges the gap between Python MCP servers and Unity's main thread. It manages connection lifecycle, port allocation, command serialization, and graceful shutdown procedures.

TCP Listener and Port Management

The bridge creates a configured listener via CreateConfiguredListener(port), binding to IPAddress.Loopback and setting ExclusiveAddressUse on macOS to prevent port-sharing conflicts. PortManager.GetPortWithFallback() selects the default port (6400) and automatically falls back to an available port if the original remains busy, as determined by PortManager.ShouldAbandonBusyPort in MCPForUnity/Editor/Dependencies/PlatformDetectors/PortManager.cs.

Heartbeat File and External Discovery

To enable external process discovery, the bridge writes a JSON heartbeat file to ~/.unity-mcp/unity-mcp-status-<hash>.json via WriteHeartbeat(bool reloading, string reason). This file contains the current TCP port, Unity version, project name, and a ready/reloading flag that Python clients poll to locate the active bridge and determine if the Editor is currently recompiling scripts.

Command Framing and Queue Processing

The bridge implements length-prefixed JSON framing using 8-byte big-endian headers written by WriteUInt64BigEndian. Incoming commands are queued in commandQueue (a Dictionary<string, QueuedCommand>) and processed on the main thread via ProcessCommands, which invokes TransportCommandDispatcher.ExecuteCommandJsonAsync. Commands exceeding 2 × FrameIOTimeoutMs are evicted with an error response to prevent UI blocking.

Transport Modes and Command Dispatch

The architecture supports dual transport modes controlled by EditorConfigurationCache.UseHttpTransport in MCPForUnity/Editor/Constants/EditorConfigurationCache.cs, allowing the system to adapt to different integration scenarios.

STDIO vs HTTP Transport

When UseHttpTransport is false, the StdioBridgeHost starts automatically via EditorApplication.update and operates in STDIO mode optimized for single-client connections. When enabled, the system switches to HTTP mode using TransportCommandDispatcher, which hosts an HTTP server capable of handling multiple simultaneous MCP clients while reusing the same command execution logic.

TransportCommandDispatcher Integration

Located in MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs, this class executes commands received from either transport on Unity's main thread. It ensures thread-safe marshaling of results back to the requesting client, whether communicating through the TCP bridge or HTTP endpoint, and handles the actual invocation of tools decorated with [McpForUnityTool].

Practical Implementation Examples

The following examples demonstrate how to interact with the PluginRegistry and PluginHub programmatically.

Enumerate all detected MCP clients:

using MCPForUnity.Editor.Clients;

// In an editor script:
foreach (var cfg in McpClientRegistry.All)
{
    Debug.Log($"Client: {cfg.DisplayName}");
    Debug.Log($"  Installed: {cfg.IsInstalled}");
    Debug.Log($"  Config path: {cfg.GetConfigPath()}");
}

Start the STDIO bridge manually:

using MCPForUnity.Editor.Services;

// Force bridge restart after configuration changes:
StdioBridgeHost.StartAutoConnect();

Send a command from Python:

import socket, json, struct

def send_command(cmd_dict, host='127.0.0.1', port=6400):
    payload = json.dumps(cmd_dict).encode('utf-8')
    header = struct.pack('>Q', len(payload))  # 8-byte big-endian length

    with socket.create_connection((host, port)) as s:
        s.sendall(header + payload)
        length = struct.unpack('>Q', s.recv(8))[0]
        response = s.recv(length).decode('utf-8')
        return json.loads(response)

# Create a cube at origin

resp = send_command({
    "command": "manage_create_gameobject",
    "params": {"name": "Cube", "position": [0,0,0]}
})

Read the bridge heartbeat file:

using System.IO;
using UnityEngine;

string statusDir = Environment.GetEnvironmentVariable("UNITY_MCP_STATUS_DIR")
    ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".unity-mcp");
string hash = ComputeProjectHash(Application.dataPath);
string path = Path.Combine(statusDir, $"unity-mcp-status-{hash}.json");

if (File.Exists(path))
{
    string json = File.ReadAllText(path);
    var info = JsonUtility.FromJson<BridgeStatus>(json);
    Debug.Log($"Bridge on port {info.unity_port}, ready={!info.reloading}");
}

Summary

  • McpClientRegistry uses reflection to auto-discover AI client configurators and caches them in McpClientRegistry.All for immediate UI access without manual registration.
  • StdioBridgeHost acts as the PluginHub, hosting a TCP listener with automatic port fallback via PortManager and writing heartbeat files to ~/.unity-mcp/ for external discovery.
  • Command framing uses 8-byte big-endian length prefixes to ensure reliable JSON transport between Python MCP servers and the Unity bridge.
  • Dual transport modes support both STDIO (single-client) and HTTP (multi-client) via EditorConfigurationCache.UseHttpTransport, with TransportCommandDispatcher handling execution on the main thread.
  • Timeout protection automatically evicts commands stalled longer than 2 × FrameIOTimeoutMs to prevent editor UI blocking.

Frequently Asked Questions

How does the Unity MCP PluginRegistry discover new AI clients without manual registration?

The registry automatically discovers clients by calling TypeCache.GetTypesDerivedFrom<IMcpClientConfigurator>() to find all classes implementing the configurator interface. It validates that each type is concrete, public, and has a parameterless constructor before instantiating via Activator.CreateInstance. This reflection-based approach eliminates the need for manual registration when adding support for new AI clients like Claude Desktop or VS Code.

What is the purpose of the heartbeat file written by StdioBridgeHost?

The heartbeat file at ~/.unity-mcp/unity-mcp-status-<hash>.json serves as a discovery mechanism for external Python MCP servers. It contains the active TCP port, Unity version, project identifier, and a reloading flag that indicates whether the Editor is currently recompiling scripts. External processes poll this file to determine where to connect and whether the bridge is ready to accept commands.

How does the bridge handle port conflicts when the default port 6400 is unavailable?

The PortManager.GetPortWithFallback() method detects busy ports using PortManager.ShouldAbandonBusyPort and automatically selects an available alternative. The bridge then writes the new port to the heartbeat file, ensuring external clients can still locate the service without manual configuration changes.

Can the Unity MCP architecture support multiple simultaneous AI clients?

Yes, by setting EditorConfigurationCache.UseHttpTransport to true, the system switches from STDIO mode to HTTP mode using TransportCommandDispatcher. This enables the HTTP server to handle multiple concurrent connections from different AI assistants, while maintaining the same command execution pipeline and thread-safety guarantees on Unity's main thread.

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 →