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 disk
main
Marcus Sanatan 2025-08-24 06:57:11 -04:00 committed by GitHub
parent 97fb4a775e
commit 22fe9dfbdb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 455 additions and 78 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d2b20bbd70a544baf891f3caecd384cb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -3,7 +3,7 @@ using Newtonsoft.Json;
using NUnit.Framework; using NUnit.Framework;
using MCPForUnity.Editor.Tools; using MCPForUnity.Editor.Tools;
namespace MCPForUnityTests.Editor namespace MCPForUnityTests.Editor.Tools
{ {
public class CommandRegistryTests public class CommandRegistryTests
{ {

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 575ef6172fca24e4bbe5ecc1160691bb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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");
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2dbc50071a45a4adc8e7a91a25bd4fd8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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");
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3fc4210e7cbef4479b2cb9498b1580a6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5c07c3369f73943919d9e086a81d1dcc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -206,10 +206,10 @@ namespace MCPForUnity.Editor.Helpers
string registryFile = GetRegistryFilePath(); string registryFile = GetRegistryFilePath();
string json = JsonConvert.SerializeObject(portConfig, Formatting.Indented); string json = JsonConvert.SerializeObject(portConfig, Formatting.Indented);
// Write to hashed, project-scoped file // 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 // Also write to legacy stable filename to avoid hash/case drift across reloads
string legacy = Path.Combine(GetRegistryDirectory(), RegistryFileName); 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"); if (IsDebugEnabled()) Debug.Log($"<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>: Saved port {port} to storage");
} }

View File

@ -719,7 +719,7 @@ namespace MCPForUnity.Editor
project_path = Application.dataPath, project_path = Application.dataPath,
last_heartbeat = DateTime.UtcNow.ToString("O") 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) catch (Exception)
{ {

View File

@ -217,7 +217,7 @@ namespace MCPForUnity.Editor.Tools
try try
{ {
File.WriteAllText(fullPath, contents); File.WriteAllText(fullPath, contents, new System.Text.UTF8Encoding(false));
AssetDatabase.ImportAsset(relativePath); AssetDatabase.ImportAsset(relativePath);
AssetDatabase.Refresh(); // Ensure Unity recognizes the new script AssetDatabase.Refresh(); // Ensure Unity recognizes the new script
return Response.Success( return Response.Success(
@ -298,7 +298,7 @@ namespace MCPForUnity.Editor.Tools
try try
{ {
File.WriteAllText(fullPath, contents); File.WriteAllText(fullPath, contents, new System.Text.UTF8Encoding(false));
AssetDatabase.ImportAsset(relativePath); // Re-import to reflect changes AssetDatabase.ImportAsset(relativePath); // Re-import to reflect changes
AssetDatabase.Refresh(); AssetDatabase.Refresh();
return Response.Success( return Response.Success(

View File

@ -171,7 +171,7 @@ namespace MCPForUnity.Editor.Tools
try try
{ {
File.WriteAllText(fullPath, contents); File.WriteAllText(fullPath, contents, new System.Text.UTF8Encoding(false));
AssetDatabase.ImportAsset(relativePath); AssetDatabase.ImportAsset(relativePath);
AssetDatabase.Refresh(); // Ensure Unity recognizes the new shader AssetDatabase.Refresh(); // Ensure Unity recognizes the new shader
return Response.Success( return Response.Success(
@ -239,7 +239,7 @@ namespace MCPForUnity.Editor.Tools
try try
{ {
File.WriteAllText(fullPath, contents); File.WriteAllText(fullPath, contents, new System.Text.UTF8Encoding(false));
AssetDatabase.ImportAsset(relativePath); AssetDatabase.ImportAsset(relativePath);
AssetDatabase.Refresh(); AssetDatabase.Refresh();
return Response.Success( return Response.Success(

View File

@ -9,6 +9,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using MCPForUnity.Editor.Data; using MCPForUnity.Editor.Data;
@ -1095,25 +1096,18 @@ namespace MCPForUnity.Editor.Windows
} }
// 4) Ensure containers exist and write back minimal changes // 4) Ensure containers exist and write back minimal changes
if (isVSCode) JObject existingRoot;
{ if (existingConfig is JObject eo)
if (existingConfig.servers == null) existingConfig.servers = new Newtonsoft.Json.Linq.JObject(); existingRoot = eo;
if (existingConfig.servers.unityMCP == null) existingConfig.servers.unityMCP = new Newtonsoft.Json.Linq.JObject(); else
existingConfig.servers.unityMCP.command = uvPath; existingRoot = JObject.FromObject(existingConfig);
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);
}
string mergedJson = JsonConvert.SerializeObject(existingConfig, jsonSettings); existingRoot = ConfigJsonBuilder.ApplyUnityServerToExistingConfig(existingRoot, uvPath, serverSrc, mcpClient);
string mergedJson = JsonConvert.SerializeObject(existingRoot, jsonSettings);
string tmp = configPath + ".tmp"; 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)) if (System.IO.File.Exists(configPath))
System.IO.File.Replace(tmp, configPath, null); System.IO.File.Replace(tmp, configPath, null);
else else
@ -1143,62 +1137,15 @@ namespace MCPForUnity.Editor.Windows
{ {
// Get the Python directory path using Package Manager API // Get the Python directory path using Package Manager API
string pythonDir = FindPackagePythonDirectory(); string pythonDir = FindPackagePythonDirectory();
string manualConfigJson; // Build manual JSON centrally using the shared builder
string uvPathForManual = FindUvPath();
// Create common JsonSerializerSettings if (uvPathForManual == null)
JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
// Use switch statement to handle different client types
switch (mcpClient.mcpType)
{ {
case McpTypes.VSCode: UnityEngine.Debug.LogError("UV package manager not found. Cannot generate manual configuration.");
// Resolve uv so VSCode launches the correct executable even if not on PATH return;
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;
} }
string manualConfigJson = ConfigJsonBuilder.BuildManualConfigJson(uvPathForManual, pythonDir, mcpClient);
ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient); ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient);
} }