# How Handler Discovery Works in Unity MCP for TypeScript: Automatic Runtime Registration Explained

> Discover how Unity MCP automatically registers TypeScript handlers at runtime. Learn about dynamic module import and type guard validation by scanning the compiled handlers directory.

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

---

**Unity MCP automatically discovers, instantiates, and registers TypeScript handlers at runtime by scanning the compiled `handlers` directory, dynamically importing JavaScript modules, and validating instances against type guards before wiring them to the MCP server.**

Unity MCP (`isuzu-shiranui/unitymcp`) implements a plug-and-play architecture for its TypeScript server that eliminates manual registration boilerplate. The [`HandlerDiscovery.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/HandlerDiscovery.ts) module orchestrates a ten-step runtime pipeline that transforms static handler files into live MCP tools, resources, and prompts without requiring code changes to the core server.

## The Runtime Discovery Pipeline in HandlerDiscovery.ts

The discovery process is encapsulated in [`unity-mcp-ts/src/core/HandlerDiscovery.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/unity-mcp-ts/src/core/HandlerDiscovery.ts) and executes automatically when the MCP server initializes.

### Scanning and Importing Handler Modules

The discovery class first resolves the handlers directory relative to its own location using `__dirname`, then reads all compiled JavaScript files:

```typescript
const handlersDir = join(__dirname, '../handlers');
const files = await fs.readdir(handlersDir);
const jsFiles = files.filter(file => file.endsWith('.js'));

```

Since the TypeScript source compiles to JavaScript before execution, the system specifically targets `.js` files. Each file undergoes an ES-module dynamic import:

```typescript
const module = await import(`../handlers/${file}`);

```

### Instantiation and Initialization

For every exported member in the imported module, the discovery logic checks if the export is a constructor function with a prototype. Valid classes are instantiated and immediately initialized with the shared `UnityConnection` instance:

```typescript
const instance = new exportedValue();
instance.initialize(this.unityConnection);

```

This initialization step ensures every handler receives the connection object required to communicate with the Unity editor before registration proceeds.

### Type-Guard Validation and Registration

The discovery system implements three private type-guards to enforce interface contracts: `isCommandHandler()`, `isResourceHandler()`, and `isPromptHandler()`. These functions verify that instances expose the required members, such as `commandPrefix` and `execute()` for commands, or `resourceUriTemplate` and `fetchResource()` for resources.

Once validated, handlers are stored in their respective registries (`CommandRegistry`, `ResourceRegistry`, `PromptRegistry`) and simultaneously adapted to the MCP SDK via [`HandlerAdapter.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/HandlerAdapter.ts):

```typescript
this.commandRegistry.registerHandler(instance);
this.adapter.registerCommandHandler(instance);

```

The system maintains counters for each `HandlerType` and logs successful registrations to stderr with the format `[INFO] Registered command handler: ${instance.commandPrefix}`.

## Interface Contracts and Type Safety

Handler implementations must satisfy one of three interfaces defined in `unity-mcp-ts/src/core/interfaces/` to pass discovery validation.

**ICommandHandler** requires `commandPrefix`, `description`, `execute(action, parameters)`, and `initialize(connection)`, plus optional `getToolDefinitions()` for exposing tools to the MCP UI.

**IResourceHandler** mandates `resourceName`, `resourceUriTemplate`, `fetchResource(uri, parameters?)`, and `initialize(connection)` for URI-based data providers.

**IPromptHandler** specifies `promptName`, `getPromptDefinitions()`, and `initialize(connection)` for template-based chat prompts.

The type-guards in [`HandlerDiscovery.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/HandlerDiscovery.ts) perform runtime structural typing checks, ensuring that only valid handlers reach the registration phase while providing clear error boundaries that log `[ERROR]` messages for failed imports or invalid exports.

## Bridging Discovery to the MCP Server

[`HandlerAdapter.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/HandlerAdapter.ts) ([`unity-mcp-ts/src/core/HandlerAdapter.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/unity-mcp-ts/src/core/HandlerAdapter.ts)) serves as the bridge between discovered handlers and the MCP SDK's `McpServer` instance. It translates internal handler representations into standard MCP protocol registrations:

- **Command handlers** become tools via `server.tool()`, utilizing definitions from `getToolDefinitions()` when available
- **Resource handlers** register as resources or resource templates via `server.resource()` depending on whether their URIs contain template parameters
- **Prompt handlers** expose prompts through `server.prompt()` with parameter injection support

This adapter pattern separates the discovery mechanics from protocol-specific implementation details, allowing the discovery system to remain agnostic of the underlying MCP SDK version.

## Implementing Custom Handlers

Adding functionality requires only creating a new file in `unity-mcp-ts/src/handlers/` that exports a class implementing one of the required interfaces.

### Command Handler Example

```typescript
// File: unity-mcp-ts/src/handlers/MyAwesomeCommandHandler.ts
import { BaseCommandHandler } from "../core/BaseCommandHandler.js";

export class MyAwesomeCommandHandler extends BaseCommandHandler {
    public get commandPrefix() { return "awesome"; }
    public get description()  { return "Demo command that greets the user"; }

    public async execute(action: string, params: any) {
        if (action === "greet") {
            return { success: true, message: `Hello, ${params.name ?? "world"}!` };
        }
        return { success: false, error: "Unsupported action" };
    }

    public getToolDefinitions() {
        return new Map([
            ["awesome_greet", {
                description: "Greets a user",
                parameterSchema: { name: z.string().optional() },
                annotations: { title: "Greet", readOnlyHint: true }
            }]
        ]);
    }
}

```

Upon server startup, `HandlerDiscovery` finds the compiled [`MyAwesomeCommandHandler.js`](https://github.com/isuzu-shiranui/unitymcp/blob/main/MyAwesomeCommandHandler.js), instantiates the class, validates it against `isCommandHandler`, and exposes the `awesome_greet` tool through the adapter.

### Resource Handler Example

```typescript
// File: unity-mcp-ts/src/handlers/AssetResourceHandler.ts
import { BaseResourceHandler } from "../core/BaseResourceHandler.js";

export class AssetResourceHandler extends BaseResourceHandler {
    public get resourceName() { return "unity/asset"; }
    public get resourceUriTemplate() { return "unity://asset/{guid}"; }
    public get description() { return "Fetch Unity assets by GUID"; }

    public async fetchResource(uri: string, parameters: any) {
        const asset = await this.unityConnection.request("asset.get", parameters);
        return asset;
    }
}

```

Because the `resourceUriTemplate` contains a `{guid}` parameter, `HandlerAdapter` automatically registers this as a `ResourceTemplate`, enabling clients to request specific assets via URIs like `unity://asset/1234abcd`.

## Summary

- **Automatic Discovery**: [`HandlerDiscovery.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/HandlerDiscovery.ts) scans `../handlers` relative to `__dirname`, filtering for `.js` files to accommodate the TypeScript compilation pipeline
- **Dynamic Loading**: ES-module dynamic imports load handler classes at runtime, enabling true plug-and-play extensibility without server restarts or configuration changes
- **Type Safety**: Runtime type-guards (`isCommandHandler`, `isResourceHandler`, `isPromptHandler`) enforce interface contracts before registration
- **Unified Initialization**: Every handler receives a `UnityConnection` instance via `initialize()` before registration, ensuring immediate access to Unity editor communication
- **SDK Integration**: [`HandlerAdapter.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/HandlerAdapter.ts) translates discovered handlers into standard MCP tools, resources, and prompts, bridging the custom handler architecture with the protocol SDK

## Frequently Asked Questions

### Why does the discovery system scan `.js` files instead of `.ts` files?

The TypeScript server must compile to JavaScript before Node.js can execute it. The discovery mechanism runs against the compiled output in the `handlers` directory, targeting `.js` files because that is what the Node.js runtime actually loads when executing `import()` statements against the compiled `unity-mcp-ts` package.

### What happens if a handler fails to import or instantiate?

Any failure during dynamic import, class instantiation, or initialization is caught by the error handling boundary in [`HandlerDiscovery.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/HandlerDiscovery.ts) and logged to stderr with an `[ERROR]` prefix containing the specific failure reason. The failed handler is skipped, but the discovery process continues processing remaining handlers, ensuring that one broken file does not crash the entire MCP server.

### Can a single handler implement multiple interface types?

While the discovery system checks each exported class against all three type-guards sequentially, the architecture expects handlers to implement only one primary interface (Command, Resource, or Prompt) for clarity and separation of concerns. However, if a class satisfies multiple guards, it would theoretically be registered in multiple registries, though this pattern is not used in the standard handler implementations like [`ConsoleCommandHandler.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/ConsoleCommandHandler.ts) or [`PackageResourceHandler.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/PackageResourceHandler.ts).

### How can I verify that my handler was discovered successfully?

Check the server logs for `[INFO] Registered command handler: your-prefix` (or the equivalent resource/prompt message). If you do not see this log entry immediately after server startup, verify that your file ends with `.js` after compilation, exports the class as a named export, and that the class properly implements all required interface methods for the type-guard to pass validation.