Unity MCP Integration
by quazaai
Verified
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { WebSocketHandler } from './websocketHandler.js';
import { registerTools } from './toolDefinitions.js';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
class UnityMCPServer {
server;
wsHandler;
constructor() {
// Initialize MCP Server
this.server = new Server({ name: 'unity-mcp-server', version: '0.2.0' }, { capabilities: { tools: {} } });
// Setup project paths and websocket
const wsPort = parseInt(process.env.MCP_WEBSOCKET_PORT || '5010');
const projectRootPath = this.setupProjectPaths();
// Initialize WebSocket Handler for Unity communication
this.wsHandler = new WebSocketHandler(wsPort);
// Register MCP tools
registerTools(this.server, this.wsHandler);
// Error handling
this.server.onerror = (error) => console.error('[MCP Error]', error);
this.setupShutdownHandlers();
}
setupProjectPaths() {
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
console.error(`[Unity MCP] Server starting from directory: ${__dirname}`);
// Get the project root path (parent of Assets)
let projectRootPath = process.env.UNITY_PROJECT_PATH || this.determineUnityProjectPath(__dirname);
projectRootPath = path.normalize(projectRootPath.replace(/["']/g, ''));
// Make sure path ends with a directory separator
if (!projectRootPath.endsWith(path.sep)) {
projectRootPath += path.sep;
}
// Create the full path to the Assets folder
const projectPath = path.join(projectRootPath, 'Assets') + path.sep;
this.setupEnvironmentPath(projectRootPath, projectPath);
return projectRootPath;
}
setupEnvironmentPath(projectRootPath, projectPath) {
try {
if (fs.existsSync(projectPath)) {
console.error(`[Unity MCP] Using project path: ${projectPath}`);
process.env.UNITY_PROJECT_PATH = projectPath;
}
else {
console.error(`[Unity MCP] WARNING: Assets folder not found at ${projectPath}`);
console.error(`[Unity MCP] Using project root instead: ${projectRootPath}`);
process.env.UNITY_PROJECT_PATH = projectRootPath;
}
}
catch (error) {
console.error(`[Unity MCP] Error checking project path: ${error}`);
process.env.UNITY_PROJECT_PATH = process.cwd();
}
}
setupShutdownHandlers() {
const cleanupHandler = async () => {
await this.cleanup();
process.exit(0);
};
process.on('SIGINT', cleanupHandler);
process.on('SIGTERM', cleanupHandler);
}
/**
* Determine the Unity project path based on the script location
*/
determineUnityProjectPath(scriptDir) {
scriptDir = path.normalize(scriptDir);
console.error(`[Unity MCP] Script directory: ${scriptDir}`);
// Case 1: Installed in Assets folder
const assetsMatch = /^(.+?[\/\\]Assets)[\/\\].*$/i.exec(scriptDir);
if (assetsMatch) {
const projectRoot = path.dirname(assetsMatch[1]);
console.error(`[Unity MCP] Detected installation in Assets folder: ${projectRoot}`);
return projectRoot;
}
// Case 2: Installed via Package Manager
const libraryMatch = /^(.+?[\/\\]Library)[\/\\]PackageCache[\/\\].*$/i.exec(scriptDir);
if (libraryMatch) {
const projectRoot = path.dirname(libraryMatch[1]);
console.error(`[Unity MCP] Detected installation via Package Manager: ${projectRoot}`);
const assetsPath = path.join(projectRoot, 'Assets');
if (fs.existsSync(assetsPath)) {
return projectRoot;
}
}
// Case 3: Check parent directories
for (const dir of this.getParentDirectories(scriptDir)) {
// Check if this directory is "UnityMCP"
if (path.basename(dir) === 'UnityMCP') {
console.error(`[Unity MCP] Found UnityMCP directory at: ${dir}`);
return dir;
}
// Check if this directory contains an Assets folder
const assetsDir = path.join(dir, 'Assets');
try {
if (fs.existsSync(assetsDir) && fs.statSync(assetsDir).isDirectory()) {
console.error(`[Unity MCP] Found Unity project at: ${dir}`);
return dir;
}
}
catch (e) {
// Ignore errors checking directories
}
}
// Fallback
console.error('[Unity MCP] Could not detect Unity project directory. Using current directory.');
return process.cwd();
}
getParentDirectories(filePath) {
const result = [];
const dirs = filePath.split(path.sep);
for (let i = 1; i <= dirs.length; i++) {
result.push(dirs.slice(0, i).join(path.sep));
}
return result;
}
async cleanup() {
console.error('Cleaning up resources...');
await this.wsHandler.close();
await this.server.close();
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('[Unity MCP] Server running and ready to accept connections');
console.error('[Unity MCP] WebSocket server listening on port', this.wsHandler.port);
}
}
// Start the server
const server = new UnityMCPServer();
server.run().catch(err => {
console.error('Fatal error in MCP server:', err);
process.exit(1);
});