Improve Windsurf MCP Config (#231)
* Move the current test to a Tools folder * feat: add env object and disabled flag handling for MCP client configuration * Format manual config specially for Windsurf and Kiro * refactor: extract config JSON building logic into dedicated ConfigJsonBuilder class * refactor: extract unity node population logic into centralized helper method * refactor: only add env property to config for Windsurf and Kiro clients If it ain't broke with the other clients, don't fix... * fix: write UTF-8 without BOM encoding for config files to avoid Windows compatibility issues * fix: enforce UTF-8 encoding without BOM when writing files to diskmain
parent
97fb4a775e
commit
22fe9dfbdb
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d2b20bbd70a544baf891f3caecd384cb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -3,7 +3,7 @@ using Newtonsoft.Json;
|
|||
using NUnit.Framework;
|
||||
using MCPForUnity.Editor.Tools;
|
||||
|
||||
namespace MCPForUnityTests.Editor
|
||||
namespace MCPForUnityTests.Editor.Tools
|
||||
{
|
||||
public class CommandRegistryTests
|
||||
{
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 575ef6172fca24e4bbe5ecc1160691bb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Editor.Models;
|
||||
|
||||
namespace MCPForUnityTests.Editor.Windows
|
||||
{
|
||||
public class ManualConfigJsonBuilderTests
|
||||
{
|
||||
[Test]
|
||||
public void VSCode_ManualJson_HasServers_NoEnv_NoDisabled()
|
||||
{
|
||||
var client = new McpClient { name = "VSCode", mcpType = McpTypes.VSCode };
|
||||
string json = ConfigJsonBuilder.BuildManualConfigJson("/usr/bin/uv", "/path/to/server", client);
|
||||
|
||||
var root = JObject.Parse(json);
|
||||
var unity = (JObject)root.SelectToken("servers.unityMCP");
|
||||
Assert.NotNull(unity, "Expected servers.unityMCP node");
|
||||
Assert.AreEqual("/usr/bin/uv", (string)unity["command"]);
|
||||
CollectionAssert.AreEqual(new[] { "run", "--directory", "/path/to/server", "server.py" }, unity["args"].ToObject<string[]>());
|
||||
Assert.AreEqual("stdio", (string)unity["type"], "VSCode should include type=stdio");
|
||||
Assert.IsNull(unity["env"], "env should not be added for VSCode");
|
||||
Assert.IsNull(unity["disabled"], "disabled should not be added for VSCode");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Windsurf_ManualJson_HasMcpServersEnv_DisabledFalse()
|
||||
{
|
||||
var client = new McpClient { name = "Windsurf", mcpType = McpTypes.Windsurf };
|
||||
string json = ConfigJsonBuilder.BuildManualConfigJson("/usr/bin/uv", "/path/to/server", client);
|
||||
|
||||
var root = JObject.Parse(json);
|
||||
var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
|
||||
Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
|
||||
Assert.NotNull(unity["env"], "env should be included");
|
||||
Assert.AreEqual(false, (bool)unity["disabled"], "disabled:false should be added for Windsurf");
|
||||
Assert.IsNull(unity["type"], "type should not be added for non-VSCode clients");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cursor_ManualJson_HasMcpServers_NoEnv_NoDisabled()
|
||||
{
|
||||
var client = new McpClient { name = "Cursor", mcpType = McpTypes.Cursor };
|
||||
string json = ConfigJsonBuilder.BuildManualConfigJson("/usr/bin/uv", "/path/to/server", client);
|
||||
|
||||
var root = JObject.Parse(json);
|
||||
var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
|
||||
Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
|
||||
Assert.IsNull(unity["env"], "env should not be added for Cursor");
|
||||
Assert.IsNull(unity["disabled"], "disabled should not be added for Cursor");
|
||||
Assert.IsNull(unity["type"], "type should not be added for non-VSCode clients");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2dbc50071a45a4adc8e7a91a25bd4fd8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using MCPForUnity.Editor.Data;
|
||||
using MCPForUnity.Editor.Models;
|
||||
using MCPForUnity.Editor.Windows;
|
||||
|
||||
namespace MCPForUnityTests.Editor.Windows
|
||||
{
|
||||
public class WriteToConfigTests
|
||||
{
|
||||
private string _tempRoot;
|
||||
private string _fakeUvPath;
|
||||
private string _serverSrcDir;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
// Tests are designed for Linux/macOS runners. Skip on Windows due to ProcessStartInfo
|
||||
// restrictions when UseShellExecute=false for .cmd/.bat scripts.
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
Assert.Ignore("WriteToConfig tests are skipped on Windows (CI runs linux).\n" +
|
||||
"ValidateUvBinarySafe requires launching an actual exe on Windows.");
|
||||
}
|
||||
_tempRoot = Path.Combine(Path.GetTempPath(), "UnityMCPTests", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(_tempRoot);
|
||||
|
||||
// Create a fake uv executable that prints a valid version string
|
||||
_fakeUvPath = Path.Combine(_tempRoot, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "uv.cmd" : "uv");
|
||||
File.WriteAllText(_fakeUvPath, "#!/bin/sh\n\necho 'uv 9.9.9'\n");
|
||||
TryChmodX(_fakeUvPath);
|
||||
|
||||
// Create a fake server directory with server.py
|
||||
_serverSrcDir = Path.Combine(_tempRoot, "server-src");
|
||||
Directory.CreateDirectory(_serverSrcDir);
|
||||
File.WriteAllText(Path.Combine(_serverSrcDir, "server.py"), "# dummy server\n");
|
||||
|
||||
// Point the editor to our server dir (so ResolveServerSrc() uses this)
|
||||
EditorPrefs.SetString("MCPForUnity.ServerSrc", _serverSrcDir);
|
||||
// Ensure no lock is enabled
|
||||
EditorPrefs.SetBool("MCPForUnity.LockCursorConfig", false);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
// Clean up editor preferences set during SetUp
|
||||
EditorPrefs.DeleteKey("MCPForUnity.ServerSrc");
|
||||
EditorPrefs.DeleteKey("MCPForUnity.LockCursorConfig");
|
||||
|
||||
// Remove temp files
|
||||
try { if (Directory.Exists(_tempRoot)) Directory.Delete(_tempRoot, true); } catch { }
|
||||
}
|
||||
|
||||
// --- Tests ---
|
||||
|
||||
[Test]
|
||||
public void AddsEnvAndDisabledFalse_ForWindsurf()
|
||||
{
|
||||
var configPath = Path.Combine(_tempRoot, "windsurf.json");
|
||||
WriteInitialConfig(configPath, isVSCode:false, command:_fakeUvPath, directory:"/old/path");
|
||||
|
||||
var client = new McpClient { name = "Windsurf", mcpType = McpTypes.Windsurf };
|
||||
InvokeWriteToConfig(configPath, client);
|
||||
|
||||
var root = JObject.Parse(File.ReadAllText(configPath));
|
||||
var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
|
||||
Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
|
||||
Assert.NotNull(unity["env"], "env should be present for all clients");
|
||||
Assert.IsTrue(unity["env"]!.Type == JTokenType.Object, "env should be an object");
|
||||
Assert.AreEqual(false, (bool)unity["disabled"], "disabled:false should be set for Windsurf when missing");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddsEnvAndDisabledFalse_ForKiro()
|
||||
{
|
||||
var configPath = Path.Combine(_tempRoot, "kiro.json");
|
||||
WriteInitialConfig(configPath, isVSCode:false, command:_fakeUvPath, directory:"/old/path");
|
||||
|
||||
var client = new McpClient { name = "Kiro", mcpType = McpTypes.Kiro };
|
||||
InvokeWriteToConfig(configPath, client);
|
||||
|
||||
var root = JObject.Parse(File.ReadAllText(configPath));
|
||||
var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
|
||||
Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
|
||||
Assert.NotNull(unity["env"], "env should be present for all clients");
|
||||
Assert.IsTrue(unity["env"]!.Type == JTokenType.Object, "env should be an object");
|
||||
Assert.AreEqual(false, (bool)unity["disabled"], "disabled:false should be set for Kiro when missing");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DoesNotAddEnvOrDisabled_ForCursor()
|
||||
{
|
||||
var configPath = Path.Combine(_tempRoot, "cursor.json");
|
||||
WriteInitialConfig(configPath, isVSCode:false, command:_fakeUvPath, directory:"/old/path");
|
||||
|
||||
var client = new McpClient { name = "Cursor", mcpType = McpTypes.Cursor };
|
||||
InvokeWriteToConfig(configPath, client);
|
||||
|
||||
var root = JObject.Parse(File.ReadAllText(configPath));
|
||||
var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
|
||||
Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
|
||||
Assert.IsNull(unity["env"], "env should not be added for non-Windsurf/Kiro clients");
|
||||
Assert.IsNull(unity["disabled"], "disabled should not be added for non-Windsurf/Kiro clients");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DoesNotAddEnvOrDisabled_ForVSCode()
|
||||
{
|
||||
var configPath = Path.Combine(_tempRoot, "vscode.json");
|
||||
WriteInitialConfig(configPath, isVSCode:true, command:_fakeUvPath, directory:"/old/path");
|
||||
|
||||
var client = new McpClient { name = "VSCode", mcpType = McpTypes.VSCode };
|
||||
InvokeWriteToConfig(configPath, client);
|
||||
|
||||
var root = JObject.Parse(File.ReadAllText(configPath));
|
||||
var unity = (JObject)root.SelectToken("servers.unityMCP");
|
||||
Assert.NotNull(unity, "Expected servers.unityMCP node");
|
||||
Assert.IsNull(unity["env"], "env should not be added for VSCode client");
|
||||
Assert.IsNull(unity["disabled"], "disabled should not be added for VSCode client");
|
||||
Assert.AreEqual("stdio", (string)unity["type"], "VSCode entry should include type=stdio");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PreservesExistingEnvAndDisabled()
|
||||
{
|
||||
var configPath = Path.Combine(_tempRoot, "preserve.json");
|
||||
|
||||
// Existing config with env and disabled=true should be preserved
|
||||
var json = new JObject
|
||||
{
|
||||
["mcpServers"] = new JObject
|
||||
{
|
||||
["unityMCP"] = new JObject
|
||||
{
|
||||
["command"] = _fakeUvPath,
|
||||
["args"] = new JArray("run", "--directory", "/old/path", "server.py"),
|
||||
["env"] = new JObject { ["FOO"] = "bar" },
|
||||
["disabled"] = true
|
||||
}
|
||||
}
|
||||
};
|
||||
File.WriteAllText(configPath, json.ToString());
|
||||
|
||||
var client = new McpClient { name = "Windsurf", mcpType = McpTypes.Windsurf };
|
||||
InvokeWriteToConfig(configPath, client);
|
||||
|
||||
var root = JObject.Parse(File.ReadAllText(configPath));
|
||||
var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
|
||||
Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
|
||||
Assert.AreEqual("bar", (string)unity["env"]!["FOO"], "Existing env should be preserved");
|
||||
Assert.AreEqual(true, (bool)unity["disabled"], "Existing disabled value should be preserved");
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
private static void TryChmodX(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "/bin/chmod",
|
||||
Arguments = "+x \"" + path + "\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
using var p = Process.Start(psi);
|
||||
p?.WaitForExit(2000);
|
||||
}
|
||||
catch { /* best-effort on non-Unix */ }
|
||||
}
|
||||
|
||||
private static void WriteInitialConfig(string configPath, bool isVSCode, string command, string directory)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(configPath)!);
|
||||
JObject root;
|
||||
if (isVSCode)
|
||||
{
|
||||
root = new JObject
|
||||
{
|
||||
["servers"] = new JObject
|
||||
{
|
||||
["unityMCP"] = new JObject
|
||||
{
|
||||
["command"] = command,
|
||||
["args"] = new JArray("run", "--directory", directory, "server.py"),
|
||||
["type"] = "stdio"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
root = new JObject
|
||||
{
|
||||
["mcpServers"] = new JObject
|
||||
{
|
||||
["unityMCP"] = new JObject
|
||||
{
|
||||
["command"] = command,
|
||||
["args"] = new JArray("run", "--directory", directory, "server.py")
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
File.WriteAllText(configPath, root.ToString());
|
||||
}
|
||||
|
||||
private static MCPForUnityEditorWindow CreateWindow()
|
||||
{
|
||||
return ScriptableObject.CreateInstance<MCPForUnityEditorWindow>();
|
||||
}
|
||||
|
||||
private static void InvokeWriteToConfig(string configPath, McpClient client)
|
||||
{
|
||||
var window = CreateWindow();
|
||||
var mi = typeof(MCPForUnityEditorWindow).GetMethod("WriteToConfig", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.NotNull(mi, "Could not find WriteToConfig via reflection");
|
||||
|
||||
// pythonDir is unused by WriteToConfig, but pass server src to keep it consistent
|
||||
var result = (string)mi!.Invoke(window, new object[] {
|
||||
/* pythonDir */ string.Empty,
|
||||
/* configPath */ configPath,
|
||||
/* mcpClient */ client
|
||||
});
|
||||
|
||||
Assert.AreEqual("Configured successfully", result, "WriteToConfig should return success");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3fc4210e7cbef4479b2cb9498b1580a6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using MCPForUnity.Editor.Models;
|
||||
|
||||
namespace MCPForUnity.Editor.Helpers
|
||||
{
|
||||
public static class ConfigJsonBuilder
|
||||
{
|
||||
public static string BuildManualConfigJson(string uvPath, string pythonDir, 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, pythonDir, client, isVSCode);
|
||||
|
||||
container["unityMCP"] = unity;
|
||||
|
||||
return root.ToString(Formatting.Indented);
|
||||
}
|
||||
|
||||
public static JObject ApplyUnityServerToExistingConfig(JObject root, string uvPath, string serverSrc, 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, serverSrc, client, isVSCode);
|
||||
|
||||
container["unityMCP"] = unity;
|
||||
return root;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Centralized builder that applies all caveats consistently.
|
||||
/// - Sets command/args with provided directory
|
||||
/// - Ensures env exists
|
||||
/// - Adds type:"stdio" for VSCode
|
||||
/// - Adds disabled:false for Windsurf/Kiro only when missing
|
||||
/// </summary>
|
||||
private static void PopulateUnityNode(JObject unity, string uvPath, string directory, McpClient client, bool isVSCode)
|
||||
{
|
||||
unity["command"] = uvPath;
|
||||
unity["args"] = JArray.FromObject(new[] { "run", "--directory", directory, "server.py" });
|
||||
|
||||
if (isVSCode)
|
||||
{
|
||||
unity["type"] = "stdio";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove type if it somehow exists from previous clients
|
||||
if (unity["type"] != null) unity.Remove("type");
|
||||
}
|
||||
|
||||
if (client != null && (client.mcpType == McpTypes.Windsurf || client.mcpType == McpTypes.Kiro))
|
||||
{
|
||||
if (unity["env"] == null)
|
||||
{
|
||||
unity["env"] = new JObject();
|
||||
}
|
||||
|
||||
if (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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5c07c3369f73943919d9e086a81d1dcc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -206,10 +206,10 @@ namespace MCPForUnity.Editor.Helpers
|
|||
string registryFile = GetRegistryFilePath();
|
||||
string json = JsonConvert.SerializeObject(portConfig, Formatting.Indented);
|
||||
// Write to hashed, project-scoped file
|
||||
File.WriteAllText(registryFile, json);
|
||||
File.WriteAllText(registryFile, json, new System.Text.UTF8Encoding(false));
|
||||
// Also write to legacy stable filename to avoid hash/case drift across reloads
|
||||
string legacy = Path.Combine(GetRegistryDirectory(), RegistryFileName);
|
||||
File.WriteAllText(legacy, json);
|
||||
File.WriteAllText(legacy, json, new System.Text.UTF8Encoding(false));
|
||||
|
||||
if (IsDebugEnabled()) Debug.Log($"<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>: Saved port {port} to storage");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -719,7 +719,7 @@ namespace MCPForUnity.Editor
|
|||
project_path = Application.dataPath,
|
||||
last_heartbeat = DateTime.UtcNow.ToString("O")
|
||||
};
|
||||
File.WriteAllText(filePath, JsonConvert.SerializeObject(payload));
|
||||
File.WriteAllText(filePath, JsonConvert.SerializeObject(payload), new System.Text.UTF8Encoding(false));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ namespace MCPForUnity.Editor.Tools
|
|||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(fullPath, contents);
|
||||
File.WriteAllText(fullPath, contents, new System.Text.UTF8Encoding(false));
|
||||
AssetDatabase.ImportAsset(relativePath);
|
||||
AssetDatabase.Refresh(); // Ensure Unity recognizes the new script
|
||||
return Response.Success(
|
||||
|
|
@ -298,7 +298,7 @@ namespace MCPForUnity.Editor.Tools
|
|||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(fullPath, contents);
|
||||
File.WriteAllText(fullPath, contents, new System.Text.UTF8Encoding(false));
|
||||
AssetDatabase.ImportAsset(relativePath); // Re-import to reflect changes
|
||||
AssetDatabase.Refresh();
|
||||
return Response.Success(
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ namespace MCPForUnity.Editor.Tools
|
|||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(fullPath, contents);
|
||||
File.WriteAllText(fullPath, contents, new System.Text.UTF8Encoding(false));
|
||||
AssetDatabase.ImportAsset(relativePath);
|
||||
AssetDatabase.Refresh(); // Ensure Unity recognizes the new shader
|
||||
return Response.Success(
|
||||
|
|
@ -239,7 +239,7 @@ namespace MCPForUnity.Editor.Tools
|
|||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(fullPath, contents);
|
||||
File.WriteAllText(fullPath, contents, new System.Text.UTF8Encoding(false));
|
||||
AssetDatabase.ImportAsset(relativePath);
|
||||
AssetDatabase.Refresh();
|
||||
return Response.Success(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using MCPForUnity.Editor.Data;
|
||||
|
|
@ -1095,25 +1096,18 @@ namespace MCPForUnity.Editor.Windows
|
|||
}
|
||||
|
||||
// 4) Ensure containers exist and write back minimal changes
|
||||
if (isVSCode)
|
||||
{
|
||||
if (existingConfig.servers == null) existingConfig.servers = new Newtonsoft.Json.Linq.JObject();
|
||||
if (existingConfig.servers.unityMCP == null) existingConfig.servers.unityMCP = new Newtonsoft.Json.Linq.JObject();
|
||||
existingConfig.servers.unityMCP.command = uvPath;
|
||||
existingConfig.servers.unityMCP.args = Newtonsoft.Json.Linq.JArray.FromObject(newArgs);
|
||||
existingConfig.servers.unityMCP.type = "stdio";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (existingConfig.mcpServers == null) existingConfig.mcpServers = new Newtonsoft.Json.Linq.JObject();
|
||||
if (existingConfig.mcpServers.unityMCP == null) existingConfig.mcpServers.unityMCP = new Newtonsoft.Json.Linq.JObject();
|
||||
existingConfig.mcpServers.unityMCP.command = uvPath;
|
||||
existingConfig.mcpServers.unityMCP.args = Newtonsoft.Json.Linq.JArray.FromObject(newArgs);
|
||||
}
|
||||
JObject existingRoot;
|
||||
if (existingConfig is JObject eo)
|
||||
existingRoot = eo;
|
||||
else
|
||||
existingRoot = JObject.FromObject(existingConfig);
|
||||
|
||||
string mergedJson = JsonConvert.SerializeObject(existingConfig, jsonSettings);
|
||||
existingRoot = ConfigJsonBuilder.ApplyUnityServerToExistingConfig(existingRoot, uvPath, serverSrc, mcpClient);
|
||||
|
||||
string mergedJson = JsonConvert.SerializeObject(existingRoot, jsonSettings);
|
||||
string tmp = configPath + ".tmp";
|
||||
System.IO.File.WriteAllText(tmp, mergedJson, System.Text.Encoding.UTF8);
|
||||
// Write UTF-8 without BOM to avoid issues on Windows editors/tools
|
||||
System.IO.File.WriteAllText(tmp, mergedJson, new System.Text.UTF8Encoding(false));
|
||||
if (System.IO.File.Exists(configPath))
|
||||
System.IO.File.Replace(tmp, configPath, null);
|
||||
else
|
||||
|
|
@ -1143,62 +1137,15 @@ namespace MCPForUnity.Editor.Windows
|
|||
{
|
||||
// Get the Python directory path using Package Manager API
|
||||
string pythonDir = FindPackagePythonDirectory();
|
||||
string manualConfigJson;
|
||||
|
||||
// Create common JsonSerializerSettings
|
||||
JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
|
||||
|
||||
// Use switch statement to handle different client types
|
||||
switch (mcpClient.mcpType)
|
||||
// Build manual JSON centrally using the shared builder
|
||||
string uvPathForManual = FindUvPath();
|
||||
if (uvPathForManual == null)
|
||||
{
|
||||
case McpTypes.VSCode:
|
||||
// Resolve uv so VSCode launches the correct executable even if not on PATH
|
||||
string uvPathManual = FindUvPath();
|
||||
if (uvPathManual == null)
|
||||
{
|
||||
UnityEngine.Debug.LogError("UV package manager not found. Cannot generate manual configuration.");
|
||||
return;
|
||||
}
|
||||
// Create VSCode-specific configuration with proper format
|
||||
var vscodeConfig = new
|
||||
{
|
||||
servers = new
|
||||
{
|
||||
unityMCP = new
|
||||
{
|
||||
command = uvPathManual,
|
||||
args = new[] { "run", "--directory", pythonDir, "server.py" },
|
||||
type = "stdio"
|
||||
}
|
||||
}
|
||||
};
|
||||
manualConfigJson = JsonConvert.SerializeObject(vscodeConfig, jsonSettings);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Create standard MCP configuration for other clients
|
||||
string uvPath = FindUvPath();
|
||||
if (uvPath == null)
|
||||
{
|
||||
UnityEngine.Debug.LogError("UV package manager not found. Cannot configure manual setup.");
|
||||
return;
|
||||
}
|
||||
|
||||
McpConfig jsonConfig = new()
|
||||
{
|
||||
mcpServers = new McpConfigServers
|
||||
{
|
||||
unityMCP = new McpConfigServer
|
||||
{
|
||||
command = uvPath,
|
||||
args = new[] { "run", "--directory", pythonDir, "server.py" },
|
||||
},
|
||||
},
|
||||
};
|
||||
manualConfigJson = JsonConvert.SerializeObject(jsonConfig, jsonSettings);
|
||||
break;
|
||||
UnityEngine.Debug.LogError("UV package manager not found. Cannot generate manual configuration.");
|
||||
return;
|
||||
}
|
||||
|
||||
string manualConfigJson = ConfigJsonBuilder.BuildManualConfigJson(uvPathForManual, pythonDir, mcpClient);
|
||||
ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue