using Newtonsoft.Json; using System.Collections.Generic; using Newtonsoft.Json.Linq; using MCPForUnity.Editor.Models; using MCPForUnity.Editor.Helpers; using MCPForUnity.Editor.Constants; using UnityEditor; namespace MCPForUnity.Editor.Helpers { public static class ConfigJsonBuilder { public static string BuildManualConfigJson(string uvPath, McpClient client) { var root = new JObject(); bool isVSCode = client?.mcpType == McpTypes.VSCode; JObject container; if (isVSCode) { container = EnsureObject(root, "servers"); } else { container = EnsureObject(root, "mcpServers"); } var unity = new JObject(); PopulateUnityNode(unity, uvPath, client, isVSCode); container["unityMCP"] = unity; return root.ToString(Formatting.Indented); } public static JObject ApplyUnityServerToExistingConfig(JObject root, string uvPath, McpClient client) { if (root == null) root = new JObject(); bool isVSCode = client?.mcpType == McpTypes.VSCode; JObject container = isVSCode ? EnsureObject(root, "servers") : EnsureObject(root, "mcpServers"); JObject unity = container["unityMCP"] as JObject ?? new JObject(); PopulateUnityNode(unity, uvPath, client, isVSCode); container["unityMCP"] = unity; return root; } /// /// Centralized builder that applies all caveats consistently. /// - Sets command/args with uvx and package version /// - Ensures env exists /// - Adds transport configuration (HTTP or stdio) /// - Adds disabled:false for Windsurf/Kiro only when missing /// private static void PopulateUnityNode(JObject unity, string uvPath, McpClient client, bool isVSCode) { // Get transport preference (default to HTTP) bool useHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true); bool isWindsurf = client?.mcpType == McpTypes.Windsurf; if (useHttpTransport) { // HTTP mode: Use URL, no command string httpUrl = HttpEndpointUtility.GetMcpRpcUrl(); string httpProperty = isWindsurf ? "serverUrl" : "url"; unity[httpProperty] = httpUrl; // Remove legacy property for Windsurf (or vice versa) string staleProperty = isWindsurf ? "url" : "serverUrl"; if (unity[staleProperty] != null) { unity.Remove(staleProperty); } // Remove command/args if they exist from previous config if (unity["command"] != null) unity.Remove("command"); if (unity["args"] != null) unity.Remove("args"); if (isVSCode) { unity["type"] = "http"; } } else { // Stdio mode: Use uvx command var (uvxPath, fromUrl, packageName) = AssetPathUtility.GetUvxCommandParts(); unity["command"] = uvxPath; var args = new List { packageName }; if (!string.IsNullOrEmpty(fromUrl)) { args.Insert(0, fromUrl); args.Insert(0, "--from"); } args.Add("--transport"); args.Add("stdio"); unity["args"] = JArray.FromObject(args.ToArray()); // Remove url/serverUrl if they exist from previous config if (unity["url"] != null) unity.Remove("url"); if (unity["serverUrl"] != null) unity.Remove("serverUrl"); if (isVSCode) { unity["type"] = "stdio"; } } // Remove type for non-VSCode clients if (!isVSCode && unity["type"] != null) { unity.Remove("type"); } bool requiresEnv = client?.mcpType == McpTypes.Kiro; bool requiresDisabled = client != null && (client.mcpType == McpTypes.Windsurf || client.mcpType == McpTypes.Kiro); if (requiresEnv) { if (unity["env"] == null) { unity["env"] = new JObject(); } } else if (isWindsurf && unity["env"] != null) { unity.Remove("env"); } if (requiresDisabled && unity["disabled"] == null) { unity["disabled"] = false; } } private static JObject EnsureObject(JObject parent, string name) { if (parent[name] is JObject o) return o; var created = new JObject(); parent[name] = created; return created; } } }