using System;
using UnityEditor;
using UnityEngine;
namespace LocalMcp.UnityServer
{
/// <summary>
/// Automatically launches the MCP Server when Unity Editor starts.
/// </summary>
[InitializeOnLoad]
public static class McpEditorServerLauncher
{
private static UnityMcpServer wsServer;
private static UnityMcpHttpServer httpServer;
private static int actualWsPort;
private static int actualHttpPort;
static McpEditorServerLauncher()
{
EditorApplication.delayCall += LaunchServer;
EditorApplication.quitting += StopServer;
// Restart server after assembly reload (compilation)
UnityEditor.Compilation.CompilationPipeline.assemblyCompilationFinished += OnAssemblyCompilationFinished;
}
private static void OnAssemblyCompilationFinished(string assemblyPath, UnityEditor.Compilation.CompilerMessage[] messages)
{
// Check if it's our MCP server assembly
if (assemblyPath.Contains("LocalMcp.UnityServer.Editor"))
{
Debug.Log("[MCP] MCP Server assembly recompiled, restarting server...");
EditorApplication.delayCall += () =>
{
StopServer();
System.Threading.Thread.Sleep(500); // Brief delay to ensure cleanup
LaunchServer();
};
}
}
private static void LaunchServer()
{
// Check if server is already running
if (wsServer != null || httpServer != null)
{
return;
}
// UnityMainThreadDispatcher is automatically initialized via [InitializeOnLoadMethod]
// No need to check Instance - it's a static class that auto-initializes
// Initialize GlobalLogCapture (required for log history)
// Singleton pattern ensures Instance is never null
var logCapture = GlobalLogCapture.Instance;
// Load configuration from .unity-mcp-config.json
var config = McpServerConfig.Load();
// Allocate available ports with fallback
try
{
(actualWsPort, actualHttpPort) = McpPortManager.AllocatePairedPorts(
config.ports.websocket,
config.ports.http
);
}
catch (Exception e)
{
Debug.LogError($"[MCP] Failed to allocate ports: {e.Message}");
return;
}
// Start WebSocket server (for direct WebSocket connections)
wsServer = new UnityMcpServer();
wsServer.StartServer(actualWsPort);
// Start HTTP server (for MCP protocol bridge)
httpServer = new UnityMcpHttpServer();
httpServer.StartServer(actualHttpPort);
// Save runtime configuration for mcp-bridge to discover
SaveRuntimeConfig(config.projectName);
Debug.Log("[MCP] Servers auto-started on Unity Editor launch");
Debug.Log($"[MCP] WebSocket: ws://localhost:{actualWsPort}");
Debug.Log($"[MCP] HTTP API: http://localhost:{actualHttpPort}");
}
private static void SaveRuntimeConfig(string projectName)
{
var runtimeConfig = new McpRuntimeConfig
{
projectName = projectName,
websocketPort = actualWsPort,
httpPort = actualHttpPort,
processId = System.Diagnostics.Process.GetCurrentProcess().Id,
startTime = DateTime.Now.ToString("o"),
unityVersion = Application.unityVersion
};
McpRuntimeConfig.Save(runtimeConfig);
}
private static void StopServer()
{
if (wsServer != null)
{
wsServer.Cleanup();
wsServer = null;
}
if (httpServer != null)
{
httpServer.Cleanup();
httpServer = null;
}
// Delete runtime config file
McpRuntimeConfig.Delete();
// Cleanup GlobalLogCapture
GlobalLogCapture.Instance?.Cleanup();
}
[MenuItem("MCP/Server/Start", false, 0)]
private static void MenuStartServer()
{
if (wsServer == null && httpServer == null)
{
LaunchServer();
}
else
{
Debug.LogWarning("[MCP] Server is already running");
}
}
[MenuItem("MCP/Server/Stop", false, 1)]
private static void MenuStopServer()
{
if (wsServer != null || httpServer != null)
{
StopServer();
Debug.Log("[MCP] Server stopped manually");
}
else
{
Debug.LogWarning("[MCP] Server is not running");
}
}
[MenuItem("MCP/Server/Status", false, 2)]
private static void MenuServerStatus()
{
if (wsServer != null)
{
int connectionCount = wsServer.GetActiveConnectionCount();
Debug.Log($"[MCP] ✅ Server is RUNNING | Active connections: {connectionCount} | Ports: WS:{actualWsPort}, HTTP:{actualHttpPort}");
}
else
{
Debug.Log("[MCP] ⚠️ Server is NOT RUNNING | Use 'MCP > Start Server' to start it");
}
}
[MenuItem("MCP/Package Version", false, 100)]
private static void MenuPackageVersion()
{
string version = GetPackageVersion();
string message;
if (!string.IsNullOrEmpty(version))
{
message = $"Unity MCP Server\n\nCurrent Version: {version}\n\n" +
"Repository: github.com/dsgarage/CC2UniMCP";
}
else
{
message = "Unity MCP Server\n\nVersion information not available.\n\n" +
"Repository: github.com/dsgarage/CC2UniMCP";
}
EditorUtility.DisplayDialog("Package Version", message, "OK");
Debug.Log($"[MCP] Package Version: {version ?? "Unknown"}");
}
[MenuItem("MCP/Update Package", false, 101)]
private static void MenuUpdatePackage()
{
string currentVersion = GetPackageVersion();
string versionInfo = !string.IsNullOrEmpty(currentVersion)
? $"Current version: {currentVersion}\n\n"
: "";
bool confirmed = EditorUtility.DisplayDialog(
"Update Unity MCP Server",
versionInfo +
"This will update the Unity MCP Server package to the latest version from GitHub.\n\n" +
"The update process will:\n" +
"1. Fetch the latest version from the repository\n" +
"2. Update the package via Package Manager\n" +
"3. Restart the MCP Server automatically\n\n" +
"Do you want to continue?",
"Update",
"Cancel"
);
if (!confirmed)
{
Debug.Log("[MCP] Package update cancelled by user");
return;
}
Debug.Log("[MCP] Starting package update...");
Debug.Log($"[MCP] Current version: {currentVersion ?? "Unknown"}");
// Use PackageManager Client API to update the package
// Explicitly specify #main to ensure we get the latest release branch
UnityEditor.PackageManager.Client.Add("git@github.com:dsgarage/CC2UniMCP.git#main");
Debug.Log("[MCP] Package update requested. Unity will refresh when complete.");
Debug.Log("[MCP] Check Package Manager window for update progress.");
}
/// <summary>
/// Gets the current package version from package.json
/// </summary>
private static string GetPackageVersion()
{
try
{
// Use PackageInfo to get the correct package path
var listRequest = UnityEditor.PackageManager.Client.List(true, false);
// Wait for request to complete (synchronous for simplicity in menu)
while (!listRequest.IsCompleted)
{
System.Threading.Thread.Sleep(10);
}
if (listRequest.Status == UnityEditor.PackageManager.StatusCode.Success)
{
foreach (var package in listRequest.Result)
{
if (package.name == "com.local.mcp.unityserver")
{
// Return version from PackageInfo
return package.version;
}
}
}
// Fallback: try direct file path in multiple locations
string[] possiblePaths = new[]
{
"Packages/com.local.mcp.unityserver/package.json",
System.IO.Path.Combine(Application.dataPath, "../Packages/com.local.mcp.unityserver/package.json"),
System.IO.Path.Combine(Application.dataPath, "../Library/PackageCache/com.local.mcp.unityserver/package.json")
};
foreach (string path in possiblePaths)
{
if (System.IO.File.Exists(path))
{
string json = System.IO.File.ReadAllText(path);
var match = System.Text.RegularExpressions.Regex.Match(json, @"""version"":\s*""([^""]+)""");
if (match.Success)
{
return match.Groups[1].Value;
}
}
}
}
catch (System.Exception e)
{
Debug.LogWarning($"[MCP] Failed to read package version: {e.Message}");
}
return null;
}
}
}