Skip to content

Commit 32e4b26

Browse files
committed
Unity-MCP AutoConnect
Autoconnect feature to prevent the port being taken by other applications.
1 parent 01a3d47 commit 32e4b26

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)