Skip to main content
Glama

MCP Server for Unity

by zabaglione
unity-bridge-deploy-service.ts6.88 kB
import * as fs from 'fs/promises'; import * as path from 'path'; import { EmbeddedScriptsProvider } from '../embedded-scripts.js'; interface DeploymentOptions { projectPath: string; forceUpdate?: boolean; } interface ScriptInfo { fileName: string; targetPath: string; version: string; } interface Logger { info(message: string): void; debug(message: string): void; error(message: string): void; } export class UnityBridgeDeployService { private logger: Logger = { info: (msg: string) => console.error(`[Unity MCP Deploy] ${msg}`), debug: (msg: string) => console.error(`[Unity MCP Deploy] DEBUG: ${msg}`), error: (msg: string) => console.error(`[Unity MCP Deploy] ERROR: ${msg}`) }; private scriptsProvider: EmbeddedScriptsProvider = new EmbeddedScriptsProvider(); private readonly SCRIPTS: ScriptInfo[] = [ { fileName: 'UnityHttpServer.cs', targetPath: 'Assets/Editor/MCP/UnityHttpServer.cs', version: '1.1.0' }, { fileName: 'UnityMCPServerWindow.cs', targetPath: 'Assets/Editor/MCP/UnityMCPServerWindow.cs', version: '1.1.0' } ]; async deployScripts(options: DeploymentOptions): Promise<void> { const { projectPath, forceUpdate = false } = options; // Validate Unity project const projectValidation = await this.validateUnityProject(projectPath); if (!projectValidation.isValid) { throw new Error(`Invalid Unity project: ${projectValidation.error}`); } // Create Editor/MCP directory if it doesn't exist const editorMCPPath = path.join(projectPath, 'Assets', 'Editor', 'MCP'); await fs.mkdir(editorMCPPath, { recursive: true }); // Deploy each script for (const script of this.SCRIPTS) { await this.deployScript(projectPath, script, forceUpdate); } this.logger.info('Unity MCP scripts deployed successfully'); } private async deployScript(projectPath: string, script: ScriptInfo, forceUpdate: boolean): Promise<void> { const targetPath = path.join(projectPath, script.targetPath); // Check if script exists and needs update const needsUpdate = await this.checkNeedsUpdate(targetPath, script.version, forceUpdate); if (needsUpdate) { // Get script from embedded provider (now async) const embeddedScript = await this.scriptsProvider.getScript(script.fileName); if (!embeddedScript) { throw new Error(`Embedded script not found: ${script.fileName}`); } this.logger.debug(`Using embedded script: ${script.fileName} (loaded from source)`); // Remove existing files if they exist (including .meta files) await this.removeExistingFiles(targetPath); // Write script using the embedded provider's method (handles UTF-8 BOM) await this.scriptsProvider.writeScriptToFile(script.fileName, targetPath); // Generate .meta file await this.generateMetaFile(targetPath); this.logger.info(`Deployed ${script.fileName} to ${script.targetPath}`); } else { this.logger.debug(`${script.fileName} is up to date`); } } private async checkNeedsUpdate(targetPath: string, currentVersion: string, forceUpdate: boolean): Promise<boolean> { if (forceUpdate) { return true; } try { const content = await fs.readFile(targetPath, 'utf8'); // Extract version from file const versionMatch = content.match(/SCRIPT_VERSION\s*=\s*"([^"]+)"/); if (versionMatch) { const installedVersion = versionMatch[1]; return this.compareVersions(currentVersion, installedVersion) > 0; } // If no version found, update needed return true; } catch (error) { // File doesn't exist, needs deployment return true; } } private compareVersions(version1: string, version2: string): number { const v1Parts = version1.split('.').map(Number); const v2Parts = version2.split('.').map(Number); for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) { const v1Part = v1Parts[i] || 0; const v2Part = v2Parts[i] || 0; if (v1Part > v2Part) return 1; if (v1Part < v2Part) return -1; } return 0; } private async generateMetaFile(filePath: string): Promise<void> { const metaPath = filePath + '.meta'; // Check if meta file already exists try { await fs.access(metaPath); return; // Meta file exists, don't overwrite } catch { // Meta file doesn't exist, create it } const guid = this.generateGUID(); const metaContent = `fileFormatVersion: 2 guid: ${guid} MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: `; await fs.writeFile(metaPath, metaContent, 'utf8'); } private async removeExistingFiles(targetPath: string): Promise<void> { try { // Remove the script file if it exists await fs.unlink(targetPath); this.logger.debug(`Removed existing file: ${targetPath}`); } catch (error: any) { if (error.code !== 'ENOENT') { this.logger.debug(`Failed to remove file ${targetPath}: ${error.message}`); } } try { // Remove the .meta file if it exists const metaPath = targetPath + '.meta'; await fs.unlink(metaPath); this.logger.debug(`Removed existing meta file: ${metaPath}`); } catch (error: any) { if (error.code !== 'ENOENT') { this.logger.debug(`Failed to remove meta file ${targetPath}.meta: ${error.message}`); } } } private generateGUID(): string { // Generate a Unity-compatible GUID const hex = '0123456789abcdef'; let guid = ''; for (let i = 0; i < 32; i++) { guid += hex[Math.floor(Math.random() * 16)]; } return guid; } private async validateUnityProject(projectPath: string): Promise<{ isValid: boolean; error?: string }> { try { // Check if directory exists const stats = await fs.stat(projectPath); if (!stats.isDirectory()) { return { isValid: false, error: 'Path is not a directory' }; } // Check for Unity project structure const requiredDirs = ['Assets', 'ProjectSettings']; for (const dir of requiredDirs) { try { const dirPath = path.join(projectPath, dir); const dirStats = await fs.stat(dirPath); if (!dirStats.isDirectory()) { return { isValid: false, error: `Missing ${dir} directory` }; } } catch { return { isValid: false, error: `Missing ${dir} directory` }; } } return { isValid: true }; } catch (error: any) { return { isValid: false, error: error.message }; } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zabaglione/mcp-server-unity'

If you have feedback or need assistance with the MCP directory API, please join our Discord server