How Commands Are Handled in Unity MCP: A Complete Technical Guide

Unity MCP processes every client operation as a command through a seven-stage pipeline involving dynamic handler discovery, prefix-based routing, and TCP socket communication with the Unity Editor.

Unity MCP (Model Context Protocol) bridges external clients and the Unity Editor via a robust command-handling architecture. Understanding how commands are handled in Unity MCP is essential for developers extending the protocol or debugging integration issues. This guide examines the complete command lifecycle from discovery to execution, referencing the actual TypeScript implementation in the isuzu-shiranui/unitymcp repository.

Command Discovery and Registration

The server initializes its command vocabulary by scanning and registering handler classes dynamically.

Dynamic Handler Discovery

When the server starts, HandlerDiscovery scans the compiled handlers directory (unity-mcp-ts/src/handlers/). It dynamically imports each .js file and instantiates every exported class.

// Conceptual flow from HandlerDiscovery.ts
const handlerFiles = await scanHandlersDirectory();
for (const file of handlerFiles) {
  const module = await import(file);
  const HandlerClass = module.default;
  const instance = new HandlerClass();
  // Registration happens next...
}

CommandRegistry Registration

Each discovered instance is examined against the ICommandHandler interface. Valid handlers are added to the central CommandRegistry, which maintains a mapping between command prefixes and handler instances.

// From CommandRegistry.ts
registerHandler(handler: ICommandHandler): void {
  const prefix = handler.commandPrefix; // e.g., "menu", "console"
  this.handlers.set(prefix, handler);
}

The same instance is also handed to HandlerAdapter so the adapter can later route calls to the correct handler.

Request Reception and Routing

Once registered, handlers process incoming TCP requests through a structured routing mechanism.

TCP Socket Communication via UnityConnection

Clients (such as the Unity Editor plugin) send JSON requests over TCP sockets. UnityConnection.sendRequest tags each request with a unique id, writes it to the active Unity client, and stores a promise in pendingRequests for later resolution.

// Client-side usage pattern
import { UnityConnection } from './unity-mcp-ts/src/core/UnityConnection.js';

async function executeMenuItem(menuPath: string) {
  const conn = UnityConnection.getInstance();
  await conn.start();
  
  const response = await conn.sendRequest({
    command: 'menu.execute',
    params: { menuItem: menuPath }
  });
  
  return response;
}

Command Prefix Routing

When the server receives a response or request, HandlerAdapter extracts the command string (e.g., menu.execute). The segment before the first dot (menu) serves as the command prefix.

HandlerAdapter looks up the corresponding handler in CommandRegistry and invokes its execute method, passing the action (execute) and parameters.

Command Execution Flow

Handlers implement a consistent execution pattern through base class abstractions.

BaseCommandHandler Abstraction

Most concrete handlers extend BaseCommandHandler. The base class first guarantees a live Unity connection via ensureUnityConnection, then delegates to the handler-specific executeCommand method.

// From BaseCommandHandler.ts
async execute(action: string, params: JObject): Promise<JObject> {
  await this.ensureUnityConnection();
  return this.executeCommand(action, params);
}

protected abstract executeCommand(
  action: string, 
  params: JObject
): Promise<JObject>;

Handlers typically forward requests to Unity using sendUnityRequest, which serializes the command and manages the socket write operation.

Concrete Handler Implementation

The MenuItemCommandHandler demonstrates a typical implementation. It declares its prefix as menu and implements executeCommand to forward menu execution requests to the Unity Editor.

// Excerpt from MenuItemCommandHandler.ts
class MenuItemCommandHandler extends BaseCommandHandler {
  get commandPrefix(): string { return 'menu'; }
  
  protected async executeCommand(
    action: string, 
    params: JObject
  ): Promise<JObject> {
    if (action === 'execute') {
      await this.ensureUnityConnection();
      const response = await this.sendUnityRequest(
        `${this.commandPrefix}.execute`,
        { menuItem: params.menuItem }
      );
      return response;
    }
    return { success: false, error: 'Unknown action' };
  }
}

Implementing Custom Command Handlers

Developers can extend Unity MCP by creating new handler classes. The system automatically discovers and registers them without modifying core router code.

Here is a complete custom handler that adds a ping health-check command:

// Save as: unity-mcp-ts/src/handlers/PingCommandHandler.ts
import { BaseCommandHandler } from '../core/BaseCommandHandler.js';
import { JObject } from '../types/index.js';

export class PingCommandHandler extends BaseCommandHandler {
  public get commandPrefix(): string { return 'ping'; }
  public get description(): string { return 'Simple health-check command'; }

  public getToolDefinitions() { return null; }

  protected async executeCommand(
    action: string, 
    _params: JObject
  ): Promise<JObject> {
    if (action.toLowerCase() !== 'ping') {
      return { success: false, error: `Unsupported action ${action}` };
    }
    // Directly answer without contacting Unity
    return { success: true, reply: 'pong' };
  }
}

Because HandlerDiscovery loads every compiled handler automatically, the new ping command becomes available as ping.pong immediately upon server restart.

Summary

  • Dynamic Discovery: HandlerDiscovery scans the handlers directory at startup, instantiating every exported class it finds.
  • Prefix Registration: Valid handlers implementing ICommandHandler register their command prefix (e.g., menu, console) with CommandRegistry.
  • Socket Communication: UnityConnection manages TCP sockets, tagging requests with unique IDs and tracking pending promises.
  • Prefix Routing: HandlerAdapter extracts the command prefix from incoming requests (e.g., menu from menu.execute) and routes to the appropriate handler.
  • Base Abstraction: BaseCommandHandler ensures Unity connectivity before delegating to handler-specific executeCommand implementations.
  • Extensibility: New commands require only creating a handler class extending BaseCommandHandler; no core router modifications are necessary.

Frequently Asked Questions

What is the role of the command prefix in Unity MCP?

The command prefix is the segment before the first dot in a command string (e.g., menu in menu.execute). It acts as a namespace that HandlerAdapter uses to look up the correct ICommandHandler instance in CommandRegistry. Each handler declares its prefix via the commandPrefix getter, enabling the router to dispatch requests without hardcoding handler references.

How does Unity MCP handle requests when Unity Editor is not connected?

BaseCommandHandler includes an ensureUnityConnection method that verifies an active TCP connection to the Unity Editor before executing commands. If the connection is lost or unavailable, the method throws an error or returns a failure response, preventing commands from being dropped silently. Handlers that do not require Unity interaction (like the example PingCommandHandler) can bypass this check by implementing custom logic directly in executeCommand.

Can I add new commands without modifying the core server code?

Yes. Unity MCP uses dynamic discovery via HandlerDiscovery to load handlers from the src/handlers/ directory at startup. To add a new command, create a class extending BaseCommandHandler, implement the commandPrefix getter and executeCommand method, and export the class from a new file in the handlers directory. The server automatically registers the new prefix and routes matching commands to your handler without requiring changes to CommandRegistry or HandlerAdapter.

What is the difference between HandlerAdapter and CommandRegistry?

CommandRegistry serves as a static storage mechanism that maps command prefixes to handler instances, providing methods like registerHandler and getHandler. HandlerAdapter acts as the active router that intercepts incoming requests, extracts the command prefix, queries CommandRegistry for the appropriate handler, and invokes the handler's execute method. While CommandRegistry maintains the lookup table, HandlerAdapter performs the runtime dispatch logic.

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 →