From 8c92074367fc54814a84256914411bb363e50553 Mon Sep 17 00:00:00 2001 From: Vladimir Kuznetsov Date: Mon, 20 Oct 2025 13:49:53 +0700 Subject: [PATCH 1/5] feat: add recompile scripts tool --- Editor/Tools/AddPackageTool.cs | 3 - Editor/Tools/LoadSceneTool.cs | 1 - Editor/Tools/RecompileScriptsTool.cs | 183 +++++++++++++++++++++ Editor/Tools/RecompileScriptsTool.cs.meta | 3 + Editor/UnityBridge/McpUnityEditorWindow.cs | 8 + Editor/UnityBridge/McpUnityServer.cs | 4 + Server~/src/index.ts | 2 + Server~/src/tools/recompileScriptsTool.ts | 89 ++++++++++ 8 files changed, 289 insertions(+), 4 deletions(-) create mode 100644 Editor/Tools/RecompileScriptsTool.cs create mode 100644 Editor/Tools/RecompileScriptsTool.cs.meta create mode 100644 Server~/src/tools/recompileScriptsTool.ts diff --git a/Editor/Tools/AddPackageTool.cs b/Editor/Tools/AddPackageTool.cs index e48d5762..90d92c09 100644 --- a/Editor/Tools/AddPackageTool.cs +++ b/Editor/Tools/AddPackageTool.cs @@ -1,13 +1,10 @@ using System; -using System.Linq; using System.Collections.Generic; -using UnityEngine; using UnityEditor; using UnityEditor.PackageManager; using UnityEditor.PackageManager.Requests; using Newtonsoft.Json.Linq; using System.Threading.Tasks; -using McpUnity.Tools; using McpUnity.Unity; using McpUnity.Utils; diff --git a/Editor/Tools/LoadSceneTool.cs b/Editor/Tools/LoadSceneTool.cs index 500be436..df9676d3 100644 --- a/Editor/Tools/LoadSceneTool.cs +++ b/Editor/Tools/LoadSceneTool.cs @@ -1,5 +1,4 @@ using System; -using UnityEngine; using UnityEditor; using Newtonsoft.Json.Linq; using McpUnity.Unity; diff --git a/Editor/Tools/RecompileScriptsTool.cs b/Editor/Tools/RecompileScriptsTool.cs new file mode 100644 index 00000000..e45ad7b5 --- /dev/null +++ b/Editor/Tools/RecompileScriptsTool.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using McpUnity.Unity; +using McpUnity.Utils; +using Newtonsoft.Json.Linq; +using UnityEditor; +using UnityEditor.Compilation; +using UnityEngine; + +namespace McpUnity.Tools { + public class RecompileScriptsTool : McpToolBase + { + private readonly List _compilationLogs = new List(); + private TaskCompletionSource _completionSource; + private int _processedAssemblies = 0; + private bool _returnWithLogs = true; + private int _logsLimit = 100; + + public RecompileScriptsTool() + { + Name = "recompile_scripts"; + Description = "Recompiles all scripts in the Unity project. Use returnWithLogs parameter to control whether compilation logs are returned. Use logsLimit parameter to limit the number of logs returned when returnWithLogs is true."; + IsAsync = true; // Compilation is asynchronous + } + + /// + /// Execute the Recompile tool asynchronously + /// + /// Tool parameters as a JObject + /// TaskCompletionSource to set the result or exception + public override void ExecuteAsync(JObject parameters, TaskCompletionSource tcs) + { + _completionSource = tcs; + _compilationLogs.Clear(); + _processedAssemblies = 0; + + // Extract and store parameters + _returnWithLogs = GetBoolParameter(parameters, "returnWithLogs", true); + _logsLimit = Mathf.Clamp(GetIntParameter(parameters, "logsLimit", 100), 0, 1000); + + CompilationPipeline.assemblyCompilationFinished += OnAssemblyCompilationFinished; + CompilationPipeline.compilationFinished += OnCompilationFinished; + + if (EditorApplication.isCompiling == false) { + McpLogger.LogInfo($"Recompiling all scripts in the Unity project (logsLimit: {_logsLimit})"); + CompilationPipeline.RequestScriptCompilation(); + } + else { + McpLogger.LogInfo("Recompilation already in progress. Waiting for completion..."); + } + } + + private void OnAssemblyCompilationFinished(string assemblyPath, CompilerMessage[] messages) + { + _processedAssemblies++; + _compilationLogs.AddRange(messages); + } + + private void OnCompilationFinished(object _) + { + McpLogger.LogInfo($"Recompilation completed. Processed {_processedAssemblies} assemblies with {_compilationLogs.Count} compiler messages"); + + CompilationPipeline.compilationFinished -= OnCompilationFinished; + CompilationPipeline.assemblyCompilationFinished -= OnAssemblyCompilationFinished; + + try + { + // Format logs as JSON array similar to ConsoleLogsService + JArray logsArray = new JArray(); + + // Separate errors, warnings, and other messages + List errors = _compilationLogs.Where(m => m.type == CompilerMessageType.Error).ToList(); + List warnings = _compilationLogs.Where(m => m.type == CompilerMessageType.Warning).ToList(); + List others = _compilationLogs.Where(m => m.type != CompilerMessageType.Error && m.type != CompilerMessageType.Warning).ToList(); + + int errorCount = errors.Count; + int warningCount = warnings.Count; + + // Sort logs and apply logsLimit - prioritize errors if logsLimit is restrictive + IEnumerable sortedLogs; + if (!_returnWithLogs || _logsLimit <= 0) + { + sortedLogs = Enumerable.Empty(); + } + else + { + // Always include all errors, then warnings, then other messages up to the logsLimit + var selectedLogs = errors.ToList(); + var remainingSlots = _logsLimit - selectedLogs.Count; + + if (remainingSlots > 0) + { + selectedLogs.AddRange(warnings.Take(remainingSlots)); + remainingSlots = _logsLimit - selectedLogs.Count; + } + + if (remainingSlots > 0) + { + selectedLogs.AddRange(others.Take(remainingSlots)); + } + + sortedLogs = selectedLogs; + } + + foreach (var message in sortedLogs) + { + var logObject = new JObject + { + ["message"] = message.message, + ["type"] = message.type.ToString(), + ["timestamp"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + }; + + // Add file information if available + if (!string.IsNullOrEmpty(message.file)) + { + logObject["file"] = message.file; + logObject["line"] = message.line; + logObject["column"] = message.column; + } + + logsArray.Add(logObject); + } + + bool hasErrors = errorCount > 0; + string summaryMessage = hasErrors + ? $"Recompilation completed with {errorCount} error(s) and {warningCount} warning(s)" + : $"Successfully recompiled all scripts with {warningCount} warning(s)"; + + summaryMessage += $" (returnWithLogs: {_returnWithLogs}, logsLimit: {_logsLimit})"; + + var response = new JObject + { + ["success"] = true, + ["type"] = "text", + ["message"] = summaryMessage, + ["logs"] = logsArray + }; + + McpLogger.LogInfo($"Setting recompilation result: success={!hasErrors}, errors={errorCount}, warnings={warningCount}"); + _completionSource.SetResult(response); + } + catch (Exception ex) + { + McpLogger.LogError($"Error creating recompilation response: {ex.Message}\n{ex.StackTrace}"); + _completionSource.SetResult(McpUnitySocketHandler.CreateErrorResponse( + $"Error creating recompilation response: {ex.Message}", + "response_creation_error" + )); + } + } + + /// + /// Helper method to safely extract integer parameters with default values + /// + /// JObject containing parameters + /// Parameter key to extract + /// Default value if parameter is missing or invalid + /// Extracted integer value or default + private static int GetIntParameter(JObject parameters, string key, int defaultValue) + { + if (parameters?[key] != null && int.TryParse(parameters[key].ToString(), out int value)) + return value; + return defaultValue; + } + + /// + /// Helper method to safely extract boolean parameters with default values + /// + /// JObject containing parameters + /// Parameter key to extract + /// Default value if parameter is missing or invalid + /// Extracted boolean value or default + private static bool GetBoolParameter(JObject parameters, string key, bool defaultValue) + { + if (parameters?[key] != null && bool.TryParse(parameters[key].ToString(), out bool value)) + return value; + return defaultValue; + } + } +} \ No newline at end of file diff --git a/Editor/Tools/RecompileScriptsTool.cs.meta b/Editor/Tools/RecompileScriptsTool.cs.meta new file mode 100644 index 00000000..98c53a52 --- /dev/null +++ b/Editor/Tools/RecompileScriptsTool.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9db4c0c982944b9da2e14745cf799f99 +timeCreated: 1758591273 \ No newline at end of file diff --git a/Editor/UnityBridge/McpUnityEditorWindow.cs b/Editor/UnityBridge/McpUnityEditorWindow.cs index b53a2226..d955d5a2 100644 --- a/Editor/UnityBridge/McpUnityEditorWindow.cs +++ b/Editor/UnityBridge/McpUnityEditorWindow.cs @@ -401,6 +401,14 @@ private void DrawHelpTab() WrappedLabel("Add the Player prefab from my project to the current scene", new GUIStyle(EditorStyles.miniLabel) { fontStyle = FontStyle.Italic }); EditorGUILayout.EndVertical(); + // recompile_scripts + WrappedLabel("recompile_scripts", EditorStyles.boldLabel); + WrappedLabel("Recompiles all scripts in the Unity project"); + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + EditorGUILayout.LabelField("Example prompt:", EditorStyles.miniLabel); + WrappedLabel("Recompile scripts in my project", new GUIStyle(EditorStyles.miniLabel) { fontStyle = FontStyle.Italic }); + EditorGUILayout.EndVertical(); + EditorGUILayout.EndVertical(); // Available Resources section diff --git a/Editor/UnityBridge/McpUnityServer.cs b/Editor/UnityBridge/McpUnityServer.cs index d623c2db..4fd122a0 100644 --- a/Editor/UnityBridge/McpUnityServer.cs +++ b/Editor/UnityBridge/McpUnityServer.cs @@ -265,6 +265,10 @@ private void RegisterTools() // Register LoadSceneTool LoadSceneTool loadSceneTool = new LoadSceneTool(); _tools.Add(loadSceneTool.Name, loadSceneTool); + + // Register RecompileScriptsTool + RecompileScriptsTool recompileScriptsTool = new RecompileScriptsTool(); + _tools.Add(recompileScriptsTool.Name, recompileScriptsTool); } /// diff --git a/Server~/src/index.ts b/Server~/src/index.ts index f5c12c09..3ff840c8 100644 --- a/Server~/src/index.ts +++ b/Server~/src/index.ts @@ -16,6 +16,7 @@ import { registerUpdateGameObjectTool } from './tools/updateGameObjectTool.js'; import { registerCreatePrefabTool } from './tools/createPrefabTool.js'; import { registerDeleteSceneTool } from './tools/deleteSceneTool.js'; import { registerLoadSceneTool } from './tools/loadSceneTool.js'; +import { registerRecompileScriptsTool } from './tools/recompileScriptsTool.js'; import { registerGetMenuItemsResource } from './resources/getMenuItemResource.js'; import { registerGetConsoleLogsResource } from './resources/getConsoleLogsResource.js'; import { registerGetHierarchyResource } from './resources/getScenesHierarchyResource.js'; @@ -63,6 +64,7 @@ registerCreatePrefabTool(server, mcpUnity, toolLogger); registerCreateSceneTool(server, mcpUnity, toolLogger); registerDeleteSceneTool(server, mcpUnity, toolLogger); registerLoadSceneTool(server, mcpUnity, toolLogger); +registerRecompileScriptsTool(server, mcpUnity, toolLogger); // Register all resources into the MCP server registerGetTestsResource(server, mcpUnity, resourceLogger); diff --git a/Server~/src/tools/recompileScriptsTool.ts b/Server~/src/tools/recompileScriptsTool.ts new file mode 100644 index 00000000..4b121903 --- /dev/null +++ b/Server~/src/tools/recompileScriptsTool.ts @@ -0,0 +1,89 @@ +import * as z from 'zod'; +import { Logger } from '../utils/logger.js'; +import { McpUnity } from '../unity/mcpUnity.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { McpUnityError, ErrorType } from '../utils/errors.js'; +import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; + +// Constants for the tool +const toolName = 'recompile_scripts'; +const toolDescription = 'Recompiles all scripts in the Unity project.'; +const paramsSchema = z.object({ + returnWithLogs: z.boolean().optional().default(true).describe('Whether to return compilation logs'), + logsLimit: z.number().int().min(0).max(1000).optional().default(100).describe('Maximum number of compilation logs to return') +}); + +/** + * Creates and registers the Recompile Scripts tool with the MCP server + * This tool allows recompiling all scripts in the Unity project + * + * @param server The MCP server instance to register with + * @param mcpUnity The McpUnity instance to communicate with Unity + * @param logger The logger instance for diagnostic information + */ +export function registerRecompileScriptsTool(server: McpServer, mcpUnity: McpUnity, logger: Logger) { + logger.info(`Registering tool: ${toolName}`); + + // Register this tool with the MCP server + server.tool( + toolName, + toolDescription, + paramsSchema.shape, + async (params: any) => { + try { + logger.info(`Executing tool: ${toolName}`, params); + const result = await toolHandler(mcpUnity, params); + logger.info(`Tool execution successful: ${toolName}`); + return result; + } catch (error) { + logger.error(`Tool execution failed: ${toolName}`, error); + throw error; + } + } + ); +} + +/** + * Handles recompile scripts tool requests + * + * @param mcpUnity The McpUnity instance to communicate with Unity + * @param params The parameters for the tool + * @returns A promise that resolves to the tool execution result + * @throws McpUnityError if the request to Unity fails + */ +async function toolHandler(mcpUnity: McpUnity, params: z.infer): Promise { + // Validate and prepare parameters + const returnWithLogs = params.returnWithLogs ?? true; + const logsLimit = Math.max(0, Math.min(1000, params.logsLimit || 100)); + + // Send to Unity with validated parameters + const response = await mcpUnity.sendRequest({ + method: toolName, + params: { + returnWithLogs, + logsLimit + } + }); + + if (!response.success) { + throw new McpUnityError( + ErrorType.TOOL_EXECUTION, + response.message || `Failed to recompile scripts` + ); + } + + return { + content: [ + { + type: 'text', + text: response.message + }, + { + type: 'text', + text: JSON.stringify({ + logs: response.logs + }, null, 2) + } + ] + }; +} From 90ed662f45b14b4a7a2ffc4ac75bcd1a255120cb Mon Sep 17 00:00:00 2001 From: Vladimir Kuznetsov Date: Sun, 26 Oct 2025 14:53:42 +0700 Subject: [PATCH 2/5] fix: restart MCP server when Unity Editor is unfocused during domain reload --- Editor/UnityBridge/McpUnityServer.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Editor/UnityBridge/McpUnityServer.cs b/Editor/UnityBridge/McpUnityServer.cs index 4fd122a0..f7fd0d90 100644 --- a/Editor/UnityBridge/McpUnityServer.cs +++ b/Editor/UnityBridge/McpUnityServer.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Threading; -using UnityEngine; using UnityEditor; using McpUnity.Tools; using McpUnity.Resources; @@ -9,8 +8,8 @@ using McpUnity.Utils; using WebSocketSharp.Server; using System.IO; -using System.Diagnostics; using System.Net.Sockets; +using UnityEditor.Callbacks; namespace McpUnity.Unity { @@ -30,16 +29,15 @@ public class McpUnityServer : IDisposable private CancellationTokenSource _cts; private TestRunnerService _testRunnerService; private ConsoleLogsService _consoleLogsService; - + /// - /// Static constructor that gets called when Unity loads due to InitializeOnLoad attribute + /// Called after every domain reload /// - static McpUnityServer() + [DidReloadScripts] + private static void AfterReload() { - EditorApplication.delayCall += () => { - // Ensure Instance is created and hooks are set up after initial domain load - var currentInstance = Instance; - }; + // Ensure Instance is created and hooks are set up after initial domain load + var currentInstance = Instance; } /// From bbec6a4f03801b3b5eb44f52562f3caebf11c52a Mon Sep 17 00:00:00 2001 From: Vladimir Kuznetsov Date: Sun, 26 Oct 2025 19:39:07 +0700 Subject: [PATCH 3/5] docs(readme): mention recompile scripts tool --- README-ja.md | 3 +++ README.md | 3 +++ README_zh-CN.md | 3 +++ 3 files changed, 9 insertions(+) diff --git a/README-ja.md b/README-ja.md index f64a8669..d99c8906 100644 --- a/README-ja.md +++ b/README-ja.md @@ -86,6 +86,9 @@ MCP Unityは、Unityの`Library/PackedCache`フォルダーをワークスペー - `create_prefab`: プレハブを作成し、オプションでMonoBehaviourスクリプトとシリアライズされたフィールド値を設定 > **例:** "'PlayerController'スクリプトから'Player'という名前のプレハブを作成" +- `recompile_scripts`: Unityプロジェクト内のすべてのスクリプトを再コンパイル + > **例:** "Unityプロジェクト内のすべてのスクリプトを再コンパイル" + ### MCPサーバーリソース - `unity://menu-items`: `execute_menu_item`ツールを容易にするために、Unityエディターで利用可能なすべてのメニュー項目のリストを取得 diff --git a/README.md b/README.md index db28e90b..30547bdd 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,9 @@ The following tools are available for manipulating and querying Unity scenes and - `create_prefab`: Creates a prefab with optional MonoBehaviour script and serialized field values > **Example prompt:** "Create a prefab named 'Player' from the 'PlayerController' script" +- `recompile_scripts`: Recompiles all scripts in the Unity project + > **Example prompt:** "Recompile scripts in my Unity project" + ### MCP Server Resources - `unity://menu-items`: Retrieves a list of all available menu items in the Unity Editor to facilitate `execute_menu_item` tool diff --git a/README_zh-CN.md b/README_zh-CN.md index c5a70fbf..9b6f5a1d 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -87,6 +87,9 @@ MCP Unity 通过将 Unity `Library/PackedCache` 文件夹添加到您的工作 - `create_prefab`: 创建预制体,并可选择添加 MonoBehaviour 脚本和设置序列化字段值 > **示例提示:** "从 'PlayerController' 脚本创建一个名为 'Player' 的预制体" +- `recompile_scripts`: 重新编译 Unity 项目中的所有脚本 + > **示例提示:** "重新编译我 Unity 项目中的所有脚本" + ### MCP 服务器资源 - `unity://menu-items`: 获取 Unity 编辑器中所有可用的菜单项列表,以方便 `execute_menu_item` 工具 From 4144bf9f00984ab55abbd3ac022df2136e5c0c0e Mon Sep 17 00:00:00 2001 From: Vladimir Kuznetsov Date: Mon, 27 Oct 2025 01:01:10 +0700 Subject: [PATCH 4/5] feat: support concurrent recompilation requests --- Editor/Tools/RecompileScriptsTool.cs | 159 +++++++++++++++++++-------- 1 file changed, 112 insertions(+), 47 deletions(-) diff --git a/Editor/Tools/RecompileScriptsTool.cs b/Editor/Tools/RecompileScriptsTool.cs index e45ad7b5..18cf7738 100644 --- a/Editor/Tools/RecompileScriptsTool.cs +++ b/Editor/Tools/RecompileScriptsTool.cs @@ -10,18 +10,32 @@ using UnityEngine; namespace McpUnity.Tools { + /// + /// Tool to recompile all scripts in the Unity project + /// public class RecompileScriptsTool : McpToolBase { + private class CompilationRequest { + public readonly bool ReturnWithLogs; + public readonly int LogsLimit; + public readonly TaskCompletionSource CompletionSource; + + public CompilationRequest(bool returnWithLogs, int logsLimit, TaskCompletionSource completionSource) + { + ReturnWithLogs = returnWithLogs; + LogsLimit = logsLimit; + CompletionSource = completionSource; + } + } + + private readonly List _pendingRequests = new List(); private readonly List _compilationLogs = new List(); - private TaskCompletionSource _completionSource; private int _processedAssemblies = 0; - private bool _returnWithLogs = true; - private int _logsLimit = 100; public RecompileScriptsTool() { Name = "recompile_scripts"; - Description = "Recompiles all scripts in the Unity project. Use returnWithLogs parameter to control whether compilation logs are returned. Use logsLimit parameter to limit the number of logs returned when returnWithLogs is true."; + Description = "Recompiles all scripts in the Unity project"; IsAsync = true; // Compilation is asynchronous } @@ -32,68 +46,119 @@ public RecompileScriptsTool() /// TaskCompletionSource to set the result or exception public override void ExecuteAsync(JObject parameters, TaskCompletionSource tcs) { - _completionSource = tcs; - _compilationLogs.Clear(); - _processedAssemblies = 0; - // Extract and store parameters - _returnWithLogs = GetBoolParameter(parameters, "returnWithLogs", true); - _logsLimit = Mathf.Clamp(GetIntParameter(parameters, "logsLimit", 100), 0, 1000); - - CompilationPipeline.assemblyCompilationFinished += OnAssemblyCompilationFinished; - CompilationPipeline.compilationFinished += OnCompilationFinished; - - if (EditorApplication.isCompiling == false) { - McpLogger.LogInfo($"Recompiling all scripts in the Unity project (logsLimit: {_logsLimit})"); - CompilationPipeline.RequestScriptCompilation(); + var returnWithLogs = GetBoolParameter(parameters, "returnWithLogs", true); + var logsLimit = Mathf.Clamp(GetIntParameter(parameters, "logsLimit", 100), 0, 1000); + var request = new CompilationRequest(returnWithLogs, logsLimit, tcs); + + bool hasActiveRequest = false; + lock (_pendingRequests) + { + hasActiveRequest = _pendingRequests.Count > 0; + _pendingRequests.Add(request); } - else { + + if (hasActiveRequest) + { McpLogger.LogInfo("Recompilation already in progress. Waiting for completion..."); + return; + } + + // On first request, initialize compilation listeners and start compilation + StartCompilationTracking(); + + if (EditorApplication.isCompiling == false) + { + McpLogger.LogInfo($"Recompiling all scripts in the Unity project (logsLimit: {logsLimit})"); + CompilationPipeline.RequestScriptCompilation(); } } + /// + /// Subscribe to compilation events, reset tracked state + /// + private void StartCompilationTracking() + { + _compilationLogs.Clear(); + _processedAssemblies = 0; + CompilationPipeline.assemblyCompilationFinished += OnAssemblyCompilationFinished; + CompilationPipeline.compilationFinished += OnCompilationFinished; + } + + /// + /// Unsubscribe from compilation events + /// + private void StopCompilationTracking() + { + CompilationPipeline.assemblyCompilationFinished -= OnAssemblyCompilationFinished; + CompilationPipeline.compilationFinished -= OnCompilationFinished; + } + + /// + /// Record compilation logs for every single assembly + /// + /// + /// private void OnAssemblyCompilationFinished(string assemblyPath, CompilerMessage[] messages) { _processedAssemblies++; _compilationLogs.AddRange(messages); } + /// + /// Complete all pending requests and stop tracking + /// + /// private void OnCompilationFinished(object _) { McpLogger.LogInfo($"Recompilation completed. Processed {_processedAssemblies} assemblies with {_compilationLogs.Count} compiler messages"); - CompilationPipeline.compilationFinished -= OnCompilationFinished; - CompilationPipeline.assemblyCompilationFinished -= OnAssemblyCompilationFinished; + // Separate errors, warnings, and other messages + List errors = _compilationLogs.Where(m => m.type == CompilerMessageType.Error).ToList(); + List warnings = _compilationLogs.Where(m => m.type == CompilerMessageType.Warning).ToList(); + List others = _compilationLogs.Where(m => m.type != CompilerMessageType.Error && m.type != CompilerMessageType.Warning).ToList(); + + // Stop tracking before completing requests + StopCompilationTracking(); + + // Complete all requests received before compilation end, the next received request will start a new compilation + List requestsToComplete = new List(); + + lock (_pendingRequests) + { + requestsToComplete.AddRange(_pendingRequests); + _pendingRequests.Clear(); + } - try + foreach (var request in requestsToComplete) { - // Format logs as JSON array similar to ConsoleLogsService - JArray logsArray = new JArray(); - - // Separate errors, warnings, and other messages - List errors = _compilationLogs.Where(m => m.type == CompilerMessageType.Error).ToList(); - List warnings = _compilationLogs.Where(m => m.type == CompilerMessageType.Warning).ToList(); - List others = _compilationLogs.Where(m => m.type != CompilerMessageType.Error && m.type != CompilerMessageType.Warning).ToList(); + CompleteRequest(request, errors, warnings, others); + } + } - int errorCount = errors.Count; - int warningCount = warnings.Count; + /// + /// Process a completed compilation request + /// + private static void CompleteRequest(CompilationRequest request, List errors, List warnings, List others) + { + try { + JArray logsArray = new JArray(); // Sort logs and apply logsLimit - prioritize errors if logsLimit is restrictive IEnumerable sortedLogs; - if (!_returnWithLogs || _logsLimit <= 0) + if (!request.ReturnWithLogs || request.LogsLimit <= 0) { sortedLogs = Enumerable.Empty(); } - else - { + else { // Always include all errors, then warnings, then other messages up to the logsLimit var selectedLogs = errors.ToList(); - var remainingSlots = _logsLimit - selectedLogs.Count; + var remainingSlots = request.LogsLimit - selectedLogs.Count; if (remainingSlots > 0) { selectedLogs.AddRange(warnings.Take(remainingSlots)); - remainingSlots = _logsLimit - selectedLogs.Count; + remainingSlots = request.LogsLimit - selectedLogs.Count; } if (remainingSlots > 0) @@ -106,7 +171,7 @@ private void OnCompilationFinished(object _) foreach (var message in sortedLogs) { - var logObject = new JObject + var logObject = new JObject { ["message"] = message.message, ["type"] = message.type.ToString(), @@ -124,14 +189,14 @@ private void OnCompilationFinished(object _) logsArray.Add(logObject); } - bool hasErrors = errorCount > 0; + bool hasErrors = errors.Count > 0; string summaryMessage = hasErrors - ? $"Recompilation completed with {errorCount} error(s) and {warningCount} warning(s)" - : $"Successfully recompiled all scripts with {warningCount} warning(s)"; - - summaryMessage += $" (returnWithLogs: {_returnWithLogs}, logsLimit: {_logsLimit})"; + ? $"Recompilation completed with {errors.Count} error(s) and {warnings.Count} warning(s)" + : $"Successfully recompiled all scripts with {warnings.Count} warning(s)"; + + summaryMessage += $" (returnWithLogs: {request.ReturnWithLogs}, logsLimit: {request.LogsLimit})"; - var response = new JObject + var response = new JObject { ["success"] = true, ["type"] = "text", @@ -139,13 +204,13 @@ private void OnCompilationFinished(object _) ["logs"] = logsArray }; - McpLogger.LogInfo($"Setting recompilation result: success={!hasErrors}, errors={errorCount}, warnings={warningCount}"); - _completionSource.SetResult(response); - } - catch (Exception ex) + McpLogger.LogInfo($"Setting recompilation result: success={!hasErrors}, errors={errors.Count}, warnings={warnings.Count}"); + request.CompletionSource.SetResult(response); + } + catch (Exception ex) { McpLogger.LogError($"Error creating recompilation response: {ex.Message}\n{ex.StackTrace}"); - _completionSource.SetResult(McpUnitySocketHandler.CreateErrorResponse( + request.CompletionSource.SetResult(McpUnitySocketHandler.CreateErrorResponse( $"Error creating recompilation response: {ex.Message}", "response_creation_error" )); From 0ef33d7dc178eccaf72973148580b5f92db6a230 Mon Sep 17 00:00:00 2001 From: Vladimir Kuznetsov Date: Mon, 27 Oct 2025 22:48:58 +0700 Subject: [PATCH 5/5] fix: apply logsLimit to compilation errors, remove useless timestamps --- Editor/Tools/RecompileScriptsTool.cs | 131 +++++++++++---------------- 1 file changed, 54 insertions(+), 77 deletions(-) diff --git a/Editor/Tools/RecompileScriptsTool.cs b/Editor/Tools/RecompileScriptsTool.cs index 18cf7738..5ea87ab0 100644 --- a/Editor/Tools/RecompileScriptsTool.cs +++ b/Editor/Tools/RecompileScriptsTool.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using McpUnity.Unity; using McpUnity.Utils; using Newtonsoft.Json.Linq; using UnityEditor; @@ -15,7 +14,8 @@ namespace McpUnity.Tools { /// public class RecompileScriptsTool : McpToolBase { - private class CompilationRequest { + private class CompilationRequest + { public readonly bool ReturnWithLogs; public readonly int LogsLimit; public readonly TaskCompletionSource CompletionSource; @@ -28,6 +28,22 @@ public CompilationRequest(bool returnWithLogs, int logsLimit, TaskCompletionSour } } + private class CompilationResult + { + public readonly List SortedLogs; + public readonly int WarningsCount; + public readonly int ErrorsCount; + + public bool HasErrors => ErrorsCount > 0; + + public CompilationResult(List sortedLogs, int warningsCount, int errorsCount) + { + SortedLogs = sortedLogs; + WarningsCount = warningsCount; + ErrorsCount = errorsCount; + } + } + private readonly List _pendingRequests = new List(); private readonly List _compilationLogs = new List(); private int _processedAssemblies = 0; @@ -69,7 +85,7 @@ public override void ExecuteAsync(JObject parameters, TaskCompletionSource /// Record compilation logs for every single assembly /// - /// - /// private void OnAssemblyCompilationFinished(string assemblyPath, CompilerMessage[] messages) { _processedAssemblies++; @@ -106,17 +120,17 @@ private void OnAssemblyCompilationFinished(string assemblyPath, CompilerMessage[ } /// - /// Complete all pending requests and stop tracking + /// Stop tracking and complete all pending requests /// - /// private void OnCompilationFinished(object _) { McpLogger.LogInfo($"Recompilation completed. Processed {_processedAssemblies} assemblies with {_compilationLogs.Count} compiler messages"); - // Separate errors, warnings, and other messages - List errors = _compilationLogs.Where(m => m.type == CompilerMessageType.Error).ToList(); - List warnings = _compilationLogs.Where(m => m.type == CompilerMessageType.Warning).ToList(); - List others = _compilationLogs.Where(m => m.type != CompilerMessageType.Error && m.type != CompilerMessageType.Warning).ToList(); + // Sort logs by type: first errors, then warnings and info + List sortedLogs = _compilationLogs.OrderBy(x => x.type).ToList(); + int errorsCount = _compilationLogs.Count(l => l.type == CompilerMessageType.Error); + int warningsCount = _compilationLogs.Count(l => l.type == CompilerMessageType.Warning); + CompilationResult result = new CompilationResult(sortedLogs, warningsCount, errorsCount); // Stop tracking before completing requests StopCompilationTracking(); @@ -132,89 +146,52 @@ private void OnCompilationFinished(object _) foreach (var request in requestsToComplete) { - CompleteRequest(request, errors, warnings, others); + CompleteRequest(request, result); } } /// /// Process a completed compilation request /// - private static void CompleteRequest(CompilationRequest request, List errors, List warnings, List others) + private static void CompleteRequest(CompilationRequest request, CompilationResult result) { - try { - JArray logsArray = new JArray(); + JArray logsArray = new JArray(); + IEnumerable logsToReturn = request.ReturnWithLogs ? result.SortedLogs.Take(request.LogsLimit) : Enumerable.Empty(); - // Sort logs and apply logsLimit - prioritize errors if logsLimit is restrictive - IEnumerable sortedLogs; - if (!request.ReturnWithLogs || request.LogsLimit <= 0) + foreach (var message in logsToReturn) + { + var logObject = new JObject { - sortedLogs = Enumerable.Empty(); - } - else { - // Always include all errors, then warnings, then other messages up to the logsLimit - var selectedLogs = errors.ToList(); - var remainingSlots = request.LogsLimit - selectedLogs.Count; - - if (remainingSlots > 0) - { - selectedLogs.AddRange(warnings.Take(remainingSlots)); - remainingSlots = request.LogsLimit - selectedLogs.Count; - } - - if (remainingSlots > 0) - { - selectedLogs.AddRange(others.Take(remainingSlots)); - } - - sortedLogs = selectedLogs; - } + ["message"] = message.message, + ["type"] = message.type.ToString() + }; - foreach (var message in sortedLogs) + // Add file information if available + if (!string.IsNullOrEmpty(message.file)) { - var logObject = new JObject - { - ["message"] = message.message, - ["type"] = message.type.ToString(), - ["timestamp"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") - }; - - // Add file information if available - if (!string.IsNullOrEmpty(message.file)) - { - logObject["file"] = message.file; - logObject["line"] = message.line; - logObject["column"] = message.column; - } - - logsArray.Add(logObject); + logObject["file"] = message.file; + logObject["line"] = message.line; + logObject["column"] = message.column; } - bool hasErrors = errors.Count > 0; - string summaryMessage = hasErrors - ? $"Recompilation completed with {errors.Count} error(s) and {warnings.Count} warning(s)" - : $"Successfully recompiled all scripts with {warnings.Count} warning(s)"; + logsArray.Add(logObject); + } - summaryMessage += $" (returnWithLogs: {request.ReturnWithLogs}, logsLimit: {request.LogsLimit})"; + string summaryMessage = result.HasErrors + ? $"Recompilation completed with {result.ErrorsCount} error(s) and {result.WarningsCount} warning(s)" + : $"Successfully recompiled all scripts with {result.WarningsCount} warning(s)"; - var response = new JObject - { - ["success"] = true, - ["type"] = "text", - ["message"] = summaryMessage, - ["logs"] = logsArray - }; + summaryMessage += $" (returnWithLogs: {request.ReturnWithLogs}, logsLimit: {request.LogsLimit})"; - McpLogger.LogInfo($"Setting recompilation result: success={!hasErrors}, errors={errors.Count}, warnings={warnings.Count}"); - request.CompletionSource.SetResult(response); - } - catch (Exception ex) + var response = new JObject { - McpLogger.LogError($"Error creating recompilation response: {ex.Message}\n{ex.StackTrace}"); - request.CompletionSource.SetResult(McpUnitySocketHandler.CreateErrorResponse( - $"Error creating recompilation response: {ex.Message}", - "response_creation_error" - )); - } + ["success"] = true, + ["type"] = "text", + ["message"] = summaryMessage, + ["logs"] = logsArray + }; + + request.CompletionSource.SetResult(response); } ///