From a7f59a71cb7f44c5a1860799bc9cbdae0aceb1a6 Mon Sep 17 00:00:00 2001 From: yankawa Date: Sun, 5 Jul 2026 13:11:17 +0900 Subject: [PATCH 1/3] feat(client): add Factory Droid as a configured MCP client (#1240) Adds DroidConfigurator so Factory Droid is detected and auto-configured on parity with the other 20+ supported clients. Droid stores MCP config in ~/.factory/mcp.json using the standard mcpServers schema, so it reuses JsonFileMcpConfigurator without a new base class. - New DroidConfigurator.cs (+ .meta) targeting ~/.factory/mcp.json on all OSes - Enable skill install support (~/.factory/skills/unity-mcp-skill) - EditMode tests: transport support, IsInstalled, config-format invariants, skill path - Docs: capability matrix in clients.md, prerequisites in install.md, configurator guide --- .../Configurators/DroidConfigurator.cs | 42 ++++++++++++ .../Configurators/DroidConfigurator.cs.meta | 11 +++ .../EditMode/Clients/IsInstalledTests.cs | 9 +++ .../Clients/SupportedTransportsTests.cs | 9 +++ .../Helpers/ClientConfigFormatTests.cs | 68 +++++++++++++++++++ website/docs/getting-started/clients.md | 1 + website/docs/getting-started/install.md | 2 +- website/docs/guides/client-configurators.md | 1 + 8 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs create mode 100644 MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs.meta diff --git a/MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs b/MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs new file mode 100644 index 000000000..6ad915306 --- /dev/null +++ b/MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.IO; +using MCPForUnity.Editor.Models; + +namespace MCPForUnity.Editor.Clients.Configurators +{ + /// + /// Factory Droid configurator. Droid stores MCP server config in + /// ~/.factory/mcp.json using the standard `mcpServers` container, so it + /// reuses the shared JSON-file configurator pipeline. + /// + public class DroidConfigurator : JsonFileMcpConfigurator + { + public DroidConfigurator() : base(new McpClient + { + name = "Droid", + // ~/.factory/mcp.json on every OS (UserProfile resolves to C:\Users\ on Windows). + windowsConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".factory", "mcp.json"), + macConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".factory", "mcp.json"), + linuxConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".factory", "mcp.json"), + SupportsHttpTransport = true, + }) + { } + + public override bool SupportsSkills => true; + + public override string GetSkillInstallPath() + { + var userHome = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + return Path.Combine(userHome, ".factory", "skills", "unity-mcp-skill"); + } + + public override IList GetInstallationSteps() => new List + { + "Install Factory Droid (https://factory.ai)", + "Click Configure to add UnityMCP to ~/.factory/mcp.json\nOR open the config file at the path above", + "Paste the configuration JSON into the mcpServers object", + "Save and restart Droid" + }; + } +} diff --git a/MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs.meta b/MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs.meta new file mode 100644 index 000000000..5f8222661 --- /dev/null +++ b/MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a1c8f3b9d5e74a2190f6c1d8e3b7a5f2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/IsInstalledTests.cs b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/IsInstalledTests.cs index e7d4467cb..4b9e715cd 100644 --- a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/IsInstalledTests.cs +++ b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/IsInstalledTests.cs @@ -41,5 +41,14 @@ public void JsonClient_Installed_WhenParentDirExists() bool expected = parent != null && Directory.Exists(parent); Assert.AreEqual(expected, claude.IsInstalled); } + + [Test] + public void Droid_Installed_WhenFactoryDirExists() + { + var droid = new DroidConfigurator(); + string parent = Path.GetDirectoryName(droid.GetConfigPath()); + bool expected = parent != null && Directory.Exists(parent); + Assert.AreEqual(expected, droid.IsInstalled); + } } } diff --git a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/SupportedTransportsTests.cs b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/SupportedTransportsTests.cs index 865561f0f..f8f9542cf 100644 --- a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/SupportedTransportsTests.cs +++ b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/SupportedTransportsTests.cs @@ -32,5 +32,14 @@ public void Cursor_SupportsBothTransports() CollectionAssert.Contains(list, ConfiguredTransport.Stdio); CollectionAssert.Contains(list, ConfiguredTransport.Http); } + + [Test] + public void Droid_SupportsBothTransports() + { + var droid = new DroidConfigurator(); + var list = droid.SupportedTransports.ToList(); + CollectionAssert.Contains(list, ConfiguredTransport.Stdio); + CollectionAssert.Contains(list, ConfiguredTransport.Http); + } } } diff --git a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/ClientConfigFormatTests.cs b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/ClientConfigFormatTests.cs index ef9301566..fbb19aa81 100644 --- a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/ClientConfigFormatTests.cs +++ b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/ClientConfigFormatTests.cs @@ -152,5 +152,73 @@ public void BuildManualConfigJson_ForGenericHttpClient_UsesPlainHttpType() Assert.AreEqual("http", (string)unity["type"], "Clients without HttpTypeValue should keep the generic type:http"); } + + [Test] + public void DroidConfigurator_DeclaresStandardMcpServersFormat() + { + // Droid stores MCP config in ~/.factory/mcp.json using the standard mcpServers + // container, so it must not override ServerContainerKey/HttpTypeValue/StdioTypeValue + // (those overrides are for clients with non-standard schemas like Kilo Code). + var client = new DroidConfigurator().Client; + + Assert.IsTrue(string.IsNullOrEmpty(client.ServerContainerKey), + "Droid must use the default mcpServers container, not a client-specific one"); + Assert.IsTrue(string.IsNullOrEmpty(client.HttpTypeValue), + "Droid must use the generic type:http for HTTP transport"); + Assert.IsTrue(string.IsNullOrEmpty(client.StdioTypeValue), + "Droid must use the generic type:stdio for stdio transport"); + Assert.IsFalse(client.IsVsCodeLayout, + "Droid must not use the VS Code servers/mcp.servers layout"); + Assert.IsTrue(client.SupportsHttpTransport, + "Droid supports both stdio and HTTP transports"); + } + + [Test] + public void BuildManualConfigJson_ForDroid_UsesMcpServersContainerAndPlainHttp() + { + var client = new DroidConfigurator().Client; + + var root = JObject.Parse(ConfigJsonBuilder.BuildManualConfigJson(uvPath: null, client)); + + Assert.IsNull(root["$schema"], "Droid must not write a $schema"); + Assert.IsNull(root["mcp"], "Droid must not use a client-specific container"); + + var unity = (JObject)root.SelectToken("mcpServers.unityMCP"); + Assert.NotNull(unity, "Expected mcpServers.unityMCP node"); + Assert.AreEqual("http", (string)unity["type"], + "Droid HTTP config must use the generic type:http, not streamableHttp/remote"); + Assert.IsNotNull(unity["url"], "HTTP transport should set a url"); + Assert.IsNull(unity["command"], "HTTP transport should not include a command"); + } + + [Test] + public void DroidConfigurator_TargetsFactoryMcpJson_OnAllPlatforms() + { + var client = new DroidConfigurator().Client; + const string expectedFileName = "mcp.json"; + const string expectedParentDirName = ".factory"; + + foreach (var path in new[] { client.windowsConfigPath, client.macConfigPath, client.linuxConfigPath }) + { + Assert.AreEqual(expectedFileName, System.IO.Path.GetFileName(path), + "Droid config must target mcp.json"); + Assert.AreEqual(expectedParentDirName, System.IO.Path.GetFileName(System.IO.Path.GetDirectoryName(path)), + "Droid config must live inside ~/.factory/"); + } + } + + [Test] + public void DroidConfigurator_SupportsSkills_AndInstallsUnderFactorySkills() + { + var droid = new DroidConfigurator(); + + Assert.IsTrue(droid.SupportsSkills, "Droid should support skill installation"); + string skillPath = droid.GetSkillInstallPath(); + Assert.IsNotNull(skillPath, "Skill install path must not be null when SupportsSkills is true"); + StringAssert.Contains(".factory", skillPath, + "Droid skills should install under ~/.factory/skills/"); + StringAssert.EndsWith("unity-mcp-skill", skillPath, + "Droid skill install path should end with the unity-mcp-skill subdirectory"); + } } } diff --git a/website/docs/getting-started/clients.md b/website/docs/getting-started/clients.md index 0be1ff516..0116c48fe 100644 --- a/website/docs/getting-started/clients.md +++ b/website/docs/getting-started/clients.md @@ -26,6 +26,7 @@ MCP for Unity auto-configures every client the package detects on your machine. | **Gemini CLI** | HTTP | yes | yes | yes | Auto-connects. | | **OpenClaw** | HTTP / stdio | yes | yes | yes | Requires `openclaw-mcp-bridge` plugin enabled. Follows MCP for Unity's transport choice. | | **Antigravity** | HTTP | yes | yes | varies | Requires an MCP toggle in Antigravity settings. | +| **Droid** | HTTP | yes | yes | yes | Factory's agent. Config written to `~/.factory/mcp.json`. | ## How to pick diff --git a/website/docs/getting-started/install.md b/website/docs/getting-started/install.md index fa4159468..66acd3813 100644 --- a/website/docs/getting-started/install.md +++ b/website/docs/getting-started/install.md @@ -14,7 +14,7 @@ Three install paths are supported. Pick one. **Git URL** is the fastest if you j - **Unity 2021.3 LTS or newer** — [Download Unity](https://unity.com/download) - **Python 3.10+** with [`uv`](https://docs.astral.sh/uv/getting-started/installation/) — the setup wizard guides you through both if missing -- **An MCP client** — [Claude Desktop](https://claude.ai/download), [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Cursor](https://www.cursor.com/), [VS Code Copilot](https://code.visualstudio.com/docs/copilot/overview), [GitHub Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli), [Windsurf](https://windsurf.com/), [Cline](https://cline.bot/), [OpenClaw](https://openclaw.ai/), and more +- **An MCP client** — [Claude Desktop](https://claude.ai/download), [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Cursor](https://www.cursor.com/), [VS Code Copilot](https://code.visualstudio.com/docs/copilot/overview), [GitHub Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli), [Windsurf](https://windsurf.com/), [Cline](https://cline.bot/), [OpenClaw](https://openclaw.ai/), [Factory Droid](https://factory.ai/), and more ## Option 1 — Git URL (fastest) diff --git a/website/docs/guides/client-configurators.md b/website/docs/guides/client-configurators.md index 8cbdf101d..5219cdee7 100644 --- a/website/docs/guides/client-configurators.md +++ b/website/docs/guides/client-configurators.md @@ -89,6 +89,7 @@ At a high level: Most MCP clients use a JSON config file that defines one or more MCP servers. Examples: - **Cursor** – `JsonFileMcpConfigurator` (global `~/.cursor/mcp.json`). +- **Droid** – `JsonFileMcpConfigurator` (global `~/.factory/mcp.json`, standard `mcpServers` layout; skills install to `~/.factory/skills/unity-mcp-skill`). - **VSCode GitHub Copilot** – `JsonFileMcpConfigurator` with `IsVsCodeLayout = true`. - **VSCode Insiders GitHub Copilot** – `JsonFileMcpConfigurator` with `IsVsCodeLayout = true` and Insider-specific `Code - Insiders/User/mcp.json` paths. - **GitHub Copilot CLI** – `JsonFileMcpConfigurator` with standard HTTP transport. From 1e14fdc53fd2c5ddd00b09c48b0edf3146350499 Mon Sep 17 00:00:00 2001 From: yankawa Date: Sun, 5 Jul 2026 13:22:42 +0900 Subject: [PATCH 2/3] refactor(droid): drop redundant SupportsHttpTransport assignment (#1241) McpClient already defaults SupportsHttpTransport to true, so the explicit assignment in DroidConfigurator is redundant. Removes it to match the pattern used by other JSON-file configurators (Cursor, Codex, etc.). CodeRabbit nitpick feedback. --- MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs b/MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs index 6ad915306..d726ff1ec 100644 --- a/MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs +++ b/MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs @@ -19,7 +19,6 @@ public DroidConfigurator() : base(new McpClient windowsConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".factory", "mcp.json"), macConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".factory", "mcp.json"), linuxConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".factory", "mcp.json"), - SupportsHttpTransport = true, }) { } From 03827dc2ed58fc4ad30e80fe6e1ed39f362a23d0 Mon Sep 17 00:00:00 2001 From: yankawa Date: Sun, 5 Jul 2026 13:26:46 +0900 Subject: [PATCH 3/3] docs(droid): clarify transport support and snippet merge step (#1241) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - clients.md: list Droid as "HTTP / stdio" since the configurator supports both transports (matches OpenClaw row wording). - DroidConfigurator: rewrite the manual-snippet step to say "merge so it results in mcpServers.unityMCP (do not nest mcpServers twice)" — the manual snippet is a full JSON doc with its own mcpServers container, so the prior "paste into the mcpServers object" wording could lead to double nesting. Copilot review feedback. --- MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs | 2 +- website/docs/getting-started/clients.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs b/MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs index d726ff1ec..652ab1aec 100644 --- a/MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs +++ b/MCPForUnity/Editor/Clients/Configurators/DroidConfigurator.cs @@ -34,7 +34,7 @@ public override string GetSkillInstallPath() { "Install Factory Droid (https://factory.ai)", "Click Configure to add UnityMCP to ~/.factory/mcp.json\nOR open the config file at the path above", - "Paste the configuration JSON into the mcpServers object", + "Merge the manual snippet so it results in mcpServers.unityMCP (do not nest mcpServers twice)", "Save and restart Droid" }; } diff --git a/website/docs/getting-started/clients.md b/website/docs/getting-started/clients.md index 0116c48fe..833b1deb1 100644 --- a/website/docs/getting-started/clients.md +++ b/website/docs/getting-started/clients.md @@ -26,7 +26,7 @@ MCP for Unity auto-configures every client the package detects on your machine. | **Gemini CLI** | HTTP | yes | yes | yes | Auto-connects. | | **OpenClaw** | HTTP / stdio | yes | yes | yes | Requires `openclaw-mcp-bridge` plugin enabled. Follows MCP for Unity's transport choice. | | **Antigravity** | HTTP | yes | yes | varies | Requires an MCP toggle in Antigravity settings. | -| **Droid** | HTTP | yes | yes | yes | Factory's agent. Config written to `~/.factory/mcp.json`. | +| **Droid** | HTTP / stdio | yes | yes | yes | Factory's agent. Config written to `~/.factory/mcp.json`. Follows MCP for Unity's transport choice. | ## How to pick