Skip to content

feat: add execute_code tool for running arbitrary C# in Unity Editor#1001

Merged
Scriptwonder merged 6 commits intoCoplayDev:betafrom
zaferdace:feat/execute-code
Apr 1, 2026
Merged

feat: add execute_code tool for running arbitrary C# in Unity Editor#1001
Scriptwonder merged 6 commits intoCoplayDev:betafrom
zaferdace:feat/execute-code

Conversation

@zaferdace
Copy link
Copy Markdown
Contributor

@zaferdace zaferdace commented Mar 29, 2026

Summary

Adds a built-in execute_code tool that compiles and runs arbitrary C# code inside the Unity Editor. Uses CSharpCodeProvider for in-memory compilation — no Roslyn dependency, no script files created.

This fills the gap between execute_menu_item (limited to menu paths) and the runtime_compilation custom tool (requires Roslyn + opt-in setup). execute_code works out of the box with zero configuration.

Actions

Action Description
execute Compile and run a C# method body, return the result
get_history List past executions with code previews
replay Re-run a history entry with its original settings
clear_history Clear execution history

Safety

  • safety_checks (default: true) blocks known dangerous patterns: File.Delete, Process.Start, AssetDatabase.DeleteAsset, EditorApplication.Exit, infinite loops (while(true), for(;;))
  • Clearly documented as a pattern-based blocklist, not a security sandbox — advanced bypass is possible
  • destructiveHint=True annotation for MCP clients
  • Can be disabled with safety_checks=false for trusted workflows

Example usage

# Get Unity version
execute_code(action="execute", code="return Application.unityVersion;")

# Count GameObjects in scene
execute_code(action="execute", code="return GameObject.FindObjectsOfType<GameObject>().Length;")

# Query camera info
execute_code(action="execute", code='var cam = Camera.main; return new { name = cam.name, ortho = cam.orthographic, size = cam.orthographicSize };')

# Check history
execute_code(action="get_history", limit=5)

# Replay entry
execute_code(action="replay", index=0)

CLI

unity-mcp code execute "return Application.unityVersion;"
unity-mcp code execute -f my_script.cs
unity-mcp code history --limit 5
unity-mcp code replay 0
unity-mcp code clear-history

Files

File Lines Type
MCPForUnity/Editor/Tools/ExecuteCode.cs 329 New
Server/src/services/tools/execute_code.py 85 New
Server/tests/test_execute_code.py 149 New
Server/src/cli/commands/code.py +89 Modified
manifest.json +4 Modified

Test plan

  • 17 Python unit tests pass (uv run python -m pytest tests/test_execute_code.py -v)
  • C# compiles in Unity 6 (6000.3.10f1)
  • Verified execute action returns correct results (Application.unityVersion, Camera.main, GameObject count)
  • Verified safety_checks blocks File.Delete, while(true)
  • Verified history, replay, clear_history actions
  • Verified compile errors show user-friendly line numbers (wrapper offset subtracted)
  • Verify in CI

Design decisions

  • CSharpCodeProvider over Roslyn: No external dependencies, works in any Unity project without setup. Trade-off: older compiler, less diagnostic detail.
  • No timeout parameter: Runs on main thread (required for Unity API access). A timeout would need a background thread which breaks Camera.main, Selection, etc. Infinite loop prevention is handled by safety_checks pattern matching instead.
  • safety_checks not sandbox: Deliberately named to avoid implying stronger protection than a pattern blocklist provides.
  • History stores code previews, not full code: Truncated to 500 chars to avoid large payloads and secret retention.

🤖 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:

  • Introduce an execute_code MCP tool in Unity that compiles and executes C# code in the Editor, with optional safety checks and execution history management.
  • Expose execute_code capabilities via a new server-side execute_code service supporting execute, get_history, replay, and clear_history actions.
  • Extend the CLI code commands to support executing C# snippets or files, viewing history, replaying entries, and clearing history.

Enhancements:

  • Register the execute_code tool in the manifest for discovery by MCP clients.

Tests:

  • Add unit tests covering execute_code server tool behavior, including parameter validation, forwarding to Unity, history limits, error normalization, and response handling.

Summary by CodeRabbit

  • New Features
    • Run C# inside the Unity Editor with execute/get_history/replay/clear actions and capped in-memory execution history.
  • CLI
    • New commands: code execute, code history, code replay, code clear-history (prints results when available).
  • UI
    • Replaced Validation tab with a new "Deps" tab to manage optional dependency installs/uninstalls.
  • Bug Fixes / Reliability
    • Improved compilation and runtime error reporting with adjusted diagnostics.
  • Safety
    • Optional safety checks that block known dangerous patterns (can be disabled).
  • Tests
    • Added tests covering execute/history/replay/clear workflows.
  • Chores
    • Removed some editor menu installer entries.

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>
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Mar 29, 2026

Reviewer's Guide

Introduces 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 history

sequenceDiagram
    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
Loading

Class diagram for new ExecuteCode tool and history entries

classDiagram
    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
Loading

File-Level Changes

Change Details Files
Add Unity-side execute_code tool that compiles and executes C# with pattern-based safety checks and execution history.
  • Implement ExecuteCode MCP tool with actions execute, get_history, replay, and clear_history.
  • Use CSharpCodeProvider to wrap user code in a helper class/method, compile in-memory, and invoke via reflection.
  • Add pattern-based safety checks for destructive APIs and infinite loops, with configurable safety_checks flag.
  • Maintain bounded in-memory execution history with code previews, timing, success flag, and result preview.
  • Normalize compilation/runtime errors into structured ErrorResponse/SuccessResponse objects and adjust compiler error line numbers to user code.
MCPForUnity/Editor/Tools/ExecuteCode.cs
MCPForUnity/Editor/Tools/ExecuteCode.cs.meta
Add server-side execute_code tool that forwards requests to Unity and normalizes responses.
  • Register execute_code as an MCP tool with descriptive docs and destructiveHint annotation.
  • Define execute_code handler supporting execute, get_history, replay, and clear_history actions and their parameters.
  • Route requests to the Unity Editor via send_with_unity_instance/async_send_command_with_retry with per-action param validation and clamping.
  • Normalize Unity responses into a stable dict shape, handling error messages and non-dict responses.
Server/src/services/tools/execute_code.py
Add comprehensive tests for the server-side execute_code tool behavior and parameter routing.
  • Mock Unity transport and instance resolution to test forwarding without a real Unity Editor.
  • Cover execute behavior including code forwarding, safety_checks default/override, and required-code validation.
  • Test get_history, replay, and clear_history param handling, clamping, and required index enforcement.
  • Verify error normalization for failed/invalid Unity responses and that per-action params are isolated.
Server/tests/test_execute_code.py
Extend CLI code command group to support executing code and managing execute_code history.
  • Update code command group description to include execution capabilities.
  • Add code execute subcommand that reads code from arg or file, toggles safety checks, and prints formatted result plus success summary.
  • Add history, replay, and clear-history subcommands that call execute_code with appropriate actions and echo formatted responses.
  • Wire new subcommands through existing config, output formatting, and Unity error handling utilities.
Server/src/cli/commands/code.py
Register execute_code tool in the MCP manifest for discovery.
  • Add execute_code entry with description emphasizing arbitrary C# execution inside Unity Editor.
  • Keep existing execute_custom_tool and execute_menu_item entries unchanged.
manifest.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 29, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new Unity Editor tool ExecuteCode with JSON command handling (execute/get_history/clear_history/replay), in-memory compilation (Roslyn/CodeDom), optional safety checks, capped execution history, server-side MCP tool + CLI integration, Unity editor UI changes, installer menu removals, and tests covering server and Unity behaviors.

Changes

Cohort / File(s) Summary
Unity ExecuteCode Tool
MCPForUnity/Editor/Tools/ExecuteCode.cs, MCPForUnity/Editor/Tools/ExecuteCode.cs.meta
New Editor tool ExecuteCode exposing HandleCommand(JObject) supporting execute/get_history/clear_history/replay; validates inputs, enforces optional safety checks, compiles user code (roslyn/codedom/auto), invokes via reflection, returns structured responses, and stores a capped in-memory history.
Editor prefs & installers
MCPForUnity/Editor/Constants/EditorPrefKeys.cs, MCPForUnity/Editor/Setup/McpForUnitySkillInstaller.cs, MCPForUnity/Editor/Setup/RoslynInstaller.cs
Adds ExecuteCodeCompiler EditorPref key; removes Unity MenuItem-exposed installer menu methods (skill installer and Roslyn menu entry).
Editor window UI / Deps
MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs, MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.uxml
Replaces Validation tab with Deps tab, moves validation UI into Advanced tab, adds dependency install/uninstall and UPM package helpers, Roslyn uninstall flow, and EditorPrefs migration for old Validation selection.
Server MCP tool
Server/src/services/tools/execute_code.py, manifest.json
Adds execute_code tool (registered) that validates/clamps action-specific params, strips None fields, forwards to Unity execute_code command, and normalizes Unity responses to {success, message, data}; tool declared in manifest.
Server CLI
Server/src/cli/commands/code.py
Adds code execute, code history, code replay, code clear-history subcommands; supports inline/file source, --no-safety-checks, and prints normalized/formatted output with conditional Result: display.
Server tests
Server/tests/test_execute_code.py
New pytest suite mocking Unity integration; verifies param forwarding/isolation, required fields, limit clamping, normalization of non-dict responses, and behaviors for execute/history/replay/clear actions.
Unity EditMode tests
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ExecuteCodeTests.cs
New NUnit edit-mode tests exercising ExecuteCode.HandleCommand for successful returns (primitives/collections/Unity API), compilation/runtime errors, safety checks, history, replay, limits, and invalid inputs.

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)
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

codex

Suggested reviewers

  • msanatan

Poem

🐰 I hop through lines and stitch a class so neat,

I wrap your code, compile, and make it fleet.
With checks and history, each run I store,
I nibble bugs and open the door,
A tiny rabbit cheering, "Run once more!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature: adding an execute_code tool for running C# in the Unity Editor.
Description check ✅ Passed The description covers Type of Change (New feature), comprehensive Changes Made section, Testing with detailed verification steps, Related Issues placeholder, and Additional Notes. All major template sections are addressed appropriately.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • 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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6907bad and c18ee94.

📒 Files selected for processing (6)
  • MCPForUnity/Editor/Tools/ExecuteCode.cs
  • MCPForUnity/Editor/Tools/ExecuteCode.cs.meta
  • Server/src/cli/commands/code.py
  • Server/src/services/tools/execute_code.py
  • Server/tests/test_execute_code.py
  • manifest.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>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 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 execute command (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

📥 Commits

Reviewing files that changed from the base of the PR and between c18ee94 and d2847a9.

📒 Files selected for processing (3)
  • MCPForUnity/Editor/Tools/ExecuteCode.cs
  • Server/src/cli/commands/code.py
  • Server/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>
@Scriptwonder
Copy link
Copy Markdown
Collaborator

Using CSharpCodeProvider sounds cool! Will take a look

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 752175c and fa0c6d1.

📒 Files selected for processing (8)
  • MCPForUnity/Editor/Constants/EditorPrefKeys.cs
  • MCPForUnity/Editor/Setup/McpForUnitySkillInstaller.cs
  • MCPForUnity/Editor/Setup/RoslynInstaller.cs
  • MCPForUnity/Editor/Tools/ExecuteCode.cs
  • MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
  • MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.uxml
  • Server/src/services/tools/execute_code.py
  • TestProjects/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

Comment on lines +86 to +94
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";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +866 to +900
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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd /tmp && find . -name "MCPForUnityEditorWindow.cs" -type f 2>/dev/null | head -5

Repository: CoplayDev/unity-mcp

Length of output: 45


🏁 Script executed:

git ls-files | grep -i "mcp.*window" | head -20

Repository: CoplayDev/unity-mcp

Length of output: 1293


🏁 Script executed:

git ls-files | grep -E "MCPForUnity.*\.cs$" | head -20

Repository: 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 f

Repository: CoplayDev/unity-mcp

Length of output: 407


🏁 Script executed:

wc -l MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs

Repository: CoplayDev/unity-mcp

Length of output: 120


🏁 Script executed:

cat -n MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs | head -100

Repository: CoplayDev/unity-mcp

Length of output: 4228


🏁 Script executed:

sed -n '850,950p' MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs | cat -n

Repository: CoplayDev/unity-mcp

Length of output: 5289


🏁 Script executed:

sed -n '810,969p' MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs | tail -100

Repository: CoplayDev/unity-mcp

Length of output: 4607


🏁 Script executed:

rg "RefreshValidationSection|Refresh.*Validation" MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs -A 5 -B 5

Repository: 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs (1)

283-286: Store the McpValidationSection reference 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 McpValidationSection implementation, it registers RegisterValueChangedCallback in 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 in McpValidationSection itself.

🤖 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.

CheckBlockedPatterns returns "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 CheckBlockedPatterns or 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

📥 Commits

Reviewing files that changed from the base of the PR and between fa0c6d1 and 88fc8f1.

📒 Files selected for processing (2)
  • MCPForUnity/Editor/Tools/ExecuteCode.cs
  • MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs

@Scriptwonder
Copy link
Copy Markdown
Collaborator

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.

@Scriptwonder Scriptwonder merged commit 97a61ef into CoplayDev:beta Apr 1, 2026
1 check passed
@zaferdace
Copy link
Copy Markdown
Contributor Author

Thanks for feedback. Async/await support will make execute_code much more powerful for users.
No questions on my end. Happy to contribute.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants