Unity 6000.6: InstanceIDToObjectCompat throws NotImplementedException for every call, breaking manage_components and gameobject resources
Description
On Unity 6000.6.0f1, UnityObjectIdCompat.InstanceIDToObjectCompat(int) throws NotImplementedException for every call, which breaks manage_components (set_property, add) whenever the target GameObject is resolved via GameObjectLookup (including search_method="by_name", since GameObjectLookup.FindByTarget round-trips through an instance ID internally), and breaks the mcpforunity://scene/gameobject/{id}/components and mcpforunity://scene/gameobject/{id} resources entirely.
Stack trace
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.NotImplementedException: The method or operation is not implemented.
at UnityEditor.EditorUtility.InstanceIDToObject (System.Int32 instanceID)
at MCPForUnity.Runtime.Helpers.UnityObjectIdCompat.InstanceIDToObjectCompat (System.Int32 instanceId) in .\Library\PackageCache\com.coplaydev.unity-mcp@045e809812a8\Runtime\Helpers\UnityObjectIdCompat.cs:66
at MCPForUnity.Editor.Helpers.GameObjectLookup.ResolveInstanceID (System.Int32 instanceId) in .\Library\PackageCache\com.coplaydev.unity-mcp@045e809812a8\Editor\Helpers\GameObjectLookup.cs:73
at MCPForUnity.Editor.Helpers.GameObjectLookup.FindById (System.Int32 instanceId) in .\Library\PackageCache\com.coplaydev.unity-mcp@045e809812a8\Editor\Helpers\GameObjectLookup.cs:81Root cause
UnityObjectIdCompat.InstanceIDToObjectCompat gates on UNITY_6000_6_OR_NEWER and reflects into EditorUtility.InstanceIDToObject(int) to sidestep the [Obsolete(..., true)] CS0619 compile error. That reflection call succeeds (MethodInfo is found), but invoking it throws NotImplementedException — on 6000.6.0f1 this method's body is a hard stub, not merely obsolete-marked. It's not a reflection-target-resolution bug; the underlying Unity API itself throws unconditionally.
I verified this directly by executing C# in a live 6000.6.0f1 Editor session via reflection:
// Both throw System.NotImplementedException at the Unity-engine level, not in MCPForUnity's code:
typeof(UnityEditor.EditorUtility)
.GetMethod("InstanceIDToObject", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(int) }, null)
.Invoke(null, new object[] { someInstanceId }); // -> NotImplementedException
// EntityId's implicit int -> EntityId conversion is *also* an unimplemented stub on 6000.6.0f1:
typeof(UnityEngine.EntityId)
.GetMethods(...).First(m => m.Name == "op_Implicit" && m.GetParameters()[0].ParameterType == typeof(int))
.Invoke(null, new object[] { someInstanceId }); // -> NotImplementedExceptionSo on 6000.6.0f1, both int-based reverse-resolution paths are broken at the engine level. The only path that actually works is:
EditorUtility.EntityIdToObject(EntityId.FromULong(fullUlongValue))...but that requires the full 64-bit EntityId value. GetInstanceIDCompat (the forward direction) already discards this by truncating to int:
return (int)EntityId.ToULong(obj.GetEntityId());So by the time InstanceIDToObjectCompat receives the int, the high 32 bits needed to reconstruct a valid EntityId via FromULong are already gone — there's no way to recover them from the int alone.
Suggested fix
Rather than widening the int wire format everywhere (a much larger change touching ~30 call sites across the package plus the Python-side protocol), cache the full 64-bit value behind the truncated int at mint time in GetInstanceIDCompat, and use that cache to resolve in InstanceIDToObjectCompat instead of trying to reconstruct the EntityId from the int alone:
#if UNITY_6000_6_OR_NEWER
private static readonly Dictionary<int, ulong> _fullIdCache = new Dictionary<int, ulong>();
#endif
public static int GetInstanceIDCompat(this Object obj)
{
if (obj == null) return 0;
#if UNITY_6000_5_OR_NEWER
ulong full = EntityId.ToULong(obj.GetEntityId());
int truncated = (int)full;
#if UNITY_6000_6_OR_NEWER
_fullIdCache[truncated] = full;
#endif
return truncated;
#else
return obj.GetInstanceID();
#endif
}
public static Object InstanceIDToObjectCompat(int instanceId)
{
#if UNITY_6000_6_OR_NEWER
if (_fullIdCache.TryGetValue(instanceId, out ulong full))
{
return EditorUtility.EntityIdToObject(EntityId.FromULong(full));
}
return null;
#elif UNITY_6000_3_OR_NEWER
return EditorUtility.EntityIdToObject(instanceId);
#else
return EditorUtility.InstanceIDToObject(instanceId);
#endif
}This preserves the existing session-scoped int handle contract exactly as already documented on GetInstanceIDCompat (ids only need to resolve within the same Editor session/domain — a static Dictionary field naturally resets on domain reload, matching that scope), and requires no changes to any of the ~30 call sites or the wire protocol.
I've verified this fix end-to-end against a live 6000.6.0f1 project: manage_components set_property by name, manage_components set_property by a cross-call instance ID, and the mcpforunity://scene/gameobject/{id}/components resource read all work correctly afterward, with no new console errors.
I'll open a PR with this fix.
Environment
- Unity 6000.6.0f1
- com.coplaydev.unity-mcp 10.2.0 (resolved from
mainvia UPM git dependency) - Windows 11
Source: CoplayDev/unity-mcp