From f7138105616180020dde244f7b0bc5b69c8962c6 Mon Sep 17 00:00:00 2001 From: Johan Holtby <72528418+JohanHoltby@users.noreply.github.com> Date: Thu, 9 Oct 2025 09:36:07 +0200 Subject: [PATCH 1/9] Fix issue #308: Find py files in MCPForUnityTools and version.txt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows for auto finding new tools. A good dir on a custom tool would look like this: CustomTool/ ├── CustomTool.MCPEnabler.asmdef ├── CustomTool.MCPEnabler.asmdef.meta ├── ExternalAssetToolFunction.cs ├── ExternalAssetToolFunction.cs.meta ├── external_asset_tool_function.py ├── external_asset_tool_function.py.meta ├── version.txt └── version.txt.meta CS files are left in the tools folder. The asmdef is recommended to allow for dependency on MCPForUnity when MCP For Unity is installed: asmdef example { "name": "CustomTool.MCPEnabler", "rootNamespace": "MCPForUnity.Editor.Tools", "references": [ "CustomTool", "MCPForUnity.Editor" ], "includePlatforms": [ "Editor" ], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": false, "precompiledReferences": [], "autoReferenced": true, "defineConstraints": [], "versionDefines": [], "noEngineReferences": false } --- MCPForUnity/Editor/Helpers/PackageDetector.cs | 110 +++++++++++++- MCPForUnity/Editor/Helpers/ServerInstaller.cs | 137 ++++++++++++++++++ 2 files changed, 246 insertions(+), 1 deletion(-) diff --git a/MCPForUnity/Editor/Helpers/PackageDetector.cs b/MCPForUnity/Editor/Helpers/PackageDetector.cs index bb8861fe..80bb96ac 100644 --- a/MCPForUnity/Editor/Helpers/PackageDetector.cs +++ b/MCPForUnity/Editor/Helpers/PackageDetector.cs @@ -23,7 +23,10 @@ static PackageDetector() bool legacyPresent = LegacyRootsExist(); bool canonicalMissing = !System.IO.File.Exists(System.IO.Path.Combine(ServerInstaller.GetServerPath(), "server.py")); - if (!EditorPrefs.GetBool(key, false) || legacyPresent || canonicalMissing) + // Check if any MCPForUnityTools have updated versions + bool toolsNeedUpdate = ToolsVersionsChanged(); + + if (!EditorPrefs.GetBool(key, false) || legacyPresent || canonicalMissing || toolsNeedUpdate) { // Marshal the entire flow to the main thread. EnsureServerInstalled may touch Unity APIs. EditorApplication.delayCall += () => @@ -103,5 +106,110 @@ private static bool LegacyRootsExist() catch { } return false; } + + /// + /// Checks if any MCPForUnityTools folders have version.txt files that differ from installed versions. + /// Returns true if any tool needs updating. + /// + private static bool ToolsVersionsChanged() + { + try + { + // Get Unity project root + string projectRoot = System.IO.Directory.GetParent(UnityEngine.Application.dataPath)?.FullName; + if (string.IsNullOrEmpty(projectRoot)) + { + return false; + } + + // Get server tools directory + string serverPath = ServerInstaller.GetServerPath(); + string toolsDir = System.IO.Path.Combine(serverPath, "tools"); + + if (!System.IO.Directory.Exists(toolsDir)) + { + // Tools directory doesn't exist yet, needs initial setup + return true; + } + + // Find all MCPForUnityTools folders in project + var toolsFolders = System.IO.Directory.GetDirectories(projectRoot, "MCPForUnityTools", System.IO.SearchOption.AllDirectories); + + foreach (var folder in toolsFolders) + { + // Check if version.txt exists in this folder + string versionFile = System.IO.Path.Combine(folder, "version.txt"); + if (!System.IO.File.Exists(versionFile)) + { + continue; // No version tracking for this folder + } + + // Read source version + string sourceVersion = System.IO.File.ReadAllText(versionFile)?.Trim(); + if (string.IsNullOrEmpty(sourceVersion)) + { + continue; + } + + // Get folder identifier (same logic as ServerInstaller.GetToolsFolderIdentifier) + string folderIdentifier = GetToolsFolderIdentifier(folder); + string trackingFile = System.IO.Path.Combine(toolsDir, $"{folderIdentifier}_version.txt"); + + // Read installed version + string installedVersion = null; + if (System.IO.File.Exists(trackingFile)) + { + installedVersion = System.IO.File.ReadAllText(trackingFile)?.Trim(); + } + + // Check if versions differ + if (string.IsNullOrEmpty(installedVersion) || sourceVersion != installedVersion) + { + return true; // Version changed, needs update + } + } + + return false; // All versions match + } + catch + { + // On error, assume update needed to be safe + return true; + } + } + + /// + /// Generates a unique identifier for a MCPForUnityTools folder (duplicates ServerInstaller logic). + /// + private static string GetToolsFolderIdentifier(string toolsFolderPath) + { + try + { + System.IO.DirectoryInfo parent = System.IO.Directory.GetParent(toolsFolderPath); + if (parent == null) return "MCPForUnityTools"; + + System.IO.DirectoryInfo current = parent; + while (current != null) + { + string name = current.Name; + System.IO.DirectoryInfo grandparent = current.Parent; + + if (grandparent != null && + (grandparent.Name.Equals("Assets", System.StringComparison.OrdinalIgnoreCase) || + grandparent.Name.Equals("Packages", System.StringComparison.OrdinalIgnoreCase))) + { + return $"{name}_MCPForUnityTools"; + } + + current = grandparent; + } + + return $"{parent.Name}_MCPForUnityTools"; + } + catch + { + return "MCPForUnityTools"; + } + } } } diff --git a/MCPForUnity/Editor/Helpers/ServerInstaller.cs b/MCPForUnity/Editor/Helpers/ServerInstaller.cs index f41e03c3..713c00a0 100644 --- a/MCPForUnity/Editor/Helpers/ServerInstaller.cs +++ b/MCPForUnity/Editor/Helpers/ServerInstaller.cs @@ -52,11 +52,16 @@ public static void EnsureServerInstalled() // Copy the entire UnityMcpServer folder (parent of src) string embeddedRoot = Path.GetDirectoryName(embeddedSrc) ?? embeddedSrc; // go up from src to UnityMcpServer CopyDirectoryRecursive(embeddedRoot, destRoot); + // Write/refresh version file try { File.WriteAllText(Path.Combine(destSrc, VersionFileName), embeddedVer ?? "unknown"); } catch { } McpLog.Info($"Installed/updated server to {destRoot} (version {embeddedVer})."); } + // Copy Unity project tools (runs independently of server version updates) + string destToolsDir = Path.Combine(destSrc, "tools"); + CopyUnityProjectTools(destToolsDir); + // Cleanup legacy installs that are missing version or older than embedded foreach (var legacyRoot in GetLegacyRootsForDetection()) { @@ -397,6 +402,134 @@ private static bool TryGetEmbeddedServerSource(out string srcPath) } private static readonly string[] _skipDirs = { ".venv", "__pycache__", ".pytest_cache", ".mypy_cache", ".git" }; + + /// + /// Searches Unity project for MCPForUnityTools folders and copies .py files to server tools directory. + /// Only copies if the tool's version.txt has changed (or doesn't exist). + /// + private static void CopyUnityProjectTools(string destToolsDir) + { + try + { + // Get Unity project root + string projectRoot = Directory.GetParent(Application.dataPath)?.FullName; + if (string.IsNullOrEmpty(projectRoot)) + { + return; + } + + // Find all MCPForUnityTools folders + var toolsFolders = Directory.GetDirectories(projectRoot, "MCPForUnityTools", SearchOption.AllDirectories); + + int copiedCount = 0; + int skippedCount = 0; + + foreach (var folder in toolsFolders) + { + // Generate unique identifier for this tools folder based on its parent directory structure + // e.g., "MooseRunner_MCPForUnityTools" or "MyPackage_MCPForUnityTools" + string folderIdentifier = GetToolsFolderIdentifier(folder); + string versionTrackingFile = Path.Combine(destToolsDir, $"{folderIdentifier}_version.txt"); + + // Read source version + string sourceVersionFile = Path.Combine(folder, "version.txt"); + string sourceVersion = ReadVersionFile(sourceVersionFile) ?? "0.0.0"; + + // Read installed version (tracked separately per tools folder) + string installedVersion = ReadVersionFile(versionTrackingFile); + + // Check if update is needed (version different or no tracking file) + bool needsUpdate = string.IsNullOrEmpty(installedVersion) || sourceVersion != installedVersion; + + if (needsUpdate) + { + // Get all .py files (excluding __init__.py) + var pyFiles = Directory.GetFiles(folder, "*.py") + .Where(f => !Path.GetFileName(f).Equals("__init__.py", StringComparison.OrdinalIgnoreCase)); + + foreach (var pyFile in pyFiles) + { + string fileName = Path.GetFileName(pyFile); + string destFile = Path.Combine(destToolsDir, fileName); + + try + { + File.Copy(pyFile, destFile, overwrite: true); + copiedCount++; + McpLog.Info($"Copied Unity project tool: {fileName} from {folderIdentifier} (v{sourceVersion})"); + } + catch (Exception ex) + { + McpLog.Warn($"Failed to copy {fileName}: {ex.Message}"); + } + } + + // Update version tracking file + try + { + File.WriteAllText(versionTrackingFile, sourceVersion); + } + catch (Exception ex) + { + McpLog.Warn($"Failed to write version tracking file for {folderIdentifier}: {ex.Message}"); + } + } + else + { + skippedCount++; + } + } + + if (copiedCount > 0) + { + McpLog.Info($"Copied {copiedCount} Unity project tool(s) to server"); + } + } + catch (Exception ex) + { + McpLog.Warn($"Failed to scan Unity project for tools: {ex.Message}"); + } + } + + /// + /// Generates a unique identifier for a MCPForUnityTools folder based on its parent directory. + /// Example: "Assets/MooseRunner/Editor/MCPForUnityTools" → "MooseRunner_MCPForUnityTools" + /// + private static string GetToolsFolderIdentifier(string toolsFolderPath) + { + try + { + // Get parent directory name (e.g., "Editor" or package name) + DirectoryInfo parent = Directory.GetParent(toolsFolderPath); + if (parent == null) return "MCPForUnityTools"; + + // Walk up to find a distinctive parent (Assets/PackageName or Packages/PackageName) + DirectoryInfo current = parent; + while (current != null) + { + string name = current.Name; + DirectoryInfo grandparent = current.Parent; + + // Stop at Assets, Packages, or if we find a package-like structure + if (grandparent != null && + (grandparent.Name.Equals("Assets", StringComparison.OrdinalIgnoreCase) || + grandparent.Name.Equals("Packages", StringComparison.OrdinalIgnoreCase))) + { + return $"{name}_MCPForUnityTools"; + } + + current = grandparent; + } + + // Fallback: use immediate parent + return $"{parent.Name}_MCPForUnityTools"; + } + catch + { + return "MCPForUnityTools"; + } + } + private static void CopyDirectoryRecursive(string sourceDir, string destinationDir) { Directory.CreateDirectory(destinationDir); @@ -461,6 +594,10 @@ public static bool RebuildMcpServer() Directory.CreateDirectory(destRoot); CopyDirectoryRecursive(embeddedRoot, destRoot); + // Copy Unity project tools + string destToolsDir = Path.Combine(destSrc, "tools"); + CopyUnityProjectTools(destToolsDir); + // Write version file string embeddedVer = ReadVersionFile(Path.Combine(embeddedSrc, VersionFileName)) ?? "unknown"; try From 48d912e0ef62890eb9b5e8e379450cea06010e09 Mon Sep 17 00:00:00 2001 From: Johan Holtby <72528418+JohanHoltby@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:38:42 +0200 Subject: [PATCH 2/9] Follow-up: address CodeRabbit feedback for #308 () --- MCPForUnity/Editor/Helpers/PackageDetector.cs | 36 +------------------ MCPForUnity/Editor/Helpers/ServerInstaller.cs | 2 +- 2 files changed, 2 insertions(+), 36 deletions(-) diff --git a/MCPForUnity/Editor/Helpers/PackageDetector.cs b/MCPForUnity/Editor/Helpers/PackageDetector.cs index 80bb96ac..cb044093 100644 --- a/MCPForUnity/Editor/Helpers/PackageDetector.cs +++ b/MCPForUnity/Editor/Helpers/PackageDetector.cs @@ -152,7 +152,7 @@ private static bool ToolsVersionsChanged() } // Get folder identifier (same logic as ServerInstaller.GetToolsFolderIdentifier) - string folderIdentifier = GetToolsFolderIdentifier(folder); + string folderIdentifier = ServerInstaller.GetToolsFolderIdentifier(folder); string trackingFile = System.IO.Path.Combine(toolsDir, $"{folderIdentifier}_version.txt"); // Read installed version @@ -177,39 +177,5 @@ private static bool ToolsVersionsChanged() return true; } } - - /// - /// Generates a unique identifier for a MCPForUnityTools folder (duplicates ServerInstaller logic). - /// - private static string GetToolsFolderIdentifier(string toolsFolderPath) - { - try - { - System.IO.DirectoryInfo parent = System.IO.Directory.GetParent(toolsFolderPath); - if (parent == null) return "MCPForUnityTools"; - - System.IO.DirectoryInfo current = parent; - while (current != null) - { - string name = current.Name; - System.IO.DirectoryInfo grandparent = current.Parent; - - if (grandparent != null && - (grandparent.Name.Equals("Assets", System.StringComparison.OrdinalIgnoreCase) || - grandparent.Name.Equals("Packages", System.StringComparison.OrdinalIgnoreCase))) - { - return $"{name}_MCPForUnityTools"; - } - - current = grandparent; - } - - return $"{parent.Name}_MCPForUnityTools"; - } - catch - { - return "MCPForUnityTools"; - } - } } } diff --git a/MCPForUnity/Editor/Helpers/ServerInstaller.cs b/MCPForUnity/Editor/Helpers/ServerInstaller.cs index 713c00a0..7ee13db0 100644 --- a/MCPForUnity/Editor/Helpers/ServerInstaller.cs +++ b/MCPForUnity/Editor/Helpers/ServerInstaller.cs @@ -495,7 +495,7 @@ private static void CopyUnityProjectTools(string destToolsDir) /// Generates a unique identifier for a MCPForUnityTools folder based on its parent directory. /// Example: "Assets/MooseRunner/Editor/MCPForUnityTools" → "MooseRunner_MCPForUnityTools" /// - private static string GetToolsFolderIdentifier(string toolsFolderPath) + internal static string GetToolsFolderIdentifier(string toolsFolderPath) { try { From 68f09e12b0757a9945aa81d2882ee5479862c68a Mon Sep 17 00:00:00 2001 From: Johan Holtby <72528418+JohanHoltby@users.noreply.github.com> Date: Thu, 9 Oct 2025 12:19:13 +0200 Subject: [PATCH 3/9] =?UTF-8?q?Follow-up:=20address=20CodeRabbit=20feedbac?= =?UTF-8?q?k=20for=20#308=20=E2=80=93=20centralize=20GetToolsFolderIdentif?= =?UTF-8?q?ier,=20fix=20tools=20copy=20dir,=20and=20limit=20scan=20scope?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MCPForUnity/Editor/Helpers/ServerInstaller.cs | 82 ++++++++++++++++++- .../UnityMcpServer~/src/tools/__init__.py | 21 ++++- 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/MCPForUnity/Editor/Helpers/ServerInstaller.cs b/MCPForUnity/Editor/Helpers/ServerInstaller.cs index 7ee13db0..a31eca9a 100644 --- a/MCPForUnity/Editor/Helpers/ServerInstaller.cs +++ b/MCPForUnity/Editor/Helpers/ServerInstaller.cs @@ -406,6 +406,7 @@ private static bool TryGetEmbeddedServerSource(out string srcPath) /// /// Searches Unity project for MCPForUnityTools folders and copies .py files to server tools directory. /// Only copies if the tool's version.txt has changed (or doesn't exist). + /// Files are copied into per-folder subdirectories to avoid conflicts. /// private static void CopyUnityProjectTools(string destToolsDir) { @@ -418,18 +419,51 @@ private static void CopyUnityProjectTools(string destToolsDir) return; } - // Find all MCPForUnityTools folders - var toolsFolders = Directory.GetDirectories(projectRoot, "MCPForUnityTools", SearchOption.AllDirectories); + // Ensure destToolsDir exists + Directory.CreateDirectory(destToolsDir); + + // Limit scan to specific directories to avoid deep recursion + var searchRoots = new List(); + var assetsPath = Path.Combine(projectRoot, "Assets"); + var packagesPath = Path.Combine(projectRoot, "Packages"); + var packageCachePath = Path.Combine(projectRoot, "Library", "PackageCache"); + + if (Directory.Exists(assetsPath)) searchRoots.Add(assetsPath); + if (Directory.Exists(packagesPath)) searchRoots.Add(packagesPath); + if (Directory.Exists(packageCachePath)) searchRoots.Add(packageCachePath); + + // Find all MCPForUnityTools folders in limited search roots + var toolsFolders = new List(); + foreach (var searchRoot in searchRoots) + { + try + { + toolsFolders.AddRange(Directory.GetDirectories(searchRoot, "MCPForUnityTools", SearchOption.AllDirectories)); + } + catch (Exception ex) + { + McpLog.Warn($"Failed to search {searchRoot}: {ex.Message}"); + } + } int copiedCount = 0; int skippedCount = 0; + // Track all active folder identifiers (for cleanup) + var activeFolderIdentifiers = new HashSet(); + foreach (var folder in toolsFolders) { // Generate unique identifier for this tools folder based on its parent directory structure // e.g., "MooseRunner_MCPForUnityTools" or "MyPackage_MCPForUnityTools" string folderIdentifier = GetToolsFolderIdentifier(folder); - string versionTrackingFile = Path.Combine(destToolsDir, $"{folderIdentifier}_version.txt"); + activeFolderIdentifiers.Add(folderIdentifier); + + // Create per-folder subdirectory in destToolsDir + string destFolderSubdir = Path.Combine(destToolsDir, folderIdentifier); + Directory.CreateDirectory(destFolderSubdir); + + string versionTrackingFile = Path.Combine(destFolderSubdir, "version.txt"); // Read source version string sourceVersionFile = Path.Combine(folder, "version.txt"); @@ -450,7 +484,7 @@ private static void CopyUnityProjectTools(string destToolsDir) foreach (var pyFile in pyFiles) { string fileName = Path.GetFileName(pyFile); - string destFile = Path.Combine(destToolsDir, fileName); + string destFile = Path.Combine(destFolderSubdir, fileName); try { @@ -480,6 +514,9 @@ private static void CopyUnityProjectTools(string destToolsDir) } } + // Clean up stale subdirectories (folders removed from upstream) + CleanupStaleToolFolders(destToolsDir, activeFolderIdentifiers); + if (copiedCount > 0) { McpLog.Info($"Copied {copiedCount} Unity project tool(s) to server"); @@ -491,6 +528,43 @@ private static void CopyUnityProjectTools(string destToolsDir) } } + /// + /// Removes stale tool subdirectories that are no longer present in the Unity project. + /// + private static void CleanupStaleToolFolders(string destToolsDir, HashSet activeFolderIdentifiers) + { + try + { + if (!Directory.Exists(destToolsDir)) return; + + // Get all subdirectories in destToolsDir + var existingSubdirs = Directory.GetDirectories(destToolsDir); + + foreach (var subdir in existingSubdirs) + { + string subdirName = Path.GetFileName(subdir); + + // Check if this subdirectory corresponds to an active tools folder + if (!activeFolderIdentifiers.Contains(subdirName)) + { + try + { + Directory.Delete(subdir, recursive: true); + McpLog.Info($"Cleaned up stale tools folder: {subdirName}"); + } + catch (Exception ex) + { + McpLog.Warn($"Failed to delete stale folder {subdirName}: {ex.Message}"); + } + } + } + } + catch (Exception ex) + { + McpLog.Warn($"Failed to cleanup stale tool folders: {ex.Message}"); + } + } + /// /// Generates a unique identifier for a MCPForUnityTools folder based on its parent directory. /// Example: "Assets/MooseRunner/Editor/MCPForUnityTools" → "MooseRunner_MCPForUnityTools" diff --git a/MCPForUnity/UnityMcpServer~/src/tools/__init__.py b/MCPForUnity/UnityMcpServer~/src/tools/__init__.py index 6ede53d3..a6fcb17c 100644 --- a/MCPForUnity/UnityMcpServer~/src/tools/__init__.py +++ b/MCPForUnity/UnityMcpServer~/src/tools/__init__.py @@ -21,13 +21,14 @@ def register_all_tools(mcp: FastMCP): """ Auto-discover and register all tools in the tools/ directory. - Any .py file in this directory with @mcp_for_unity_tool decorated + Any .py file in this directory or subdirectories with @mcp_for_unity_tool decorated functions will be automatically registered. """ logger.info("Auto-discovering MCP for Unity Server tools...") # Dynamic import of all modules in this directory tools_dir = Path(__file__).parent + # Discover modules in the top level for _, module_name, _ in pkgutil.iter_modules([str(tools_dir)]): # Skip private modules and __init__ if module_name.startswith('_'): @@ -38,6 +39,24 @@ def register_all_tools(mcp: FastMCP): except Exception as e: logger.warning(f"Failed to import tool module {module_name}: {e}") + # Discover modules in subdirectories (one level deep) + for subdir in tools_dir.iterdir(): + if not subdir.is_dir() or subdir.name.startswith('_') or subdir.name.startswith('.'): + continue + + # Check if subdirectory contains Python modules + for _, module_name, _ in pkgutil.iter_modules([str(subdir)]): + # Skip private modules and __init__ + if module_name.startswith('_'): + continue + + try: + # Import as tools.subdirname.modulename + full_module_name = f'.{subdir.name}.{module_name}' + importlib.import_module(full_module_name, __package__) + except Exception as e: + logger.warning(f"Failed to import tool module {subdir.name}.{module_name}: {e}") + tools = get_registered_tools() if not tools: From 069f1bebcd035e70bf15ce6807e64bedf380f917 Mon Sep 17 00:00:00 2001 From: Johan Holtby <72528418+JohanHoltby@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:08:15 +0200 Subject: [PATCH 4/9] Fixing so the MCP don't removes _skipDirs e.g. __pycache__ --- MCPForUnity/Editor/Helpers/ServerInstaller.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MCPForUnity/Editor/Helpers/ServerInstaller.cs b/MCPForUnity/Editor/Helpers/ServerInstaller.cs index a31eca9a..2cf5b198 100644 --- a/MCPForUnity/Editor/Helpers/ServerInstaller.cs +++ b/MCPForUnity/Editor/Helpers/ServerInstaller.cs @@ -544,6 +544,13 @@ private static void CleanupStaleToolFolders(string destToolsDir, HashSet { string subdirName = Path.GetFileName(subdir); + // Skip Python cache and virtual environment directories + foreach (var skip in _skipDirs) + { + if (subdirName.Equals(skip, StringComparison.OrdinalIgnoreCase)) + goto NextSubdir; + } + // Check if this subdirectory corresponds to an active tools folder if (!activeFolderIdentifiers.Contains(subdirName)) { @@ -557,6 +564,7 @@ private static void CleanupStaleToolFolders(string destToolsDir, HashSet McpLog.Warn($"Failed to delete stale folder {subdirName}: {ex.Message}"); } } + NextSubdir:; } } catch (Exception ex) From b1f696d8343b2d15c26ca383c88d8594883b970b Mon Sep 17 00:00:00 2001 From: Johan Holtby <72528418+JohanHoltby@users.noreply.github.com> Date: Fri, 10 Oct 2025 20:45:26 +0200 Subject: [PATCH 5/9] skip empty folders with no py files --- MCPForUnity/Editor/Helpers/ServerInstaller.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MCPForUnity/Editor/Helpers/ServerInstaller.cs b/MCPForUnity/Editor/Helpers/ServerInstaller.cs index 2cf5b198..6d9c7834 100644 --- a/MCPForUnity/Editor/Helpers/ServerInstaller.cs +++ b/MCPForUnity/Editor/Helpers/ServerInstaller.cs @@ -480,6 +480,13 @@ private static void CopyUnityProjectTools(string destToolsDir) // Get all .py files (excluding __init__.py) var pyFiles = Directory.GetFiles(folder, "*.py") .Where(f => !Path.GetFileName(f).Equals("__init__.py", StringComparison.OrdinalIgnoreCase)); + + // Skip folders with no .py files + if (!pyFiles.Any()) + { + skippedCount++; + continue; + } foreach (var pyFile in pyFiles) { From 88a64911a005bed115dc0414401caf9ac53f8c82 Mon Sep 17 00:00:00 2001 From: Johan Holtby <72528418+JohanHoltby@users.noreply.github.com> Date: Fri, 10 Oct 2025 22:26:59 +0200 Subject: [PATCH 6/9] Rabbit: "Fix identifier collision between different package roots." --- MCPForUnity/Editor/Helpers/ServerInstaller.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MCPForUnity/Editor/Helpers/ServerInstaller.cs b/MCPForUnity/Editor/Helpers/ServerInstaller.cs index 6d9c7834..a44d904f 100644 --- a/MCPForUnity/Editor/Helpers/ServerInstaller.cs +++ b/MCPForUnity/Editor/Helpers/ServerInstaller.cs @@ -604,7 +604,7 @@ internal static string GetToolsFolderIdentifier(string toolsFolderPath) (grandparent.Name.Equals("Assets", StringComparison.OrdinalIgnoreCase) || grandparent.Name.Equals("Packages", StringComparison.OrdinalIgnoreCase))) { - return $"{name}_MCPForUnityTools"; + return $"{grandparent.Name}_{name}_MCPForUnityTools"; } current = grandparent; From 6a4d55b366b06e55c734517521018c330140baac Mon Sep 17 00:00:00 2001 From: Johan Holtby <72528418+JohanHoltby@users.noreply.github.com> Date: Fri, 10 Oct 2025 22:36:50 +0200 Subject: [PATCH 7/9] Update MCPForUnity/Editor/Helpers/ServerInstaller.cs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- MCPForUnity/Editor/Helpers/ServerInstaller.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/MCPForUnity/Editor/Helpers/ServerInstaller.cs b/MCPForUnity/Editor/Helpers/ServerInstaller.cs index a44d904f..984b487e 100644 --- a/MCPForUnity/Editor/Helpers/ServerInstaller.cs +++ b/MCPForUnity/Editor/Helpers/ServerInstaller.cs @@ -488,6 +488,7 @@ private static void CopyUnityProjectTools(string destToolsDir) continue; } + bool copyFailed = false; foreach (var pyFile in pyFiles) { string fileName = Path.GetFileName(pyFile); @@ -502,17 +503,21 @@ private static void CopyUnityProjectTools(string destToolsDir) catch (Exception ex) { McpLog.Warn($"Failed to copy {fileName}: {ex.Message}"); + copyFailed = true; } } - // Update version tracking file - try + // Update version tracking file only on full success + if (!copyFailed) { - File.WriteAllText(versionTrackingFile, sourceVersion); - } - catch (Exception ex) - { - McpLog.Warn($"Failed to write version tracking file for {folderIdentifier}: {ex.Message}"); + try + { + File.WriteAllText(versionTrackingFile, sourceVersion); + } + catch (Exception ex) + { + McpLog.Warn($"Failed to write version tracking file for {folderIdentifier}: {ex.Message}"); + } } } else From fac3b954a30a74de8c04a8558aa1b01e41742e2e Mon Sep 17 00:00:00 2001 From: Johan Holtby <72528418+JohanHoltby@users.noreply.github.com> Date: Fri, 10 Oct 2025 22:40:11 +0200 Subject: [PATCH 8/9] =?UTF-8?q?Rabbbit:=20Cleanup=20may=20delete=20server?= =?UTF-8?q?=E2=80=99s=20built-in=20tool=20subfolders=20=E2=80=94=20restric?= =?UTF-8?q?t=20to=20managed=20names.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MCPForUnity/Editor/Helpers/ServerInstaller.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MCPForUnity/Editor/Helpers/ServerInstaller.cs b/MCPForUnity/Editor/Helpers/ServerInstaller.cs index a44d904f..4882bb81 100644 --- a/MCPForUnity/Editor/Helpers/ServerInstaller.cs +++ b/MCPForUnity/Editor/Helpers/ServerInstaller.cs @@ -557,6 +557,10 @@ private static void CleanupStaleToolFolders(string destToolsDir, HashSet if (subdirName.Equals(skip, StringComparison.OrdinalIgnoreCase)) goto NextSubdir; } + + // Only manage per-folder tool installs created by this feature ++ if (!subdirName.EndsWith("_MCPForUnityTools", StringComparison.OrdinalIgnoreCase)) ++ goto NextSubdir; // Check if this subdirectory corresponds to an active tools folder if (!activeFolderIdentifiers.Contains(subdirName)) From 571ca8c5de3d07da3791dad558677909a07e886d Mon Sep 17 00:00:00 2001 From: Johan Holtby <72528418+JohanHoltby@users.noreply.github.com> Date: Sun, 12 Oct 2025 16:32:33 +0200 Subject: [PATCH 9/9] Fixed minor + missed onadding rabit change --- MCPForUnity/Editor/Dependencies/Models.meta | 2 +- MCPForUnity/Editor/Dependencies/PlatformDetectors.meta | 2 +- MCPForUnity/Editor/Helpers/ServerInstaller.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MCPForUnity/Editor/Dependencies/Models.meta b/MCPForUnity/Editor/Dependencies/Models.meta index 2174dd52..3ba640c9 100644 --- a/MCPForUnity/Editor/Dependencies/Models.meta +++ b/MCPForUnity/Editor/Dependencies/Models.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b2c3d4e5f6789012345678901234abcd +guid: 2b4fca8c8f964494e82a2c1d1d8d2041 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/MCPForUnity/Editor/Dependencies/PlatformDetectors.meta b/MCPForUnity/Editor/Dependencies/PlatformDetectors.meta index 22a6b1db..be8b8dce 100644 --- a/MCPForUnity/Editor/Dependencies/PlatformDetectors.meta +++ b/MCPForUnity/Editor/Dependencies/PlatformDetectors.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c3d4e5f6789012345678901234abcdef +guid: c6d16631d05433740a7193d3384364a8 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/MCPForUnity/Editor/Helpers/ServerInstaller.cs b/MCPForUnity/Editor/Helpers/ServerInstaller.cs index bd632880..0368b596 100644 --- a/MCPForUnity/Editor/Helpers/ServerInstaller.cs +++ b/MCPForUnity/Editor/Helpers/ServerInstaller.cs @@ -564,8 +564,8 @@ private static void CleanupStaleToolFolders(string destToolsDir, HashSet } // Only manage per-folder tool installs created by this feature -+ if (!subdirName.EndsWith("_MCPForUnityTools", StringComparison.OrdinalIgnoreCase)) -+ goto NextSubdir; + if (!subdirName.EndsWith("_MCPForUnityTools", StringComparison.OrdinalIgnoreCase)) + goto NextSubdir; // Check if this subdirectory corresponds to an active tools folder if (!activeFolderIdentifiers.Contains(subdirName))