Skip to content

Commit 68f09e1

Browse files
committed
Follow-up: address CodeRabbit feedback for #308 – centralize GetToolsFolderIdentifier, fix tools copy dir, and limit scan scope
1 parent 48d912e commit 68f09e1

File tree

2 files changed

+98
-5
lines changed

2 files changed

+98
-5
lines changed

MCPForUnity/Editor/Helpers/ServerInstaller.cs

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ private static bool TryGetEmbeddedServerSource(out string srcPath)
406406
/// <summary>
407407
/// Searches Unity project for MCPForUnityTools folders and copies .py files to server tools directory.
408408
/// Only copies if the tool's version.txt has changed (or doesn't exist).
409+
/// Files are copied into per-folder subdirectories to avoid conflicts.
409410
/// </summary>
410411
private static void CopyUnityProjectTools(string destToolsDir)
411412
{
@@ -418,18 +419,51 @@ private static void CopyUnityProjectTools(string destToolsDir)
418419
return;
419420
}
420421

421-
// Find all MCPForUnityTools folders
422-
var toolsFolders = Directory.GetDirectories(projectRoot, "MCPForUnityTools", SearchOption.AllDirectories);
422+
// Ensure destToolsDir exists
423+
Directory.CreateDirectory(destToolsDir);
424+
425+
// Limit scan to specific directories to avoid deep recursion
426+
var searchRoots = new List<string>();
427+
var assetsPath = Path.Combine(projectRoot, "Assets");
428+
var packagesPath = Path.Combine(projectRoot, "Packages");
429+
var packageCachePath = Path.Combine(projectRoot, "Library", "PackageCache");
430+
431+
if (Directory.Exists(assetsPath)) searchRoots.Add(assetsPath);
432+
if (Directory.Exists(packagesPath)) searchRoots.Add(packagesPath);
433+
if (Directory.Exists(packageCachePath)) searchRoots.Add(packageCachePath);
434+
435+
// Find all MCPForUnityTools folders in limited search roots
436+
var toolsFolders = new List<string>();
437+
foreach (var searchRoot in searchRoots)
438+
{
439+
try
440+
{
441+
toolsFolders.AddRange(Directory.GetDirectories(searchRoot, "MCPForUnityTools", SearchOption.AllDirectories));
442+
}
443+
catch (Exception ex)
444+
{
445+
McpLog.Warn($"Failed to search {searchRoot}: {ex.Message}");
446+
}
447+
}
423448

424449
int copiedCount = 0;
425450
int skippedCount = 0;
426451

452+
// Track all active folder identifiers (for cleanup)
453+
var activeFolderIdentifiers = new HashSet<string>();
454+
427455
foreach (var folder in toolsFolders)
428456
{
429457
// Generate unique identifier for this tools folder based on its parent directory structure
430458
// e.g., "MooseRunner_MCPForUnityTools" or "MyPackage_MCPForUnityTools"
431459
string folderIdentifier = GetToolsFolderIdentifier(folder);
432-
string versionTrackingFile = Path.Combine(destToolsDir, $"{folderIdentifier}_version.txt");
460+
activeFolderIdentifiers.Add(folderIdentifier);
461+
462+
// Create per-folder subdirectory in destToolsDir
463+
string destFolderSubdir = Path.Combine(destToolsDir, folderIdentifier);
464+
Directory.CreateDirectory(destFolderSubdir);
465+
466+
string versionTrackingFile = Path.Combine(destFolderSubdir, "version.txt");
433467

434468
// Read source version
435469
string sourceVersionFile = Path.Combine(folder, "version.txt");
@@ -450,7 +484,7 @@ private static void CopyUnityProjectTools(string destToolsDir)
450484
foreach (var pyFile in pyFiles)
451485
{
452486
string fileName = Path.GetFileName(pyFile);
453-
string destFile = Path.Combine(destToolsDir, fileName);
487+
string destFile = Path.Combine(destFolderSubdir, fileName);
454488

455489
try
456490
{
@@ -480,6 +514,9 @@ private static void CopyUnityProjectTools(string destToolsDir)
480514
}
481515
}
482516

517+
// Clean up stale subdirectories (folders removed from upstream)
518+
CleanupStaleToolFolders(destToolsDir, activeFolderIdentifiers);
519+
483520
if (copiedCount > 0)
484521
{
485522
McpLog.Info($"Copied {copiedCount} Unity project tool(s) to server");
@@ -491,6 +528,43 @@ private static void CopyUnityProjectTools(string destToolsDir)
491528
}
492529
}
493530

531+
/// <summary>
532+
/// Removes stale tool subdirectories that are no longer present in the Unity project.
533+
/// </summary>
534+
private static void CleanupStaleToolFolders(string destToolsDir, HashSet<string> activeFolderIdentifiers)
535+
{
536+
try
537+
{
538+
if (!Directory.Exists(destToolsDir)) return;
539+
540+
// Get all subdirectories in destToolsDir
541+
var existingSubdirs = Directory.GetDirectories(destToolsDir);
542+
543+
foreach (var subdir in existingSubdirs)
544+
{
545+
string subdirName = Path.GetFileName(subdir);
546+
547+
// Check if this subdirectory corresponds to an active tools folder
548+
if (!activeFolderIdentifiers.Contains(subdirName))
549+
{
550+
try
551+
{
552+
Directory.Delete(subdir, recursive: true);
553+
McpLog.Info($"Cleaned up stale tools folder: {subdirName}");
554+
}
555+
catch (Exception ex)
556+
{
557+
McpLog.Warn($"Failed to delete stale folder {subdirName}: {ex.Message}");
558+
}
559+
}
560+
}
561+
}
562+
catch (Exception ex)
563+
{
564+
McpLog.Warn($"Failed to cleanup stale tool folders: {ex.Message}");
565+
}
566+
}
567+
494568
/// <summary>
495569
/// Generates a unique identifier for a MCPForUnityTools folder based on its parent directory.
496570
/// Example: "Assets/MooseRunner/Editor/MCPForUnityTools" → "MooseRunner_MCPForUnityTools"

MCPForUnity/UnityMcpServer~/src/tools/__init__.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ def register_all_tools(mcp: FastMCP):
2121
"""
2222
Auto-discover and register all tools in the tools/ directory.
2323
24-
Any .py file in this directory with @mcp_for_unity_tool decorated
24+
Any .py file in this directory or subdirectories with @mcp_for_unity_tool decorated
2525
functions will be automatically registered.
2626
"""
2727
logger.info("Auto-discovering MCP for Unity Server tools...")
2828
# Dynamic import of all modules in this directory
2929
tools_dir = Path(__file__).parent
3030

31+
# Discover modules in the top level
3132
for _, module_name, _ in pkgutil.iter_modules([str(tools_dir)]):
3233
# Skip private modules and __init__
3334
if module_name.startswith('_'):
@@ -38,6 +39,24 @@ def register_all_tools(mcp: FastMCP):
3839
except Exception as e:
3940
logger.warning(f"Failed to import tool module {module_name}: {e}")
4041

42+
# Discover modules in subdirectories (one level deep)
43+
for subdir in tools_dir.iterdir():
44+
if not subdir.is_dir() or subdir.name.startswith('_') or subdir.name.startswith('.'):
45+
continue
46+
47+
# Check if subdirectory contains Python modules
48+
for _, module_name, _ in pkgutil.iter_modules([str(subdir)]):
49+
# Skip private modules and __init__
50+
if module_name.startswith('_'):
51+
continue
52+
53+
try:
54+
# Import as tools.subdirname.modulename
55+
full_module_name = f'.{subdir.name}.{module_name}'
56+
importlib.import_module(full_module_name, __package__)
57+
except Exception as e:
58+
logger.warning(f"Failed to import tool module {subdir.name}.{module_name}: {e}")
59+
4160
tools = get_registered_tools()
4261

4362
if not tools:

0 commit comments

Comments
 (0)