#1398·unity-mcp

Unity 6000.6: InstanceIDToObjectCompat throws NotImplementedException for every call, breaking manage_components and gameobject resources

Author: yuseresCreated Sep 13, 2026Updated Sep 13, 2026

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:81

Root 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:

csharp
// 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 }); // -> NotImplementedException

So on 6000.6.0f1, both int-based reverse-resolution paths are broken at the engine level. The only path that actually works is:

csharp
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:

csharp
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:

csharp
#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 main via UPM git dependency)
  • Windows 11