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+ }
0 commit comments