feat: add execute_code tool for running arbitrary C# in Unity Editor#1001
feat: add execute_code tool for running arbitrary C# in Unity Editor#1001Scriptwonder merged 6 commits intoCoplayDev:betafrom
Conversation
Adds a built-in `execute_code` tool that compiles and runs C# code inside the Unity Editor via CSharpCodeProvider. No external dependencies (Roslyn not required), no script files created. ## Actions - `execute` — compile and run C# method body, return result - `get_history` — list past executions with previews - `replay` — re-run a history entry with original settings - `clear_history` — clear execution history ## Safety - `safety_checks` (default: true) blocks known dangerous patterns (File.Delete, Process.Start, AssetDatabase.DeleteAsset, infinite loops) - Clearly documented as pattern-based blocklist, NOT a security sandbox - `destructiveHint=True` annotation for MCP clients ## Features - In-memory compilation with all loaded assembly references - User-friendly error line numbers (wrapper offset subtracted) - Execution history (max 50 entries) with code preview truncation - Replay preserves original safety_checks setting - CLI commands: `code execute`, `code history`, `code replay`, `code clear-history` ## Files - C#: `MCPForUnity/Editor/Tools/ExecuteCode.cs` (329 lines) - Python: `Server/src/services/tools/execute_code.py` (85 lines) - CLI: `Server/src/cli/commands/code.py` (+89 lines) - Tests: `Server/tests/test_execute_code.py` (17 tests, all passing) - Manifest: added `execute_code` entry Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reviewer's GuideIntroduces a new execute_code tool that compiles and runs arbitrary C# in the Unity Editor with safety checks and history, wires it through the MCP server, and exposes it via new CLI subcommands and manifest registration. Sequence diagram for replay action using historysequenceDiagram
actor cli_user
participant CLI as CLI_code_command
participant Server as Server_execute_code_tool
participant Transport as Unity_transport
participant Editor as Unity_Editor
participant Tool as ExecuteCode
cli_user->>CLI: unity-mcp code replay 0
CLI->>Server: run_command execute_code {action: replay, index: 0}
Server->>Transport: async_send_command_with_retry execute_code params
Transport->>Editor: JSON RPC execute_code params
Editor->>Tool: HandleCommand(JObject params)
Tool->>Tool: HandleReplay(params)
Tool->>Tool: lookup HistoryEntry by index
Tool->>Tool: HandleExecute(replayParams)
Tool->>Tool: CompileAndExecute(stored code)
Tool-->>Tool: SuccessResponse or ErrorResponse
Tool-->>Editor: response
Editor-->>Transport: response
Transport-->>Server: response dict
Server-->>CLI: {success, message, data}
CLI-->>cli_user: formatted output + Result line
Class diagram for new ExecuteCode tool and history entriesclassDiagram
class ExecuteCode {
<<static>>
-int MaxCodeLength
-int MaxHistoryEntries
-int MaxHistoryCodePreview
-int WrapperLineOffset
-string WrapperClassName
-string WrapperMethodName
-string ActionExecute
-string ActionGetHistory
-string ActionClearHistory
-string ActionReplay
-List~HistoryEntry~ _history
-HashSet~string~ _blockedPatterns
+object HandleCommand(JObject params)
-object HandleExecute(JObject params)
-object HandleGetHistory(JObject params)
-object HandleClearHistory()
-object HandleReplay(JObject params)
-object CompileAndExecute(string code)
-string WrapUserCode(string code)
-void AddReferences(CompilerParameters parameters)
-string CheckBlockedPatterns(string code)
-void AddToHistory(string code, object result, double elapsedMs, bool safetyChecks)
-object SerializeResult(object result)
}
class HistoryEntry {
+string code
+bool success
+string resultPreview
+double elapsedMs
+string timestamp
+bool safetyChecksEnabled
}
ExecuteCode "1" o-- "*" HistoryEntry
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a new Unity Editor tool Changes
Sequence Diagram(s)sequenceDiagram
participant CLI as CLI Client
participant Server as MCP Server
participant Tool as execute_code Tool
participant Unity as Unity Editor\n(ExecuteCode)
CLI->>Server: code execute --source "..."
Server->>Tool: execute_code(action="execute", code="...", safety_checks=True)
Tool->>Tool: validate & isolate params
Tool->>Unity: send_with_unity_instance(tool_name="execute_code", params={...})
Unity->>Unity: safety checks, wrap code, choose compiler (roslyn/codedom)
Unity->>Unity: compile in-memory, invoke wrapper, capture result/exception
Unity-->>Tool: SuccessResponse / ErrorResponse
Tool->>Server: normalized {success, message, data}
Server->>CLI: formatted output (prints Result on success)
sequenceDiagram
participant CLI as CLI Client
participant Server as MCP Server
participant Tool as execute_code Tool
participant Unity as Unity Editor\n(ExecuteCode)
CLI->>Server: code history --limit 5
Server->>Tool: execute_code(action="get_history", limit=5)
Tool->>Tool: clamp limit to [1,50]
Tool->>Unity: send_with_unity_instance(tool_name="execute_code", params={action, limit})
Unity->>Unity: read history, slice/format entries
Unity-->>Tool: SuccessResponse with history payload
Tool->>Server: normalized dict
Server->>CLI: print history list
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The hard-coded
WrapperLineOffset = 9is brittle since it depends on the exact structure ofWrapUserCode; consider computing the offset dynamically (e.g., by counting wrapper lines) so compiler error line mapping stays correct if the wrapper changes. - In
AddReferences, walking allAppDomain.CurrentDomain.GetAssemblies()and adding locations on every execution could become expensive; consider caching the resolved reference list in a static field so subsequent compilations reuse it.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The hard-coded `WrapperLineOffset = 9` is brittle since it depends on the exact structure of `WrapUserCode`; consider computing the offset dynamically (e.g., by counting wrapper lines) so compiler error line mapping stays correct if the wrapper changes.
- In `AddReferences`, walking all `AppDomain.CurrentDomain.GetAssemblies()` and adding locations on every execution could become expensive; consider caching the resolved reference list in a static field so subsequent compilations reuse it.
## Individual Comments
### Comment 1
<location path="MCPForUnity/Editor/Tools/ExecuteCode.cs" line_range="20-29" />
<code_context>
+ private const int WrapperLineOffset = 9;
</code_context>
<issue_to_address>
**issue (bug_risk):** WrapperLineOffset appears off by one, which will skew reported user line numbers.
User code begins at physical line 11, but WrapperLineOffset is set to 9. That makes an error on the first user line map to 2 instead of 1 (11 - 9), so all reported error lines are off by one. Please update WrapperLineOffset to 10, or compute it directly from WrapUserCode so it stays correct if the wrapper changes.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@MCPForUnity/Editor/Tools/ExecuteCode.cs`:
- Line 20: The WrapperLineOffset constant is off-by-one; update the value of
WrapperLineOffset (used to adjust compiler error line numbers) from 9 to 10 so
it accounts for the full 10-line wrapper header generated by the template (the
wrapper that declares MCPDynamicCode and Execute). Locate the constant named
WrapperLineOffset and change its value to 10, then run a test compiling a sample
user snippet that triggers an error on the first user line to verify the
reported line now matches the user's first line.
In `@Server/src/cli/commands/code.py`:
- Around line 38-40: The file-reading branch uses a plain open(...) call which
relies on platform default encoding; change the open in the CLI command where it
reads into variable "source" (the with open(file, "r") as f: block) to
explicitly set the encoding (e.g., encoding="utf-8") so non-ASCII characters are
handled consistently across platforms; keep the same read() logic and consider
adding errors="replace" if you want to tolerate malformed bytes.
In `@Server/src/services/tools/execute_code.py`:
- Around line 21-34: The mcp_for_unity_tool decorator invocation for the Execute
Code tool is missing the required group parameter; update the decorator call
(mcp_for_unity_tool) to include group='scripting_ext' (or another allowed group
from 'core','vfx','animation','ui','scripting_ext','probuilder','testing') so
the tool is properly categorized, keeping the existing description and
ToolAnnotations (title="Execute Code", destructiveHint=True) intact; ensure the
new group argument is added at the top-level decorator arguments alongside
description and annotations.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 096cf8a3-00d7-4c55-87a7-da20c45d6e63
📒 Files selected for processing (6)
MCPForUnity/Editor/Tools/ExecuteCode.csMCPForUnity/Editor/Tools/ExecuteCode.cs.metaServer/src/cli/commands/code.pyServer/src/services/tools/execute_code.pyServer/tests/test_execute_code.pymanifest.json
- Fix off-by-one in WrapperLineOffset (9 → 10) - Cache resolved assembly paths in static field for performance - Add encoding="utf-8" to CLI file read - Add group="scripting_ext" to Python tool decorator Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
Server/src/cli/commands/code.py (1)
75-92: Consider extracting result printing helper.The result printing logic (lines 89-92) is duplicated from the
executecommand (lines 53-56). This is minor given it's only 4 lines.♻️ Optional: Extract helper function
def _print_execution_result(result: dict[str, Any]) -> None: """Print execution result if present.""" if result.get("success"): data = result.get("data", {}) if data and data.get("result") is not None: print_success(f"Result: {data['result']}")Then use in both commands:
click.echo(format_output(result, config.format)) - if result.get("success"): - data = result.get("data", {}) - if data and data.get("result") is not None: - print_success(f"Result: {data['result']}") + _print_execution_result(result)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Server/src/cli/commands/code.py` around lines 75 - 92, The replay command duplicates result-printing logic found in execute; extract a small helper (e.g., _print_execution_result(result: dict[str, Any])) that encapsulates the existing conditional printing logic and call it from both replay() and execute() to remove duplication; locate the two blocks in replay (function replay) and execute (function execute) and replace the repeated lines that check result.get("success") / data.get("result") with a call to _print_execution_result(result).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@Server/src/cli/commands/code.py`:
- Around line 75-92: The replay command duplicates result-printing logic found
in execute; extract a small helper (e.g., _print_execution_result(result:
dict[str, Any])) that encapsulates the existing conditional printing logic and
call it from both replay() and execute() to remove duplication; locate the two
blocks in replay (function replay) and execute (function execute) and replace
the repeated lines that check result.get("success") / data.get("result") with a
call to _print_execution_result(result).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b1318bce-6322-422c-abd5-cf7f754d5ed5
📒 Files selected for processing (3)
MCPForUnity/Editor/Tools/ExecuteCode.csServer/src/cli/commands/code.pyServer/src/services/tools/execute_code.py
🚧 Files skipped from review as they are similar to previous changes (1)
- MCPForUnity/Editor/Tools/ExecuteCode.cs
Address CodeRabbit nitpick — deduplicate result printing logic between execute and replay commands. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Using CSharpCodeProvider sounds cool! Will take a look |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@MCPForUnity/Editor/Tools/ExecuteCode.cs`:
- Around line 165-170: The replay payload construction drops the original
compiler choice, so update the code that builds the replay JObject (the variable
replayParams in ExecuteCode.cs) to include the compiler field (e.g., "compiler"
= entry.compiler) and ensure the history model/save path records entry.compiler
when creating history entries; also audit the other replay/save code paths
referenced around lines 392-445 to propagate entry.compiler (or default to the
stored value) so replays use the original compiler selection instead of "auto".
- Around line 86-94: The ExecuteCode handler currently parses code,
safety_checks, limit and index directly from the raw `@params` payload; replace
those ad hoc parses with the ToolParams helper used elsewhere: construct a
ToolParams from `@params` and call RequireString("code") for code,
GetBool("safety_checks", true) for safetyChecks, and
GetInt("limit")/GetInt("index") (or their optional variants) for limit and index
so all validation/error paths are consistent; make the same replacement for the
other ad hoc parses in this file (the other occurrences near the ExecuteCode
logic) so the handler uses ToolParams methods for all tool parameters.
In `@MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs`:
- Around line 741-765: The Install All and Uninstall All handlers call
InstallUpmPackage/RemoveUpmPackage repeatedly which issues multiple
Client.Add/Client.Remove calls concurrently; change these handlers to perform a
single serialized package manager operation by using Client.AddAndRemove (or by
awaiting each Client request sequentially) so the UPM mutations are atomic;
specifically, replace the three InstallUpmPackage calls in the installAllButton
callback with one Client.AddAndRemove call that adds
"com.unity.probuilder","com.unity.cinemachine","com.unity.visualeffectgraph",
and replace the three RemoveUpmPackage calls in the uninstallAllButton callback
with one Client.AddAndRemove call that removes those same package IDs (or
alternatively update InstallUpmPackage and RemoveUpmPackage to return the async
request and await them sequentially from the installAllButton/uninstallAllButton
delegates).
- Around line 866-900: The install/uninstall handlers
(installButton/uninstallButton) are updating UI immediately but the operations
triggered by installAction/uninstallAction (e.g., Client.Add/Client.Remove and
their async polling callbacks) are asynchronous, so move all UI state changes
(statusIcon, statusText, button enable/text changes and buttonRow row
reconstruction) out of the immediate lambda and into the async
polling/completion callback that runs after the Client.Add/Client.Remove
finishes; keep the button disabled and show an in-progress state while waiting,
then on success update to the final installed/uninstalled UI (rebuild the row),
and on failure revert button text/state and show an error state so the UI
matches actual operation result.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 40c771f7-887f-40c1-9eae-1097ec78a59a
📒 Files selected for processing (8)
MCPForUnity/Editor/Constants/EditorPrefKeys.csMCPForUnity/Editor/Setup/McpForUnitySkillInstaller.csMCPForUnity/Editor/Setup/RoslynInstaller.csMCPForUnity/Editor/Tools/ExecuteCode.csMCPForUnity/Editor/Windows/MCPForUnityEditorWindow.csMCPForUnity/Editor/Windows/MCPForUnityEditorWindow.uxmlServer/src/services/tools/execute_code.pyTestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ExecuteCodeTests.cs
💤 Files with no reviewable changes (2)
- MCPForUnity/Editor/Setup/McpForUnitySkillInstaller.cs
- MCPForUnity/Editor/Setup/RoslynInstaller.cs
✅ Files skipped from review due to trivial changes (2)
- MCPForUnity/Editor/Constants/EditorPrefKeys.cs
- Server/src/services/tools/execute_code.py
| string code = @params["code"]?.ToString(); | ||
| if (string.IsNullOrWhiteSpace(code)) | ||
| return new ErrorResponse("Required parameter 'code' is missing or empty."); | ||
|
|
||
| if (code.Length > MaxCodeLength) | ||
| return new ErrorResponse($"Code exceeds maximum length of {MaxCodeLength} characters."); | ||
|
|
||
| bool safetyChecks = @params["safety_checks"]?.Value<bool>() ?? true; | ||
| string compiler = @params["compiler"]?.ToString()?.ToLowerInvariant() ?? "auto"; |
There was a problem hiding this comment.
Use ToolParams for the remaining request fields.
action goes through the shared validation helper, but code, safety_checks, limit, and index are still parsed ad hoc from the raw payload. That gives those fields a different validation/error path from the rest of the tool and makes this handler harder to reason about as more actions get added. As per coding guidelines, "Use ToolParams class for consistent parameter validation in C# tool handlers with methods like GetInt(), RequireString(), etc."
Also applies to: 123-124, 160-162
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@MCPForUnity/Editor/Tools/ExecuteCode.cs` around lines 86 - 94, The
ExecuteCode handler currently parses code, safety_checks, limit and index
directly from the raw `@params` payload; replace those ad hoc parses with the
ToolParams helper used elsewhere: construct a ToolParams from `@params` and call
RequireString("code") for code, GetBool("safety_checks", true) for safetyChecks,
and GetInt("limit")/GetInt("index") (or their optional variants) for limit and
index so all validation/error paths are consistent; make the same replacement
for the other ad hoc parses in this file (the other occurrences near the
ExecuteCode logic) so the handler uses ToolParams methods for all tool
parameters.
| if (!isInstalled && installAction != null) | ||
| { | ||
| Button installButton = null; | ||
| installButton = new Button(() => | ||
| { | ||
| installAction(); | ||
| statusIcon.text = "\u2713"; | ||
| statusIcon.style.color = new Color(0.4f, 0.8f, 0.4f); | ||
| statusText.text = installedText; | ||
| statusText.style.color = new Color(0.6f, 0.8f, 0.6f); | ||
| installButton.SetEnabled(false); | ||
| installButton.text = "Installing..."; | ||
| }); | ||
| installButton.text = "Install"; | ||
| installButton.AddToClassList("action-button"); | ||
| buttonRow.Add(installButton); | ||
| } | ||
|
|
||
| if (isInstalled && uninstallAction != null) | ||
| { | ||
| var uninstallButton = new Button(() => | ||
| { | ||
| if (EditorUtility.DisplayDialog("Remove " + name, | ||
| $"Are you sure you want to remove {name}?", "Remove", "Cancel")) | ||
| { | ||
| uninstallAction(); | ||
| statusIcon.text = "\u2717"; | ||
| statusIcon.style.color = new Color(0.8f, 0.4f, 0.4f); | ||
| statusText.text = missingText; | ||
| statusText.style.color = new Color(0.8f, 0.7f, 0.5f); | ||
| } | ||
| }); | ||
| uninstallButton.text = "Uninstall"; | ||
| uninstallButton.AddToClassList("action-button"); | ||
| buttonRow.Add(uninstallButton); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd /tmp && find . -name "MCPForUnityEditorWindow.cs" -type f 2>/dev/null | head -5Repository: CoplayDev/unity-mcp
Length of output: 45
🏁 Script executed:
git ls-files | grep -i "mcp.*window" | head -20Repository: CoplayDev/unity-mcp
Length of output: 1293
🏁 Script executed:
git ls-files | grep -E "MCPForUnity.*\.cs$" | head -20Repository: CoplayDev/unity-mcp
Length of output: 1338
🏁 Script executed:
git ls-files | grep -i "editorwindow"Repository: CoplayDev/unity-mcp
Length of output: 532
🏁 Script executed:
git ls-files | grep -i "MCPForUnity.*Window" | grep "\.cs$"Repository: CoplayDev/unity-mcp
Length of output: 934
🏁 Script executed:
fd "MCPForUnityEditorWindow" --type fRepository: CoplayDev/unity-mcp
Length of output: 407
🏁 Script executed:
wc -l MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.csRepository: CoplayDev/unity-mcp
Length of output: 120
🏁 Script executed:
cat -n MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs | head -100Repository: CoplayDev/unity-mcp
Length of output: 4228
🏁 Script executed:
sed -n '850,950p' MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs | cat -nRepository: CoplayDev/unity-mcp
Length of output: 5289
🏁 Script executed:
sed -n '810,969p' MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs | tail -100Repository: CoplayDev/unity-mcp
Length of output: 4607
🏁 Script executed:
rg "RefreshValidationSection|Refresh.*Validation" MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs -A 5 -B 5Repository: CoplayDev/unity-mcp
Length of output: 45
Defer UI state changes until the async operation completes.
The button handlers immediately flip icon/text and disable controls, but Client.Add() and Client.Remove() are asynchronous—their polling callbacks only log and clear the progress bar without rebuilding the UI. If the request fails, the UI shows success with no rollback. The "Installing..." button text never becomes "Uninstall" after completion, and failed removals leave the wrong button in place. Move the icon/text changes, button state updates, and row reconstruction into the async polling callback to reflect actual completion state.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs` around lines 866 -
900, The install/uninstall handlers (installButton/uninstallButton) are updating
UI immediately but the operations triggered by installAction/uninstallAction
(e.g., Client.Add/Client.Remove and their async polling callbacks) are
asynchronous, so move all UI state changes (statusIcon, statusText, button
enable/text changes and buttonRow row reconstruction) out of the immediate
lambda and into the async polling/completion callback that runs after the
Client.Add/Client.Remove finishes; keep the button disabled and show an
in-progress state while waiting, then on success update to the final
installed/uninstalled UI (rebuild the row), and on failure revert button
text/state and show an error state so the UI matches actual operation result.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs (1)
283-286: Store theMcpValidationSectionreference for proper lifecycle management.The instance is created but not stored, leaving it unreachable while its callbacks remain registered on the UI elements. Per the existing
McpValidationSectionimplementation, it registersRegisterValueChangedCallbackin its constructor but has no lifecycle hooks to unregister them. If the window is reloaded or the container is removed from the DOM, these callbacks can become orphaned.Consider storing the reference in a field (similar to other section controllers like
advancedSection) so it can be properly disposed or have its callbacks unregistered when the window is disabled/closed.♻️ Suggested approach
Add a field and store the reference:
+ private McpValidationSection validationSection; // (in CreateGUI, after instantiating) - new McpValidationSection(validationRoot); + validationSection = new McpValidationSection(validationRoot);Then consider adding cleanup logic in
OnDisable()or implementing proper lifecycle management inMcpValidationSectionitself.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs` around lines 283 - 286, The McpValidationSection instance created with new McpValidationSection(validationRoot) is not stored and its callbacks stay registered; add a field (e.g., private McpValidationSection validationSection;) to hold the instance when you call new McpValidationSection(validationRoot) after creating validationRoot and adding it to advancedContainer (similar to advancedSection), then use that field in OnDisable() (or window cleanup) to call a teardown/unregister method on McpValidationSection (or implement IDisposable/unregister logic inside McpValidationSection and call it from OnDisable) so callbacks are removed and the lifecycle is managed.MCPForUnity/Editor/Tools/ExecuteCode.cs (2)
98-100: Redundant error message phrasing.
CheckBlockedPatternsreturns"Code contains blocked pattern: '{pattern}'..."and line 100 wraps it with"Blocked pattern detected: {violation}", resulting in: "Blocked pattern detected: Code contains blocked pattern: 'X'..."Consider either returning just the pattern from
CheckBlockedPatternsor using the violation message directly without the prefix.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@MCPForUnity/Editor/Tools/ExecuteCode.cs` around lines 98 - 100, The current wrapper in ExecuteCode (where you call CheckBlockedPatterns(code) and then return new ErrorResponse($"Blocked pattern detected: {violation}")) duplicates wording because CheckBlockedPatterns already returns a full message; change the call to pass the violation string directly to the ErrorResponse (i.e., return new ErrorResponse(violation)) or alternatively modify CheckBlockedPatterns to return only the offending pattern and keep the current prefix—pick one approach and update the CheckBlockedPatterns method or the ErrorResponse invocation accordingly, referencing the CheckBlockedPatterns function and the ErrorResponse creation in ExecuteCode.cs.
429-436: Empty catch block silently swallows exceptions.The fallback to
ToString()is reasonable, but the empty catch hides what went wrong during serialization, which could be useful for debugging complex types.Suggested improvement
try { return JToken.FromObject(result); } - catch + catch (Exception e) { + McpLog.Debug($"[ExecuteCode] JSON serialization failed for {result.GetType().Name}, using ToString: {e.Message}"); return result.ToString(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@MCPForUnity/Editor/Tools/ExecuteCode.cs` around lines 429 - 436, The current catch block around JToken.FromObject(result) swallows exceptions; change it to catch(Exception ex) and record the exception before falling back to result.ToString() so you don't lose debug info. Locate the try { return JToken.FromObject(result); } catch { ... } block (in the ExecuteCode class / method shown) and replace the empty catch with a catch that logs the exception (e.g., UnityEngine.Debug.LogException or processLogger/console with the exception and the result's type/value) and then returns result.ToString() as the fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@MCPForUnity/Editor/Tools/ExecuteCode.cs`:
- Around line 133-144: The get_history projection currently builds an anonymous
object from entries but omits the compiler field; update the Select projection
(the entries = entries.Select(...) block) to include the history item's compiler
(e.g., add compiler = e.compiler) so the response includes which compiler was
used for each entry; ensure the property name matches the stored model's
compiler field used by replay so downstream code can read it.
- Around line 661-662: The code currently uses a hardcoded 10 for the wrapper
offset when computing userLine; update this to use the canonical constant
ExecuteCode.WrapperLineOffset and make that constant accessible to
RoslynCompiler (e.g., change its visibility from private to internal/public or
move it to a shared static class). Replace the literal 10 in RoslynCompiler
where userLine is computed with ExecuteCode.WrapperLineOffset and ensure the
constant's new visibility allows direct reference from RoslynCompiler.
In `@MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs`:
- Around line 744-773: The Install All and Uninstall All handlers
(installAllButton / uninstallAllButton) disable their buttons and change text
but never re-enable or reset text because BatchUpmAdd and BatchUpmRemove run
asynchronously and PollUpmRequest only logs results; modify BatchUpmAdd and
BatchUpmRemove (or their caller) to accept a completion callback or Task
continuation and, when the UPM work finishes (success or failure), restore the
button UI (SetEnabled(true) and reset text to "Install All"/"Uninstall All") or
call the existing dependency UI refresh routine so the whole panel is rebuilt;
ensure you update the code paths that call PollUpmRequest to invoke that
completion callback on both success and error so the button state is always
restored.
---
Nitpick comments:
In `@MCPForUnity/Editor/Tools/ExecuteCode.cs`:
- Around line 98-100: The current wrapper in ExecuteCode (where you call
CheckBlockedPatterns(code) and then return new ErrorResponse($"Blocked pattern
detected: {violation}")) duplicates wording because CheckBlockedPatterns already
returns a full message; change the call to pass the violation string directly to
the ErrorResponse (i.e., return new ErrorResponse(violation)) or alternatively
modify CheckBlockedPatterns to return only the offending pattern and keep the
current prefix—pick one approach and update the CheckBlockedPatterns method or
the ErrorResponse invocation accordingly, referencing the CheckBlockedPatterns
function and the ErrorResponse creation in ExecuteCode.cs.
- Around line 429-436: The current catch block around JToken.FromObject(result)
swallows exceptions; change it to catch(Exception ex) and record the exception
before falling back to result.ToString() so you don't lose debug info. Locate
the try { return JToken.FromObject(result); } catch { ... } block (in the
ExecuteCode class / method shown) and replace the empty catch with a catch that
logs the exception (e.g., UnityEngine.Debug.LogException or
processLogger/console with the exception and the result's type/value) and then
returns result.ToString() as the fallback.
In `@MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs`:
- Around line 283-286: The McpValidationSection instance created with new
McpValidationSection(validationRoot) is not stored and its callbacks stay
registered; add a field (e.g., private McpValidationSection validationSection;)
to hold the instance when you call new McpValidationSection(validationRoot)
after creating validationRoot and adding it to advancedContainer (similar to
advancedSection), then use that field in OnDisable() (or window cleanup) to call
a teardown/unregister method on McpValidationSection (or implement
IDisposable/unregister logic inside McpValidationSection and call it from
OnDisable) so callbacks are removed and the lifecycle is managed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b199154c-1b01-46a4-825e-5f74307e91a8
📒 Files selected for processing (2)
MCPForUnity/Editor/Tools/ExecuteCode.csMCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
|
Thanks for the PR! Tried it and testing seems alright. I added Roslyn as the backend if they have that installed, since it has more runtime functionalities like async/await and other patterns compared to CSharpCodeProvider. Will close this, let me know if you have any questions. |
|
Thanks for feedback. Async/await support will make execute_code much more powerful for users. |
Summary
Adds a built-in
execute_codetool that compiles and runs arbitrary C# code inside the Unity Editor. UsesCSharpCodeProviderfor in-memory compilation — no Roslyn dependency, no script files created.This fills the gap between
execute_menu_item(limited to menu paths) and theruntime_compilationcustom tool (requires Roslyn + opt-in setup).execute_codeworks out of the box with zero configuration.Actions
executeget_historyreplayclear_historySafety
safety_checks(default:true) blocks known dangerous patterns:File.Delete,Process.Start,AssetDatabase.DeleteAsset,EditorApplication.Exit, infinite loops (while(true),for(;;))destructiveHint=Trueannotation for MCP clientssafety_checks=falsefor trusted workflowsExample usage
CLI
Files
MCPForUnity/Editor/Tools/ExecuteCode.csServer/src/services/tools/execute_code.pyServer/tests/test_execute_code.pyServer/src/cli/commands/code.pymanifest.jsonTest plan
uv run python -m pytest tests/test_execute_code.py -v)Design decisions
Camera.main,Selection, etc. Infinite loop prevention is handled by safety_checks pattern matching instead.safety_checksnotsandbox: Deliberately named to avoid implying stronger protection than a pattern blocklist provides.🤖 Generated with Claude Code
Summary by Sourcery
Add a new execute_code tool for running arbitrary C# code in the Unity Editor, with history and safety controls, and expose it through the MCP service and CLI.
New Features:
Enhancements:
Tests:
Summary by CodeRabbit