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))