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+ }
0 commit comments