using System;
using System.Collections.Generic;
using UnityEngine;
namespace LocalMcp.UnityServer
{
/// <summary>
/// Manages Unity log streaming and history for MCP clients.
/// Captures Debug.Log, Debug.LogWarning, and Debug.LogError messages.
/// </summary>
public class McpLogStream : IDisposable
{
private McpSession session;
private List<LogEntry> logHistory = new List<LogEntry>();
private bool isStreaming = false;
private const int MAX_LOG_HISTORY = 1000;
private string levelFilter = "all";
public McpLogStream(McpSession mcpSession)
{
session = mcpSession;
Application.logMessageReceived += HandleLog;
}
/// <summary>
/// Enables or disables log streaming to the client.
/// </summary>
public string EnableStream(string paramsJson, object id)
{
try
{
var parameters = JsonUtility.FromJson<LogStreamParams>(paramsJson);
isStreaming = parameters.enable;
levelFilter = string.IsNullOrEmpty(parameters.level) ? "all" : parameters.level;
string status = isStreaming ? "streaming" : "stopped";
string message = isStreaming ? "Log stream started" : "Log stream stopped";
var response = JsonRpcResponse.Success(new { status, message, level = levelFilter }, id);
return response.ToJson();
}
catch (Exception e)
{
return JsonRpcResponseHelper.ErrorMessage($"Failed to enable stream: {e.Message}", id);
}
}
/// <summary>
/// Returns log history filtered by level and count.
/// </summary>
public string GetHistory(string paramsJson, object id)
{
try
{
var parameters = JsonUtility.FromJson<LogHistoryParams>(paramsJson);
int count = parameters.count > 0 ? Math.Min(parameters.count, MAX_LOG_HISTORY) : 50;
string level = string.IsNullOrEmpty(parameters.level) ? "all" : parameters.level;
List<LogEntry> filteredLogs = new List<LogEntry>();
lock (logHistory)
{
foreach (var log in logHistory)
{
if (ShouldIncludeLog(log.type, level))
{
filteredLogs.Add(log);
if (filteredLogs.Count >= count)
{
break;
}
}
}
}
var response = JsonRpcResponse.Success(new
{
logs = filteredLogs,
total = filteredLogs.Count
}, id);
return response.ToJson();
}
catch (Exception e)
{
return JsonRpcResponseHelper.ErrorMessage($"Failed to get history: {e.Message}", id);
}
}
/// <summary>
/// Handles Unity log messages and streams them to clients.
/// </summary>
private void HandleLog(string message, string stackTrace, LogType type)
{
try
{
var entry = new LogEntry
{
timestamp = DateTime.UtcNow.ToString("o"),
type = type,
message = message,
stackTrace = stackTrace
};
// Add to history
lock (logHistory)
{
logHistory.Insert(0, entry); // Insert at beginning for most recent first
if (logHistory.Count > MAX_LOG_HISTORY)
{
logHistory.RemoveAt(logHistory.Count - 1);
}
}
// Stream to client if enabled
if (isStreaming && ShouldIncludeLog(type, levelFilter))
{
SendLogNotification(entry);
}
}
catch (Exception e)
{
Debug.LogError($"[MCP] Error handling log: {e.Message}");
}
}
/// <summary>
/// Sends a log notification to the client.
/// </summary>
private void SendLogNotification(LogEntry entry)
{
try
{
// Create a notification (no id field for notifications in JSON-RPC)
var notification = new
{
jsonrpc = "2.0",
method = "unity.log.notify",
@params = entry
};
string json = JsonUtility.ToJson(notification);
session.SendMessage(json);
}
catch (Exception e)
{
Debug.LogError($"[MCP] Error sending log notification: {e.Message}");
}
}
/// <summary>
/// Determines if a log should be included based on level filter.
/// </summary>
private bool ShouldIncludeLog(LogType logType, string level)
{
if (level == "all")
{
return true;
}
else if (level == "warning")
{
return logType == LogType.Warning || logType == LogType.Error || logType == LogType.Assert || logType == LogType.Exception;
}
else if (level == "error")
{
return logType == LogType.Error || logType == LogType.Assert || logType == LogType.Exception;
}
return true;
}
/// <summary>
/// Cleans up resources.
/// </summary>
public void Dispose()
{
Application.logMessageReceived -= HandleLog;
isStreaming = false;
logHistory.Clear();
}
}
/// <summary>
/// Parameters for log stream enable/disable.
/// </summary>
[Serializable]
public class LogStreamParams
{
public bool enable = true;
public string level = "all"; // "all", "warning", "error"
}
/// <summary>
/// Parameters for log history retrieval.
/// </summary>
[Serializable]
public class LogHistoryParams
{
public int count = 50;
public string level = "all";
}
}