Skip to content

Commit 2d00ed2

Browse files
crowdedfireclaude
andcommitted
fix: resolve Claude registrations for git-worktree Unity projects
`claude mcp add --scope local` keys the registration in ~/.claude.json by the git MAIN repo root. When the Unity project is a linked worktree (e.g. C:/Dev/stay-booping -> repo C:/Dev/stay) that key is a sibling path, so ReadUserScopeConfig's ancestor walk never finds it and CheckStatus reports NotConfigured while the CLI itself resolves the worktree fine (`claude mcp list` shows Connected). Parse the main root from the worktree's .git pointer file and retry the walk from there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent a5a99ee commit 2d00ed2

1 file changed

Lines changed: 70 additions & 21 deletions

File tree

MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,39 +1393,88 @@ private static (JObject serverConfig, string error) ReadUserScopeConfig(string p
13931393
// Walk up the directory tree to find a matching project config
13941394
// Claude Code may be configured at a parent directory (e.g., repo root)
13951395
// while Unity project is in a subdirectory (e.g., TestProjects/UnityMCPTests)
1396-
string currentDir = NormalizePath(projectDir);
1397-
while (!string.IsNullOrEmpty(currentDir))
1396+
var fromProjectDir = FindUnityServerFromWalk(normalizedProjects, NormalizePath(projectDir));
1397+
if (fromProjectDir != null)
1398+
return (fromProjectDir, null);
1399+
1400+
// `claude mcp add --scope local` keys the registration by the
1401+
// git MAIN repo root. When the Unity project lives in a linked
1402+
// worktree, that root is a sibling path the ancestor walk above
1403+
// can never reach — retry from the parsed main root.
1404+
string mainRoot = GetGitMainRepoRoot(projectDir);
1405+
if (!string.IsNullOrEmpty(mainRoot)
1406+
&& !string.Equals(mainRoot, NormalizePath(projectDir), StringComparison.OrdinalIgnoreCase))
13981407
{
1399-
if (normalizedProjects.TryGetValue(currentDir, out var projectConfig))
1408+
var fromMainRoot = FindUnityServerFromWalk(normalizedProjects, mainRoot);
1409+
if (fromMainRoot != null)
1410+
return (fromMainRoot, null);
1411+
}
1412+
1413+
return (null, null);
1414+
}
1415+
catch (Exception ex)
1416+
{
1417+
return (null, $"Error reading user Claude config: {ex.Message}");
1418+
}
1419+
}
1420+
1421+
/// <summary>
1422+
/// Walks up from startDir (normalized) looking for a project entry with
1423+
/// a UnityMCP registration. Stops at the first project entry found even
1424+
/// if it lacks UnityMCP (a configured project boundary).
1425+
/// </summary>
1426+
private static JObject FindUnityServerFromWalk(Dictionary<string, JObject> normalizedProjects, string startDir)
1427+
{
1428+
string currentDir = startDir;
1429+
while (!string.IsNullOrEmpty(currentDir))
1430+
{
1431+
if (normalizedProjects.TryGetValue(currentDir, out var projectConfig))
1432+
{
1433+
var mcpServers = projectConfig?["mcpServers"] as JObject;
1434+
if (mcpServers != null)
14001435
{
1401-
var mcpServers = projectConfig?["mcpServers"] as JObject;
1402-
if (mcpServers != null)
1436+
foreach (var server in mcpServers.Properties())
14031437
{
1404-
foreach (var server in mcpServers.Properties())
1438+
if (string.Equals(server.Name, "UnityMCP", StringComparison.OrdinalIgnoreCase))
14051439
{
1406-
if (string.Equals(server.Name, "UnityMCP", StringComparison.OrdinalIgnoreCase))
1407-
{
1408-
return (server.Value as JObject, null);
1409-
}
1440+
return server.Value as JObject;
14101441
}
14111442
}
1412-
// Found the project but no UnityMCP - don't continue walking up
1413-
return (null, null);
14141443
}
1415-
1416-
// Move up one directory
1417-
int lastSlash = currentDir.LastIndexOf('/');
1418-
if (lastSlash <= 0)
1419-
break;
1420-
currentDir = currentDir.Substring(0, lastSlash);
1444+
// Found the project but no UnityMCP - don't continue walking up
1445+
return null;
14211446
}
14221447

1423-
return (null, null);
1448+
// Move up one directory
1449+
int lastSlash = currentDir.LastIndexOf('/');
1450+
if (lastSlash <= 0)
1451+
break;
1452+
currentDir = currentDir.Substring(0, lastSlash);
14241453
}
1425-
catch (Exception ex)
1454+
return null;
1455+
}
1456+
1457+
/// <summary>
1458+
/// For a linked git worktree, the main repo root parsed from the .git
1459+
/// pointer file ("gitdir: &lt;root&gt;/.git/worktrees/&lt;name&gt;"),
1460+
/// normalized. Null for a regular checkout (.git directory) or
1461+
/// anything unparseable.
1462+
/// </summary>
1463+
private static string GetGitMainRepoRoot(string dir)
1464+
{
1465+
try
14261466
{
1427-
return (null, $"Error reading user Claude config: {ex.Message}");
1467+
string gitPath = Path.Combine(dir, ".git");
1468+
if (!File.Exists(gitPath)) return null;
1469+
string line = File.ReadAllText(gitPath).Trim();
1470+
if (!line.StartsWith("gitdir:", StringComparison.OrdinalIgnoreCase)) return null;
1471+
string gitDir = line.Substring("gitdir:".Length).Trim();
1472+
if (!Path.IsPathRooted(gitDir)) gitDir = Path.Combine(dir, gitDir);
1473+
gitDir = NormalizePath(Path.GetFullPath(gitDir));
1474+
int i = gitDir.LastIndexOf("/.git/", StringComparison.OrdinalIgnoreCase);
1475+
return i > 0 ? gitDir.Substring(0, i) : null;
14281476
}
1477+
catch { return null; }
14291478
}
14301479

14311480
/// <summary>

0 commit comments

Comments
 (0)