# Unity MCP HandlerAdapter: Bridging Custom Handlers to the MCP SDK

> Discover how the Unity MCP HandlerAdapter bridges custom handlers to the MCP SDK, registering them as callable tools, accessible resources, and reusable prompts for seamless integration.

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

---

**The HandlerAdapter serves as the central registration bridge in Unity MCP, connecting command, resource, and prompt handlers to the Model Context Protocol (MCP) SDK by exposing them as callable tools, accessible resources, and reusable prompts.**

The Unity MCP project (`isuzu-shiranui/unitymcp`) provides a TypeScript-based server that exposes Unity Editor functionality to LLM-driven workflows through the Model Context Protocol. At the heart of this integration sits the **HandlerAdapter**, which abstracts the complexity of the MCP SDK's registration API and allows developers to focus on handler logic while the adapter manages protocol-level exposure.

## Core Responsibilities of the HandlerAdapter

Located in [`unity-mcp-ts/src/core/HandlerAdapter.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/unity-mcp-ts/src/core/HandlerAdapter.ts), the HandlerAdapter encapsulates three distinct registration pathways corresponding to the three handler types supported by Unity MCP. The adapter receives handler instances from **HandlerDiscovery** and translates their definitions into MCP SDK registrations.

### Registering Command Handlers as Callable Tools

When a command handler implements the `getToolDefinitions` method, the HandlerAdapter iterates through the returned definitions and registers each tool with `McpServer.tool`. This process maps the handler's execution logic to an MCP-compatible callable function.

The adapter extracts tool metadata—including name, description, parameter schema, and annotations—and wraps the handler's `execute` method in a callback that returns properly formatted MCP content:

```typescript
// From unity-mcp-ts/src/core/HandlerAdapter.ts
private registerHandlerTools(handler: ICommandHandler): void {
  const toolDefs = handler.getToolDefinitions?.();
  if (!toolDefs) return;

  for (const [toolName, def] of toolDefs.entries()) {
    this.server.tool(
      toolName,
      def.description,
      def.parameterSchema,
      def.annotations ?? {},
      async (params) => {
        // Extracts command name from toolName (e.g., "cmd_execute" -> "execute")
        const commandName = toolName.split('_')[1] ?? "execute";
        const result = await handler.execute(commandName, params);
        return { content: [{ type: "text", text: JSON.stringify(result) }] };
      }
    );
    console.error(`[INFO] Registered tool: ${toolName}`);
  }
}

```

### Resource Handler Registration with URI Template Support

For resource handlers implementing `IResourceHandler`, the HandlerAdapter inspects the `resourceUriTemplate` property to determine whether the URI contains dynamic parameters. When the template includes `{` and `}` placeholders, the adapter instantiates a **ResourceTemplate**; otherwise, it registers the resource as a static string URI:

```typescript
// From unity-mcp-ts/src/core/HandlerAdapter.ts
public registerResourceHandler(handler: IResourceHandler): void {
  const hasParams = handler.resourceUriTemplate.includes('{') && 
                    handler.resourceUriTemplate.includes('}');
  
  if (hasParams) {
    // Dynamic URI with parameters
    const template = new ResourceTemplate(handler.resourceUriTemplate, { list: undefined });
    this.server.resource(handler.resourceName, template, async (uri, params) =>
      handler.fetchResource(uri, params)
    );
  } else {
    // Static URI
    this.server.resource(handler.resourceName, handler.resourceUriTemplate, async (uri) =>
      handler.fetchResource(uri)
    );
  }
  console.error(`[INFO] Registered resource: ${handler.resourceName}`);
}

```

### Prompt Handler Registration with Parameter Injection

For prompt handlers, the adapter registers each definition from `getPromptDefinitions()` using `McpServer.prompt`. The implementation checks for `additionalProperties` to determine whether the prompt requires parameter substitution via `applyTemplateParams` or returns a static template:

```typescript
// From unity-mcp-ts/src/core/HandlerAdapter.ts
public registerPromptHandler(handler: IPromptHandler): void {
  const defs = handler.getPromptDefinitions();
  if (!defs) return;

  for (const [name, def] of defs.entries()) {
    if (def.additionalProperties && Object.keys(def.additionalProperties).length) {
      // Parameterized prompt
      this.server.prompt(
        name,
        def.description,
        def.additionalProperties,
        async (params) => ({
          messages: [{ 
            role: "user", 
            content: { 
              type: "text", 
              text: this.applyTemplateParams(def.template, params) 
            } 
          }]
        })
      );
    } else {
      // Static prompt
      this.server.prompt(name, def.description, async () => ({
        messages: [{ 
          role: "user", 
          content: { type: "text", text: def.template } 
        }]
      }));
    }
    console.error(`[INFO] Registered prompt: ${name}`);
  }
}

```

## HandlerAdapter Architecture and Discovery Integration

The HandlerAdapter is instantiated once in the server entry point ([`unity-mcp-ts/src/index.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/unity-mcp-ts/src/index.ts)) and passed to **HandlerDiscovery**, which orchestrates the initialization sequence. HandlerDiscovery scans the handlers directory, creates instances of each handler, injects the shared **UnityConnection** singleton, and delegates registration to the adapter:

```typescript
// From unity-mcp-ts/src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { HandlerAdapter } from "./core/HandlerAdapter.js";
import { HandlerDiscovery } from "./core/HandlerDiscovery.js";

const mcpServer = new McpServer({ name: "unity-mcp", version: "1.0.0" });
const adapter = new HandlerAdapter(mcpServer);
const discovery = new HandlerDiscovery(
  adapter,
  UnityConnection.getInstance(),
  new CommandRegistry(),
  new ResourceRegistry(),
  new PromptRegistry()
);

await discovery.discoverAndRegisterHandlers(); // Delegates all registration to the adapter

```

This architecture separates concerns cleanly: HandlerDiscovery manages instantiation and dependency injection, while HandlerAdapter manages MCP protocol compliance and SDK registration. Developers implementing custom handlers in TypeScript or C# need only satisfy the interface contracts (`ICommandHandler`, `IResourceHandler`, or `IPromptHandler`) without understanding the underlying MCP SDK mechanics.

## Summary

- **Central Registration Hub**: The HandlerAdapter in [`unity-mcp-ts/src/core/HandlerAdapter.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/unity-mcp-ts/src/core/HandlerAdapter.ts) abstracts the MCP SDK registration API for tools, resources, and prompts.
- **Dynamic URI Support**: It automatically detects parameterized URI templates in resource handlers and instantiates `ResourceTemplate` objects when needed.
- **Discovery Integration**: The adapter works in tandem with `HandlerDiscovery` to receive handler instances with pre-injected `UnityConnection` dependencies.
- **Protocol Translation**: It handles the conversion between handler-specific return types and the standardized MCP response formats required by `McpServer.tool`, `McpServer.resource`, and `McpServer.prompt`.

## Frequently Asked Questions

### What is the difference between HandlerAdapter and HandlerDiscovery in Unity MCP?

**HandlerDiscovery** ([`unity-mcp-ts/src/core/HandlerDiscovery.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/unity-mcp-ts/src/core/HandlerDiscovery.ts)) is responsible for filesystem scanning, handler instantiation, and dependency injection—including the shared `UnityConnection`. **HandlerAdapter** ([`unity-mcp-ts/src/core/HandlerAdapter.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/unity-mcp-ts/src/core/HandlerAdapter.ts)) receives these fully constructed handler instances and manages their registration with the MCP SDK. Discovery finds the handlers; the adapter exposes them to the protocol.

### How does HandlerAdapter handle URI parameters in resource handlers?

The adapter checks the `resourceUriTemplate` string for the presence of `{` and `}` characters. When found, it wraps the template in a **ResourceTemplate** instance with `{ list: undefined }` options, allowing the MCP SDK to parse URI parameters and pass them to the handler's `fetchResource` method. Static URIs without parameters are registered as plain strings.

### Can HandlerAdapter work with C# handlers, or only TypeScript?

The HandlerAdapter works with any handler satisfying the TypeScript interface contracts (`ICommandHandler`, `IResourceHandler`, `IPromptHandler`). While Unity MCP primarily implements handlers in TypeScript, the architecture supports C# handlers as long as they expose compatible methods for tool definition retrieval, resource fetching, and prompt generation that the adapter can invoke.

### Where is the HandlerAdapter instantiated in the Unity MCP server lifecycle?

The adapter is created in [`unity-mcp-ts/src/index.ts`](https://github.com/isuzu-shiranui/unitymcp/blob/main/unity-mcp-ts/src/index.ts), the server entry point, immediately after the `McpServer` instantiation. It is then passed as a constructor argument to `HandlerDiscovery`, ensuring all handlers discovered during startup are registered through the same adapter instance before the server begins accepting connections.