Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions UnityMcpBridge/Editor/Helpers/PortManager.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Manages dynamic port allocation and persistent storage for Unity MCP Bridge
/// </summary>
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;
}

/// <summary>
/// Get the port to use - either from storage or discover a new one
/// Will try stored port first, then fallback to discovering new port
/// </summary>
/// <returns>Port number to use</returns>
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;
}

/// <summary>
/// Discover and save a new available port (used by Auto-Connect button)
/// </summary>
/// <returns>New available port</returns>
public static int DiscoverNewPort()
{
int newPort = FindAvailablePort();
SavePort(newPort);
Debug.Log($"Discovered and saved new port: {newPort}");
return newPort;
}

/// <summary>
/// Find an available port starting from the default port
/// </summary>
/// <returns>Available port number</returns>
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}");
}

/// <summary>
/// Check if a specific port is available
/// </summary>
/// <param name="port">Port to check</param>
/// <returns>True if port is available</returns>
public static bool IsPortAvailable(int port)
{
try
{
var testListener = new TcpListener(IPAddress.Loopback, port);
testListener.Start();
testListener.Stop();
return true;
}
catch (SocketException)
{
return false;
}
}

/// <summary>
/// Save port to persistent storage
/// </summary>
/// <param name="port">Port to save</param>
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}");
}
}

/// <summary>
/// Load port from persistent storage
/// </summary>
/// <returns>Stored port number, or 0 if not found</returns>
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<PortConfig>(json);

return portConfig?.unity_port ?? 0;
}
catch (Exception ex)
{
Debug.LogWarning($"Could not load port from storage: {ex.Message}");
return 0;
}
}

/// <summary>
/// Get the current stored port configuration
/// </summary>
/// <returns>Port configuration if exists, null otherwise</returns>
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<PortConfig>(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");
}
}
}
11 changes: 11 additions & 0 deletions UnityMcpBridge/Editor/Helpers/PortManager.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 39 additions & 4 deletions UnityMcpBridge/Editor/UnityMcpBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,40 @@ private static Dictionary<
string,
(string commandJson, TaskCompletionSource<string> 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;

/// <summary>
/// Start with Auto-Connect mode - discovers new port and saves it
/// </summary>
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)
{
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand Down
37 changes: 34 additions & 3 deletions UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -45,6 +44,7 @@ private void OnEnable()
{
UpdatePythonServerInstallationStatus();

// Refresh bridge status
isUnityBridgeRunning = UnityMcpBridge.IsRunning;
foreach (McpClient mcpClient in mcpClients.clients)
{
Expand Down Expand Up @@ -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();
}
Expand Down
2 changes: 1 addition & 1 deletion UnityMcpServer/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading