Unity-MCP AutoConnect

Autoconnect feature to prevent the port being taken by other applications.
main
Scriptwonder 2025-07-29 00:17:36 -04:00
parent 01a3d472af
commit 32e4b2642f
7 changed files with 356 additions and 9 deletions

View File

@ -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");
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a1b2c3d4e5f6789012345678901234ab
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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."""