@@ -67,23 +67,6 @@ public static void EnsureServerInstalled()
6767 || ( ! string . IsNullOrEmpty ( embeddedVer ) && CompareSemverSafe ( legacyVer , embeddedVer ) < 0 ) ;
6868 if ( legacyOlder )
6969 {
70- // If referenced, attempt to rewire known configs (EditorPrefs, Cursor) to canonical
71- bool stillRef = IsPathPossiblyReferencedByPrefsOrKnownConfigs ( legacySrc ) ;
72- if ( stillRef )
73- {
74- bool rewired = TryRewriteKnownConfigsToCanonical ( legacySrc , destSrc ) ;
75- if ( rewired )
76- {
77- McpLog . Info ( $ "Rewired configs from legacy '{ legacySrc } ' to canonical '{ destSrc } '.", always : false ) ;
78- }
79- stillRef = IsPathPossiblyReferencedByPrefsOrKnownConfigs ( legacySrc ) ;
80- }
81- // If still referenced after rewrite attempts, skip deletion
82- if ( stillRef )
83- {
84- McpLog . Info ( $ "Skipping removal of legacy server at '{ legacyRoot } ' (still referenced).", always : false ) ;
85- continue ;
86- }
8770 TryKillUvForPath ( legacySrc ) ;
8871 try
8972 {
@@ -134,24 +117,20 @@ public static string GetServerPath()
134117 /// </summary>
135118 private static string GetSaveLocation ( )
136119 {
137- // Prefer Unity's platform to avoid RuntimeInformation quirks under Mono/macOS
120+ // Prefer Unity's platform first (more reliable under Mono/macOS), then fallback
138121 try
139122 {
140123 if ( Application . platform == RuntimePlatform . OSXEditor )
141124 {
142125 string home = Environment . GetFolderPath ( Environment . SpecialFolder . Personal ) ?? string . Empty ;
143126 string appSupport = Path . Combine ( home , "Library" , "Application Support" ) ;
144- string path = Path . Combine ( appSupport , RootFolder ) ;
145- McpLog . Info ( $ "Resolved canonical install root (macOS): { path } ", always : false ) ;
146- return path ;
127+ return Path . Combine ( appSupport , RootFolder ) ;
147128 }
148129 if ( Application . platform == RuntimePlatform . WindowsEditor )
149130 {
150131 var localAppData = Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData )
151132 ?? Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) ?? string . Empty , "AppData" , "Local" ) ;
152- string path = Path . Combine ( localAppData , RootFolder ) ;
153- McpLog . Info ( $ "Resolved canonical install root (Windows): { path } ", always : false ) ;
154- return path ;
133+ return Path . Combine ( localAppData , RootFolder ) ;
155134 }
156135 if ( Application . platform == RuntimePlatform . LinuxEditor )
157136 {
@@ -160,19 +139,12 @@ private static string GetSaveLocation()
160139 {
161140 xdg = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) ?? string . Empty , ".local" , "share" ) ;
162141 }
163- string path = Path . Combine ( xdg , RootFolder ) ;
164- McpLog . Info ( $ "Resolved canonical install root (Linux): { path } ", always : false ) ;
165- return path ;
142+ return Path . Combine ( xdg , RootFolder ) ;
166143 }
167144 }
168145 catch { }
169146
170- // Fallback to RuntimeInformation if Application.platform is unavailable
171- if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
172- {
173- string home = Environment . GetFolderPath ( Environment . SpecialFolder . Personal ) ?? string . Empty ;
174- return Path . Combine ( home , "Library" , "Application Support" , RootFolder ) ;
175- }
147+ // Fallback to RuntimeInformation
176148 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
177149 {
178150 var localAppData = Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData )
@@ -182,9 +154,17 @@ private static string GetSaveLocation()
182154 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) )
183155 {
184156 var xdg = Environment . GetEnvironmentVariable ( "XDG_DATA_HOME" ) ;
185- if ( string . IsNullOrEmpty ( xdg ) ) xdg = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) ?? string . Empty , ".local" , "share" ) ;
157+ if ( string . IsNullOrEmpty ( xdg ) )
158+ {
159+ xdg = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) ?? string . Empty , ".local" , "share" ) ;
160+ }
186161 return Path . Combine ( xdg , RootFolder ) ;
187162 }
163+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
164+ {
165+ string home = Environment . GetFolderPath ( Environment . SpecialFolder . Personal ) ?? string . Empty ;
166+ return Path . Combine ( home , "Library" , "Application Support" , RootFolder ) ;
167+ }
188168 throw new Exception ( "Unsupported operating system." ) ;
189169 }
190170
@@ -297,200 +277,6 @@ private static bool PathsEqualSafe(string a, string b)
297277 catch { return false ; }
298278 }
299279
300- private static bool IsPathPossiblyReferencedByPrefsOrKnownConfigs ( string serverSrcPath )
301- {
302- try
303- {
304- if ( string . IsNullOrEmpty ( serverSrcPath ) ) return false ;
305-
306- // EditorPrefs overrides
307- try
308- {
309- string prefServerSrc = EditorPrefs . GetString ( "MCPForUnity.ServerSrc" , string . Empty ) ?? string . Empty ;
310- if ( ! string . IsNullOrEmpty ( prefServerSrc ) && PathsEqualSafe ( prefServerSrc , serverSrcPath ) ) return true ;
311-
312- string prefOverride = EditorPrefs . GetString ( "MCPForUnity.PythonDirOverride" , string . Empty ) ?? string . Empty ;
313- if ( ! string . IsNullOrEmpty ( prefOverride ) && PathsEqualSafe ( prefOverride , serverSrcPath ) ) return true ;
314- }
315- catch { }
316-
317- // Cursor config (~/.cursor/mcp.json)
318- string user = Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) ?? string . Empty ;
319- string cursorCfg = Path . Combine ( user , ".cursor" , "mcp.json" ) ;
320- if ( File . Exists ( cursorCfg ) )
321- {
322- try
323- {
324- string json = File . ReadAllText ( cursorCfg ) ;
325- string dir = ExtractDirectoryArgFromJson ( json ) ;
326- if ( ! string . IsNullOrEmpty ( dir ) && PathsEqualSafe ( dir , serverSrcPath ) ) return true ;
327- }
328- catch { }
329- }
330- }
331- catch { }
332- return false ;
333- }
334-
335- private static bool TryRewriteKnownConfigsToCanonical ( string legacySrc , string canonicalSrc )
336- {
337- bool changed = false ;
338- try
339- {
340- // Normalize for comparison
341- string normLegacy = NormalizePathSafe ( legacySrc ) ;
342- string normCanon = NormalizePathSafe ( canonicalSrc ) ;
343-
344- // EditorPrefs
345- try
346- {
347- string prefServerSrc = EditorPrefs . GetString ( "MCPForUnity.ServerSrc" , string . Empty ) ?? string . Empty ;
348- if ( ! string . IsNullOrEmpty ( prefServerSrc ) && PathsEqualSafe ( prefServerSrc , normLegacy ) )
349- {
350- EditorPrefs . SetString ( "MCPForUnity.ServerSrc" , normCanon ) ;
351- changed = true ;
352- }
353- string prefOverride = EditorPrefs . GetString ( "MCPForUnity.PythonDirOverride" , string . Empty ) ?? string . Empty ;
354- if ( ! string . IsNullOrEmpty ( prefOverride ) && PathsEqualSafe ( prefOverride , normLegacy ) )
355- {
356- EditorPrefs . SetString ( "MCPForUnity.PythonDirOverride" , normCanon ) ;
357- changed = true ;
358- }
359- }
360- catch { }
361-
362- // Cursor config (~/.cursor/mcp.json)
363- try
364- {
365- string user = Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) ?? string . Empty ;
366- string cursorCfg = Path . Combine ( user , ".cursor" , "mcp.json" ) ;
367- if ( File . Exists ( cursorCfg ) )
368- {
369- string json = File . ReadAllText ( cursorCfg ) ;
370- string currentDir = ExtractDirectoryArgFromJson ( json ) ;
371- if ( ! string . IsNullOrEmpty ( currentDir ) && PathsEqualSafe ( currentDir , normLegacy ) )
372- {
373- string updated = ReplaceDirectoryArgInJson ( json , normCanon ) ;
374- if ( ! string . IsNullOrEmpty ( updated ) && ! string . Equals ( updated , json , StringComparison . Ordinal ) )
375- {
376- try
377- {
378- string backup = cursorCfg + ".bak" ;
379- File . Copy ( cursorCfg , backup , overwrite : true ) ;
380- }
381- catch { }
382- File . WriteAllText ( cursorCfg , updated ) ;
383- changed = true ;
384- }
385- }
386- }
387- }
388- catch { }
389- }
390- catch { }
391- return changed ;
392- }
393-
394- // Best-effort: rewrite the value following --directory in the first args array found
395- private static string ReplaceDirectoryArgInJson ( string json , string newDirectory )
396- {
397- try
398- {
399- if ( string . IsNullOrEmpty ( json ) ) return json ;
400- int argsIdx = json . IndexOf ( "\" args\" " , StringComparison . OrdinalIgnoreCase ) ;
401- if ( argsIdx < 0 ) return json ;
402- int arrStart = json . IndexOf ( '[' , argsIdx ) ;
403- if ( arrStart < 0 ) return json ;
404- int depth = 0 ;
405- int arrEnd = - 1 ;
406- for ( int i = arrStart ; i < json . Length ; i ++ )
407- {
408- char c = json [ i ] ;
409- if ( c == '[' ) depth ++ ;
410- else if ( c == ']' ) { depth -- ; if ( depth == 0 ) { arrEnd = i ; break ; } }
411- }
412- if ( arrEnd <= arrStart ) return json ;
413-
414- string arrBody = json . Substring ( arrStart + 1 , arrEnd - arrStart - 1 ) ;
415- // Split simple string array by commas at top level
416- string [ ] raw = arrBody . Split ( new [ ] { ',' } , StringSplitOptions . RemoveEmptyEntries ) ;
417- var parts = new List < string > ( raw . Length ) ;
418- foreach ( var r in raw )
419- {
420- string s = r . Trim ( ) ;
421- if ( s . Length >= 2 && s [ 0 ] == '"' && s [ s . Length - 1 ] == '"' )
422- {
423- s = s . Substring ( 1 , s . Length - 2 ) ;
424- }
425- parts . Add ( s ) ;
426- }
427-
428- for ( int i = 0 ; i < parts . Count - 1 ; i ++ )
429- {
430- if ( string . Equals ( parts [ i ] , "--directory" , StringComparison . OrdinalIgnoreCase ) )
431- {
432- parts [ i + 1 ] = newDirectory ;
433- // Rebuild array JSON
434- var sb = new StringBuilder ( ) ;
435- for ( int j = 0 ; j < parts . Count ; j ++ )
436- {
437- if ( j > 0 ) sb . Append ( ", " ) ;
438- sb . Append ( '"' ) . Append ( parts [ j ] . Replace ( "\\ " , "\\ \\ " ) . Replace ( "\" " , "\\ \" " ) ) . Append ( '"' ) ;
439- }
440- string newArr = sb . ToString ( ) ;
441- string rebuilt = json . Substring ( 0 , arrStart + 1 ) + newArr + json . Substring ( arrEnd ) ;
442- return rebuilt ;
443- }
444- }
445- }
446- catch { }
447- return json ;
448- }
449-
450- // Minimal helper to extract the value following a --directory token in a plausible JSON args array
451- private static string ExtractDirectoryArgFromJson ( string json )
452- {
453- try
454- {
455- if ( string . IsNullOrEmpty ( json ) ) return null ;
456- int argsIdx = json . IndexOf ( "\" args\" " , StringComparison . OrdinalIgnoreCase ) ;
457- if ( argsIdx < 0 ) return null ;
458- int arrStart = json . IndexOf ( '[' , argsIdx ) ;
459- if ( arrStart < 0 ) return null ;
460- int depth = 0 ;
461- int arrEnd = - 1 ;
462- for ( int i = arrStart ; i < json . Length ; i ++ )
463- {
464- char c = json [ i ] ;
465- if ( c == '[' ) depth ++ ;
466- else if ( c == ']' ) { depth -- ; if ( depth == 0 ) { arrEnd = i ; break ; } }
467- }
468- if ( arrEnd <= arrStart ) return null ;
469- string arrBody = json . Substring ( arrStart + 1 , arrEnd - arrStart - 1 ) ;
470- // Split on commas at top-level (best effort for simple arrays of strings)
471- string [ ] raw = arrBody . Split ( new [ ] { ',' } , StringSplitOptions . RemoveEmptyEntries ) ;
472- var parts = new List < string > ( raw . Length ) ;
473- foreach ( var r in raw )
474- {
475- string s = r . Trim ( ) ;
476- if ( s . Length >= 2 && s [ 0 ] == '"' && s [ s . Length - 1 ] == '"' )
477- {
478- s = s . Substring ( 1 , s . Length - 2 ) ;
479- }
480- parts . Add ( s ) ;
481- }
482- for ( int i = 0 ; i < parts . Count - 1 ; i ++ )
483- {
484- if ( string . Equals ( parts [ i ] , "--directory" , StringComparison . OrdinalIgnoreCase ) )
485- {
486- return parts [ i + 1 ] ;
487- }
488- }
489- }
490- catch { }
491- return null ;
492- }
493-
494280 private static IEnumerable < string > GetLegacyRootsForDetection ( )
495281 {
496282 var roots = new System . Collections . Generic . List < string > ( ) ;
@@ -544,37 +330,32 @@ private static void TryKillUvForPath(string serverSrcPath)
544330 catch { }
545331 }
546332
547- private static string ReadVersionFile ( string path )
548- {
549- try
550- {
551- if ( string . IsNullOrEmpty ( path ) || ! File . Exists ( path ) ) return null ;
552- string v = File . ReadAllText ( path ) . Trim ( ) ;
553- return string . IsNullOrEmpty ( v ) ? null : v ;
554- }
555- catch { return null ; }
556- }
557-
558333 // Escape regex metacharacters so the path is treated literally by pgrep -f
559334 private static string EscapeForPgrep ( string path )
560335 {
561336 if ( string . IsNullOrEmpty ( path ) ) return path ;
562- // Escape backslash first, then regex metacharacters
563337 string s = path . Replace ( "\\ " , "\\ \\ " ) ;
564338 char [ ] meta = new [ ] { '.' , '+' , '*' , '?' , '^' , '$' , '(' , ')' , '[' , ']' , '{' , '}' , '|' } ;
565339 var sb = new StringBuilder ( s . Length * 2 ) ;
566340 foreach ( char c in s )
567341 {
568- if ( Array . IndexOf ( meta , c ) >= 0 )
569- {
570- sb . Append ( '\\ ' ) ;
571- }
342+ if ( Array . IndexOf ( meta , c ) >= 0 ) sb . Append ( '\\ ' ) ;
572343 sb . Append ( c ) ;
573344 }
574- // Also escape double quotes which we wrap the pattern with
575345 return sb . ToString ( ) . Replace ( "\" " , "\\ \" " ) ;
576346 }
577347
348+ private static string ReadVersionFile ( string path )
349+ {
350+ try
351+ {
352+ if ( string . IsNullOrEmpty ( path ) || ! File . Exists ( path ) ) return null ;
353+ string v = File . ReadAllText ( path ) . Trim ( ) ;
354+ return string . IsNullOrEmpty ( v ) ? null : v ;
355+ }
356+ catch { return null ; }
357+ }
358+
578359 private static int CompareSemverSafe ( string a , string b )
579360 {
580361 try
0 commit comments