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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using MCPForUnity.Editor.Models;
|
||||
|
||||
namespace MCPForUnity.Editor.Data
|
||||
|
|
@ -82,19 +83,31 @@ namespace MCPForUnity.Editor.Data
|
|||
new()
|
||||
{
|
||||
name = "VSCode GitHub Copilot",
|
||||
// Windows path is canonical under %AppData%\Code\User
|
||||
windowsConfigPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"Code",
|
||||
"User",
|
||||
"mcp.json"
|
||||
),
|
||||
linuxConfigPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
".config",
|
||||
"Code",
|
||||
"User",
|
||||
"mcp.json"
|
||||
),
|
||||
// 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),
|
||||
".config",
|
||||
"Code",
|
||||
"User",
|
||||
"mcp.json"
|
||||
),
|
||||
mcpType = McpTypes.VSCode,
|
||||
configStatus = "Not Configured",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -50,7 +50,41 @@ namespace MCPForUnity.Editor.Helpers
|
|||
private static void PopulateUnityNode(JObject unity, string uvPath, string directory, McpClient client, bool isVSCode)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ namespace MCPForUnity.Editor.Helpers
|
|||
try
|
||||
{
|
||||
string saveLocation = GetSaveLocation();
|
||||
TryCreateMacSymlinkForAppSupport();
|
||||
string destRoot = Path.Combine(saveLocation, ServerFolder);
|
||||
string destSrc = Path.Combine(destRoot, "src");
|
||||
|
||||
|
|
@ -117,57 +118,79 @@ namespace MCPForUnity.Editor.Helpers
|
|||
/// </summary>
|
||||
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))
|
||||
{
|
||||
// Use per-user LocalApplicationData for canonical install location
|
||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)
|
||||
?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty, "AppData", "Local");
|
||||
return Path.Combine(localAppData, RootFolder);
|
||||
}
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
var xdg = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
|
||||
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);
|
||||
}
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
string home = Environment.GetFolderPath(Environment.SpecialFolder.Personal) ?? string.Empty;
|
||||
return Path.Combine(home, "Library", "Application Support", RootFolder);
|
||||
// On macOS, use LocalApplicationData (~/Library/Application Support)
|
||||
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.");
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
try
|
||||
|
|
@ -302,11 +325,10 @@ namespace MCPForUnity.Editor.Helpers
|
|||
if (string.IsNullOrEmpty(serverSrcPath)) return;
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;
|
||||
|
||||
string safePath = EscapeForPgrep(serverSrcPath);
|
||||
var psi = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "/usr/bin/pgrep",
|
||||
Arguments = $"-f \"uv .*--directory {safePath}\"",
|
||||
Arguments = $"-f \"uv .*--directory {serverSrcPath}\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
|
|
@ -330,21 +352,6 @@ namespace MCPForUnity.Editor.Helpers
|
|||
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)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -961,18 +961,15 @@ namespace MCPForUnity.Editor.Windows
|
|||
UnityEngine.Debug.LogError("UV package manager not found. Cannot configure VSCode.");
|
||||
return;
|
||||
}
|
||||
|
||||
// VSCode now reads from mcp.json with a top-level "servers" block
|
||||
var vscodeConfig = new
|
||||
{
|
||||
mcp = new
|
||||
servers = new
|
||||
{
|
||||
servers = new
|
||||
unityMCP = new
|
||||
{
|
||||
unityMCP = new
|
||||
{
|
||||
command = uvPath,
|
||||
args = new[] { "run", "--directory", pythonDir, "server.py" }
|
||||
}
|
||||
command = uvPath,
|
||||
args = new[] { "run", "--directory", pythonDir, "server.py" }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -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
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
&& !string.IsNullOrEmpty(serverSrc)
|
||||
|
|
|
|||
|
|
@ -90,25 +90,21 @@ namespace MCPForUnity.Editor.Windows
|
|||
EditorStyles.boldLabel
|
||||
);
|
||||
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
|
||||
);
|
||||
EditorGUILayout.LabelField(
|
||||
"b) Click on the 'Open Settings (JSON)' button in the top right",
|
||||
"b) Paste the JSON shown below into mcp.json",
|
||||
instructionStyle
|
||||
);
|
||||
EditorGUILayout.LabelField(
|
||||
"c) Add the MCP configuration shown below to your settings.json file",
|
||||
instructionStyle
|
||||
);
|
||||
EditorGUILayout.LabelField(
|
||||
"d) Save the file and restart VSCode",
|
||||
"c) Save the file and restart VSCode",
|
||||
instructionStyle
|
||||
);
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
EditorGUILayout.LabelField(
|
||||
"3. VSCode settings.json location:",
|
||||
"3. VSCode mcp.json location:",
|
||||
EditorStyles.boldLabel
|
||||
);
|
||||
|
||||
|
|
@ -121,7 +117,7 @@ namespace MCPForUnity.Editor.Windows
|
|||
System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData),
|
||||
"Code",
|
||||
"User",
|
||||
"settings.json"
|
||||
"mcp.json"
|
||||
);
|
||||
}
|
||||
else
|
||||
|
|
@ -132,7 +128,7 @@ namespace MCPForUnity.Editor.Windows
|
|||
"Application Support",
|
||||
"Code",
|
||||
"User",
|
||||
"settings.json"
|
||||
"mcp.json"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -205,7 +201,7 @@ namespace MCPForUnity.Editor.Windows
|
|||
EditorGUILayout.Space(10);
|
||||
|
||||
EditorGUILayout.LabelField(
|
||||
"4. Add this configuration to your settings.json:",
|
||||
"4. Add this configuration to your mcp.json:",
|
||||
EditorStyles.boldLabel
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue