diff --git a/UnityMcpBridge/Editor/Helpers/PortManager.cs b/UnityMcpBridge/Editor/Helpers/PortManager.cs
new file mode 100644
index 00000000..8e368a6a
--- /dev/null
+++ b/UnityMcpBridge/Editor/Helpers/PortManager.cs
@@ -0,0 +1,195 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using Newtonsoft.Json;
+using UnityEngine;
+
+namespace UnityMcpBridge.Editor.Helpers
+{
+ ///
+ /// Manages dynamic port allocation and persistent storage for Unity MCP Bridge
+ ///
+ public static class PortManager
+ {
+ private const int DefaultPort = 6400;
+ private const int MaxPortAttempts = 100;
+ private const string RegistryFileName = "unity-mcp-port.json";
+
+ [Serializable]
+ public class PortConfig
+ {
+ public int unity_port;
+ public string created_date;
+ public string project_path;
+ }
+
+ ///
+ /// Get the port to use - either from storage or discover a new one
+ /// Will try stored port first, then fallback to discovering new port
+ ///
+ /// Port number to use
+ public static int GetPortWithFallback()
+ {
+ // Try to load stored port first
+ int storedPort = LoadStoredPort();
+ if (storedPort > 0 && IsPortAvailable(storedPort))
+ {
+ Debug.Log($"Using stored port {storedPort}");
+ return storedPort;
+ }
+
+ // If no stored port or stored port is unavailable, find a new one
+ int newPort = FindAvailablePort();
+ SavePort(newPort);
+ return newPort;
+ }
+
+ ///
+ /// Discover and save a new available port (used by Auto-Connect button)
+ ///
+ /// New available port
+ public static int DiscoverNewPort()
+ {
+ int newPort = FindAvailablePort();
+ SavePort(newPort);
+ Debug.Log($"Discovered and saved new port: {newPort}");
+ return newPort;
+ }
+
+ ///
+ /// Find an available port starting from the default port
+ ///
+ /// Available port number
+ private static int FindAvailablePort()
+ {
+ // Always try default port first
+ if (IsPortAvailable(DefaultPort))
+ {
+ Debug.Log($"Using default port {DefaultPort}");
+ return DefaultPort;
+ }
+
+ Debug.Log($"Default port {DefaultPort} is in use, searching for alternative...");
+
+ // Search for alternatives
+ for (int port = DefaultPort + 1; port < DefaultPort + MaxPortAttempts; port++)
+ {
+ if (IsPortAvailable(port))
+ {
+ Debug.Log($"Found available port {port}");
+ return port;
+ }
+ }
+
+ throw new Exception($"No available ports found in range {DefaultPort}-{DefaultPort + MaxPortAttempts}");
+ }
+
+ ///
+ /// Check if a specific port is available
+ ///
+ /// Port to check
+ /// True if port is available
+ public static bool IsPortAvailable(int port)
+ {
+ try
+ {
+ var testListener = new TcpListener(IPAddress.Loopback, port);
+ testListener.Start();
+ testListener.Stop();
+ return true;
+ }
+ catch (SocketException)
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Save port to persistent storage
+ ///
+ /// Port to save
+ private static void SavePort(int port)
+ {
+ try
+ {
+ var portConfig = new PortConfig
+ {
+ unity_port = port,
+ created_date = DateTime.UtcNow.ToString("O"),
+ project_path = Application.dataPath
+ };
+
+ string registryDir = GetRegistryDirectory();
+ Directory.CreateDirectory(registryDir);
+
+ string registryFile = Path.Combine(registryDir, RegistryFileName);
+ string json = JsonConvert.SerializeObject(portConfig, Formatting.Indented);
+ File.WriteAllText(registryFile, json);
+
+ Debug.Log($"Saved port {port} to storage");
+ }
+ catch (Exception ex)
+ {
+ Debug.LogWarning($"Could not save port to storage: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Load port from persistent storage
+ ///
+ /// Stored port number, or 0 if not found
+ private static int LoadStoredPort()
+ {
+ try
+ {
+ string registryFile = Path.Combine(GetRegistryDirectory(), RegistryFileName);
+
+ if (!File.Exists(registryFile))
+ {
+ return 0;
+ }
+
+ string json = File.ReadAllText(registryFile);
+ var portConfig = JsonConvert.DeserializeObject(json);
+
+ return portConfig?.unity_port ?? 0;
+ }
+ catch (Exception ex)
+ {
+ Debug.LogWarning($"Could not load port from storage: {ex.Message}");
+ return 0;
+ }
+ }
+
+ ///
+ /// Get the current stored port configuration
+ ///
+ /// Port configuration if exists, null otherwise
+ public static PortConfig GetStoredPortConfig()
+ {
+ try
+ {
+ string registryFile = Path.Combine(GetRegistryDirectory(), RegistryFileName);
+
+ if (!File.Exists(registryFile))
+ {
+ return null;
+ }
+
+ string json = File.ReadAllText(registryFile);
+ return JsonConvert.DeserializeObject(json);
+ }
+ catch (Exception ex)
+ {
+ Debug.LogWarning($"Could not load port config: {ex.Message}");
+ return null;
+ }
+ }
+
+ private static string GetRegistryDirectory()
+ {
+ return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".unity-mcp");
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnityMcpBridge/Editor/Helpers/PortManager.cs.meta b/UnityMcpBridge/Editor/Helpers/PortManager.cs.meta
new file mode 100644
index 00000000..ee3f667c
--- /dev/null
+++ b/UnityMcpBridge/Editor/Helpers/PortManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a1b2c3d4e5f6789012345678901234ab
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
\ No newline at end of file
diff --git a/UnityMcpBridge/Editor/UnityMcpBridge.cs b/UnityMcpBridge/Editor/UnityMcpBridge.cs
index 2242cd62..4f3a6082 100644
--- a/UnityMcpBridge/Editor/UnityMcpBridge.cs
+++ b/UnityMcpBridge/Editor/UnityMcpBridge.cs
@@ -25,9 +25,40 @@ private static Dictionary<
string,
(string commandJson, TaskCompletionSource tcs)
> commandQueue = new();
- private static readonly int unityPort = 6400; // Hardcoded port
+ private static int currentUnityPort = 6400; // Dynamic port, starts with default
+ private static bool isAutoConnectMode = false;
public static bool IsRunning => isRunning;
+ public static int GetCurrentPort() => currentUnityPort;
+ public static bool IsAutoConnectMode() => isAutoConnectMode;
+
+ ///
+ /// Start with Auto-Connect mode - discovers new port and saves it
+ ///
+ public static void StartAutoConnect()
+ {
+ Stop(); // Stop current connection
+
+ try
+ {
+ // Discover new port and save it
+ currentUnityPort = PortManager.DiscoverNewPort();
+
+ listener = new TcpListener(IPAddress.Loopback, currentUnityPort);
+ listener.Start();
+ isRunning = true;
+ isAutoConnectMode = true;
+
+ Debug.Log($"UnityMcpBridge auto-connected on port {currentUnityPort}");
+ Task.Run(ListenerLoop);
+ EditorApplication.update += ProcessCommands;
+ }
+ catch (Exception ex)
+ {
+ Debug.LogError($"Auto-connect failed: {ex.Message}");
+ throw;
+ }
+ }
public static bool FolderExists(string path)
{
@@ -74,10 +105,14 @@ public static void Start()
try
{
- listener = new TcpListener(IPAddress.Loopback, unityPort);
+ // Use PortManager to get available port with automatic fallback
+ currentUnityPort = PortManager.GetPortWithFallback();
+
+ listener = new TcpListener(IPAddress.Loopback, currentUnityPort);
listener.Start();
isRunning = true;
- Debug.Log($"UnityMcpBridge started on port {unityPort}.");
+ isAutoConnectMode = false; // Normal startup mode
+ Debug.Log($"UnityMcpBridge started on port {currentUnityPort}.");
// Assuming ListenerLoop and ProcessCommands are defined elsewhere
Task.Run(ListenerLoop);
EditorApplication.update += ProcessCommands;
@@ -87,7 +122,7 @@ public static void Start()
if (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
{
Debug.LogError(
- $"Port {unityPort} is already in use. Ensure no other instances are running or change the port."
+ $"Port {currentUnityPort} is already in use. This should not happen with dynamic port allocation."
);
}
else
diff --git a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs
index 561cd39d..62c919d8 100644
--- a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs
+++ b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs
@@ -18,8 +18,7 @@ public class UnityMcpEditorWindow : EditorWindow
private Vector2 scrollPosition;
private string pythonServerInstallationStatus = "Not Installed";
private Color pythonServerInstallationStatusColor = Color.red;
- private const int unityPort = 6400; // Hardcoded Unity port
- private const int mcpPort = 6500; // Hardcoded MCP port
+ private const int mcpPort = 6500; // MCP port (still hardcoded for MCP server)
private readonly McpClients mcpClients = new();
// Script validation settings
@@ -45,6 +44,7 @@ private void OnEnable()
{
UpdatePythonServerInstallationStatus();
+ // Refresh bridge status
isUnityBridgeRunning = UnityMcpBridge.IsRunning;
foreach (McpClient mcpClient in mcpClients.clients)
{
@@ -210,11 +210,42 @@ private void DrawServerStatusSection()
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
+
+ // Connection mode and Auto-Connect button
+ EditorGUILayout.BeginHorizontal();
+
+ bool isAutoMode = UnityMcpBridge.IsAutoConnectMode();
+ GUIStyle modeStyle = new GUIStyle(EditorStyles.miniLabel) { fontSize = 11 };
+ EditorGUILayout.LabelField($"Mode: {(isAutoMode ? "Auto" : "Standard")}", modeStyle);
+
+ // Auto-Connect button
+ if (GUILayout.Button(isAutoMode ? "Connected ✓" : "Auto-Connect", GUILayout.Width(100), GUILayout.Height(24)))
+ {
+ if (!isAutoMode)
+ {
+ try
+ {
+ UnityMcpBridge.StartAutoConnect();
+ // Update UI state
+ isUnityBridgeRunning = UnityMcpBridge.IsRunning;
+ Repaint();
+ }
+ catch (Exception ex)
+ {
+ EditorUtility.DisplayDialog("Auto-Connect Failed", ex.Message, "OK");
+ }
+ }
+ }
+
+ EditorGUILayout.EndHorizontal();
+
+ // Current ports display
+ int currentUnityPort = UnityMcpBridge.GetCurrentPort();
GUIStyle portStyle = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 11
};
- EditorGUILayout.LabelField($"Ports: Unity {unityPort}, MCP {mcpPort}", portStyle);
+ EditorGUILayout.LabelField($"Ports: Unity {currentUnityPort}, MCP {mcpPort}", portStyle);
EditorGUILayout.Space(5);
EditorGUILayout.EndVertical();
}
diff --git a/UnityMcpServer/src/config.py b/UnityMcpServer/src/config.py
index 58f6f846..c42437a7 100644
--- a/UnityMcpServer/src/config.py
+++ b/UnityMcpServer/src/config.py
@@ -15,7 +15,7 @@ class ServerConfig:
mcp_port: int = 6500
# Connection settings
- connection_timeout: float = 86400.0 # 24 hours timeout
+ connection_timeout: float = 600.0 # 10 minutes timeout
buffer_size: int = 16 * 1024 * 1024 # 16MB buffer
# Logging settings
diff --git a/UnityMcpServer/src/port_discovery.py b/UnityMcpServer/src/port_discovery.py
new file mode 100644
index 00000000..a0dfe961
--- /dev/null
+++ b/UnityMcpServer/src/port_discovery.py
@@ -0,0 +1,69 @@
+"""
+Port discovery utility for Unity MCP Server.
+Reads port configuration saved by Unity Bridge.
+"""
+
+import json
+import os
+import logging
+from pathlib import Path
+from typing import Optional
+
+logger = logging.getLogger("unity-mcp-server")
+
+class PortDiscovery:
+ """Handles port discovery from Unity Bridge registry"""
+
+ REGISTRY_FILE = "unity-mcp-port.json"
+
+ @staticmethod
+ def get_registry_path() -> Path:
+ """Get the path to the port registry file"""
+ return Path.home() / ".unity-mcp" / PortDiscovery.REGISTRY_FILE
+
+ @staticmethod
+ def discover_unity_port() -> int:
+ """
+ Discover Unity port from registry file with fallback to default
+
+ Returns:
+ Port number to connect to
+ """
+ registry_file = PortDiscovery.get_registry_path()
+
+ if registry_file.exists():
+ try:
+ with open(registry_file, 'r') as f:
+ port_config = json.load(f)
+
+ unity_port = port_config.get('unity_port')
+ if unity_port and isinstance(unity_port, int):
+ logger.info(f"Discovered Unity port from registry: {unity_port}")
+ return unity_port
+
+ except Exception as e:
+ logger.warning(f"Could not read port registry: {e}")
+
+ # Fallback to default port
+ logger.info("No port registry found, using default port 6400")
+ return 6400
+
+ @staticmethod
+ def get_port_config() -> Optional[dict]:
+ """
+ Get the full port configuration from registry
+
+ Returns:
+ Port configuration dict or None if not found
+ """
+ registry_file = PortDiscovery.get_registry_path()
+
+ if not registry_file.exists():
+ return None
+
+ try:
+ with open(registry_file, 'r') as f:
+ return json.load(f)
+ except Exception as e:
+ logger.warning(f"Could not read port configuration: {e}")
+ return None
\ No newline at end of file
diff --git a/UnityMcpServer/src/unity_connection.py b/UnityMcpServer/src/unity_connection.py
index 252b5048..da88d9bd 100644
--- a/UnityMcpServer/src/unity_connection.py
+++ b/UnityMcpServer/src/unity_connection.py
@@ -4,6 +4,7 @@
from dataclasses import dataclass
from typing import Dict, Any
from config import config
+from port_discovery import PortDiscovery
# Configure logging using settings from config
logging.basicConfig(
@@ -16,8 +17,13 @@
class UnityConnection:
"""Manages the socket connection to the Unity Editor."""
host: str = config.unity_host
- port: int = config.unity_port
+ port: int = None # Will be set dynamically
sock: socket.socket = None # Socket for Unity communication
+
+ def __post_init__(self):
+ """Set port from discovery if not explicitly provided"""
+ if self.port is None:
+ self.port = PortDiscovery.discover_unity_port()
def connect(self) -> bool:
"""Establish a connection to the Unity Editor."""