feat+fix: one-click client connection + autotest bug-fix batch#1142
Conversation
Reflect the new onboarding wizard, multi-client one-click flow, Claude Desktop transport auto-coercion, and startup config rewrite in the README. Update the Claude Desktop section of the configurator guide to drop the stale "throw on HTTP" language.
JsonFileMcpConfigurator.CheckStatus deserialized the standard mcpServers layout through McpConfigServer.url, so HTTP configs that use serverUrl (Antigravity 2.0, Windsurf) or httpUrl (Gemini CLI) parsed with configuredUrl=null. The configurator then reported transport=Unknown and status=IncorrectPath even right after a successful Configure() that wrote a valid HTTP entry, and the new startup auto-rewrite path kept rewriting those files every Editor launch. Switch the standard-layout branch to JObject parsing (same shape as the VS Code branch) and accept url / serverUrl / httpUrl. Add CheckStatus tests that lock all three property names against a synthetic configurator.
…very (CoplayDev#1138) The converter was declared public with a default constructor, so generic Newtonsoft scanners (e.g. jillejr's newtonsoft-json-for-unity converters) could pick it up and bind it into JsonConvert.DefaultSettings. Once attached globally, any unrelated project code serializing a struct or class containing a UnityEngine.Object reference would silently get an asset path string back instead of the expected object JSON — purely from having MCP for Unity installed. Make the converter internal and expose Runtime internals to the Editor and test assemblies via a Runtime AssemblyInfo so existing internal consumers (UnityJsonSerializer, GameObjectSerializer) keep compiling.
…ction (CoplayDev#1134) AssetImportWorker is a separate Editor subprocess that doesn't host the MCP transport, so it has no reason to scan loaded assemblies. But our [InitializeOnLoad] path runs there too, and Mono's reflection layer can hard-crash inside type.GetCustomAttribute<T>() when assembly metadata isn't fully restored after domain reload in the worker process — the crash in the linked report freezes/kills the main Editor through the import worker's IPC channel. Detect the worker process via AssetDatabase.IsAssetImportWorkerProcess (looked up reflectively so we tolerate visibility differences across Unity versions) with a -importWorker command-line fallback, and bail out of AutoDiscoverCommands before scanning. As a secondary belt, wrap GetCustomAttribute<T>() in a try/catch helper so any single bad type no longer aborts the entire registration pass.
CoplayDev#946) FastMCP derives the wire schema from each tool's type annotations. execute_custom_tool declared `parameters: dict | None`, which generates a permissive schema some strict clients (Roo Code, certain VSCode MCP adapters) reject during tool discovery — the tool then appears missing even though everything else is healthy. Switch to `dict[str, Any] | None` so the generated schema includes proper additionalProperties bounds. The four other files cited in CoplayDev#946 (custom_tool_service, manage_components, manage_material, manage_texture) were already parametrised in prior commits; execute_custom_tool was the last remaining MCP-exposed tool with a bare `dict` annotation.
…ayDev#865) Multi-agent and sub-agent workflows churn through stdio connections — each new agent triggers a connect → close-stale → exit cycle, so the three associated McpLog.Info calls in StdioBridgeHost flooded the Unity console (the reporter saw 185+ lines in a single session). They're useful for debugging a single hung connection but pure noise during normal multi-agent operation. Move all three to always:false so they only surface when verbose logging is on, matching the existing recv-frame log on the same path. Lifecycle messages that fire once (startup, port switch) and warnings about anomalies (TCS timeout, queue eviction) are left at INFO.
Now that "Configure All Detected Clients" actually does what its name says (auto-rewrite + per-client transport coercion + IsInstalled filtering all landed in the recent client-config work), it's the path we want first-run users on — not buried at the bottom of the panel. Layout changes in McpClientConfigSection.uxml: - Move the Configure-All button to the top of the section, right under the "Client Configuration" header, with a one-line helper underneath. - Wrap the dropdown / status / single-client Configure button / Claude CLI path / project-dir / Manual Configuration foldout in a new "Configure a single client" foldout, collapsed by default. Persist its open/closed state via a new EditorPrefs key. Style changes in Common.uss: - New .primary-button class (bright green, 34px, bold) for the one-click action so it visually distinguishes itself from the regular blue .action-button rows. - Light/dark-aware foldout header styling for the new client-details foldout that matches the existing manual-command-foldout treatment.
…t setup" Helper line under the green button was visual noise — the button label already says what it does. "Configure a single client" was redundant inside a section titled "Client Configuration"; "Per-client setup" reads cleaner. Also drop the now-orphan .primary-button-hint style.
…allowlist
The whole manage_vfx tool dispatcher and every VfxGraph* helper is
gated behind '#if UNITY_VFX_GRAPH', but nothing in the Editor asmdef
ever defined that symbol. Users with com.unity.visualeffectgraph
properly installed (e.g. Unity 6.3 + VFX Graph 17.x) hit the false
"VFX Graph package (com.unity.visualeffectgraph) not installed" branch
for every action, including read-only ones like list_templates and
get_info — reported via Discord.
Add a versionDefines entry to MCPForUnity.Editor.asmdef so Unity sets
UNITY_VFX_GRAPH whenever the VFX Graph package is present at any
version (`expression: 0.0.0`). This is the canonical way to detect
optional packages and lets the existing #if branches do their job.
Also drop ValidateVfxGraphVersion's hard-coded {"12.1"} allowlist,
which only matches Unity 2022.3-era VFX Graph and would still block
CreateAsset on modern installs even after the compile-gate fix. The
asset-level APIs we touch (VisualEffectAsset, AssetDatabase.CopyAsset,
template enumeration via PackageInfo) are stable across the
12.x → 17.x range, so the safer guard is just "package present" with
the compile-time gate handling the real "not installed" path.
…ailure AddDependencyRow's click handlers flipped the button to "Installing..." and only restored it inside a synchronous try/catch. But the per-package call sites passed `() => InstallUpmPackage(...)` — no onComplete — so when the UPM AddAndRemove request eventually completed (success OR failure), nothing in PollUpmRequest's continuation ever told the button to revert. A network timeout on com.unity.cinemachine therefore parked the button on "Installing..." forever (Discord report); same for Removing... on uninstall. The "Install All" path was fine because it already threaded an onComplete callback through. Change the install/uninstall hooks to Action<Action>, thread a `restore` callback from the click handler into the UPM helpers, and invoke it from PollUpmRequest's completion regardless of StatusCode. Roslyn (the one synchronous install) just invokes the callback inline after Install returns. The unrelated CRLF/LF noise in the diff is a side effect of normalizing the file (988 CRLF lines, 41 LF lines pre-existing) onto the dominant CRLF convention while my edits were in flight.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
📝 WalkthroughWalkthroughThis PR adds declarative configurator capabilities (IsInstalled, SupportedTransports), routes configuration through a transport-coercing wrapper, auto-rewrites detected client configs once per editor session, adds a setup-wizard clients step and UI persistence/styling, and includes robustness, visibility, and test additions. ChangesClient Capability System & Automated Configuration
Robustness and Infrastructure Improvements
Estimated code review effort 🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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 |
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
This PR combines a client-configuration UX overhaul (one-click setup, startup drift correction, and updated docs) with a batch of targeted fixes from autotesting (converter visibility, AssetImportWorker safety, MCP schema typing, noisy logs, HTTP URL parsing, VFX Graph detection/version gating, and async dependency button restoration).
Changes:
- Adds “configure detected clients” flows (wizard picker + prominent UI action) and a startup pass that rewrites drifted client configs.
- Improves configurator capability modeling (installed detection + supported transports) and updates docs/README accordingly.
- Fixes several regressions/compat issues (Newtonsoft converter visibility, AssetImportWorker crash, strict client schema typing, HTTP URL property parsing, VFX Graph symbol/version, async UPM UI restore, log verbosity).
Reviewed changes
Copilot reviewed 28 out of 35 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/StartupConfigRewriteTests.cs.meta | Adds Unity meta for new edit-mode test. |
| TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/StartupConfigRewriteTests.cs | Adds tests asserting StartupConfigRewrite exists and has expected guards/attributes. |
| TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/ConfigureDetectedClientsTests.cs.meta | Adds Unity meta for new edit-mode test. |
| TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/ConfigureDetectedClientsTests.cs | Adds tests around ConfigureAllDetectedClients summary accounting. |
| TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/SupportedTransportsTests.cs.meta | Adds Unity meta for new edit-mode test. |
| TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/SupportedTransportsTests.cs | Adds tests for SupportedTransports exposure and specific configurator transport support. |
| TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/IsInstalledTests.cs.meta | Adds Unity meta for new edit-mode test. |
| TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/IsInstalledTests.cs | Adds tests asserting IsInstalled exists and behaves based on config path parent dir. |
| TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/CheckStatusUrlPropertyTests.cs.meta | Adds Unity meta for new edit-mode test. |
| TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/CheckStatusUrlPropertyTests.cs | Adds regression tests for CheckStatus accepting url/serverUrl/httpUrl. |
| TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients.meta | Adds Unity meta for new test folder. |
| Server/src/services/tools/execute_custom_tool.py | Fixes MCP tool schema strictness by typing parameters as dict[str, Any] | None. |
| README.md | Updates onboarding docs to reflect wizard + “configure detected clients” workflows and gotchas. |
| MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs | Makes UnityEngineObjectConverter internal to prevent unintended global Json.NET converter discovery. |
| MCPForUnity/Runtime/AssemblyInfo.cs.meta | Adds Unity meta for new runtime AssemblyInfo. |
| MCPForUnity/Runtime/AssemblyInfo.cs | Exposes runtime internals to editor and test assemblies via InternalsVisibleTo. |
| MCPForUnity/Editor/Windows/MCPSetupWindow.uxml | Splits setup window into dependency step + new “select clients to configure” step. |
| MCPForUnity/Editor/Windows/MCPSetupWindow.cs | Implements step navigation, detected-client list, and configure-selected behavior. |
| MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs | Fixes per-dependency install/uninstall buttons getting stuck by threading completion callbacks. |
| MCPForUnity/Editor/Windows/Components/Common.uss | Adds styling for primary one-click button and per-client foldout. |
| MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.uxml | Promotes “Configure All Detected Clients” and collapses per-client controls into a foldout. |
| MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs | Persists foldout state and adjusts configure-all dialog messaging. |
| MCPForUnity/Editor/Tools/Vfx/VfxGraphAssets.cs | Removes restrictive VFX Graph version allowlist; retains package presence check. |
| MCPForUnity/Editor/Tools/CommandRegistry.cs | Skips reflection scan in AssetImportWorker and makes attribute probing resilient to half-loaded types. |
| MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs | Reduces reconnect log spam by demoting lifecycle logs behind non-verbose mode. |
| MCPForUnity/Editor/Services/StartupConfigRewrite.cs.meta | Adds Unity meta for new startup rewrite service. |
| MCPForUnity/Editor/Services/StartupConfigRewrite.cs | Adds startup-time drift correction by re-checking installed clients once per session. |
| MCPForUnity/Editor/Services/ClientConfigurationService.cs | Adds “installed-only” configure-all and transport coercion when a client can’t support the global preference. |
| MCPForUnity/Editor/MCPForUnity.Editor.asmdef | Defines UNITY_VFX_GRAPH automatically when com.unity.visualeffectgraph is installed. |
| MCPForUnity/Editor/Constants/EditorPrefKeys.cs | Adds EditorPref key for per-client foldout persisted state. |
| MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs | Adds IsInstalled + SupportedTransports, plus URL-property parsing improvements in JsonFile CheckStatus. |
| MCPForUnity/Editor/Clients/IMcpClientConfigurator.cs | Extends configurator interface to include IsInstalled and SupportedTransports. |
| MCPForUnity/Editor/Clients/Configurators/ClaudeDesktopConfigurator.cs | Declares Claude Desktop as stdio-only via SupportedTransports instead of throwing on HTTP. |
| docs/guides/MCP_CLIENT_CONFIGURATORS.md | Updates configurator docs to describe SupportedTransports + coercion flow. |
| .gitignore | Ignores local .worktrees/ directory. |
Files not reviewed (6)
- MCPForUnity/Editor/Services/StartupConfigRewrite.cs.meta: Language not supported
- MCPForUnity/Runtime/AssemblyInfo.cs.meta: Language not supported
- TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients.meta: Language not supported
- TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/CheckStatusUrlPropertyTests.cs.meta: Language not supported
- TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/IsInstalledTests.cs.meta: Language not supported
- TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/SupportedTransportsTests.cs.meta: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| bool currentlyHttp = EditorConfigurationCache.Instance.UseHttpTransport; | ||
| var requested = currentlyHttp ? ConfiguredTransport.Http : ConfiguredTransport.Stdio; | ||
|
|
||
| if (supported.Contains(requested)) return; // user preference is supported, no change | ||
|
|
||
| var chosen = supported[0]; | ||
| bool needHttp = chosen == ConfiguredTransport.Http; | ||
| if (EditorConfigurationCache.Instance.UseHttpTransport != needHttp) | ||
| { | ||
| EditorConfigurationCache.Instance.SetUseHttpTransport(needHttp); | ||
| McpLog.Info( | ||
| $"[{configurator.DisplayName}] auto-selected {chosen} transport (client does not support {requested})."); | ||
| } |
| public McpStatus Status => client.status; | ||
| public ConfiguredTransport ConfiguredTransport => client.configuredTransport; | ||
| public virtual bool SupportsAutoConfigure => true; | ||
| public virtual bool IsInstalled => true; | ||
| private static readonly ConfiguredTransport[] DefaultTransports = | ||
| { ConfiguredTransport.Stdio, ConfiguredTransport.Http }; | ||
| public virtual IReadOnlyList<ConfiguredTransport> SupportedTransports => DefaultTransports; | ||
| public virtual bool SupportsSkills => false; |
| if (UnityEditorInternal.InternalEditorUtility.inBatchMode) return; | ||
| if (SessionState.GetBool(SESSION_GUARD_KEY, false)) return; | ||
| EditorApplication.delayCall += RunOnce; | ||
| } | ||
|
|
| public void Summary_ContainsOnlyInstalledClients() | ||
| { | ||
| var svc = new ClientConfigurationService(); | ||
| var summary = svc.ConfigureAllDetectedClients(); | ||
| int installedCount = svc.GetAllClients().Count(c => c.IsInstalled); | ||
| Assert.AreEqual(installedCount, summary.SuccessCount + summary.FailureCount, | ||
| "Only installed clients should appear in success/failure totals"); |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Server/src/services/tools/execute_custom_tool.py (1)
26-45:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
parameters=Noneis rejected despite the optional signature.Line 26 allows
None, but Line 41 rejects it, so calls that omitparametersfail with"parameters must be an object/dictionary". This breaks no-arg custom tools.💡 Proposed fix
async def execute_custom_tool(ctx: Context, tool_name: str, parameters: dict[str, Any] | None = None) -> MCPResponse: @@ - if not isinstance(parameters, dict): + if parameters is None: + parameters = {} + elif not isinstance(parameters, dict): return MCPResponse( success=False, message="parameters must be an object/dictionary", )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Server/src/services/tools/execute_custom_tool.py` around lines 26 - 45, The function execute_custom_tool rejects parameters set to None even though its signature allows it; update the logic in execute_custom_tool to accept parameters=None by either treating None as an empty dict (e.g., assign parameters = {} when parameters is None) or by changing the validation so None is allowed and only non-None values must be a dict, referencing execute_custom_tool and the existing isinstance(parameters, dict) check to locate where to adjust the behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@MCPForUnity/Editor/Services/ClientConfigurationService.cs`:
- Around line 101-103: The code currently picks the first supported transport
(var chosen = supported[0]) which ignores the configurator's preferred
transport; change the selection to prefer the configurator-declared transport
(e.g., EditorConfigurationCache.Instance.ConfiguredTransport or similar
preferred symbol) if it is present in the supported list, and only fall back
deterministically to supported[0] when the preferred transport isn't available,
then compute needHttp from that chosen transport and compare to
EditorConfigurationCache.Instance.UseHttpTransport as before; update references
to chosen and needHttp in ClientConfigurationService (the block that sets
chosen, needHttp, and checks UseHttpTransport) accordingly.
In `@MCPForUnity/Editor/Services/StartupConfigRewrite.cs`:
- Around line 40-43: The current code reads c.Status into before and skips if
before == McpStatus.NotConfigured which can be stale; instead, call
c.CheckStatus(attemptAutoRewrite: true) first to get an up-to-date status and
then compare the previous in-memory status if needed. Remove the early continue
on c.Status, call var after = c.CheckStatus(attemptAutoRewrite: true)
immediately, and then use the before/after comparison (keeping before = c.Status
if you still want to detect transitions to McpStatus.Configured and increment
rewrote) so auto-rewrite runs for clients whose on-disk state is configured.
In `@MCPForUnity/Editor/Windows/MCPSetupWindow.cs`:
- Around line 163-187: In OnConfigureSelectedClicked, detect when no client
toggles are selected (i.e., iterate clientToggles or check a count of toggles
with toggle.value true) and if zero show an EditorUtility.DisplayDialog
prompting the user to select at least one client and then return early; ensure
you do not call Setup.SetupWindowService.MarkSetupCompleted() or Close() in that
case so the setup window stays open; otherwise proceed with the existing
configure loop, message aggregation, and final dialog as before.
In `@README.md`:
- Around line 90-93: The README contains conflicting guidance about Claude
Desktop's transport: update the manual config section to match the earlier
statement that "Claude Desktop only supports stdio." Specifically, edit any
lines that claim Claude Desktop can use HTTP so they instead say it is
stdio-only, and add a clarifying note that MCP for Unity will force stdio for
Claude Desktop even if HTTP is selected; ensure references to "MCP for Unity",
"Claude Desktop", "HTTP", and "stdio" reflect this single consistent behavior.
In
`@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/ConfigureDetectedClientsTests.cs`:
- Around line 13-25: The test calls the concrete
ClientConfigurationService.ConfigureAllDetectedClients() which performs real,
side-effectful writes and makes the test machine-dependent; modify the test to
use a non-mutating substitute by injecting a fake/config-only configurator or
configuring ClientConfigurationService to use a temporary/dry-run config path
before calling ConfigureAllDetectedClients(): e.g., construct
ClientConfigurationService with a test-only IClientConfigurator mock or set its
config directory/property to a temp folder, then call
ConfigureAllDetectedClients() and assert counts against svc.GetAllClients() so
no real detected client files are written.
In
`@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/StartupConfigRewriteTests.cs`:
- Around line 13-15: The test currently only checks that System.Type.GetType
returned a non-null Type (variable t) but does not verify public visibility;
update the assertion after obtaining t (for the type
"MCPForUnity.Editor.Services.StartupConfigRewrite") to explicitly assert its
visibility (e.g., assert t.IsVisible or Assert.IsTrue(t.IsPublic ||
t.IsNestedPublic) or Assert.IsTrue(t.IsVisible) with a clear message) so the
test truly validates the type is publicly visible.
---
Outside diff comments:
In `@Server/src/services/tools/execute_custom_tool.py`:
- Around line 26-45: The function execute_custom_tool rejects parameters set to
None even though its signature allows it; update the logic in
execute_custom_tool to accept parameters=None by either treating None as an
empty dict (e.g., assign parameters = {} when parameters is None) or by changing
the validation so None is allowed and only non-None values must be a dict,
referencing execute_custom_tool and the existing isinstance(parameters, dict)
check to locate where to adjust the behavior.
🪄 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: 0e7d16c6-9a10-419d-890c-1317c007cf9b
📒 Files selected for processing (35)
.gitignoreMCPForUnity/Editor/Clients/Configurators/ClaudeDesktopConfigurator.csMCPForUnity/Editor/Clients/IMcpClientConfigurator.csMCPForUnity/Editor/Clients/McpClientConfiguratorBase.csMCPForUnity/Editor/Constants/EditorPrefKeys.csMCPForUnity/Editor/MCPForUnity.Editor.asmdefMCPForUnity/Editor/Services/ClientConfigurationService.csMCPForUnity/Editor/Services/StartupConfigRewrite.csMCPForUnity/Editor/Services/StartupConfigRewrite.cs.metaMCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.csMCPForUnity/Editor/Tools/CommandRegistry.csMCPForUnity/Editor/Tools/Vfx/VfxGraphAssets.csMCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.csMCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.uxmlMCPForUnity/Editor/Windows/Components/Common.ussMCPForUnity/Editor/Windows/MCPForUnityEditorWindow.csMCPForUnity/Editor/Windows/MCPSetupWindow.csMCPForUnity/Editor/Windows/MCPSetupWindow.uxmlMCPForUnity/Runtime/AssemblyInfo.csMCPForUnity/Runtime/AssemblyInfo.cs.metaMCPForUnity/Runtime/Serialization/UnityTypeConverters.csREADME.mdServer/src/services/tools/execute_custom_tool.pyTestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients.metaTestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/CheckStatusUrlPropertyTests.csTestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/CheckStatusUrlPropertyTests.cs.metaTestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/IsInstalledTests.csTestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/IsInstalledTests.cs.metaTestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/SupportedTransportsTests.csTestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/SupportedTransportsTests.cs.metaTestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/ConfigureDetectedClientsTests.csTestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/ConfigureDetectedClientsTests.cs.metaTestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/StartupConfigRewriteTests.csTestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/StartupConfigRewriteTests.cs.metadocs/guides/MCP_CLIENT_CONFIGURATORS.md
| var svc = new ClientConfigurationService(); | ||
| var summary = svc.ConfigureAllDetectedClients(); | ||
| int installedCount = svc.GetAllClients().Count(c => c.IsInstalled); | ||
| Assert.AreEqual(installedCount, summary.SuccessCount + summary.FailureCount, | ||
| "Only installed clients should appear in success/failure totals"); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Summary_SkippedCountTracksUninstalled() | ||
| { | ||
| var svc = new ClientConfigurationService(); | ||
| var summary = svc.ConfigureAllDetectedClients(); | ||
| int uninstalledCount = svc.GetAllClients().Count(c => !c.IsInstalled); |
There was a problem hiding this comment.
Avoid side-effectful config writes in EditMode unit tests.
Line 14 and Line 24 invoke ConfigureAllDetectedClients() on the concrete service, which can write real detected client configs and make this suite machine-dependent. Please isolate this with fake configurators/temp-only config paths (or a dry-run code path) so these assertions stay deterministic and non-mutating.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/ConfigureDetectedClientsTests.cs`
around lines 13 - 25, The test calls the concrete
ClientConfigurationService.ConfigureAllDetectedClients() which performs real,
side-effectful writes and makes the test machine-dependent; modify the test to
use a non-mutating substitute by injecting a fake/config-only configurator or
configuring ClientConfigurationService to use a temporary/dry-run config path
before calling ConfigureAllDetectedClients(): e.g., construct
ClientConfigurationService with a test-only IClientConfigurator mock or set its
config directory/property to a temp folder, then call
ConfigureAllDetectedClients() and assert counts against svc.GetAllClients() so
no real detected client files are written.
…fety (PR review) Two correctness bugs flagged on PR CoplayDev#1142 (Copilot + CodeRabbit). CoerceTransportFor only treated `ConfiguredTransport.Http` as an HTTP choice, so a client that declares `SupportedTransports = { HttpRemote }` would get flipped to stdio whenever the user preferred HTTP. The fallback selection was also order-dependent (`supported[0]`), which could pick stdio over Http when the user wanted HTTP and the client supported both. Now: any HTTP variant satisfies an HTTP request, and when we must fall back we prefer the variant that matches user intent (HTTP family vs stdio). StartupConfigRewrite shared two problems with the CommandRegistry path from CoplayDev#1134: it fired in the AssetImportWorker subprocess too (where half-loaded domains can crash Mono on reflection and where writing client configs from a worker is plain wrong), and it skipped rewrite when in-memory Status was NotConfigured *before* refreshing from disk — on a fresh editor load that miscategorized clients that were already configured on disk and prevented the auto-rewrite. Added the same AssetImportWorker guard as CoplayDev#1134 (reflective lookup + cmdline fallback) and dropped the pre-refresh status skip so CheckStatus always reads the file before deciding.
…custom_tool (PR review) Two review nits from PR CoplayDev#1142. McpClientConfiguratorBase.IsInstalled defaulted to `true`, which meant any future configurator that derives directly from the base (without going through JsonFile/Codex/ClaudeCli) would be treated as "detected" by ConfigureAllDetectedClients and could end up writing config files for apps that aren't on the machine. Default to a cheap filesystem check via ParentDirectoryExists(GetConfigPath()); the three existing base classes that override with the same check are harmlessly redundant now, and CLI configurators (where GetConfigPath isn't a real path) keep their own overrides. execute_custom_tool declared `parameters: dict[str, Any] | None = None` in its signature but then rejected `None` at runtime with "parameters must be an object/dictionary". For parameter-less custom tools the type hint and the behavior contradicted each other. Coerce `None` to an empty dict; reject only genuinely-wrong types.
…ish (PR review) Four smaller items flagged on PR CoplayDev#1142. MCPSetupWindow.OnConfigureSelectedClicked marked setup as completed and closed the window even when the user hadn't ticked any client — clicking "Configure Selected" with everything unchecked would silently skip setup forever. Show a prompt and return early instead. README listed Claude Desktop under the HTTP-default group while a neighboring paragraph correctly says Claude Desktop is stdio-only; removed it from the HTTP list and pointed at the stdio block. StartupConfigRewrite_TypeExists only checked that the type resolved, not that it's public — added an explicit IsPublic assertion so the test matches its own message about the [InitializeOnLoad] requirement. ConfigureDetectedClientsTests calls the real ClientConfigurationService which walks McpClientRegistry and Configure()s every detected client — on CI this is harmless (no clients installed) but on a dev machine it mutates real user config files. Marked both tests [Explicit] until we DI the configurator list through the service.
Two pieces of work stacked together.
The bigger one is a client-configuration overhaul that turns the per-client setup into an actual one-click flow. We have been getting a lot of asks and issues around the connection, which is indeed worth looking at. Each configurator now declares whether its target is installed on the machine, which transports it supports, and which one to coerce the user's preference toward.
Configure All Detected Clientsonly writes for clients that actually exist. At Editor startup, every detected client gets a quietCheckStatuspass and a silent rewrite if its config has drifted (transport mismatch, server version drift after a plugin update, etc.). The first-run wizard got a picker step so new users tick the clients they want and one button finishes the wiring. README + onboarding docs updated to match.In the GUI, the prominent action is finally the prominent action. "Configure All Detected Clients" is now a bright green primary button at the top of the section instead of a low-contrast gray button at the bottom, and the per-client dropdown / status / Configure / path-overrides / manual-config row collapses behind a "Per-client setup" foldout (collapsed by default, state remembered between sessions).
Then a batch of bug fixes from a follow-up autotest pass.
#1138 —
UnityEngineObjectConverterwas declaredpublicwith a default constructor, so third-party Newtonsoft scanners (jillejr's converters) auto-discovered it and bound it intoJsonConvert.DefaultSettings. Any unrelated project code that serialized aUnityEngine.Objectreference suddenly got an asset path string back, just from having MCP for Unity installed. Made the converterinternaland added a RuntimeAssemblyInfoexposing internals to the Editor and test assemblies so the existing internal consumers (UnityJsonSerializer,GameObjectSerializer) still see it.#1134 —
CommandRegistry.AutoDiscoverCommandsruns from[InitializeOnLoad], which also fires in theAssetImportWorkersubprocess. Mono can hard-crash insideGetCustomAttribute<T>()on partially-loaded types in that subprocess and take the main Editor down through the IPC channel. The worker doesn't host MCP transport, so the scan is unnecessary there — bail out viaAssetDatabase.IsAssetImportWorkerProcess()(looked up reflectively to tolerate Unity version drift, with a command-line fallback). Also wrapped eachGetCustomAttribute<T>()in a try/catch helper so one bad type can't abort the whole scan.#946 —
execute_custom_tool's signature wasparameters: dict | None. FastMCP generates a permissive schema from that which strict clients like Roo Code reject during tool discovery, so the tool just looked missing. Bumped todict[str, Any] | None. The other four files cited in the issue were already parametrized in earlier commits — this was the last baredicton an MCP-exposed boundary.#865 — Multi-agent and sub-agent workflows churn through stdio connections, and three lifecycle messages fired on every reconnect:
Client connected …,Closing N stale client(s) after new connection,Client handler exited …. The reporter saw 185+ lines in one session. All three moved behindMcpLog.Info(..., always: false)so they only surface with verbose logging on. Startup messages and anomaly warnings stay at INFO.Antigravity 2.0 / Windsurf / Gemini CLI HTTP detection (Discord report, and the one most relevant to the connection overhaul above). After a successful HTTP
Configure, the configurator parsed backconfiguredUrl=nulland reportedIncorrectPath. That alone was annoying; combined with the new startup auto-rewrite, the Editor was silently rewriting those three clients' config files on every launch.ConfigJsonBuilderwrites per client —serverUrlfor Antigravity 2.0 and Windsurf,httpUrlfor Gemini CLI,urlfor everyone else — butJsonFileMcpConfigurator.CheckStatusdeserialized via a model that only knewurl. Switched the read side toJObjectparsing withurl ?? serverUrl ?? httpUrl. EditMode tests lock all three property names against a synthetic configurator.VFX Graph "not installed" when actually installed (Discord report, Unity 6 + VFX Graph 17.x). Two stacked bugs. First, every VFX path is gated behind
#if UNITY_VFX_GRAPH, butMCPForUnity.Editor.asmdefhad an emptyversionDefinesarray, so that symbol was never defined for anyone — every action returned the "not installed" stub regardless of package presence. An older comment in the codebase actually said "Please enable the symbol in the project settings", expecting users to flip a scripting-define by hand. Added a properversionDefinesentry so Unity defines the symbol whenevercom.unity.visualeffectgraphis installed at any version. Second,VfxGraphAssets.ValidateVfxGraphVersion()had a{ "12.1" }allowlist — Unity 6's 14.x / 17.x would still have failedcreate_assetwith"Unsupported VFX Graph version 17.x.x". Dropped the allowlist; kept the cheap runtimePackageInfoexistence check as defense for the brief install/uninstall window. Verified end-to-end on VFX Graph 17.2.0.Setup window dependency buttons stuck on "Installing…" (Discord report).
AddDependencyRowflipped the button to "Installing…" and only restored it inside a synchronous try/catch. UPMClient.AddAndRemoveis async, so on any failure (ETIMEDOUTtodownload.packages.unity.comin the reporter's case) the request eventually completed insidePollUpmRequest, logged the error, and stranded the button. The "Install All" path already threaded a completion callback — per-package didn't. Threaded anAction<Action>throughinstallAction/uninstallActionsoPollUpmRequest's completion (which fires for success and failure both) restores the button label.Closes #865, #946, #1134, #1138.
Summary by CodeRabbit
New Features
Bug Fixes
Style
Documentation
Tests