Skip to content

Conversation

msanatan
Copy link
Contributor

@msanatan msanatan commented Aug 21, 2025

I had some issues with Windsurf not detecting the MCP servers. Kiro continued to work fine. When I checked Kiro's config, the editor added more fields. When I used Kiro's config in Windsurf, it's working pretty consistently again... I'm over the IDE, but this should help other Windsurf users.

To update the auto-config and the manual config, I created a helper function

Summary by CodeRabbit

  • New Features

    • Centralized helper to build and update Unity MCP configuration JSON for supported clients (VSCode, Windsurf, Cursor, Kiro).
  • Refactor

    • Editor window now delegates manual config generation and config merging to the centralized builder for consistent output and safe updates.
  • Tests

    • Added edit-mode tests for manual JSON generation and writing/merging across clients; updated test namespaces.
  • Chores

    • Added Unity asset metadata (.meta) for new test and editor assets.
    • Normalize file writes to UTF-8 without BOM for config, heartbeat, script, shader, and port files.

@msanatan msanatan self-assigned this Aug 21, 2025
Copy link
Contributor

coderabbitai bot commented Aug 21, 2025

Walkthrough

Adds a ConfigJsonBuilder helper to build/merge Unity MCP JSON, refactors MCPForUnityEditorWindow to use it (centralized merge and UTF‑8 no‑BOM writes), adds Windows EditMode tests and related .meta files, adjusts a test namespace, and updates several file writes to use UTF‑8 without BOM.

Changes

Cohort / File(s) Summary
Config builder & editor integration
UnityMcpBridge/Editor/Helpers/ConfigJsonBuilder.cs, UnityMcpBridge/Editor/Helpers/ConfigJsonBuilder.cs.meta, UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs
New public static ConfigJsonBuilder with BuildManualConfigJson and ApplyUnityServerToExistingConfig; MCPForUnityEditorWindow refactored to use the builder, merge into a JObject root, and write serialized JSON as UTF‑8 without BOM.
File encoding fixes (UTF‑8 no BOM)
UnityMcpBridge/Editor/Helpers/PortManager.cs, UnityMcpBridge/Editor/MCPForUnityBridge.cs, UnityMcpBridge/Editor/Tools/ManageScript.cs, UnityMcpBridge/Editor/Tools/ManageShader.cs
Replaced File.WriteAllText calls to explicitly use UTF‑8 encoding without BOM for port/heartbeat, script, and shader writes.
Windows EditMode tests & metas
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/ManualConfigJsonBuilderTests.cs, TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/WriteToConfigTests.cs, TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/*.meta, TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows.meta
Adds tests for BuildManualConfigJson and WriteToConfig across client types, setup/teardown and IO helpers, plus corresponding Unity .meta files.
Tools test namespace + meta
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandRegistryTests.cs, TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools.meta
Changes test namespace to MCPForUnityTests.Editor.Tools and adds Tools folder .meta.
Misc folder metas
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools.meta, .../Windows.meta
Adds folder-level Unity .meta files for new/updated test directories.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as Editor User
  participant Win as MCPForUnityEditorWindow
  participant FS as File System
  participant CJ as ConfigJsonBuilder
  note over Win,CJ: WriteToConfig (merge → write UTF‑8 no BOM)

  User->>Win: Click "Write to Config"
  Win->>FS: Read existing config (if present)
  Win->>CJ: ApplyUnityServerToExistingConfig(root, uvPath, serverSrc, client)
  CJ-->>Win: Updated JObject root
  Win->>FS: Serialize (UTF‑8 no BOM) → write temp file → replace target
  FS-->>User: Config file updated
Loading
sequenceDiagram
  autonumber
  actor User as Editor User
  participant Win as MCPForUnityEditorWindow
  participant CJ as ConfigJsonBuilder
  note over Win,CJ: Manual JSON generation

  User->>Win: Open Manual Instructions
  Win->>CJ: BuildManualConfigJson(uvPath, pythonDir, client)
  CJ-->>Win: Indented JSON string
  Win-->>User: Display manual JSON
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Scriptwonder
  • dsarno

Poem

A rabbit taps keys with delight and care,
Merging JSON and tests into one tidy lair.
Servers line up, args and types in bloom,
UTF‑8 hops in, no BOM in the room.
Thump-thump — tests pass, celebrate with a carrot 🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@msanatan
Copy link
Contributor Author

This will close #228

Copy link
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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs (2)

1079-1086: Potential null-dereference and invalid write when serverSrc cannot be resolved.

serverSrc can be null if ResolveServerSrc() returns null, which will:

  • Throw on Line 1081: serverSrc.IndexOf(...) on Windows.
  • Produce args containing a null directory path.

Guard serverSrc before the Windows PackageCache check and abort early if server.py cannot be found.

Apply this patch near the existing logic:

-            string serverSrc = ExtractDirectoryArg(existingArgs);
-            bool serverValid = !string.IsNullOrEmpty(serverSrc)
-                && System.IO.File.Exists(System.IO.Path.Combine(serverSrc, "server.py"));
-            if (!serverValid)
-            {
-                serverSrc = ResolveServerSrc();
-            }
+            string serverSrc = ExtractDirectoryArg(existingArgs);
+            bool serverValid = !string.IsNullOrEmpty(serverSrc)
+                && System.IO.File.Exists(System.IO.Path.Combine(serverSrc, "server.py"));
+            if (!serverValid)
+            {
+                serverSrc = ResolveServerSrc();
+            }
+            // Abort early if still unresolved
+            if (string.IsNullOrEmpty(serverSrc) || !System.IO.File.Exists(System.IO.Path.Combine(serverSrc, "server.py")))
+            {
+                return "Python server not found. Please install or select the server directory.";
+            }
 
-            // Hard-block PackageCache on Windows unless dev override is set
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
-                && serverSrc.IndexOf(@"\Library\PackageCache\", StringComparison.OrdinalIgnoreCase) >= 0
+            // Hard-block PackageCache on Windows unless dev override is set
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
+                && serverSrc.IndexOf(@"\Library\PackageCache\", StringComparison.OrdinalIgnoreCase) >= 0
                 && !UnityEditor.EditorPrefs.GetBool("MCPForUnity.UseEmbeddedServer", false))
             {
                 serverSrc = ServerInstaller.GetServerPath();
+                if (string.IsNullOrEmpty(serverSrc) || !System.IO.File.Exists(System.IO.Path.Combine(serverSrc, "server.py")))
+                {
+                    return "Installed server not found. Please install or select the server directory.";
+                }
             }

Additionally, consider normalizing serverSrc with Path.GetFullPath() before writing args to avoid later mismatches.


882-909: VSCode Manual Setup bypasses ConfigJsonBuilder and uses outdated schema (mcp.servers) without type=stdio.

This path reintroduces the inconsistency that the PR is fixing. Use ConfigJsonBuilder.BuildManualConfigJson for VSCode as well so we emit top-level "servers" and include type:"stdio".

Apply this diff:

                     if (mcpClient.mcpType == McpTypes.VSCode)
                     {
-                        string pythonDir = FindPackagePythonDirectory();
-                        string uvPath = FindUvPath();
-                        if (uvPath == null)
-                        {
-                            UnityEngine.Debug.LogError("UV package manager not found. Cannot configure VSCode.");
-                            return;
-                        }
-                        
-                        var vscodeConfig = new
-                        {
-                            mcp = new
-                            {
-                                servers = new
-                                {
-                                    unityMCP = new
-                                    {
-                                        command = uvPath,
-                                        args = new[] { "run", "--directory", pythonDir, "server.py" }
-                                    }
-                                }
-                            }
-                        };
-                        JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
-                        string manualConfigJson = JsonConvert.SerializeObject(vscodeConfig, jsonSettings);
-                        VSCodeManualSetupWindow.ShowWindow(configPath, manualConfigJson);
+                        string pythonDir = FindPackagePythonDirectory();
+                        string uvPath = FindUvPath();
+                        if (uvPath == null)
+                        {
+                            UnityEngine.Debug.LogError("UV package manager not found. Cannot configure VSCode.");
+                            return;
+                        }
+                        // Reuse centralized builder to ensure correct schema and fields
+                        string manualConfigJson = ConfigJsonBuilder.BuildManualConfigJson(uvPath, pythonDir, mcpClient);
+                        VSCodeManualSetupWindow.ShowWindow(configPath, manualConfigJson);
                     }
🧹 Nitpick comments (13)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/WriteToConfigTests.cs (7)

35-39: Fake uv shim is sufficient; minor hardening optional.

Your stub prints a valid version string regardless of args, which is fine. If you want stricter behavior, you could parse $1 and only print on --version.


55-60: Clean up UvPath EditorPrefs in TearDown to prevent cross-test leakage.

WriteToConfig sets MCPForUnity.UvPath on success. Delete it in TearDown so this test suite doesn’t influence others.

Apply this diff:

         // Clean up editor preferences set during SetUp
         EditorPrefs.DeleteKey("MCPForUnity.ServerSrc");
         EditorPrefs.DeleteKey("MCPForUnity.LockCursorConfig");
+        EditorPrefs.DeleteKey("MCPForUnity.UvPath");

64-79: Assert command and args normalization as part of the contract.

After WriteToConfig, we expect:

  • command == _fakeUvPath (validated and reused)
  • args == ["run","--directory", _serverSrcDir, "server.py"]

Add these assertions to make regressions visible.

Apply this diff:

             var root = JObject.Parse(File.ReadAllText(configPath));
             var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
             Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
             Assert.NotNull(unity["env"], "env should be present for all clients");
             Assert.IsTrue(unity["env"]!.Type == JTokenType.Object, "env should be an object");
             Assert.AreEqual(false, (bool)unity["disabled"], "disabled:false should be set for Windsurf when missing");
+            Assert.AreEqual(_fakeUvPath, (string)unity["command"], "command should be set to validated uv path");
+            CollectionAssert.AreEqual(
+                new[] { "run", "--directory", _serverSrcDir, "server.py" },
+                unity["args"]!.ToObject<string[]>(),
+                "args should be canonicalized to the resolved server src");

81-96: Mirror the command/args assertions for Kiro.

Apply this diff:

             var root = JObject.Parse(File.ReadAllText(configPath));
             var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
             Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
             Assert.NotNull(unity["env"], "env should be present for all clients");
             Assert.IsTrue(unity["env"]!.Type == JTokenType.Object, "env should be an object");
             Assert.AreEqual(false, (bool)unity["disabled"], "disabled:false should be set for Kiro when missing");
+            Assert.AreEqual(_fakeUvPath, (string)unity["command"]);
+            CollectionAssert.AreEqual(
+                new[] { "run", "--directory", _serverSrcDir, "server.py" },
+                unity["args"]!.ToObject<string[]>());

98-112: Also assert args for Cursor to ensure path migration happened.

Apply this diff:

             var root = JObject.Parse(File.ReadAllText(configPath));
             var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
             Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
             Assert.IsNull(unity["env"], "env should not be added for non-Windsurf/Kiro clients");
             Assert.IsNull(unity["disabled"], "disabled should not be added for non-Windsurf/Kiro clients");
+            CollectionAssert.AreEqual(
+                new[] { "run", "--directory", _serverSrcDir, "server.py" },
+                unity["args"]!.ToObject<string[]>(),
+                "args should be canonicalized to the resolved server src for Cursor as well");

114-129: And for VSCode, assert command/args (plus existing type check).

Apply this diff:

             var root = JObject.Parse(File.ReadAllText(configPath));
             var unity = (JObject)root.SelectToken("servers.unityMCP");
             Assert.NotNull(unity, "Expected servers.unityMCP node");
             Assert.IsNull(unity["env"], "env should not be added for VSCode client");
             Assert.IsNull(unity["disabled"], "disabled should not be added for VSCode client");
             Assert.AreEqual("stdio", (string)unity["type"], "VSCode entry should include type=stdio");
+            Assert.AreEqual(_fakeUvPath, (string)unity["command"]);
+            CollectionAssert.AreEqual(
+                new[] { "run", "--directory", _serverSrcDir, "server.py" },
+                unity["args"]!.ToObject<string[]>());

131-160: Great preservation test; add one more assert to confirm args were updated.

Confirm that the directory migrated from /old/path to _serverSrcDir while env/disabled remained intact.

Apply this diff:

             var root = JObject.Parse(File.ReadAllText(configPath));
             var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
             Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
             Assert.AreEqual("bar", (string)unity["env"]!["FOO"], "Existing env should be preserved");
             Assert.AreEqual(true, (bool)unity["disabled"], "Existing disabled value should be preserved");
+            CollectionAssert.AreEqual(
+                new[] { "run", "--directory", _serverSrcDir, "server.py" },
+                unity["args"]!.ToObject<string[]>(),
+                "args should be updated to the new server src while preserving other fields");
UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs (2)

1025-1066: Dynamic JSON access is brittle; consider uniform JObject traversal.

existingConfig as dynamic plus try/catch is workable but fragile. Using JObject.Parse with SelectToken("servers.unityMCP.command")/Value() avoids binder issues and allocations from JObject.FromObject later.

If you choose to refactor, a minimal pattern:

var root = string.IsNullOrWhiteSpace(existingJson) ? new JObject() : JObject.Parse(existingJson);
var unity = (JObject)(isVSCode ? root.SelectToken("servers.unityMCP") : root.SelectToken("mcpServers.unityMCP"));
string existingCommand = unity?["command"]?.Value<string>();
string[] existingArgs = unity?["args"]?.ToObject<string[]>();

1701-1779: Consolidate UV discovery logic into a single implementation

To avoid drift and maintenance overhead, delegate all UV‐path resolution in MCPForUnityEditorWindow to the existing ServerInstaller.FindUvPath (exposed via ExecPath.ResolveUv) instead of duplicating it.

• Remove the private FindUvPath method in UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs (lines 1701–1779).
• Replace each call to FindUvPath() (in the early UV check at line 724; VS Code setup at 885; quick‐info visibility at 923; existing‐command fallback at 1068; manual JSON builder at 1140; and manual setup at 1346) with:

string uvPath = ExecPath.ResolveUv()  
                 ?? (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "uv.exe" : "uv");  

• Keep the existing IsValidUvInstallation(...) helper in MCPForUnityEditorWindow for fast UI validation feedback where needed.

This centralizes UV detection and ensures all callers use the same logic and fallback behavior.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/ManualConfigJsonBuilderTests.cs (3)

26-38: Add explicit command/args assertions for Windsurf.

This complements the env/disabled checks and catches regressions in argument ordering.

Apply this diff:

             var root = JObject.Parse(json);
             var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
             Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
             Assert.NotNull(unity["env"], "env should be included");
             Assert.AreEqual(false, (bool)unity["disabled"], "disabled:false should be added for Windsurf");
             Assert.IsNull(unity["type"], "type should not be added for non-VSCode clients");
+            Assert.AreEqual("/usr/bin/uv", (string)unity["command"]);
+            CollectionAssert.AreEqual(
+                new[] { "run", "--directory", "/path/to/server", "server.py" },
+                unity["args"]!.ToObject<string[]>());

40-52: Ditto for Cursor: assert command/args too.

Ensures manual JSON is end-to-end correct.

Apply this diff:

             var root = JObject.Parse(json);
             var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
             Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
             Assert.IsNull(unity["env"], "env should not be added for Cursor");
             Assert.IsNull(unity["disabled"], "disabled should not be added for Cursor");
             Assert.IsNull(unity["type"], "type should not be added for non-VSCode clients");
+            Assert.AreEqual("/usr/bin/uv", (string)unity["command"]);
+            CollectionAssert.AreEqual(
+                new[] { "run", "--directory", "/path/to/server", "server.py" },
+                unity["args"]!.ToObject<string[]>());

6-9: Consider adding a Kiro case to mirror Windsurf expectations.

Same behavior as Windsurf (env present, disabled=false). Increases confidence across supported clients.

Apply this diff to add a new test:

     public class ManualConfigJsonBuilderTests
     {
+        [Test]
+        public void Kiro_ManualJson_HasMcpServersEnv_DisabledFalse()
+        {
+            var client = new McpClient { name = "Kiro", mcpType = McpTypes.Kiro };
+            string json = ConfigJsonBuilder.BuildManualConfigJson("/usr/bin/uv", "/path/to/server", client);
+
+            var root = JObject.Parse(json);
+            var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
+            Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
+            Assert.NotNull(unity["env"], "env should be included");
+            Assert.AreEqual(false, (bool)unity["disabled"], "disabled:false should be added for Kiro");
+            Assert.IsNull(unity["type"], "type should not be added for non-VSCode clients");
+            Assert.AreEqual("/usr/bin/uv", (string)unity["command"]);
+            CollectionAssert.AreEqual(
+                new[] { "run", "--directory", "/path/to/server", "server.py" },
+                unity["args"]!.ToObject<string[]>());
+        }
UnityMcpBridge/Editor/Helpers/ConfigJsonBuilder.cs (1)

43-51: Docstring clarity: env is ensured only for Windsurf/Kiro.

The summary “Ensures env exists” is a bit ambiguous. Tighten wording to reflect the current conditional behavior.

Apply this diff:

-    /// Centralized builder that applies all caveats consistently.
-    /// - Sets command/args with provided directory
-    /// - Ensures env exists
-    /// - Adds type:"stdio" for VSCode
-    /// - Adds disabled:false for Windsurf/Kiro only when missing
+    /// Centralized builder that applies all caveats consistently.
+    /// - Sets command/args with provided directory
+    /// - Ensures env exists only for Windsurf/Kiro
+    /// - Adds type:"stdio" for VSCode
+    /// - Adds disabled:false for Windsurf/Kiro only when missing
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between eeb67e8 and 2c1630e.

📒 Files selected for processing (10)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools.meta (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandRegistryTests.cs (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows.meta (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/ManualConfigJsonBuilderTests.cs (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/ManualConfigJsonBuilderTests.cs.meta (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/WriteToConfigTests.cs (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/WriteToConfigTests.cs.meta (1 hunks)
  • UnityMcpBridge/Editor/Helpers/ConfigJsonBuilder.cs (1 hunks)
  • UnityMcpBridge/Editor/Helpers/ConfigJsonBuilder.cs.meta (1 hunks)
  • UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/WriteToConfigTests.cs (3)
UnityMcpBridge/Editor/Models/McpClient.cs (1)
  • McpClient (3-45)
UnityMcpBridge/Editor/Helpers/ConfigJsonBuilder.cs (2)
  • JObject (31-41)
  • JObject (79-85)
UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs (1)
  • MCPForUnityEditorWindow (21-2238)
UnityMcpBridge/Editor/Helpers/ConfigJsonBuilder.cs (1)
UnityMcpBridge/Editor/Models/McpClient.cs (1)
  • McpClient (3-45)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/ManualConfigJsonBuilderTests.cs (3)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/WriteToConfigTests.cs (5)
  • Test (64-79)
  • Test (81-96)
  • Test (98-112)
  • Test (114-129)
  • Test (131-160)
UnityMcpBridge/Editor/Models/McpClient.cs (1)
  • McpClient (3-45)
UnityMcpBridge/Editor/Helpers/ConfigJsonBuilder.cs (4)
  • ConfigJsonBuilder (7-86)
  • BuildManualConfigJson (9-29)
  • JObject (31-41)
  • JObject (79-85)
UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs (2)
UnityMcpBridge/Editor/Helpers/ConfigJsonBuilder.cs (4)
  • JObject (31-41)
  • JObject (79-85)
  • ConfigJsonBuilder (7-86)
  • BuildManualConfigJson (9-29)
UnityMcpBridge/Editor/Helpers/ServerInstaller.cs (1)
  • FindUvPath (259-422)
🔇 Additional comments (14)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools.meta (1)

1-8: LGTM — correct Unity folder meta

The folder asset meta looks standard (DefaultImporter with empty fields). This should stabilize GUID-based references for collaborators and CI. No action required.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows.meta (1)

1-8: LGTM — Windows tests folder meta added

Consistent with Unity’s folder meta schema. This avoids GUID churn and keeps the new Windows EditMode tests discoverable in the project. No action required.

UnityMcpBridge/Editor/Helpers/ConfigJsonBuilder.cs.meta (1)

1-11: LGTM — script meta for Editor helper

MonoImporter settings look standard, and the file resides under an Editor path, so it will compile into the Editor assembly as intended. No action required.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/WriteToConfigTests.cs.meta (1)

1-11: LGTM — test script meta

Appropriate MonoImporter settings for a C# test under EditMode. No changes needed.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandRegistryTests.cs (1)

6-6: Namespace alignment verified

All symbols resolve correctly under the MCPForUnity.Editor.Tools namespace:

  • UnityMcpBridge/Editor/Tools/CommandRegistry.cs defines
    namespace MCPForUnity.Editor.Tools { … public static class CommandRegistry }
  • UnityMcpBridge/Editor/Tools/ManageGameObject.cs defines
    namespace MCPForUnity.Editor.Tools { … public static class ManageGameObject }
    and exposes
    public static object HandleCommand(JObject @params)
  • The test file includes using MCPForUnity.Editor.Tools; and will correctly bind to both types.

No further action is needed.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/ManualConfigJsonBuilderTests.cs.meta (1)

1-11: Unity meta file looks correct.

Standard MonoImporter metadata; nothing actionable.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/WriteToConfigTests.cs (1)

25-31: Pragmatic Windows skip to avoid ProcessStartInfo pitfalls.

Good call to skip on Windows, given cmd/bat + UseShellExecute=false issues. Keeps CI stable.

UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs (3)

12-12: JObject usage import is appropriate.

Required for the new centralized config update path.


1099-1107: Good: centralize merge via ConfigJsonBuilder and serialize once.

Switching to ApplyUnityServerToExistingConfig reduces branching and keeps VSCode/non-VSCode parity in one place.


1139-1148: Nice: manual instructions now source JSON from the builder.

This keeps VSCode/other clients consistent when invoked via ShowManualInstructionsWindow.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/ManualConfigJsonBuilderTests.cs (1)

10-24: VSCode path validation is solid.

Covers container key, command, args, and type=stdio; also asserts env/disabled absence.

UnityMcpBridge/Editor/Helpers/ConfigJsonBuilder.cs (3)

9-29: Manual JSON builder reads clean and centralized.

Correctly chooses servers vs. mcpServers and uses PopulateUnityNode to avoid drift.


31-41: Apply to existing config path is safe and idempotent.

Ensures container exists and preserves preexisting unityMCP subfields not touched by PopulateUnityNode.


65-76: Non-destructive defaults are handled well.

Only adding env/disabled when missing avoids clobbering user overrides.

@msanatan
Copy link
Contributor Author

@Scriptwonder @dsarno can one of you check this out whenever you got the time?

I couldn't connect using Windsurf (before version 3, it just stopped working) but Kiro was working correctly. I much rather use Windsurf than Kiro, when trying to debug I noticed that Kiro added more fields to its JSON. I copied it over to Windsurf and tinkered to get the minimal config that worked consistently for Windsurf.

The logic to add was simple, but after having to update 4 different functions in the code for consistency, I centralized it a bit. The tests ensure that other IDEs are unaffected. And Windsurf has been working really well for me since

@Scriptwonder
Copy link
Collaborator

I will do it this weekend. I am not a Windsurf user so it might take a bit of time to set it up.

@dsarno
Copy link
Collaborator

dsarno commented Aug 22, 2025

Tested on Mac OS / Apple Silicon, and found the same issue we've been seeing setting up with Claude Desktop lately: Windsurf doesn't like when the mcp_config.json is encoded UTF-8 with BOM. If the file is re-encoded without BOM (just UTF-8), then it works. (see bottom screenshot). Recommend double checking this in the code and we can re-test on Mac.

image image

@msanatan
Copy link
Contributor Author

msanatan commented Aug 22, 2025 via email

string mergedJson = JsonConvert.SerializeObject(existingConfig, jsonSettings);
string mergedJson = JsonConvert.SerializeObject(existingRoot, jsonSettings);
string tmp = configPath + ".tmp";
System.IO.File.WriteAllText(tmp, mergedJson, System.Text.Encoding.UTF8);
Copy link
Collaborator

@dsarno dsarno Aug 22, 2025

Choose a reason for hiding this comment

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

@msanatan is this the fix?

- System.IO.File.WriteAllText(tmp, mergedJson, System.Text.Encoding.UTF8);
+ System.IO.File.WriteAllText(tmp, mergedJson, new System.Text.UTF8Encoding(false));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, it's the false flag

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just pushed it up!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We call WriteAllText in a few other files, should I add this parameter to those calls as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dsarno where did you see those Windsurf errors btw? What flagged the file encoding?

Copy link
Collaborator

Choose a reason for hiding this comment

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

That fixed it for me, nice!

I'm no BOM expert, but from what I understand now, it's mainly a legacy thing and the fact that it trips up multiple clients is a sign that we should err on the side of not using it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@msanatan I'm not sure if this answers your question, but -- there was no specific error, it was that Windsurf (on Mac / Silicon) was silently failing to process the mcp config JSON -- no MCP servers appeared in its dialog, even when the config was correct. That's the same thing that was happening with Claude to me and a few other people in recent days. Somehow we were using the BOM encoding to write the config files, may or may not have been a change I made when updating config stuff last week / week before.

So I asked Windsurf to look at its own config file, and say why it wasn't working, and it confirmed that was the issue.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@msanatan Oh I got you -- the one in the screenshot. That was in the Windsurf developer console (where Windsurf told me to look):


(Windsurf) 2025-08-21 17:13:40 [ERROR]: Error refreshing MCP servers [unknown] failed to extract specs from json: failed to parse JSON config: hujson: line 1, column 1: invalid character '\ufeff' at start of value: hujson: line 1, column 1: invalid character '\ufeff' at start of value
workbench.desktop.main.js:3585 Extension Host
workbench.desktop.main.js:3585 (Windsurf) 2025-08-21 17:13:40 [ERROR]: Error refreshing MCP servers [unknown] failed to extract specs from json: failed to parse JSON config: hujson: line 1, column 1: invalid character '\ufeff' at start of value: hujson: line 1, column 1: invalid character '\ufeff' at start of value

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for sharing! It's always good to learn how we find the problems, and solve them 🙂

Copy link
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
UnityMcpBridge/Editor/Tools/ManageScript.cs (1)

234-255: Ensure explicit UTF-8 no-BOM in all File.WriteAllText calls

The sweep uncovered four occurrences of File.WriteAllText in your test suite that don’t specify UTF8Encoding(false), which means they’ll use the platform default (potentially UTF-8 with BOM). To enforce consistency and avoid BOM-related issues, please update these calls to include the no-BOM encoding overload.

Locations to update:

  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/WriteToConfigTests.cs:37
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/WriteToConfigTests.cs:43
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/WriteToConfigTests.cs:150
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/WriteToConfigTests.cs:216

Suggested change for each:

- File.WriteAllText(path, contents);
+ File.WriteAllText(path, contents, new System.Text.UTF8Encoding(false));
🧹 Nitpick comments (6)
UnityMcpBridge/Editor/MCPForUnityBridge.cs (2)

706-724: Make heartbeat writes atomic to avoid partial reads by clients

Windsurf/Kiro may read this file while it’s being updated. Use a temp file + replace to ensure readers never see truncated JSON.

Apply this diff in WriteHeartbeat:

-                File.WriteAllText(filePath, JsonConvert.SerializeObject(payload), new System.Text.UTF8Encoding(false));
+                var json = JsonConvert.SerializeObject(payload);
+                var enc = new System.Text.UTF8Encoding(false);
+                var tmp = filePath + ".tmp";
+                File.WriteAllText(tmp, json, enc);
+                try
+                {
+                    // Atomic on most platforms
+                    File.Replace(tmp, filePath, null);
+                }
+                catch
+                {
+                    // Fallback if Replace is not supported
+                    File.Copy(tmp, filePath, true);
+                    File.Delete(tmp);
+                }

328-347: Consider signaling “stopped” in final heartbeat

Stop() currently calls WriteHeartbeat(false) which records reason “ready”. That’s ambiguous for consumers. Prefer a terminal state.

-                    // Mark heartbeat one last time before stopping
-                    WriteHeartbeat(false);
+                    // Mark heartbeat one last time before stopping
+                    WriteHeartbeat(false, "stopped");
UnityMcpBridge/Editor/Tools/ManageScript.cs (2)

220-223: BOM‑free UTF‑8 for script writes — approved; centralize encoding constant

The change to new UTF8Encoding(false) is correct and consistent with the repo’s policy. To avoid repetition and mistakes, centralize the encoding (single source of truth) and reuse.

Apply this minimal diff here:

-                File.WriteAllText(fullPath, contents, new System.Text.UTF8Encoding(false));
+                File.WriteAllText(fullPath, contents, MCPForUnity.Editor.Helpers.TextEncodings.Utf8NoBom);
-                File.WriteAllText(fullPath, contents, new System.Text.UTF8Encoding(false));
+                File.WriteAllText(fullPath, contents, MCPForUnity.Editor.Helpers.TextEncodings.Utf8NoBom);

Add this tiny helper once (new file or existing helpers namespace):

// UnityMcpBridge/Editor/Helpers/TextEncodings.cs
namespace MCPForUnity.Editor.Helpers
{
    public static class TextEncodings
    {
        public static readonly System.Text.Encoding Utf8NoBom = new System.Text.UTF8Encoding(false);
    }
}

Also applies to: 301-304


218-227: Optional: atomic writes for create/update to prevent transient reads of partial files

Unity imports right after the write, but on slower disks or external tooling, atomic replacement can still help avoid transient partial reads.

Example tweak (pattern — same idea as in heartbeat):

-                File.WriteAllText(fullPath, contents, MCPForUnity.Editor.Helpers.TextEncodings.Utf8NoBom);
+                var tmp = fullPath + ".tmp";
+                File.WriteAllText(tmp, contents, MCPForUnity.Editor.Helpers.TextEncodings.Utf8NoBom);
+                try { System.IO.File.Replace(tmp, fullPath, null); }
+                catch { System.IO.File.Copy(tmp, fullPath, true); System.IO.File.Delete(tmp); }

Also applies to: 299-308

UnityMcpBridge/Editor/Tools/ManageShader.cs (2)

174-177: BOM‑free UTF‑8 for shader writes — approved; reuse a shared encoding constant

Matches the project standard and prevents BOM‑related parsing surprises in toolchains. Recommend reusing a centralized encoding constant for consistency.

Apply this small diff:

-                File.WriteAllText(fullPath, contents, new System.Text.UTF8Encoding(false));
+                File.WriteAllText(fullPath, contents, MCPForUnity.Editor.Helpers.TextEncodings.Utf8NoBom);
-                File.WriteAllText(fullPath, contents, new System.Text.UTF8Encoding(false));
+                File.WriteAllText(fullPath, contents, MCPForUnity.Editor.Helpers.TextEncodings.Utf8NoBom);

Also applies to: 242-249


172-181: Optional: atomic shader writes

Same rationale as scripts: reduce the chance of partial reads if external tooling scans shaders immediately after creation/update.

Pattern:

-                File.WriteAllText(fullPath, contents, MCPForUnity.Editor.Helpers.TextEncodings.Utf8NoBom);
+                var tmp = fullPath + ".tmp";
+                File.WriteAllText(tmp, contents, MCPForUnity.Editor.Helpers.TextEncodings.Utf8NoBom);
+                try { System.IO.File.Replace(tmp, fullPath, null); }
+                catch { System.IO.File.Copy(tmp, fullPath, true); System.IO.File.Delete(tmp); }

Also applies to: 240-249

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 317b099 and c53f5f1.

📒 Files selected for processing (4)
  • UnityMcpBridge/Editor/Helpers/PortManager.cs (1 hunks)
  • UnityMcpBridge/Editor/MCPForUnityBridge.cs (1 hunks)
  • UnityMcpBridge/Editor/Tools/ManageScript.cs (2 hunks)
  • UnityMcpBridge/Editor/Tools/ManageShader.cs (2 hunks)
🔇 Additional comments (2)
UnityMcpBridge/Editor/MCPForUnityBridge.cs (1)

706-724: Explicit UTF‑8 (no BOM) write for heartbeat JSON looks good

This directly addresses the macOS/Windsurf “UTF‑8 with BOM” pitfall and aligns with the PR goal of BOM‑free JSON.

UnityMcpBridge/Editor/Helpers/PortManager.cs (1)

206-214: BOM‑free UTF‑8 for registry files — approved

This aligns with the Windsurf/Kiro expectations and dsarno’s macOS BOM finding. Good move.

@msanatan
Copy link
Contributor Author

Ok, first round of feedback was addressed (I think lol). Let round 2 commence 😀

@dsarno
Copy link
Collaborator

dsarno commented Aug 23, 2025

@msanatan I did another test run -- uninstalled the server from my local dev directory (which you also have to do or current version won't install latest), and also made sure the .config server was clear. And it worked like a charm.

I'd say merge it and we can fix if we hear any issues. Thanks for doing this!

@msanatan msanatan merged commit 22fe9df into CoplayDev:main Aug 24, 2025
1 check passed
@msanatan msanatan deleted the improve-windsurf-mcp-config branch August 24, 2025 10:57
dsarno added a commit to dsarno/unity-mcp that referenced this pull request Aug 24, 2025
dsarno added a commit to dsarno/unity-mcp that referenced this pull request Aug 25, 2025
…ing-testes

* fix/installer-cleanup-v2:
  Server: set server_version.txt to 3.0.1
  MCP: Fix macOS paths and VSCode manual setup
  refactor(installer): revert to lean installer logic from ee23346; fix macOS path via Application.platform; escape pgrep pattern
  feat(installer): rewire known configs (EditorPrefs, Cursor mcp.json) to canonical path; then remove legacy if unreferenced
  chore(version): bump server_version.txt to 3.0.5
  fix(installer): use Application.platform for OS detection; add canonical root logs; fallback to RuntimeInformation
  chore(logging): include OS and server version in MCP bridge start log
  chore(server): bump server_version.txt to 3.0.4
  bump server version
  sec(installer): escape server path in pgrep pattern to prevent injection/regex issues
  chore(editor): WriteToConfig honors pythonDir; robust config match; remove unused helpers
  fix(installer): skip legacy deletion when still referenced in prefs or Cursor config
  Improve Windsurf MCP Config (CoplayDev#231)
  feat: installer cleanup, auto-migration, logging normalization
  chore: bump version to 3.0.0
  Don't ignore the package's README (CoplayDev#230)
  Rename namespace and public facing plugin output from "Unity MCP" to "MCP for Unity" (CoplayDev#225)

# Conflicts:
#	UnityMcpBridge/Editor/Tools/ManageScript.cs
#	UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs
#	UnityMcpBridge/UnityMcpServer~/src/server.py
#	UnityMcpBridge/UnityMcpServer~/src/tools/__init__.py
#	UnityMcpBridge/UnityMcpServer~/src/uv.lock
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.

3 participants