diff --git a/MCPForUnity/Editor/Services/IPackageUpdateService.cs b/MCPForUnity/Editor/Services/IPackageUpdateService.cs
new file mode 100644
index 00000000..a9a14913
--- /dev/null
+++ b/MCPForUnity/Editor/Services/IPackageUpdateService.cs
@@ -0,0 +1,60 @@
+namespace MCPForUnity.Editor.Services
+{
+ ///
+ /// Service for checking package updates and version information
+ ///
+ public interface IPackageUpdateService
+ {
+ ///
+ /// Checks if a newer version of the package is available
+ ///
+ /// The current package version
+ /// Update check result containing availability and latest version info
+ UpdateCheckResult CheckForUpdate(string currentVersion);
+
+ ///
+ /// Compares two version strings to determine if the first is newer than the second
+ ///
+ /// First version string
+ /// Second version string
+ /// True if version1 is newer than version2
+ bool IsNewerVersion(string version1, string version2);
+
+ ///
+ /// Determines if the package was installed via Git or Asset Store
+ ///
+ /// True if installed via Git, false if Asset Store or unknown
+ bool IsGitInstallation();
+
+ ///
+ /// Clears the cached update check data, forcing a fresh check on next request
+ ///
+ void ClearCache();
+ }
+
+ ///
+ /// Result of an update check operation
+ ///
+ public class UpdateCheckResult
+ {
+ ///
+ /// Whether an update is available
+ ///
+ public bool UpdateAvailable { get; set; }
+
+ ///
+ /// The latest version available (null if check failed or no update)
+ ///
+ public string LatestVersion { get; set; }
+
+ ///
+ /// Whether the check was successful (false if network error, etc.)
+ ///
+ public bool CheckSucceeded { get; set; }
+
+ ///
+ /// Optional message about the check result
+ ///
+ public string Message { get; set; }
+ }
+}
diff --git a/MCPForUnity/Editor/Services/IPackageUpdateService.cs.meta b/MCPForUnity/Editor/Services/IPackageUpdateService.cs.meta
new file mode 100644
index 00000000..d9e68455
--- /dev/null
+++ b/MCPForUnity/Editor/Services/IPackageUpdateService.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e94ae28f193184e4fb5068f62f4f00c6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/MCPForUnity/Editor/Services/MCPServiceLocator.cs b/MCPForUnity/Editor/Services/MCPServiceLocator.cs
index ac286b1b..2a7f070c 100644
--- a/MCPForUnity/Editor/Services/MCPServiceLocator.cs
+++ b/MCPForUnity/Editor/Services/MCPServiceLocator.cs
@@ -13,6 +13,7 @@ public static class MCPServiceLocator
private static IPythonToolRegistryService _pythonToolRegistryService;
private static ITestRunnerService _testRunnerService;
private static IToolSyncService _toolSyncService;
+ private static IPackageUpdateService _packageUpdateService;
public static IBridgeControlService Bridge => _bridgeService ??= new BridgeControlService();
public static IClientConfigurationService Client => _clientService ??= new ClientConfigurationService();
@@ -20,6 +21,7 @@ public static class MCPServiceLocator
public static IPythonToolRegistryService PythonToolRegistry => _pythonToolRegistryService ??= new PythonToolRegistryService();
public static ITestRunnerService Tests => _testRunnerService ??= new TestRunnerService();
public static IToolSyncService ToolSync => _toolSyncService ??= new ToolSyncService();
+ public static IPackageUpdateService Updates => _packageUpdateService ??= new PackageUpdateService();
///
/// Registers a custom implementation for a service (useful for testing)
@@ -40,6 +42,8 @@ public static void Register(T implementation) where T : class
_testRunnerService = t;
else if (implementation is IToolSyncService ts)
_toolSyncService = ts;
+ else if (implementation is IPackageUpdateService pu)
+ _packageUpdateService = pu;
}
///
@@ -53,6 +57,7 @@ public static void Reset()
(_pythonToolRegistryService as IDisposable)?.Dispose();
(_testRunnerService as IDisposable)?.Dispose();
(_toolSyncService as IDisposable)?.Dispose();
+ (_packageUpdateService as IDisposable)?.Dispose();
_bridgeService = null;
_clientService = null;
@@ -60,6 +65,7 @@ public static void Reset()
_pythonToolRegistryService = null;
_testRunnerService = null;
_toolSyncService = null;
+ _packageUpdateService = null;
}
}
}
diff --git a/MCPForUnity/Editor/Services/PackageUpdateService.cs b/MCPForUnity/Editor/Services/PackageUpdateService.cs
new file mode 100644
index 00000000..7a5bc9f1
--- /dev/null
+++ b/MCPForUnity/Editor/Services/PackageUpdateService.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Net;
+using MCPForUnity.Editor.Helpers;
+using Newtonsoft.Json.Linq;
+using UnityEditor;
+
+namespace MCPForUnity.Editor.Services
+{
+ ///
+ /// Service for checking package updates from GitHub
+ ///
+ public class PackageUpdateService : IPackageUpdateService
+ {
+ private const string LastCheckDateKey = "MCPForUnity.LastUpdateCheck";
+ private const string CachedVersionKey = "MCPForUnity.LatestKnownVersion";
+ private const string PackageJsonUrl = "https://raw.githubusercontent.com/CoplayDev/unity-mcp/main/MCPForUnity/package.json";
+
+ ///
+ public UpdateCheckResult CheckForUpdate(string currentVersion)
+ {
+ // Check cache first - only check once per day
+ string lastCheckDate = EditorPrefs.GetString(LastCheckDateKey, "");
+ string cachedLatestVersion = EditorPrefs.GetString(CachedVersionKey, "");
+
+ if (lastCheckDate == DateTime.Now.ToString("yyyy-MM-dd") && !string.IsNullOrEmpty(cachedLatestVersion))
+ {
+ return new UpdateCheckResult
+ {
+ CheckSucceeded = true,
+ LatestVersion = cachedLatestVersion,
+ UpdateAvailable = IsNewerVersion(cachedLatestVersion, currentVersion),
+ Message = "Using cached version check"
+ };
+ }
+
+ // Don't check for Asset Store installations
+ if (!IsGitInstallation())
+ {
+ return new UpdateCheckResult
+ {
+ CheckSucceeded = false,
+ UpdateAvailable = false,
+ Message = "Asset Store installations are updated via Unity Asset Store"
+ };
+ }
+
+ // Fetch latest version from GitHub
+ string latestVersion = FetchLatestVersionFromGitHub();
+
+ if (!string.IsNullOrEmpty(latestVersion))
+ {
+ // Cache the result
+ EditorPrefs.SetString(LastCheckDateKey, DateTime.Now.ToString("yyyy-MM-dd"));
+ EditorPrefs.SetString(CachedVersionKey, latestVersion);
+
+ return new UpdateCheckResult
+ {
+ CheckSucceeded = true,
+ LatestVersion = latestVersion,
+ UpdateAvailable = IsNewerVersion(latestVersion, currentVersion),
+ Message = "Successfully checked for updates"
+ };
+ }
+
+ return new UpdateCheckResult
+ {
+ CheckSucceeded = false,
+ UpdateAvailable = false,
+ Message = "Failed to check for updates (network issue or offline)"
+ };
+ }
+
+ ///
+ public bool IsNewerVersion(string version1, string version2)
+ {
+ try
+ {
+ // Remove any "v" prefix
+ version1 = version1.TrimStart('v', 'V');
+ version2 = version2.TrimStart('v', 'V');
+
+ var version1Parts = version1.Split('.');
+ var version2Parts = version2.Split('.');
+
+ for (int i = 0; i < Math.Min(version1Parts.Length, version2Parts.Length); i++)
+ {
+ if (int.TryParse(version1Parts[i], out int v1Num) &&
+ int.TryParse(version2Parts[i], out int v2Num))
+ {
+ if (v1Num > v2Num) return true;
+ if (v1Num < v2Num) return false;
+ }
+ }
+ return false;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ public bool IsGitInstallation()
+ {
+ // Git packages are installed via Package Manager and have a package.json in Packages/
+ // Asset Store packages are in Assets/
+ string packageRoot = AssetPathUtility.GetMcpPackageRootPath();
+
+ if (string.IsNullOrEmpty(packageRoot))
+ {
+ return false;
+ }
+
+ // If the package is in Packages/ it's a PM install (likely Git)
+ // If it's in Assets/ it's an Asset Store install
+ return packageRoot.StartsWith("Packages/", StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ public void ClearCache()
+ {
+ EditorPrefs.DeleteKey(LastCheckDateKey);
+ EditorPrefs.DeleteKey(CachedVersionKey);
+ }
+
+ ///
+ /// Fetches the latest version from GitHub's main branch package.json
+ ///
+ private string FetchLatestVersionFromGitHub()
+ {
+ try
+ {
+ // GitHub API endpoint (Option 1 - has rate limits):
+ // https://api.github.com/repos/CoplayDev/unity-mcp/releases/latest
+ //
+ // We use Option 2 (package.json directly) because:
+ // - No API rate limits (GitHub serves raw files freely)
+ // - Simpler - just parse JSON for version field
+ // - More reliable - doesn't require releases to be published
+ // - Direct source of truth from the main branch
+
+ using (var client = new WebClient())
+ {
+ client.Headers.Add("User-Agent", "Unity-MCPForUnity-UpdateChecker");
+ string jsonContent = client.DownloadString(PackageJsonUrl);
+
+ var packageJson = JObject.Parse(jsonContent);
+ string version = packageJson["version"]?.ToString();
+
+ return string.IsNullOrEmpty(version) ? null : version;
+ }
+ }
+ catch (Exception ex)
+ {
+ // Silent fail - don't interrupt the user if network is unavailable
+ McpLog.Info($"Update check failed (this is normal if offline): {ex.Message}");
+ return null;
+ }
+ }
+ }
+}
diff --git a/MCPForUnity/Editor/Services/PackageUpdateService.cs.meta b/MCPForUnity/Editor/Services/PackageUpdateService.cs.meta
new file mode 100644
index 00000000..281cda95
--- /dev/null
+++ b/MCPForUnity/Editor/Services/PackageUpdateService.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7c3c2304b14e9485ca54182fad73b035
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/MCPForUnity/Editor/Windows/MCPForUnityEditorWindowNew.cs b/MCPForUnity/Editor/Windows/MCPForUnityEditorWindowNew.cs
index 39e8c156..fe58f6ea 100644
--- a/MCPForUnity/Editor/Windows/MCPForUnityEditorWindowNew.cs
+++ b/MCPForUnity/Editor/Windows/MCPForUnityEditorWindowNew.cs
@@ -242,7 +242,7 @@ private void CacheUIElements()
private void InitializeUI()
{
// Settings Section
- versionLabel.text = AssetPathUtility.GetPackageVersion();
+ UpdateVersionLabel();
debugLogsToggle.value = EditorPrefs.GetBool("MCPForUnity.DebugLogs", false);
validationLevelField.Init(ValidationLevel.Standard);
@@ -833,5 +833,28 @@ private void OnCopyJsonClicked()
EditorGUIUtility.systemCopyBuffer = configJsonField.value;
McpLog.Info("Configuration copied to clipboard");
}
+
+ private void UpdateVersionLabel()
+ {
+ string currentVersion = AssetPathUtility.GetPackageVersion();
+ versionLabel.text = $"v{currentVersion}";
+
+ // Check for updates using the service
+ var updateCheck = MCPServiceLocator.Updates.CheckForUpdate(currentVersion);
+
+ if (updateCheck.UpdateAvailable && !string.IsNullOrEmpty(updateCheck.LatestVersion))
+ {
+ // Update available - enhance the label
+ versionLabel.text = $"\u2191 v{currentVersion} (Update available: v{updateCheck.LatestVersion})";
+ versionLabel.style.color = new Color(1f, 0.7f, 0f); // Orange
+ versionLabel.tooltip = $"Version {updateCheck.LatestVersion} is available. Update via Package Manager.\n\nGit URL: https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity";
+ }
+ else
+ {
+ versionLabel.style.color = StyleKeyword.Null; // Default color
+ versionLabel.tooltip = $"Current version: {currentVersion}";
+ }
+ }
+
}
}
diff --git a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/PackageUpdateServiceTests.cs b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/PackageUpdateServiceTests.cs
new file mode 100644
index 00000000..8f2ee71a
--- /dev/null
+++ b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/PackageUpdateServiceTests.cs
@@ -0,0 +1,295 @@
+using System;
+using NUnit.Framework;
+using UnityEditor;
+using MCPForUnity.Editor.Services;
+
+namespace MCPForUnityTests.Editor.Services
+{
+ public class PackageUpdateServiceTests
+ {
+ private PackageUpdateService _service;
+ private const string TestLastCheckDateKey = "MCPForUnity.LastUpdateCheck";
+ private const string TestCachedVersionKey = "MCPForUnity.LatestKnownVersion";
+
+ [SetUp]
+ public void SetUp()
+ {
+ _service = new PackageUpdateService();
+
+ // Clean up any existing test data
+ CleanupEditorPrefs();
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ // Clean up test data
+ CleanupEditorPrefs();
+ }
+
+ private void CleanupEditorPrefs()
+ {
+ if (EditorPrefs.HasKey(TestLastCheckDateKey))
+ {
+ EditorPrefs.DeleteKey(TestLastCheckDateKey);
+ }
+ if (EditorPrefs.HasKey(TestCachedVersionKey))
+ {
+ EditorPrefs.DeleteKey(TestCachedVersionKey);
+ }
+ }
+
+ [Test]
+ public void IsNewerVersion_ReturnsTrue_WhenMajorVersionIsNewer()
+ {
+ bool result = _service.IsNewerVersion("2.0.0", "1.0.0");
+ Assert.IsTrue(result, "2.0.0 should be newer than 1.0.0");
+ }
+
+ [Test]
+ public void IsNewerVersion_ReturnsTrue_WhenMinorVersionIsNewer()
+ {
+ bool result = _service.IsNewerVersion("1.2.0", "1.1.0");
+ Assert.IsTrue(result, "1.2.0 should be newer than 1.1.0");
+ }
+
+ [Test]
+ public void IsNewerVersion_ReturnsTrue_WhenPatchVersionIsNewer()
+ {
+ bool result = _service.IsNewerVersion("1.0.2", "1.0.1");
+ Assert.IsTrue(result, "1.0.2 should be newer than 1.0.1");
+ }
+
+ [Test]
+ public void IsNewerVersion_ReturnsFalse_WhenVersionsAreEqual()
+ {
+ bool result = _service.IsNewerVersion("1.0.0", "1.0.0");
+ Assert.IsFalse(result, "Same versions should return false");
+ }
+
+ [Test]
+ public void IsNewerVersion_ReturnsFalse_WhenVersionIsOlder()
+ {
+ bool result = _service.IsNewerVersion("1.0.0", "2.0.0");
+ Assert.IsFalse(result, "1.0.0 should not be newer than 2.0.0");
+ }
+
+ [Test]
+ public void IsNewerVersion_HandlesVersionPrefix_v()
+ {
+ bool result = _service.IsNewerVersion("v2.0.0", "v1.0.0");
+ Assert.IsTrue(result, "Should handle 'v' prefix correctly");
+ }
+
+ [Test]
+ public void IsNewerVersion_HandlesVersionPrefix_V()
+ {
+ bool result = _service.IsNewerVersion("V2.0.0", "V1.0.0");
+ Assert.IsTrue(result, "Should handle 'V' prefix correctly");
+ }
+
+ [Test]
+ public void IsNewerVersion_HandlesMixedPrefixes()
+ {
+ bool result = _service.IsNewerVersion("v2.0.0", "1.0.0");
+ Assert.IsTrue(result, "Should handle mixed prefixes correctly");
+ }
+
+ [Test]
+ public void IsNewerVersion_ComparesCorrectly_WhenMajorDiffers()
+ {
+ bool result1 = _service.IsNewerVersion("10.0.0", "9.0.0");
+ bool result2 = _service.IsNewerVersion("2.0.0", "10.0.0");
+
+ Assert.IsTrue(result1, "10.0.0 should be newer than 9.0.0");
+ Assert.IsFalse(result2, "2.0.0 should not be newer than 10.0.0");
+ }
+
+ [Test]
+ public void IsNewerVersion_ReturnsFalse_OnInvalidVersionFormat()
+ {
+ // Service should handle errors gracefully
+ bool result = _service.IsNewerVersion("invalid", "1.0.0");
+ Assert.IsFalse(result, "Should return false for invalid version format");
+ }
+
+ [Test]
+ public void CheckForUpdate_ReturnsCachedVersion_WhenCacheIsValid()
+ {
+ // Arrange: Set up valid cache
+ string today = DateTime.Now.ToString("yyyy-MM-dd");
+ string cachedVersion = "5.5.5";
+ EditorPrefs.SetString(TestLastCheckDateKey, today);
+ EditorPrefs.SetString(TestCachedVersionKey, cachedVersion);
+
+ // Act
+ var result = _service.CheckForUpdate("5.0.0");
+
+ // Assert
+ Assert.IsTrue(result.CheckSucceeded, "Check should succeed with valid cache");
+ Assert.AreEqual(cachedVersion, result.LatestVersion, "Should return cached version");
+ Assert.IsTrue(result.UpdateAvailable, "Update should be available (5.5.5 > 5.0.0)");
+ }
+
+ [Test]
+ public void CheckForUpdate_DetectsUpdateAvailable_WhenNewerVersionCached()
+ {
+ // Arrange
+ string today = DateTime.Now.ToString("yyyy-MM-dd");
+ EditorPrefs.SetString(TestLastCheckDateKey, today);
+ EditorPrefs.SetString(TestCachedVersionKey, "6.0.0");
+
+ // Act
+ var result = _service.CheckForUpdate("5.0.0");
+
+ // Assert
+ Assert.IsTrue(result.UpdateAvailable, "Should detect update is available");
+ Assert.AreEqual("6.0.0", result.LatestVersion);
+ }
+
+ [Test]
+ public void CheckForUpdate_DetectsNoUpdate_WhenVersionsMatch()
+ {
+ // Arrange
+ string today = DateTime.Now.ToString("yyyy-MM-dd");
+ EditorPrefs.SetString(TestLastCheckDateKey, today);
+ EditorPrefs.SetString(TestCachedVersionKey, "5.0.0");
+
+ // Act
+ var result = _service.CheckForUpdate("5.0.0");
+
+ // Assert
+ Assert.IsFalse(result.UpdateAvailable, "Should detect no update needed");
+ Assert.AreEqual("5.0.0", result.LatestVersion);
+ }
+
+ [Test]
+ public void CheckForUpdate_DetectsNoUpdate_WhenCurrentVersionIsNewer()
+ {
+ // Arrange
+ string today = DateTime.Now.ToString("yyyy-MM-dd");
+ EditorPrefs.SetString(TestLastCheckDateKey, today);
+ EditorPrefs.SetString(TestCachedVersionKey, "5.0.0");
+
+ // Act
+ var result = _service.CheckForUpdate("6.0.0");
+
+ // Assert
+ Assert.IsFalse(result.UpdateAvailable, "Should detect no update when current is newer");
+ Assert.AreEqual("5.0.0", result.LatestVersion);
+ }
+
+ [Test]
+ public void CheckForUpdate_IgnoresExpiredCache_AndAttemptsFreshFetch()
+ {
+ // Arrange: Set cache from yesterday (expired)
+ string yesterday = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd");
+ string cachedVersion = "4.0.0";
+ EditorPrefs.SetString(TestLastCheckDateKey, yesterday);
+ EditorPrefs.SetString(TestCachedVersionKey, cachedVersion);
+
+ // Act
+ var result = _service.CheckForUpdate("5.0.0");
+
+ // Assert
+ Assert.IsNotNull(result, "Should return a result");
+
+ // If the check succeeded (network available), verify it didn't use the expired cache
+ if (result.CheckSucceeded)
+ {
+ Assert.AreNotEqual(cachedVersion, result.LatestVersion,
+ "Should not return expired cached version when fresh fetch succeeds");
+ Assert.IsNotNull(result.LatestVersion, "Should have fetched a new version");
+ }
+ else
+ {
+ // If offline, check should fail (not succeed with cached data)
+ Assert.IsFalse(result.UpdateAvailable,
+ "Should not report update available when fetch fails and cache is expired");
+ }
+ }
+
+ [Test]
+ public void CheckForUpdate_ReturnsAssetStoreMessage_ForNonGitInstallations()
+ {
+ // Note: This test verifies the service behavior when IsGitInstallation() returns false.
+ // Since the actual result depends on package installation method, we create a mock
+ // implementation to test this specific code path.
+
+ var mockService = new MockAssetStorePackageUpdateService();
+
+ // Act
+ var result = mockService.CheckForUpdate("5.0.0");
+
+ // Assert
+ Assert.IsFalse(result.CheckSucceeded, "Check should not succeed for Asset Store installs");
+ Assert.IsFalse(result.UpdateAvailable, "No update should be reported for Asset Store installs");
+ Assert.AreEqual("Asset Store installations are updated via Unity Asset Store", result.Message,
+ "Should return Asset Store update message");
+ Assert.IsNull(result.LatestVersion, "Latest version should be null for Asset Store installs");
+ }
+
+ [Test]
+ public void ClearCache_RemovesAllCachedData()
+ {
+ // Arrange: Set up cache
+ EditorPrefs.SetString(TestLastCheckDateKey, DateTime.Now.ToString("yyyy-MM-dd"));
+ EditorPrefs.SetString(TestCachedVersionKey, "5.0.0");
+
+ // Verify cache exists
+ Assert.IsTrue(EditorPrefs.HasKey(TestLastCheckDateKey), "Cache should exist before clearing");
+ Assert.IsTrue(EditorPrefs.HasKey(TestCachedVersionKey), "Cache should exist before clearing");
+
+ // Act
+ _service.ClearCache();
+
+ // Assert
+ Assert.IsFalse(EditorPrefs.HasKey(TestLastCheckDateKey), "Date cache should be cleared");
+ Assert.IsFalse(EditorPrefs.HasKey(TestCachedVersionKey), "Version cache should be cleared");
+ }
+
+ [Test]
+ public void ClearCache_DoesNotThrow_WhenNoCacheExists()
+ {
+ // Ensure no cache exists
+ CleanupEditorPrefs();
+
+ // Act & Assert - should not throw
+ Assert.DoesNotThrow(() => _service.ClearCache(), "Should not throw when clearing non-existent cache");
+ }
+ }
+
+ ///
+ /// Mock implementation of IPackageUpdateService that simulates Asset Store installation behavior
+ ///
+ internal class MockAssetStorePackageUpdateService : IPackageUpdateService
+ {
+ public UpdateCheckResult CheckForUpdate(string currentVersion)
+ {
+ // Simulate Asset Store installation (IsGitInstallation returns false)
+ return new UpdateCheckResult
+ {
+ CheckSucceeded = false,
+ UpdateAvailable = false,
+ Message = "Asset Store installations are updated via Unity Asset Store"
+ };
+ }
+
+ public bool IsNewerVersion(string version1, string version2)
+ {
+ // Not used in the Asset Store test, but required by interface
+ return false;
+ }
+
+ public bool IsGitInstallation()
+ {
+ // Simulate non-Git installation (Asset Store)
+ return false;
+ }
+
+ public void ClearCache()
+ {
+ // Not used in the Asset Store test, but required by interface
+ }
+ }
+}
diff --git a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/PackageUpdateServiceTests.cs.meta b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/PackageUpdateServiceTests.cs.meta
new file mode 100644
index 00000000..5d626f2f
--- /dev/null
+++ b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/PackageUpdateServiceTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 676c3849f71a84b17b14d813774d3f74
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs b/UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs
index 98a5295e..7beb4c5d 100644
--- a/UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs
+++ b/UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs
@@ -71,6 +71,9 @@ private void OnEnable()
// Load validation level setting
LoadValidationLevelSetting();
+ // Show one-time migration dialog
+ ShowMigrationDialogIfNeeded();
+
// First-run auto-setup only if Claude CLI is available
if (autoRegisterEnabled && !string.IsNullOrEmpty(ExecPath.ResolveClaude()))
{
@@ -170,6 +173,9 @@ private void OnGUI()
{
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
+ // Migration warning banner (non-dismissible)
+ DrawMigrationWarningBanner();
+
// Header
DrawHeader();
@@ -1573,6 +1579,65 @@ private void CheckClaudeCodeConfiguration(McpClient mcpClient)
}
}
+ private void ShowMigrationDialogIfNeeded()
+ {
+ const string dialogShownKey = "MCPForUnity.LegacyMigrationDialogShown";
+ if (EditorPrefs.GetBool(dialogShownKey, false))
+ {
+ return; // Already shown
+ }
+
+ int result = EditorUtility.DisplayDialogComplex(
+ "Migration Required",
+ "This is the legacy UnityMcpBridge package.\n\n" +
+ "Please migrate to the new MCPForUnity package to receive updates and support.\n\n" +
+ "Migration takes just a few minutes.",
+ "View Migration Guide",
+ "Remind Me Later",
+ "I'll Migrate Later"
+ );
+
+ if (result == 0) // View Migration Guide
+ {
+ Application.OpenURL("https://github.com/CoplayDev/unity-mcp/blob/main/docs/v5_MIGRATION.md");
+ EditorPrefs.SetBool(dialogShownKey, true);
+ }
+ else if (result == 2) // I'll Migrate Later
+ {
+ EditorPrefs.SetBool(dialogShownKey, true);
+ }
+ // result == 1 (Remind Me Later) - don't set the flag, show again next time
+ }
+
+ private void DrawMigrationWarningBanner()
+ {
+ // Warning banner - not dismissible, always visible
+ EditorGUILayout.Space(5);
+ Rect bannerRect = EditorGUILayout.GetControlRect(false, 50);
+ EditorGUI.DrawRect(bannerRect, new Color(1f, 0.6f, 0f, 0.3f)); // Orange background
+
+ GUIStyle warningStyle = new GUIStyle(EditorStyles.boldLabel)
+ {
+ fontSize = 13,
+ alignment = TextAnchor.MiddleLeft,
+ richText = true
+ };
+
+ // Use Unicode warning triangle (same as used elsewhere in codebase at line 647, 652)
+ string warningText = "\u26A0 LEGACY PACKAGE: Please migrate to MCPForUnity for updates and support.";
+
+ Rect textRect = new Rect(bannerRect.x + 15, bannerRect.y + 8, bannerRect.width - 180, bannerRect.height - 16);
+ GUI.Label(textRect, warningText, warningStyle);
+
+ // Button on the right
+ Rect buttonRect = new Rect(bannerRect.xMax - 160, bannerRect.y + 10, 145, 30);
+ if (GUI.Button(buttonRect, "View Migration Guide"))
+ {
+ Application.OpenURL("https://github.com/CoplayDev/unity-mcp/blob/main/docs/v5_MIGRATION.md");
+ }
+ EditorGUILayout.Space(5);
+ }
+
private bool IsPythonDetected()
{
try