How UDP Discovery Enables Automatic Server Detection in Unity MCP

Unity MCP uses UDP broadcast discovery on port 27183 to automatically detect running TypeScript MCP servers without manual IP configuration, parsing JSON announcements to establish TCP connections.

The Unity MCP package (isuzu-shiranui/unitymcp) implements zero-configuration networking through UDP discovery. This mechanism allows the Unity Editor to locate and connect to MCP servers dynamically, eliminating the need for developers to hardcode host addresses or ports. The implementation spans the settings system and core server infrastructure, utilizing asynchronous socket operations for non-blocking network communication.

How UDP Discovery Works in Unity MCP

The discovery system operates through a broadcast listener that parses JSON packets to extract connection parameters. When enabled, the McpServer class maintains a background UDP listener that updates connection settings in real-time.

Settings Configuration in McpSettings.cs

Discovery behavior is controlled through the McpSettings ScriptableObject, which persists editor preferences. The useUdpDiscovery boolean flag enables the feature, while udpDiscoveryPort defines the broadcast port (default 27183).

// jp.shiranui-isuzu.unity-mcp/Editor/Settings/McpSettings.cs
[SerializeField] public bool useUdpDiscovery = true;      // Enable discovery
[SerializeField] public int udpDiscoveryPort = 27183;     // Port for broadcast packets

These settings are checked during McpServer initialization. When useUdpDiscovery is true, the server automatically invokes StartUdpListener() to begin monitoring for broadcasts.

Starting the UDP Listener in McpServer.cs

The StartUdpListener() method in Editor/Core/McpServer.cs initializes a reusable UDP socket bound to all network interfaces. It configures the UdpClient with SO_REUSEADDR to prevent port binding conflicts and begins an asynchronous receive loop.

// jp.shiranui-isuzu.unity-mcp/Editor/Core/McpServer.cs
private void StartUdpListener() {
    lock (this.udpLock) {
        if (this.isUdpListening) return;

        this.udpListener = new UdpClient();
        this.udpListener.Client.SetSocketOption(
            SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        this.udpListener.Client.Bind(new IPEndPoint(IPAddress.Any, this.broadcastPort));
        this.isUdpListening = true;

        // Async callback for every packet
        this.udpListener.BeginReceive(this.ReceiveCallback, this.udpListener);
    }
}

This implementation uses IPAddress.Any to listen on all available network interfaces, ensuring the editor receives broadcasts regardless of which network adapter the MCP server uses.

Processing Broadcast Packets via ReceiveCallback

When a UDP datagram arrives, the ReceiveCallback method decodes the UTF-8 payload and parses it as JSON. The system distinguishes between legacy announcements and modern MCP-TS broadcasts using the type field.

private void ReceiveCallback(IAsyncResult ar) {
    // ... receive data into `data` ...

    var jsonStr = Encoding.UTF8.GetString(data);
    var serverInfo = JObject.Parse(jsonStr);
    var messageType = serverInfo["type"]?.ToString();

    switch (messageType) {
        case "listClients":
            // Legacy path – updates host/port and tries to connect
            break;
        case "mcp_server_announce":
            // Modern MCP-TS announcement
            var host = serverInfo["host"]?.ToString();
            var port = serverInfo["port"]?.Value<int>() ?? 0;
            var version = serverInfo["version"]?.ToString();

            if (!string.IsNullOrEmpty(host) && port > 0) {
                ExecuteOnMainThread(() => {
                    this.host = host;
                    this.port = port;
                    this.TryConnect();      // Initiate TCP connection
                });
            }
            break;
    }
}

The callback extracts three critical fields from the announcement: host (string), port (integer), and version (string). Valid packets trigger a main-thread update to prevent Unity API threading violations.

Establishing the TCP Connection

All UI-thread sensitive operations execute through ExecuteOnMainThread, which marshals the connection update to Unity's main execution loop. The TryConnect() method then initiates the TCP handshake to the advertised endpoint, raising the Connected event upon successful establishment.

This architecture ensures that network I/O remains asynchronous while Unity-specific operations (updating inspector fields, logging) occur on the appropriate thread.

Cleanup and Shutdown

When the editor shuts down or the server stops, StopUdpListener() closes the UDP socket and halts the background receive loop. This method is invoked from the McpServer destructor or Dispose() pattern to prevent resource leaks.

private void StopUdpListener() {
    lock (this.udpLock) {
        if (!this.isUdpListening) return;
        this.udpListener?.Close();
        this.isUdpListening = false;
        this.udpListener = null;
    }
}

The lock statement ensures thread-safe cleanup even if a packet arrives during shutdown.

Practical Implementation Examples

Enabling Discovery via Editor Script

Toggle UDP discovery programmatically by modifying the settings instance:

using UnityMCP.Editor.Settings;

public static void SetDiscovery(bool enabled) {
    McpSettings.instance.useUdpDiscovery = enabled;
    McpSettings.instance.Save();
}

Changes persist to disk via Unity's ScriptableObject serialization system and take effect the next time McpServer initializes.

Manual Server Startup with Discovery

Instantiate the server directly to trigger automatic discovery:

using UnityMCP.Editor.Core;

// Reads settings, starts UDP listener if enabled
var server = new McpServer();
server.Start();  // Begins TCP connection attempts when broadcasts arrive

The constructor automatically checks McpSettings.instance.useUdpDiscovery and invokes StartUdpListener() when the flag is true.

Simulating Server Broadcasts for Testing

Send fake announcements to test client behavior without a running TypeScript server:

using System.Net;
using System.Net.Sockets;
using System.Text;
using Newtonsoft.Json.Linq;

void SendFakeAnnouncement(string host, int port) {
    var socket = new UdpClient();
    socket.EnableBroadcast = true;
    var payload = new JObject {
        ["type"] = "mcp_server_announce",
        ["host"] = host,
        ["port"] = port,
        ["version"] = "1.0.0"
    };
    var bytes = Encoding.UTF8.GetBytes(payload.ToString());
    socket.Send(bytes, bytes.Length, new IPEndPoint(IPAddress.Broadcast, 27183));
    socket.Close();
}

This utility is useful for integration testing or verifying that the Unity client correctly parses JSON payloads and updates connection parameters.

Summary

  • UDP discovery is enabled by default via McpSettings.useUdpDiscovery and listens on port 27183 as defined in Editor/Settings/McpSettings.cs.
  • The StartUdpListener() method in Editor/Core/McpServer.cs binds to IPAddress.Any with socket reuse enabled to prevent port conflicts.
  • JSON broadcasts with type: "mcp_server_announce" trigger automatic TCP connection attempts via TryConnect() after marshaling to the main thread.
  • Thread safety is maintained through ExecuteOnMainThread for Unity API operations and lock statements for UDP socket management.
  • Cleanup occurs through StopUdpListener(), which closes the UdpClient and terminates the async receive loop when the editor shuts down.

Frequently Asked Questions

What port does Unity MCP use for UDP discovery?

Unity MCP uses port 27183 by default for UDP broadcast discovery. This value is configurable through the udpDiscoveryPort field in McpSettings.cs. The server binds to this port with SO_REUSEADDR enabled, allowing multiple instances or rapid restarts without "address already in use" errors.

How does the Unity editor handle UDP packets on the main thread?

The ReceiveCallback method uses ExecuteOnMainThread to marshal connection updates onto Unity's main execution loop. This pattern prevents threading violations since Unity API operations (such as updating inspector values or logging) must occur on the main thread, while the UDP listener operates on a background thread pool.

Can UDP discovery be disabled if I want manual configuration?

Yes. Set McpSettings.instance.useUdpDiscovery = false and call Save(). When disabled, the McpServer constructor skips the StartUdpListener() call, and you must manually configure the host and port fields before calling Start(). This is useful for connecting to remote servers outside the local broadcast domain.

What JSON format does the MCP server broadcast?

The TypeScript MCP server broadcasts a JSON object containing at minimum: {"type": "mcp_server_announce", "host": "127.0.0.1", "port": 8080, "version": "1.0.0"}. The Unity client specifically looks for the mcp_server_announce type to distinguish modern announcements from legacy listClients packets. Missing or invalid fields (null host or zero port) cause the packet to be ignored.

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 →