Skip to content

Commit c7d4218

Browse files
authored
Merge pull request #239 from dsarno/fix/installer-cleanup-v2
feat: installer cleanup, auto-migration of legacy server location to canonical, server logging normalization
2 parents 22fe9df + a7d7bcd commit c7d4218

14 files changed

+712
-180
lines changed

UnityMcpBridge/Editor/Data/McpClients.cs

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4+
using System.Runtime.InteropServices;
45
using MCPForUnity.Editor.Models;
56

67
namespace MCPForUnity.Editor.Data
@@ -69,32 +70,54 @@ public class McpClients
6970
"Claude",
7071
"claude_desktop_config.json"
7172
),
72-
linuxConfigPath = Path.Combine(
73-
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
74-
".config",
75-
"Claude",
76-
"claude_desktop_config.json"
77-
),
73+
// For macOS, Claude Desktop stores config under ~/Library/Application Support/Claude
74+
// For Linux, it remains under ~/.config/Claude
75+
linuxConfigPath = RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
76+
? Path.Combine(
77+
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
78+
"Library",
79+
"Application Support",
80+
"Claude",
81+
"claude_desktop_config.json"
82+
)
83+
: Path.Combine(
84+
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
85+
".config",
86+
"Claude",
87+
"claude_desktop_config.json"
88+
),
7889
mcpType = McpTypes.ClaudeDesktop,
7990
configStatus = "Not Configured",
8091
},
8192
// 5) VSCode GitHub Copilot
8293
new()
8394
{
8495
name = "VSCode GitHub Copilot",
96+
// Windows path is canonical under %AppData%\Code\User
8597
windowsConfigPath = Path.Combine(
8698
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
8799
"Code",
88100
"User",
89101
"mcp.json"
90102
),
91-
linuxConfigPath = Path.Combine(
92-
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
93-
".config",
94-
"Code",
95-
"User",
96-
"mcp.json"
97-
),
103+
// For macOS, VSCode stores user config under ~/Library/Application Support/Code/User
104+
// For Linux, it remains under ~/.config/Code/User
105+
linuxConfigPath = RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
106+
? Path.Combine(
107+
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
108+
"Library",
109+
"Application Support",
110+
"Code",
111+
"User",
112+
"mcp.json"
113+
)
114+
: Path.Combine(
115+
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
116+
".config",
117+
"Code",
118+
"User",
119+
"mcp.json"
120+
),
98121
mcpType = McpTypes.VSCode,
99122
configStatus = "Not Configured",
100123
},

UnityMcpBridge/Editor/Helpers/ConfigJsonBuilder.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,41 @@ public static JObject ApplyUnityServerToExistingConfig(JObject root, string uvPa
5050
private static void PopulateUnityNode(JObject unity, string uvPath, string directory, McpClient client, bool isVSCode)
5151
{
5252
unity["command"] = uvPath;
53-
unity["args"] = JArray.FromObject(new[] { "run", "--directory", directory, "server.py" });
53+
54+
// For Cursor (non-VSCode) on macOS, prefer a no-spaces symlink path to avoid arg parsing issues in some runners
55+
string effectiveDir = directory;
56+
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
57+
bool isCursor = !isVSCode && (client == null || client.mcpType != Models.McpTypes.VSCode);
58+
if (isCursor && !string.IsNullOrEmpty(directory))
59+
{
60+
// Replace canonical path segment with the symlink path if present
61+
const string canonical = "/Library/Application Support/";
62+
const string symlinkSeg = "/Library/AppSupport/";
63+
try
64+
{
65+
// Normalize to full path style
66+
if (directory.Contains(canonical))
67+
{
68+
effectiveDir = directory.Replace(canonical, symlinkSeg);
69+
}
70+
else
71+
{
72+
// If installer returned XDG-style on macOS, map to canonical symlink
73+
string norm = directory.Replace('\\', '/');
74+
int idx = norm.IndexOf("/.local/share/UnityMCP/", System.StringComparison.Ordinal);
75+
if (idx >= 0)
76+
{
77+
string home = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal) ?? string.Empty;
78+
string suffix = norm.Substring(idx + "/.local/share/".Length); // UnityMCP/...
79+
effectiveDir = System.IO.Path.Combine(home, "Library", "AppSupport", suffix).Replace('\\', '/');
80+
}
81+
}
82+
}
83+
catch { /* fallback to original directory on any error */ }
84+
}
85+
#endif
86+
87+
unity["args"] = JArray.FromObject(new[] { "run", "--directory", effectiveDir, "server.py" });
5488

5589
if (isVSCode)
5690
{
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using UnityEditor;
2+
using UnityEngine;
3+
4+
namespace MCPForUnity.Editor.Helpers
5+
{
6+
internal static class McpLog
7+
{
8+
private const string Prefix = "<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>:";
9+
10+
private static bool IsDebugEnabled()
11+
{
12+
try { return EditorPrefs.GetBool("MCPForUnity.DebugLogs", false); } catch { return false; }
13+
}
14+
15+
public static void Info(string message, bool always = true)
16+
{
17+
if (!always && !IsDebugEnabled()) return;
18+
Debug.Log($"{Prefix} {message}");
19+
}
20+
21+
public static void Warn(string message)
22+
{
23+
Debug.LogWarning($"<color=#cc7a00>{Prefix} {message}</color>");
24+
}
25+
26+
public static void Error(string message)
27+
{
28+
Debug.LogError($"<color=#cc3333>{Prefix} {message}</color>");
29+
}
30+
}
31+
}
32+
33+

UnityMcpBridge/Editor/Helpers/McpLog.cs.meta

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using UnityEditor;
2+
using UnityEngine;
3+
4+
namespace MCPForUnity.Editor.Helpers
5+
{
6+
/// <summary>
7+
/// Auto-runs legacy/older install detection on package load/update (log-only).
8+
/// Runs once per embedded server version using an EditorPrefs version-scoped key.
9+
/// </summary>
10+
[InitializeOnLoad]
11+
public static class PackageDetector
12+
{
13+
private const string DetectOnceFlagKeyPrefix = "MCPForUnity.LegacyDetectLogged:";
14+
15+
static PackageDetector()
16+
{
17+
try
18+
{
19+
string pkgVer = ReadPackageVersionOrFallback();
20+
string key = DetectOnceFlagKeyPrefix + pkgVer;
21+
22+
// Always force-run if legacy roots exist or canonical install is missing
23+
bool legacyPresent = LegacyRootsExist();
24+
bool canonicalMissing = !System.IO.File.Exists(System.IO.Path.Combine(ServerInstaller.GetServerPath(), "server.py"));
25+
26+
if (!EditorPrefs.GetBool(key, false) || legacyPresent || canonicalMissing)
27+
{
28+
EditorApplication.delayCall += () =>
29+
{
30+
try
31+
{
32+
ServerInstaller.EnsureServerInstalled();
33+
}
34+
catch (System.Exception ex)
35+
{
36+
Debug.LogWarning("MCP for Unity: Auto-detect on load failed: " + ex.Message);
37+
}
38+
finally
39+
{
40+
EditorPrefs.SetBool(key, true);
41+
}
42+
};
43+
}
44+
}
45+
catch { /* ignore */ }
46+
}
47+
48+
private static string ReadEmbeddedVersionOrFallback()
49+
{
50+
try
51+
{
52+
if (ServerPathResolver.TryFindEmbeddedServerSource(out var embeddedSrc))
53+
{
54+
var p = System.IO.Path.Combine(embeddedSrc, "server_version.txt");
55+
if (System.IO.File.Exists(p))
56+
return (System.IO.File.ReadAllText(p)?.Trim() ?? "unknown");
57+
}
58+
}
59+
catch { }
60+
return "unknown";
61+
}
62+
63+
private static string ReadPackageVersionOrFallback()
64+
{
65+
try
66+
{
67+
var info = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(PackageDetector).Assembly);
68+
if (info != null && !string.IsNullOrEmpty(info.version)) return info.version;
69+
}
70+
catch { }
71+
// Fallback to embedded server version if package info unavailable
72+
return ReadEmbeddedVersionOrFallback();
73+
}
74+
75+
private static bool LegacyRootsExist()
76+
{
77+
try
78+
{
79+
string home = System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile) ?? string.Empty;
80+
string[] roots =
81+
{
82+
System.IO.Path.Combine(home, ".config", "UnityMCP", "UnityMcpServer", "src"),
83+
System.IO.Path.Combine(home, ".local", "share", "UnityMCP", "UnityMcpServer", "src")
84+
};
85+
foreach (var r in roots)
86+
{
87+
try { if (System.IO.File.Exists(System.IO.Path.Combine(r, "server.py"))) return true; } catch { }
88+
}
89+
}
90+
catch { }
91+
return false;
92+
}
93+
}
94+
}
95+
96+

UnityMcpBridge/Editor/Helpers/PackageDetector.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)