MCP: Fix macOS paths and VSCode manual setup
Normalize macOS to Application Support; use AppSupport symlink for Cursor args Map XDG (~/.local/share, ~/.config) to Application Support VSCode manual: show mcp.json path; use top-level servers JSON VSCode macOS path: ~/Library/Application Support/Code/User/mcp.jsonmain
parent
01ea2f46ac
commit
ad5c3112ca
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using MCPForUnity.Editor.Models;
|
using MCPForUnity.Editor.Models;
|
||||||
|
|
||||||
namespace MCPForUnity.Editor.Data
|
namespace MCPForUnity.Editor.Data
|
||||||
|
|
@ -82,13 +83,25 @@ namespace MCPForUnity.Editor.Data
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
name = "VSCode GitHub Copilot",
|
name = "VSCode GitHub Copilot",
|
||||||
|
// Windows path is canonical under %AppData%\Code\User
|
||||||
windowsConfigPath = Path.Combine(
|
windowsConfigPath = Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
"Code",
|
"Code",
|
||||||
"User",
|
"User",
|
||||||
"mcp.json"
|
"mcp.json"
|
||||||
),
|
),
|
||||||
linuxConfigPath = Path.Combine(
|
// For macOS, VSCode stores user config under ~/Library/Application Support/Code/User
|
||||||
|
// For Linux, it remains under ~/.config/Code/User
|
||||||
|
linuxConfigPath = RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
|
||||||
|
? Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||||
|
"Library",
|
||||||
|
"Application Support",
|
||||||
|
"Code",
|
||||||
|
"User",
|
||||||
|
"mcp.json"
|
||||||
|
)
|
||||||
|
: Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||||
".config",
|
".config",
|
||||||
"Code",
|
"Code",
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,41 @@ namespace MCPForUnity.Editor.Helpers
|
||||||
private static void PopulateUnityNode(JObject unity, string uvPath, string directory, McpClient client, bool isVSCode)
|
private static void PopulateUnityNode(JObject unity, string uvPath, string directory, McpClient client, bool isVSCode)
|
||||||
{
|
{
|
||||||
unity["command"] = uvPath;
|
unity["command"] = uvPath;
|
||||||
unity["args"] = JArray.FromObject(new[] { "run", "--directory", directory, "server.py" });
|
|
||||||
|
// For Cursor (non-VSCode) on macOS, prefer a no-spaces symlink path to avoid arg parsing issues in some runners
|
||||||
|
string effectiveDir = directory;
|
||||||
|
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
|
||||||
|
bool isCursor = !isVSCode && (client == null || client.mcpType != Models.McpTypes.VSCode);
|
||||||
|
if (isCursor && !string.IsNullOrEmpty(directory))
|
||||||
|
{
|
||||||
|
// Replace canonical path segment with the symlink path if present
|
||||||
|
const string canonical = "/Library/Application Support/";
|
||||||
|
const string symlinkSeg = "/Library/AppSupport/";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Normalize to full path style
|
||||||
|
if (directory.Contains(canonical))
|
||||||
|
{
|
||||||
|
effectiveDir = directory.Replace(canonical, symlinkSeg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If installer returned XDG-style on macOS, map to canonical symlink
|
||||||
|
string norm = directory.Replace('\\', '/');
|
||||||
|
int idx = norm.IndexOf("/.local/share/UnityMCP/", System.StringComparison.Ordinal);
|
||||||
|
if (idx >= 0)
|
||||||
|
{
|
||||||
|
string home = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal) ?? string.Empty;
|
||||||
|
string suffix = norm.Substring(idx + "/.local/share/".Length); // UnityMCP/...
|
||||||
|
effectiveDir = System.IO.Path.Combine(home, "Library", "AppSupport", suffix).Replace('\\', '/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* fallback to original directory on any error */ }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
unity["args"] = JArray.FromObject(new[] { "run", "--directory", effectiveDir, "server.py" });
|
||||||
|
|
||||||
if (isVSCode)
|
if (isVSCode)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ namespace MCPForUnity.Editor.Helpers
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string saveLocation = GetSaveLocation();
|
string saveLocation = GetSaveLocation();
|
||||||
|
TryCreateMacSymlinkForAppSupport();
|
||||||
string destRoot = Path.Combine(saveLocation, ServerFolder);
|
string destRoot = Path.Combine(saveLocation, ServerFolder);
|
||||||
string destSrc = Path.Combine(destRoot, "src");
|
string destSrc = Path.Combine(destRoot, "src");
|
||||||
|
|
||||||
|
|
@ -117,57 +118,79 @@ namespace MCPForUnity.Editor.Helpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static string GetSaveLocation()
|
private static string GetSaveLocation()
|
||||||
{
|
{
|
||||||
// Prefer Unity's platform first (more reliable under Mono/macOS), then fallback
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Application.platform == RuntimePlatform.OSXEditor)
|
|
||||||
{
|
|
||||||
string home = Environment.GetFolderPath(Environment.SpecialFolder.Personal) ?? string.Empty;
|
|
||||||
string appSupport = Path.Combine(home, "Library", "Application Support");
|
|
||||||
return Path.Combine(appSupport, RootFolder);
|
|
||||||
}
|
|
||||||
if (Application.platform == RuntimePlatform.WindowsEditor)
|
|
||||||
{
|
|
||||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)
|
|
||||||
?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty, "AppData", "Local");
|
|
||||||
return Path.Combine(localAppData, RootFolder);
|
|
||||||
}
|
|
||||||
if (Application.platform == RuntimePlatform.LinuxEditor)
|
|
||||||
{
|
|
||||||
var xdg = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
|
|
||||||
if (string.IsNullOrEmpty(xdg))
|
|
||||||
{
|
|
||||||
xdg = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty, ".local", "share");
|
|
||||||
}
|
|
||||||
return Path.Combine(xdg, RootFolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
// Fallback to RuntimeInformation
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
|
// Use per-user LocalApplicationData for canonical install location
|
||||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)
|
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)
|
||||||
?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty, "AppData", "Local");
|
?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty, "AppData", "Local");
|
||||||
return Path.Combine(localAppData, RootFolder);
|
return Path.Combine(localAppData, RootFolder);
|
||||||
}
|
}
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
{
|
{
|
||||||
var xdg = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
|
var xdg = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
|
||||||
if (string.IsNullOrEmpty(xdg))
|
if (string.IsNullOrEmpty(xdg))
|
||||||
{
|
{
|
||||||
xdg = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty, ".local", "share");
|
xdg = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty,
|
||||||
|
".local", "share");
|
||||||
}
|
}
|
||||||
return Path.Combine(xdg, RootFolder);
|
return Path.Combine(xdg, RootFolder);
|
||||||
}
|
}
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||||
{
|
{
|
||||||
string home = Environment.GetFolderPath(Environment.SpecialFolder.Personal) ?? string.Empty;
|
// On macOS, use LocalApplicationData (~/Library/Application Support)
|
||||||
return Path.Combine(home, "Library", "Application Support", RootFolder);
|
var localAppSupport = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||||
|
// Unity/Mono may map LocalApplicationData to ~/.local/share on macOS; normalize to Application Support
|
||||||
|
bool looksLikeXdg = !string.IsNullOrEmpty(localAppSupport) && localAppSupport.Replace('\\', '/').Contains("/.local/share");
|
||||||
|
if (string.IsNullOrEmpty(localAppSupport) || looksLikeXdg)
|
||||||
|
{
|
||||||
|
// Fallback: construct from $HOME
|
||||||
|
var home = Environment.GetFolderPath(Environment.SpecialFolder.Personal) ?? string.Empty;
|
||||||
|
localAppSupport = Path.Combine(home, "Library", "Application Support");
|
||||||
|
}
|
||||||
|
TryCreateMacSymlinkForAppSupport();
|
||||||
|
return Path.Combine(localAppSupport, RootFolder);
|
||||||
}
|
}
|
||||||
throw new Exception("Unsupported operating system.");
|
throw new Exception("Unsupported operating system.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// On macOS, create a no-spaces symlink ~/Library/AppSupport -> ~/Library/Application Support
|
||||||
|
/// to mitigate arg parsing and quoting issues in some MCP clients.
|
||||||
|
/// Safe to call repeatedly.
|
||||||
|
/// </summary>
|
||||||
|
private static void TryCreateMacSymlinkForAppSupport()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return;
|
||||||
|
string home = Environment.GetFolderPath(Environment.SpecialFolder.Personal) ?? string.Empty;
|
||||||
|
if (string.IsNullOrEmpty(home)) return;
|
||||||
|
|
||||||
|
string canonical = Path.Combine(home, "Library", "Application Support");
|
||||||
|
string symlink = Path.Combine(home, "Library", "AppSupport");
|
||||||
|
|
||||||
|
// If symlink exists already, nothing to do
|
||||||
|
if (Directory.Exists(symlink) || File.Exists(symlink)) return;
|
||||||
|
|
||||||
|
// Create symlink only if canonical exists
|
||||||
|
if (!Directory.Exists(canonical)) return;
|
||||||
|
|
||||||
|
// Use 'ln -s' to create a directory symlink (macOS)
|
||||||
|
var psi = new System.Diagnostics.ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "/bin/ln",
|
||||||
|
Arguments = $"-s \"{canonical}\" \"{symlink}\"",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
using var p = System.Diagnostics.Process.Start(psi);
|
||||||
|
p?.WaitForExit(2000);
|
||||||
|
}
|
||||||
|
catch { /* best-effort */ }
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsDirectoryWritable(string path)
|
private static bool IsDirectoryWritable(string path)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -302,11 +325,10 @@ namespace MCPForUnity.Editor.Helpers
|
||||||
if (string.IsNullOrEmpty(serverSrcPath)) return;
|
if (string.IsNullOrEmpty(serverSrcPath)) return;
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;
|
||||||
|
|
||||||
string safePath = EscapeForPgrep(serverSrcPath);
|
|
||||||
var psi = new System.Diagnostics.ProcessStartInfo
|
var psi = new System.Diagnostics.ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = "/usr/bin/pgrep",
|
FileName = "/usr/bin/pgrep",
|
||||||
Arguments = $"-f \"uv .*--directory {safePath}\"",
|
Arguments = $"-f \"uv .*--directory {serverSrcPath}\"",
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true,
|
RedirectStandardError = true,
|
||||||
|
|
@ -330,21 +352,6 @@ namespace MCPForUnity.Editor.Helpers
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape regex metacharacters so the path is treated literally by pgrep -f
|
|
||||||
private static string EscapeForPgrep(string path)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(path)) return path;
|
|
||||||
string s = path.Replace("\\", "\\\\");
|
|
||||||
char[] meta = new[] {'.','+','*','?','^','$','(',')','[',']','{','}','|'};
|
|
||||||
var sb = new StringBuilder(s.Length * 2);
|
|
||||||
foreach (char c in s)
|
|
||||||
{
|
|
||||||
if (Array.IndexOf(meta, c) >= 0) sb.Append('\\');
|
|
||||||
sb.Append(c);
|
|
||||||
}
|
|
||||||
return sb.ToString().Replace("\"", "\\\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ReadVersionFile(string path)
|
private static string ReadVersionFile(string path)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -961,10 +961,8 @@ namespace MCPForUnity.Editor.Windows
|
||||||
UnityEngine.Debug.LogError("UV package manager not found. Cannot configure VSCode.");
|
UnityEngine.Debug.LogError("UV package manager not found. Cannot configure VSCode.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// VSCode now reads from mcp.json with a top-level "servers" block
|
||||||
var vscodeConfig = new
|
var vscodeConfig = new
|
||||||
{
|
|
||||||
mcp = new
|
|
||||||
{
|
{
|
||||||
servers = new
|
servers = new
|
||||||
{
|
{
|
||||||
|
|
@ -974,7 +972,6 @@ namespace MCPForUnity.Editor.Windows
|
||||||
args = new[] { "run", "--directory", pythonDir, "server.py" }
|
args = new[] { "run", "--directory", pythonDir, "server.py" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
|
JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
|
||||||
string manualConfigJson = JsonConvert.SerializeObject(vscodeConfig, jsonSettings);
|
string manualConfigJson = JsonConvert.SerializeObject(vscodeConfig, jsonSettings);
|
||||||
|
|
@ -1157,6 +1154,24 @@ namespace MCPForUnity.Editor.Windows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// macOS normalization: map XDG-style ~/.local/share to canonical Application Support
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX)
|
||||||
|
&& !string.IsNullOrEmpty(serverSrc))
|
||||||
|
{
|
||||||
|
string norm = serverSrc.Replace('\\', '/');
|
||||||
|
int idx = norm.IndexOf("/.local/share/UnityMCP/", StringComparison.Ordinal);
|
||||||
|
if (idx >= 0)
|
||||||
|
{
|
||||||
|
string home = Environment.GetFolderPath(Environment.SpecialFolder.Personal) ?? string.Empty;
|
||||||
|
string suffix = norm.Substring(idx + "/.local/share/".Length); // UnityMCP/...
|
||||||
|
serverSrc = System.IO.Path.Combine(home, "Library", "Application Support", suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
// Hard-block PackageCache on Windows unless dev override is set
|
// Hard-block PackageCache on Windows unless dev override is set
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||||
&& !string.IsNullOrEmpty(serverSrc)
|
&& !string.IsNullOrEmpty(serverSrc)
|
||||||
|
|
|
||||||
|
|
@ -90,25 +90,21 @@ namespace MCPForUnity.Editor.Windows
|
||||||
EditorStyles.boldLabel
|
EditorStyles.boldLabel
|
||||||
);
|
);
|
||||||
EditorGUILayout.LabelField(
|
EditorGUILayout.LabelField(
|
||||||
"a) Open VSCode Settings (File > Preferences > Settings)",
|
"a) Open or create your VSCode MCP config file (mcp.json) at the path below",
|
||||||
instructionStyle
|
instructionStyle
|
||||||
);
|
);
|
||||||
EditorGUILayout.LabelField(
|
EditorGUILayout.LabelField(
|
||||||
"b) Click on the 'Open Settings (JSON)' button in the top right",
|
"b) Paste the JSON shown below into mcp.json",
|
||||||
instructionStyle
|
instructionStyle
|
||||||
);
|
);
|
||||||
EditorGUILayout.LabelField(
|
EditorGUILayout.LabelField(
|
||||||
"c) Add the MCP configuration shown below to your settings.json file",
|
"c) Save the file and restart VSCode",
|
||||||
instructionStyle
|
|
||||||
);
|
|
||||||
EditorGUILayout.LabelField(
|
|
||||||
"d) Save the file and restart VSCode",
|
|
||||||
instructionStyle
|
instructionStyle
|
||||||
);
|
);
|
||||||
EditorGUILayout.Space(5);
|
EditorGUILayout.Space(5);
|
||||||
|
|
||||||
EditorGUILayout.LabelField(
|
EditorGUILayout.LabelField(
|
||||||
"3. VSCode settings.json location:",
|
"3. VSCode mcp.json location:",
|
||||||
EditorStyles.boldLabel
|
EditorStyles.boldLabel
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -121,7 +117,7 @@ namespace MCPForUnity.Editor.Windows
|
||||||
System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData),
|
System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData),
|
||||||
"Code",
|
"Code",
|
||||||
"User",
|
"User",
|
||||||
"settings.json"
|
"mcp.json"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -132,7 +128,7 @@ namespace MCPForUnity.Editor.Windows
|
||||||
"Application Support",
|
"Application Support",
|
||||||
"Code",
|
"Code",
|
||||||
"User",
|
"User",
|
||||||
"settings.json"
|
"mcp.json"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,7 +201,7 @@ namespace MCPForUnity.Editor.Windows
|
||||||
EditorGUILayout.Space(10);
|
EditorGUILayout.Space(10);
|
||||||
|
|
||||||
EditorGUILayout.LabelField(
|
EditorGUILayout.LabelField(
|
||||||
"4. Add this configuration to your settings.json:",
|
"4. Add this configuration to your mcp.json:",
|
||||||
EditorStyles.boldLabel
|
EditorStyles.boldLabel
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue