Skip to content

Commit 6e22721

Browse files
committed
feat: preserve existing client config; prefer installed server path; add ResolveServerSrc; block PackageCache unless dev override; canonical uv args
1 parent a7af0cd commit 6e22721

File tree

1 file changed

+152
-138
lines changed

1 file changed

+152
-138
lines changed

UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs

Lines changed: 152 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -931,24 +931,41 @@ private void ToggleUnityBridge()
931931
Repaint();
932932
}
933933

934-
private string WriteToConfig(string pythonDir, string configPath, McpClient mcpClient = null)
934+
private static bool IsValidUv(string path)
935+
{
936+
return !string.IsNullOrEmpty(path)
937+
&& System.IO.Path.IsPathRooted(path)
938+
&& System.IO.File.Exists(path);
939+
}
940+
941+
private static string ExtractDirectoryArg(string[] args)
942+
{
943+
if (args == null) return null;
944+
for (int i = 0; i < args.Length - 1; i++)
945+
{
946+
if (string.Equals(args[i], "--directory", StringComparison.OrdinalIgnoreCase))
947+
{
948+
return args[i + 1];
949+
}
950+
}
951+
return null;
952+
}
953+
954+
private static bool ArgsEqual(string[] a, string[] b)
955+
{
956+
if (a == null || b == null) return a == b;
957+
if (a.Length != b.Length) return false;
958+
for (int i = 0; i < a.Length; i++)
959+
{
960+
if (!string.Equals(a[i], b[i], StringComparison.Ordinal)) return false;
961+
}
962+
return true;
963+
}
964+
965+
private string WriteToConfig(string pythonDir, string configPath, McpClient mcpClient = null)
935966
{
936-
string uvPath = FindUvPath();
937-
if (uvPath == null)
938-
{
939-
return "UV package manager not found. Please install UV first.";
940-
}
941-
942-
// Create configuration object for unityMCP
943-
McpConfigServer unityMCPConfig = new()
944-
{
945-
command = uvPath,
946-
args = new[] { "--directory", pythonDir, "run", "server.py" },
947-
};
948-
if (mcpClient?.mcpType == McpTypes.VSCode)
949-
{
950-
unityMCPConfig.type = "stdio";
951-
}
967+
// 0) Respect explicit lock (hidden pref or UI toggle)
968+
try { if (UnityEditor.EditorPrefs.GetBool("UnityMCP.LockCursorConfig", false)) return "Skipped (locked)"; } catch { }
952969

953970
JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
954971

@@ -989,123 +1006,83 @@ private string WriteToConfig(string pythonDir, string configPath, McpClient mcpC
9891006
existingConfig = new Newtonsoft.Json.Linq.JObject();
9901007
}
9911008

992-
// Handle different client types with a switch statement
993-
//Comments: Interestingly, VSCode has mcp.servers.unityMCP while others have mcpServers.unityMCP, which is why we need to prevent this
994-
switch (mcpClient?.mcpType)
995-
{
996-
case McpTypes.VSCode:
997-
// VSCode-specific configuration (top-level "servers")
998-
if (existingConfig.servers == null)
999-
{
1000-
existingConfig.servers = new Newtonsoft.Json.Linq.JObject();
1001-
}
1009+
// Determine existing entry references (command/args)
1010+
string existingCommand = null;
1011+
string[] existingArgs = null;
1012+
bool isVSCode = (mcpClient?.mcpType == McpTypes.VSCode);
1013+
try
1014+
{
1015+
if (isVSCode)
1016+
{
1017+
existingCommand = existingConfig?.servers?.unityMCP?.command?.ToString();
1018+
existingArgs = existingConfig?.servers?.unityMCP?.args?.ToObject<string[]>();
1019+
}
1020+
else
1021+
{
1022+
existingCommand = existingConfig?.mcpServers?.unityMCP?.command?.ToString();
1023+
existingArgs = existingConfig?.mcpServers?.unityMCP?.args?.ToObject<string[]>();
1024+
}
1025+
}
1026+
catch { }
10021027

1003-
// Add/update UnityMCP server in VSCode mcp.json
1004-
existingConfig.servers.unityMCP =
1005-
JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JToken>(
1006-
JsonConvert.SerializeObject(unityMCPConfig)
1007-
);
1008-
break;
1028+
// 1) Start from existing, only fill gaps
1029+
string uvPath = IsValidUv(existingCommand) ? existingCommand : FindUvPath();
1030+
if (uvPath == null) return "UV package manager not found. Please install UV first.";
10091031

1010-
default:
1011-
// Standard MCP configuration (Claude Desktop, Cursor, etc.)
1012-
// Ensure mcpServers object exists
1013-
if (existingConfig.mcpServers == null)
1014-
{
1015-
existingConfig.mcpServers = new Newtonsoft.Json.Linq.JObject();
1016-
}
1032+
string serverSrc = ExtractDirectoryArg(existingArgs);
1033+
bool serverValid = !string.IsNullOrEmpty(serverSrc)
1034+
&& System.IO.File.Exists(System.IO.Path.Combine(serverSrc, "server.py"));
1035+
if (!serverValid)
1036+
{
1037+
serverSrc = ResolveServerSrc();
1038+
}
10171039

1018-
// Add/update UnityMCP server in standard MCP settings
1019-
existingConfig.mcpServers.unityMCP =
1020-
JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JToken>(
1021-
JsonConvert.SerializeObject(unityMCPConfig)
1022-
);
1023-
break;
1024-
}
1040+
// Hard-block PackageCache on Windows unless dev override is set
1041+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
1042+
&& serverSrc.IndexOf(@"\Library\PackageCache\", StringComparison.OrdinalIgnoreCase) >= 0
1043+
&& !UnityEditor.EditorPrefs.GetBool("UnityMCP.UseEmbeddedServer", false))
1044+
{
1045+
serverSrc = ServerInstaller.GetServerPath();
1046+
}
10251047

1026-
// If config already has a working absolute uv path, avoid rewriting it on refresh
1027-
try
1028-
{
1029-
if (mcpClient?.mcpType != McpTypes.ClaudeCode)
1030-
{
1031-
// Inspect existing command for stability (Windows absolute path that exists)
1032-
string existingCommand = null;
1033-
if (mcpClient?.mcpType == McpTypes.VSCode)
1034-
{
1035-
existingCommand = existingConfig?.servers?.unityMCP?.command?.ToString();
1036-
}
1037-
else
1038-
{
1039-
existingCommand = existingConfig?.mcpServers?.unityMCP?.command?.ToString();
1040-
}
1048+
// 2) Canonical args order
1049+
var newArgs = new[] { "run", "--directory", serverSrc, "server.py" };
10411050

1042-
if (!string.IsNullOrEmpty(existingCommand))
1043-
{
1044-
bool keep = false;
1045-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
1046-
{
1047-
// Consider absolute, existing paths as stable; prefer WinGet Links
1048-
if (Path.IsPathRooted(existingCommand) && File.Exists(existingCommand))
1049-
{
1050-
keep = true;
1051-
}
1052-
}
1053-
else
1054-
{
1055-
// On Unix, keep absolute existing path as well
1056-
if (Path.IsPathRooted(existingCommand) && File.Exists(existingCommand))
1057-
{
1058-
keep = true;
1059-
}
1060-
}
1051+
// 3) Only write if changed
1052+
bool changed = !string.Equals(existingCommand, uvPath, StringComparison.Ordinal)
1053+
|| !ArgsEqual(existingArgs, newArgs);
1054+
if (!changed)
1055+
{
1056+
return "Configured successfully"; // nothing to do
1057+
}
10611058

1062-
if (keep)
1063-
{
1064-
// Merge without replacing the existing command
1065-
if (mcpClient?.mcpType == McpTypes.VSCode)
1066-
{
1067-
if (existingConfig.servers == null)
1068-
{
1069-
existingConfig.servers = new Newtonsoft.Json.Linq.JObject();
1070-
}
1071-
if (existingConfig.servers.unityMCP == null)
1072-
{
1073-
existingConfig.servers.unityMCP = new Newtonsoft.Json.Linq.JObject();
1074-
}
1075-
existingConfig.servers.unityMCP.args =
1076-
JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JToken>(
1077-
JsonConvert.SerializeObject(unityMCPConfig.args)
1078-
);
1079-
}
1080-
else
1081-
{
1082-
if (existingConfig.mcpServers == null)
1083-
{
1084-
existingConfig.mcpServers = new Newtonsoft.Json.Linq.JObject();
1085-
}
1086-
if (existingConfig.mcpServers.unityMCP == null)
1087-
{
1088-
existingConfig.mcpServers.unityMCP = new Newtonsoft.Json.Linq.JObject();
1089-
}
1090-
existingConfig.mcpServers.unityMCP.args =
1091-
JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JToken>(
1092-
JsonConvert.SerializeObject(unityMCPConfig.args)
1093-
);
1094-
}
1095-
string mergedKeep = JsonConvert.SerializeObject(existingConfig, jsonSettings);
1096-
File.WriteAllText(configPath, mergedKeep);
1097-
return "Configured successfully";
1098-
}
1099-
}
1100-
}
1101-
}
1102-
catch { /* fall back to normal write */ }
1059+
// 4) Ensure containers exist and write back minimal changes
1060+
if (isVSCode)
1061+
{
1062+
if (existingConfig.servers == null) existingConfig.servers = new Newtonsoft.Json.Linq.JObject();
1063+
if (existingConfig.servers.unityMCP == null) existingConfig.servers.unityMCP = new Newtonsoft.Json.Linq.JObject();
1064+
existingConfig.servers.unityMCP.command = uvPath;
1065+
existingConfig.servers.unityMCP.args = Newtonsoft.Json.Linq.JArray.FromObject(newArgs);
1066+
existingConfig.servers.unityMCP.type = "stdio";
1067+
}
1068+
else
1069+
{
1070+
if (existingConfig.mcpServers == null) existingConfig.mcpServers = new Newtonsoft.Json.Linq.JObject();
1071+
if (existingConfig.mcpServers.unityMCP == null) existingConfig.mcpServers.unityMCP = new Newtonsoft.Json.Linq.JObject();
1072+
existingConfig.mcpServers.unityMCP.command = uvPath;
1073+
existingConfig.mcpServers.unityMCP.args = Newtonsoft.Json.Linq.JArray.FromObject(newArgs);
1074+
}
11031075

1104-
// Write the merged configuration back to file
1105-
string mergedJson = JsonConvert.SerializeObject(existingConfig, jsonSettings);
1106-
File.WriteAllText(configPath, mergedJson);
1076+
string mergedJson = JsonConvert.SerializeObject(existingConfig, jsonSettings);
1077+
File.WriteAllText(configPath, mergedJson);
1078+
try
1079+
{
1080+
if (IsValidUv(uvPath)) UnityEditor.EditorPrefs.SetString("UnityMCP.UvPath", uvPath);
1081+
UnityEditor.EditorPrefs.SetString("UnityMCP.ServerSrc", serverSrc);
1082+
}
1083+
catch { }
11071084

1108-
return "Configured successfully";
1085+
return "Configured successfully";
11091086
}
11101087

11111088
private void ShowManualConfigurationInstructions(
@@ -1182,9 +1159,38 @@ private void ShowManualInstructionsWindow(string configPath, McpClient mcpClient
11821159
ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient);
11831160
}
11841161

1185-
private string FindPackagePythonDirectory()
1162+
private static string ResolveServerSrc()
1163+
{
1164+
try
1165+
{
1166+
string remembered = UnityEditor.EditorPrefs.GetString("UnityMCP.ServerSrc", string.Empty);
1167+
if (!string.IsNullOrEmpty(remembered) && File.Exists(Path.Combine(remembered, "server.py")))
1168+
{
1169+
return remembered;
1170+
}
1171+
1172+
ServerInstaller.EnsureServerInstalled();
1173+
string installed = ServerInstaller.GetServerPath();
1174+
if (File.Exists(Path.Combine(installed, "server.py")))
1175+
{
1176+
return installed;
1177+
}
1178+
1179+
bool useEmbedded = UnityEditor.EditorPrefs.GetBool("UnityMCP.UseEmbeddedServer", false);
1180+
if (useEmbedded && ServerPathResolver.TryFindEmbeddedServerSource(out string embedded)
1181+
&& File.Exists(Path.Combine(embedded, "server.py")))
1182+
{
1183+
return embedded;
1184+
}
1185+
1186+
return installed;
1187+
}
1188+
catch { return ServerInstaller.GetServerPath(); }
1189+
}
1190+
1191+
private string FindPackagePythonDirectory()
11861192
{
1187-
string pythonDir = ServerInstaller.GetServerPath();
1193+
string pythonDir = ResolveServerSrc();
11881194

11891195
try
11901196
{
@@ -1211,17 +1217,25 @@ private string FindPackagePythonDirectory()
12111217
}
12121218
}
12131219

1214-
// Resolve via shared helper (handles local registry and older fallback)
1215-
if (ServerPathResolver.TryFindEmbeddedServerSource(out string embedded))
1216-
{
1217-
return embedded;
1218-
}
1220+
// Resolve via shared helper (handles local registry and older fallback) only if dev override on
1221+
if (UnityEditor.EditorPrefs.GetBool("UnityMCP.UseEmbeddedServer", false))
1222+
{
1223+
if (ServerPathResolver.TryFindEmbeddedServerSource(out string embedded))
1224+
{
1225+
return embedded;
1226+
}
1227+
}
12191228

1220-
// If still not found, return the placeholder path
1221-
if (debugLogsEnabled)
1222-
{
1223-
UnityEngine.Debug.LogWarning("Could not find Python directory, using placeholder path");
1224-
}
1229+
// Log only if the resolved path does not actually contain server.py
1230+
if (debugLogsEnabled)
1231+
{
1232+
bool hasServer = false;
1233+
try { hasServer = File.Exists(Path.Combine(pythonDir, "server.py")); } catch { }
1234+
if (!hasServer)
1235+
{
1236+
UnityEngine.Debug.LogWarning("Could not find Python directory with server.py; falling back to installed path");
1237+
}
1238+
}
12251239
}
12261240
catch (Exception e)
12271241
{

0 commit comments

Comments
 (0)