@@ -701,6 +701,20 @@ private void DrawClientConfigurationCompact(McpClient mcpClient)
701701 }
702702 }
703703
704+ // Pre-check for clients that require uv (all except Claude Code)
705+ bool uvRequired = mcpClient . mcpType != McpTypes . ClaudeCode ;
706+ bool uvMissingEarly = false ;
707+ if ( uvRequired )
708+ {
709+ string uvPathEarly = FindUvPath ( ) ;
710+ if ( string . IsNullOrEmpty ( uvPathEarly ) )
711+ {
712+ uvMissingEarly = true ;
713+ mcpClient . configStatus = "uv Not Found" ;
714+ mcpClient . status = McpStatus . NotConfigured ;
715+ }
716+ }
717+
704718 // Status display
705719 EditorGUILayout . BeginHorizontal ( ) ;
706720 Rect statusRect = GUILayoutUtility . GetRect ( 0 , 28 , GUILayout . Width ( 24 ) ) ;
@@ -732,7 +746,46 @@ private void DrawClientConfigurationCompact(McpClient mcpClient)
732746 EditorGUILayout . EndHorizontal ( ) ;
733747 }
734748
735- EditorGUILayout . Space ( 10 ) ;
749+ EditorGUILayout . Space ( 10 ) ;
750+
751+ // If uv is missing for required clients, show hint and picker then exit early to avoid showing other controls
752+ if ( uvRequired && uvMissingEarly )
753+ {
754+ GUIStyle installHintStyle2 = new GUIStyle ( EditorStyles . label )
755+ {
756+ fontSize = 12 ,
757+ fontStyle = FontStyle . Bold ,
758+ wordWrap = false
759+ } ;
760+ installHintStyle2 . normal . textColor = new Color ( 1f , 0.5f , 0f ) ;
761+ EditorGUILayout . BeginHorizontal ( ) ;
762+ GUIContent installText2 = new GUIContent ( "Make sure uv is installed!" ) ;
763+ Vector2 sz = installHintStyle2 . CalcSize ( installText2 ) ;
764+ EditorGUILayout . LabelField ( installText2 , installHintStyle2 , GUILayout . Height ( 22 ) , GUILayout . Width ( sz . x + 2 ) , GUILayout . ExpandWidth ( false ) ) ;
765+ GUIStyle helpLinkStyle2 = new GUIStyle ( EditorStyles . linkLabel ) { fontStyle = FontStyle . Bold } ;
766+ GUILayout . Space ( 6 ) ;
767+ if ( GUILayout . Button ( "[CLICK]" , helpLinkStyle2 , GUILayout . Height ( 22 ) , GUILayout . ExpandWidth ( false ) ) )
768+ {
769+ Application . OpenURL ( "https://github.com/CoplayDev/unity-mcp/wiki/Troubleshooting-Unity-MCP-and-Cursor,-VSCode-&-Windsurf" ) ;
770+ }
771+ EditorGUILayout . EndHorizontal ( ) ;
772+
773+ EditorGUILayout . Space ( 8 ) ;
774+ EditorGUILayout . BeginHorizontal ( ) ;
775+ if ( GUILayout . Button ( "Choose UV Install Location" , GUILayout . Width ( 260 ) , GUILayout . Height ( 22 ) ) )
776+ {
777+ string suggested = RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ? "/opt/homebrew/bin" : Environment . GetFolderPath ( Environment . SpecialFolder . ProgramFiles ) ;
778+ string picked = EditorUtility . OpenFilePanel ( "Select 'uv' binary" , suggested , "" ) ;
779+ if ( ! string . IsNullOrEmpty ( picked ) )
780+ {
781+ EditorPrefs . SetString ( "UnityMCP.UvPath" , picked ) ;
782+ ConfigureMcpClient ( mcpClient ) ;
783+ Repaint ( ) ;
784+ }
785+ }
786+ EditorGUILayout . EndHorizontal ( ) ;
787+ return ;
788+ }
736789
737790 // Action buttons in horizontal layout
738791 EditorGUILayout . BeginHorizontal ( ) ;
@@ -850,7 +903,9 @@ private void DrawClientConfigurationCompact(McpClient mcpClient)
850903
851904 EditorGUILayout . Space ( 8 ) ;
852905 // Quick info (hide when Claude is not found to avoid confusion)
853- bool hideConfigInfo = ( mcpClient . mcpType == McpTypes . ClaudeCode ) && string . IsNullOrEmpty ( ExecPath . ResolveClaude ( ) ) ;
906+ bool hideConfigInfo =
907+ ( mcpClient . mcpType == McpTypes . ClaudeCode && string . IsNullOrEmpty ( ExecPath . ResolveClaude ( ) ) )
908+ || ( ( mcpClient . mcpType != McpTypes . ClaudeCode ) && string . IsNullOrEmpty ( FindUvPath ( ) ) ) ;
854909 if ( ! hideConfigInfo )
855910 {
856911 GUIStyle configInfoStyle = new GUIStyle ( EditorStyles . miniLabel )
@@ -889,6 +944,7 @@ private string WriteToConfig(string pythonDir, string configPath, McpClient mcpC
889944 {
890945 command = uvPath ,
891946 args = new [ ] { "--directory" , pythonDir , "run" , "server.py" } ,
947+ type = "stdio" ,
892948 } ;
893949
894950 JsonSerializerSettings jsonSettings = new ( ) { Formatting = Formatting . Indented } ;
@@ -908,29 +964,41 @@ private string WriteToConfig(string pythonDir, string configPath, McpClient mcpC
908964 }
909965
910966 // Parse the existing JSON while preserving all properties
911- dynamic existingConfig = JsonConvert . DeserializeObject ( existingJson ) ;
912- existingConfig ??= new Newtonsoft . Json . Linq . JObject ( ) ;
967+ dynamic existingConfig ;
968+ try
969+ {
970+ if ( string . IsNullOrWhiteSpace ( existingJson ) )
971+ {
972+ existingConfig = new Newtonsoft . Json . Linq . JObject ( ) ;
973+ }
974+ else
975+ {
976+ existingConfig = JsonConvert . DeserializeObject ( existingJson ) ?? new Newtonsoft . Json . Linq . JObject ( ) ;
977+ }
978+ }
979+ catch
980+ {
981+ // If user has partial/invalid JSON (e.g., mid-edit), start from a fresh object
982+ if ( ! string . IsNullOrWhiteSpace ( existingJson ) )
983+ {
984+ UnityEngine . Debug . LogWarning ( "UnityMCP: VSCode mcp.json could not be parsed; rewriting servers block." ) ;
985+ }
986+ existingConfig = new Newtonsoft . Json . Linq . JObject ( ) ;
987+ }
913988
914989 // Handle different client types with a switch statement
915990 //Comments: Interestingly, VSCode has mcp.servers.unityMCP while others have mcpServers.unityMCP, which is why we need to prevent this
916991 switch ( mcpClient ? . mcpType )
917992 {
918993 case McpTypes . VSCode :
919- // VSCode specific configuration
920- // Ensure mcp object exists
921- if ( existingConfig . mcp == null )
922- {
923- existingConfig . mcp = new Newtonsoft . Json . Linq . JObject ( ) ;
924- }
925-
926- // Ensure mcp.servers object exists
927- if ( existingConfig . mcp . servers == null )
994+ // VSCode-specific configuration (top-level "servers")
995+ if ( existingConfig . servers == null )
928996 {
929- existingConfig . mcp . servers = new Newtonsoft . Json . Linq . JObject ( ) ;
997+ existingConfig . servers = new Newtonsoft . Json . Linq . JObject ( ) ;
930998 }
931999
932- // Add/update UnityMCP server in VSCode settings
933- existingConfig . mcp . servers . unityMCP =
1000+ // Add/update UnityMCP server in VSCode mcp.json
1001+ existingConfig . servers . unityMCP =
9341002 JsonConvert . DeserializeObject < Newtonsoft . Json . Linq . JToken > (
9351003 JsonConvert . SerializeObject ( unityMCPConfig )
9361004 ) ;
@@ -986,15 +1054,13 @@ private void ShowManualInstructionsWindow(string configPath, McpClient mcpClient
9861054 // Create VSCode-specific configuration with proper format
9871055 var vscodeConfig = new
9881056 {
989- mcp = new
1057+ servers = new
9901058 {
991- servers = new
1059+ unityMCP = new
9921060 {
993- unityMCP = new
994- {
995- command = "uv" ,
996- args = new [ ] { "--directory" , pythonDir , "run" , "server.py" }
997- }
1061+ command = "uv" ,
1062+ args = new [ ] { "--directory" , pythonDir , "run" , "server.py" } ,
1063+ type = "stdio"
9981064 }
9991065 }
10001066 } ;
@@ -1303,9 +1369,15 @@ private void CheckMcpConfiguration(McpClient mcpClient)
13031369 case McpTypes . VSCode :
13041370 dynamic config = JsonConvert . DeserializeObject ( configJson ) ;
13051371
1306- if ( config ? . mcp ? . servers ? . unityMCP != null )
1372+ // New schema: top-level servers
1373+ if ( config ? . servers ? . unityMCP != null )
1374+ {
1375+ args = config . servers . unityMCP . args . ToObject < string [ ] > ( ) ;
1376+ configExists = true ;
1377+ }
1378+ // Back-compat: legacy mcp.servers
1379+ else if ( config ? . mcp ? . servers ? . unityMCP != null )
13071380 {
1308- // Extract args from VSCode config format
13091381 args = config . mcp . servers . unityMCP . args . ToObject < string [ ] > ( ) ;
13101382 configExists = true ;
13111383 }
0 commit comments