using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
namespace LocalMcp.UnityServer
{
/// <summary>
/// WebSocket server that runs inside Unity Editor and accepts MCP (Model Context Protocol) connections.
/// Listens on ws://localhost:5050 by default.
/// </summary>
public class UnityMcpServer
{
private TcpListener tcpListener;
private Thread listenerThread;
private bool isRunning = false;
private int port = 5050;
private List<McpSession> activeSessions = new List<McpSession>();
/// <summary>
/// Singleton instance of the MCP server.
/// </summary>
public static UnityMcpServer Instance { get; private set; }
/// <summary>
/// Gets whether the server is currently running.
/// </summary>
public bool IsRunning => isRunning;
public UnityMcpServer()
{
Instance = this;
}
/// <summary>
/// Starts the WebSocket server on the specified port.
/// </summary>
/// <param name="serverPort">Port number to listen on (default: 5050)</param>
public void StartServer(int serverPort = 5050)
{
if (isRunning)
{
Debug.LogWarning("[MCP] Server is already running");
return;
}
port = serverPort;
isRunning = true;
listenerThread = new Thread(ServerLoop);
listenerThread.IsBackground = true;
listenerThread.Start();
Debug.Log($"[MCP] Server started on ws://localhost:{port}");
}
/// <summary>
/// Stops the WebSocket server and closes all active connections.
/// </summary>
public void StopServer()
{
if (!isRunning)
{
return;
}
isRunning = false;
// Close all active sessions
lock (activeSessions)
{
foreach (var session in activeSessions)
{
session.Close();
}
activeSessions.Clear();
}
// Stop the TCP listener
if (tcpListener != null)
{
tcpListener.Stop();
tcpListener = null;
}
// Wait for listener thread to finish
if (listenerThread != null && listenerThread.IsAlive)
{
listenerThread.Join(1000);
listenerThread = null;
}
Debug.Log("[MCP] Server stopped");
}
private void ServerLoop()
{
try
{
tcpListener = new TcpListener(IPAddress.Loopback, port);
tcpListener.Start();
Debug.Log($"[MCP] Server listening on port {port}");
while (isRunning)
{
if (tcpListener.Pending())
{
TcpClient client = tcpListener.AcceptTcpClient();
Debug.Log("[MCP] Client connected");
// Handle the client in a new session
McpSession session = new McpSession(client);
lock (activeSessions)
{
activeSessions.Add(session);
}
Thread sessionThread = new Thread(session.HandleClient);
sessionThread.IsBackground = true;
sessionThread.Start();
}
else
{
Thread.Sleep(100);
}
}
}
catch (ThreadAbortException)
{
// Silently handle thread abort during domain reload
// This is expected when Unity recompiles scripts
Thread.ResetAbort();
}
catch (Exception e)
{
// Only log unexpected errors
if (isRunning)
{
Debug.LogError($"[MCP] Server error: {e.Message}");
}
}
}
/// <summary>
/// Removes a session from the active sessions list.
/// </summary>
/// <param name="session">The session to remove</param>
internal void RemoveSession(McpSession session)
{
lock (activeSessions)
{
activeSessions.Remove(session);
}
Debug.Log($"[MCP] Session removed. Active sessions: {activeSessions.Count}");
}
/// <summary>
/// Gets the number of active connections.
/// </summary>
public int GetActiveConnectionCount()
{
lock (activeSessions)
{
return activeSessions.Count;
}
}
/// <summary>
/// Broadcasts a message to all connected clients.
/// </summary>
/// <param name="message">The message to broadcast</param>
public void BroadcastMessage(string message)
{
lock (activeSessions)
{
foreach (var session in activeSessions)
{
session.SendMessage(message);
}
}
}
/// <summary>
/// Cleanup method to be called when shutting down.
/// </summary>
public void Cleanup()
{
StopServer();
}
}
}