using System.IO; using System.Linq; using MCPForUnity.Editor.Data; using MCPForUnity.Editor.Services; using UnityEditor; using UnityEngine; namespace MCPForUnity.Editor.Helpers { /// /// Automatically syncs Python tools to the MCP server when: /// - PythonToolsAsset is modified /// - Python files are imported/reimported /// - Unity starts up /// [InitializeOnLoad] public class PythonToolSyncProcessor : AssetPostprocessor { private const string SyncEnabledKey = "MCPForUnity.AutoSyncEnabled"; private static bool _isSyncing = false; static PythonToolSyncProcessor() { // Sync on Unity startup EditorApplication.delayCall += () => { if (IsAutoSyncEnabled()) { SyncAllTools(); } }; } /// /// Called after any assets are imported, deleted, or moved /// private static void OnPostprocessAllAssets( string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { // Prevent infinite loop - don't process if we're currently syncing if (_isSyncing || !IsAutoSyncEnabled()) return; bool needsSync = false; // Only check for .py file changes, not PythonToolsAsset changes // (PythonToolsAsset changes are internal state updates from syncing) foreach (string path in importedAssets.Concat(movedAssets)) { // Check if any .py files were modified if (path.EndsWith(".py")) { needsSync = true; break; } } // Check if any .py files were deleted if (!needsSync && deletedAssets.Any(path => path.EndsWith(".py"))) { needsSync = true; } if (needsSync) { SyncAllTools(); } } /// /// Syncs all Python tools from all PythonToolsAsset instances to the MCP server /// public static void SyncAllTools() { // Prevent re-entrant calls if (_isSyncing) { McpLog.Warn("Sync already in progress, skipping..."); return; } _isSyncing = true; try { if (!ServerPathResolver.TryFindEmbeddedServerSource(out string srcPath)) { McpLog.Warn("Cannot sync Python tools: MCP server source not found"); return; } string toolsDir = Path.Combine(srcPath, "tools", "custom"); var result = MCPServiceLocator.ToolSync.SyncProjectTools(toolsDir); if (result.Success) { if (result.CopiedCount > 0 || result.SkippedCount > 0) { McpLog.Info($"Python tools synced: {result.CopiedCount} copied, {result.SkippedCount} skipped"); } } else { McpLog.Error($"Python tool sync failed with {result.ErrorCount} errors"); foreach (var msg in result.Messages) { McpLog.Error($" - {msg}"); } } } catch (System.Exception ex) { McpLog.Error($"Python tool sync exception: {ex.Message}"); } finally { _isSyncing = false; } } /// /// Checks if auto-sync is enabled (default: true) /// public static bool IsAutoSyncEnabled() { return EditorPrefs.GetBool(SyncEnabledKey, true); } /// /// Enables or disables auto-sync /// public static void SetAutoSyncEnabled(bool enabled) { EditorPrefs.SetBool(SyncEnabledKey, enabled); McpLog.Info($"Python tool auto-sync {(enabled ? "enabled" : "disabled")}"); } /// /// Reimport all Python files in the project /// public static void ReimportPythonFiles() { // Find all Python files (imported as TextAssets by PythonFileImporter) var pythonGuids = AssetDatabase.FindAssets("t:TextAsset", new[] { "Assets" }) .Select(AssetDatabase.GUIDToAssetPath) .Where(path => path.EndsWith(".py", System.StringComparison.OrdinalIgnoreCase)) .ToArray(); foreach (string path in pythonGuids) { AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate); } int count = pythonGuids.Length; McpLog.Info($"Reimported {count} Python files"); AssetDatabase.Refresh(); } /// /// Manually trigger sync /// public static void ManualSync() { McpLog.Info("Manually syncing Python tools..."); SyncAllTools(); } /// /// Toggle auto-sync /// public static void ToggleAutoSync() { SetAutoSyncEnabled(!IsAutoSyncEnabled()); } /// /// Validate menu item (shows checkmark when enabled) /// public static bool ToggleAutoSyncValidate() { Menu.SetChecked("Window/MCP For Unity/Tool Sync/Auto-Sync Python Tools", IsAutoSyncEnabled()); return true; } } }