-
Notifications
You must be signed in to change notification settings - Fork 462
Fix issue #308: Find py files in MCPForUnityTools and version.txt #309
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f713810
48d912e
68f09e1
069f1be
b1f696d
88a6491
6a4d55b
fac3b95
3fef23e
571ca8c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,232 @@ private static bool TryGetEmbeddedServerSource(out string srcPath) | |
} | ||
|
||
private static readonly string[] _skipDirs = { ".venv", "__pycache__", ".pytest_cache", ".mypy_cache", ".git" }; | ||
|
||
/// <summary> | ||
/// 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. | ||
/// </summary> | ||
private static void CopyUnityProjectTools(string destToolsDir) | ||
{ | ||
try | ||
{ | ||
// Get Unity project root | ||
string projectRoot = Directory.GetParent(Application.dataPath)?.FullName; | ||
if (string.IsNullOrEmpty(projectRoot)) | ||
{ | ||
return; | ||
} | ||
|
||
// Ensure destToolsDir exists | ||
Directory.CreateDirectory(destToolsDir); | ||
|
||
// Limit scan to specific directories to avoid deep recursion | ||
var searchRoots = new List<string>(); | ||
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<string>(); | ||
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<string>(); | ||
|
||
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); | ||
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"); | ||
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)); | ||
|
||
// Skip folders with no .py files | ||
if (!pyFiles.Any()) | ||
{ | ||
skippedCount++; | ||
continue; | ||
} | ||
|
||
bool copyFailed = false; | ||
foreach (var pyFile in pyFiles) | ||
{ | ||
string fileName = Path.GetFileName(pyFile); | ||
string destFile = Path.Combine(destFolderSubdir, 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}"); | ||
copyFailed = true; | ||
} | ||
} | ||
Comment on lines
+478
to
+508
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Copy one-level subdirectories too (aligns with server’s module discovery). Only copying top-level .py files breaks directory-style tools (packages with init.py). Copy subdirs (excluding _skipDirs) one level deep. bool copyFailed = false;
foreach (var pyFile in pyFiles)
{
string fileName = Path.GetFileName(pyFile);
string destFile = Path.Combine(destFolderSubdir, 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}");
copyFailed = true;
}
}
+
+ // Also copy one-level subdirectories (package-style tools), skipping caches/venvs
+ try
+ {
+ foreach (var sub in Directory.GetDirectories(folder, "*", SearchOption.TopDirectoryOnly))
+ {
+ string name = Path.GetFileName(sub);
+ if (_skipDirs.Any(s => name.Equals(s, StringComparison.OrdinalIgnoreCase)))
+ continue;
+ string destSub = Path.Combine(destFolderSubdir, name);
+ try
+ {
+ CopyDirectoryRecursive(sub, destSub);
+ }
+ catch (Exception ex)
+ {
+ McpLog.Warn($"Failed to copy subdirectory {name}: {ex.Message}");
+ copyFailed = true;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ McpLog.Warn($"Failed enumerating subdirectories for {folderIdentifier}: {ex.Message}");
+ copyFailed = true;
+ }
🤖 Prompt for AI Agents
|
||
|
||
// Update version tracking file only on full success | ||
if (!copyFailed) | ||
{ | ||
try | ||
{ | ||
File.WriteAllText(versionTrackingFile, sourceVersion); | ||
} | ||
catch (Exception ex) | ||
{ | ||
McpLog.Warn($"Failed to write version tracking file for {folderIdentifier}: {ex.Message}"); | ||
} | ||
} | ||
} | ||
else | ||
{ | ||
skippedCount++; | ||
} | ||
} | ||
|
||
// Clean up stale subdirectories (folders removed from upstream) | ||
CleanupStaleToolFolders(destToolsDir, activeFolderIdentifiers); | ||
|
||
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}"); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Removes stale tool subdirectories that are no longer present in the Unity project. | ||
/// </summary> | ||
private static void CleanupStaleToolFolders(string destToolsDir, HashSet<string> 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); | ||
|
||
// Skip Python cache and virtual environment directories | ||
foreach (var skip in _skipDirs) | ||
{ | ||
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)) | ||
{ | ||
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}"); | ||
} | ||
} | ||
NextSubdir:; | ||
} | ||
} | ||
catch (Exception ex) | ||
{ | ||
McpLog.Warn($"Failed to cleanup stale tool folders: {ex.Message}"); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Generates a unique identifier for a MCPForUnityTools folder based on its parent directory. | ||
/// Example: "Assets/MooseRunner/Editor/MCPForUnityTools" → "MooseRunner_MCPForUnityTools" | ||
/// </summary> | ||
internal 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 $"{grandparent.Name}_{name}_MCPForUnityTools"; | ||
} | ||
|
||
current = grandparent; | ||
} | ||
|
||
// Fallback: use immediate parent | ||
return $"{parent.Name}_MCPForUnityTools"; | ||
} | ||
catch | ||
{ | ||
return "MCPForUnityTools"; | ||
} | ||
} | ||
JohanHoltby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private static void CopyDirectoryRecursive(string sourceDir, string destinationDir) | ||
{ | ||
Directory.CreateDirectory(destinationDir); | ||
|
@@ -461,6 +692,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 | ||
|
Uh oh!
There was an error while loading. Please reload this page.