/* ┌──────────────────────────────────────────────────────────────────┐ │ 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.ComponentModel; using System.Threading.Tasks; using com.IvanMurzak.McpPlugin; using com.IvanMurzak.McpPlugin.Common.Model; using com.IvanMurzak.ReflectorNet.Utils; using com.IvanMurzak.Unity.MCP.Editor.Utils; using UnityEditor.PackageManager; namespace com.IvanMurzak.Unity.MCP.Editor.API { public partial class Tool_Package { public const string PackageAddToolId = "package-add"; [AiTool ( PackageAddToolId, Title = "Package Manager / Add", OpenWorldHint = true, Enabled = false )] [AiSkillDescription("Install a Unity package from the registry, a Git URL, or a local path. " + "Modifies `manifest.json` and triggers package resolution; may also trigger a domain reload — the final " + "result is delivered after the reload via the request's `requestId`. " + "Use '" + PackageSearchToolId + "' / '" + PackageListToolId + "' for discovery first.")] [AiSkillBody("Install a package from the Unity Package Manager registry, Git URL, or local path. " + "This operation modifies the project's manifest.json and triggers package resolution. " + "Note: Package installation may trigger a domain reload. The result will be sent after the reload completes. " + "Use '" + PackageSearchToolId + "' tool to search for packages and '" + PackageListToolId + "' to list installed packages.\n\n" + "## `packageId` formats\n\n" + "- Plain package ID: `com.unity.textmeshpro` (installs latest compatible version).\n" + "- Pinned version: `com.unity.textmeshpro@3.0.6`.\n" + "- Git URL: `https://github.com/user/repo.git`.\n" + "- Git URL with branch/tag: `https://github.com/user/repo.git#v1.0.0`.\n" + "- Local path: `file:../MyPackage`.\n\n" + "## Processing model\n\n" + "Returns `Processing` immediately with the supplied `requestId`. Once `Client.Add` completes, a follow-up " + "result is delivered: success → `SchedulePostDomainReloadNotification` (delivers after the reload finishes), " + "failure → an immediate error notification with the underlying Unity error message.")] [Description("Install a package from the Unity Package Manager registry, Git URL, or local path. " + "This operation modifies the project's manifest.json and triggers package resolution. " + "Note: Package installation may trigger a domain reload. The result will be sent after the reload completes. " + "Use '" + PackageSearchToolId + "' tool to search for packages and '" + PackageListToolId + "' to list installed packages.")] public static ResponseCallTool Add ( [Description("The package ID to install. " + "Formats: Package ID 'com.unity.textmeshpro' (installs latest compatible version), " + "Package ID with version 'com.unity.textmeshpro@3.0.6', " + "Git URL 'https://github.com/user/repo.git', " + "Git URL with branch/tag 'https://github.com/user/repo.git#v1.0.0', " + "Local path 'file:../MyPackage'.")] string packageId, [RequestID] string? requestId = null ) { if (requestId == null || string.IsNullOrWhiteSpace(requestId)) return ResponseCallTool.Error("[Error] Original request with valid RequestID must be provided."); if (string.IsNullOrWhiteSpace(packageId)) return ResponseCallTool.Error(Error.PackageIdentifierIsEmpty()).SetRequestID(requestId); MainThread.Instance.RunAsync(async () => { await Task.Yield(); var addRequest = Client.Add(packageId); while (!addRequest.IsCompleted) await Task.Yield(); var success = addRequest.Status == StatusCode.Success; var packageName = success ? addRequest.Result.name : packageId; var version = success ? addRequest.Result.version : string.Empty; if (!success) { // If the operation failed immediately, send error response var errorMessage = Error.PackageOperationFailed("add", packageId, addRequest.Error?.message ?? "Unknown error"); _ = UnityMcpPluginEditor.NotifyToolRequestCompleted(new RequestToolCompletedData { RequestId = requestId, Result = ResponseCallTool.Error(errorMessage).SetRequestID(requestId) }); return; } // Schedule notification to be sent after domain reload completes var displayName = addRequest.Result.displayName ?? packageName; PackageUtils.SchedulePostDomainReloadNotification( requestId, $"{displayName} v{version}", "add", expectedResult: true ); }); return ResponseCallTool.Processing($"Adding package '{packageId}'. Waiting for package resolution and potential domain reload...").SetRequestID(requestId); } } }