diff --git a/MCPForUnity/Editor/Tools/ExecuteCode.cs b/MCPForUnity/Editor/Tools/ExecuteCode.cs index bef08d49a..c7392837d 100644 --- a/MCPForUnity/Editor/Tools/ExecuteCode.cs +++ b/MCPForUnity/Editor/Tools/ExecuteCode.cs @@ -663,10 +663,32 @@ public static void ResetCache() _isAvailable = null; } + // Unity's script updater ships a Mono-loadable Roslyn, so roslyn needs nothing installed + // The DotNetSdkRoslyn copy beside it is .NET Core and BadImageFormats in the editor domain + private static void TryLoadUnityRoslyn() + { + try + { + string dir = Path.Combine(UnityEditor.EditorApplication.applicationContentsPath, "Tools", "ScriptUpdater"); + foreach (string name in new[] { "Microsoft.CodeAnalysis.dll", "Microsoft.CodeAnalysis.CSharp.dll" }) + { + string path = Path.Combine(dir, name); + if (File.Exists(path)) Assembly.LoadFrom(path); + } + } + catch (Exception e) + { + McpLog.Warn($"[ExecuteCode] Could not load Unity's Roslyn: {e.Message}"); + } + } + private static bool Initialize() { try { + if (Type.GetType("Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree, Microsoft.CodeAnalysis.CSharp") == null) + TryLoadUnityRoslyn(); + _syntaxTreeType = Type.GetType("Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree, Microsoft.CodeAnalysis.CSharp"); _compilationType = Type.GetType("Microsoft.CodeAnalysis.CSharp.CSharpCompilation, Microsoft.CodeAnalysis.CSharp"); _compilationOptionsType = Type.GetType("Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions, Microsoft.CodeAnalysis.CSharp"); diff --git a/Server/src/services/tools/execute_code.py b/Server/src/services/tools/execute_code.py index 3cc44e260..b100900f9 100644 --- a/Server/src/services/tools/execute_code.py +++ b/Server/src/services/tools/execute_code.py @@ -26,7 +26,7 @@ "Actions: execute (run code), get_history (list past executions), " "replay (re-run a history entry), clear_history. " "NOTE: safety_checks blocks known dangerous patterns but is not a full sandbox. " - "Compiler options: 'auto' (Roslyn if available, else CodeDom), 'roslyn' (C# 12+, requires Microsoft.CodeAnalysis), 'codedom' (C# 6 only)." + "Compiler options: 'auto' and 'roslyn' both use the Roslyn that ships with Unity (C# 9); 'codedom' forces the legacy provider (C# 6 only)." ), group="scripting_ext", annotations=ToolAnnotations( @@ -61,8 +61,8 @@ async def execute_code( compiler: Annotated[ Literal["auto", "roslyn", "codedom"], "Compiler backend for 'execute' action. " - "'auto' uses Roslyn if Microsoft.CodeAnalysis is installed, else falls back to CodeDom. " - "'roslyn' forces Roslyn (C# 12+). 'codedom' forces legacy CSharpCodeProvider (C# 6). Default: auto.", + "'auto' and 'roslyn' both load the Roslyn shipped with the editor, which caps the language at C# 9. " + "'codedom' forces the legacy CSharpCodeProvider (C# 6). Default: auto.", ] = "auto", ) -> dict[str, Any]: unity_instance = await get_unity_instance_from_context(ctx) diff --git a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ExecuteCodeTests.cs b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ExecuteCodeTests.cs index dc3c4e360..ac3942c3f 100644 --- a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ExecuteCodeTests.cs +++ b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ExecuteCodeTests.cs @@ -537,6 +537,29 @@ private static void AssertCompilerSuccess(CompilerResults results) Assert.IsFalse(results.Errors.HasErrors, string.Join("\n", errors)); } + + // ──────────────────── Execute: compiler selection ──────────────────── + + [Test] + public void Execute_Auto_UsesRoslynLoadedFromUnity() + { + var result = Execute("return 1;"); + + Assert.IsTrue(result.Value("success"), result.ToString()); + Assert.AreEqual("roslyn", result["data"]["compiler"].Value(), + "auto fell back to CodeDom, so execute_code is silently limited to C# 6"); + } + + [Test] + public void Execute_UsingDeclaration_CompilesUnderRoslyn() + { + // C# 8: unavailable on the CodeDom fallback, so this also pins the language version + var result = Execute("using var s = new System.IO.MemoryStream();\nreturn s.CanRead;"); + + Assert.IsTrue(result.Value("success"), result.ToString()); + Assert.IsTrue(result["data"]["result"].Value()); + } + private static JObject Execute(string code) { return ToJObject(ExecuteCode.HandleCommand(new JObject diff --git a/website/docs/reference/tools/scripting_ext/execute_code.md b/website/docs/reference/tools/scripting_ext/execute_code.md index ddf399882..94f9a1e7f 100644 --- a/website/docs/reference/tools/scripting_ext/execute_code.md +++ b/website/docs/reference/tools/scripting_ext/execute_code.md @@ -12,7 +12,7 @@ description: "Execute arbitrary C# code inside the Unity Editor." ## Description -Execute arbitrary C# code inside the Unity Editor. The code runs as a method body with access to UnityEngine and UnityEditor namespaces. Use 'return' to send data back. Compiled in-memory — no script files created. Actions: execute (run code), get_history (list past executions), replay (re-run a history entry), clear_history. NOTE: safety_checks blocks known dangerous patterns but is not a full sandbox. Compiler options: 'auto' (Roslyn if available, else CodeDom), 'roslyn' (C# 12+, requires Microsoft.CodeAnalysis), 'codedom' (C# 6 only). +Execute arbitrary C# code inside the Unity Editor. The code runs as a method body with access to UnityEngine and UnityEditor namespaces. Use 'return' to send data back. Compiled in-memory — no script files created. Actions: execute (run code), get_history (list past executions), replay (re-run a history entry), clear_history. NOTE: safety_checks blocks known dangerous patterns but is not a full sandbox. Compiler options: 'auto' and 'roslyn' both use the Roslyn that ships with Unity (C# 9); 'codedom' forces the legacy provider (C# 6 only). ## Parameters @@ -23,7 +23,7 @@ Execute arbitrary C# code inside the Unity Editor. The code runs as a method bod | `safety_checks` | `bool` | — | Enable basic blocked-pattern checks (File.Delete, Process.Start, infinite loops, etc). Not a full sandbox — advanced bypass is possible. Default: true. | | `index` | `int \| None` | — | History entry index to replay (for 'replay' action). | | `limit` | `int` | — | Number of history entries to return (for 'get_history' action, 1-50). Default: 10. | -| `compiler` | `Literal['auto', 'roslyn', 'codedom']` | — | Compiler backend for 'execute' action. 'auto' uses Roslyn if Microsoft.CodeAnalysis is installed, else falls back to CodeDom. 'roslyn' forces Roslyn (C# 12+). 'codedom' forces legacy CSharpCodeProvider (C# 6). Default: auto. | +| `compiler` | `Literal['auto', 'roslyn', 'codedom']` | — | Compiler backend for 'execute' action. 'auto' and 'roslyn' both load the Roslyn shipped with the editor, which caps the language at C# 9. 'codedom' forces the legacy CSharpCodeProvider (C# 6). Default: auto. | ## Returns