diff --git a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
index 74fd836f7..a8f45d876 100644
--- a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
+++ b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
@@ -134,14 +134,14 @@ protected static bool IsBetaPackageSource(string packageSource)
return true;
// PyPI prerelease range: >=0.0.0a0 (used for prerelease package builds)
- if (packageSource.Contains(">=0.0.0a0", StringComparison.OrdinalIgnoreCase))
+ if (packageSource.IndexOf(">=0.0.0a0", StringComparison.OrdinalIgnoreCase) >= 0)
return true;
// Git-based beta references
- if (packageSource.Contains("@beta", StringComparison.OrdinalIgnoreCase))
+ if (packageSource.IndexOf("@beta", StringComparison.OrdinalIgnoreCase) >= 0)
return true;
- if (packageSource.Contains("-beta", StringComparison.OrdinalIgnoreCase))
+ if (packageSource.IndexOf("-beta", StringComparison.OrdinalIgnoreCase) >= 0)
return true;
return false;
diff --git a/MCPForUnity/Editor/Dependencies/PlatformDetectors/LinuxPlatformDetector.cs b/MCPForUnity/Editor/Dependencies/PlatformDetectors/LinuxPlatformDetector.cs
index be9db17f6..9bd18785e 100644
--- a/MCPForUnity/Editor/Dependencies/PlatformDetectors/LinuxPlatformDetector.cs
+++ b/MCPForUnity/Editor/Dependencies/PlatformDetectors/LinuxPlatformDetector.cs
@@ -182,7 +182,7 @@ protected string BuildAugmentedPath()
if (additions.Length == 0) return null;
// Only return the additions - ExecPath.TryRun will prepend to existing PATH
- return string.Join(Path.PathSeparator, additions);
+ return string.Join(Path.PathSeparator.ToString(), additions);
}
private string[] GetPathAdditions()
diff --git a/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs b/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs
index 2c06219f5..3b38e1f04 100644
--- a/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs
+++ b/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs
@@ -180,7 +180,7 @@ protected string BuildAugmentedPath()
if (additions.Length == 0) return null;
// Only return the additions - ExecPath.TryRun will prepend to existing PATH
- return string.Join(Path.PathSeparator, additions);
+ return string.Join(Path.PathSeparator.ToString(), additions);
}
private string[] GetPathAdditions()
diff --git a/MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs b/MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs
index 706e5030f..ff7d09a43 100644
--- a/MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs
+++ b/MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs
@@ -245,7 +245,7 @@ protected string BuildAugmentedPath()
if (additions.Length == 0) return null;
// Only return the additions - ExecPath.TryRun will prepend to existing PATH
- return string.Join(Path.PathSeparator, additions);
+ return string.Join(Path.PathSeparator.ToString(), additions);
}
private string[] GetPathAdditions()
diff --git a/MCPForUnity/Editor/External/Tommy.cs b/MCPForUnity/Editor/External/Tommy.cs
index 22e83b816..f71820878 100644
--- a/MCPForUnity/Editor/External/Tommy.cs
+++ b/MCPForUnity/Editor/External/Tommy.cs
@@ -517,7 +517,7 @@ internal void WriteTo(TextWriter tw, string name, bool writeSectionName)
if (collapsedItems.Count == 0)
return;
- var hasRealValues = !collapsedItems.All(n => n.Value is TomlTable { IsInline: false } or TomlArray { IsTableArray: true });
+ var hasRealValues = !collapsedItems.All(n => n.Value is TomlTable { IsInline: false } || n.Value is TomlArray { IsTableArray: true });
Comment?.AsComment(tw);
@@ -539,7 +539,7 @@ internal void WriteTo(TextWriter tw, string name, bool writeSectionName)
foreach (var collapsedItem in collapsedItems)
{
var key = collapsedItem.Key;
- if (collapsedItem.Value is TomlArray { IsTableArray: true } or TomlTable { IsInline: false })
+ if (collapsedItem.Value is TomlArray { IsTableArray: true } || collapsedItem.Value is TomlTable { IsInline: false })
{
if (!first) tw.WriteLine();
first = false;
@@ -819,7 +819,7 @@ public TomlTable Parse()
if (TomlSyntax.IsWhiteSpace(c) || c == TomlSyntax.NEWLINE_CARRIAGE_RETURN_CHARACTER)
goto consume_character;
- if (c is TomlSyntax.COMMENT_SYMBOL or TomlSyntax.NEWLINE_CHARACTER)
+ if (c == TomlSyntax.COMMENT_SYMBOL || c == TomlSyntax.NEWLINE_CHARACTER)
{
currentState = ParseState.None;
AdvanceLine();
@@ -1838,13 +1838,13 @@ internal static class TomlSyntax
public const string POS_INF_VALUE = "+inf";
public const string NEG_INF_VALUE = "-inf";
- public static bool IsBoolean(string s) => s is TRUE_VALUE or FALSE_VALUE;
+ public static bool IsBoolean(string s) => s == TRUE_VALUE || s == FALSE_VALUE;
- public static bool IsPosInf(string s) => s is INF_VALUE or POS_INF_VALUE;
+ public static bool IsPosInf(string s) => s == INF_VALUE || s == POS_INF_VALUE;
public static bool IsNegInf(string s) => s == NEG_INF_VALUE;
- public static bool IsNaN(string s) => s is NAN_VALUE or POS_NAN_VALUE or NEG_NAN_VALUE;
+ public static bool IsNaN(string s) => s == NAN_VALUE || s == POS_NAN_VALUE || s == NEG_NAN_VALUE;
public static bool IsInteger(string s) => IntegerPattern.IsMatch(s);
@@ -1863,25 +1863,25 @@ public static bool IsIntegerWithBase(string s, out int numberBase)
* A pattern to verify the integer value according to the TOML specification.
*/
public static readonly Regex IntegerPattern =
- new(@"^(\+|-)?(?!_)(0|(?!0)(_?\d)*)$", RegexOptions.Compiled);
+ new Regex(@"^(\+|-)?(?!_)(0|(?!0)(_?\d)*)$", RegexOptions.Compiled);
/**
* A pattern to verify a special 0x, 0o and 0b forms of an integer according to the TOML specification.
*/
public static readonly Regex BasedIntegerPattern =
- new(@"^0(?x|b|o)(?!_)(_?[0-9A-F])*$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ new Regex(@"^0(?x|b|o)(?!_)(_?[0-9A-F])*$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/**
* A pattern to verify the float value according to the TOML specification.
*/
public static readonly Regex FloatPattern =
- new(@"^(\+|-)?(?!_)(0|(?!0)(_?\d)+)(((e(\+|-)?(?!_)(_?\d)+)?)|(\.(?!_)(_?\d)+(e(\+|-)?(?!_)(_?\d)+)?))$",
+ new Regex(@"^(\+|-)?(?!_)(0|(?!0)(_?\d)+)(((e(\+|-)?(?!_)(_?\d)+)?)|(\.(?!_)(_?\d)+(e(\+|-)?(?!_)(_?\d)+)?))$",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/**
* A helper dictionary to map TOML base codes into the radii.
*/
- public static readonly Dictionary IntegerBases = new()
+ public static readonly Dictionary IntegerBases = new Dictionary()
{
["x"] = 16,
["o"] = 8,
@@ -1891,7 +1891,7 @@ public static bool IsIntegerWithBase(string s, out int numberBase)
/**
* A helper dictionary to map non-decimal bases to their TOML identifiers
*/
- public static readonly Dictionary BaseIdentifiers = new()
+ public static readonly Dictionary BaseIdentifiers = new Dictionary()
{
[2] = "b",
[8] = "o",
@@ -1962,29 +1962,29 @@ public static bool IsIntegerWithBase(string s, out int numberBase)
public static readonly char[] NewLineCharacters = { NEWLINE_CHARACTER, NEWLINE_CARRIAGE_RETURN_CHARACTER };
- public static bool IsQuoted(char c) => c is BASIC_STRING_SYMBOL or LITERAL_STRING_SYMBOL;
+ public static bool IsQuoted(char c) => c == BASIC_STRING_SYMBOL || c == LITERAL_STRING_SYMBOL;
- public static bool IsWhiteSpace(char c) => c is ' ' or '\t';
+ public static bool IsWhiteSpace(char c) => c == ' ' || c == '\t';
- public static bool IsNewLine(char c) => c is NEWLINE_CHARACTER or NEWLINE_CARRIAGE_RETURN_CHARACTER;
+ public static bool IsNewLine(char c) => c == NEWLINE_CHARACTER || c == NEWLINE_CARRIAGE_RETURN_CHARACTER;
public static bool IsLineBreak(char c) => c == NEWLINE_CHARACTER;
public static bool IsEmptySpace(char c) => IsWhiteSpace(c) || IsNewLine(c);
public static bool IsBareKey(char c) =>
- c is >= 'A' and <= 'Z' or >= 'a' and <= 'z' or >= '0' and <= '9' or '_' or '-';
+ (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-';
public static bool MustBeEscaped(char c, bool allowNewLines = false)
{
- var result = c is (>= '\u0000' and <= '\u0008') or '\u000b' or '\u000c' or (>= '\u000e' and <= '\u001f') or '\u007f';
+ var result = (c >= '\u0000' && c <= '\u0008') || c == '\u000b' || c == '\u000c' || (c >= '\u000e' && c <= '\u001f') || c == '\u007f';
if (!allowNewLines)
- result |= c is >= '\u000a' and <= '\u000e';
+ result |= c >= '\u000a' && c <= '\u000e';
return result;
}
public static bool IsValueSeparator(char c) =>
- c is ITEM_SEPARATOR or ARRAY_END_SYMBOL or INLINE_TABLE_END_SYMBOL;
+ c == ITEM_SEPARATOR || c == ARRAY_END_SYMBOL || c == INLINE_TABLE_END_SYMBOL;
#endregion
}
diff --git a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs
index 6daeab970..fcdd376b2 100644
--- a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs
+++ b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs
@@ -266,7 +266,7 @@ internal static string ResolveLocalServerPath(string path)
}
// If it looks like a PyPI package reference (no path separators), skip
- if (!path.Contains('/') && !path.Contains('\\') && !path.StartsWith("file:", StringComparison.OrdinalIgnoreCase))
+ if (!path.Contains("/") && !path.Contains("\\") && !path.StartsWith("file:", StringComparison.OrdinalIgnoreCase))
{
return path;
}
@@ -658,11 +658,11 @@ private static bool IsSemVerPreRelease(string version)
// Common semver prerelease indicators:
// e.g., "9.3.0-beta.1", "9.3.0-alpha", "9.3.0-rc.2", "9.3.0-preview"
- return version.Contains("-beta", StringComparison.OrdinalIgnoreCase) ||
- version.Contains("-alpha", StringComparison.OrdinalIgnoreCase) ||
- version.Contains("-rc", StringComparison.OrdinalIgnoreCase) ||
- version.Contains("-preview", StringComparison.OrdinalIgnoreCase) ||
- version.Contains("-pre", StringComparison.OrdinalIgnoreCase);
+ return version.IndexOf("-beta", StringComparison.OrdinalIgnoreCase) >= 0 ||
+ version.IndexOf("-alpha", StringComparison.OrdinalIgnoreCase) >= 0 ||
+ version.IndexOf("-rc", StringComparison.OrdinalIgnoreCase) >= 0 ||
+ version.IndexOf("-preview", StringComparison.OrdinalIgnoreCase) >= 0 ||
+ version.IndexOf("-pre", StringComparison.OrdinalIgnoreCase) >= 0;
}
}
}
diff --git a/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs b/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
index 45ae5a39e..196f05466 100644
--- a/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
+++ b/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
@@ -236,7 +236,7 @@ private static void EnsureRmcpClientFeature(TomlTable root)
{
if (root == null) return;
- if (!root.TryGetNode("features", out var featuresNode) || featuresNode is not TomlTable features)
+ if (!root.TryGetNode("features", out var featuresNode) || !(featuresNode is TomlTable features))
{
features = new TomlTable();
root["features"] = features;
diff --git a/MCPForUnity/Editor/Helpers/GameObjectLookup.cs b/MCPForUnity/Editor/Helpers/GameObjectLookup.cs
index 7ee58efc1..b09b4cdbc 100644
--- a/MCPForUnity/Editor/Helpers/GameObjectLookup.cs
+++ b/MCPForUnity/Editor/Helpers/GameObjectLookup.cs
@@ -3,7 +3,11 @@
using System.Linq;
using Newtonsoft.Json.Linq;
using UnityEditor;
+#if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement;
+#else
+using UnityEditor.Experimental.SceneManagement;
+#endif
using UnityEngine;
using UnityEngine.SceneManagement;
using MCPForUnity.Runtime.Helpers;
diff --git a/MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs b/MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs
index 94e2f3be9..ce727fa35 100644
--- a/MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs
+++ b/MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs
@@ -333,7 +333,7 @@ private static string NormalizeBaseUrl(string value, string defaultUrl, bool rem
// Strip trailing "/mcp" (case-insensitive) if provided.
if (trimmed.EndsWith("/mcp", StringComparison.OrdinalIgnoreCase))
{
- trimmed = trimmed[..^4];
+ trimmed = trimmed.Substring(0, trimmed.Length - 4);
}
// For local scope, force 127.0.0.1 over "localhost". Windows getaddrinfo returns ::1
diff --git a/MCPForUnity/Editor/Helpers/McpConfigurationHelper.cs b/MCPForUnity/Editor/Helpers/McpConfigurationHelper.cs
index 61eccb13b..7db45f742 100644
--- a/MCPForUnity/Editor/Helpers/McpConfigurationHelper.cs
+++ b/MCPForUnity/Editor/Helpers/McpConfigurationHelper.cs
@@ -37,7 +37,7 @@ public static string WriteMcpConfiguration(string configPath, McpClient mcpClien
}
catch { }
- JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
+ JsonSerializerSettings jsonSettings = new JsonSerializerSettings() { Formatting = Formatting.Indented };
// Read existing config if it exists
string existingJson = "{}";
diff --git a/MCPForUnity/Editor/Helpers/McpLogRecord.cs b/MCPForUnity/Editor/Helpers/McpLogRecord.cs
index f126a47c2..84b30bc5d 100644
--- a/MCPForUnity/Editor/Helpers/McpLogRecord.cs
+++ b/MCPForUnity/Editor/Helpers/McpLogRecord.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using System.IO;
using MCPForUnity.Editor.Constants;
using Newtonsoft.Json;
@@ -15,7 +16,7 @@ internal static class McpLogRecord
private static readonly string ErrorLogPath = Path.Combine(LogDir, "mcpError.log");
private const long MaxLogSizeBytes = 1024 * 1024; // 1 MB
private static bool _sessionStarted;
- private static readonly object _logLock = new();
+ private static readonly object _logLock = new object();
private static volatile bool _isEnabledCached;
[InitializeOnLoadMethod]
@@ -106,7 +107,7 @@ private static void RotateIfNeeded(string path)
var lines = File.ReadAllLines(path);
var half = lines.Length / 2;
- File.WriteAllLines(path, lines[half..]);
+ File.WriteAllLines(path, new ArraySegment(lines, half, lines.Length - half).ToArray());
}
catch
{
diff --git a/MCPForUnity/Editor/Helpers/PortManager.cs b/MCPForUnity/Editor/Helpers/PortManager.cs
index f9a2915c3..43ce861a7 100644
--- a/MCPForUnity/Editor/Helpers/PortManager.cs
+++ b/MCPForUnity/Editor/Helpers/PortManager.cs
@@ -336,7 +336,7 @@ private static string ComputeProjectHash(string input)
{
sb.Append(b.ToString("x2"));
}
- return sb.ToString()[..8]; // short, sufficient for filenames
+ return sb.ToString().Substring(0, 8); // short, sufficient for filenames
}
catch
{
diff --git a/MCPForUnity/Editor/Helpers/ProcessArgumentListCompat.cs b/MCPForUnity/Editor/Helpers/ProcessArgumentListCompat.cs
new file mode 100644
index 000000000..6a55825b6
--- /dev/null
+++ b/MCPForUnity/Editor/Helpers/ProcessArgumentListCompat.cs
@@ -0,0 +1,70 @@
+using System.Diagnostics;
+
+namespace MCPForUnity.Editor.Helpers
+{
+ ///
+ /// Unity 2020.3 compatibility shim: ProcessStartInfo.ArgumentList was introduced in
+ /// .NET Core 2.1 / netstandard2.1. On Unity 2020.3 (netstandard2.0) we emulate it
+ /// with ProcessStartInfo.Arguments, quoting each argument.
+ ///
+ public static class ProcessArgumentListCompat
+ {
+ /// Append one argument (quoted if it contains whitespace) — replaces ArgumentList.Add.
+ public static ProcessStartInfo AddArg(this ProcessStartInfo psi, string arg)
+ {
+ if (string.IsNullOrEmpty(psi.Arguments))
+ {
+ psi.Arguments = Quote(arg);
+ }
+ else
+ {
+ psi.Arguments += " " + Quote(arg);
+ }
+ return psi;
+ }
+
+ private static string Quote(string arg)
+ {
+ if (string.IsNullOrEmpty(arg))
+ {
+ return "\"\"";
+ }
+
+ // Only quote when necessary; otherwise pass through verbatim so
+ // backslashes in paths (e.g. C:\Program Files\...) are preserved.
+ if (arg.IndexOfAny(new[] { ' ', '\t', '"' }) < 0)
+ {
+ return arg;
+ }
+
+ // Standard Windows command-line quoting (the algorithm used by
+ // .NET's ArgumentList / CommandLineToArgvW): wrap in quotes; for every
+ // run of backslashes, double them only when immediately followed by a
+ // quote or at the very end of the argument (before the closing quote).
+ var sb = new System.Text.StringBuilder();
+ sb.Append('"');
+ int backslashes = 0;
+ foreach (char ch in arg)
+ {
+ if (ch == '\\')
+ {
+ backslashes++;
+ continue;
+ }
+ if (ch == '"')
+ {
+ sb.Append('\\', backslashes * 2 + 1);
+ sb.Append('"');
+ backslashes = 0;
+ continue;
+ }
+ sb.Append('\\', backslashes);
+ backslashes = 0;
+ sb.Append(ch);
+ }
+ sb.Append('\\', backslashes * 2);
+ sb.Append('"');
+ return sb.ToString();
+ }
+ }
+}
diff --git a/MCPForUnity/Editor/Helpers/ProcessArgumentListCompat.cs.meta b/MCPForUnity/Editor/Helpers/ProcessArgumentListCompat.cs.meta
new file mode 100644
index 000000000..3083dadd8
--- /dev/null
+++ b/MCPForUnity/Editor/Helpers/ProcessArgumentListCompat.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 19b085f18fec119419363e70edc91c9f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/MCPForUnity/Editor/Helpers/ProjectIdentityUtility.cs b/MCPForUnity/Editor/Helpers/ProjectIdentityUtility.cs
index 34a53916f..023754dbb 100644
--- a/MCPForUnity/Editor/Helpers/ProjectIdentityUtility.cs
+++ b/MCPForUnity/Editor/Helpers/ProjectIdentityUtility.cs
@@ -113,7 +113,7 @@ private static string ComputeProjectName(string dataPath)
projectPath = projectPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
if (projectPath.EndsWith("Assets", StringComparison.OrdinalIgnoreCase))
{
- projectPath = projectPath[..^6].TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
+ projectPath = projectPath.Substring(0, projectPath.Length - 6).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}
string name = Path.GetFileName(projectPath);
diff --git a/MCPForUnity/Editor/Helpers/UnityTypeResolver.cs b/MCPForUnity/Editor/Helpers/UnityTypeResolver.cs
index 7ed1bbe39..bc1c39888 100644
--- a/MCPForUnity/Editor/Helpers/UnityTypeResolver.cs
+++ b/MCPForUnity/Editor/Helpers/UnityTypeResolver.cs
@@ -18,8 +18,8 @@ namespace MCPForUnity.Editor.Helpers
///
public static class UnityTypeResolver
{
- private static readonly Dictionary CacheByFqn = new(StringComparer.Ordinal);
- private static readonly Dictionary CacheByName = new(StringComparer.Ordinal);
+ private static readonly Dictionary CacheByFqn = new Dictionary(StringComparer.Ordinal);
+ private static readonly Dictionary CacheByName = new Dictionary(StringComparer.Ordinal);
///
/// Resolves a type by name, with optional base type constraint.
@@ -150,7 +150,7 @@ private static void Cache(Type t)
private static List FindCandidates(string query, Type requiredBaseType)
{
- bool isShort = !query.Contains('.');
+ bool isShort = !query.Contains(".");
var loaded = UnityAssembliesCompat.GetLoadedAssemblies();
#if UNITY_EDITOR
@@ -166,9 +166,11 @@ private static List FindCandidates(string query, Type requiredBaseType)
var editorAsms = Array.Empty();
#endif
- Func match = isShort
- ? (t => t.Name.Equals(query, StringComparison.Ordinal))
- : (t => t.FullName?.Equals(query, StringComparison.Ordinal) ?? false);
+ Func match;
+ if (isShort)
+ match = t => t.Name.Equals(query, StringComparison.Ordinal);
+ else
+ match = t => t.FullName?.Equals(query, StringComparison.Ordinal) ?? false;
var fromPlayer = playerAsms.SelectMany(SafeGetTypes)
.Where(t => PassesConstraint(t, requiredBaseType))
diff --git a/MCPForUnity/Editor/Helpers/VectorParsing.cs b/MCPForUnity/Editor/Helpers/VectorParsing.cs
index 0e81cca88..f0e27a8b2 100644
--- a/MCPForUnity/Editor/Helpers/VectorParsing.cs
+++ b/MCPForUnity/Editor/Helpers/VectorParsing.cs
@@ -523,7 +523,7 @@ public static bool ValidateAnimationCurveFormat(JToken valueToken, out string me
for (int i = 0; i < keysArray.Count; i++)
{
var keyToken = keysArray[i];
- if (keyToken is not JObject keyObj)
+ if (!(keyToken is JObject keyObj))
{
message = $"Keyframe at index {i} must be an object with 'time' and 'value'.";
return false;
diff --git a/MCPForUnity/Editor/Models/McpClient.cs b/MCPForUnity/Editor/Models/McpClient.cs
index 85b30c2ce..932562936 100644
--- a/MCPForUnity/Editor/Models/McpClient.cs
+++ b/MCPForUnity/Editor/Models/McpClient.cs
@@ -22,7 +22,7 @@ public class McpClient
public string ServerContainerKey; // Top-level JSON container key for servers (null => "mcpServers"; Kilo => "mcp")
public string SchemaUrl; // Optional root "$schema" URL written into the config (e.g. Kilo's kilo.jsonc)
public string HttpUrlProperty = "url"; // The property name for the HTTP URL in the config
- public Dictionary DefaultUnityFields = new();
+ public Dictionary DefaultUnityFields = new Dictionary();
// Helper method to convert the enum to a display string
public string GetStatusDisplayString()
diff --git a/MCPForUnity/Editor/Resources/Editor/GetPrefabStage.cs b/MCPForUnity/Editor/Resources/Editor/GetPrefabStage.cs
index 28bd0a06e..810ff4b61 100644
--- a/MCPForUnity/Editor/Resources/Editor/GetPrefabStage.cs
+++ b/MCPForUnity/Editor/Resources/Editor/GetPrefabStage.cs
@@ -1,7 +1,11 @@
using System;
using MCPForUnity.Editor.Helpers;
using Newtonsoft.Json.Linq;
+#if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement;
+#else
+using UnityEditor.Experimental.SceneManagement;
+#endif
namespace MCPForUnity.Editor.Resources.Editor
{
diff --git a/MCPForUnity/Editor/Security/SecureKeyStore/EncryptedFileKeyStore.cs b/MCPForUnity/Editor/Security/SecureKeyStore/EncryptedFileKeyStore.cs
index 3774b3731..1cea906d9 100644
--- a/MCPForUnity/Editor/Security/SecureKeyStore/EncryptedFileKeyStore.cs
+++ b/MCPForUnity/Editor/Security/SecureKeyStore/EncryptedFileKeyStore.cs
@@ -3,6 +3,7 @@
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
+using MCPForUnity.Editor.Helpers;
namespace MCPForUnity.Editor.Security
{
@@ -92,6 +93,7 @@ private void DeriveKeys(out byte[] encKey, out byte[] macKey)
byte[] master = LoadOrCreate(Path.Combine(_dir, "secret.bin"), 32);
byte[] salt = LoadOrCreate(Path.Combine(_dir, "salt.bin"), 16);
string password = Convert.ToBase64String(master) + "|" + MachineId();
+#if UNITY_2021_2_OR_NEWER
using (var kdf = new Rfc2898DeriveBytes(password, salt, Iterations, HashAlgorithmName.SHA256))
{
byte[] material = kdf.GetBytes(64);
@@ -100,8 +102,56 @@ private void DeriveKeys(out byte[] encKey, out byte[] macKey)
Buffer.BlockCopy(material, 0, encKey, 0, 32);
Buffer.BlockCopy(material, 32, macKey, 0, 32);
}
+#else
+ // Unity 2020.3 (netstandard2.0 profile) has no 4-arg Rfc2898DeriveBytes overload, so
+ // implement PBKDF2-HMAC-SHA256 (RFC 2898) manually to keep key derivation
+ // byte-identical with the 2021.2+ path (a 3-arg SHA1 derivation would make
+ // existing ciphertext fail MAC validation).
+ byte[] material = Pbkdf2Sha256(password, salt, Iterations, 64);
+ encKey = new byte[32];
+ macKey = new byte[32];
+ Buffer.BlockCopy(material, 0, encKey, 0, 32);
+ Buffer.BlockCopy(material, 32, macKey, 0, 32);
+#endif
}
+#if !UNITY_2021_2_OR_NEWER
+ /// PBKDF2 with HMAC-SHA256 (RFC 2898), matching the .NET 4-arg Rfc2898DeriveBytes output.
+ private static byte[] Pbkdf2Sha256(string password, byte[] salt, int iterations, int numBytes)
+ {
+ var prf = new System.Security.Cryptography.HMACSHA256(
+ System.Text.Encoding.UTF8.GetBytes(password));
+ int hLen = prf.HashSize / 8;
+ int blocks = (numBytes + hLen - 1) / hLen;
+
+ var output = new byte[blocks * hLen];
+ var saltPlusOne = new byte[salt.Length + 4];
+
+ for (int block = 1; block <= blocks; block++)
+ {
+ Buffer.BlockCopy(salt, 0, saltPlusOne, 0, salt.Length);
+ saltPlusOne[salt.Length] = (byte)((block >> 24) & 0xFF);
+ saltPlusOne[salt.Length + 1] = (byte)((block >> 16) & 0xFF);
+ saltPlusOne[salt.Length + 2] = (byte)((block >> 8) & 0xFF);
+ saltPlusOne[salt.Length + 3] = (byte)(block & 0xFF);
+
+ byte[] u = prf.ComputeHash(saltPlusOne);
+ byte[] t = (byte[])u.Clone();
+ for (int i = 1; i < iterations; i++)
+ {
+ u = prf.ComputeHash(u);
+ for (int j = 0; j < hLen; j++) t[j] ^= u[j];
+ }
+ Buffer.BlockCopy(t, 0, output, (block - 1) * hLen, hLen);
+ }
+
+ prf.Dispose();
+ var result = new byte[numBytes];
+ Buffer.BlockCopy(output, 0, result, 0, numBytes);
+ return result;
+ }
+#endif
+
private byte[] Encrypt(byte[] plaintext)
{
DeriveKeys(out byte[] encKey, out byte[] macKey);
@@ -210,8 +260,8 @@ private static void TryChmod(string path, string mode)
RedirectStandardError = true,
RedirectStandardOutput = true,
};
- psi.ArgumentList.Add(mode);
- psi.ArgumentList.Add(path);
+ psi.AddArg(mode);
+ psi.AddArg(path);
using (var p = System.Diagnostics.Process.Start(psi)) p?.WaitForExit(2000);
}
catch { /* hardening is best-effort */ }
diff --git a/MCPForUnity/Editor/Security/SecureKeyStore/LinuxSecretToolKeyStore.cs b/MCPForUnity/Editor/Security/SecureKeyStore/LinuxSecretToolKeyStore.cs
index 96710e8ad..b14de1100 100644
--- a/MCPForUnity/Editor/Security/SecureKeyStore/LinuxSecretToolKeyStore.cs
+++ b/MCPForUnity/Editor/Security/SecureKeyStore/LinuxSecretToolKeyStore.cs
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
+using MCPForUnity.Editor.Helpers;
namespace MCPForUnity.Editor.Security
{
@@ -17,7 +18,7 @@ internal static bool IsAvailable()
try
{
var psi = NewPsi();
- psi.ArgumentList.Add("--version");
+ psi.AddArg("--version");
using (var p = Process.Start(psi))
{
p.WaitForExit(2000);
@@ -36,9 +37,9 @@ public bool TryGet(string providerId, out string apiKey)
try
{
var psi = NewPsi();
- psi.ArgumentList.Add("lookup");
- psi.ArgumentList.Add("service"); psi.ArgumentList.Add(Service);
- psi.ArgumentList.Add("account"); psi.ArgumentList.Add(providerId);
+ psi.AddArg("lookup");
+ psi.AddArg("service"); psi.AddArg(Service);
+ psi.AddArg("account"); psi.AddArg(providerId);
using (var p = Process.Start(psi))
{
string outp = p.StandardOutput.ReadToEnd();
@@ -58,10 +59,10 @@ public void Set(string providerId, string apiKey)
try
{
var psi = NewPsi(redirectIn: true);
- psi.ArgumentList.Add("store");
- psi.ArgumentList.Add("--label=MCPForUnity AssetGen");
- psi.ArgumentList.Add("service"); psi.ArgumentList.Add(Service);
- psi.ArgumentList.Add("account"); psi.ArgumentList.Add(providerId);
+ psi.AddArg("store");
+ psi.AddArg("--label=MCPForUnity AssetGen");
+ psi.AddArg("service"); psi.AddArg(Service);
+ psi.AddArg("account"); psi.AddArg(providerId);
using (var p = Process.Start(psi))
{
p.StandardInput.Write(apiKey);
@@ -78,9 +79,9 @@ public void Delete(string providerId)
try
{
var psi = NewPsi();
- psi.ArgumentList.Add("clear");
- psi.ArgumentList.Add("service"); psi.ArgumentList.Add(Service);
- psi.ArgumentList.Add("account"); psi.ArgumentList.Add(providerId);
+ psi.AddArg("clear");
+ psi.AddArg("service"); psi.AddArg(Service);
+ psi.AddArg("account"); psi.AddArg(providerId);
using (var p = Process.Start(psi)) p.WaitForExit(5000);
}
catch { /* best effort */ }
@@ -96,7 +97,7 @@ private static ProcessStartInfo NewPsi(bool redirectIn = false)
RedirectStandardError = true,
RedirectStandardInput = redirectIn,
};
- psi.ArgumentList.Add("secret-tool");
+ psi.AddArg("secret-tool");
return psi;
}
}
diff --git a/MCPForUnity/Editor/Security/SecureKeyStore/MacKeychainKeyStore.cs b/MCPForUnity/Editor/Security/SecureKeyStore/MacKeychainKeyStore.cs
index 9cf86249b..306209e30 100644
--- a/MCPForUnity/Editor/Security/SecureKeyStore/MacKeychainKeyStore.cs
+++ b/MCPForUnity/Editor/Security/SecureKeyStore/MacKeychainKeyStore.cs
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
+using MCPForUnity.Editor.Helpers;
namespace MCPForUnity.Editor.Security
{
@@ -49,7 +50,7 @@ private static (int code, string stdout, string stderr) Run(params string[] args
RedirectStandardOutput = true,
RedirectStandardError = true,
};
- foreach (string a in args) psi.ArgumentList.Add(a);
+ foreach (string a in args) psi.AddArg(a);
using (var p = Process.Start(psi))
{
string outp = p.StandardOutput.ReadToEnd();
diff --git a/MCPForUnity/Editor/Services/AssetGen/AssetGenJobManager.cs b/MCPForUnity/Editor/Services/AssetGen/AssetGenJobManager.cs
index e9fa9e674..d5f0a24a1 100644
--- a/MCPForUnity/Editor/Services/AssetGen/AssetGenJobManager.cs
+++ b/MCPForUnity/Editor/Services/AssetGen/AssetGenJobManager.cs
@@ -57,9 +57,9 @@ public static class AssetGenJobManager
internal static double PollIntervalSeconds = 3.0;
internal static double TimeoutSeconds = 600.0;
- private static readonly Dictionary Jobs = new();
- private static readonly Dictionary Runners = new();
- private static readonly List _tickIds = new();
+ private static readonly Dictionary Jobs = new Dictionary();
+ private static readonly Dictionary Runners = new Dictionary();
+ private static readonly List _tickIds = new List();
private static bool _ticking;
static AssetGenJobManager()
@@ -245,7 +245,7 @@ private sealed class Runner
public string Name;
public string Subfolder;
- public CancellationTokenSource Cts = new();
+ public CancellationTokenSource Cts = new CancellationTokenSource();
public RunnerPhase Phase = RunnerPhase.Submit;
public double StartedAt;
public double NextPollAt;
@@ -441,20 +441,20 @@ private static void Advance(Runner r)
// provider-controlled result URL (OverrideExt), so a rogue provider could otherwise land a
// .cs/.asmdef/.meta/.asset under Assets/ and get it compiled/imported on Refresh — Editor
// RCE. Anything outside these sets is rejected. Mirrors ModelImportPipeline's allowlist style.
- private static readonly HashSet AudioAllowedExtensions = new(StringComparer.OrdinalIgnoreCase)
+ private static readonly HashSet AudioAllowedExtensions = new HashSet(StringComparer.OrdinalIgnoreCase)
{
"wav", "mp3", "ogg", "aiff", "aif", "flac",
};
- private static readonly HashSet ImageAllowedExtensions = new(StringComparer.OrdinalIgnoreCase)
+ private static readonly HashSet ImageAllowedExtensions = new HashSet(StringComparer.OrdinalIgnoreCase)
{
"png", "jpg", "jpeg", "exr", "tga", "psd", "tiff", "webp", "gif", "bmp",
};
- private static readonly HashSet ModelAllowedExtensions = new(StringComparer.OrdinalIgnoreCase)
+ private static readonly HashSet ModelAllowedExtensions = new HashSet(StringComparer.OrdinalIgnoreCase)
{
"glb", "gltf", "fbx", "obj", "usd", "usdz", "dae", "ply", "stl", "zip",
};
// Fail closed: an unexpected kind allows nothing, so the RCE boundary never opens by default.
- private static readonly HashSet NoAllowedExtensions = new(StringComparer.OrdinalIgnoreCase);
+ private static readonly HashSet NoAllowedExtensions = new HashSet(StringComparer.OrdinalIgnoreCase);
///
/// Whether (no leading dot) is an allowed result extension for the
diff --git a/MCPForUnity/Editor/Services/AssetGen/Import/ModelImportPipeline.cs b/MCPForUnity/Editor/Services/AssetGen/Import/ModelImportPipeline.cs
index 02f1ab035..9c77c1df0 100644
--- a/MCPForUnity/Editor/Services/AssetGen/Import/ModelImportPipeline.cs
+++ b/MCPForUnity/Editor/Services/AssetGen/Import/ModelImportPipeline.cs
@@ -19,7 +19,7 @@ public static class ModelImportPipeline
// Inert asset types permitted out of an UNTRUSTED provider archive (Sketchfab et al.).
// Anything else — scripts, assemblies, asmdefs — is skipped on extraction so it can never
// compile or load inside the Editor. See SafeZipExtractor for the enforcement.
- private static readonly HashSet ArchiveAllowedExtensions = new(StringComparer.OrdinalIgnoreCase)
+ private static readonly HashSet ArchiveAllowedExtensions = new HashSet(StringComparer.OrdinalIgnoreCase)
{
".gltf", ".glb", ".bin", ".fbx", ".obj", ".mtl",
".png", ".jpg", ".jpeg", ".tga", ".bmp", ".tif", ".tiff", ".webp", ".exr", ".hdr",
diff --git a/MCPForUnity/Editor/Services/AssetGen/Providers/LocalImage.cs b/MCPForUnity/Editor/Services/AssetGen/Providers/LocalImage.cs
index ce81358f5..82b629813 100644
--- a/MCPForUnity/Editor/Services/AssetGen/Providers/LocalImage.cs
+++ b/MCPForUnity/Editor/Services/AssetGen/Providers/LocalImage.cs
@@ -13,7 +13,7 @@ namespace MCPForUnity.Editor.Services.AssetGen.Providers
internal static class LocalImage
{
// Extensions that can be inlined as a data URI for provider image input.
- private static readonly HashSet SupportedExtensions = new(StringComparer.OrdinalIgnoreCase)
+ private static readonly HashSet SupportedExtensions = new HashSet(StringComparer.OrdinalIgnoreCase)
{ ".png", ".jpg", ".jpeg", ".webp", ".gif" };
///
diff --git a/MCPForUnity/Editor/Services/EditorStateCache.cs b/MCPForUnity/Editor/Services/EditorStateCache.cs
index d02b26528..0be7a67bb 100644
--- a/MCPForUnity/Editor/Services/EditorStateCache.cs
+++ b/MCPForUnity/Editor/Services/EditorStateCache.cs
@@ -17,7 +17,7 @@ namespace MCPForUnity.Editor.Services
[InitializeOnLoad]
internal static class EditorStateCache
{
- private static readonly object LockObj = new();
+ private static readonly object LockObj = new object();
private static long _sequence;
private static long _observedUnixMs;
diff --git a/MCPForUnity/Editor/Services/IClientConfigurationService.cs b/MCPForUnity/Editor/Services/IClientConfigurationService.cs
index 6172e8fb3..748445a38 100644
--- a/MCPForUnity/Editor/Services/IClientConfigurationService.cs
+++ b/MCPForUnity/Editor/Services/IClientConfigurationService.cs
@@ -56,7 +56,7 @@ public class ClientConfigurationSummary
///
/// Detailed messages for each client
///
- public System.Collections.Generic.List Messages { get; set; } = new();
+ public System.Collections.Generic.List Messages { get; set; } = new System.Collections.Generic.List();
///
/// Gets a human-readable summary message
diff --git a/MCPForUnity/Editor/Services/PackageJobManager.cs b/MCPForUnity/Editor/Services/PackageJobManager.cs
index d2cba494a..1e918bf0a 100644
--- a/MCPForUnity/Editor/Services/PackageJobManager.cs
+++ b/MCPForUnity/Editor/Services/PackageJobManager.cs
@@ -31,8 +31,8 @@ internal static class PackageJobManager
private const int MaxJobsToKeep = 10;
private const long DomainReloadTimeoutMs = 120_000;
- private static readonly object LockObj = new();
- private static readonly Dictionary Jobs = new();
+ private static readonly object LockObj = new object();
+ private static readonly Dictionary Jobs = new Dictionary();
static PackageJobManager()
{
@@ -128,7 +128,7 @@ internal static void TryRecoverJob(PackageJob job, long nowMs)
try
{
string packageName = ExtractPackageName(job.Package);
- var allPackages = PackageInfo.GetAllRegisteredPackages();
+ var allPackages = RegisteredPackageInfo.GetRegisteredPackages();
var info = FindPackageInfo(allPackages, packageName, job.Package);
if (job.Operation == "add" || job.Operation == "embed")
@@ -179,7 +179,11 @@ internal static void TryRecoverJob(PackageJob job, long nowMs)
///
/// Find a PackageInfo by name, falling back to packageId or git/local source for non-standard identifiers.
///
- private static PackageInfo FindPackageInfo(PackageInfo[] allPackages, string packageName, string originalIdentifier)
+
+
+
+
+ private static RegisteredPackageInfo FindPackageInfo(RegisteredPackageInfo[] allPackages, string packageName, string originalIdentifier)
{
// Direct name match (handles normal com.company.package identifiers)
var info = allPackages.FirstOrDefault(p =>
@@ -200,7 +204,7 @@ private static PackageInfo FindPackageInfo(PackageInfo[] allPackages, string pac
return allPackages.FirstOrDefault(p =>
p.source == PackageSource.Git || p.source == PackageSource.Local
? p.packageId != null && p.packageId.Contains(originalIdentifier)
- || p.resolvedPath != null && p.resolvedPath.Contains(originalIdentifier)
+ || p.name != null && p.name.Contains(originalIdentifier)
: false);
}
diff --git a/MCPForUnity/Editor/Services/PackageUpdateService.cs b/MCPForUnity/Editor/Services/PackageUpdateService.cs
index 8c441d9b9..29d204e1d 100644
--- a/MCPForUnity/Editor/Services/PackageUpdateService.cs
+++ b/MCPForUnity/Editor/Services/PackageUpdateService.cs
@@ -281,7 +281,7 @@ private static bool IsPreReleaseVersion(string version)
return AssetPathUtility.IsPreReleaseVersion();
}
- return version.IndexOf('-', StringComparison.Ordinal) >= 0;
+ return version.IndexOf("-", StringComparison.Ordinal) >= 0;
}
///
diff --git a/MCPForUnity/Editor/Services/PathResolverService.cs b/MCPForUnity/Editor/Services/PathResolverService.cs
index c233d8680..d5acbf1f5 100644
--- a/MCPForUnity/Editor/Services/PathResolverService.cs
+++ b/MCPForUnity/Editor/Services/PathResolverService.cs
@@ -187,7 +187,7 @@ public bool TryValidateUvxExecutable(string uvxPath, out string version)
try
{
// Check if the path is just a command name (no directory separator)
- bool isBareCommand = !uvxPath.Contains('/') && !uvxPath.Contains('\\');
+ bool isBareCommand = !uvxPath.Contains("/") && !uvxPath.Contains("\\");
if (isBareCommand)
{
diff --git a/MCPForUnity/Editor/Services/RegisteredPackageInfo.cs b/MCPForUnity/Editor/Services/RegisteredPackageInfo.cs
new file mode 100644
index 000000000..cf6d04c13
--- /dev/null
+++ b/MCPForUnity/Editor/Services/RegisteredPackageInfo.cs
@@ -0,0 +1,221 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Newtonsoft.Json.Linq;
+using UnityEditor.PackageManager;
+
+namespace MCPForUnity.Editor.Services
+{
+ ///
+ /// Cross-version snapshot of an installed package.
+ ///
+ /// Unity 2021.2+ has PackageInfo.GetAllRegisteredPackages() (synchronous).
+ /// Unity 2020.3 has no synchronous "list all packages" API: PackageManager.Client.List
+ /// only completes on the main thread, so blocking on it from a tool handler would
+ /// deadlock the editor. Instead we read the authoritative Packages/packages-lock.json
+ /// (pure synchronous file I/O, no editor pumping required) and map entries here.
+ ///
+ public sealed class RegisteredPackageInfo
+ {
+ public string name;
+ public string version;
+ public string displayName;
+ public PackageSource source;
+ public string packageId;
+ public string description;
+ public string resolvedPath;
+ public AuthorInfo author;
+ public List dependencies = new List();
+
+ public struct AuthorInfo
+ {
+ public string name;
+ }
+
+ public struct DependencyInfo
+ {
+ public string name;
+ public string version;
+ }
+
+ /// Read the project's resolved package list without editor pumping.
+ public static RegisteredPackageInfo[] GetRegisteredPackages()
+ {
+#if UNITY_2021_2_OR_NEWER
+ return PackageInfo.GetAllRegisteredPackages()
+ .Select(p => new RegisteredPackageInfo
+ {
+ name = p.name,
+ version = p.version,
+ displayName = p.displayName,
+ source = p.source,
+ packageId = p.packageId,
+ description = p.description,
+ resolvedPath = p.resolvedPath,
+ author = p.author != null
+ ? new AuthorInfo { name = p.author.name }
+ : default,
+ dependencies = p.dependencies
+ .Select(d => new DependencyInfo { name = d.name, version = d.version })
+ .ToList()
+ })
+ .ToArray();
+#else
+ try
+ {
+ // Packages/packages-lock.json is maintained by Package Manager itself and
+ // contains every resolved package (direct + transitive) with version, source
+ // and direct dependencies. Reading it is synchronous and side-effect free.
+ string lockPath = Path.Combine(Directory.GetCurrentDirectory(), "Packages", "packages-lock.json");
+ if (!File.Exists(lockPath))
+ {
+ return new RegisteredPackageInfo[0];
+ }
+
+ JObject root = JObject.Parse(File.ReadAllText(lockPath));
+ JObject deps = root["dependencies"] as JObject;
+ if (deps == null)
+ {
+ return new RegisteredPackageInfo[0];
+ }
+
+ var result = new List();
+ foreach (var kv in deps)
+ {
+ JObject entry = kv.Value as JObject;
+ if (entry == null) continue;
+
+ var info = new RegisteredPackageInfo { name = kv.Key };
+ info.version = entry["version"]?.ToString() ?? string.Empty;
+ info.displayName = kv.Key;
+ PopulateMetadata(info);
+
+ string source = entry["source"]?.ToString() ?? "registry";
+ switch (source)
+ {
+ case "builtin": info.source = PackageSource.BuiltIn; break;
+ case "embedded": info.source = PackageSource.Embedded; break;
+ case "git": info.source = PackageSource.Git; break;
+ case "local": info.source = PackageSource.Local; break;
+ case "localTarball": info.source = PackageSource.LocalTarball; break;
+ case "tarball": info.source = PackageSource.LocalTarball; break;
+ default: info.source = PackageSource.Registry; break;
+ }
+
+ // Best-effort packageId: registry/git entries carry a url; local/embedded
+ // entries use "file:" + path when resolvable, otherwise name@version.
+ string url = entry["url"]?.ToString();
+ if (!string.IsNullOrEmpty(url))
+ {
+ info.packageId = info.source == PackageSource.Git
+ ? url + "#" + info.version
+ : url;
+ }
+ else
+ {
+ info.packageId = string.IsNullOrEmpty(info.version)
+ ? info.name
+ : info.name + "@" + info.version;
+ }
+
+ JObject depObj = entry["dependencies"] as JObject;
+ if (depObj != null)
+ {
+ foreach (var d in depObj)
+ {
+ info.dependencies.Add(new DependencyInfo
+ {
+ name = d.Key,
+ version = d.Value?.ToString() ?? string.Empty
+ });
+ }
+ }
+
+ result.Add(info);
+ }
+ return result.ToArray();
+ }
+ catch
+ {
+ return new RegisteredPackageInfo[0];
+ }
+#endif
+ }
+
+ ///
+ /// Best-effort metadata (description/author/resolvedPath) for 2020.3:
+ /// packages-lock.json does not carry them, so read the package's own
+ /// package.json when it exists on disk. Failures are non-fatal.
+ ///
+ private static void PopulateMetadata(RegisteredPackageInfo info)
+ {
+ try
+ {
+ string pkgJsonPath = null;
+ string localPath = Path.Combine(Directory.GetCurrentDirectory(), "Packages", info.name, "package.json");
+ if (File.Exists(localPath))
+ {
+ pkgJsonPath = localPath;
+ info.resolvedPath = Path.Combine(Directory.GetCurrentDirectory(), "Packages", info.name);
+ }
+ else
+ {
+ string cacheDir = Path.Combine(Directory.GetCurrentDirectory(), "Library", "PackageCache");
+ if (Directory.Exists(cacheDir))
+ {
+ string versioned = info.name + "@" + info.version;
+ string cachePath = Path.Combine(cacheDir, versioned, "package.json");
+ if (File.Exists(cachePath))
+ {
+ pkgJsonPath = cachePath;
+ info.resolvedPath = Path.Combine(cacheDir, versioned);
+ }
+ else
+ {
+ // PackageCache uses "-" suffixes for some sources; scan prefix match.
+ string prefix = info.name + "@";
+ foreach (string dir in Directory.GetDirectories(cacheDir))
+ {
+ if (dir.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ {
+ string cand = Path.Combine(dir, "package.json");
+ if (File.Exists(cand))
+ {
+ pkgJsonPath = cand;
+ info.resolvedPath = dir;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (pkgJsonPath != null)
+ {
+ JObject pkg = JObject.Parse(File.ReadAllText(pkgJsonPath));
+ info.description = pkg["description"]?.ToString() ?? string.Empty;
+ JObject authorObj = pkg["author"] as JObject;
+ if (authorObj != null)
+ {
+ info.author = new AuthorInfo { name = authorObj["name"]?.ToString() ?? string.Empty };
+ }
+ else
+ {
+ string authorStr = pkg["author"]?.ToString();
+ if (!string.IsNullOrEmpty(authorStr) && !authorStr.StartsWith("{"))
+ {
+ info.author = new AuthorInfo { name = authorStr };
+ }
+ }
+ }
+ }
+ catch
+ {
+ // Non-fatal: metadata stays empty.
+ }
+ }
+
+ }
+}
diff --git a/MCPForUnity/Editor/Services/RegisteredPackageInfo.cs.meta b/MCPForUnity/Editor/Services/RegisteredPackageInfo.cs.meta
new file mode 100644
index 000000000..66c9caa8a
--- /dev/null
+++ b/MCPForUnity/Editor/Services/RegisteredPackageInfo.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 37aa7fec72536814c81350a19073f9f3
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/MCPForUnity/Editor/Services/TestJobManager.cs b/MCPForUnity/Editor/Services/TestJobManager.cs
index bdf626036..2c07514e7 100644
--- a/MCPForUnity/Editor/Services/TestJobManager.cs
+++ b/MCPForUnity/Editor/Services/TestJobManager.cs
@@ -60,8 +60,8 @@ internal static class TestJobManager
private const string SessionKeyJobs = "MCPForUnity.TestJobsV1";
private const string SessionKeyCurrentJobId = "MCPForUnity.CurrentTestJobIdV1";
- private static readonly object LockObj = new();
- private static readonly Dictionary Jobs = new();
+ private static readonly object LockObj = new object();
+ private static readonly Dictionary Jobs = new Dictionary();
private static string _currentJobId;
private static long _lastPersistUnixMs;
diff --git a/MCPForUnity/Editor/Services/TestRunStatus.cs b/MCPForUnity/Editor/Services/TestRunStatus.cs
index da3ae6c26..c0db7f08d 100644
--- a/MCPForUnity/Editor/Services/TestRunStatus.cs
+++ b/MCPForUnity/Editor/Services/TestRunStatus.cs
@@ -9,7 +9,7 @@ namespace MCPForUnity.Editor.Services
///
internal static class TestRunStatus
{
- private static readonly object LockObj = new();
+ private static readonly object LockObj = new object();
private static bool _isRunning;
private static TestMode? _mode;
diff --git a/MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs b/MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs
index becc3bd88..1df42d7d2 100644
--- a/MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs
+++ b/MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs
@@ -62,8 +62,8 @@ public void TrySetCanceled()
}
}
- private static readonly Dictionary Pending = new();
- private static readonly object PendingLock = new();
+ private static readonly Dictionary Pending = new Dictionary();
+ private static readonly object PendingLock = new object();
private static bool updateHooked;
private static bool initialised;
@@ -303,7 +303,7 @@ private static void ProcessCommand(string id, PendingCommand pending)
{
status = "error",
error = "Invalid JSON format",
- receivedText = commandText.Length > 50 ? commandText[..50] + "..." : commandText
+ receivedText = commandText.Length > 50 ? commandText.Substring(0, 50) + "..." : commandText
};
pending.TrySetResult(JsonConvert.SerializeObject(invalidJsonResponse));
RemovePending(id, pending);
@@ -381,7 +381,7 @@ private static void ProcessCommand(string id, PendingCommand pending)
logStatus = "ERROR";
logError = t.Exception?.InnerException?.Message;
}
- else if (t.IsCompletedSuccessfully && t.Result != null)
+ else if (t.Status == TaskStatus.RanToCompletion && t.Result != null)
{
try
{
@@ -429,8 +429,9 @@ private static void CancelPending(string id, CancellationToken token)
PendingCommand pending = null;
lock (PendingLock)
{
- if (Pending.Remove(id, out pending))
+ if (Pending.TryGetValue(id, out pending))
{
+ Pending.Remove(id);
UnhookUpdateIfIdle();
}
}
diff --git a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs
index 4c0ff0b87..fb814d15a 100644
--- a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs
+++ b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs
@@ -34,10 +34,10 @@ public static class StdioBridgeHost
{
private static TcpListener listener;
private static bool isRunning = false;
- private static readonly object lockObj = new();
- private static readonly object startStopLock = new();
- private static readonly object clientsLock = new();
- private static readonly HashSet activeClients = new();
+ private static readonly object lockObj = new object();
+ private static readonly object startStopLock = new object();
+ private static readonly object clientsLock = new object();
+ private static readonly HashSet activeClients = new HashSet();
private static CancellationTokenSource cts;
private static Task listenerTask;
private static int processingCommands = 0;
@@ -53,7 +53,7 @@ public static class StdioBridgeHost
// stale leftover from an abandoned retry and a fresh window is started (#1173).
private const double PortBusyStaleResetSeconds = 60.0;
private static int heartbeatSeq = 0;
- private static Dictionary commandQueue = new();
+ private static Dictionary commandQueue = new Dictionary();
private static int mainThreadId;
private static int currentUnityPort = 6400;
private static bool isAutoConnectMode = false;
@@ -138,7 +138,7 @@ public static bool FolderExists(string path)
string fullPath = Path.Combine(
Application.dataPath,
- path.StartsWith("Assets/") ? path[7..] : path
+ path.StartsWith("Assets/") ? path.Substring(7) : path
);
return Directory.Exists(fullPath);
}
@@ -920,7 +920,7 @@ private static void ProcessCommands()
status = "error",
error = "Invalid JSON format",
receivedText = commandText.Length > 50
- ? commandText[..50] + "..."
+ ? commandText.Substring(0, 50) + "..."
: commandText,
};
tcs.SetResult(JsonConvert.SerializeObject(invalidJsonResponse));
@@ -964,7 +964,7 @@ async void Runner()
status = "error",
error = ex.Message,
receivedText = payload?.Length > 50
- ? payload[..50] + "..."
+ ? payload.Substring(0, 50) + "..."
: payload,
};
completionSource.TrySetResult(JsonConvert.SerializeObject(response));
@@ -1133,7 +1133,7 @@ private static string ComputeProjectHash(string input)
{
sb.Append(b.ToString("x2"));
}
- return sb.ToString()[..8];
+ return sb.ToString().Substring(0, 8);
}
catch
{
diff --git a/MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs b/MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs
index 8aaf2249e..7f5828ee1 100644
--- a/MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs
+++ b/MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs
@@ -1,5 +1,4 @@
using System;
-using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Net.WebSockets;
@@ -45,7 +44,7 @@ public class WebSocketTransportClient : IMcpTransportClient, IDisposable
private CancellationTokenSource _connectionCts;
private Task _receiveTask;
private Task _keepAliveTask;
- private readonly SemaphoreSlim _sendLock = new(1, 1);
+ private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
private Uri _endpointUri;
private string _sessionId;
@@ -415,7 +414,7 @@ private async Task ReceiveMessageAsync(CancellationToken token)
return null;
}
- byte[] rentedBuffer = System.Buffers.ArrayPool.Shared.Rent(8192);
+ var rentedBuffer = new byte[8192];
var buffer = new ArraySegment(rentedBuffer);
using var ms = new MemoryStream(8192);
@@ -451,7 +450,6 @@ private async Task ReceiveMessageAsync(CancellationToken token)
}
finally
{
- System.Buffers.ArrayPool.Shared.Return(rentedBuffer);
}
}
diff --git a/MCPForUnity/Editor/Setup/McpForUnitySkillInstaller.cs b/MCPForUnity/Editor/Setup/McpForUnitySkillInstaller.cs
index 5d6709368..329e912b9 100644
--- a/MCPForUnity/Editor/Setup/McpForUnitySkillInstaller.cs
+++ b/MCPForUnity/Editor/Setup/McpForUnitySkillInstaller.cs
@@ -25,8 +25,8 @@ public class McpForUnitySkillInstaller : EditorWindow
private string _installDir;
private Vector2 _scroll;
private volatile bool _isRunning;
- private readonly ConcurrentQueue _pendingLogs = new();
- private readonly StringBuilder _logBuilder = new(4096);
+ private readonly ConcurrentQueue _pendingLogs = new ConcurrentQueue();
+ private readonly StringBuilder _logBuilder = new StringBuilder(4096);
public static void OpenWindow()
{
diff --git a/MCPForUnity/Editor/Setup/SkillSyncService.cs b/MCPForUnity/Editor/Setup/SkillSyncService.cs
index 745d05856..1eeb73dfc 100644
--- a/MCPForUnity/Editor/Setup/SkillSyncService.cs
+++ b/MCPForUnity/Editor/Setup/SkillSyncService.cs
@@ -513,7 +513,7 @@ internal static Dictionary ListFiles(string root)
var normalizedRoot = Path.GetFullPath(root);
foreach (var filePath in Directory.GetFiles(normalizedRoot, "*", SearchOption.AllDirectories))
{
- var relativePath = Path.GetRelativePath(normalizedRoot, filePath).Replace('\\', '/');
+ var relativePath = MakeRelativePath(normalizedRoot, filePath).Replace('\\', '/');
if (string.Equals(relativePath, SyncOwnershipMarker, StringComparison.OrdinalIgnoreCase))
{
continue;
@@ -808,9 +808,19 @@ internal sealed class GitHubTreeEntry
internal sealed class SyncPlan
{
- public List Added { get; } = new();
- public List Updated { get; } = new();
- public List Deleted { get; } = new();
+ public List Added { get; } = new List();
+ public List Updated { get; } = new List();
+ public List Deleted { get; } = new List();
+ }
+
+ private static string MakeRelativePath(string root, string fullPath)
+ {
+ string rootNorm = root.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
+ if (fullPath.StartsWith(rootNorm, StringComparison.OrdinalIgnoreCase))
+ {
+ return fullPath.Substring(rootNorm.Length);
+ }
+ return fullPath;
}
}
}
diff --git a/MCPForUnity/Editor/Tools/Animation/ClipCreate.cs b/MCPForUnity/Editor/Tools/Animation/ClipCreate.cs
index b091dce37..8f131e00a 100644
--- a/MCPForUnity/Editor/Tools/Animation/ClipCreate.cs
+++ b/MCPForUnity/Editor/Tools/Animation/ClipCreate.cs
@@ -251,7 +251,7 @@ public static object SetVectorCurve(JObject @params)
string relativePath = @params["relativePath"]?.ToString() ?? "";
JToken keysToken = @params["keys"];
- if (keysToken == null || keysToken is not JArray keysArray || keysArray.Count == 0)
+ if (keysToken == null || !(keysToken is JArray keysArray) || keysArray.Count == 0)
return new { success = false, message = "'keys' is required. Use [{\"time\":0,\"value\":[0,1,0]},...]" };
// Map property group to axis suffixes
@@ -281,12 +281,12 @@ public static object SetVectorCurve(JObject @params)
foreach (var item in keysArray)
{
- if (item is not JObject keyObj)
+ if (!(item is JObject keyObj))
return new { success = false, message = "Each key must be an object with 'time' and 'value' (Vector3 array)" };
float time = keyObj["time"]?.ToObject() ?? 0f;
JToken valueToken = keyObj["value"];
- if (valueToken is not JArray valArray || valArray.Count < 3)
+ if (!(valueToken is JArray valArray) || valArray.Count < 3)
return new { success = false, message = $"Key at time {time}: 'value' must be a 3-element array [x, y, z]" };
float vx = valArray[0].ToObject();
diff --git a/MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs b/MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs
index 328c18d78..76e786188 100644
--- a/MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs
+++ b/MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs
@@ -180,7 +180,7 @@ public static object AddTransition(JObject @params)
{
foreach (var condItem in conditionsArray)
{
- if (condItem is not JObject condObj) continue;
+ if (!(condItem is JObject condObj)) continue;
string paramName = condObj["parameter"]?.ToString();
if (string.IsNullOrEmpty(paramName)) continue;
diff --git a/MCPForUnity/Editor/Tools/AssetGen/AssetGenToolHelpers.cs b/MCPForUnity/Editor/Tools/AssetGen/AssetGenToolHelpers.cs
index e2955f82a..eb5565d5d 100644
--- a/MCPForUnity/Editor/Tools/AssetGen/AssetGenToolHelpers.cs
+++ b/MCPForUnity/Editor/Tools/AssetGen/AssetGenToolHelpers.cs
@@ -47,7 +47,7 @@ public static object Cancel(ToolParams p)
string jobId = p.Get("job_id");
if (string.IsNullOrEmpty(jobId)) return new ErrorResponse("'job_id' is required for cancel.");
return AssetGenJobManager.Cancel(jobId)
- ? new SuccessResponse($"Cancel requested for job '{jobId}'.")
+ ? (IMcpResponse)new SuccessResponse($"Cancel requested for job '{jobId}'.")
: new ErrorResponse($"No cancelable job found with ID '{jobId}'.");
}
diff --git a/MCPForUnity/Editor/Tools/AssetGen/ImportModel.cs b/MCPForUnity/Editor/Tools/AssetGen/ImportModel.cs
index 0f5103692..266a84310 100644
--- a/MCPForUnity/Editor/Tools/AssetGen/ImportModel.cs
+++ b/MCPForUnity/Editor/Tools/AssetGen/ImportModel.cs
@@ -144,7 +144,7 @@ private static object Cancel(ToolParams p)
string jobId = p.Get("job_id");
if (string.IsNullOrEmpty(jobId)) return new ErrorResponse("'job_id' is required for cancel.");
return AssetGenJobManager.Cancel(jobId)
- ? new SuccessResponse($"Cancel requested for job '{jobId}'.")
+ ? (IMcpResponse)new SuccessResponse($"Cancel requested for job '{jobId}'.")
: new ErrorResponse($"No cancelable job found with ID '{jobId}'.");
}
diff --git a/MCPForUnity/Editor/Tools/BatchExecute.cs b/MCPForUnity/Editor/Tools/BatchExecute.cs
index aca4a1994..ff49bbf31 100644
--- a/MCPForUnity/Editor/Tools/BatchExecute.cs
+++ b/MCPForUnity/Editor/Tools/BatchExecute.cs
@@ -28,7 +28,7 @@ public static class BatchExecute
internal static int GetMaxCommandsPerBatch()
{
int configured = EditorPrefs.GetInt(EditorPrefKeys.BatchExecuteMaxCommands, DefaultMaxCommandsPerBatch);
- return Math.Clamp(configured, 1, AbsoluteMaxCommandsPerBatch);
+ return UnityEngine.Mathf.Clamp(configured, 1, AbsoluteMaxCommandsPerBatch);
}
public static async Task
public static class BuildJobStore
{
- private static readonly Dictionary _buildJobs = new();
- private static readonly Dictionary _batchJobs = new();
+ private static readonly Dictionary _buildJobs = new Dictionary();
+ private static readonly Dictionary _batchJobs = new Dictionary();
private static BuildJob _lastCompletedJob;
public static string CreateJobId() => $"build-{Guid.NewGuid():N}".Substring(0, 16);
diff --git a/MCPForUnity/Editor/Tools/Build/BuildRunner.cs b/MCPForUnity/Editor/Tools/Build/BuildRunner.cs
index baed78da6..82161914d 100644
--- a/MCPForUnity/Editor/Tools/Build/BuildRunner.cs
+++ b/MCPForUnity/Editor/Tools/Build/BuildRunner.cs
@@ -69,7 +69,9 @@ public static BuildPlayerOptions CreateBuildOptions(
|| target == BuildTarget.StandaloneOSX
|| target == BuildTarget.StandaloneLinux64)
{
+#if UNITY_2021_2_OR_NEWER
options.subtarget = subtarget;
+#endif
}
return options;
@@ -87,7 +89,11 @@ public static BuildOptions ParseBuildOptions(string[] optionNames, bool developm
{
switch (name.ToLowerInvariant())
{
- case "clean_build": opts |= BuildOptions.CleanBuildCache; break;
+ case "clean_build":
+#if UNITY_2021_2_OR_NEWER
+ opts |= BuildOptions.CleanBuildCache;
+#endif
+ break;
case "auto_run": opts |= BuildOptions.AutoRunPlayer; break;
case "deep_profiling": opts |= BuildOptions.EnableDeepProfilingSupport; break;
case "compress_lz4": opts |= BuildOptions.CompressWithLz4; break;
diff --git a/MCPForUnity/Editor/Tools/Build/BuildSettingsHelper.cs b/MCPForUnity/Editor/Tools/Build/BuildSettingsHelper.cs
index fae16d37c..1c8271e61 100644
--- a/MCPForUnity/Editor/Tools/Build/BuildSettingsHelper.cs
+++ b/MCPForUnity/Editor/Tools/Build/BuildSettingsHelper.cs
@@ -7,7 +7,7 @@ namespace MCPForUnity.Editor.Tools.Build
{
public static class BuildSettingsHelper
{
- public static object ReadProperty(string property, NamedBuildTarget namedTarget)
+ public static object ReadProperty(string property, BuildTargetGroup namedTarget)
{
switch (property.ToLowerInvariant())
{
@@ -23,17 +23,19 @@ public static object ReadProperty(string property, NamedBuildTarget namedTarget)
var backend = PlayerSettings.GetScriptingBackend(namedTarget);
return new { property, value = backend == ScriptingImplementation.IL2CPP ? "il2cpp" : "mono" };
case "defines":
- return new { property, value = PlayerSettings.GetScriptingDefineSymbols(namedTarget) };
+ return new { property, value = PlayerSettings.GetScriptingDefineSymbolsForGroup(namedTarget) };
case "architecture":
var arch = PlayerSettings.GetArchitecture(namedTarget);
- string archName = arch switch { 0 => "x86_64", 1 => "arm64", 2 => "universal", _ => "unknown" };
+ // GetArchitecture returns the BuildTargetGroup's architecture setting,
+ // where 0 means None (not x86_64); see PlayerSettings.SetArchitecture docs.
+ string archName = arch switch { 0 => "none", 1 => "arm64", 2 => "universal", _ => "unknown" };
return new { property, value = archName, raw = arch };
default:
return null;
}
}
- public static string WriteProperty(string property, string value, NamedBuildTarget namedTarget)
+ public static string WriteProperty(string property, string value, BuildTargetGroup namedTarget)
{
try
{
@@ -61,18 +63,19 @@ public static string WriteProperty(string property, string value, NamedBuildTarg
PlayerSettings.SetScriptingBackend(namedTarget, impl);
return null;
case "defines":
- PlayerSettings.SetScriptingDefineSymbols(namedTarget, value);
+ PlayerSettings.SetScriptingDefineSymbolsForGroup(namedTarget, value);
return null;
case "architecture":
int arch = value.ToLowerInvariant() switch
{
- "x86_64" or "none" or "default" => 0,
+ "none" => 0,
+ "default" => 0,
"arm64" => 1,
"universal" => 2,
_ => -1
};
if (arch < 0)
- return $"Unknown architecture '{value}'. Valid: x86_64, arm64, universal";
+ return $"Unknown architecture '{value}'. Valid: none, arm64, universal";
PlayerSettings.SetArchitecture(namedTarget, arch);
return null;
default:
diff --git a/MCPForUnity/Editor/Tools/Build/BuildTargetMapping.cs b/MCPForUnity/Editor/Tools/Build/BuildTargetMapping.cs
index 43383c672..fbfeffe1a 100644
--- a/MCPForUnity/Editor/Tools/Build/BuildTargetMapping.cs
+++ b/MCPForUnity/Editor/Tools/Build/BuildTargetMapping.cs
@@ -63,12 +63,12 @@ public static BuildTargetGroup GetTargetGroup(BuildTarget target)
}
}
- public static NamedBuildTarget GetNamedBuildTarget(BuildTarget target)
+ public static BuildTargetGroup GetNamedBuildTarget(BuildTarget target)
{
- return NamedBuildTarget.FromBuildTargetGroup(GetTargetGroup(target));
+ return GetTargetGroup(target);
}
- public static string TryResolveNamedBuildTarget(string name, out NamedBuildTarget namedTarget)
+ public static string TryResolveNamedBuildTarget(string name, out BuildTargetGroup namedTarget)
{
if (!TryResolveBuildTarget(name, out var buildTarget))
{
@@ -85,7 +85,7 @@ public static string TryResolveNamedBuildTarget(string name, out NamedBuildTarge
: $"Build target group could not be resolved for target '{buildTarget}'.";
}
- namedTarget = NamedBuildTarget.FromBuildTargetGroup(targetGroup);
+ namedTarget = targetGroup;
return null;
}
@@ -154,12 +154,17 @@ public static string GetDefaultOutputPath(BuildTarget target, string productName
public static int ResolveSubtarget(string subtarget)
{
+#if UNITY_2021_2_OR_NEWER
if (string.IsNullOrEmpty(subtarget))
return (int)StandaloneBuildSubtarget.Player;
string lower = subtarget.ToLowerInvariant();
if (lower == "server")
return (int)StandaloneBuildSubtarget.Server;
return (int)StandaloneBuildSubtarget.Player;
+#else
+ // Unity 2020.3 has no StandaloneBuildSubtarget API; only Player subtarget is supported.
+ return 0;
+#endif
}
}
}
diff --git a/MCPForUnity/Editor/Tools/Cameras/CameraCreate.cs b/MCPForUnity/Editor/Tools/Cameras/CameraCreate.cs
index d01e2ca84..f0920af4d 100644
--- a/MCPForUnity/Editor/Tools/Cameras/CameraCreate.cs
+++ b/MCPForUnity/Editor/Tools/Cameras/CameraCreate.cs
@@ -11,7 +11,7 @@ namespace MCPForUnity.Editor.Tools.Cameras
{
internal static class CameraCreate
{
- private static readonly Dictionary Presets = new(StringComparer.OrdinalIgnoreCase)
+ private static readonly Dictionary Presets = new Dictionary(StringComparer.OrdinalIgnoreCase)
{
["follow"] = ("CinemachineFollow", "CinemachineRotationComposer"),
["third_person"] = ("CinemachineThirdPersonFollow", "CinemachineRotationComposer"),
diff --git a/MCPForUnity/Editor/Tools/Cameras/CameraHelpers.cs b/MCPForUnity/Editor/Tools/Cameras/CameraHelpers.cs
index 80e75a3bb..d4f0b5e82 100644
--- a/MCPForUnity/Editor/Tools/Cameras/CameraHelpers.cs
+++ b/MCPForUnity/Editor/Tools/Cameras/CameraHelpers.cs
@@ -247,7 +247,8 @@ internal static string GetFallbackSuggestion(string action)
{
return action switch
{
- "set_body" or "set_aim" => "Use 'set_lens' and 'set_target' for basic camera configuration.",
+ "set_body" => "Use 'set_lens' and 'set_target' for basic camera configuration.",
+ "set_aim" => "Use 'set_lens' and 'set_target' for basic camera configuration.",
"set_blend" => "Without Cinemachine, switch cameras by enabling/disabling Camera components.",
"set_noise" => "Camera shake without Cinemachine requires a custom script.",
"ensure_brain" => "CinemachineBrain requires the Cinemachine package. Basic Camera does not need a Brain.",
@@ -260,7 +261,11 @@ internal static void MarkDirty(GameObject go)
{
if (go == null) return;
EditorUtility.SetDirty(go);
+#if UNITY_2021_2_OR_NEWER
var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
+#else
+ var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
+#endif
if (prefabStage != null)
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(prefabStage.scene);
else
diff --git a/MCPForUnity/Editor/Tools/CommandRegistry.cs b/MCPForUnity/Editor/Tools/CommandRegistry.cs
index 7588e2cfb..0a1d1c57c 100644
--- a/MCPForUnity/Editor/Tools/CommandRegistry.cs
+++ b/MCPForUnity/Editor/Tools/CommandRegistry.cs
@@ -36,7 +36,7 @@ public HandlerInfo(string commandName, Func syncHandler, Func
public static class CommandRegistry
{
- private static readonly Dictionary _handlers = new();
+ private static readonly Dictionary _handlers = new Dictionary();
private static bool _initialized = false;
///
@@ -363,7 +363,7 @@ private static Func> CreateAsyncHandlerDelegate(MethodInfo
return null;
}
- if (rawResult is not Task task)
+ if (!(rawResult is Task task))
{
throw new InvalidOperationException(
$"Async handler '{commandName}' returned an object that is not a Task"
diff --git a/MCPForUnity/Editor/Tools/ExecuteCode.cs b/MCPForUnity/Editor/Tools/ExecuteCode.cs
index 229c084d4..2f0b8b1ed 100644
--- a/MCPForUnity/Editor/Tools/ExecuteCode.cs
+++ b/MCPForUnity/Editor/Tools/ExecuteCode.cs
@@ -124,7 +124,7 @@ private static object HandleExecute(JObject @params)
private static object HandleGetHistory(JObject @params)
{
int limit = @params["limit"]?.Value() ?? 10;
- limit = Math.Clamp(limit, 1, MaxHistoryEntries);
+ limit = Mathf.Clamp(limit, 1, MaxHistoryEntries);
if (_history.Count == 0)
return new SuccessResponse("No execution history.", new { total = 0, entries = new object[0] });
diff --git a/MCPForUnity/Editor/Tools/GameObjects/ComponentResolver.cs b/MCPForUnity/Editor/Tools/GameObjects/ComponentResolver.cs
index 589374a15..641b858f5 100644
--- a/MCPForUnity/Editor/Tools/GameObjects/ComponentResolver.cs
+++ b/MCPForUnity/Editor/Tools/GameObjects/ComponentResolver.cs
@@ -72,7 +72,7 @@ public static List GetFuzzyPropertySuggestions(string userInput, List> PropertySuggestionCache = new();
+ private static readonly Dictionary> PropertySuggestionCache = new Dictionary>();
///
/// Rule-based suggestions that mimic AI behavior for property matching.
diff --git a/MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs b/MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs
index 1ffa16bf7..d21bb1c38 100644
--- a/MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs
+++ b/MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs
@@ -250,7 +250,7 @@ internal static object SetComponentPropertiesInternal(GameObject targetGo, strin
// Nested paths (e.g. "transform.position") need local handling
// since ComponentOps doesn't support dot/bracket notation.
- if (propName.Contains('.') || propName.Contains('['))
+ if (propName.Contains(".") || propName.Contains("["))
{
setResult = SetNestedProperty(targetComponent, propName, propValue, InputSerializer, out setError);
}
diff --git a/MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs b/MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs
index 2bfce076c..3f5f4661a 100644
--- a/MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs
+++ b/MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs
@@ -5,6 +5,9 @@
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
+#if !UNITY_2021_2_OR_NEWER
+using UnityEditor.Experimental.SceneManagement;
+#endif
using UnityEditorInternal;
using UnityEngine;
diff --git a/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs b/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs
index 7afa27510..eae9fef49 100644
--- a/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs
+++ b/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs
@@ -5,7 +5,11 @@
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Tools;
using Newtonsoft.Json.Linq;
+#if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement;
+#else
+using UnityEditor.Experimental.SceneManagement;
+#endif
using UnityEngine;
using UnityEngine.SceneManagement;
using MCPForUnity.Runtime.Helpers;
@@ -46,7 +50,7 @@ internal static List FindObjectsInternal(
{
if (targetToken?.Type == JTokenType.Integer)
searchMethod = "by_id";
- else if (!string.IsNullOrEmpty(searchTerm) && searchTerm.Contains('/'))
+ else if (!string.IsNullOrEmpty(searchTerm) && searchTerm.Contains("/"))
searchMethod = "by_path";
else
searchMethod = "by_name";
diff --git a/MCPForUnity/Editor/Tools/Graphics/GraphicsHelpers.cs b/MCPForUnity/Editor/Tools/Graphics/GraphicsHelpers.cs
index c410c32f0..d9ab52424 100644
--- a/MCPForUnity/Editor/Tools/Graphics/GraphicsHelpers.cs
+++ b/MCPForUnity/Editor/Tools/Graphics/GraphicsHelpers.cs
@@ -257,7 +257,11 @@ internal static void MarkDirty(UnityEngine.Object obj)
EditorUtility.SetDirty(obj);
if (obj is Component comp)
{
- var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
+ #if UNITY_2021_2_OR_NEWER
+ var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
+#else
+ var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
+#endif
if (prefabStage != null)
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(prefabStage.scene);
else
diff --git a/MCPForUnity/Editor/Tools/Graphics/LightBakingOps.cs b/MCPForUnity/Editor/Tools/Graphics/LightBakingOps.cs
index b2f904421..9232bdd33 100644
--- a/MCPForUnity/Editor/Tools/Graphics/LightBakingOps.cs
+++ b/MCPForUnity/Editor/Tools/Graphics/LightBakingOps.cs
@@ -151,7 +151,9 @@ internal static object GetSettings(JObject @params)
["indirectSampleCount"] = settings.indirectSampleCount,
["environmentSampleCount"] = settings.environmentSampleCount,
["mixedBakeMode"] = settings.mixedBakeMode.ToString(),
+#if UNITY_2021_2_OR_NEWER
["lightmapCompression"] = settings.lightmapCompression.ToString(),
+#endif
["ao"] = settings.ao,
["aoMaxDistance"] = settings.aoMaxDistance
};
@@ -510,6 +512,7 @@ private static bool TrySetLightingSetting(LightingSettings settings, string name
case "compress_lightmaps":
case "lightmapcompression":
case "lightmap_compression":
+#if UNITY_2021_2_OR_NEWER
var strVal = value?.ToString() ?? "";
if (System.Enum.TryParse(strVal, true, out var compression))
settings.lightmapCompression = compression;
@@ -521,6 +524,9 @@ private static bool TrySetLightingSetting(LightingSettings settings, string name
else
return false;
return true;
+#else
+ return true;
+#endif
case "ao":
settings.ao = ParamCoercion.CoerceBool(value, settings.ao);
diff --git a/MCPForUnity/Editor/Tools/Graphics/RenderPipelineOps.cs b/MCPForUnity/Editor/Tools/Graphics/RenderPipelineOps.cs
index 46b4f6ffe..be6a932ce 100644
--- a/MCPForUnity/Editor/Tools/Graphics/RenderPipelineOps.cs
+++ b/MCPForUnity/Editor/Tools/Graphics/RenderPipelineOps.cs
@@ -258,8 +258,15 @@ private static object ConvertPropertyValue(JToken value, Type targetType)
if (targetType.IsEnum)
{
string str = value.ToString();
- if (Enum.TryParse(targetType, str, true, out object enumVal))
+ try
+ {
+ object enumVal = Enum.Parse(targetType, str, true);
return enumVal;
+ }
+ catch (ArgumentException)
+ {
+ // Not a defined enum value; fall through to int coercion.
+ }
if (int.TryParse(str, out int intVal))
return Enum.ToObject(targetType, intVal);
}
diff --git a/MCPForUnity/Editor/Tools/Graphics/SkyboxOps.cs b/MCPForUnity/Editor/Tools/Graphics/SkyboxOps.cs
index 5882505d4..a8b1614b3 100644
--- a/MCPForUnity/Editor/Tools/Graphics/SkyboxOps.cs
+++ b/MCPForUnity/Editor/Tools/Graphics/SkyboxOps.cs
@@ -11,7 +11,7 @@ namespace MCPForUnity.Editor.Tools.Graphics
{
internal static class SkyboxOps
{
- static Texture CustomReflectionTexture
+ static Cubemap CustomReflectionTexture
{
get =>
#if UNITY_2022_1_OR_NEWER
@@ -316,7 +316,7 @@ public static object SetReflection(JObject @params)
string cubemapPath = p.Get("path") ?? p.Get("cubemap_path");
if (!string.IsNullOrEmpty(cubemapPath))
{
- var cubemap = AssetDatabase.LoadAssetAtPath(cubemapPath);
+ var cubemap = AssetDatabase.LoadAssetAtPath(cubemapPath);
if (cubemap != null)
CustomReflectionTexture = cubemap;
else
@@ -417,8 +417,10 @@ private static object ReadMaterialProperty(Material mat, string propName, Shader
case ShaderPropertyType.Float:
case ShaderPropertyType.Range:
return mat.GetFloat(propName);
+#if UNITY_2021_2_OR_NEWER
case ShaderPropertyType.Int:
return mat.GetInt(propName);
+#endif
case ShaderPropertyType.Vector:
var v = mat.GetVector(propName);
return new[] { v.x, v.y, v.z, v.w };
@@ -453,9 +455,11 @@ private static bool SetMaterialProperty(Material mat, string propName, JToken va
case ShaderPropertyType.Range:
mat.SetFloat(propName, (float)value);
return true;
+#if UNITY_2021_2_OR_NEWER
case ShaderPropertyType.Int:
mat.SetInt(propName, (int)value);
return true;
+#endif
case ShaderPropertyType.Vector:
if (value is JArray vecArr && vecArr.Count >= 2)
{
diff --git a/MCPForUnity/Editor/Tools/Graphics/VolumeOps.cs b/MCPForUnity/Editor/Tools/Graphics/VolumeOps.cs
index 69375f09a..ea5a8643b 100644
--- a/MCPForUnity/Editor/Tools/Graphics/VolumeOps.cs
+++ b/MCPForUnity/Editor/Tools/Graphics/VolumeOps.cs
@@ -629,8 +629,15 @@ private static object ConvertToParameterType(JToken value, Type targetType)
if (targetType.IsEnum)
{
string str = value.ToString();
- if (Enum.TryParse(targetType, str, true, out object enumVal))
+ try
+ {
+ object enumVal = Enum.Parse(targetType, str, true);
return enumVal;
+ }
+ catch (ArgumentException)
+ {
+ // Not a defined enum value; fall through to int coercion.
+ }
// Try as int
if (int.TryParse(str, out int intVal))
return Enum.ToObject(targetType, intVal);
diff --git a/MCPForUnity/Editor/Tools/ManageBuild.cs b/MCPForUnity/Editor/Tools/ManageBuild.cs
index 8b5c89ac3..81fb32010 100644
--- a/MCPForUnity/Editor/Tools/ManageBuild.cs
+++ b/MCPForUnity/Editor/Tools/ManageBuild.cs
@@ -215,7 +215,11 @@ private static object HandlePlatform(ToolParams p)
target = EditorUserBuildSettings.activeBuildTarget.ToString(),
target_group = BuildTargetMapping.GetTargetGroup(
EditorUserBuildSettings.activeBuildTarget).ToString(),
+#if UNITY_2021_2_OR_NEWER
subtarget = EditorUserBuildSettings.standaloneBuildSubtarget.ToString()
+#else
+ subtarget = "player"
+#endif
});
}
@@ -228,25 +232,35 @@ private static object HandlePlatform(ToolParams p)
return new ErrorResponse(
$"Platform '{target}' is not installed. Install it via Unity Hub.");
- if (EditorUserBuildSettings.activeBuildTarget == target)
- return new SuccessResponse("Already on this platform.", new
- {
- target = target.ToString()
- });
-
- // Capture previous target before switching
- string previousTarget = EditorUserBuildSettings.activeBuildTarget.ToString();
-
+ // Process subtarget before the active-target short-circuit so a "server"
+ // request on Unity < 2021.2 errors out even when the platform is already active.
string subtargetStr = p.Get("subtarget");
if (!string.IsNullOrEmpty(subtargetStr))
{
+#if UNITY_2021_2_OR_NEWER
string subtargetLower = subtargetStr.ToLowerInvariant();
if (subtargetLower == "server")
EditorUserBuildSettings.standaloneBuildSubtarget = StandaloneBuildSubtarget.Server;
else if (subtargetLower == "player")
EditorUserBuildSettings.standaloneBuildSubtarget = StandaloneBuildSubtarget.Player;
+#else
+ // Unity 2020.3 has no server subtarget; fail loudly instead of silently
+ // building the player variant.
+ string subtargetLower = subtargetStr.ToLowerInvariant();
+ if (subtargetLower == "server")
+ return new ErrorResponse("subtarget 'server' requires Unity 2021.2 or newer (StandaloneBuildSubtarget API).");
+#endif
}
+ if (EditorUserBuildSettings.activeBuildTarget == target)
+ return new SuccessResponse("Already on this platform.", new
+ {
+ target = target.ToString()
+ });
+
+ // Capture previous target before switching
+ string previousTarget = EditorUserBuildSettings.activeBuildTarget.ToString();
+
// SwitchActiveBuildTarget is synchronous — blocks until reimport completes
EditorUserBuildSettings.SwitchActiveBuildTarget(group, target);
@@ -477,7 +491,11 @@ private static object HandleBatch(ToolParams p)
if (EditorUserBuildSettings.activeBuildTarget != child.Target)
EditorUserBuildSettings.SwitchActiveBuildTarget(group, child.Target);
+ #if UNITY_2021_2_OR_NEWER
int subtarget = (int)StandaloneBuildSubtarget.Player;
+#else
+ int subtarget = 0;
+#endif
var options = BuildRunner.CreateBuildOptions(
child.Target, child.OutputPath, null, buildOpts, subtarget);
BuildRunner.ScheduleBuild(child, options);
diff --git a/MCPForUnity/Editor/Tools/ManageComponents.cs b/MCPForUnity/Editor/Tools/ManageComponents.cs
index 0cb6757b9..616ab060d 100644
--- a/MCPForUnity/Editor/Tools/ManageComponents.cs
+++ b/MCPForUnity/Editor/Tools/ManageComponents.cs
@@ -6,6 +6,9 @@
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
+#if !UNITY_2021_2_OR_NEWER
+using UnityEditor.Experimental.SceneManagement;
+#endif
using UnityEngine;
using MCPForUnity.Runtime.Helpers;
diff --git a/MCPForUnity/Editor/Tools/ManagePackages.cs b/MCPForUnity/Editor/Tools/ManagePackages.cs
index a61398a55..a86589015 100644
--- a/MCPForUnity/Editor/Tools/ManagePackages.cs
+++ b/MCPForUnity/Editor/Tools/ManagePackages.cs
@@ -19,11 +19,11 @@ namespace MCPForUnity.Editor.Tools
public static class ManagePackages
{
// Pending async requests keyed by job ID
- private static readonly Dictionary PendingRequests = new();
+ private static readonly Dictionary PendingRequests = new Dictionary();
// Pending list/search requests keyed by job ID
- private static readonly Dictionary PendingListRequests = new();
- private static readonly Dictionary PendingSearchRequests = new();
+ private static readonly Dictionary PendingListRequests = new Dictionary();
+ private static readonly Dictionary PendingSearchRequests = new Dictionary();
public static object HandleCommand(JObject @params)
{
@@ -360,7 +360,7 @@ private static object GetPackageInfo(ToolParams p)
try
{
- var allPackages = PackageInfo.GetAllRegisteredPackages();
+ var allPackages = RegisteredPackageInfo.GetRegisteredPackages();
var info = allPackages.FirstOrDefault(pkg =>
string.Equals(pkg.name, package, StringComparison.OrdinalIgnoreCase));
@@ -381,7 +381,7 @@ private static object GetPackageInfo(ToolParams p)
description = info.description,
source = info.source.ToString(),
resolved_path = info.resolvedPath,
- author = info.author?.name,
+ author = info.author.name,
dependencies,
dependency_count = dependencies.Length
}
@@ -593,7 +593,7 @@ private static object Ping()
{
try
{
- var allPackages = PackageInfo.GetAllRegisteredPackages();
+ var allPackages = RegisteredPackageInfo.GetRegisteredPackages();
return new SuccessResponse(
"Package manager is available.",
new
@@ -721,7 +721,7 @@ private static string[] GetDependentPackages(string packageName)
{
string name = PackageJobManager.ExtractPackageName(packageName);
- var allPackages = PackageInfo.GetAllRegisteredPackages();
+ var allPackages = RegisteredPackageInfo.GetRegisteredPackages();
return allPackages
.Where(pkg => pkg.dependencies.Any(d =>
string.Equals(d.name, name, StringComparison.OrdinalIgnoreCase)))
@@ -733,5 +733,8 @@ private static string[] GetDependentPackages(string packageName)
return null;
}
}
+
+
+
}
}
diff --git a/MCPForUnity/Editor/Tools/ManageScene.cs b/MCPForUnity/Editor/Tools/ManageScene.cs
index c3c66b3fa..d84077b54 100644
--- a/MCPForUnity/Editor/Tools/ManageScene.cs
+++ b/MCPForUnity/Editor/Tools/ManageScene.cs
@@ -7,6 +7,9 @@
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
+#if !UNITY_2021_2_OR_NEWER
+using UnityEditor.Experimental.SceneManagement;
+#endif
using UnityEngine;
using UnityEngine.SceneManagement;
diff --git a/MCPForUnity/Editor/Tools/ManageScript.cs b/MCPForUnity/Editor/Tools/ManageScript.cs
index 4f94df606..9db728070 100644
--- a/MCPForUnity/Editor/Tools/ManageScript.cs
+++ b/MCPForUnity/Editor/Tools/ManageScript.cs
@@ -271,7 +271,7 @@ public static object HandleCommand(JObject @params)
}).ToArray();
var result = new { diagnostics = diags };
- return ok ? new SuccessResponse("Validation completed.", result)
+ return ok ? (IMcpResponse)new SuccessResponse("Validation completed.", result)
: new ErrorResponse("Validation failed.", result);
}
case "edit":
diff --git a/MCPForUnity/Editor/Tools/ManageScriptableObject.cs b/MCPForUnity/Editor/Tools/ManageScriptableObject.cs
index 2ddd021c3..4e762dfa6 100644
--- a/MCPForUnity/Editor/Tools/ManageScriptableObject.cs
+++ b/MCPForUnity/Editor/Tools/ManageScriptableObject.cs
@@ -28,7 +28,7 @@ public static class ManageScriptableObject
private const string CodeTargetNotFound = "target_not_found";
private const string CodeAssetCreateFailed = "asset_create_failed";
- private static readonly HashSet ValidActions = new(StringComparer.OrdinalIgnoreCase)
+ private static readonly HashSet ValidActions = new HashSet(StringComparer.OrdinalIgnoreCase)
{
// NOTE: Action strings are normalized by NormalizeAction() (lowercased, '_'/'-' removed),
// so we only need the canonical normalized forms here.
@@ -223,7 +223,7 @@ private static object HandleModify(JObject @params)
return new ErrorResponse(CodeInvalidParams, new { message = "'patches' is required.", targetPath, targetGuid });
}
- if (patchesToken is not JArray patches)
+ if (!(patchesToken is JArray patches))
{
return new ErrorResponse(CodeInvalidParams, new { message = "'patches' must be an array.", targetPath, targetGuid });
}
@@ -275,7 +275,7 @@ private static List ValidatePatches(UnityEngine.Object target, JArray pa
for (int i = 0; i < patches.Count; i++)
{
- if (patches[i] is not JObject patchObj)
+ if (!(patches[i] is JObject patchObj))
{
results.Add(new { index = i, propertyPath = "", op = "", ok = false, message = $"Patch at index {i} must be an object." });
continue;
@@ -450,7 +450,7 @@ private static (List results, List warnings) ApplyPatches(UnityE
for (int i = 0; i < patches.Count; i++)
{
- if (patches[i] is not JObject patchObj)
+ if (!(patches[i] is JObject patchObj))
{
results.Add(new { propertyPath = "", op = "", ok = false, message = $"Patch at index {i} must be an object." });
continue;
@@ -1137,7 +1137,7 @@ private static bool TrySetAnimationCurve(SerializedProperty prop, JToken valueTo
var curve = new AnimationCurve();
foreach (var keyToken in keysArray)
{
- if (keyToken is not JObject keyObj)
+ if (!(keyToken is JObject keyObj))
{
message = "Each keyframe must be an object with 'time' and 'value'.";
return false;
@@ -1299,7 +1299,7 @@ private static bool TryResolveTarget(JToken targetToken, out UnityEngine.Object
targetGuid = null;
error = null;
- if (targetToken is not JObject targetObj)
+ if (!(targetToken is JObject targetObj))
{
error = new ErrorResponse(CodeInvalidParams, new { message = "'target' must be an object with {guid|path}." });
return false;
@@ -1424,7 +1424,7 @@ private static string SanitizeSlashes(string path)
var s = AssetPathUtility.NormalizeSeparators(path);
while (s.IndexOf("//", StringComparison.Ordinal) >= 0)
{
- s = s.Replace("//", "/", StringComparison.Ordinal);
+ s = s.Replace("//", "/");
}
return s;
}
diff --git a/MCPForUnity/Editor/Tools/ManageUI.cs b/MCPForUnity/Editor/Tools/ManageUI.cs
index cad174139..8baf302e8 100644
--- a/MCPForUnity/Editor/Tools/ManageUI.cs
+++ b/MCPForUnity/Editor/Tools/ManageUI.cs
@@ -16,7 +16,7 @@ namespace MCPForUnity.Editor.Tools
[McpForUnityTool("manage_ui", AutoRegister = false, Group = "ui")]
public static class ManageUI
{
- private static readonly HashSet ValidExtensions = new(StringComparer.OrdinalIgnoreCase)
+ private static readonly HashSet ValidExtensions = new HashSet(StringComparer.OrdinalIgnoreCase)
{
".uxml", ".uss"
};
@@ -296,6 +296,12 @@ private static object UpdateFile(JObject @params)
new { path });
}
+#if !UNITY_2021_2_OR_NEWER
+ private static object AttachUIDocument(JObject @params)
+ {
+ return new ErrorResponse("Attach UIDocument requires Unity 2021.2 or newer (UIDocument component).");
+ }
+#else
private static object AttachUIDocument(JObject @params)
{
var p = new ToolParams(@params);
@@ -391,7 +397,14 @@ private static object AttachUIDocument(JObject @params)
sortOrder
});
}
+#endif
+#if !UNITY_2021_2_OR_NEWER
+ private static object CreatePanelSettings(JObject @params)
+ {
+ return new ErrorResponse("PanelSettings requires Unity 2021.2 or newer.");
+ }
+#else
private static object CreatePanelSettings(JObject @params)
{
var p = new ToolParams(@params);
@@ -458,7 +471,14 @@ private static object CreatePanelSettings(JObject @params)
return new SuccessResponse($"Created PanelSettings at {path}",
new { path, applied = changes });
}
+#endif
+#if !UNITY_2021_2_OR_NEWER
+ private static object UpdatePanelSettings(JObject @params)
+ {
+ return new ErrorResponse("PanelSettings requires Unity 2021.2 or newer.");
+ }
+#else
private static object UpdatePanelSettings(JObject @params)
{
var p = new ToolParams(@params);
@@ -479,7 +499,7 @@ private static object UpdatePanelSettings(JObject @params)
return new ErrorResponse($"No PanelSettings found at {path}");
JToken settingsToken = p.GetRaw("settings");
- if (settingsToken is not JObject settingsObj || settingsObj.Count == 0)
+ if (!(settingsToken is JObject settingsObj) || settingsObj.Count == 0)
return new ErrorResponse("'settings' dict is required with at least one property to update.");
var changes = new List();
@@ -494,7 +514,14 @@ private static object UpdatePanelSettings(JObject @params)
return new SuccessResponse($"Updated PanelSettings at {path}",
new { path, applied = changes });
}
+#endif
+#if !UNITY_2021_2_OR_NEWER
+ private static object CreateDefaultPanelSettings(string path)
+ {
+ return null;
+ }
+#else
private static PanelSettings CreateDefaultPanelSettings(string path)
{
string dir = Path.GetDirectoryName(path);
@@ -508,6 +535,7 @@ private static PanelSettings CreateDefaultPanelSettings(string path)
AssetDatabase.SaveAssets();
return ps;
}
+#endif
///
/// Generic, data-driven applicator for PanelSettings properties.
@@ -518,6 +546,11 @@ private static PanelSettings CreateDefaultPanelSettings(string path)
/// clearColor, colorClearValue, clearDepthStencil,
/// themeStyleSheet, dynamicAtlasSettings.
///
+#if !UNITY_2021_2_OR_NEWER
+ private static void ApplyPanelSettingsProperties(object ps, JObject settings, List changes)
+ {
+ }
+#else
private static void ApplyPanelSettingsProperties(PanelSettings ps, JObject settings, List changes)
{
foreach (var prop in settings)
@@ -603,7 +636,13 @@ private static void ApplyPanelSettingsProperties(PanelSettings ps, JObject setti
}
}
}
+#endif
+#if !UNITY_2021_2_OR_NEWER
+ private static void ApplyDynamicAtlasSettings(object ps, JObject da, List changes)
+ {
+ }
+#else
private static void ApplyDynamicAtlasSettings(PanelSettings ps, JObject da, List changes)
{
var daCopy = ps.dynamicAtlasSettings;
@@ -620,6 +659,7 @@ private static void ApplyDynamicAtlasSettings(PanelSettings ps, JObject da, List
ps.dynamicAtlasSettings = daCopy;
changes.Add("dynamicAtlasSettings");
}
+#endif
// ── Tiny helpers to keep the switch compact ─────────────────────────
@@ -704,6 +744,12 @@ private static void EnsureFolderExists(string assetFolderPath)
}
}
+#if !UNITY_2021_2_OR_NEWER
+ private static object GetVisualTree(JObject @params)
+ {
+ return new ErrorResponse("Get visual tree requires Unity 2021.2 or newer (UIDocument runtime UI).");
+ }
+#else
private static object GetVisualTree(JObject @params)
{
var p = new ToolParams(@params);
@@ -753,6 +799,7 @@ private static object GetVisualTree(JObject @params)
tree
});
}
+#endif
private static object SerializeVisualElement(VisualElement element, int depth, int maxDepth)
{
@@ -807,7 +854,7 @@ private static object SerializeVisualElement(VisualElement element, int depth, i
// Persistent RenderTextures keyed by PanelSettings instance ID so the panel
// renders into them automatically every frame.
- private static readonly Dictionary s_panelRTs = new();
+ private static readonly Dictionary s_panelRTs = new Dictionary();
// Play-mode coroutine capture state. Only one capture is in-flight at a
// time; concurrent render_ui calls while a capture is pending are rejected
@@ -816,6 +863,12 @@ private static object SerializeVisualElement(VisualElement element, int depth, i
private static bool s_pendingCaptureDone;
private static bool s_pendingCaptureStarted;
+#if !UNITY_2021_2_OR_NEWER
+ private static object RenderUI(JObject @params)
+ {
+ return new ErrorResponse("Render UI requires Unity 2021.2 or newer (UIDocument runtime UI).");
+ }
+#else
private static object RenderUI(JObject @params)
{
var p = new ToolParams(@params);
@@ -1185,6 +1238,7 @@ private static object RenderUI(JObject @params)
}
}
}
+#endif
// ---- Link Stylesheet ----
@@ -1386,6 +1440,12 @@ private static object ListUIAssets(JObject @params)
// ---- Detach UIDocument ----
+#if !UNITY_2021_2_OR_NEWER
+ private static object DetachUIDocument(JObject @params)
+ {
+ return new ErrorResponse("Detach UIDocument requires Unity 2021.2 or newer (UIDocument component).");
+ }
+#else
private static object DetachUIDocument(JObject @params)
{
var p = new ToolParams(@params);
@@ -1421,9 +1481,16 @@ private static object DetachUIDocument(JObject @params)
removedSourceAsset = sourceAsset,
});
}
+#endif
// ---- Modify Visual Element ----
+#if !UNITY_2021_2_OR_NEWER
+ private static object ModifyVisualElement(JObject @params)
+ {
+ return new ErrorResponse("Modify visual element requires Unity 2021.2 or newer (UIDocument runtime UI).");
+ }
+#else
private static object ModifyVisualElement(JObject @params)
{
var p = new ToolParams(@params);
@@ -1580,6 +1647,7 @@ private static object ModifyVisualElement(JObject @params)
$"Modified element '{elementName}' on {go.name}: {string.Join(", ", applied)}",
responseData);
}
+#endif
private static void ApplyInlineStyles(VisualElement element, JObject styleObj, List modifications)
{
diff --git a/MCPForUnity/Editor/Tools/Prefabs/ManagePrefabs.cs b/MCPForUnity/Editor/Tools/Prefabs/ManagePrefabs.cs
index 65d158a5d..11422c452 100644
--- a/MCPForUnity/Editor/Tools/Prefabs/ManagePrefabs.cs
+++ b/MCPForUnity/Editor/Tools/Prefabs/ManagePrefabs.cs
@@ -6,6 +6,9 @@
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
+#if !UNITY_2021_2_OR_NEWER
+using UnityEditor.Experimental.SceneManagement;
+#endif
using UnityEngine;
using UnityEngine.SceneManagement;
using MCPForUnity.Runtime.Helpers;
@@ -419,9 +422,24 @@ private static void ApplyPropertyBlockToMaterial(Renderer renderer, int slot, Ma
string[] colorProps = { "_BaseColor", "_Color" };
foreach (string prop in colorProps)
{
- if (mat.HasProperty(prop) && block.HasColor(prop))
+ if (mat.HasProperty(prop))
{
- mat.SetColor(prop, block.GetColor(prop));
+#if UNITY_2021_2_OR_NEWER
+ if (block.HasColor(prop))
+ {
+ mat.SetColor(prop, block.GetColor(prop));
+ }
+#else
+ try
+ {
+ Color c = block.GetColor(prop);
+ mat.SetColor(prop, c);
+ }
+ catch
+ {
+ // Property exists on material but not present in this block.
+ }
+#endif
}
}
}
@@ -958,7 +976,7 @@ private static (bool modified, ErrorResponse error) ApplyModificationsToPrefabOb
continue;
}
- if (entry.Value is not JObject props || !props.HasValues)
+ if (!(entry.Value is JObject props) || !props.HasValues)
{
continue;
}
@@ -1311,10 +1329,18 @@ private static object OpenPrefabStage(string requestedPath)
return new ErrorResponse($"Prefab asset not found at '{sanitizedPath}'.");
}
+#if UNITY_2021_2_OR_NEWER
var prefabStage = PrefabStageUtility.OpenPrefab(sanitizedPath);
bool enteredStage = prefabStage != null
&& string.Equals(prefabStage.assetPath, sanitizedPath, StringComparison.OrdinalIgnoreCase)
&& prefabStage.prefabContentsRoot != null;
+#else
+ AssetDatabase.OpenAsset(prefabAsset);
+ var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
+ bool enteredStage = prefabStage != null
+ && string.Equals(prefabStage.assetPath, sanitizedPath, StringComparison.OrdinalIgnoreCase)
+ && prefabStage.prefabContentsRoot != null;
+#endif
if (!enteredStage)
{
diff --git a/MCPForUnity/Editor/Tools/Profiler/Operations/CounterOps.cs b/MCPForUnity/Editor/Tools/Profiler/Operations/CounterOps.cs
index c14482ebe..3a4cc5ebd 100644
--- a/MCPForUnity/Editor/Tools/Profiler/Operations/CounterOps.cs
+++ b/MCPForUnity/Editor/Tools/Profiler/Operations/CounterOps.cs
@@ -144,8 +144,13 @@ void Tick()
case "vr": return ProfilerCategory.Vr;
case "internal": return ProfilerCategory.Internal;
case "particles": return ProfilerCategory.Particles;
+#if UNITY_2021_2_OR_NEWER
case "fileio": return ProfilerCategory.FileIO;
case "virtualtexturing": return ProfilerCategory.VirtualTexturing;
+#else
+ case "fileio": return ProfilerCategory.Loading;
+ case "virtualtexturing": return ProfilerCategory.Render;
+#endif
default:
error = $"Unknown category '{name}'. Valid: {string.Join(", ", ValidCategories)}";
return null;
diff --git a/MCPForUnity/Editor/Tools/ReadConsole.cs b/MCPForUnity/Editor/Tools/ReadConsole.cs
index 155484b6f..5d28b74f3 100644
--- a/MCPForUnity/Editor/Tools/ReadConsole.cs
+++ b/MCPForUnity/Editor/Tools/ReadConsole.cs
@@ -535,7 +535,7 @@ private static int FindStackStartIndex(string[] lines)
(
trimmedLine.Length > 0
&& char.IsUpper(trimmedLine[0])
- && trimmedLine.Contains('.')
+ && trimmedLine.Contains(".")
)
)
{
diff --git a/MCPForUnity/Editor/Tools/UnityReflect.cs b/MCPForUnity/Editor/Tools/UnityReflect.cs
index 236b74342..8db7ce03e 100644
--- a/MCPForUnity/Editor/Tools/UnityReflect.cs
+++ b/MCPForUnity/Editor/Tools/UnityReflect.cs
@@ -17,8 +17,8 @@ namespace MCPForUnity.Editor.Tools
public static class UnityReflect
{
private static Dictionary _assemblyTypeCache;
- private static readonly object CacheLock = new();
- private static readonly ConcurrentDictionary ExtensionMethodCache = new();
+ private static readonly object CacheLock = new object();
+ private static readonly ConcurrentDictionary ExtensionMethodCache = new ConcurrentDictionary();
private static readonly string[] NamespacePrefixes =
{
@@ -40,7 +40,7 @@ public static class UnityReflect
"UnityEngine.UIElements."
};
- private static readonly Dictionary FriendlyTypeNames = new()
+ private static readonly Dictionary FriendlyTypeNames = new Dictionary()
{
{ typeof(void), "void" },
{ typeof(int), "int" },
@@ -146,7 +146,7 @@ private static object GetTypeInfo(ToolParams p)
string normalizedName = NormalizeGenericName(className);
// Check for ambiguity first (only for short names without namespace)
- if (!normalizedName.Contains('.') && !normalizedName.Contains('`'))
+ if (!normalizedName.Contains(".") && !normalizedName.Contains("`"))
{
var matches = FindAllTypesByShortName(normalizedName);
if (matches.Count > 1)
@@ -265,7 +265,7 @@ private static object GetMemberInfo(ToolParams p)
string memberName = memberResult.Value;
string normalizedName = NormalizeGenericName(className);
- if (!normalizedName.Contains('.') && !normalizedName.Contains('`'))
+ if (!normalizedName.Contains(".") && !normalizedName.Contains("`"))
{
var matches = FindAllTypesByShortName(normalizedName);
if (matches.Count > 1)
diff --git a/MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs b/MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs
index 18e542b6e..5f3604575 100644
--- a/MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs
+++ b/MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs
@@ -189,8 +189,8 @@ public static object AddBurst(JObject @params)
float time = @params["time"]?.ToObject() ?? 0f;
int minCountRaw = @params["minCount"]?.ToObject() ?? @params["count"]?.ToObject() ?? 30;
int maxCountRaw = @params["maxCount"]?.ToObject() ?? @params["count"]?.ToObject() ?? 30;
- short minCount = (short)Math.Clamp(minCountRaw, 0, short.MaxValue);
- short maxCount = (short)Math.Clamp(maxCountRaw, 0, short.MaxValue);
+ short minCount = (short)Mathf.Clamp(minCountRaw, 0, short.MaxValue);
+ short maxCount = (short)Mathf.Clamp(maxCountRaw, 0, short.MaxValue);
int cycles = @params["cycles"]?.ToObject() ?? 1;
float interval = @params["interval"]?.ToObject() ?? 0.01f;
diff --git a/MCPForUnity/Editor/Windows/Components/AssetGen/McpAssetGenSection.cs b/MCPForUnity/Editor/Windows/Components/AssetGen/McpAssetGenSection.cs
index 6b0e6d113..6a92b4d1b 100644
--- a/MCPForUnity/Editor/Windows/Components/AssetGen/McpAssetGenSection.cs
+++ b/MCPForUnity/Editor/Windows/Components/AssetGen/McpAssetGenSection.cs
@@ -6,6 +6,7 @@
using MCPForUnity.Editor.Services.AssetGen.Import;
using UnityEngine;
using UnityEngine.UIElements;
+using MCPForUnity.Editor.Windows.Components;
namespace MCPForUnity.Editor.Windows.Components.AssetGen
{
@@ -39,7 +40,7 @@ private static readonly (string Id, string Label)[] ImageProviders =
// UI Elements
private VisualElement providersContainer;
private VisualElement gltfastNotice;
- private DropdownField formatDropdown;
+ private CompatDropdownField formatDropdown;
private TextField outputRootField;
private Toggle autoNormalizeToggle;
private Button refreshButton;
@@ -47,7 +48,7 @@ private static readonly (string Id, string Label)[] ImageProviders =
// Per-provider enable toggles for the GLB-capable (model) providers, used to
// recompute the glTFast notice when a toggle changes.
- private readonly List<(string Id, Toggle Toggle)> modelEnableToggles = new();
+ private readonly List<(string Id, Toggle Toggle)> modelEnableToggles = new List<(string Id, Toggle Toggle)>();
public VisualElement Root { get; private set; }
@@ -63,7 +64,7 @@ private void CacheUIElements()
{
providersContainer = Root.Q("assetgen-providers-container");
gltfastNotice = Root.Q("gltfast-notice");
- formatDropdown = Root.Q("assetgen-format-dropdown");
+ formatDropdown = Root.Q("assetgen-format-dropdown");
outputRootField = Root.Q("assetgen-output-root");
autoNormalizeToggle = Root.Q("assetgen-auto-normalize");
refreshButton = Root.Q
public class McpToolsSection
{
- private readonly Dictionary toolToggleMap = new();
+ private readonly Dictionary toolToggleMap = new Dictionary();
private Toggle projectScopedToolsToggle;
private Label summaryLabel;
private Label noteLabel;
@@ -31,12 +31,12 @@ public class McpToolsSection
private Button rescanButton;
private Button reconfigureButton;
private VisualElement categoryContainer;
- private List allTools = new();
- private readonly Dictionary groupToggleMap = new();
- private readonly List<(Foldout foldout, string title, List tools)> foldoutEntries = new();
+ private List allTools = new List();
+ private readonly Dictionary groupToggleMap = new Dictionary();
+ private readonly List<(Foldout foldout, string title, List tools)> foldoutEntries = new List<(Foldout foldout, string title, List tools)>();
/// Human-friendly names for tool groups shown in the UI.
- private static readonly Dictionary GroupDisplayNames = new(StringComparer.OrdinalIgnoreCase)
+ private static readonly Dictionary GroupDisplayNames = new Dictionary(StringComparer.OrdinalIgnoreCase)
{
{ "core", "Core Tools" },
{ "vfx", "VFX & Shaders" },
@@ -654,14 +654,14 @@ private VisualElement CreateBatchExecuteSettings()
var field = new IntegerField
{
- value = Math.Clamp(currentValue, 1, BatchExecute.AbsoluteMaxCommandsPerBatch),
+ value = UnityEngine.Mathf.Clamp(currentValue, 1, BatchExecute.AbsoluteMaxCommandsPerBatch),
style = { width = 60 }
};
field.tooltip = $"Number of commands allowed per batch_execute call (1–{BatchExecute.AbsoluteMaxCommandsPerBatch}). Default: {BatchExecute.DefaultMaxCommandsPerBatch}.";
field.RegisterValueChangedCallback(evt =>
{
- int clamped = Math.Clamp(evt.newValue, 1, BatchExecute.AbsoluteMaxCommandsPerBatch);
+ int clamped = UnityEngine.Mathf.Clamp(evt.newValue, 1, BatchExecute.AbsoluteMaxCommandsPerBatch);
if (clamped != evt.newValue)
{
field.SetValueWithoutNotify(clamped);
diff --git a/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefItem.uxml b/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefItem.uxml
index 4d949a17c..53de075e6 100644
--- a/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefItem.uxml
+++ b/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefItem.uxml
@@ -1,4 +1,4 @@
-
+
@@ -10,7 +10,7 @@
-
+
diff --git a/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs b/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs
index 3d011362a..fb418a5c3 100644
--- a/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs
+++ b/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs
@@ -8,6 +8,7 @@
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
+using MCPForUnity.Editor.Windows.Components;
namespace MCPForUnity.Editor.Windows
{
@@ -325,7 +326,7 @@ private VisualElement CreateItemUI(EditorPrefItem item)
var valueField = itemElement.Q("value-field");
valueField.value = item.Value;
- var typeDropdown = itemElement.Q("type-dropdown");
+ var typeDropdown = itemElement.Q("type-dropdown");
typeDropdown.index = (int)item.Type;
// Buttons
diff --git a/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.uxml b/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.uxml
index 0bd30d314..fbb2762df 100644
--- a/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.uxml
+++ b/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.uxml
@@ -1,4 +1,4 @@
-
+
@@ -14,7 +14,7 @@
-
+
diff --git a/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs b/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
index 085593905..dd42aae37 100644
--- a/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
+++ b/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
@@ -1,1108 +1,1222 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using MCPForUnity.Editor.Constants;
-using MCPForUnity.Editor.Helpers;
-using MCPForUnity.Editor.Services;
-using MCPForUnity.Editor.Windows.Components.Advanced;
-using MCPForUnity.Editor.Windows.Components.AssetGen;
-using MCPForUnity.Editor.Windows.Components.Branding;
-using MCPForUnity.Editor.Windows.Components.ClientConfig;
-using MCPForUnity.Editor.Windows.Components.Connection;
-using MCPForUnity.Editor.Windows.Components.Resources;
-using MCPForUnity.Editor.Windows.Components.Tools;
-using MCPForUnity.Editor.Setup;
-using MCPForUnity.Editor.Windows.Components.Validation;
-using UnityEditor;
-using UnityEditor.UIElements;
-using UnityEngine;
-using UnityEngine.UIElements;
-
-namespace MCPForUnity.Editor.Windows
-{
- public class MCPForUnityEditorWindow : EditorWindow
- {
- // Section controllers
- private McpConnectionSection connectionSection;
- private McpClientConfigSection clientConfigSection;
- private McpAdvancedSection advancedSection;
- private McpToolsSection toolsSection;
- private McpResourcesSection resourcesSection;
- private McpAssetGenSection assetGenSection;
-
- // UI Elements
- private Label versionLabel;
- private VisualElement updateNotification;
- private Label updateNotificationText;
-
- private ToolbarToggle clientsTabToggle;
- private ToolbarToggle depsTabToggle;
- private ToolbarToggle advancedTabToggle;
- private ToolbarToggle toolsTabToggle;
- private ToolbarToggle resourcesTabToggle;
- private ToolbarToggle assetGenTabToggle;
- private VisualElement clientsPanel;
- private VisualElement depsPanel;
- private VisualElement advancedPanel;
- private VisualElement toolsPanel;
- private VisualElement resourcesPanel;
- private VisualElement assetGenPanel;
-
- private static readonly HashSet OpenWindows = new();
- private bool guiCreated = false;
- private bool toolsLoaded = false;
- private bool resourcesLoaded = false;
- private double lastRefreshTime = 0;
- private const double RefreshDebounceSeconds = 0.5;
- private bool updateCheckQueued = false;
- private bool updateCheckInFlight = false;
-
- private enum ActivePanel
- {
- Clients,
- Deps,
- Advanced,
- Tools,
- Resources,
- AssetGen
- }
-
- internal static void CloseAllWindows()
- {
- var windows = OpenWindows.Where(window => window != null).ToArray();
- foreach (var window in windows)
- {
- window.Close();
- }
- }
-
- public static void ShowWindow()
- {
- var existingWindows = UnityEngine.Resources.FindObjectsOfTypeAll();
- MCPForUnityEditorWindow window = null;
-
- if (existingWindows.Length > 0)
- {
- window = existingWindows[0];
-
- // If multiple instances exist, keep one and close the extras to avoid stale hidden tabs.
- for (int i = 1; i < existingWindows.Length; i++)
- {
- try
- {
- existingWindows[i].Close();
- }
- catch (Exception ex)
- {
- McpLog.Warn($"Error closing duplicate MCP window: {ex.Message}");
- }
- }
- }
- else
- {
- window = GetWindow(ProductInfo.ProductName);
- }
-
- window.titleContent = new GUIContent(ProductInfo.ProductName);
- window.minSize = new Vector2(500, 340);
-
- if (window.position.width < 100 || window.position.height < 100)
- {
- window.position = new Rect(120, 120, 900, 700);
- }
-
- window.Show();
- window.ShowTab();
- window.Focus();
- }
-
- // Helper to check and manage open windows from other classes
- public static bool HasAnyOpenWindow()
- {
- return OpenWindows.Count > 0;
- }
-
- public static void CloseAllOpenWindows()
- {
- if (OpenWindows.Count == 0)
- return;
-
- // Copy to array to avoid modifying the collection while iterating
- var arr = new MCPForUnityEditorWindow[OpenWindows.Count];
- OpenWindows.CopyTo(arr);
- foreach (var window in arr)
- {
- try
- {
- window?.Close();
- }
- catch (Exception ex)
- {
- McpLog.Warn($"Error closing MCP window: {ex.Message}");
- }
- }
- }
-
- public void CreateGUI()
- {
- // Guard against repeated CreateGUI calls (e.g., domain reloads)
- if (guiCreated)
- return;
-
- string basePath = AssetPathUtility.GetMcpPackageRootPath();
-
- // Load main window UXML
- var visualTree = AssetDatabase.LoadAssetAtPath(
- $"{basePath}/Editor/Windows/MCPForUnityEditorWindow.uxml"
- );
-
- if (visualTree == null)
- {
- McpLog.Error(
- $"Failed to load UXML at: {basePath}/Editor/Windows/MCPForUnityEditorWindow.uxml"
- );
- return;
- }
-
- rootVisualElement.Clear();
- visualTree.CloneTree(rootVisualElement);
-
- // Load main window USS
- var mainStyleSheet = AssetDatabase.LoadAssetAtPath(
- $"{basePath}/Editor/Windows/MCPForUnityEditorWindow.uss"
- );
- if (mainStyleSheet != null)
- {
- rootVisualElement.styleSheets.Add(mainStyleSheet);
- }
-
- // Load common USS
- var commonStyleSheet = AssetDatabase.LoadAssetAtPath(
- $"{basePath}/Editor/Windows/Components/Common.uss"
- );
- if (commonStyleSheet != null)
- {
- rootVisualElement.styleSheets.Add(commonStyleSheet);
- }
-
- // Embed the Ocean brand mark at the left of the header bar
- var headerLeft = rootVisualElement.Q("header-left");
- if (headerLeft != null && headerLeft.Q() == null)
- {
- var logo = new OceanMark { name = "header-logo" };
- logo.AddToClassList("header-logo");
- headerLeft.Insert(0, logo);
- }
-
- // Cache UI elements
- versionLabel = rootVisualElement.Q