@@ -19,11 +19,9 @@ namespace MCPForUnity.Editor.Tools
1919{
2020 /// <summary>
2121 /// Runtime compilation tool for MCP Unity.
22- /// Compiles and loads C# code at runtime without triggering domain reload via Roslyn Runtime Compilation, where in traditional Unity workflow it would take seconds to reload assets and reset script states for each script change.
22+ /// Compiles and loads C# code at runtime without triggering domain reload.
2323 /// </summary>
24- [ McpForUnityTool (
25- name : "runtime_compilation" ,
26- Description = "Enable runtime compilation of C# code within Unity without domain reload via Roslyn." ) ]
24+ [ McpForUnityTool ( "runtime_compilation" ) ]
2725 public static class ManageRuntimeCompilation
2826 {
2927 private static readonly Dictionary < string , LoadedAssemblyInfo > LoadedAssemblies = new Dictionary < string , LoadedAssemblyInfo > ( ) ;
@@ -44,7 +42,7 @@ public static object HandleCommand(JObject @params)
4442
4543 if ( string . IsNullOrEmpty ( action ) )
4644 {
47- return new ErrorResponse ( "Action parameter is required. Valid actions: compile_and_load, list_loaded, get_types, execute_with_roslyn, get_history, save_history, clear_history" ) ;
45+ return Response . Error ( "Action parameter is required. Valid actions: compile_and_load, list_loaded, get_types, execute_with_roslyn, get_history, save_history, clear_history" ) ;
4846 }
4947
5048 switch ( action )
@@ -71,28 +69,31 @@ public static object HandleCommand(JObject @params)
7169 return ClearCompilationHistory ( ) ;
7270
7371 default :
74- return new ErrorResponse ( $ "Unknown action '{ action } '. Valid actions: compile_and_load, list_loaded, get_types, execute_with_roslyn, get_history, save_history, clear_history") ;
72+ return Response . Error ( $ "Unknown action '{ action } '. Valid actions: compile_and_load, list_loaded, get_types, execute_with_roslyn, get_history, save_history, clear_history") ;
7573 }
7674 }
7775
7876 private static object CompileAndLoad ( JObject @params )
7977 {
8078#if ! USE_ROSLYN
81- return new ErrorResponse (
79+ return Response . Error (
8280 "Runtime compilation requires Roslyn. Please install Microsoft.CodeAnalysis.CSharp NuGet package and add USE_ROSLYN to Scripting Define Symbols. " +
8381 "See ManageScript.cs header for installation instructions."
8482 ) ;
8583#else
8684 try
8785 {
8886 string code = @params [ "code" ] ? . ToString ( ) ;
89- string assemblyName = @params [ "assembly_name" ] ? . ToString ( ) ?? $ "DynamicAssembly_{ DateTime . Now . Ticks } ";
87+ var assemblyToken = @params [ "assembly_name" ] ;
88+ string assemblyName = assemblyToken == null || string . IsNullOrWhiteSpace ( assemblyToken . ToString ( ) )
89+ ? $ "DynamicAssembly_{ DateTime . Now . Ticks } "
90+ : assemblyToken . ToString ( ) . Trim ( ) ;
9091 string attachTo = @params [ "attach_to" ] ? . ToString ( ) ;
9192 bool loadImmediately = @params [ "load_immediately" ] ? . ToObject < bool > ( ) ?? true ;
9293
9394 if ( string . IsNullOrEmpty ( code ) )
9495 {
95- return new ErrorResponse ( "'code' parameter is required" ) ;
96+ return Response . Error ( "'code' parameter is required" ) ;
9697 }
9798
9899 // Ensure unique assembly name
@@ -103,8 +104,21 @@ private static object CompileAndLoad(JObject @params)
103104
104105 // Create output directory
105106 Directory . CreateDirectory ( DynamicAssembliesPath ) ;
106- string dllPath = Path . Combine ( DynamicAssembliesPath , $ "{ assemblyName } .dll") ;
107-
107+ string basePath = Path . GetFullPath ( DynamicAssembliesPath ) ;
108+ Directory . CreateDirectory ( basePath ) ;
109+ string safeFileName = SanitizeAssemblyFileName ( assemblyName ) ;
110+ string dllPath = Path . GetFullPath ( Path . Combine ( basePath , $ "{ safeFileName } .dll") ) ;
111+
112+ if ( ! dllPath . StartsWith ( basePath , StringComparison . Ordinal ) )
113+ {
114+ return Response . Error ( "Assembly name must resolve inside the dynamic assemblies directory." ) ;
115+ }
116+
117+ if ( File . Exists ( dllPath ) )
118+ {
119+ dllPath = Path . GetFullPath ( Path . Combine ( basePath , $ "{ safeFileName } _{ DateTime . Now . Ticks } .dll") ) ;
120+ }
121+
108122 // Parse code
109123 var syntaxTree = CSharpSyntaxTree . ParseText ( code ) ;
110124
@@ -123,7 +137,7 @@ private static object CompileAndLoad(JObject @params)
123137
124138 // Emit to file
125139 EmitResult emitResult ;
126- using ( var stream = new FileStream ( dllPath , FileMode . Create ) )
140+ using ( var stream = new FileStream ( dllPath , FileMode . Create , FileAccess . Write , FileShare . None ) )
127141 {
128142 emitResult = compilation . Emit ( stream ) ;
129143 }
@@ -142,7 +156,7 @@ private static object CompileAndLoad(JObject @params)
142156 } )
143157 . ToList ( ) ;
144158
145- return new ErrorResponse ( "Compilation failed" , new
159+ return Response . Error ( "Compilation failed" , new
146160 {
147161 errors = errors ,
148162 error_count = errors . Count
@@ -208,7 +222,7 @@ private static object CompileAndLoad(JObject @params)
208222 }
209223 }
210224
211- return new SuccessResponse ( "Runtime compilation completed successfully" , new
225+ return Response . Success ( "Runtime compilation completed successfully" , new
212226 {
213227 assembly_name = assemblyName ,
214228 dll_path = dllPath ,
@@ -221,15 +235,15 @@ private static object CompileAndLoad(JObject @params)
221235 }
222236 catch ( Exception ex )
223237 {
224- return new ErrorResponse ( $ "Runtime compilation failed: { ex . Message } ", new
238+ return Response . Error ( $ "Runtime compilation failed: { ex . Message } ", new
225239 {
226240 exception = ex . GetType ( ) . Name ,
227241 stack_trace = ex . StackTrace
228242 } ) ;
229243 }
230244#endif
231245 }
232-
246+
233247 private static object ListLoadedAssemblies ( )
234248 {
235249 var assemblies = LoadedAssemblies . Values . Select ( info => new
@@ -240,26 +254,33 @@ private static object ListLoadedAssemblies()
240254 type_count = info . TypeNames . Count ,
241255 types = info . TypeNames
242256 } ) . ToList ( ) ;
243-
244- return new SuccessResponse ( $ "Found { assemblies . Count } loaded dynamic assemblies", new
257+
258+ return Response . Success ( $ "Found { assemblies . Count } loaded dynamic assemblies", new
245259 {
246260 count = assemblies . Count ,
247261 assemblies = assemblies
248262 } ) ;
249263 }
250264
265+ private static string SanitizeAssemblyFileName ( string assemblyName )
266+ {
267+ var invalidChars = Path . GetInvalidFileNameChars ( ) ;
268+ var sanitized = new string ( assemblyName . Where ( c => ! invalidChars . Contains ( c ) ) . ToArray ( ) ) ;
269+ return string . IsNullOrWhiteSpace ( sanitized ) ? $ "DynamicAssembly_{ DateTime . Now . Ticks } " : sanitized ;
270+ }
271+
251272 private static object GetAssemblyTypes ( JObject @params )
252273 {
253274 string assemblyName = @params [ "assembly_name" ] ? . ToString ( ) ;
254275
255276 if ( string . IsNullOrEmpty ( assemblyName ) )
256277 {
257- return new ErrorResponse ( "'assembly_name' parameter is required" ) ;
278+ return Response . Error ( "'assembly_name' parameter is required" ) ;
258279 }
259280
260281 if ( ! LoadedAssemblies . TryGetValue ( assemblyName , out var info ) )
261282 {
262- return new ErrorResponse ( $ "Assembly '{ assemblyName } ' not found in loaded assemblies") ;
283+ return Response . Error ( $ "Assembly '{ assemblyName } ' not found in loaded assemblies") ;
263284 }
264285
265286 var types = info . Assembly . GetTypes ( ) . Select ( t => new
@@ -273,7 +294,7 @@ private static object GetAssemblyTypes(JObject @params)
273294 base_type = t . BaseType ? . FullName
274295 } ) . ToList ( ) ;
275296
276- return new SuccessResponse ( $ "Retrieved { types . Count } types from { assemblyName } ", new
297+ return Response . Success ( $ "Retrieved { types . Count } types from { assemblyName } ", new
277298 {
278299 assembly_name = assemblyName ,
279300 type_count = types . Count ,
@@ -297,7 +318,7 @@ private static object ExecuteWithRoslyn(JObject @params)
297318
298319 if ( string . IsNullOrEmpty ( code ) )
299320 {
300- return new ErrorResponse ( "'code' parameter is required" ) ;
321+ return Response . Error ( "'code' parameter is required" ) ;
301322 }
302323
303324 // Get or create the RoslynRuntimeCompiler instance
@@ -315,7 +336,7 @@ private static object ExecuteWithRoslyn(JObject @params)
315336
316337 if ( targetObject == null )
317338 {
318- return new ErrorResponse ( $ "Target GameObject '{ targetObjectName } ' not found") ;
339+ return Response . Error ( $ "Target GameObject '{ targetObjectName } ' not found") ;
319340 }
320341 }
321342
@@ -331,7 +352,7 @@ out string errorMessage
331352
332353 if ( success )
333354 {
334- return new SuccessResponse ( $ "Code compiled and executed successfully", new
355+ return Response . Success ( $ "Code compiled and executed successfully", new
335356 {
336357 class_name = className ,
337358 method_name = methodName ,
@@ -342,15 +363,15 @@ out string errorMessage
342363 }
343364 else
344365 {
345- return new ErrorResponse ( $ "Execution failed: { errorMessage } ", new
366+ return Response . Error ( $ "Execution failed: { errorMessage } ", new
346367 {
347368 diagnostics = compiler . lastCompileDiagnostics
348369 } ) ;
349370 }
350371 }
351372 catch ( Exception ex )
352373 {
353- return new ErrorResponse ( $ "Failed to execute with Roslyn: { ex . Message } ", new
374+ return Response . Error ( $ "Failed to execute with Roslyn: { ex . Message } ", new
354375 {
355376 exception = ex . GetType ( ) . Name ,
356377 stack_trace = ex . StackTrace
@@ -381,15 +402,15 @@ private static object GetCompilationHistory()
381402 : entry . sourceCode
382403 } ) . ToList ( ) ;
383404
384- return new SuccessResponse ( $ "Retrieved { historyData . Count } history entries", new
405+ return Response . Success ( $ "Retrieved { historyData . Count } history entries", new
385406 {
386407 count = historyData . Count ,
387408 history = historyData
388409 } ) ;
389410 }
390411 catch ( Exception ex )
391412 {
392- return new ErrorResponse ( $ "Failed to get history: { ex . Message } ") ;
413+ return Response . Error ( $ "Failed to get history: { ex . Message } ") ;
393414 }
394415 }
395416
@@ -404,20 +425,20 @@ private static object SaveCompilationHistory()
404425
405426 if ( compiler . SaveHistoryToFile ( out string savedPath , out string error ) )
406427 {
407- return new SuccessResponse ( $ "History saved successfully", new
428+ return Response . Success ( $ "History saved successfully", new
408429 {
409430 path = savedPath ,
410431 entry_count = compiler . CompilationHistory . Count
411432 } ) ;
412433 }
413434 else
414435 {
415- return new ErrorResponse ( $ "Failed to save history: { error } ") ;
436+ return Response . Error ( $ "Failed to save history: { error } ") ;
416437 }
417438 }
418439 catch ( Exception ex )
419440 {
420- return new ErrorResponse ( $ "Failed to save history: { ex . Message } ") ;
441+ return Response . Error ( $ "Failed to save history: { ex . Message } ") ;
421442 }
422443 }
423444
@@ -432,11 +453,11 @@ private static object ClearCompilationHistory()
432453 int count = compiler . CompilationHistory . Count ;
433454 compiler . ClearHistory ( ) ;
434455
435- return new SuccessResponse ( $ "Cleared { count } history entries") ;
456+ return Response . Success ( $ "Cleared { count } history entries") ;
436457 }
437458 catch ( Exception ex )
438459 {
439- return new ErrorResponse ( $ "Failed to clear history: { ex . Message } ") ;
460+ return Response . Error ( $ "Failed to clear history: { ex . Message } ") ;
440461 }
441462 }
442463
0 commit comments