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 commandJson, TaskCompletionSource<string> tcs)
|
||||
> 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 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)
|
||||
{
|
||||
|
|
@ -74,10 +105,14 @@ namespace UnityMcpBridge.Editor
|
|||
|
||||
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();
|
||||
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
|
||||
Task.Run(ListenerLoop);
|
||||
EditorApplication.update += ProcessCommands;
|
||||
|
|
@ -87,7 +122,7 @@ namespace UnityMcpBridge.Editor
|
|||
if (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
|
||||
{
|
||||
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
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ namespace UnityMcpBridge.Editor.Windows
|
|||
private Vector2 scrollPosition;
|
||||
private string pythonServerInstallationStatus = "Not Installed";
|
||||
private Color pythonServerInstallationStatusColor = Color.red;
|
||||
private const int unityPort = 6400; // Hardcoded Unity port
|
||||
private const int mcpPort = 6500; // Hardcoded MCP port
|
||||
private const int mcpPort = 6500; // MCP port (still hardcoded for MCP server)
|
||||
private readonly McpClients mcpClients = new();
|
||||
|
||||
// Script validation settings
|
||||
|
|
@ -45,6 +44,7 @@ namespace UnityMcpBridge.Editor.Windows
|
|||
{
|
||||
UpdatePythonServerInstallationStatus();
|
||||
|
||||
// Refresh bridge status
|
||||
isUnityBridgeRunning = UnityMcpBridge.IsRunning;
|
||||
foreach (McpClient mcpClient in mcpClients.clients)
|
||||
{
|
||||
|
|
@ -210,11 +210,42 @@ namespace UnityMcpBridge.Editor.Windows
|
|||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
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)
|
||||
{
|
||||
fontSize = 11
|
||||
};
|
||||
EditorGUILayout.LabelField($"Ports: Unity {unityPort}, MCP {mcpPort}", portStyle);
|
||||
EditorGUILayout.LabelField($"Ports: Unity {currentUnityPort}, MCP {mcpPort}", portStyle);
|
||||
EditorGUILayout.Space(5);
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class ServerConfig:
|
|||
mcp_port: int = 6500
|
||||
|
||||
# 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
|
||||
|
||||
# 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 typing import Dict, Any
|
||||
from config import config
|
||||
from port_discovery import PortDiscovery
|
||||
|
||||
# Configure logging using settings from config
|
||||
logging.basicConfig(
|
||||
|
|
@ -16,8 +17,13 @@ logger = logging.getLogger("unity-mcp-server")
|
|||
class UnityConnection:
|
||||
"""Manages the socket connection to the Unity Editor."""
|
||||
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
|
||||
|
||||
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:
|
||||
"""Establish a connection to the Unity Editor."""
|
||||
|
|
|
|||
Loading…
Reference in New Issue