diff --git a/MCPForUnity/Editor/Clients/Configurators/OpenCodeConfigurator.cs b/MCPForUnity/Editor/Clients/Configurators/OpenCodeConfigurator.cs new file mode 100644 index 000000000..a0ec94117 --- /dev/null +++ b/MCPForUnity/Editor/Clients/Configurators/OpenCodeConfigurator.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.IO; +using MCPForUnity.Editor.Helpers; +using MCPForUnity.Editor.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace MCPForUnity.Editor.Clients.Configurators +{ + /// + /// Configurator for OpenCode (opencode.ai) - a Go-based terminal AI coding assistant. + /// OpenCode uses ~/.config/opencode/opencode.json with a custom "mcp" format. + /// + public class OpenCodeConfigurator : McpClientConfiguratorBase + { + private const string ServerName = "unityMCP"; + private const string SchemaUrl = "https://opencode.ai/config.json"; + + public OpenCodeConfigurator() : base(new McpClient + { + name = "OpenCode", + windowsConfigPath = BuildConfigPath(), + macConfigPath = BuildConfigPath(), + linuxConfigPath = BuildConfigPath() + }) + { } + + private static string BuildConfigPath() + { + string xdgConfigHome = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"); + string configBase = !string.IsNullOrEmpty(xdgConfigHome) + ? xdgConfigHome + : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config"); + return Path.Combine(configBase, "opencode", "opencode.json"); + } + + public override string GetConfigPath() => CurrentOsPath(); + + /// + /// Attempts to load and parse the config file. + /// Returns null if file doesn't exist. + /// Returns empty JObject if file exists but contains malformed JSON (logs warning). + /// Throws on I/O errors (permission denied, etc.). + /// + private JObject TryLoadConfig(string path) + { + if (!File.Exists(path)) + return null; + + string content = File.ReadAllText(path); + try + { + return JsonConvert.DeserializeObject(content) ?? new JObject(); + } + catch (JsonException) + { + // Malformed JSON - return empty object so caller can overwrite with valid config + UnityEngine.Debug.LogWarning($"[OpenCodeConfigurator] Malformed JSON in {path}, will overwrite with valid config"); + return new JObject(); + } + } + + public override McpStatus CheckStatus(bool attemptAutoRewrite = true) + { + try + { + string path = GetConfigPath(); + var config = TryLoadConfig(path); + + if (config == null) + { + client.SetStatus(McpStatus.NotConfigured); + return client.status; + } + + var unityMcp = config["mcp"]?[ServerName] as JObject; + + if (unityMcp == null) + { + client.SetStatus(McpStatus.NotConfigured); + return client.status; + } + + string configuredUrl = unityMcp["url"]?.ToString(); + string expectedUrl = HttpEndpointUtility.GetMcpRpcUrl(); + + if (UrlsEqual(configuredUrl, expectedUrl)) + { + client.SetStatus(McpStatus.Configured); + } + else if (attemptAutoRewrite) + { + Configure(); + } + else + { + client.SetStatus(McpStatus.IncorrectPath); + } + } + catch (Exception ex) + { + client.SetStatus(McpStatus.Error, ex.Message); + } + + return client.status; + } + + public override void Configure() + { + try + { + string path = GetConfigPath(); + McpConfigurationHelper.EnsureConfigDirectoryExists(path); + + var config = TryLoadConfig(path) ?? new JObject { ["$schema"] = SchemaUrl }; + + var mcpSection = config["mcp"] as JObject ?? new JObject(); + config["mcp"] = mcpSection; + + mcpSection[ServerName] = BuildServerEntry(); + + McpConfigurationHelper.WriteAtomicFile(path, JsonConvert.SerializeObject(config, Formatting.Indented)); + client.SetStatus(McpStatus.Configured); + } + catch (Exception ex) + { + client.SetStatus(McpStatus.Error, ex.Message); + } + } + + public override string GetManualSnippet() + { + var snippet = new JObject + { + ["mcp"] = new JObject { [ServerName] = BuildServerEntry() } + }; + return JsonConvert.SerializeObject(snippet, Formatting.Indented); + } + + public override IList GetInstallationSteps() => new List + { + "Install OpenCode (https://opencode.ai)", + "Click Configure to add Unity MCP to ~/.config/opencode/opencode.json", + "Restart OpenCode", + "The Unity MCP server should be detected automatically" + }; + + private static JObject BuildServerEntry() => new JObject + { + ["type"] = "remote", + ["url"] = HttpEndpointUtility.GetMcpRpcUrl(), + ["enabled"] = true + }; + } +} diff --git a/MCPForUnity/Editor/Clients/Configurators/OpenCodeConfigurator.cs.meta b/MCPForUnity/Editor/Clients/Configurators/OpenCodeConfigurator.cs.meta new file mode 100644 index 000000000..8094fb1d0 --- /dev/null +++ b/MCPForUnity/Editor/Clients/Configurators/OpenCodeConfigurator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 489f99ffb7e6743e88e3203552c8b37b \ No newline at end of file