Unity MCP HandlerAdapter: Bridging Custom Handlers to the MCP SDK
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, 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:
// 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:
// 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:
// 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) 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:
// 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.tsabstracts the MCP SDK registration API for tools, resources, and prompts. - Dynamic URI Support: It automatically detects parameterized URI templates in resource handlers and instantiates
ResourceTemplateobjects when needed. - Discovery Integration: The adapter works in tandem with
HandlerDiscoveryto receive handler instances with pre-injectedUnityConnectiondependencies. - Protocol Translation: It handles the conversion between handler-specific return types and the standardized MCP response formats required by
McpServer.tool,McpServer.resource, andMcpServer.prompt.
Frequently Asked Questions
What is the difference between HandlerAdapter and HandlerDiscovery in Unity MCP?
HandlerDiscovery (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) 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, 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.
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 →