Skip to content

Commit 41f0a57

Browse files
authored
Merge pull request #176 from Scriptwonder/master
Autoconnect feature to prevent the port from being taken by other applications. Currently still supporting local host, and we will figure out how remote hosting should work. MCP still is a 1:1 service. The newly instantiated port will take over the new port from the commands. Something worth discussing for.
2 parents 01a3d47 + 32e4b26 commit 41f0a57

File tree

7 files changed

+356
-9
lines changed

7 files changed

+356
-9
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
using System;
2+
using System.IO;
3+
using System.Net;
4+
using System.Net.Sockets;
5+
using Newtonsoft.Json;
6+
using UnityEngine;
7+
8+
namespace UnityMcpBridge.Editor.Helpers
9+
{
10+
/// <summary>
11+
/// Manages dynamic port allocation and persistent storage for Unity MCP Bridge
12+
/// </summary>
13+
public static class PortManager
14+
{
15+
private const int DefaultPort = 6400;
16+
private const int MaxPortAttempts = 100;
17+
private const string RegistryFileName = "unity-mcp-port.json";
18+
19+
[Serializable]
20+
public class PortConfig
21+
{
22+
public int unity_port;
23+
public string created_date;
24+
public string project_path;
25+
}
26+
27+
/// <summary>
28+
/// Get the port to use - either from storage or discover a new one
29+
/// Will try stored port first, then fallback to discovering new port
30+
/// </summary>
31+
/// <returns>Port number to use</returns>
32+
public static int GetPortWithFallback()
33+
{
34+
// Try to load stored port first
35+
int storedPort = LoadStoredPort();
36+
if (storedPort > 0 && IsPortAvailable(storedPort))
37+
{
38+
Debug.Log($"Using stored port {storedPort}");
39+
return storedPort;
40+
}
41+
42+
// If no stored port or stored port is unavailable, find a new one
43+
int newPort = FindAvailablePort();
44+
SavePort(newPort);
45+
return newPort;
46+
}
47+
48+
/// <summary>
49+
/// Discover and save a new available port (used by Auto-Connect button)
50+
/// </summary>
51+
/// <returns>New available port</returns>
52+
public static int DiscoverNewPort()
53+
{
54+
int newPort = FindAvailablePort();
55+
SavePort(newPort);
56+
Debug.Log($"Discovered and saved new port: {newPort}");
57+
return newPort;
58+
}
59+
60+
/// <summary>
61+
/// Find an available port starting from the default port
62+
/// </summary>
63+
/// <returns>Available port number</returns>
64+
private static int FindAvailablePort()
65+
{
66+
// Always try default port first
67+
if (IsPortAvailable(DefaultPort))
68+
{
69+
Debug.Log($"Using default port {DefaultPort}");
70+
return DefaultPort;
71+
}
72+
73+
Debug.Log($"Default port {DefaultPort} is in use, searching for alternative...");
74+
75+
// Search for alternatives
76+
for (int port = DefaultPort + 1; port < DefaultPort + MaxPortAttempts; port++)
77+
{
78+
if (IsPortAvailable(port))
79+
{
80+
Debug.Log($"Found available port {port}");
81+
return port;
82+
}
83+
}
84+
85+
throw new Exception($"No available ports found in range {DefaultPort}-{DefaultPort + MaxPortAttempts}");
86+
}
87+
88+
/// <summary>
89+
/// Check if a specific port is available
90+
/// </summary>
91+
/// <param name="port">Port to check</param>
92+
/// <returns>True if port is available</returns>
93+
public static bool IsPortAvailable(int port)
94+
{
95+
try
96+
{
97+
var testListener = new TcpListener(IPAddress.Loopback, port);
98+
testListener.Start();
99+
testListener.Stop();
100+
return true;
101+
}
102+
catch (SocketException)
103+
{
104+
return false;
105+
}
106+
}
107+
108+
/// <summary>
109+
/// Save port to persistent storage
110+
/// </summary>
111+
/// <param name="port">Port to save</param>
112+
private static void SavePort(int port)
113+
{
114+
try
115+
{
116+
var portConfig = new PortConfig
117+
{
118+
unity_port = port,
119+
created_date = DateTime.UtcNow.ToString("O"),
120+
project_path = Application.dataPath
121+
};
122+
123+
string registryDir = GetRegistryDirectory();
124+
Directory.CreateDirectory(registryDir);
125+
126+
string registryFile = Path.Combine(registryDir, RegistryFileName);
127+
string json = JsonConvert.SerializeObject(portConfig, Formatting.Indented);
128+
File.WriteAllText(registryFile, json);
129+
130+
Debug.Log($"Saved port {port} to storage");
131+
}
132+
catch (Exception ex)
133+
{
134+
Debug.LogWarning($"Could not save port to storage: {ex.Message}");
135+
}
136+
}
137+
138+
/// <summary>
139+
/// Load port from persistent storage
140+
/// </summary>
141+
/// <returns>Stored port number, or 0 if not found</returns>
142+
private static int LoadStoredPort()
143+
{
144+
try
145+
{
146+
string registryFile = Path.Combine(GetRegistryDirectory(), RegistryFileName);
147+
148+
if (!File.Exists(registryFile))
149+
{
150+
return 0;
151+
}
152+
153+
string json = File.ReadAllText(registryFile);
154+
var portConfig = JsonConvert.DeserializeObject<PortConfig>(json);
155+
156+
return portConfig?.unity_port ?? 0;
157+
}
158+
catch (Exception ex)
159+
{
160+
Debug.LogWarning($"Could not load port from storage: {ex.Message}");
161+
return 0;
162+
}
163+
}
164+
165+
/// <summary>
166+
/// Get the current stored port configuration
167+
/// </summary>
168+
/// <returns>Port configuration if exists, null otherwise</returns>
169+
public static PortConfig GetStoredPortConfig()
170+
{
171+
try
172+
{
173+
string registryFile = Path.Combine(GetRegistryDirectory(), RegistryFileName);
174+
175+
if (!File.Exists(registryFile))
176+
{
177+
return null;
178+
}
179+
180+
string json = File.ReadAllText(registryFile);
181+
return JsonConvert.DeserializeObject<PortConfig>(json);
182+
}
183+
catch (Exception ex)
184+
{
185+
Debug.LogWarning($"Could not load port config: {ex.Message}");
186+
return null;
187+
}
188+
}
189+
190+
private static string GetRegistryDirectory()
191+
{
192+
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".unity-mcp");
193+
}
194+
}
195+
}

UnityMcpBridge/Editor/Helpers/PortManager.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

UnityMcpBridge/Editor/UnityMcpBridge.cs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,40 @@ private static Dictionary<
2525
string,
2626
(string commandJson, TaskCompletionSource<string> tcs)
2727
> commandQueue = new();
28-
private static readonly int unityPort = 6400; // Hardcoded port
28+
private static int currentUnityPort = 6400; // Dynamic port, starts with default
29+
private static bool isAutoConnectMode = false;
2930

3031
public static bool IsRunning => isRunning;
32+
public static int GetCurrentPort() => currentUnityPort;
33+
public static bool IsAutoConnectMode() => isAutoConnectMode;
34+
35+
/// <summary>
36+
/// Start with Auto-Connect mode - discovers new port and saves it
37+
/// </summary>
38+
public static void StartAutoConnect()
39+
{
40+
Stop(); // Stop current connection
41+
42+
try
43+
{
44+
// Discover new port and save it
45+
currentUnityPort = PortManager.DiscoverNewPort();
46+
47+
listener = new TcpListener(IPAddress.Loopback, currentUnityPort);
48+
listener.Start();
49+
isRunning = true;
50+
isAutoConnectMode = true;
51+
52+
Debug.Log($"UnityMcpBridge auto-connected on port {currentUnityPort}");
53+
Task.Run(ListenerLoop);
54+
EditorApplication.update += ProcessCommands;
55+
}
56+
catch (Exception ex)
57+
{
58+
Debug.LogError($"Auto-connect failed: {ex.Message}");
59+
throw;
60+
}
61+
}
3162

3263
public static bool FolderExists(string path)
3364
{
@@ -74,10 +105,14 @@ public static void Start()
74105

75106
try
76107
{
77-
listener = new TcpListener(IPAddress.Loopback, unityPort);
108+
// Use PortManager to get available port with automatic fallback
109+
currentUnityPort = PortManager.GetPortWithFallback();
110+
111+
listener = new TcpListener(IPAddress.Loopback, currentUnityPort);
78112
listener.Start();
79113
isRunning = true;
80-
Debug.Log($"UnityMcpBridge started on port {unityPort}.");
114+
isAutoConnectMode = false; // Normal startup mode
115+
Debug.Log($"UnityMcpBridge started on port {currentUnityPort}.");
81116
// Assuming ListenerLoop and ProcessCommands are defined elsewhere
82117
Task.Run(ListenerLoop);
83118
EditorApplication.update += ProcessCommands;
@@ -87,7 +122,7 @@ public static void Start()
87122
if (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
88123
{
89124
Debug.LogError(
90-
$"Port {unityPort} is already in use. Ensure no other instances are running or change the port."
125+
$"Port {currentUnityPort} is already in use. This should not happen with dynamic port allocation."
91126
);
92127
}
93128
else

UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ public class UnityMcpEditorWindow : EditorWindow
1818
private Vector2 scrollPosition;
1919
private string pythonServerInstallationStatus = "Not Installed";
2020
private Color pythonServerInstallationStatusColor = Color.red;
21-
private const int unityPort = 6400; // Hardcoded Unity port
22-
private const int mcpPort = 6500; // Hardcoded MCP port
21+
private const int mcpPort = 6500; // MCP port (still hardcoded for MCP server)
2322
private readonly McpClients mcpClients = new();
2423

2524
// Script validation settings
@@ -45,6 +44,7 @@ private void OnEnable()
4544
{
4645
UpdatePythonServerInstallationStatus();
4746

47+
// Refresh bridge status
4848
isUnityBridgeRunning = UnityMcpBridge.IsRunning;
4949
foreach (McpClient mcpClient in mcpClients.clients)
5050
{
@@ -210,11 +210,42 @@ private void DrawServerStatusSection()
210210
EditorGUILayout.EndHorizontal();
211211

212212
EditorGUILayout.Space(5);
213+
214+
// Connection mode and Auto-Connect button
215+
EditorGUILayout.BeginHorizontal();
216+
217+
bool isAutoMode = UnityMcpBridge.IsAutoConnectMode();
218+
GUIStyle modeStyle = new GUIStyle(EditorStyles.miniLabel) { fontSize = 11 };
219+
EditorGUILayout.LabelField($"Mode: {(isAutoMode ? "Auto" : "Standard")}", modeStyle);
220+
221+
// Auto-Connect button
222+
if (GUILayout.Button(isAutoMode ? "Connected ✓" : "Auto-Connect", GUILayout.Width(100), GUILayout.Height(24)))
223+
{
224+
if (!isAutoMode)
225+
{
226+
try
227+
{
228+
UnityMcpBridge.StartAutoConnect();
229+
// Update UI state
230+
isUnityBridgeRunning = UnityMcpBridge.IsRunning;
231+
Repaint();
232+
}
233+
catch (Exception ex)
234+
{
235+
EditorUtility.DisplayDialog("Auto-Connect Failed", ex.Message, "OK");
236+
}
237+
}
238+
}
239+
240+
EditorGUILayout.EndHorizontal();
241+
242+
// Current ports display
243+
int currentUnityPort = UnityMcpBridge.GetCurrentPort();
213244
GUIStyle portStyle = new GUIStyle(EditorStyles.miniLabel)
214245
{
215246
fontSize = 11
216247
};
217-
EditorGUILayout.LabelField($"Ports: Unity {unityPort}, MCP {mcpPort}", portStyle);
248+
EditorGUILayout.LabelField($"Ports: Unity {currentUnityPort}, MCP {mcpPort}", portStyle);
218249
EditorGUILayout.Space(5);
219250
EditorGUILayout.EndVertical();
220251
}

UnityMcpServer/src/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class ServerConfig:
1515
mcp_port: int = 6500
1616

1717
# Connection settings
18-
connection_timeout: float = 86400.0 # 24 hours timeout
18+
connection_timeout: float = 600.0 # 10 minutes timeout
1919
buffer_size: int = 16 * 1024 * 1024 # 16MB buffer
2020

2121
# Logging settings

0 commit comments

Comments
 (0)