unity-mcp/MCPForUnity/Editor/Services/ToolSyncService.cs

135 lines
4.9 KiB
C#
Raw Normal View History

Allow users to easily add tools in the Asset folder (#324) * 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 } * Follow-up: address CodeRabbit feedback for #308 (<GetToolsFolderIdentifier was duplicated>) * Follow-up: address CodeRabbit feedback for #308 – centralize GetToolsFolderIdentifier, fix tools copy dir, and limit scan scope * Fixing so the MCP don't removes _skipDirs e.g. __pycache__ * skip empty folders with no py files * Rabbit: "Fix identifier collision between different package roots." * Update MCPForUnity/Editor/Helpers/ServerInstaller.cs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Rabbbit: Cleanup may delete server’s built-in tool subfolders — restrict to managed names. * Fixed minor + missed onadding rabit change * Revert "Fixed minor + missed onadding rabit change" This reverts commit 571ca8c5de3d07da3791dad558677909a07e886d. * refactor: remove Unity project tools copying and version tracking functionality * refactor: consolidate module discovery logic into shared utility function * Remove unused imports * feat: add Python tool registry and sync system for MCP server integration * feat: add auto-sync processor for Python tools with Unity editor integration * feat: add menu item to reimport all Python files in project Good to give users a manual option * Fix infinite loop error Don't react to PythonToolAsset changes - it only needs to react to Python file changes. And we also batch asset edits to minimise the DB refreshes * refactor: move Python tool sync menu items under Window/MCP For Unity/Tool Sync * Update docs * Remove duplicate header * feat: add OnValidate handler to sync Python tools when asset is modified This fixes the issue with deletions in the asset, now file removals are synced * test: add unit tests for Python tools asset and sync services * Update MCPForUnity/Editor/Helpers/PythonToolSyncProcessor.cs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * style: remove trailing whitespace from Python tool sync files * test: remove incomplete unit tests from ToolSyncServiceTests * perf: optimize Python file reimport by using AssetDatabase.FindAssets instead of GetAllAssetPaths --------- Co-authored-by: Johan Holtby <72528418+JohanHoltby@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-10-18 12:18:25 +08:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MCPForUnity.Editor.Helpers;
using UnityEditor;
namespace MCPForUnity.Editor.Services
{
public class ToolSyncService : IToolSyncService
{
private readonly IPythonToolRegistryService _registryService;
public ToolSyncService(IPythonToolRegistryService registryService = null)
{
_registryService = registryService ?? MCPServiceLocator.PythonToolRegistry;
}
public ToolSyncResult SyncProjectTools(string destToolsDir)
{
var result = new ToolSyncResult();
try
{
Directory.CreateDirectory(destToolsDir);
// Get all PythonToolsAsset instances in the project
var registries = _registryService.GetAllRegistries().ToList();
if (!registries.Any())
{
McpLog.Info("No PythonToolsAsset found. Create one via Assets > Create > MCP For Unity > Python Tools");
return result;
}
var syncedFiles = new HashSet<string>();
// Batch all asset modifications together to minimize reimports
AssetDatabase.StartAssetEditing();
try
{
foreach (var registry in registries)
{
foreach (var file in registry.GetValidFiles())
{
try
{
// Check if needs syncing (hash-based or always)
if (_registryService.NeedsSync(registry, file))
{
string destPath = Path.Combine(destToolsDir, file.name + ".py");
// Write the Python file content
File.WriteAllText(destPath, file.text);
// Record sync
_registryService.RecordSync(registry, file);
result.CopiedCount++;
syncedFiles.Add(destPath);
McpLog.Info($"Synced Python tool: {file.name}.py");
}
else
{
string destPath = Path.Combine(destToolsDir, file.name + ".py");
syncedFiles.Add(destPath);
result.SkippedCount++;
}
}
catch (Exception ex)
{
result.ErrorCount++;
result.Messages.Add($"Failed to sync {file.name}: {ex.Message}");
}
}
// Cleanup stale states in registry
registry.CleanupStaleStates();
EditorUtility.SetDirty(registry);
}
// Cleanup stale Python files in destination
CleanupStaleFiles(destToolsDir, syncedFiles);
}
finally
{
// End batch editing - this triggers a single asset refresh
AssetDatabase.StopAssetEditing();
}
// Save all modified registries
AssetDatabase.SaveAssets();
}
catch (Exception ex)
{
result.ErrorCount++;
result.Messages.Add($"Sync failed: {ex.Message}");
}
return result;
}
private void CleanupStaleFiles(string destToolsDir, HashSet<string> currentFiles)
{
try
{
if (!Directory.Exists(destToolsDir)) return;
// Find all .py files in destination that aren't in our current set
var existingFiles = Directory.GetFiles(destToolsDir, "*.py");
foreach (var file in existingFiles)
{
if (!currentFiles.Contains(file))
{
try
{
File.Delete(file);
McpLog.Info($"Cleaned up stale tool: {Path.GetFileName(file)}");
}
catch (Exception ex)
{
McpLog.Warn($"Failed to cleanup {file}: {ex.Message}");
}
}
}
}
catch (Exception ex)
{
McpLog.Warn($"Failed to cleanup stale files: {ex.Message}");
}
}
}
}