@@ -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: <root>/.git/worktrees/<name>"),
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