88using System . Net . Sockets ;
99using System . Threading . Tasks ;
1010using System . Text ;
11+ using System . Collections . Generic ;
1112
1213public class DefaultServerConfig : ServerConfig
1314{
@@ -277,36 +278,80 @@ private void ConfigureClaudeDesktop()
277278 string serverPath = null ;
278279 string pythonDir = null ;
279280
280- // First try to find it in the current project's Assets folder
281- string localServerPath = Path . GetFullPath ( Path . Combine ( Application . dataPath , "unity-mcp" , "Python" , "server.py" ) ) ;
282- if ( File . Exists ( localServerPath ) )
281+ // List of possible locations to search
282+ var possiblePaths = new List < string >
283283 {
284- serverPath = localServerPath ;
285- pythonDir = Path . GetDirectoryName ( serverPath ) ;
284+ // Search in Assets folder - Manual installation
285+ Path . GetFullPath ( Path . Combine ( Application . dataPath , "unity-mcp" , "Python" , "server.py" ) ) ,
286+ Path . GetFullPath ( Path . Combine ( Application . dataPath , "Packages" , "com.justinpbarnett.unity-mcp" , "Python" , "server.py" ) ) ,
287+
288+ // Search in package cache - Package manager installation
289+ Path . GetFullPath ( Path . Combine ( Application . dataPath , ".." , "Library" , "PackageCache" , "com.justinpbarnett.unity-mcp@*" , "Python" , "server.py" ) ) ,
290+
291+ // Search in package manager packages - Git installation
292+ Path . GetFullPath ( Path . Combine ( Application . dataPath , ".." , "Packages" , "com.justinpbarnett.unity-mcp" , "Python" , "server.py" ) )
293+ } ;
294+
295+ UnityEngine . Debug . Log ( "Searching for server.py in the following locations:" ) ;
296+
297+ // First try with explicit paths
298+ foreach ( var path in possiblePaths )
299+ {
300+ // Skip wildcard paths for now
301+ if ( path . Contains ( "*" ) ) continue ;
302+
303+ UnityEngine . Debug . Log ( $ "Checking: { path } ") ;
304+ if ( File . Exists ( path ) )
305+ {
306+ serverPath = path ;
307+ pythonDir = Path . GetDirectoryName ( serverPath ) ;
308+ UnityEngine . Debug . Log ( $ "Found server.py at: { serverPath } ") ;
309+ break ;
310+ }
286311 }
287- else
312+
313+ // If not found, try with wildcard paths (package cache with version)
314+ if ( serverPath == null )
288315 {
289- // If not found locally, try to find it in the package cache
290- string packageName = "com.justinpbarnett.unity-mcp" ;
291- string packageCachePath = Path . Combine ( Application . dataPath , ".." , "Library" , "PackageCache" , packageName ) ;
292- if ( Directory . Exists ( packageCachePath ) )
316+ foreach ( var path in possiblePaths )
293317 {
294- serverPath = Path . GetFullPath ( Path . Combine ( packageCachePath , "Python" , "server.py" ) ) ;
295- if ( File . Exists ( serverPath ) )
318+ if ( ! path . Contains ( "*" ) ) continue ;
319+
320+ string directoryPath = Path . GetDirectoryName ( path ) ;
321+ string searchPattern = Path . GetFileName ( Path . GetDirectoryName ( path ) ) ;
322+ string parentDir = Path . GetDirectoryName ( directoryPath ) ;
323+
324+ if ( Directory . Exists ( parentDir ) )
296325 {
297- pythonDir = Path . GetDirectoryName ( serverPath ) ;
326+ var matchingDirs = Directory . GetDirectories ( parentDir , searchPattern ) ;
327+ UnityEngine . Debug . Log ( $ "Searching in: { parentDir } for pattern: { searchPattern } , found { matchingDirs . Length } matches") ;
328+
329+ foreach ( var dir in matchingDirs )
330+ {
331+ string candidatePath = Path . Combine ( dir , "Python" , "server.py" ) ;
332+ UnityEngine . Debug . Log ( $ "Checking: { candidatePath } ") ;
333+
334+ if ( File . Exists ( candidatePath ) )
335+ {
336+ serverPath = candidatePath ;
337+ pythonDir = Path . GetDirectoryName ( serverPath ) ;
338+ UnityEngine . Debug . Log ( $ "Found server.py at: { serverPath } ") ;
339+ break ;
340+ }
341+ }
342+
343+ if ( serverPath != null ) break ;
298344 }
299345 }
300346 }
301347
302348 if ( serverPath == null || ! File . Exists ( serverPath ) )
303349 {
304- claudeConfigStatus = "Error: Could not find server.py" ;
305- UnityEngine . Debug . LogError ( "Could not find server.py in either local project or package cache" ) ;
350+ ShowManualConfigurationInstructions ( configPath ) ;
306351 return ;
307352 }
308353
309- UnityEngine . Debug . Log ( $ "Found server.py at: { serverPath } ") ;
354+ UnityEngine . Debug . Log ( $ "Using server.py at: { serverPath } ") ;
310355 UnityEngine . Debug . Log ( $ "Python directory: { pythonDir } ") ;
311356
312357 // Create configuration object
@@ -342,8 +387,192 @@ private void ConfigureClaudeDesktop()
342387 }
343388 catch ( Exception e )
344389 {
345- claudeConfigStatus = "Configuration failed" ;
346- UnityEngine . Debug . LogError ( $ "Failed to configure Claude Desktop: { e . Message } ") ;
390+ // Determine the config file path based on OS for error message
391+ string configPath = "" ;
392+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
393+ {
394+ configPath = Path . Combine (
395+ Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) ,
396+ "Claude" ,
397+ "claude_desktop_config.json"
398+ ) ;
399+ }
400+ else if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
401+ {
402+ configPath = Path . Combine (
403+ Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) ,
404+ "Library" ,
405+ "Application Support" ,
406+ "Claude" ,
407+ "claude_desktop_config.json"
408+ ) ;
409+ }
410+
411+ ShowManualConfigurationInstructions ( configPath ) ;
412+ UnityEngine . Debug . LogError ( $ "Failed to configure Claude Desktop: { e . Message } \n { e . StackTrace } ") ;
413+ }
414+ }
415+
416+ private void ShowManualConfigurationInstructions ( string configPath )
417+ {
418+ claudeConfigStatus = "Error: Manual configuration required" ;
419+
420+ // Get the Python directory path that's likely to exist
421+ string pythonDir = "" ;
422+ string [ ] possibleDirs = {
423+ Path . GetFullPath ( Path . Combine ( Application . dataPath , "unity-mcp" , "Python" ) )
424+ } ;
425+
426+ foreach ( var dir in possibleDirs )
427+ {
428+ if ( Directory . Exists ( dir ) && File . Exists ( Path . Combine ( dir , "server.py" ) ) )
429+ {
430+ pythonDir = dir ;
431+ break ;
432+ }
433+ }
434+
435+ // If we couldn't find a Python directory, try to get the Assets path as a fallback
436+ if ( string . IsNullOrEmpty ( pythonDir ) )
437+ {
438+ pythonDir = "/path/to/your/unity-mcp/Python" ;
439+ }
440+
441+ // Create the manual configuration message
442+ var jsonConfig = new MCPConfig
443+ {
444+ mcpServers = new MCPConfigServers
445+ {
446+ unityMCP = new MCPConfigServer
447+ {
448+ command = "uv" ,
449+ args = new [ ]
450+ {
451+ "--directory" ,
452+ pythonDir ,
453+ "run" ,
454+ "server.py"
455+ }
456+ }
457+ }
458+ } ;
459+
460+ var jsonSettings = new JsonSerializerSettings
461+ {
462+ Formatting = Formatting . Indented
463+ } ;
464+ string manualConfigJson = JsonConvert . SerializeObject ( jsonConfig , jsonSettings ) ;
465+
466+ // Show a dedicated configuration window instead of console logs
467+ ManualConfigWindow . ShowWindow ( configPath , manualConfigJson ) ;
468+ }
469+ }
470+
471+ // Editor window to display manual configuration instructions
472+ public class ManualConfigWindow : EditorWindow
473+ {
474+ private string configPath ;
475+ private string configJson ;
476+ private Vector2 scrollPos ;
477+ private bool pathCopied = false ;
478+ private bool jsonCopied = false ;
479+ private float copyFeedbackTimer = 0 ;
480+
481+ public static void ShowWindow ( string configPath , string configJson )
482+ {
483+ var window = GetWindow < ManualConfigWindow > ( "Manual Configuration" ) ;
484+ window . configPath = configPath ;
485+ window . configJson = configJson ;
486+ window . minSize = new Vector2 ( 500 , 400 ) ;
487+ window . Show ( ) ;
488+ }
489+
490+ private void OnGUI ( )
491+ {
492+ scrollPos = EditorGUILayout . BeginScrollView ( scrollPos ) ;
493+
494+ // Header
495+ EditorGUILayout . Space ( 10 ) ;
496+ EditorGUILayout . LabelField ( "Claude Desktop Manual Configuration" , EditorStyles . boldLabel ) ;
497+ EditorGUILayout . Space ( 10 ) ;
498+
499+ // Instructions
500+ EditorGUILayout . LabelField ( "The automatic configuration failed. Please follow these steps:" , EditorStyles . boldLabel ) ;
501+ EditorGUILayout . Space ( 5 ) ;
502+
503+ EditorGUILayout . LabelField ( "1. Open Claude Desktop and go to Settings > Developer > Edit Config" , EditorStyles . wordWrappedLabel ) ;
504+ EditorGUILayout . LabelField ( "2. Create or edit the configuration file at:" , EditorStyles . wordWrappedLabel ) ;
505+
506+ // Config path section with copy button
507+ EditorGUILayout . BeginHorizontal ( ) ;
508+ EditorGUILayout . SelectableLabel ( configPath , EditorStyles . textField , GUILayout . Height ( EditorGUIUtility . singleLineHeight ) ) ;
509+
510+ if ( GUILayout . Button ( "Copy Path" , GUILayout . Width ( 80 ) ) )
511+ {
512+ EditorGUIUtility . systemCopyBuffer = configPath ;
513+ pathCopied = true ;
514+ copyFeedbackTimer = 2f ;
515+ }
516+
517+ EditorGUILayout . EndHorizontal ( ) ;
518+
519+ if ( pathCopied )
520+ {
521+ EditorGUILayout . LabelField ( "Path copied to clipboard!" , EditorStyles . miniLabel ) ;
522+ }
523+
524+ EditorGUILayout . Space ( 10 ) ;
525+
526+ // JSON configuration
527+ EditorGUILayout . LabelField ( "3. Paste the following JSON configuration:" , EditorStyles . wordWrappedLabel ) ;
528+ EditorGUILayout . Space ( 5 ) ;
529+
530+ EditorGUILayout . LabelField ( "Make sure to replace the Python path if necessary:" , EditorStyles . wordWrappedLabel ) ;
531+ EditorGUILayout . Space ( 5 ) ;
532+
533+ // JSON text area with copy button
534+ GUIStyle textAreaStyle = new GUIStyle ( EditorStyles . textArea )
535+ {
536+ wordWrap = true ,
537+ richText = true
538+ } ;
539+
540+ EditorGUILayout . BeginHorizontal ( ) ;
541+ EditorGUILayout . SelectableLabel ( configJson , textAreaStyle , GUILayout . MinHeight ( 200 ) ) ;
542+ EditorGUILayout . EndHorizontal ( ) ;
543+
544+ if ( GUILayout . Button ( "Copy JSON Configuration" ) )
545+ {
546+ EditorGUIUtility . systemCopyBuffer = configJson ;
547+ jsonCopied = true ;
548+ copyFeedbackTimer = 2f ;
549+ }
550+
551+ if ( jsonCopied )
552+ {
553+ EditorGUILayout . LabelField ( "JSON copied to clipboard!" , EditorStyles . miniLabel ) ;
554+ }
555+
556+ EditorGUILayout . Space ( 10 ) ;
557+
558+ // Additional note
559+ EditorGUILayout . HelpBox ( "After configuring, restart Claude Desktop to apply the changes." , MessageType . Info ) ;
560+
561+ EditorGUILayout . EndScrollView ( ) ;
562+ }
563+
564+ private void Update ( )
565+ {
566+ // Handle the feedback message timer
567+ if ( copyFeedbackTimer > 0 )
568+ {
569+ copyFeedbackTimer -= Time . deltaTime ;
570+ if ( copyFeedbackTimer <= 0 )
571+ {
572+ pathCopied = false ;
573+ jsonCopied = false ;
574+ Repaint ( ) ;
575+ }
347576 }
348577 }
349578}
0 commit comments