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