What happened?
On a large commercial Unity project (several hundred assemblies), every domain reload spends ~8.9 s inside CommandRegistry.AutoDiscoverCommands() — roughly half of our total ~20 s reload time. Since the registry re-initializes after every reload, we pay this cost on every script compile and every Play Mode entry.
The cause is that discovery enumerates every type of every loaded assembly (UnityAssembliesCompat.GetLoadedAssemblies() → GetTypes()) and then checks GetCustomAttribute<T>() per type, twice (tools + resources). The cost scales with the project's total type count, not with the number of MCP tools.
ToolDiscoveryService.DiscoverAllTools() has the same issue: it runs a TypeCache primary scan but then unconditionally runs the exhaustive AppDomain scan as a fallback and merges the results, so the full-domain scan cost is paid there as well.
Expected: discovery should use UnityEditor.TypeCache.GetTypesWithAttribute<T>(), which is a prebuilt-index lookup and effectively free. The codebase already trusts TypeCache for exactly this pattern — ResourceDiscoveryService is TypeCache-only, and McpClientRegistry, GetMenuItems, and the graphics helpers all use TypeCache. CommandRegistry (and the ToolDiscoveryService fallback) are the remaining exhaustive scans.
Side benefit: TypeCache reads a prebuilt native index instead of reflecting over assembly metadata, which avoids the half-loaded-metadata crash class that led to the AssetImportWorker workaround in #1134.
Regarding the existing comment in ToolDiscoveryService ("TypeCache … can miss project assemblies in some domain-reload states"): if there is a known repro for that state we would love details. Otherwise a conservative fix is TypeCache primary + AppDomain fallback only when TypeCache returns nothing — same coverage guarantees, none of the per-reload cost.
Happy to submit a PR (against beta) for both call sites either way — just let us know which shape you prefer (TypeCache-only vs. lazy fallback).
Reproduction steps
- Install
com.coplaydev.unity-mcp 10.1.0 in a project with a large number of assemblies/types (we see it with several hundred asmdefs; smaller projects pay proportionally less).
- Trigger a domain reload (edit any script and recompile, or enter Play Mode).
- Profile the reload —
CommandRegistry.AutoDiscoverCommands accounts for ~8.9 s of it.
- Replacing the scan with
TypeCache.GetTypesWithAttribute<McpForUnityToolAttribute>() / TypeCache.GetTypesWithAttribute<McpForUnityResourceAttribute>() finds the same tools/resources with discovery time effectively at 0.
Unity version
2021.3.56f2
MCP for Unity package version
10.1.0 (OpenUPM)
Python server version
n/a — editor-side issue; the Python server is not involved.
MCP client
Claude Code
Transport
stdio (the scan runs on every domain reload regardless of transport)
OS
Windows 11
Relevant logs / console output
Editor profiling of a domain reload shows ~8.9 s attributed to CommandRegistry.AutoDiscoverCommands() out of a ~20 s total reload. Happy to attach profiler captures if useful.
Checks
What happened?
On a large commercial Unity project (several hundred assemblies), every domain reload spends ~8.9 s inside
CommandRegistry.AutoDiscoverCommands()— roughly half of our total ~20 s reload time. Since the registry re-initializes after every reload, we pay this cost on every script compile and every Play Mode entry.The cause is that discovery enumerates every type of every loaded assembly (
UnityAssembliesCompat.GetLoadedAssemblies()→GetTypes()) and then checksGetCustomAttribute<T>()per type, twice (tools + resources). The cost scales with the project's total type count, not with the number of MCP tools.ToolDiscoveryService.DiscoverAllTools()has the same issue: it runs a TypeCache primary scan but then unconditionally runs the exhaustive AppDomain scan as a fallback and merges the results, so the full-domain scan cost is paid there as well.Expected: discovery should use
UnityEditor.TypeCache.GetTypesWithAttribute<T>(), which is a prebuilt-index lookup and effectively free. The codebase already trusts TypeCache for exactly this pattern —ResourceDiscoveryServiceis TypeCache-only, andMcpClientRegistry,GetMenuItems, and the graphics helpers all use TypeCache.CommandRegistry(and the ToolDiscoveryService fallback) are the remaining exhaustive scans.Side benefit: TypeCache reads a prebuilt native index instead of reflecting over assembly metadata, which avoids the half-loaded-metadata crash class that led to the AssetImportWorker workaround in #1134.
Regarding the existing comment in
ToolDiscoveryService("TypeCache … can miss project assemblies in some domain-reload states"): if there is a known repro for that state we would love details. Otherwise a conservative fix is TypeCache primary + AppDomain fallback only when TypeCache returns nothing — same coverage guarantees, none of the per-reload cost.Happy to submit a PR (against
beta) for both call sites either way — just let us know which shape you prefer (TypeCache-only vs. lazy fallback).Reproduction steps
com.coplaydev.unity-mcp10.1.0 in a project with a large number of assemblies/types (we see it with several hundred asmdefs; smaller projects pay proportionally less).CommandRegistry.AutoDiscoverCommandsaccounts for ~8.9 s of it.TypeCache.GetTypesWithAttribute<McpForUnityToolAttribute>()/TypeCache.GetTypesWithAttribute<McpForUnityResourceAttribute>()finds the same tools/resources with discovery time effectively at 0.Unity version
2021.3.56f2
MCP for Unity package version
10.1.0 (OpenUPM)
Python server version
n/a — editor-side issue; the Python server is not involved.
MCP client
Claude Code
Transport
stdio (the scan runs on every domain reload regardless of transport)
OS
Windows 11
Relevant logs / console output
Editor profiling of a domain reload shows ~8.9 s attributed to
CommandRegistry.AutoDiscoverCommands()out of a ~20 s total reload. Happy to attach profiler captures if useful.Checks