How Resources Are Handled in Unity MCP: A Complete Technical Guide
Unity MCP handles resources through a modular pipeline that discovers handlers in src/handlers/*.ts, registers them with ResourceRegistry, and exposes them via HandlerAdapter to the MCP server, using BaseResourceHandler to manage Unity connections and transform data.
Unity MCP (Model-Context-Protocol) treats resources as read-only data endpoints that allow clients to query live information from a running Unity editor. Understanding how resources are handled in Unity MCP requires examining the TypeScript architecture that bridges the MCP SDK with Unity's internal state.
The Resource Handling Pipeline
The architecture splits resource management into four distinct phases: discovery, registration, connection handling, and resource-specific logic. This separation allows developers to add new resources by creating a single handler class without modifying core infrastructure.
When the server starts, HandlerDiscovery scans the src/handlers directory, instantiates each class, and checks for the IResourceHandler interface. Valid handlers are stored in ResourceRegistry and adapted to MCP resources via HandlerAdapter. When a client requests a URI like unity://packages?includeRegistry=true, the system invokes the handler's fetchResource method, which communicates with Unity through BaseResourceHandler and returns transformed JSON.
Handler Discovery and Registration
Automatic Discovery in src/handlers
The HandlerDiscovery class implements a filesystem-based plugin system. It reads all TypeScript files in src/handlers, dynamically imports them, and instantiates exported classes:
// src/core/HandlerDiscovery.ts (lines 55-84)
const files = await fs.readdir(handlersDir);
for (const file of jsFiles) {
const module = await import(`../handlers/${file}`);
for (const exportName in module) {
const instance = new module[exportName]();
instance.initialize(this.unityConnection);
if (this.isResourceHandler(instance)) {
this.resourceRegistry.registerHandler(instance);
this.adapter.registerResourceHandler(instance);
}
}
}
This pattern ensures that adding a new resource requires only creating a file in src/handlers that exports a class implementing IResourceHandler.
ResourceRegistry Registration
The ResourceRegistry maintains three internal maps: handlers by name, enabled states, and URI templates to resource names. Registration fails if a resource name is empty or already exists:
// src/core/ResourceRegistry.ts (lines 11-33)
private handlers: Map<string, IResourceHandler> = new Map();
public registerHandler(handler: IResourceHandler, enabled: boolean = true): boolean {
const resourceName = handler.resourceName;
if (!resourceName || this.handlers.has(resourceName)) return false;
this.handlers.set(resourceName, handler);
this.enabledState.set(resourceName, enabled);
if (handler.resourceUriTemplate) this.uriMap.set(handler.resourceUriTemplate, resourceName);
return true;
}
This registry acts as the single source of truth for which resources are available and active.
BaseResourceHandler and the Template Method Pattern
All concrete resource handlers extend BaseResourceHandler, which implements the Template Method pattern to enforce consistent connection management and response formatting.
Connection Management
The fetchResource method guarantees a live Unity connection before executing resource-specific logic:
// src/core/BaseResourceHandler.ts (lines 49-68)
public async fetchResource(uri: URL, parameters?: JObject) {
await this.ensureUnityConnection();
const result = await this.fetchResourceData(uri, parameters);
return { contents: [{ uri: uri.href, text: JSON.stringify(result), mimeType: 'application/json' }] };
}
The ensureUnityConnection method checks if the WebSocket or IPC connection to Unity is active, throwing an error if the editor is unreachable.
Request Dispatch to Unity
Concrete handlers use sendUnityRequest to communicate with Unity's internal API:
// src/core/BaseResourceHandler.ts (lines 17-26)
protected async sendUnityRequest(command: string, parameters: JObject): Promise<JObject> {
await this.ensureUnityConnection();
return this.unityConnection!.sendRequest({ command, type: "resource", params: parameters });
}
This method standardizes the request envelope, setting the type to "resource" and passing parameters to Unity's command dispatcher.
Concrete Resource Implementations
PackageResourceHandler (unity://packages)
The PackageResourceHandler queries Unity's Package Manager for installed and registry packages. It supports the includeRegistry parameter via URI query string or direct parameters:
// src/handlers/PackageResourceHandler.ts (lines 31-76)
protected async fetchResourceData(uri: URL, parameters?: JObject) {
const includeRegistry = parameters?.includeRegistry === true ||
uri.searchParams.get("includeRegistry") === "true";
const response = await this.sendUnityRequest("packages.get", { includeRegistry });
if (!response.success) throw new Error(response.error as string);
return {
projectPackages: response.projectPackages.map(pkg => ({
name: pkg.name, displayName: pkg.displayName, version: pkg.version,
description: pkg.description, category: pkg.category,
source: pkg.source, state: pkg.state, author: pkg.author
})),
registryPackages: includeRegistry ? response.registryPackages.map(pkg => ({
name: pkg.name, displayName: pkg.displayName, version: pkg.version,
description: pkg.description, category: pkg.category,
source: pkg.source, state: pkg.state, author: pkg.author
})) : []
};
}
This handler transforms Unity's internal package representation into a clean JSON schema suitable for LLM consumption.
AssemblyResourceHandler (unity://assemblies)
The AssemblyResourceHandler retrieves loaded assemblies from Unity's AppDomain, supporting filters for system, Unity, and project assemblies:
// src/handlers/AssemblyResourceHandler.ts (lines 36-62)
protected async fetchResourceData(uri: URL, parameters?: JObject) {
const includeSystem = parameters?.includeSystemAssemblies === true ||
uri.searchParams.get("includeSystemAssemblies") === "true";
const includeUnity = parameters?.includeUnityAssemblies !== false &&
uri.searchParams.get("includeUnityAssemblies") !== "false";
const includeProject = parameters?.includeProjectAssemblies !== false &&
uri.searchParams.get("includeProjectAssemblies") !== "false";
const response = await this.sendUnityRequest("assemblies.get", {
includeSystemAssemblies: includeSystem,
includeUnityAssemblies: includeUnity,
includeProjectAssemblies: includeProject
});
if (!response.success) throw new Error(response.error as string);
return { assemblies: response.assemblies || [], count: response.count || 0 };
}
Both handlers demonstrate the pattern: parse parameters, call sendUnityRequest with a Unity command, validate success, and return structured data.
Exposing Resources to the MCP Server
The HandlerAdapter class bridges the internal handler system with the official MCP SDK. It registers handlers as MCP resources, supporting both static URIs and URI templates with parameters:
// src/core/HandlerAdapter.ts (lines 35-71)
if (handler.resourceUriTemplate.includes('{')) {
const template = new ResourceTemplate(handler.resourceUriTemplate, {list: undefined});
this.server.resource(handler.resourceName, template, async (uri, parameters) => {
return await handler.fetchResource(uri, parameters);
});
} else {
this.server.resource(handler.resourceName, handler.resourceUriTemplate, async (uri) => {
return await handler.fetchResource(uri);
});
}
This adapter determines whether to use ResourceTemplate for parameterized URIs or simple string registration for static resources. Once registered, the MCP server handles the network protocol, leaving the Unity MCP handlers to focus solely on data retrieval and transformation.
Summary
- Unity MCP resources are read-only data endpoints that query live Unity editor state through a structured pipeline.
- Handler discovery automatically loads classes from
src/handlers/*.tsthat implementIResourceHandler, registering them withResourceRegistry. - BaseResourceHandler enforces connection management via
ensureUnityConnection()and standardizes Unity communication throughsendUnityRequest(). - Concrete handlers like
PackageResourceHandlerandAssemblyResourceHandlerparse URI parameters, execute Unity commands (packages.get,assemblies.get), and transform responses into clean JSON. - HandlerAdapter bridges internal handlers to the MCP SDK, supporting both static URIs and templated URIs with parameters.
Frequently Asked Questions
How does Unity MCP discover new resource handlers?
Unity MCP uses the HandlerDiscovery class to scan the src/handlers directory at startup. It dynamically imports each TypeScript file, instantiates exported classes, and checks if they implement the IResourceHandler interface. Valid handlers are automatically registered with ResourceRegistry and exposed through HandlerAdapter without requiring manual configuration.
What is the difference between a resource URI and a resource URI template in Unity MCP?
A resource URI is a static string like unity://packages that identifies a single endpoint. A resource URI template contains parameterized segments (e.g., unity://objects/{id}) that allow dynamic values. The HandlerAdapter detects templates by checking for { characters and registers them with the MCP SDK's ResourceTemplate class, enabling parameterized queries.
How does Unity MCP maintain connection stability when fetching resources?
All resource handlers extend BaseResourceHandler, which implements the Template Method pattern. Before executing any resource-specific logic, the fetchResource method calls ensureUnityConnection() to verify the WebSocket or IPC link to Unity is active. If the connection is lost, the method throws an error before attempting to send requests, preventing partial failures and ensuring consistent error handling across all resources.
Can I create a custom resource handler for Unity MCP?
Yes, creating a custom resource requires implementing a class that extends BaseResourceHandler and implements IResourceHandler. You must define resourceName, description, and resourceUriTemplate (or resourceUri), then override fetchResourceData to parse parameters and return data. Place the file in src/handlers/, and the HandlerDiscovery system will automatically register it on the next server startup.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →