UNPKG

unity-editor-mcp

Version:

MCP server for Unity Editor integration - enables AI assistants to control Unity Editor

252 lines (216 loc) 7.32 kB
import { BaseToolHandler } from '../base/BaseToolHandler.js'; /** * Handler for creating C# scripts in Unity */ export class CreateScriptToolHandler extends BaseToolHandler { constructor(unityConnection) { super( 'create_script', 'Create a new C# script in Unity project', { type: 'object', properties: { scriptName: { type: 'string', description: 'Name of the script (without .cs extension)' }, scriptType: { type: 'string', enum: ['MonoBehaviour', 'ScriptableObject', 'Editor', 'StaticClass', 'Interface'], default: 'MonoBehaviour', description: 'Type of script to create' }, path: { type: 'string', default: 'Assets/Scripts/', description: 'Directory path where script will be created' }, namespace: { type: 'string', default: '', description: 'Namespace for the script' } }, required: ['scriptName'] } ); this.unityConnection = unityConnection; } /** * Validates the input parameters * @param {Object} params - The input parameters * @throws {Error} If validation fails */ validate(params) { const { scriptName, scriptType, path, namespace } = params; // Validate script name if (!scriptName || scriptName.trim() === '') { throw new Error('scriptName cannot be empty'); } // Validate C# class name format const classNameRegex = /^[A-Za-z_][A-Za-z0-9_]*$/; if (!classNameRegex.test(scriptName)) { throw new Error('scriptName must be a valid C# class name (alphanumeric and underscore only, cannot start with number)'); } // Validate script type if (scriptType && !['MonoBehaviour', 'ScriptableObject', 'Editor', 'StaticClass', 'Interface'].includes(scriptType)) { throw new Error('scriptType must be one of: MonoBehaviour, ScriptableObject, Editor, StaticClass, Interface'); } // Validate path if (path && !path.startsWith('Assets/')) { throw new Error('path must start with Assets/'); } // Validate namespace if (namespace && namespace.trim() !== '') { const namespaceRegex = /^[A-Za-z_][A-Za-z0-9_.]*$/; if (!namespaceRegex.test(namespace)) { throw new Error('namespace must be a valid C# namespace (alphanumeric, underscore, and dots only)'); } } } /** * Executes the script creation * @param {Object} params - The input parameters * @returns {Promise<Object>} The result of the script creation */ async execute(params) { const { scriptName, scriptType = 'MonoBehaviour', path = 'Assets/Scripts/', namespace = '' } = params; // Ensure connection to Unity if (!this.unityConnection.isConnected()) { await this.unityConnection.connect(); } // Generate script content const scriptContent = this.generateScriptContent(scriptName, scriptType, namespace); // Prepare command parameters const commandParams = { scriptName, scriptType, path: path.endsWith('/') ? path : path + '/', fileName: `${scriptName}.cs`, namespace, scriptContent }; // Send command to Unity const response = await this.unityConnection.sendCommand('create_script', commandParams); // Handle Unity response if (response.success === false || response.status === 'error') { throw new Error(response.error || 'Failed to create script'); } // Handle nested data structure from Unity const data = response.data || response; return { scriptPath: data.scriptPath, message: data.message || 'Script created successfully' }; } /** * Generates the C# script content based on type * @param {string} scriptName - Name of the script * @param {string} scriptType - Type of script * @param {string} namespace - Namespace for the script * @returns {string} Generated script content */ generateScriptContent(scriptName, scriptType, namespace) { const templates = { MonoBehaviour: this.generateMonoBehaviourTemplate, ScriptableObject: this.generateScriptableObjectTemplate, Editor: this.generateEditorTemplate, StaticClass: this.generateStaticClassTemplate, Interface: this.generateInterfaceTemplate }; const generateTemplate = templates[scriptType]; if (!generateTemplate) { throw new Error(`Unknown script type: ${scriptType}`); } return generateTemplate.call(this, scriptName, namespace); } /** * Generates MonoBehaviour script template */ generateMonoBehaviourTemplate(scriptName, namespace) { const usings = 'using UnityEngine;'; const classDeclaration = `public class ${scriptName} : MonoBehaviour`; const classBody = `{ // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } }`; return this.wrapInNamespace(usings, classDeclaration, classBody, namespace); } /** * Generates ScriptableObject script template */ generateScriptableObjectTemplate(scriptName, namespace) { const usings = 'using UnityEngine;'; const classDeclaration = `[CreateAssetMenu(fileName = "New ${scriptName}", menuName = "${scriptName}")]\npublic class ${scriptName} : ScriptableObject`; const classBody = `{ // Add your ScriptableObject properties here }`; return this.wrapInNamespace(usings, classDeclaration, classBody, namespace); } /** * Generates Editor script template */ generateEditorTemplate(scriptName, namespace) { const usings = 'using UnityEngine;\nusing UnityEditor;'; const targetClass = scriptName.replace('Editor', ''); const classDeclaration = `[CustomEditor(typeof(${targetClass}))]\npublic class ${scriptName} : Editor`; const classBody = `{ public override void OnInspectorGUI() { DrawDefaultInspector(); // Add custom inspector GUI here } }`; return this.wrapInNamespace(usings, classDeclaration, classBody, namespace); } /** * Generates static class template */ generateStaticClassTemplate(scriptName, namespace) { const usings = 'using UnityEngine;'; const classDeclaration = `public static class ${scriptName}`; const classBody = `{ // Add your static methods and properties here }`; return this.wrapInNamespace(usings, classDeclaration, classBody, namespace); } /** * Generates interface template */ generateInterfaceTemplate(scriptName, namespace) { const usings = ''; const classDeclaration = `public interface ${scriptName}`; const classBody = `{ // Define your interface methods here }`; return this.wrapInNamespace(usings, classDeclaration, classBody, namespace); } /** * Wraps content in namespace if provided */ wrapInNamespace(usings, classDeclaration, classBody, namespace) { if (namespace && namespace.trim() !== '') { return `${usings} namespace ${namespace} { ${classDeclaration} ${classBody} }`; } else { return `${usings} ${classDeclaration} ${classBody}`; } } }