agentleFS
nowsprintingnowsprinting/mcp-extension-unity/CLAUDE.md

mcp-extension-unity

CLAUDE.md3 starsChanged 5 months agoApache-2.0View on GitHub

A Rider IDE plugin (PoC stage) that extends the built-in JetBrains MCP Server with a custom MCP tool. The goal is to allow Coding Agents (e.g., Claude Code) to run Unity tests through Rider's test infrastructure, rather than invoking Unity directly. Current status: Steps 1–11 complete. Initial connection wait added in Step 11. Rider itself uses two separate Rd (Reactive Distributed) protocol connections: The Kotlin Frontend cannot directly access BackendUnityModel; a custom Rd model (UnityTestMcpModel) bridges the two layers, implemented…

The file

We split this CLAUDE.md into the rules it gives an agent

8 rules. About 0 are common, 1 is distinctive, and 7 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use McpToolset + @McpTool + @McpDescription (confirmed working in Rider 2025.3.3):

    line 105
  2. Very specific:

    All tools use WaitForUnityModel (returns as soon as model != null).

    line 159
  3. Very specific:

    All failure paths call TryAbortLaunch (best-effort.

    line 168
  4. Very specific:

    Stale model race: After Refresh.Start() throws (domain reload detected), BackendUnityModel may still point to the pre-reload instance.

    line 178
  5. Very specific:

    If GetCompilationResult is still cancelled after reconnection, the error message instructs the agent to wait and retry.

    line 180
  6. Distinctive:

    All files, commit messages, GitHub Issues, and Pull Requests must be written in English.

    line 198
  7. Very specific:

    Read the /implementation-planning-guide skill to orchestrate the test-first planning workflow

    line 204
  8. Very specific:

    Add the following final step at the end of the ## Development Workflow section in the plan file (adjust the step number to follow the last …

    line 205

≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

CLAUDE.md — mcp-extension-unity

Project Overview

A Rider IDE plugin (PoC stage) that extends the built-in JetBrains MCP Server with a custom MCP tool. The goal is to allow Coding Agents (e.g., Claude Code) to run Unity tests through Rider's test infrastructure, rather than invoking Unity directly.

Current status: Steps 1–11 complete. Initial connection wait added in Step 11.

Architecture

Coding Agent (Claude Code)
    ↓ MCP (HTTP/SSE)
JetBrains MCP Server (built into Rider 2025.3+)
    ↓ extension point (com.intellij.mcpServer)
[This Plugin — Kotlin Frontend]   ← UnityEditorToolset.kt
    ↓ UnityTestMcpModel (custom Rd: IRdCall<McpRunTestsRequest, McpRunTestsResponse>)
[Plugin Backend — C# / UnityTestMcpHandler]
    ↓ BackendUnityModel.UnitTestLaunch + RunUnitTestLaunch (existing Rd)
Unity Editor
    ↓ TestRunnerApi.Execute()
Test execution (results via TestResult/RunResult signals)

Rider itself uses two separate Rd (Reactive Distributed) protocol connections:

  • Kotlin Frontend ↔ C# Backend: FrontendBackendModel
  • C# Backend ↔ Unity Editor: BackendUnityModel

The Kotlin Frontend cannot directly access BackendUnityModel; a custom Rd model (UnityTestMcpModel) bridges the two layers, implemented in Step 6.

Tech Stack

ItemValue
LanguageKotlin 2.3.0
Serializationkotlinx-serialization 1.6.3 (compileOnly)
Build pluginIntelliJ Platform Gradle Plugin 2.17.0
Target IDERider 2026.2 (build 262.8665.329)
JDKJBR 25.0.2 (~/Library/Java/JavaVirtualMachines/jbr-25.0.2)
Gradle9.3.1

Key Files

mcp-extension-unity/
├── CLAUDE.md                                          # this file
├── build.gradle.kts                                   # build configuration (incl. compileDotNet, prepareSandbox)
├── gradle.properties
├── settings.gradle.kts
├── protocol/                                          # Rd model definition (rdgen)
│   └── src/main/kotlin/model/rider/
│       ├── UnityTestMcpModel.kt                       # Rd DSL: McpRunTestsRequest/Response
│       └── UnityCompilationMcpModel.kt                # Rd DSL: compilation result model
├── src/main/
│   ├── kotlin/com/nowsprinting/mcp_extension_unity/   # Kotlin frontend: McpToolset implementations and Rd model providers
│   ├── generated/                                     # auto-generated Kotlin model (gitignored)
│   └── resources/META-INF/
│       └── plugin.xml                                 # plugin descriptor
├── src/test/
│   └── kotlin/com/nowsprinting/mcp_extension_unity/   # Kotlin unit tests for each tool and utility
├── src/dotnet/
│   ├── McpExtensionUnity.sln
│   └── McpExtensionUnity/                             # C# backend: Rd handlers, model providers, and connection utilities
└── docs/plans/                                        # Implementation plan documents (written in Japanese)

Build

First-time setup: On a fresh clone, run dotnet restore once before building. The Gradle restoreDotNet task now handles this automatically, but if you encounter MSB3644: .NETFramework,Version=v4.7.2 reference assemblies not found, run manually:

dotnet restore src/dotnet/McpExtensionUnity.sln
JAVA_HOME=~/Library/Java/JavaVirtualMachines/jbr-25.0.2/Contents/Home ./gradlew --no-configuration-cache buildPlugin

Note: --no-configuration-cache is required due to incompatibilities with the rdgen and generateDotNetSdkProperties tasks under Gradle 9.3.1 configuration cache.

If dotnet is not found during restoreDotNet: The Gradle daemon may have started without /opt/homebrew/bin in its PATH. Stop the daemon and re-run without a persistent daemon:

JAVA_HOME=~/Library/Java/JavaVirtualMachines/jbr-25.0.2/Contents/Home ./gradlew --stop
PATH="/opt/homebrew/bin:$PATH" JAVA_HOME=~/Library/Java/JavaVirtualMachines/jbr-25.0.2/Contents/Home ./gradlew --no-daemon --no-configuration-cache buildPlugin

Output ZIP is generated under build/distributions/.

Install: Rider → Settings → Plugins → Install Plugin from Disk → select ZIP → restart Rider.

Unit Tests

JAVA_HOME=~/Library/Java/JavaVirtualMachines/jbr-25.0.2/Contents/Home ./gradlew --no-configuration-cache test

MCP Extension Pattern

Use McpToolset + @McpTool + @McpDescription (confirmed working in Rider 2025.3.3):

@Suppress("RedundantSuspendModifier")
class MyToolset : McpToolset {
    @McpTool(name = "tool_name")
    @McpDescription(description = "What this tool does")
    suspend fun tool_name(
        @McpDescription(description = "Parameter description")
        param: String = "default"
    ): MyResult {
    }
}

Register in plugin.xml:

<extensions defaultExtensionNs="com.intellij.mcpServer">
    <mcpToolset implementation="com.nowsprinting.mcp_extension_unity.UnityEditorToolset"/>
</extensions>

Note: The legacy AbstractMcpTool<T> pattern (used in the mcpExtensionPlugin demo) is deprecated in the Rider 2025.3.3 bundled MCP Server. Always use McpToolset + annotations.

Important Constraints

  1. kotlinx-serialization-json must be compileOnly — using implementation causes a class collision with the version bundled in the MCP Server plugin.
  2. Rider's "Unit testing configuration" is not XML-reproducible — it is Rd-based and generated in-memory at runtime. There is no create_run_configuration MCP tool, and the configuration cannot be persisted as an XML file.
  3. Kotlin Frontend cannot directly access BackendUnityModel — the two Rd connections (Frontend↔Backend and Backend↔Unity) are independent. A custom Rd model is required to bridge them.
  4. @McpTool annotation is required — omitting it causes a runtime warning and the toolset is skipped:
    WARN - ReflectionToolsProvider - Cannot load tools for UnityEditorToolset
    java.lang.IllegalArgumentException: No tools found in class ...UnityEditorToolset
  5. All input validation is done on the Kotlin side (fail-fast) — assemblyNames and testMode are validated in RunUnityTestsTool.kt before the Rd call is made. Invalid inputs return an immediate error without reaching the C# backend or Unity Editor.
    • assemblyNames: must contain at least one non-blank name (empty TestFilter disconnects Unity Editor)
    • testMode: must be one of EditMode, edit, PlayMode, play (case insensitive)
    • Find assembly names in .asmdef files or Rider's Unit Test Explorer.
  6. Initial connection wait — All 4 MCP tools wait up to 30 seconds for the Unity Editor to connect before failing with "not connected". This covers the domain-reload window after .cs file creation/modification. Implemented in EditorConnectionUtils.kt (Kotlin side) and RdConnectionHelper.cs (C# side).
    • All tools use WaitForUnityModel (returns as soon as model != null). WaitForStableUnityModel (polls IsConnectionEstablished()) is intentionally avoided because IsConnectionEstablished() can remain False for many minutes after cumulative domain reloads (e.g., after PlayMode test runs).
  7. Cancellation, disconnection, and domain-reload handling — UnityTestMcpHandler.cs monitors three failure paths:

    • lt.OnTermination: Rd lifetime ends (protocol disconnect, Kotlin coroutine cancel) → TrySetCanceled()
    • BackendUnityModel.Advise(null) or initial-launch exception: Unity Editor disconnects mid-run (or WaitForUnityModel returns a transient model on the first call) → StartReconnect(previousModel) spawns RunReconnectLoop off-thread. Uses WaitForModelReconnect (reference-based, not IsConnectionEstablished()) + retry loop to re-launch tests. If the reconnected model is transient (any exception from LaunchTests), previousModel is updated and the loop waits for the next distinct instance. If timeout expires, TrySetException("did not reconnect within N seconds"). A reconnecting guard prevents concurrent loops from either source.
      • WHY WaitForModelReconnect instead of WaitForStableUnityModel: IsConnectionEstablished() can remain False for many minutes after cumulative PlayMode domain reloads due to macOS deprioritising the Rd Shell Dispatcher thread (:1), which delays GetUnityEditorState polling. The model is functional long before the flag catches up.
    • Timeout timer: configurable via MCP_TOOL_TIMEOUT env var (milliseconds per Claude Code spec, default 100000000) → TrySetException("timed out after N seconds")
    • All failure paths call TryAbortLaunch (best-effort; aborts whatever launch is currently on the model).
    • Known limitation: Unity Test Runner manual Cancel may not fire RunResult, causing a wait until timeout. Set MCP_TOOL_TIMEOUT to a smaller value (in milliseconds) to reduce feedback delay in this case.

    MCP_TOOL_TIMEOUT applies to each waiting phase independently:

    • run_unity_tests: test execution wait + domain-reload reconnection wait (both = MCP_TOOL_TIMEOUT)
    • get_unity_compilation_result: Refresh wait + post-Refresh reconnection wait + compilation result wait (each = MCP_TOOL_TIMEOUT); shared helper RdConnectionHelper.GetMcpToolTimeout() reads the env var
    • run_method_in_unity / unity_play_control: not governed by MCP_TOOL_TIMEOUT — these use resharper-unity's existing Rd RPCs directly; a Kotlin-side timeout would orphan the in-flight RPC and risk double-invocation on retry
  8. get_unity_compilation_result domain-reload race condition — UnityCompilationMcpHandler.cs handles race conditions that occur when the tool is called while Unity is compiling:
    • Stale model race: After Refresh.Start() throws (domain reload detected), BackendUnityModel may still point to the pre-reload instance. WaitForModelReconnect (in RdConnectionHelper.cs) requires a different instance (!ReferenceEquals) to ensure the post-reload model is used.
    • Transient model race: During domain reload, the Rd client may briefly expose a new BackendUnityModel instance that is immediately rejected. GetCompilationResult.Start on this instance throws immediately — either OperationCanceledException or a non-OCE Rd exception (e.g. InvalidOperationException) depending on how far the lifetime teardown has progressed. TryCallGetCompilationResult isolates the Start call in its own try/catch and returns null (retry signal) for any exception from it. A retry loop updates previousModel on each transient rejection and waits for the next candidate, eventually reaching the stable connection.
    • If GetCompilationResult is still cancelled after reconnection, the error message instructs the agent to wait and retry.

Reference Documents

  • docs/plans/2026-02-22-poc-rider-mcp-unity-test.md — Full PoC investigation: Rider architecture, Rd model details, MCP extension mechanism, encountered issues, and verification results.

External References

Language Guidelines

  • All files, commit messages, GitHub Issues, and Pull Requests must be written in English.
  • Exception: docs/ — write in Japanese.

Skill Guidelines

<important if="Feature implementation planning (writing or modifying a feature implementation plan in plan mode)"> - Read the `/implementation-planning-guide` skill to orchestrate the test-first planning workflow - Add the following final step at the end of the `## Development Workflow` section in the plan file (adjust the step number to follow the last existing step):
### Step N: Finalize

1. Copy this plan file to `docs/plans/yyyy-MM-dd-<plan-name>.md`
2. Append `## Implementation Notes` to the end of the copied plan file and write the following:
  - Design decisions: choices you made where the spec was ambiguous
  - Deviations: places where you intentionally departed from the spec, and why
  - Tradeoffs: alternatives you considered and why you picked what you did
  - Test changes: Test code modified during the implementation phase
  - Open questions: anything you'd want me to confirm or revise
3. Append an entry to `CHANGELOG.md` under `## [Unreleased]`
  - Do not document changes whose paths start with `.claude/`, `.github/`, and `/src/test/` (test code changes are excluded from the changelog)
  - If the plan only modifies files under `.claude/`, `.github/`, and `/src/test/`, skip this step entirely
</important>

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.