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…
In this repository2 other files it gives its agents.
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
- Very specific: line 105
Use McpToolset + @McpTool + @McpDescription (confirmed working in Rider 2025.3.3):
- Very specific: line 159
All tools use WaitForUnityModel (returns as soon as model != null).
- Very specific: line 168
All failure paths call TryAbortLaunch (best-effort.
- Very specific: line 178
Stale model race: After Refresh.Start() throws (domain reload detected), BackendUnityModel may still point to the pre-reload instance.
- Very specific: line 180
If GetCompilationResult is still cancelled after reconnection, the error message instructs the agent to wait and retry.
- Distinctive: line 198
All files, commit messages, GitHub Issues, and Pull Requests must be written in English.
- Very specific: line 204
Read the /implementation-planning-guide skill to orchestrate the test-first planning workflow
- Very specific: line 205
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 …
≈ “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.
Files
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
| Item | Value |
| Language | Kotlin 2.3.0 |
| Serialization | kotlinx-serialization 1.6.3 (compileOnly) |
| Build plugin | IntelliJ Platform Gradle Plugin 2.17.0 |
| Target IDE | Rider 2026.2 (build 262.8665.329) |
| JDK | JBR 25.0.2 (~/Library/Java/JavaVirtualMachines/jbr-25.0.2) |
| Gradle | 9.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 restoreonce before building. The GradlerestoreDotNettask now handles this automatically, but if you encounterMSB3644: .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 buildPluginNote:
--no-configuration-cacheis required due to incompatibilities with therdgenandgenerateDotNetSdkPropertiestasks under Gradle 9.3.1 configuration cache.
If
dotnetis not found duringrestoreDotNet: The Gradle daemon may have started without/opt/homebrew/binin 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 testMCP 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 useMcpToolset+ annotations.
Important Constraints
kotlinx-serialization-jsonmust becompileOnly— usingimplementationcauses a class collision with the version bundled in the MCP Server plugin.- Rider's "Unit testing configuration" is not XML-reproducible — it is Rd-based and generated
in-memory at runtime. There is no
create_run_configurationMCP tool, and the configuration cannot be persisted as an XML file. - 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. @McpToolannotation 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- All input validation is done on the Kotlin side (fail-fast) —
assemblyNamesandtestModeare validated inRunUnityTestsTool.ktbefore 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 (emptyTestFilterdisconnects Unity Editor)testMode: must be one ofEditMode,edit,PlayMode,play(case insensitive)- Find assembly names in
.asmdeffiles or Rider's Unit Test Explorer.
- 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
.csfile creation/modification. Implemented inEditorConnectionUtils.kt(Kotlin side) andRdConnectionHelper.cs(C# side).- All tools use
WaitForUnityModel(returns as soon as model != null).WaitForStableUnityModel(pollsIsConnectionEstablished()) is intentionally avoided becauseIsConnectionEstablished()can remain False for many minutes after cumulative domain reloads (e.g., after PlayMode test runs).
- All tools use
Cancellation, disconnection, and domain-reload handling —
UnityTestMcpHandler.csmonitors 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 (orWaitForUnityModelreturns a transient model on the first call) →StartReconnect(previousModel)spawnsRunReconnectLoopoff-thread. UsesWaitForModelReconnect(reference-based, notIsConnectionEstablished()) + retry loop to re-launch tests. If the reconnected model is transient (any exception fromLaunchTests),previousModelis updated and the loop waits for the next distinct instance. If timeout expires,TrySetException("did not reconnect within N seconds"). Areconnectingguard prevents concurrent loops from either source.- WHY
WaitForModelReconnectinstead ofWaitForStableUnityModel:IsConnectionEstablished()can remain False for many minutes after cumulative PlayMode domain reloads due to macOS deprioritising the Rd Shell Dispatcher thread (:1), which delaysGetUnityEditorStatepolling. The model is functional long before the flag catches up.
- WHY
- Timeout timer: configurable via
MCP_TOOL_TIMEOUTenv 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. SetMCP_TOOL_TIMEOUTto a smaller value (in milliseconds) to reduce feedback delay in this case.
MCP_TOOL_TIMEOUTapplies 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 helperRdConnectionHelper.GetMcpToolTimeout()reads the env varrun_method_in_unity/unity_play_control: not governed byMCP_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
get_unity_compilation_resultdomain-reload race condition —UnityCompilationMcpHandler.cshandles race conditions that occur when the tool is called while Unity is compiling:- Stale model race: After
Refresh.Start()throws (domain reload detected),BackendUnityModelmay still point to the pre-reload instance.WaitForModelReconnect(inRdConnectionHelper.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
BackendUnityModelinstance that is immediately rejected.GetCompilationResult.Starton this instance throws immediately — eitherOperationCanceledExceptionor a non-OCE Rd exception (e.g.InvalidOperationException) depending on how far the lifetime teardown has progressed.TryCallGetCompilationResultisolates theStartcall in its own try/catch and returns null (retry signal) for any exception from it. A retry loop updatespreviousModelon each transient rejection and waits for the next candidate, eventually reaching the stable connection. - If
GetCompilationResultis still cancelled after reconnection, the error message instructs the agent to wait and retry.
- Stale model race: After
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
- resharper-unity — Rider Unity Support source
BackendUnityModel.kt,FrontendBackendModel.ktRunViaUnityEditorStrategy.cs,UnityNUnitServiceProvider.cs
- mcpExtensionPlugin Demo — MCP extension reference (old API pattern)
- JetBrains MCP Server Plugin — extension point spec
- MCP Server | JetBrains Rider Documentation
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>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
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.

