Skip to content

Commit 32274a3

Browse files
committed
UnityMCP stability: robust auto-restart on compile/play transitions; stop on domain reload; start/stop locking; per-project sticky ports + brief release wait; Python discovery scans hashed+legacy files and probes; editor window live status refresh.
1 parent eabf727 commit 32274a3

31 files changed

+614
-115
lines changed

UnityMcpBridge/Editor/Data/DefaultServerConfig.cs.meta

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

UnityMcpBridge/Editor/Data/McpClients.cs.meta

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

UnityMcpBridge/Editor/Helpers/GameObjectSerializer.cs.meta

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

UnityMcpBridge/Editor/Helpers/PortManager.cs

Lines changed: 123 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
using System.IO;
33
using System.Net;
44
using System.Net.Sockets;
5+
using System.Security.Cryptography;
6+
using System.Text;
7+
using System.Threading;
58
using Newtonsoft.Json;
69
using UnityEngine;
710

@@ -31,15 +34,28 @@ public class PortConfig
3134
/// <returns>Port number to use</returns>
3235
public static int GetPortWithFallback()
3336
{
34-
// Try to load stored port first
35-
int storedPort = LoadStoredPort();
36-
if (storedPort > 0 && IsPortAvailable(storedPort))
37+
// Try to load stored port first, but only if it's from the current project
38+
var storedConfig = GetStoredPortConfig();
39+
if (storedConfig != null &&
40+
storedConfig.unity_port > 0 &&
41+
storedConfig.project_path == Application.dataPath &&
42+
IsPortAvailable(storedConfig.unity_port))
3743
{
38-
Debug.Log($"Using stored port {storedPort}");
39-
return storedPort;
44+
Debug.Log($"Using stored port {storedConfig.unity_port} for current project");
45+
return storedConfig.unity_port;
4046
}
4147

42-
// If no stored port or stored port is unavailable, find a new one
48+
// If stored port exists but is currently busy, wait briefly for release
49+
if (storedConfig != null && storedConfig.unity_port > 0)
50+
{
51+
if (WaitForPortRelease(storedConfig.unity_port, 1500))
52+
{
53+
Debug.Log($"Stored port {storedConfig.unity_port} became available after short wait");
54+
return storedConfig.unity_port;
55+
}
56+
}
57+
58+
// If no valid stored port, find a new one and save it
4359
int newPort = FindAvailablePort();
4460
SavePort(newPort);
4561
return newPort;
@@ -86,7 +102,7 @@ private static int FindAvailablePort()
86102
}
87103

88104
/// <summary>
89-
/// Check if a specific port is available
105+
/// Check if a specific port is available for binding
90106
/// </summary>
91107
/// <param name="port">Port to check</param>
92108
/// <returns>True if port is available</returns>
@@ -105,6 +121,61 @@ public static bool IsPortAvailable(int port)
105121
}
106122
}
107123

124+
/// <summary>
125+
/// Check if a port is currently being used by Unity MCP Bridge
126+
/// This helps avoid unnecessary port changes when Unity itself is using the port
127+
/// </summary>
128+
/// <param name="port">Port to check</param>
129+
/// <returns>True if port appears to be used by Unity MCP</returns>
130+
public static bool IsPortUsedByUnityMcp(int port)
131+
{
132+
try
133+
{
134+
// Try to make a quick connection to see if it's a Unity MCP server
135+
using var client = new TcpClient();
136+
var connectTask = client.ConnectAsync(IPAddress.Loopback, port);
137+
if (connectTask.Wait(100)) // 100ms timeout
138+
{
139+
// If connection succeeded, it's likely the Unity MCP server
140+
return client.Connected;
141+
}
142+
return false;
143+
}
144+
catch
145+
{
146+
return false;
147+
}
148+
}
149+
150+
/// <summary>
151+
/// Wait for a port to become available for a limited amount of time.
152+
/// Used to bridge the gap during domain reload when the old listener
153+
/// hasn't released the socket yet.
154+
/// </summary>
155+
private static bool WaitForPortRelease(int port, int timeoutMs)
156+
{
157+
int waited = 0;
158+
const int step = 100;
159+
while (waited < timeoutMs)
160+
{
161+
if (IsPortAvailable(port))
162+
{
163+
return true;
164+
}
165+
166+
// If the port is in use by an MCP instance, continue waiting briefly
167+
if (!IsPortUsedByUnityMcp(port))
168+
{
169+
// In use by something else; don't keep waiting
170+
return false;
171+
}
172+
173+
Thread.Sleep(step);
174+
waited += step;
175+
}
176+
return IsPortAvailable(port);
177+
}
178+
108179
/// <summary>
109180
/// Save port to persistent storage
110181
/// </summary>
@@ -123,7 +194,7 @@ private static void SavePort(int port)
123194
string registryDir = GetRegistryDirectory();
124195
Directory.CreateDirectory(registryDir);
125196

126-
string registryFile = Path.Combine(registryDir, RegistryFileName);
197+
string registryFile = GetRegistryFilePath();
127198
string json = JsonConvert.SerializeObject(portConfig, Formatting.Indented);
128199
File.WriteAllText(registryFile, json);
129200

@@ -143,11 +214,17 @@ private static int LoadStoredPort()
143214
{
144215
try
145216
{
146-
string registryFile = Path.Combine(GetRegistryDirectory(), RegistryFileName);
217+
string registryFile = GetRegistryFilePath();
147218

148219
if (!File.Exists(registryFile))
149220
{
150-
return 0;
221+
// Backwards compatibility: try the legacy file name
222+
string legacy = Path.Combine(GetRegistryDirectory(), RegistryFileName);
223+
if (!File.Exists(legacy))
224+
{
225+
return 0;
226+
}
227+
registryFile = legacy;
151228
}
152229

153230
string json = File.ReadAllText(registryFile);
@@ -170,11 +247,17 @@ public static PortConfig GetStoredPortConfig()
170247
{
171248
try
172249
{
173-
string registryFile = Path.Combine(GetRegistryDirectory(), RegistryFileName);
250+
string registryFile = GetRegistryFilePath();
174251

175252
if (!File.Exists(registryFile))
176253
{
177-
return null;
254+
// Backwards compatibility: try the legacy file
255+
string legacy = Path.Combine(GetRegistryDirectory(), RegistryFileName);
256+
if (!File.Exists(legacy))
257+
{
258+
return null;
259+
}
260+
registryFile = legacy;
178261
}
179262

180263
string json = File.ReadAllText(registryFile);
@@ -191,5 +274,33 @@ private static string GetRegistryDirectory()
191274
{
192275
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".unity-mcp");
193276
}
277+
278+
private static string GetRegistryFilePath()
279+
{
280+
string dir = GetRegistryDirectory();
281+
string hash = ComputeProjectHash(Application.dataPath);
282+
string fileName = $"unity-mcp-port-{hash}.json";
283+
return Path.Combine(dir, fileName);
284+
}
285+
286+
private static string ComputeProjectHash(string input)
287+
{
288+
try
289+
{
290+
using SHA1 sha1 = SHA1.Create();
291+
byte[] bytes = Encoding.UTF8.GetBytes(input ?? string.Empty);
292+
byte[] hashBytes = sha1.ComputeHash(bytes);
293+
var sb = new StringBuilder();
294+
foreach (byte b in hashBytes)
295+
{
296+
sb.Append(b.ToString("x2"));
297+
}
298+
return sb.ToString()[..8]; // short, sufficient for filenames
299+
}
300+
catch
301+
{
302+
return "default";
303+
}
304+
}
194305
}
195306
}

UnityMcpBridge/Editor/Helpers/Response.cs.meta

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

UnityMcpBridge/Editor/Helpers/ServerInstaller.cs.meta

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

UnityMcpBridge/Editor/Helpers/Vector3Helper.cs.meta

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

UnityMcpBridge/Editor/Models/Command.cs.meta

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

UnityMcpBridge/Editor/Models/MCPConfigServer.cs.meta

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

UnityMcpBridge/Editor/Models/MCPConfigServers.cs.meta

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)