# Performance Optimization Tips for Unity MCP Handlers: 7 Proven Techniques

> Boost Unity MCP handler performance with 7 proven techniques. Cache metadata, paginate data, and skip system assemblies for responsive, high-frequency editor operations.

- Repository: [いすず/unitymcp](https://github.com/isuzu-shiranui/unitymcp)
- Tags: performance
- Published: 2026-03-04

---

**Cache reflection metadata, paginate large datasets, and skip system assemblies to keep Unity MCP handlers responsive during high-frequency editor operations.**

Unity MCP (Message-Command Protocol) handlers execute inside the Unity Editor and are invoked on-demand from external tools. Because these handlers frequently rely on reflection, assembly scanning, and blocking Editor APIs, small inefficiencies can compound into noticeable lag. The `isuzu-shiranui/unitymcp` repository demonstrates several battle-tested patterns for performance optimization in Unity MCP handlers; applying these patterns ensures your custom handlers remain lightweight and responsive.

## 1. Limit Assembly Scanning to Reduce Startup Overhead

The handler discovery mechanism in [`McpHandlerDiscovery.cs`](https://github.com/isuzu-shiranui/unitymcp/blob/main/McpHandlerDiscovery.cs) intentionally skips large system and Unity assemblies to prevent expensive type inspection during startup.

```csharp
// McpHandlerDiscovery.cs – skips system & Unity assemblies
if (assembly.FullName.StartsWith("System.") ||
    assembly.FullName.StartsWith("Unity.") ||
    assembly.FullName.StartsWith("UnityEngine.") ||
    assembly.FullName.StartsWith("UnityEditor."))
{
    continue;   // ← avoids scanning huge built‑in assemblies
}

```

*Source: [McpHandlerDiscovery.cs lines 39‑44](https://github.com/isuzu-shiranui/unitymcp/blob/main/jp.shiranui-isuzu.unity-mcp/Editor/Core/McpHandlerDiscovery.cs#L39-L44)*

**Tip:** Place custom handlers in a dedicated assembly (e.g., `MyCompany.MCP.Handlers`) so the discovery loop exits early after scanning only your specific types.

## 2. Cache Reflection Metadata in Static Constructors

`ConsoleCommandHandler` eliminates per-call reflection overhead by caching `MethodInfo` and `Type` objects in a static constructor. This ensures costly `GetMethod` and `GetType` lookups execute only once per Editor session.

```csharp
static ConsoleCommandHandler()
{
    // Cache reflection data for LogEntries only once
    LogEntriesType = typeof(UnityEditor.EditorWindow).Assembly.GetType("UnityEditor.LogEntries");
    StartGettingEntriesMethod = LogEntriesType.GetMethod("StartGettingEntries", BindingFlags.Public | BindingFlags.Static);
    // … other methods cached here …
}

```

*Source: [ConsoleCommandHandler.cs lines 26‑46](https://github.com/isuzu-shiranui/unitymcp/blob/main/jp.shiranui-isuzu.unity-mcp/Editor/Handlers/ConsoleCommandHandler.cs#L26-L46)*

**Tip:** Any handler relying on reflection should adopt this static-caching pattern to avoid repeated metadata lookups during high-frequency command execution.

## 3. Implement Pagination for Large Dataset Retrieval

Fetching the entire Unity Console log can return massive datasets. `ConsoleCommandHandler.GetLogs` accepts `startRow` and `count` parameters, clamps the request to the actual log count, and returns only the requested slice.

```csharp
var startRow = parameters["startRow"]?.Value<int>() ?? 0;
var count     = parameters["count"]?.Value<int>() ?? 100;

// Clamp to available logs
var totalCount = (int)GetCountMethod.Invoke(null, null);
count = Math.Min(count, totalCount - startRow);

```

*Source: [ConsoleCommandHandler.cs lines 10‑16](https://github.com/isuzu-shiranui/unitymcp/blob/main/jp.shiranui-isuzu.unity-mcp/Editor/Handlers/ConsoleCommandHandler.cs#L10-L16)*

**Tip:** Design new handlers to expose pagination parameters (`offset`/`limit`) and enforce a maximum batch size (e.g., 200 items) to keep memory usage predictable.

## 4. Replace Blocking Waits with Asynchronous Callbacks

`PackagesResourceHandler` currently polls `Client.List` and `Client.SearchAll` with a `Thread.Sleep(100)` loop. While functional, this blocks the calling thread and can stall the Editor when many requests queue up.

```csharp
while (!listRequest.IsCompleted)
{
    Thread.Sleep(100);
}

```

*Source: [PackagesResourceHandler.cs lines 75‑78](https://github.com/isuzu-shiranui/unitymcp/blob/main/jp.shiranui-isuzu.unity-mcp/Editor/Handlers/PackagesResourceHandler.cs#L75-L78)*

**Tip:** Switch to Unity’s asynchronous callbacks (`listRequest.Completed += ...`) or use `EditorApplication.update` to poll without sleeping. This keeps the main thread responsive and reduces UI stutter.

## 5. Add Early-Exit Guard Clauses

Both console and package handlers check for empty results before entering costly loops, returning a minimal JSON payload immediately.

```csharp
if (count <= 0 || startRow >= totalCount)
{
    return new JObject { ["success"] = true, ["logs"] = new JArray(), ["totalCount"] = totalCount };
}

```

*Source: [ConsoleCommandHandler.cs lines 17‑24](https://github.com/isuzu-shiranui/unitymcp/blob/main/jp.shiranui-isuzu.unity-mcp/Editor/Handlers/ConsoleCommandHandler.cs#L17-L24)*

**Tip:** Insert guard clauses at the top of any heavy operation to avoid unnecessary processing when data is missing or parameters are out of bounds.

## 6. Minimize Allocations in Processing Loops

When iterating over log entries or packages, the code re-uses a single `JObject` per entry and adds it to a `JArray`. Avoiding temporary collections inside loops reduces GC pressure.

```csharp
var logs = new JArray();
for (var i = 0; i < count; i++)
{
    // … create logEntry, read fields, then add a new JObject once
    logs.Add(new JObject { ... });
}

```

*Source: [ConsoleCommandHandler.cs lines 32‑64](https://github.com/isuzu-shiranui/unitymcp/blob/main/jp.shiranui-isuzu.unity-mcp/Editor/Handlers/ConsoleCommandHandler.cs#L32-L64)*

**Tip:** Pool reusable objects or use struct-based containers for high-frequency loops to minimize garbage collection spikes in the Editor.

## 7. Use Strongly-Typed Parameters Instead of Re-Parsing JSON Repeatedly

Handlers currently read parameters from a `JObject` each call (`parameters["count"]?.Value<int>()`). If a handler is invoked many times with the same shape, parse the JSON once into a C# DTO and reuse it.

**Tip:** Add a small helper method like `private static T GetValue<T>(JObject obj, string key, T fallback)` to reduce repetitive `?.Value<T>()` calls, or define a strongly-typed options class and deserialize once.

## Key Implementation Files

| File | Role | Performance Relevance |
|------|------|----------------------|
| [`Editor/Core/McpHandlerDiscovery.cs`](https://github.com/isuzu-shiranui/unitymcp/blob/main/Editor/Core/McpHandlerDiscovery.cs) | Scans assemblies and registers handlers | Demonstrates the *skip-system-assembly* optimization to reduce startup time |
| [`Editor/Handlers/ConsoleCommandHandler.cs`](https://github.com/isuzu-shiranui/unitymcp/blob/main/Editor/Handlers/ConsoleCommandHandler.cs) | Provides console log access | Shows *static reflection caching*, *pagination*, and *early-exit* patterns |
| [`Editor/Handlers/PackagesResourceHandler.cs`](https://github.com/isuzu-shiranui/unitymcp/blob/main/Editor/Handlers/PackagesResourceHandler.cs) | Retrieves Unity package data | Contains the *blocking wait* anti-pattern and serves as the target for async refactoring |
| [`Editor/Handlers/AssembliesResourceHandler.cs`](https://github.com/isuzu-shiranui/unitymcp/blob/main/Editor/Handlers/AssembliesResourceHandler.cs) | Lists available assemblies | Uses the same discovery flow; extend the same optimizations here |
| [`Editor/Handlers/MenuItemCommandHandler.cs`](https://github.com/isuzu-shiranui/unitymcp/blob/main/Editor/Handlers/MenuItemCommandHandler.cs) | Executes menu-item commands | Handler invocation site; apply *max-batch* limits here if processing multiple items |

## Summary

- **Skip large assemblies** during discovery by filtering out `System.*`, `Unity.*`, and `UnityEngine.*` namespaces in [`McpHandlerDiscovery.cs`](https://github.com/isuzu-shiranui/unitymcp/blob/main/McpHandlerDiscovery.cs).
- **Cache reflection metadata** in static constructors to eliminate per-call `GetMethod`/`GetType` overhead, following the `ConsoleCommandHandler` pattern.
- **Paginate all data retrieval** by accepting `startRow`/`count` parameters and clamping to actual dataset size to prevent memory spikes.
- **Replace blocking sleeps** with asynchronous callbacks or `EditorApplication.update` polling to keep the Unity Editor UI responsive.
- **Add early-exit guard clauses** to return minimal JSON payloads immediately when parameters are invalid or datasets are empty.
- **Minimize allocations** by reusing `JObject` instances and avoiding temporary collections inside tight loops.

## Frequently Asked Questions

### How does assembly scanning impact Unity MCP handler performance?

Assembly scanning occurs during handler discovery and can add significant startup latency if the framework inspects every loaded assembly. The `isuzu-shiranui/unitymcp` repository mitigates this by explicitly skipping `System.*`, `Unity.*`, `UnityEngine.*`, and `UnityEditor.*` assemblies in [`McpHandlerDiscovery.cs`](https://github.com/isuzu-shiranui/unitymcp/blob/main/McpHandlerDiscovery.cs), reducing the reflection overhead to only user-defined handler assemblies.

### What is the best way to cache reflection data in MCP handlers?

The optimal approach is to use a static constructor to populate `Type` and `MethodInfo` fields once per editor session. The `ConsoleCommandHandler` in [`Editor/Handlers/ConsoleCommandHandler.cs`](https://github.com/isuzu-shiranui/unitymcp/blob/main/Editor/Handlers/ConsoleCommandHandler.cs) demonstrates this by caching `LogEntriesType` and related methods at lines 26-46, ensuring that subsequent command executions invoke the cached delegates without repeated `GetMethod` lookups.

### Why should I avoid Thread.Sleep in Unity MCP package handlers?

`Thread.Sleep` blocks the calling thread, which in the Unity Editor context can stall the main thread and cause UI stutter when multiple requests queue up. The [`PackagesResourceHandler.cs`](https://github.com/isuzu-shiranui/unitymcp/blob/main/PackagesResourceHandler.cs) currently uses `while (!listRequest.IsCompleted) { Thread.Sleep(100); }` at lines 75-78. Replacing this with the `Completed` event callback or `EditorApplication.update` polling keeps the editor responsive during package operations.

### How can I implement pagination in custom Unity MCP handlers?

Expose `offset` (or `startRow`) and `limit` (or `count`) parameters in your handler's JSON input, then clamp the requested range to the actual dataset size before processing. As shown in [`ConsoleCommandHandler.cs`](https://github.com/isuzu-shiranui/unitymcp/blob/main/ConsoleCommandHandler.cs) lines 10-16, calculate `Math.Min(count, totalCount - startRow)` and return only the sliced subset. Always enforce a hard maximum batch size (e.g., 200 items) to prevent memory spikes.