UnityConnection Component in TypeScript MCP: Complete Networking Layer Guide
The UnityConnection component is a singleton TCP server that manages bidirectional communication between TypeScript MCP handlers and Unity Editor instances, handling client registration, request/response correlation, and UDP auto-discovery.
The UnityConnection class serves as the backbone of the networking stack in the isuzu-shiranui/unitymcp repository. Located in unity-mcp-ts/src/core/UnityConnection.ts, this component abstracts low-level socket operations to provide a reliable, promise-based API for higher-level MCP handlers.
What Is the UnityConnection Component?
The UnityConnection is a singleton class that implements the server-side networking layer of the Model Context Protocol (MCP) implementation for Unity. It maintains a TCP server that listens for connections from one or more Unity Editor instances, manages the lifecycle of those connections, and correlates asynchronous requests with their corresponding responses.
Unlike standard HTTP-based MCP transports, this component uses persistent TCP sockets with newline-delimited JSON (NDJSON) messaging to enable real-time, bidirectional communication between the TypeScript MCP server and the Unity C# runtime.
Key Features and Architecture
Singleton Pattern Implementation
The component enforces a single instance across the application using a static getter:
private static instance: UnityConnection | null = null;
public static getInstance(): UnityConnection {
if (!UnityConnection.instance) {
UnityConnection.instance = new UnityConnection();
}
return UnityConnection.instance;
}
This ensures that all MCP handlers—whether commands, resources, or prompts—share the same connection state and client registry.
TCP Server and Client Management
The start() method in unity-mcp-ts/src/core/UnityConnection.ts creates a TCP server using Node.js net.createServer. When a Unity client connects, the server:
- Generates a temporary client ID for the socket
- Adds the client to an internal
Mapof connected clients - Sets up data listeners for incoming messages
- Automatically designates the first connected client as the active client
The setActiveClient() method allows runtime switching between multiple Unity instances, enabling workflows where you might target different editor windows or build targets.
Request/Response Correlation
The component implements a promise-based request system with automatic ID correlation:
public async sendRequest(request: any): Promise<any> {
const id = ++this.requestIdCounter;
const message = { ...request, id };
return new Promise((resolve, reject) => {
this.pendingRequests.set(id, { resolve, reject, timeout });
this.activeClient?.write(JSON.stringify(message) + '\n');
});
}
Each outgoing request receives a monotonically incrementing id. The component stores resolve and reject callbacks in a pendingRequests map. When a response arrives with a matching id, the promise is resolved. If no response arrives within 30 seconds, the promise rejects with a timeout error.
UDP Auto-Discovery
To eliminate manual configuration, the component supports UDP broadcast discovery. When start() is called, sendInitialBroadcast() creates a temporary UDP socket and broadcasts a JSON packet containing the server's host, port, and version:
private sendInitialBroadcast(): void {
const message = JSON.stringify({
type: 'mcp_server_announce',
host: this.host,
port: this.port,
version: '1.0.0'
});
// Broadcast to 255.255.255.255 on discovery port
}
Unity clients listening on the discovery port can automatically detect and connect to the MCP server without requiring users to input IP addresses or port numbers manually.
Implementation Details
Client Registration Protocol
When a Unity client first connects, it must send a registration message. The handleRegistration() method in unity-mcp-ts/src/core/UnityConnection.ts processes this:
private handleRegistration(client: Client, message: any): void {
const oldId = client.id;
const newId = message.clientId;
// Update the client ID from temporary to persistent
client.id = newId;
this.clients.delete(oldId);
this.clients.set(newId, client);
this.emit('clientRegistered', { clientId: newId });
}
This registration step allows the Unity client to specify its own identifier (typically derived from the project name or process ID), replacing the temporary socket ID assigned by the server.
Event-Driven Architecture
The component extends Node.js EventEmitter and emits specific events that other parts of the system can listen to:
clientConnected– Emitted when a new TCP socket connectsclientDisconnected– Emitted when a socket closes or errorsclientRegistered– Emitted after successful registration handshakeserverStarted– Emitted when the TCP server begins listeningserverStopped– Emitted after graceful shutdownmessage– Emitted for every incoming message (used for logging/debugging)
Handlers in unity-mcp-ts/src/core/BaseCommandHandler.ts and related files rely on these events to detect when Unity becomes available or unavailable.
Graceful Shutdown
The stop() method ensures clean termination:
public stop(): void {
// Reject all pending requests with an error
for (const [id, pending] of this.pendingRequests) {
pending.reject(new Error('Server shutting down'));
}
this.pendingRequests.clear();
// Destroy all client sockets
for (const client of this.clients.values()) {
client.socket.destroy();
}
this.clients.clear();
// Close the server
this.server?.close();
this.emit('serverStopped');
}
This prevents hanging promises and ensures that Unity clients receive immediate connection termination rather than waiting for TCP timeouts.
Code Examples
Getting the Singleton Instance
import { UnityConnection } from './core/UnityConnection.js';
const connection = UnityConnection.getInstance();
Starting the TCP Server
await connection.configure('127.0.0.1', 27182);
await connection.start();
Sending a Request from a Custom Handler
const request = {
command: 'unity.editor.openScene',
type: 'command',
params: { path: 'Assets/Scenes/Main.unity' }
};
try {
const response = await connection.sendRequest(request);
console.log('Unity responded with', response);
} catch (err) {
console.error('Request failed:', err);
}
Listening for Incoming Messages
connection.on('message', ({ clientId, message }) => {
console.log(`Message from ${clientId}:`, message);
});
Switching the Active Client
const clients = connection.getConnectedClients();
if (clients.length > 1) {
const newActive = clients[1].id;
connection.setActiveClient(newActive);
}
Stopping the Server
connection.stop();
Summary
- The UnityConnection component is a singleton TCP server that bridges TypeScript MCP handlers with Unity Editor instances via persistent sockets.
- It manages client lifecycle through registration events, supports multiple simultaneous Unity clients, and maintains an active client for request routing.
- Request/response correlation uses monotonic IDs and Promise-based resolution with automatic 30-second timeouts.
- UDP auto-discovery eliminates manual configuration by broadcasting server availability to the local network.
- The component emits events for all connection state changes, enabling reactive handler logic in
BaseCommandHandler,BaseResourceHandler, andBasePromptHandlerclasses.
Frequently Asked Questions
How does UnityConnection handle multiple Unity Editor instances?
The component maintains a Map of all connected clients in unity-mcp-ts/src/core/UnityConnection.ts. While multiple clients can remain connected simultaneously, the server designates one as the active client (by default, the first to connect). You can switch the active target at runtime using setActiveClient(clientId), allowing you to send commands to specific Unity instances without disconnecting others.
What happens if a request to Unity times out?
Every request sent via sendRequest() includes a 30-second timeout mechanism. If the Unity client does not respond with a matching request ID within this window, the stored Promise is rejected with a timeout error. This prevents the TypeScript MCP server from hanging indefinitely when Unity is busy, crashed, or disconnected. The timeout duration is hardcoded in the sendRequest implementation in unity-mcp-ts/src/core/UnityConnection.ts.
Can I use UnityConnection without UDP auto-discovery?
Yes. While the component automatically broadcasts a UDP packet on startup via sendInitialBroadcast(), this is purely for convenience. You can disable or ignore the discovery mechanism by simply not implementing the listener on the Unity side. The TCP server will still accept direct connections at the configured host and port (default 127.0.0.1:27182). The registration handshake over TCP is mandatory, but UDP discovery is optional.
How does the registration handshake work between TypeScript and Unity?
When a Unity client first connects via TCP, the server assigns it a temporary socket ID. The client must then send a JSON registration message containing its preferred clientId (typically derived from the project name). The handleRegistration() method in unity-mcp-ts/src/core/UnityConnection.ts swaps the temporary ID for the persistent one, updates the internal client map, and emits a clientRegistered event. This handshake allows Unity to maintain a stable identity across reconnections.
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 →