/* ┌──────────────────────────────────────────────────────────────────┐ │ Author: Ivan Murzak (https://github.com/IvanMurzak) │ │ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │ │ Copyright (c) 2025 Ivan Murzak │ │ Licensed under the Apache License, Version 2.0. │ │ See the LICENSE file in the project root for more information. │ └──────────────────────────────────────────────────────────────────┘ */ #nullable enable using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using com.IvanMurzak.McpPlugin; using com.IvanMurzak.ReflectorNet.Utils; using com.IvanMurzak.Unity.MCP.Editor.Utils; using AIGD; using com.IvanMurzak.Unity.MCP.Runtime.Extensions; using UnityObject = UnityEngine.Object; namespace com.IvanMurzak.Unity.MCP.Editor.API { public partial class Tool_Assets { public const string AssetsFindBuiltInToolId = "assets-find-built-in"; [AiTool ( AssetsFindBuiltInToolId, Title = "Assets / Find (Built-in)", ReadOnlyHint = true, IdempotentHint = true )] [AiSkillDescription("Search the built-in assets of the Unity Editor (located at " + ExtensionsRuntimeObject.UnityEditorBuiltInResourcesPath + "). " + "Filters by name and/or type; built-in assets have no GUID so GUID-based lookups are not supported.")] [AiSkillBody("Search the built-in assets of the Unity Editor located in the built-in resources: " + ExtensionsRuntimeObject.UnityEditorBuiltInResourcesPath + ". " + "Doesn't support GUIDs since built-in assets do not have them.\n\n" + "## Inputs\n\n" + "- `name` (optional) — case-insensitive name fragment. Underscores, hyphens, spaces, and periods " + "delimit search words so partial-word matching works.\n" + "- `type` (optional) — restrict results to assets assignable to this type (e.g. `UnityEngine.Texture2D`).\n" + "- `maxResults` — cap on returned list size (default 10).\n\n" + "## Ranking\n\n" + "Results are sorted by descending match quality: exact match → substring match → all-words match → " + "partial-words match. Within a rank, results are sorted alphabetically by filename for stable ordering.")] [Description("Search the built-in assets of the Unity Editor located in the built-in resources: " + ExtensionsRuntimeObject.UnityEditorBuiltInResourcesPath + ". " + "Doesn't support GUIDs since built-in assets do not have them.")] public List FindBuiltIn ( [Description("The name of the asset to filter by.")] string? name = null, [Description("The type of the asset to filter by.")] Type? type = null, [Description("Maximum number of assets to return. If the number of found assets exceeds this limit, the result will be truncated.")] int maxResults = 10 ) { if (maxResults <= 0) throw new ArgumentException($"{nameof(maxResults)} must be greater than zero."); var nameWords = string.IsNullOrEmpty(name) ? Array.Empty() : name!.Split(new[] { ' ', '_', '-', '.' }, StringSplitOptions.RemoveEmptyEntries); return MainThread.Instance.Run(() => { return BuiltInAssetCache.GetAllAssets() .Where(obj => obj != null && !string.IsNullOrEmpty(obj.name)) .Where(obj => type == null || type.IsAssignableFrom(obj.GetType())) .Select(obj => ToAssetObjectRef(obj)) .Select(assetRef => (assetRef, priority: GetMatchPriority(assetRef, name, nameWords))) .Where(x => x.priority >= 0) .OrderBy(x => x.priority) .ThenBy(x => GetAssetFileName(x.assetRef)) // Secondary sort for consistent ordering .Take(maxResults) .Select(x => x.assetRef) .ToList(); }); } private static string? GetAssetFileName(AssetObjectRef assetRef) { if (string.IsNullOrEmpty(assetRef.AssetPath)) return null; return System.IO.Path.GetFileNameWithoutExtension(assetRef.AssetPath); } private static int GetMatchPriority(AssetObjectRef assetRef, string? name, string[] nameWords) { if (string.IsNullOrEmpty(name)) return 0; // No name filter, all items have same priority var assetName = GetAssetFileName(assetRef); if (string.IsNullOrEmpty(assetName)) return -1; // No valid asset name if (assetName!.Equals(name, StringComparison.OrdinalIgnoreCase)) return 0; // Exact match - top priority if (assetName.Contains(name!, StringComparison.OrdinalIgnoreCase)) return 1; // Partial match - medium priority if (nameWords.Length > 0) { var matchedWords = nameWords.Count(word => assetName.Contains(word, StringComparison.OrdinalIgnoreCase)); if (matchedWords == nameWords.Length) return 2; // All words match - higher word match priority if (matchedWords > 0) return 3; // Some words match - lower word match priority } return -1; // No match } private static AssetObjectRef ToAssetObjectRef(UnityObject obj) { var assetObjRef = new AssetObjectRef(obj) { AssetGuid = null // Unity built-in assets do not have GUIDs }; // Distinguish built-in assets: if the path doesn't end with the asset name, append it. // This handles cases where multiple built-in assets map to "Resources/unity_builtin_extra". if (!string.IsNullOrEmpty(assetObjRef.AssetPath) && !assetObjRef.AssetPath!.EndsWith("/" + obj.name)) { var extension = BuiltInAssetCache.GetExtensionForAsset(obj); assetObjRef.AssetPath = $"{assetObjRef.AssetPath}/{obj.name}{extension}"; } return assetObjRef; } } }