unity-mcp/MCPForUnity/Editor/Data/PythonToolsAsset.cs

107 lines
3.6 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.Linq;
using UnityEngine;
namespace MCPForUnity.Editor.Data
{
/// <summary>
/// Registry of Python tool files to sync to the MCP server.
/// Add your Python files here - they can be stored anywhere in your project.
/// </summary>
[CreateAssetMenu(fileName = "PythonTools", menuName = "MCP For Unity/Python Tools")]
public class PythonToolsAsset : ScriptableObject
{
[Tooltip("Add Python files (.py) to sync to the MCP server. Files can be located anywhere in your project.")]
public List<TextAsset> pythonFiles = new List<TextAsset>();
[Header("Sync Options")]
[Tooltip("Use content hashing to detect changes (recommended). If false, always copies on startup.")]
public bool useContentHashing = true;
[Header("Sync State (Read-only)")]
[Tooltip("Internal tracking - do not modify")]
public List<PythonFileState> fileStates = new List<PythonFileState>();
/// <summary>
/// Gets all valid Python files (filters out null/missing references)
/// </summary>
public IEnumerable<TextAsset> GetValidFiles()
{
return pythonFiles.Where(f => f != null);
}
/// <summary>
/// Checks if a file needs syncing
/// </summary>
public bool NeedsSync(TextAsset file, string currentHash)
{
if (!useContentHashing) return true; // Always sync if hashing disabled
var state = fileStates.FirstOrDefault(s => s.assetGuid == GetAssetGuid(file));
return state == null || state.contentHash != currentHash;
}
/// <summary>
/// Records that a file was synced
/// </summary>
public void RecordSync(TextAsset file, string hash)
{
string guid = GetAssetGuid(file);
var state = fileStates.FirstOrDefault(s => s.assetGuid == guid);
if (state == null)
{
state = new PythonFileState { assetGuid = guid };
fileStates.Add(state);
}
state.contentHash = hash;
state.lastSyncTime = DateTime.UtcNow;
state.fileName = file.name;
}
/// <summary>
/// Removes state entries for files no longer in the list
/// </summary>
public void CleanupStaleStates()
{
var validGuids = new HashSet<string>(GetValidFiles().Select(GetAssetGuid));
fileStates.RemoveAll(s => !validGuids.Contains(s.assetGuid));
}
private string GetAssetGuid(TextAsset asset)
{
return UnityEditor.AssetDatabase.AssetPathToGUID(UnityEditor.AssetDatabase.GetAssetPath(asset));
}
/// <summary>
/// Called when the asset is modified in the Inspector
/// Triggers sync to handle file additions/removals
/// </summary>
private void OnValidate()
{
// Cleanup stale states immediately
CleanupStaleStates();
// Trigger sync after a delay to handle file removals
// Delay ensures the asset is saved before sync runs
UnityEditor.EditorApplication.delayCall += () =>
{
if (this != null) // Check if asset still exists
{
MCPForUnity.Editor.Helpers.PythonToolSyncProcessor.SyncAllTools();
}
};
}
}
[Serializable]
public class PythonFileState
{
public string assetGuid;
public string fileName;
public string contentHash;
public DateTime lastSyncTime;
}
}