Skip to content

Commit f713810

Browse files
committed
Fix issue #308: Find py files in MCPForUnityTools and version.txt
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 }
1 parent 50844c2 commit f713810

File tree

2 files changed

+246
-1
lines changed

2 files changed

+246
-1
lines changed

MCPForUnity/Editor/Helpers/PackageDetector.cs

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ static PackageDetector()
2323
bool legacyPresent = LegacyRootsExist();
2424
bool canonicalMissing = !System.IO.File.Exists(System.IO.Path.Combine(ServerInstaller.GetServerPath(), "server.py"));
2525

26-
if (!EditorPrefs.GetBool(key, false) || legacyPresent || canonicalMissing)
26+
// Check if any MCPForUnityTools have updated versions
27+
bool toolsNeedUpdate = ToolsVersionsChanged();
28+
29+
if (!EditorPrefs.GetBool(key, false) || legacyPresent || canonicalMissing || toolsNeedUpdate)
2730
{
2831
// Marshal the entire flow to the main thread. EnsureServerInstalled may touch Unity APIs.
2932
EditorApplication.delayCall += () =>
@@ -103,5 +106,110 @@ private static bool LegacyRootsExist()
103106
catch { }
104107
return false;
105108
}
109+
110+
/// <summary>
111+
/// Checks if any MCPForUnityTools folders have version.txt files that differ from installed versions.
112+
/// Returns true if any tool needs updating.
113+
/// </summary>
114+
private static bool ToolsVersionsChanged()
115+
{
116+
try
117+
{
118+
// Get Unity project root
119+
string projectRoot = System.IO.Directory.GetParent(UnityEngine.Application.dataPath)?.FullName;
120+
if (string.IsNullOrEmpty(projectRoot))
121+
{
122+
return false;
123+
}
124+
125+
// Get server tools directory
126+
string serverPath = ServerInstaller.GetServerPath();
127+
string toolsDir = System.IO.Path.Combine(serverPath, "tools");
128+
129+
if (!System.IO.Directory.Exists(toolsDir))
130+
{
131+
// Tools directory doesn't exist yet, needs initial setup
132+
return true;
133+
}
134+
135+
// Find all MCPForUnityTools folders in project
136+
var toolsFolders = System.IO.Directory.GetDirectories(projectRoot, "MCPForUnityTools", System.IO.SearchOption.AllDirectories);
137+
138+
foreach (var folder in toolsFolders)
139+
{
140+
// Check if version.txt exists in this folder
141+
string versionFile = System.IO.Path.Combine(folder, "version.txt");
142+
if (!System.IO.File.Exists(versionFile))
143+
{
144+
continue; // No version tracking for this folder
145+
}
146+
147+
// Read source version
148+
string sourceVersion = System.IO.File.ReadAllText(versionFile)?.Trim();
149+
if (string.IsNullOrEmpty(sourceVersion))
150+
{
151+
continue;
152+
}
153+
154+
// Get folder identifier (same logic as ServerInstaller.GetToolsFolderIdentifier)
155+
string folderIdentifier = GetToolsFolderIdentifier(folder);
156+
string trackingFile = System.IO.Path.Combine(toolsDir, $"{folderIdentifier}_version.txt");
157+
158+
// Read installed version
159+
string installedVersion = null;
160+
if (System.IO.File.Exists(trackingFile))
161+
{
162+
installedVersion = System.IO.File.ReadAllText(trackingFile)?.Trim();
163+
}
164+
165+
// Check if versions differ
166+
if (string.IsNullOrEmpty(installedVersion) || sourceVersion != installedVersion)
167+
{
168+
return true; // Version changed, needs update
169+
}
170+
}
171+
172+
return false; // All versions match
173+
}
174+
catch
175+
{
176+
// On error, assume update needed to be safe
177+
return true;
178+
}
179+
}
180+
181+
/// <summary>
182+
/// Generates a unique identifier for a MCPForUnityTools folder (duplicates ServerInstaller logic).
183+
/// </summary>
184+
private static string GetToolsFolderIdentifier(string toolsFolderPath)
185+
{
186+
try
187+
{
188+
System.IO.DirectoryInfo parent = System.IO.Directory.GetParent(toolsFolderPath);
189+
if (parent == null) return "MCPForUnityTools";
190+
191+
System.IO.DirectoryInfo current = parent;
192+
while (current != null)
193+
{
194+
string name = current.Name;
195+
System.IO.DirectoryInfo grandparent = current.Parent;
196+
197+
if (grandparent != null &&
198+
(grandparent.Name.Equals("Assets", System.StringComparison.OrdinalIgnoreCase) ||
199+
grandparent.Name.Equals("Packages", System.StringComparison.OrdinalIgnoreCase)))
200+
{
201+
return $"{name}_MCPForUnityTools";
202+
}
203+
204+
current = grandparent;
205+
}
206+
207+
return $"{parent.Name}_MCPForUnityTools";
208+
}
209+
catch
210+
{
211+
return "MCPForUnityTools";
212+
}
213+
}
106214
}
107215
}

MCPForUnity/Editor/Helpers/ServerInstaller.cs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,16 @@ public static void EnsureServerInstalled()
5252
// Copy the entire UnityMcpServer folder (parent of src)
5353
string embeddedRoot = Path.GetDirectoryName(embeddedSrc) ?? embeddedSrc; // go up from src to UnityMcpServer
5454
CopyDirectoryRecursive(embeddedRoot, destRoot);
55+
5556
// Write/refresh version file
5657
try { File.WriteAllText(Path.Combine(destSrc, VersionFileName), embeddedVer ?? "unknown"); } catch { }
5758
McpLog.Info($"Installed/updated server to {destRoot} (version {embeddedVer}).");
5859
}
5960

61+
// Copy Unity project tools (runs independently of server version updates)
62+
string destToolsDir = Path.Combine(destSrc, "tools");
63+
CopyUnityProjectTools(destToolsDir);
64+
6065
// Cleanup legacy installs that are missing version or older than embedded
6166
foreach (var legacyRoot in GetLegacyRootsForDetection())
6267
{
@@ -397,6 +402,134 @@ private static bool TryGetEmbeddedServerSource(out string srcPath)
397402
}
398403

399404
private static readonly string[] _skipDirs = { ".venv", "__pycache__", ".pytest_cache", ".mypy_cache", ".git" };
405+
406+
/// <summary>
407+
/// Searches Unity project for MCPForUnityTools folders and copies .py files to server tools directory.
408+
/// Only copies if the tool's version.txt has changed (or doesn't exist).
409+
/// </summary>
410+
private static void CopyUnityProjectTools(string destToolsDir)
411+
{
412+
try
413+
{
414+
// Get Unity project root
415+
string projectRoot = Directory.GetParent(Application.dataPath)?.FullName;
416+
if (string.IsNullOrEmpty(projectRoot))
417+
{
418+
return;
419+
}
420+
421+
// Find all MCPForUnityTools folders
422+
var toolsFolders = Directory.GetDirectories(projectRoot, "MCPForUnityTools", SearchOption.AllDirectories);
423+
424+
int copiedCount = 0;
425+
int skippedCount = 0;
426+
427+
foreach (var folder in toolsFolders)
428+
{
429+
// Generate unique identifier for this tools folder based on its parent directory structure
430+
// e.g., "MooseRunner_MCPForUnityTools" or "MyPackage_MCPForUnityTools"
431+
string folderIdentifier = GetToolsFolderIdentifier(folder);
432+
string versionTrackingFile = Path.Combine(destToolsDir, $"{folderIdentifier}_version.txt");
433+
434+
// Read source version
435+
string sourceVersionFile = Path.Combine(folder, "version.txt");
436+
string sourceVersion = ReadVersionFile(sourceVersionFile) ?? "0.0.0";
437+
438+
// Read installed version (tracked separately per tools folder)
439+
string installedVersion = ReadVersionFile(versionTrackingFile);
440+
441+
// Check if update is needed (version different or no tracking file)
442+
bool needsUpdate = string.IsNullOrEmpty(installedVersion) || sourceVersion != installedVersion;
443+
444+
if (needsUpdate)
445+
{
446+
// Get all .py files (excluding __init__.py)
447+
var pyFiles = Directory.GetFiles(folder, "*.py")
448+
.Where(f => !Path.GetFileName(f).Equals("__init__.py", StringComparison.OrdinalIgnoreCase));
449+
450+
foreach (var pyFile in pyFiles)
451+
{
452+
string fileName = Path.GetFileName(pyFile);
453+
string destFile = Path.Combine(destToolsDir, fileName);
454+
455+
try
456+
{
457+
File.Copy(pyFile, destFile, overwrite: true);
458+
copiedCount++;
459+
McpLog.Info($"Copied Unity project tool: {fileName} from {folderIdentifier} (v{sourceVersion})");
460+
}
461+
catch (Exception ex)
462+
{
463+
McpLog.Warn($"Failed to copy {fileName}: {ex.Message}");
464+
}
465+
}
466+
467+
// Update version tracking file
468+
try
469+
{
470+
File.WriteAllText(versionTrackingFile, sourceVersion);
471+
}
472+
catch (Exception ex)
473+
{
474+
McpLog.Warn($"Failed to write version tracking file for {folderIdentifier}: {ex.Message}");
475+
}
476+
}
477+
else
478+
{
479+
skippedCount++;
480+
}
481+
}
482+
483+
if (copiedCount > 0)
484+
{
485+
McpLog.Info($"Copied {copiedCount} Unity project tool(s) to server");
486+
}
487+
}
488+
catch (Exception ex)
489+
{
490+
McpLog.Warn($"Failed to scan Unity project for tools: {ex.Message}");
491+
}
492+
}
493+
494+
/// <summary>
495+
/// Generates a unique identifier for a MCPForUnityTools folder based on its parent directory.
496+
/// Example: "Assets/MooseRunner/Editor/MCPForUnityTools" → "MooseRunner_MCPForUnityTools"
497+
/// </summary>
498+
private static string GetToolsFolderIdentifier(string toolsFolderPath)
499+
{
500+
try
501+
{
502+
// Get parent directory name (e.g., "Editor" or package name)
503+
DirectoryInfo parent = Directory.GetParent(toolsFolderPath);
504+
if (parent == null) return "MCPForUnityTools";
505+
506+
// Walk up to find a distinctive parent (Assets/PackageName or Packages/PackageName)
507+
DirectoryInfo current = parent;
508+
while (current != null)
509+
{
510+
string name = current.Name;
511+
DirectoryInfo grandparent = current.Parent;
512+
513+
// Stop at Assets, Packages, or if we find a package-like structure
514+
if (grandparent != null &&
515+
(grandparent.Name.Equals("Assets", StringComparison.OrdinalIgnoreCase) ||
516+
grandparent.Name.Equals("Packages", StringComparison.OrdinalIgnoreCase)))
517+
{
518+
return $"{name}_MCPForUnityTools";
519+
}
520+
521+
current = grandparent;
522+
}
523+
524+
// Fallback: use immediate parent
525+
return $"{parent.Name}_MCPForUnityTools";
526+
}
527+
catch
528+
{
529+
return "MCPForUnityTools";
530+
}
531+
}
532+
400533
private static void CopyDirectoryRecursive(string sourceDir, string destinationDir)
401534
{
402535
Directory.CreateDirectory(destinationDir);
@@ -461,6 +594,10 @@ public static bool RebuildMcpServer()
461594
Directory.CreateDirectory(destRoot);
462595
CopyDirectoryRecursive(embeddedRoot, destRoot);
463596

597+
// Copy Unity project tools
598+
string destToolsDir = Path.Combine(destSrc, "tools");
599+
CopyUnityProjectTools(destToolsDir);
600+
464601
// Write version file
465602
string embeddedVer = ReadVersionFile(Path.Combine(embeddedSrc, VersionFileName)) ?? "unknown";
466603
try

0 commit comments

Comments
 (0)