Unity-MCP AutoConnect
Autoconnect feature to prevent the port being taken by other applications.main
parent
01a3d472af
commit
32e4b2642f
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a1b2c3d4e5f6789012345678901234ab
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -25,9 +25,40 @@ namespace UnityMcpBridge.Editor
|
||||||
string,
|
string,
|
||||||
(string commandJson, TaskCompletionSource<string> tcs)
|
(string commandJson, TaskCompletionSource<string> tcs)
|
||||||
> commandQueue = new();
|
> 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 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)
|
public static bool FolderExists(string path)
|
||||||
{
|
{
|
||||||
|
|
@ -74,10 +105,14 @@ namespace UnityMcpBridge.Editor
|
||||||
|
|
||||||
try
|
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();
|
listener.Start();
|
||||||
isRunning = true;
|
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
|
// Assuming ListenerLoop and ProcessCommands are defined elsewhere
|
||||||
Task.Run(ListenerLoop);
|
Task.Run(ListenerLoop);
|
||||||
EditorApplication.update += ProcessCommands;
|
EditorApplication.update += ProcessCommands;
|
||||||
|
|
@ -87,7 +122,7 @@ namespace UnityMcpBridge.Editor
|
||||||
if (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
|
if (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
|
||||||
{
|
{
|
||||||
Debug.LogError(
|
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
|
else
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,7 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
private Vector2 scrollPosition;
|
private Vector2 scrollPosition;
|
||||||
private string pythonServerInstallationStatus = "Not Installed";
|
private string pythonServerInstallationStatus = "Not Installed";
|
||||||
private Color pythonServerInstallationStatusColor = Color.red;
|
private Color pythonServerInstallationStatusColor = Color.red;
|
||||||
private const int unityPort = 6400; // Hardcoded Unity port
|
private const int mcpPort = 6500; // MCP port (still hardcoded for MCP server)
|
||||||
private const int mcpPort = 6500; // Hardcoded MCP port
|
|
||||||
private readonly McpClients mcpClients = new();
|
private readonly McpClients mcpClients = new();
|
||||||
|
|
||||||
// Script validation settings
|
// Script validation settings
|
||||||
|
|
@ -45,6 +44,7 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
{
|
{
|
||||||
UpdatePythonServerInstallationStatus();
|
UpdatePythonServerInstallationStatus();
|
||||||
|
|
||||||
|
// Refresh bridge status
|
||||||
isUnityBridgeRunning = UnityMcpBridge.IsRunning;
|
isUnityBridgeRunning = UnityMcpBridge.IsRunning;
|
||||||
foreach (McpClient mcpClient in mcpClients.clients)
|
foreach (McpClient mcpClient in mcpClients.clients)
|
||||||
{
|
{
|
||||||
|
|
@ -210,11 +210,42 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
EditorGUILayout.Space(5);
|
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)
|
GUIStyle portStyle = new GUIStyle(EditorStyles.miniLabel)
|
||||||
{
|
{
|
||||||
fontSize = 11
|
fontSize = 11
|
||||||
};
|
};
|
||||||
EditorGUILayout.LabelField($"Ports: Unity {unityPort}, MCP {mcpPort}", portStyle);
|
EditorGUILayout.LabelField($"Ports: Unity {currentUnityPort}, MCP {mcpPort}", portStyle);
|
||||||
EditorGUILayout.Space(5);
|
EditorGUILayout.Space(5);
|
||||||
EditorGUILayout.EndVertical();
|
EditorGUILayout.EndVertical();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class ServerConfig:
|
||||||
mcp_port: int = 6500
|
mcp_port: int = 6500
|
||||||
|
|
||||||
# Connection settings
|
# 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
|
buffer_size: int = 16 * 1024 * 1024 # 16MB buffer
|
||||||
|
|
||||||
# Logging settings
|
# Logging settings
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
"""
|
||||||
|
Port discovery utility for Unity MCP Server.
|
||||||
|
Reads port configuration saved by Unity Bridge.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
logger = logging.getLogger("unity-mcp-server")
|
||||||
|
|
||||||
|
class PortDiscovery:
|
||||||
|
"""Handles port discovery from Unity Bridge registry"""
|
||||||
|
|
||||||
|
REGISTRY_FILE = "unity-mcp-port.json"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_registry_path() -> Path:
|
||||||
|
"""Get the path to the port registry file"""
|
||||||
|
return Path.home() / ".unity-mcp" / PortDiscovery.REGISTRY_FILE
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def discover_unity_port() -> int:
|
||||||
|
"""
|
||||||
|
Discover Unity port from registry file with fallback to default
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Port number to connect to
|
||||||
|
"""
|
||||||
|
registry_file = PortDiscovery.get_registry_path()
|
||||||
|
|
||||||
|
if registry_file.exists():
|
||||||
|
try:
|
||||||
|
with open(registry_file, 'r') as f:
|
||||||
|
port_config = json.load(f)
|
||||||
|
|
||||||
|
unity_port = port_config.get('unity_port')
|
||||||
|
if unity_port and isinstance(unity_port, int):
|
||||||
|
logger.info(f"Discovered Unity port from registry: {unity_port}")
|
||||||
|
return unity_port
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not read port registry: {e}")
|
||||||
|
|
||||||
|
# Fallback to default port
|
||||||
|
logger.info("No port registry found, using default port 6400")
|
||||||
|
return 6400
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_port_config() -> Optional[dict]:
|
||||||
|
"""
|
||||||
|
Get the full port configuration from registry
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Port configuration dict or None if not found
|
||||||
|
"""
|
||||||
|
registry_file = PortDiscovery.get_registry_path()
|
||||||
|
|
||||||
|
if not registry_file.exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(registry_file, 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not read port configuration: {e}")
|
||||||
|
return None
|
||||||
|
|
@ -4,6 +4,7 @@ import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
from config import config
|
from config import config
|
||||||
|
from port_discovery import PortDiscovery
|
||||||
|
|
||||||
# Configure logging using settings from config
|
# Configure logging using settings from config
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
|
|
@ -16,9 +17,14 @@ logger = logging.getLogger("unity-mcp-server")
|
||||||
class UnityConnection:
|
class UnityConnection:
|
||||||
"""Manages the socket connection to the Unity Editor."""
|
"""Manages the socket connection to the Unity Editor."""
|
||||||
host: str = config.unity_host
|
host: str = config.unity_host
|
||||||
port: int = config.unity_port
|
port: int = None # Will be set dynamically
|
||||||
sock: socket.socket = None # Socket for Unity communication
|
sock: socket.socket = None # Socket for Unity communication
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
"""Set port from discovery if not explicitly provided"""
|
||||||
|
if self.port is None:
|
||||||
|
self.port = PortDiscovery.discover_unity_port()
|
||||||
|
|
||||||
def connect(self) -> bool:
|
def connect(self) -> bool:
|
||||||
"""Establish a connection to the Unity Editor."""
|
"""Establish a connection to the Unity Editor."""
|
||||||
if self.sock:
|
if self.sock:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue