Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 75 additions & 1 deletion MCPForUnity/Editor/Helpers/PackageDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 += () =>
Expand Down Expand Up @@ -103,5 +106,76 @@ private static bool LegacyRootsExist()
catch { }
return false;
}

/// <summary>
/// Checks if any MCPForUnityTools folders have version.txt files that differ from installed versions.
/// Returns true if any tool needs updating.
/// </summary>
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 = ServerInstaller.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;
}
}
}
}
137 changes: 137 additions & 0 deletions MCPForUnity/Editor/Helpers/ServerInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
{
Expand Down Expand Up @@ -397,6 +402,134 @@ 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).
/// </summary>
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}");
}
}

/// <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 $"{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);
Expand Down Expand Up @@ -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
Expand Down