diff --git a/MCPForUnity/Editor/Services/IPackageUpdateService.cs b/MCPForUnity/Editor/Services/IPackageUpdateService.cs
index 9d4d2e487..9211c1c70 100644
--- a/MCPForUnity/Editor/Services/IPackageUpdateService.cs
+++ b/MCPForUnity/Editor/Services/IPackageUpdateService.cs
@@ -12,6 +12,23 @@ public interface IPackageUpdateService
/// Update check result containing availability and latest version info
UpdateCheckResult CheckForUpdate(string currentVersion);
+ ///
+ /// Returns a cached update result if one exists for today, or null if a network fetch is needed.
+ /// Main-thread only (reads EditorPrefs).
+ ///
+ UpdateCheckResult? TryGetCachedResult(string currentVersion);
+
+ ///
+ /// Performs only the network fetch and version comparison (no EditorPrefs access).
+ /// Safe to call from a background thread.
+ ///
+ UpdateCheckResult FetchAndCompare(string currentVersion);
+
+ ///
+ /// Caches a successful fetch result in EditorPrefs. Must be called from the main thread.
+ ///
+ void CacheFetchResult(string currentVersion, string fetchedVersion);
+
///
/// Compares two version strings to determine if the first is newer than the second
///
diff --git a/MCPForUnity/Editor/Services/PackageUpdateService.cs b/MCPForUnity/Editor/Services/PackageUpdateService.cs
index 5f594e9ca..dab04098c 100644
--- a/MCPForUnity/Editor/Services/PackageUpdateService.cs
+++ b/MCPForUnity/Editor/Services/PackageUpdateService.cs
@@ -14,6 +14,7 @@ namespace MCPForUnity.Editor.Services
///
public class PackageUpdateService : IPackageUpdateService
{
+ private const int DefaultRequestTimeoutMs = 3000;
private const string LastCheckDateKey = EditorPrefKeys.LastUpdateCheck;
private const string CachedVersionKey = EditorPrefKeys.LatestKnownVersion;
private const string LastBetaCheckDateKey = EditorPrefKeys.LastUpdateCheck + ".beta";
@@ -81,6 +82,88 @@ public UpdateCheckResult CheckForUpdate(string currentVersion)
};
}
+ ///
+ public UpdateCheckResult? TryGetCachedResult(string currentVersion)
+ {
+ bool isGitInstallation = IsGitInstallation();
+ string gitBranch = isGitInstallation ? GetGitUpdateBranch(currentVersion) : "main";
+ bool useBetaChannel = isGitInstallation && string.Equals(gitBranch, "beta", StringComparison.OrdinalIgnoreCase);
+
+ string lastCheckKey = isGitInstallation
+ ? (useBetaChannel ? LastBetaCheckDateKey : LastCheckDateKey)
+ : LastAssetStoreCheckDateKey;
+ string cachedVersionKey = isGitInstallation
+ ? (useBetaChannel ? CachedBetaVersionKey : CachedVersionKey)
+ : CachedAssetStoreVersionKey;
+
+ string lastCheckDate = EditorPrefs.GetString(lastCheckKey, "");
+ 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"
+ };
+ }
+
+ return null;
+ }
+
+ ///
+ public UpdateCheckResult FetchAndCompare(string currentVersion)
+ {
+ bool isGitInstallation = IsGitInstallation();
+ string gitBranch = isGitInstallation ? GetGitUpdateBranch(currentVersion) : "main";
+
+ string latestVersion = isGitInstallation
+ ? FetchLatestVersionFromGitHub(gitBranch)
+ : FetchLatestVersionFromAssetStoreJson();
+
+ if (!string.IsNullOrEmpty(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 = isGitInstallation
+ ? "Failed to check for updates (network issue or offline)"
+ : "Failed to check for Asset Store updates (network issue or offline)"
+ };
+ }
+
+ ///
+ public void CacheFetchResult(string currentVersion, string fetchedVersion)
+ {
+ if (string.IsNullOrEmpty(fetchedVersion)) return;
+
+ bool isGitInstallation = IsGitInstallation();
+ string gitBranch = isGitInstallation ? GetGitUpdateBranch(currentVersion) : "main";
+ bool useBetaChannel = isGitInstallation && string.Equals(gitBranch, "beta", StringComparison.OrdinalIgnoreCase);
+
+ string lastCheckKey = isGitInstallation
+ ? (useBetaChannel ? LastBetaCheckDateKey : LastCheckDateKey)
+ : LastAssetStoreCheckDateKey;
+ string cachedVersionKey = isGitInstallation
+ ? (useBetaChannel ? CachedBetaVersionKey : CachedVersionKey)
+ : CachedAssetStoreVersionKey;
+
+ EditorPrefs.SetString(lastCheckKey, DateTime.Now.ToString("yyyy-MM-dd"));
+ EditorPrefs.SetString(cachedVersionKey, fetchedVersion);
+ }
+
///
public bool IsNewerVersion(string version1, string version2)
{
@@ -265,7 +348,7 @@ protected virtual string FetchLatestVersionFromGitHub(string branch)
// - More reliable - doesn't require releases to be published
// - Direct source of truth from the main branch
- using (var client = new WebClient())
+ using (var client = CreateWebClient())
{
client.Headers.Add("User-Agent", "Unity-MCPForUnity-UpdateChecker");
string packageJsonUrl = string.Equals(branch, "beta", StringComparison.OrdinalIgnoreCase)
@@ -304,7 +387,7 @@ protected virtual string FetchLatestVersionFromAssetStoreJson()
{
try
{
- using (var client = new WebClient())
+ using (var client = CreateWebClient())
{
client.Headers.Add("User-Agent", "Unity-MCPForUnity-AssetStoreUpdateChecker");
string jsonContent = client.DownloadString(AssetStoreVersionUrl);
@@ -322,5 +405,41 @@ protected virtual string FetchLatestVersionFromAssetStoreJson()
return null;
}
}
+
+ protected virtual WebClient CreateWebClient()
+ {
+ return new TimeoutWebClient(GetRequestTimeoutMs());
+ }
+
+ protected virtual int GetRequestTimeoutMs()
+ {
+ return DefaultRequestTimeoutMs;
+ }
+
+ private sealed class TimeoutWebClient : WebClient
+ {
+ private readonly int _timeoutMs;
+
+ public TimeoutWebClient(int timeoutMs)
+ {
+ _timeoutMs = timeoutMs;
+ }
+
+ protected override WebRequest GetWebRequest(Uri address)
+ {
+ var request = base.GetWebRequest(address);
+ if (request != null)
+ {
+ request.Timeout = _timeoutMs;
+
+ if (request is HttpWebRequest httpRequest)
+ {
+ httpRequest.ReadWriteTimeout = _timeoutMs;
+ }
+ }
+
+ return request;
+ }
+ }
}
}
diff --git a/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs b/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
index bf41353a6..174177ffc 100644
--- a/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
+++ b/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
@@ -52,6 +52,7 @@ public class MCPForUnityEditorWindow : EditorWindow
private double lastRefreshTime = 0;
private const double RefreshDebounceSeconds = 0.5;
private bool updateCheckQueued = false;
+ private bool updateCheckInFlight = false;
private enum ActivePanel
{
@@ -352,7 +353,7 @@ private void UpdateVersionLabel()
private void QueueUpdateCheck()
{
- if (updateCheckQueued)
+ if (updateCheckQueued || updateCheckInFlight)
{
return;
}
@@ -377,23 +378,61 @@ private void CheckForPackageUpdates()
return;
}
- try
+ // Main thread: resolve service + read EditorPrefs cache (both require main thread)
+ var updateService = MCPServiceLocator.Updates;
+ var cachedResult = updateService.TryGetCachedResult(currentVersion);
+ if (cachedResult != null)
{
- var result = MCPServiceLocator.Updates.CheckForUpdate(currentVersion);
- if (result.CheckSucceeded && result.UpdateAvailable && !string.IsNullOrEmpty(result.LatestVersion))
+ ApplyUpdateCheckResult(cachedResult, currentVersion);
+ return;
+ }
+
+ // Background thread: network I/O only (no EditorPrefs access)
+ updateCheckInFlight = true;
+ Task.Run(() =>
+ {
+ try
{
- updateNotificationText.text = $"Update available: v{result.LatestVersion} (current: v{currentVersion})";
- updateNotificationText.tooltip = $"Latest version: v{result.LatestVersion}\nCurrent version: v{currentVersion}";
- updateNotification.AddToClassList("visible");
+ return updateService.FetchAndCompare(currentVersion);
}
- else
+ catch (Exception ex)
{
- updateNotification.RemoveFromClassList("visible");
+ McpLog.Info($"Package update check skipped: {ex.Message}");
+ return null;
}
+ }).ContinueWith(t =>
+ {
+ EditorApplication.delayCall += () =>
+ {
+ updateCheckInFlight = false;
+
+ // Main thread: cache the result in EditorPrefs
+ var result = t.Status == TaskStatus.RanToCompletion ? t.Result : null;
+ if (result != null && result.CheckSucceeded && !string.IsNullOrEmpty(result.LatestVersion))
+ {
+ updateService.CacheFetchResult(currentVersion, result.LatestVersion);
+ }
+
+ if (this == null || updateNotification == null || updateNotificationText == null)
+ {
+ return;
+ }
+
+ ApplyUpdateCheckResult(result, currentVersion);
+ };
+ }, TaskScheduler.Default);
+ }
+
+ private void ApplyUpdateCheckResult(UpdateCheckResult result, string currentVersion)
+ {
+ if (result != null && result.CheckSucceeded && result.UpdateAvailable && !string.IsNullOrEmpty(result.LatestVersion))
+ {
+ updateNotificationText.text = $"Update available: v{result.LatestVersion} (current: v{currentVersion})";
+ updateNotificationText.tooltip = $"Latest version: v{result.LatestVersion}\nCurrent version: v{currentVersion}";
+ updateNotification.AddToClassList("visible");
}
- catch (Exception ex)
+ else
{
- McpLog.Info($"Package update check skipped: {ex.Message}");
updateNotification.RemoveFromClassList("visible");
}
}