Skip to content

Commit 0d46436

Browse files
Merge pull request #83 from GallopingDino/feature/recompile-scripts-tool
Feature/recompile scripts tool
2 parents 7a061ae + 0ef33d7 commit 0d46436

File tree

11 files changed

+347
-13
lines changed

11 files changed

+347
-13
lines changed

Editor/Tools/AddPackageTool.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
using System;
2-
using System.Linq;
32
using System.Collections.Generic;
4-
using UnityEngine;
53
using UnityEditor;
64
using UnityEditor.PackageManager;
75
using UnityEditor.PackageManager.Requests;
86
using Newtonsoft.Json.Linq;
97
using System.Threading.Tasks;
10-
using McpUnity.Tools;
118
using McpUnity.Unity;
129
using McpUnity.Utils;
1310

Editor/Tools/LoadSceneTool.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using UnityEngine;
32
using UnityEditor;
43
using Newtonsoft.Json.Linq;
54
using McpUnity.Unity;
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using McpUnity.Utils;
6+
using Newtonsoft.Json.Linq;
7+
using UnityEditor;
8+
using UnityEditor.Compilation;
9+
using UnityEngine;
10+
11+
namespace McpUnity.Tools {
12+
/// <summary>
13+
/// Tool to recompile all scripts in the Unity project
14+
/// </summary>
15+
public class RecompileScriptsTool : McpToolBase
16+
{
17+
private class CompilationRequest
18+
{
19+
public readonly bool ReturnWithLogs;
20+
public readonly int LogsLimit;
21+
public readonly TaskCompletionSource<JObject> CompletionSource;
22+
23+
public CompilationRequest(bool returnWithLogs, int logsLimit, TaskCompletionSource<JObject> completionSource)
24+
{
25+
ReturnWithLogs = returnWithLogs;
26+
LogsLimit = logsLimit;
27+
CompletionSource = completionSource;
28+
}
29+
}
30+
31+
private class CompilationResult
32+
{
33+
public readonly List<CompilerMessage> SortedLogs;
34+
public readonly int WarningsCount;
35+
public readonly int ErrorsCount;
36+
37+
public bool HasErrors => ErrorsCount > 0;
38+
39+
public CompilationResult(List<CompilerMessage> sortedLogs, int warningsCount, int errorsCount)
40+
{
41+
SortedLogs = sortedLogs;
42+
WarningsCount = warningsCount;
43+
ErrorsCount = errorsCount;
44+
}
45+
}
46+
47+
private readonly List<CompilationRequest> _pendingRequests = new List<CompilationRequest>();
48+
private readonly List<CompilerMessage> _compilationLogs = new List<CompilerMessage>();
49+
private int _processedAssemblies = 0;
50+
51+
public RecompileScriptsTool()
52+
{
53+
Name = "recompile_scripts";
54+
Description = "Recompiles all scripts in the Unity project";
55+
IsAsync = true; // Compilation is asynchronous
56+
}
57+
58+
/// <summary>
59+
/// Execute the Recompile tool asynchronously
60+
/// </summary>
61+
/// <param name="parameters">Tool parameters as a JObject</param>
62+
/// <param name="tcs">TaskCompletionSource to set the result or exception</param>
63+
public override void ExecuteAsync(JObject parameters, TaskCompletionSource<JObject> tcs)
64+
{
65+
// Extract and store parameters
66+
var returnWithLogs = GetBoolParameter(parameters, "returnWithLogs", true);
67+
var logsLimit = Mathf.Clamp(GetIntParameter(parameters, "logsLimit", 100), 0, 1000);
68+
var request = new CompilationRequest(returnWithLogs, logsLimit, tcs);
69+
70+
bool hasActiveRequest = false;
71+
lock (_pendingRequests)
72+
{
73+
hasActiveRequest = _pendingRequests.Count > 0;
74+
_pendingRequests.Add(request);
75+
}
76+
77+
if (hasActiveRequest)
78+
{
79+
McpLogger.LogInfo("Recompilation already in progress. Waiting for completion...");
80+
return;
81+
}
82+
83+
// On first request, initialize compilation listeners and start compilation
84+
StartCompilationTracking();
85+
86+
if (EditorApplication.isCompiling == false)
87+
{
88+
McpLogger.LogInfo("Recompiling all scripts in the Unity project");
89+
CompilationPipeline.RequestScriptCompilation();
90+
}
91+
}
92+
93+
/// <summary>
94+
/// Subscribe to compilation events, reset tracked state
95+
/// </summary>
96+
private void StartCompilationTracking()
97+
{
98+
_compilationLogs.Clear();
99+
_processedAssemblies = 0;
100+
CompilationPipeline.assemblyCompilationFinished += OnAssemblyCompilationFinished;
101+
CompilationPipeline.compilationFinished += OnCompilationFinished;
102+
}
103+
104+
/// <summary>
105+
/// Unsubscribe from compilation events
106+
/// </summary>
107+
private void StopCompilationTracking()
108+
{
109+
CompilationPipeline.assemblyCompilationFinished -= OnAssemblyCompilationFinished;
110+
CompilationPipeline.compilationFinished -= OnCompilationFinished;
111+
}
112+
113+
/// <summary>
114+
/// Record compilation logs for every single assembly
115+
/// </summary>
116+
private void OnAssemblyCompilationFinished(string assemblyPath, CompilerMessage[] messages)
117+
{
118+
_processedAssemblies++;
119+
_compilationLogs.AddRange(messages);
120+
}
121+
122+
/// <summary>
123+
/// Stop tracking and complete all pending requests
124+
/// </summary>
125+
private void OnCompilationFinished(object _)
126+
{
127+
McpLogger.LogInfo($"Recompilation completed. Processed {_processedAssemblies} assemblies with {_compilationLogs.Count} compiler messages");
128+
129+
// Sort logs by type: first errors, then warnings and info
130+
List<CompilerMessage> sortedLogs = _compilationLogs.OrderBy(x => x.type).ToList();
131+
int errorsCount = _compilationLogs.Count(l => l.type == CompilerMessageType.Error);
132+
int warningsCount = _compilationLogs.Count(l => l.type == CompilerMessageType.Warning);
133+
CompilationResult result = new CompilationResult(sortedLogs, warningsCount, errorsCount);
134+
135+
// Stop tracking before completing requests
136+
StopCompilationTracking();
137+
138+
// Complete all requests received before compilation end, the next received request will start a new compilation
139+
List<CompilationRequest> requestsToComplete = new List<CompilationRequest>();
140+
141+
lock (_pendingRequests)
142+
{
143+
requestsToComplete.AddRange(_pendingRequests);
144+
_pendingRequests.Clear();
145+
}
146+
147+
foreach (var request in requestsToComplete)
148+
{
149+
CompleteRequest(request, result);
150+
}
151+
}
152+
153+
/// <summary>
154+
/// Process a completed compilation request
155+
/// </summary>
156+
private static void CompleteRequest(CompilationRequest request, CompilationResult result)
157+
{
158+
JArray logsArray = new JArray();
159+
IEnumerable<CompilerMessage> logsToReturn = request.ReturnWithLogs ? result.SortedLogs.Take(request.LogsLimit) : Enumerable.Empty<CompilerMessage>();
160+
161+
foreach (var message in logsToReturn)
162+
{
163+
var logObject = new JObject
164+
{
165+
["message"] = message.message,
166+
["type"] = message.type.ToString()
167+
};
168+
169+
// Add file information if available
170+
if (!string.IsNullOrEmpty(message.file))
171+
{
172+
logObject["file"] = message.file;
173+
logObject["line"] = message.line;
174+
logObject["column"] = message.column;
175+
}
176+
177+
logsArray.Add(logObject);
178+
}
179+
180+
string summaryMessage = result.HasErrors
181+
? $"Recompilation completed with {result.ErrorsCount} error(s) and {result.WarningsCount} warning(s)"
182+
: $"Successfully recompiled all scripts with {result.WarningsCount} warning(s)";
183+
184+
summaryMessage += $" (returnWithLogs: {request.ReturnWithLogs}, logsLimit: {request.LogsLimit})";
185+
186+
var response = new JObject
187+
{
188+
["success"] = true,
189+
["type"] = "text",
190+
["message"] = summaryMessage,
191+
["logs"] = logsArray
192+
};
193+
194+
request.CompletionSource.SetResult(response);
195+
}
196+
197+
/// <summary>
198+
/// Helper method to safely extract integer parameters with default values
199+
/// </summary>
200+
/// <param name="parameters">JObject containing parameters</param>
201+
/// <param name="key">Parameter key to extract</param>
202+
/// <param name="defaultValue">Default value if parameter is missing or invalid</param>
203+
/// <returns>Extracted integer value or default</returns>
204+
private static int GetIntParameter(JObject parameters, string key, int defaultValue)
205+
{
206+
if (parameters?[key] != null && int.TryParse(parameters[key].ToString(), out int value))
207+
return value;
208+
return defaultValue;
209+
}
210+
211+
/// <summary>
212+
/// Helper method to safely extract boolean parameters with default values
213+
/// </summary>
214+
/// <param name="parameters">JObject containing parameters</param>
215+
/// <param name="key">Parameter key to extract</param>
216+
/// <param name="defaultValue">Default value if parameter is missing or invalid</param>
217+
/// <returns>Extracted boolean value or default</returns>
218+
private static bool GetBoolParameter(JObject parameters, string key, bool defaultValue)
219+
{
220+
if (parameters?[key] != null && bool.TryParse(parameters[key].ToString(), out bool value))
221+
return value;
222+
return defaultValue;
223+
}
224+
}
225+
}

Editor/Tools/RecompileScriptsTool.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/UnityBridge/McpUnityEditorWindow.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,14 @@ private void DrawHelpTab()
401401
WrappedLabel("Add the Player prefab from my project to the current scene", new GUIStyle(EditorStyles.miniLabel) { fontStyle = FontStyle.Italic });
402402
EditorGUILayout.EndVertical();
403403

404+
// recompile_scripts
405+
WrappedLabel("recompile_scripts", EditorStyles.boldLabel);
406+
WrappedLabel("Recompiles all scripts in the Unity project");
407+
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
408+
EditorGUILayout.LabelField("Example prompt:", EditorStyles.miniLabel);
409+
WrappedLabel("Recompile scripts in my project", new GUIStyle(EditorStyles.miniLabel) { fontStyle = FontStyle.Italic });
410+
EditorGUILayout.EndVertical();
411+
404412
EditorGUILayout.EndVertical();
405413

406414
// Available Resources section

Editor/UnityBridge/McpUnityServer.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Threading;
4-
using UnityEngine;
54
using UnityEditor;
65
using McpUnity.Tools;
76
using McpUnity.Resources;
87
using McpUnity.Services;
98
using McpUnity.Utils;
109
using WebSocketSharp.Server;
1110
using System.IO;
12-
using System.Diagnostics;
1311
using System.Net.Sockets;
12+
using UnityEditor.Callbacks;
1413

1514
namespace McpUnity.Unity
1615
{
@@ -30,16 +29,15 @@ public class McpUnityServer : IDisposable
3029
private CancellationTokenSource _cts;
3130
private TestRunnerService _testRunnerService;
3231
private ConsoleLogsService _consoleLogsService;
33-
32+
3433
/// <summary>
35-
/// Static constructor that gets called when Unity loads due to InitializeOnLoad attribute
34+
/// Called after every domain reload
3635
/// </summary>
37-
static McpUnityServer()
36+
[DidReloadScripts]
37+
private static void AfterReload()
3838
{
39-
EditorApplication.delayCall += () => {
40-
// Ensure Instance is created and hooks are set up after initial domain load
41-
var currentInstance = Instance;
42-
};
39+
// Ensure Instance is created and hooks are set up after initial domain load
40+
var currentInstance = Instance;
4341
}
4442

4543
/// <summary>
@@ -265,6 +263,10 @@ private void RegisterTools()
265263
// Register LoadSceneTool
266264
LoadSceneTool loadSceneTool = new LoadSceneTool();
267265
_tools.Add(loadSceneTool.Name, loadSceneTool);
266+
267+
// Register RecompileScriptsTool
268+
RecompileScriptsTool recompileScriptsTool = new RecompileScriptsTool();
269+
_tools.Add(recompileScriptsTool.Name, recompileScriptsTool);
268270
}
269271

270272
/// <summary>

README-ja.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ MCP Unityは、Unityの`Library/PackedCache`フォルダーをワークスペー
8686
- `create_prefab`: プレハブを作成し、オプションでMonoBehaviourスクリプトとシリアライズされたフィールド値を設定
8787
> **例:** "'PlayerController'スクリプトから'Player'という名前のプレハブを作成"
8888
89+
- `recompile_scripts`: Unityプロジェクト内のすべてのスクリプトを再コンパイル
90+
> **例:** "Unityプロジェクト内のすべてのスクリプトを再コンパイル"
91+
8992
### MCPサーバーリソース
9093

9194
- `unity://menu-items`: `execute_menu_item`ツールを容易にするために、Unityエディターで利用可能なすべてのメニュー項目のリストを取得

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ The following tools are available for manipulating and querying Unity scenes and
9090
- `create_prefab`: Creates a prefab with optional MonoBehaviour script and serialized field values
9191
> **Example prompt:** "Create a prefab named 'Player' from the 'PlayerController' script"
9292
93+
- `recompile_scripts`: Recompiles all scripts in the Unity project
94+
> **Example prompt:** "Recompile scripts in my Unity project"
95+
9396
### MCP Server Resources
9497

9598
- `unity://menu-items`: Retrieves a list of all available menu items in the Unity Editor to facilitate `execute_menu_item` tool

README_zh-CN.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ MCP Unity 通过将 Unity `Library/PackedCache` 文件夹添加到您的工作
8787
- `create_prefab`: 创建预制体,并可选择添加 MonoBehaviour 脚本和设置序列化字段值
8888
> **示例提示:** "从 'PlayerController' 脚本创建一个名为 'Player' 的预制体"
8989
90+
- `recompile_scripts`: 重新编译 Unity 项目中的所有脚本
91+
> **示例提示:** "重新编译我 Unity 项目中的所有脚本"
92+
9093
### MCP 服务器资源
9194

9295
- `unity://menu-items`: 获取 Unity 编辑器中所有可用的菜单项列表,以方便 `execute_menu_item` 工具

Server~/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { registerUpdateGameObjectTool } from './tools/updateGameObjectTool.js';
1616
import { registerCreatePrefabTool } from './tools/createPrefabTool.js';
1717
import { registerDeleteSceneTool } from './tools/deleteSceneTool.js';
1818
import { registerLoadSceneTool } from './tools/loadSceneTool.js';
19+
import { registerRecompileScriptsTool } from './tools/recompileScriptsTool.js';
1920
import { registerGetMenuItemsResource } from './resources/getMenuItemResource.js';
2021
import { registerGetConsoleLogsResource } from './resources/getConsoleLogsResource.js';
2122
import { registerGetHierarchyResource } from './resources/getScenesHierarchyResource.js';
@@ -63,6 +64,7 @@ registerCreatePrefabTool(server, mcpUnity, toolLogger);
6364
registerCreateSceneTool(server, mcpUnity, toolLogger);
6465
registerDeleteSceneTool(server, mcpUnity, toolLogger);
6566
registerLoadSceneTool(server, mcpUnity, toolLogger);
67+
registerRecompileScriptsTool(server, mcpUnity, toolLogger);
6668

6769
// Register all resources into the MCP server
6870
registerGetTestsResource(server, mcpUnity, resourceLogger);

0 commit comments

Comments
 (0)