Skip to content

Commit 4f9017d

Browse files
committed
VSCode MCP: switch to mcp.json top-level servers schema; add type=stdio; robust parse/merge; Cursor/Windsurf UV gating UI; Claude Code UX polish and NVM detection
1 parent 5965158 commit 4f9017d

File tree

3 files changed

+103
-27
lines changed

3 files changed

+103
-27
lines changed

UnityMcpBridge/Editor/Data/McpClients.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,15 @@ public class McpClients
8787
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
8888
"Code",
8989
"User",
90-
"settings.json"
90+
"mcp.json"
9191
),
9292
linuxConfigPath = Path.Combine(
9393
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
9494
"Library",
9595
"Application Support",
9696
"Code",
9797
"User",
98-
"settings.json"
98+
"mcp.json"
9999
),
100100
mcpType = McpTypes.VSCode,
101101
configStatus = "Not Configured",

UnityMcpBridge/Editor/Models/MCPConfigServer.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@ public class McpConfigServer
1111

1212
[JsonProperty("args")]
1313
public string[] args;
14+
15+
// VSCode expects a transport type; default to stdio for compatibility
16+
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
17+
public string type = "stdio";
1418
}
1519
}

UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs

Lines changed: 97 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)