diff --git a/MCPForUnity/Editor/Clients/Configurators/OpenClawConfigurator.cs b/MCPForUnity/Editor/Clients/Configurators/OpenClawConfigurator.cs deleted file mode 100644 index 1d800332e..000000000 --- a/MCPForUnity/Editor/Clients/Configurators/OpenClawConfigurator.cs +++ /dev/null @@ -1,398 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using MCPForUnity.Editor.Constants; -using MCPForUnity.Editor.Helpers; -using MCPForUnity.Editor.Models; -using MCPForUnity.Editor.Services; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using UnityEditor; - -namespace MCPForUnity.Editor.Clients.Configurators -{ - /// - /// Configurator for OpenClaw via the openclaw-mcp-bridge plugin. - /// OpenClaw stores config at ~/.openclaw/openclaw.json. - /// - public class OpenClawConfigurator : McpClientConfiguratorBase - { - private const string PluginName = "openclaw-mcp-bridge"; - private const string ServerName = "unityMCP"; - private const string HttpTransportName = "http"; - private const string StdioTransportName = "stdio"; - private const string StdioUrl = "stdio://local"; - - public OpenClawConfigurator() : base(new McpClient - { - name = "OpenClaw", - windowsConfigPath = BuildConfigPath(), - macConfigPath = BuildConfigPath(), - linuxConfigPath = BuildConfigPath() - }) - { } - - private static string BuildConfigPath() - { - return Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - ".openclaw", - "openclaw.json"); - } - - public override string GetConfigPath() => CurrentOsPath(); - - public override McpStatus CheckStatus(bool attemptAutoRewrite = true) - { - try - { - string path = GetConfigPath(); - if (!File.Exists(path)) - { - client.SetStatus(McpStatus.NotConfigured); - client.configuredTransport = ConfiguredTransport.Unknown; - return client.status; - } - - JObject root = LoadConfig(path); - JObject pluginEntry = root["plugins"]?["entries"]?[PluginName] as JObject; - JObject unityServer = FindUnityServer(pluginEntry?["config"]?["servers"]); - - if (pluginEntry == null || unityServer == null) - { - client.SetStatus(McpStatus.MissingConfig); - client.configuredTransport = ConfiguredTransport.Unknown; - return client.status; - } - - if (!IsEnabled(pluginEntry) || !IsEnabled(unityServer)) - { - client.SetStatus(McpStatus.NotConfigured); - client.configuredTransport = ConfiguredTransport.Unknown; - return client.status; - } - - bool matches = ServerMatchesCurrentEndpoint(unityServer); - if (matches) - { - client.SetStatus(McpStatus.Configured); - client.configuredTransport = ResolveTransport(unityServer); - return client.status; - } - - if (attemptAutoRewrite) - { - Configure(); - } - else - { - client.SetStatus(McpStatus.IncorrectPath); - client.configuredTransport = ConfiguredTransport.Unknown; - } - } - catch (Exception ex) - { - client.SetStatus(McpStatus.Error, ex.Message); - client.configuredTransport = ConfiguredTransport.Unknown; - } - - return client.status; - } - - public override void Configure() - { - if (EditorPrefs.GetBool(EditorPrefKeys.LockCursorConfig, false)) - return; - - string path = GetConfigPath(); - McpConfigurationHelper.EnsureConfigDirectoryExists(path); - - JObject root = File.Exists(path) ? LoadConfig(path) : new JObject(); - - JObject plugins = root["plugins"] as JObject ?? new JObject(); - root["plugins"] = plugins; - - JObject entries = plugins["entries"] as JObject ?? new JObject(); - plugins["entries"] = entries; - - JObject pluginEntry = entries[PluginName] as JObject ?? new JObject(); - entries[PluginName] = pluginEntry; - pluginEntry["enabled"] = true; - - JObject pluginConfig = pluginEntry["config"] as JObject ?? new JObject(); - pluginEntry["config"] = pluginConfig; - pluginConfig.Remove("timeout"); // removed in openclaw-mcp-bridge v2+ - pluginConfig.Remove("retries"); // removed in openclaw-mcp-bridge v2+ - pluginConfig["servers"] = UpsertUnityServer(pluginConfig["servers"]); - - McpConfigurationHelper.WriteAtomicFile(path, root.ToString(Formatting.Indented)); - client.SetStatus(McpStatus.Configured); - client.configuredTransport = HttpEndpointUtility.GetCurrentServerTransport(); - } - - public override string GetManualSnippet() - { - JObject snippet = new JObject - { - ["plugins"] = new JObject - { - ["entries"] = new JObject - { - [PluginName] = new JObject - { - ["enabled"] = true, - ["config"] = new JObject - { - ["servers"] = new JObject - { - [ServerName] = BuildUnityServerEntry() - } - } - } - } - } - }; - - return snippet.ToString(Formatting.Indented); - } - - public override IList GetInstallationSteps() => new List - { - "Install OpenClaw", - "Install the bridge plugin: npm install -g openclaw-mcp-bridge (or pnpm add -g openclaw-mcp-bridge)", - "In MCP for Unity, choose OpenClaw and click Configure", - "OpenClaw uses the currently selected MCP for Unity transport (HTTP or stdio)", - "OpenClaw exposes a proxy tool such as unityMCP__call for Unity MCP access", - "Restart OpenClaw if the plugin does not hot-reload the new config" - }; - - private JObject LoadConfig(string path) - { - string text = File.ReadAllText(path); - if (string.IsNullOrWhiteSpace(text)) - { - return new JObject(); - } - - try - { - return JsonConvert.DeserializeObject(text) ?? new JObject(); - } - catch (JsonException ex) - { - throw new InvalidOperationException( - $"OpenClaw config contains non-JSON content and cannot be safely auto-edited: {ex.Message}"); - } - } - - private JObject FindUnityServer(JToken serversToken) - { - if (serversToken is JObject serverMap) - { - return serverMap[ServerName] as JObject; - } - - if (serversToken is JArray legacyServers) - { - foreach (JToken token in legacyServers) - { - JObject server = token as JObject; - if (server == null) - { - continue; - } - - string name = server["name"]?.ToString(); - if (string.Equals(name, ServerName, StringComparison.OrdinalIgnoreCase)) - { - return server; - } - } - } - - return null; - } - - private JObject UpsertUnityServer(JToken serversToken) - { - JObject servers = NormalizeServers(serversToken); - JObject entry = servers[ServerName] as JObject ?? new JObject(); - JObject desiredEntry = BuildUnityServerEntry(); - - entry.Remove("name"); - entry.Remove("prefix"); - entry.Remove("healthCheck"); - entry.Remove("command"); - entry.Remove("args"); - entry.Remove("env"); - entry.Remove("connectTimeoutMs"); - - foreach (var property in desiredEntry.Properties()) - { - entry[property.Name] = property.Value.DeepClone(); - } - - servers[ServerName] = entry; - - return servers; - } - - private static JObject NormalizeServers(JToken serversToken) - { - if (serversToken is JObject serverMap) - { - return serverMap; - } - - var normalized = new JObject(); - if (!(serversToken is JArray legacyServers)) - { - return normalized; - } - - foreach (JToken token in legacyServers) - { - if (!(token is JObject legacyServer)) - { - continue; - } - - string name = legacyServer["name"]?.ToString(); - if (string.IsNullOrWhiteSpace(name)) - { - continue; - } - - normalized[name] = legacyServer; - } - - return normalized; - } - - private static JObject BuildUnityServerEntry() - { - ConfiguredTransport transport = HttpEndpointUtility.GetCurrentServerTransport(); - if (transport == ConfiguredTransport.Stdio) - { - var (uvxPath, _, packageName) = AssetPathUtility.GetUvxCommandParts(); - if (string.IsNullOrWhiteSpace(uvxPath)) - { - throw new InvalidOperationException("uvx not found. Install uv/uvx or set the override in Advanced Settings."); - } - - var args = new JArray(); - foreach (string value in AssetPathUtility.GetUvxDevFlagsList()) - { - args.Add(value); - } - foreach (string value in AssetPathUtility.GetBetaServerFromArgsList()) - { - args.Add(value); - } - args.Add(packageName); - args.Add("--transport"); - args.Add("stdio"); - - return new JObject - { - ["enabled"] = true, - ["url"] = StdioUrl, - ["transport"] = StdioTransportName, - ["command"] = uvxPath, - ["args"] = args, - ["toolPrefix"] = ServerName, - ["requestTimeoutMs"] = 60000, - ["connectTimeoutMs"] = 15000 - }; - } - - return new JObject - { - ["enabled"] = true, - ["url"] = HttpEndpointUtility.GetMcpRpcUrl(), - ["transport"] = HttpTransportName, - ["toolPrefix"] = ServerName, - ["requestTimeoutMs"] = 30000 - }; - } - - private bool ServerMatchesCurrentEndpoint(JObject server) - { - if (server == null) - { - return false; - } - - ConfiguredTransport expectedTransport = HttpEndpointUtility.GetCurrentServerTransport(); - ConfiguredTransport configuredTransport = ResolveTransport(server); - if (configuredTransport != expectedTransport) - { - return false; - } - - if (configuredTransport == ConfiguredTransport.Stdio) - { - string configuredUrl = server["url"]?.ToString(); - string command = server["command"]?.ToString(); - if (!UrlsEqual(configuredUrl, StdioUrl) || string.IsNullOrWhiteSpace(command)) - { - return false; - } - - // Validate the --from package source hasn't drifted (e.g. stable vs prerelease switch) - string[] args = (server["args"] as JArray)?.ToObject(); - string configuredSource = McpConfigurationHelper.ExtractUvxUrl(args); - string expectedSource = GetExpectedPackageSourceForValidation(); - if (!string.IsNullOrEmpty(configuredSource) && !string.IsNullOrEmpty(expectedSource) && - !McpConfigurationHelper.PathsEqual(configuredSource, expectedSource)) - { - return false; - } - } - else - { - string configuredUrl = server["url"]?.ToString(); - if (string.IsNullOrWhiteSpace(configuredUrl) || - (!UrlsEqual(configuredUrl, HttpEndpointUtility.GetLocalMcpRpcUrl()) && - !UrlsEqual(configuredUrl, HttpEndpointUtility.GetRemoteMcpRpcUrl()))) - { - return false; - } - } - - string toolPrefix = server["toolPrefix"]?.ToString(); - return string.IsNullOrWhiteSpace(toolPrefix) || - string.Equals(toolPrefix, ServerName, StringComparison.OrdinalIgnoreCase); - } - - private static bool IsEnabled(JObject entry) - { - JToken enabledToken = entry["enabled"]; - return enabledToken == null || enabledToken.Type != JTokenType.Boolean || enabledToken.Value(); - } - - private ConfiguredTransport ResolveTransport(JObject server) - { - string configuredTransport = server?["transport"]?.ToString(); - string configuredUrl = server?["url"]?.ToString(); - - if (string.Equals(configuredTransport, StdioTransportName, StringComparison.OrdinalIgnoreCase) || - UrlsEqual(configuredUrl, StdioUrl)) - { - return ConfiguredTransport.Stdio; - } - - if (UrlsEqual(configuredUrl, HttpEndpointUtility.GetRemoteMcpRpcUrl())) - { - return ConfiguredTransport.HttpRemote; - } - - if (UrlsEqual(configuredUrl, HttpEndpointUtility.GetLocalMcpRpcUrl())) - { - return ConfiguredTransport.Http; - } - - return ConfiguredTransport.Unknown; - } - } -} diff --git a/MCPForUnity/Editor/Clients/Configurators/OpenClawConfigurator.cs.meta b/MCPForUnity/Editor/Clients/Configurators/OpenClawConfigurator.cs.meta deleted file mode 100644 index 16f5304fc..000000000 --- a/MCPForUnity/Editor/Clients/Configurators/OpenClawConfigurator.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 19f29c88761345158fc766d24e7c18f3 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/MCPForUnity/README.md b/MCPForUnity/README.md index 3631f60f6..77a962371 100644 --- a/MCPForUnity/README.md +++ b/MCPForUnity/README.md @@ -17,7 +17,7 @@ The window has four areas: Server Status, Unity Bridge, MCP Client Configuration - Install Python and/or uv/uvx if missing so the server can be managed locally. - For Claude Code, ensure the `claude` CLI is installed. 4. Click “Start Bridge” if the Unity Bridge shows “Stopped”. -5. Use your MCP client (Cursor, VS Code, OpenClaw, Claude Code) to connect. +5. Use your MCP client (Cursor, VS Code, Windsurf, Claude Code) to connect. --- @@ -60,11 +60,6 @@ The window has four areas: Server Status, Unity Bridge, MCP Client Configuration - Register with Claude Code / Unregister MCP for Unity with Claude Code. - If the CLI isn’t found, click “Choose Claude Install Location”. - The window displays the resolved Claude CLI path when detected. - - OpenClaw: - - Uses `~/.openclaw/openclaw.json` and the `openclaw-mcp-bridge` plugin. - - MCP for Unity writes `plugins.entries.openclaw-mcp-bridge.config.servers.unityMCP`. - - OpenClaw follows the currently selected MCP for Unity transport (`HTTP` or `stdio`). - - The bridge exposes a proxy tool such as `unityMCP__call`. Notes: - The UI shows a status dot and a short status text (e.g., “Configured”, “uv Not Found”, “Claude Not Found”). diff --git a/README.md b/README.md index ee669136a..47cda5640 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ * **Unity 2021.3 LTS+** — [Download Unity](https://unity.com/download) * **Python 3.10+** and **uv** — [Install uv](https://docs.astral.sh/uv/getting-started/installation/) -* **An MCP Client** — [Claude Desktop](https://claude.ai/download) | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | [Cursor](https://www.cursor.com/en/downloads) | [VS Code Copilot](https://code.visualstudio.com/docs/copilot/overview) | [GitHub Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli) | [OpenClaw](https://openclaw.ai) +* **An MCP Client** — [Claude Desktop](https://claude.ai/download) | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | [Cursor](https://www.cursor.com/en/downloads) | [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) ### 1. Install the Unity Package @@ -76,7 +76,7 @@ openupm add com.coplaydev.unity-mcp 2. Click **Start Server** (launches HTTP server on `localhost:8080`) 3. Select your MCP Client from the dropdown and click **Configure** 4. Look for 🟢 "Connected ✓" -5. **Connect your client:** Some clients (Cursor, Antigravity, OpenClaw) require enabling an MCP toggle or plugin in settings. OpenClaw also needs the `openclaw-mcp-bridge` plugin enabled and follows the currently selected MCP for Unity transport (`HTTP` or `stdio`). Others (Claude Desktop, Claude Code) auto-connect after configuration. +5. **Connect your client:** Some clients (Cursor, Windsurf, Antigravity) require enabling an MCP toggle in settings, while others (Claude Desktop, Claude Code) auto-connect after configuration. **That's it!** Try a prompt like: *"Create a red, blue and yellow cube"* or *"Build a simple player controller"* diff --git a/docs/guides/MCP_CLIENT_CONFIGURATORS.md b/docs/guides/MCP_CLIENT_CONFIGURATORS.md index 09d8a0540..c8998cc3a 100644 --- a/docs/guides/MCP_CLIENT_CONFIGURATORS.md +++ b/docs/guides/MCP_CLIENT_CONFIGURATORS.md @@ -5,7 +5,7 @@ This guide explains how MCP client configurators work in this repo and how to ad It covers: - **Typical JSON-file clients** (Cursor, VSCode GitHub Copilot, VSCode Insiders, GitHub Copilot CLI, Windsurf, Kiro, Trae, Antigravity, etc.). -- **Special clients** like **Claude CLI**, **Codex**, and **OpenClaw** that require custom logic. +- **Special clients** like **Claude CLI** and **Codex** that require custom logic. - **How to add a new configurator class** so it shows up automatically in the MCP for Unity window. ## Quick example: JSON-file configurator @@ -165,16 +165,6 @@ Some clients cannot be handled by the generic JSON configurator alone. - Guard against HTTP mode. - Provide clear error text if HTTP is enabled. -### OpenClaw (plugin-based) - -- Uses a custom configurator (`OpenClawConfigurator`) because OpenClaw MCP is plugin-driven. -- Config file path is `~/.openclaw/openclaw.json`. -- Unity MCP is configured through `plugins.entries.openclaw-mcp-bridge.config.servers.unityMCP`. -- When Unity MCP is set to HTTP, the bridge expects the MCP JSON-RPC endpoint URL (`http://127.0.0.1:/mcp`), not just the HTTP base URL. -- When Unity MCP is set to stdio, the configurator writes a `uvx ... mcp-for-unity --transport stdio` subprocess entry. -- The bridge exposes a single proxy tool such as `unityMCP__call`, which then forwards to Unity MCP tool names. -- OpenClaw support follows the currently selected MCP for Unity transport (via `openclaw-mcp-bridge`). - --- ## Adding a new MCP client (typical JSON case) diff --git a/docs/i18n/README-zh.md b/docs/i18n/README-zh.md index 87ae852ba..b8686d4d5 100644 --- a/docs/i18n/README-zh.md +++ b/docs/i18n/README-zh.md @@ -41,7 +41,7 @@ * **Unity 2021.3 LTS+** — [下载 Unity](https://unity.com/download) * **Python 3.10+** 和 **uv** — [安装 uv](https://docs.astral.sh/uv/getting-started/installation/) -* **一个 MCP 客户端** — [Claude Desktop](https://claude.ai/download) | [Cursor](https://www.cursor.com/en/downloads) | [VS Code Copilot](https://code.visualstudio.com/docs/copilot/overview) | [GitHub Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli) | [OpenClaw](https://openclaw.ai) +* **一个 MCP 客户端** — [Claude Desktop](https://claude.ai/download) | [Cursor](https://www.cursor.com/en/downloads) | [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) ### 1. 安装 Unity 包 @@ -76,7 +76,7 @@ openupm add com.coplaydev.unity-mcp 2. 点击 **Start Server**(会在 `localhost:8080` 启动 HTTP 服务器) 3. 从下拉菜单选择你的 MCP Client,然后点击 **Configure** 4. 查找 🟢 "Connected ✓" -5. **连接你的客户端:** 一些客户端(Cursor、Antigravity、OpenClaw)需要在设置里启用 MCP 开关或插件。OpenClaw 还需要启用 `openclaw-mcp-bridge` 插件,并会跟随 MCP for Unity 当前选择的传输方式(HTTP 或 stdio);另一些(Claude Desktop、Claude Code)在配置后会自动连接。 +5. **连接你的客户端:** 一些客户端(Cursor、Windsurf、Antigravity)需要在设置里启用 MCP 开关;另一些(Claude Desktop、Claude Code)在配置后会自动连接。 **就这些!** 试试这样的提示词:*"Create a red, blue and yellow cube"* 或 *"Build a simple player controller"*