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
};
}
}