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
parent
5965158533
commit
4f9017d676
|
|
@ -87,7 +87,7 @@ namespace UnityMcpBridge.Editor.Data
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
"Code",
|
"Code",
|
||||||
"User",
|
"User",
|
||||||
"settings.json"
|
"mcp.json"
|
||||||
),
|
),
|
||||||
linuxConfigPath = Path.Combine(
|
linuxConfigPath = Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||||
|
|
@ -95,7 +95,7 @@ namespace UnityMcpBridge.Editor.Data
|
||||||
"Application Support",
|
"Application Support",
|
||||||
"Code",
|
"Code",
|
||||||
"User",
|
"User",
|
||||||
"settings.json"
|
"mcp.json"
|
||||||
),
|
),
|
||||||
mcpType = McpTypes.VSCode,
|
mcpType = McpTypes.VSCode,
|
||||||
configStatus = "Not Configured",
|
configStatus = "Not Configured",
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,9 @@ namespace UnityMcpBridge.Editor.Models
|
||||||
|
|
||||||
[JsonProperty("args")]
|
[JsonProperty("args")]
|
||||||
public string[] args;
|
public string[] args;
|
||||||
|
|
||||||
|
// VSCode expects a transport type; default to stdio for compatibility
|
||||||
|
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
|
public string type = "stdio";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -701,6 +701,20 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-check for clients that require uv (all except Claude Code)
|
||||||
|
bool uvRequired = mcpClient.mcpType != McpTypes.ClaudeCode;
|
||||||
|
bool uvMissingEarly = false;
|
||||||
|
if (uvRequired)
|
||||||
|
{
|
||||||
|
string uvPathEarly = FindUvPath();
|
||||||
|
if (string.IsNullOrEmpty(uvPathEarly))
|
||||||
|
{
|
||||||
|
uvMissingEarly = true;
|
||||||
|
mcpClient.configStatus = "uv Not Found";
|
||||||
|
mcpClient.status = McpStatus.NotConfigured;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Status display
|
// Status display
|
||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
Rect statusRect = GUILayoutUtility.GetRect(0, 28, GUILayout.Width(24));
|
Rect statusRect = GUILayoutUtility.GetRect(0, 28, GUILayout.Width(24));
|
||||||
|
|
@ -734,6 +748,45 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
|
|
||||||
EditorGUILayout.Space(10);
|
EditorGUILayout.Space(10);
|
||||||
|
|
||||||
|
// If uv is missing for required clients, show hint and picker then exit early to avoid showing other controls
|
||||||
|
if (uvRequired && uvMissingEarly)
|
||||||
|
{
|
||||||
|
GUIStyle installHintStyle2 = new GUIStyle(EditorStyles.label)
|
||||||
|
{
|
||||||
|
fontSize = 12,
|
||||||
|
fontStyle = FontStyle.Bold,
|
||||||
|
wordWrap = false
|
||||||
|
};
|
||||||
|
installHintStyle2.normal.textColor = new Color(1f, 0.5f, 0f);
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
GUIContent installText2 = new GUIContent("Make sure uv is installed!");
|
||||||
|
Vector2 sz = installHintStyle2.CalcSize(installText2);
|
||||||
|
EditorGUILayout.LabelField(installText2, installHintStyle2, GUILayout.Height(22), GUILayout.Width(sz.x + 2), GUILayout.ExpandWidth(false));
|
||||||
|
GUIStyle helpLinkStyle2 = new GUIStyle(EditorStyles.linkLabel) { fontStyle = FontStyle.Bold };
|
||||||
|
GUILayout.Space(6);
|
||||||
|
if (GUILayout.Button("[CLICK]", helpLinkStyle2, GUILayout.Height(22), GUILayout.ExpandWidth(false)))
|
||||||
|
{
|
||||||
|
Application.OpenURL("https://github.com/CoplayDev/unity-mcp/wiki/Troubleshooting-Unity-MCP-and-Cursor,-VSCode-&-Windsurf");
|
||||||
|
}
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
EditorGUILayout.Space(8);
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
if (GUILayout.Button("Choose UV Install Location", GUILayout.Width(260), GUILayout.Height(22)))
|
||||||
|
{
|
||||||
|
string suggested = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "/opt/homebrew/bin" : Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
||||||
|
string picked = EditorUtility.OpenFilePanel("Select 'uv' binary", suggested, "");
|
||||||
|
if (!string.IsNullOrEmpty(picked))
|
||||||
|
{
|
||||||
|
EditorPrefs.SetString("UnityMCP.UvPath", picked);
|
||||||
|
ConfigureMcpClient(mcpClient);
|
||||||
|
Repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Action buttons in horizontal layout
|
// Action buttons in horizontal layout
|
||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
|
||||||
|
|
@ -850,7 +903,9 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
|
|
||||||
EditorGUILayout.Space(8);
|
EditorGUILayout.Space(8);
|
||||||
// Quick info (hide when Claude is not found to avoid confusion)
|
// Quick info (hide when Claude is not found to avoid confusion)
|
||||||
bool hideConfigInfo = (mcpClient.mcpType == McpTypes.ClaudeCode) && string.IsNullOrEmpty(ExecPath.ResolveClaude());
|
bool hideConfigInfo =
|
||||||
|
(mcpClient.mcpType == McpTypes.ClaudeCode && string.IsNullOrEmpty(ExecPath.ResolveClaude()))
|
||||||
|
|| ((mcpClient.mcpType != McpTypes.ClaudeCode) && string.IsNullOrEmpty(FindUvPath()));
|
||||||
if (!hideConfigInfo)
|
if (!hideConfigInfo)
|
||||||
{
|
{
|
||||||
GUIStyle configInfoStyle = new GUIStyle(EditorStyles.miniLabel)
|
GUIStyle configInfoStyle = new GUIStyle(EditorStyles.miniLabel)
|
||||||
|
|
@ -889,6 +944,7 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
{
|
{
|
||||||
command = uvPath,
|
command = uvPath,
|
||||||
args = new[] { "--directory", pythonDir, "run", "server.py" },
|
args = new[] { "--directory", pythonDir, "run", "server.py" },
|
||||||
|
type = "stdio",
|
||||||
};
|
};
|
||||||
|
|
||||||
JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
|
JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
|
||||||
|
|
@ -908,29 +964,41 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the existing JSON while preserving all properties
|
// Parse the existing JSON while preserving all properties
|
||||||
dynamic existingConfig = JsonConvert.DeserializeObject(existingJson);
|
dynamic existingConfig;
|
||||||
existingConfig ??= new Newtonsoft.Json.Linq.JObject();
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(existingJson))
|
||||||
|
{
|
||||||
|
existingConfig = new Newtonsoft.Json.Linq.JObject();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existingConfig = JsonConvert.DeserializeObject(existingJson) ?? new Newtonsoft.Json.Linq.JObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If user has partial/invalid JSON (e.g., mid-edit), start from a fresh object
|
||||||
|
if (!string.IsNullOrWhiteSpace(existingJson))
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogWarning("UnityMCP: VSCode mcp.json could not be parsed; rewriting servers block.");
|
||||||
|
}
|
||||||
|
existingConfig = new Newtonsoft.Json.Linq.JObject();
|
||||||
|
}
|
||||||
|
|
||||||
// Handle different client types with a switch statement
|
// Handle different client types with a switch statement
|
||||||
//Comments: Interestingly, VSCode has mcp.servers.unityMCP while others have mcpServers.unityMCP, which is why we need to prevent this
|
//Comments: Interestingly, VSCode has mcp.servers.unityMCP while others have mcpServers.unityMCP, which is why we need to prevent this
|
||||||
switch (mcpClient?.mcpType)
|
switch (mcpClient?.mcpType)
|
||||||
{
|
{
|
||||||
case McpTypes.VSCode:
|
case McpTypes.VSCode:
|
||||||
// VSCode specific configuration
|
// VSCode-specific configuration (top-level "servers")
|
||||||
// Ensure mcp object exists
|
if (existingConfig.servers == null)
|
||||||
if (existingConfig.mcp == null)
|
|
||||||
{
|
{
|
||||||
existingConfig.mcp = new Newtonsoft.Json.Linq.JObject();
|
existingConfig.servers = new Newtonsoft.Json.Linq.JObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure mcp.servers object exists
|
// Add/update UnityMCP server in VSCode mcp.json
|
||||||
if (existingConfig.mcp.servers == null)
|
existingConfig.servers.unityMCP =
|
||||||
{
|
|
||||||
existingConfig.mcp.servers = new Newtonsoft.Json.Linq.JObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add/update UnityMCP server in VSCode settings
|
|
||||||
existingConfig.mcp.servers.unityMCP =
|
|
||||||
JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JToken>(
|
JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JToken>(
|
||||||
JsonConvert.SerializeObject(unityMCPConfig)
|
JsonConvert.SerializeObject(unityMCPConfig)
|
||||||
);
|
);
|
||||||
|
|
@ -985,16 +1053,14 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
case McpTypes.VSCode:
|
case McpTypes.VSCode:
|
||||||
// Create VSCode-specific configuration with proper format
|
// Create VSCode-specific configuration with proper format
|
||||||
var vscodeConfig = new
|
var vscodeConfig = new
|
||||||
{
|
|
||||||
mcp = new
|
|
||||||
{
|
{
|
||||||
servers = new
|
servers = new
|
||||||
{
|
{
|
||||||
unityMCP = new
|
unityMCP = new
|
||||||
{
|
{
|
||||||
command = "uv",
|
command = "uv",
|
||||||
args = new[] { "--directory", pythonDir, "run", "server.py" }
|
args = new[] { "--directory", pythonDir, "run", "server.py" },
|
||||||
}
|
type = "stdio"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1303,9 +1369,15 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
case McpTypes.VSCode:
|
case McpTypes.VSCode:
|
||||||
dynamic config = JsonConvert.DeserializeObject(configJson);
|
dynamic config = JsonConvert.DeserializeObject(configJson);
|
||||||
|
|
||||||
if (config?.mcp?.servers?.unityMCP != null)
|
// New schema: top-level servers
|
||||||
|
if (config?.servers?.unityMCP != null)
|
||||||
|
{
|
||||||
|
args = config.servers.unityMCP.args.ToObject<string[]>();
|
||||||
|
configExists = true;
|
||||||
|
}
|
||||||
|
// Back-compat: legacy mcp.servers
|
||||||
|
else if (config?.mcp?.servers?.unityMCP != null)
|
||||||
{
|
{
|
||||||
// Extract args from VSCode config format
|
|
||||||
args = config.mcp.servers.unityMCP.args.ToObject<string[]>();
|
args = config.mcp.servers.unityMCP.args.ToObject<string[]>();
|
||||||
configExists = true;
|
configExists = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue