#
tokens: 28333/50000 8/8 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .github
│   └── FUNDING.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   └── build.js
├── src
│   ├── index.ts
│   └── scripts
│       └── godot_operations.gd
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Dependencies
 2 | node_modules/
 3 | 
 4 | # Build output
 5 | build/
 6 | dist/
 7 | 
 8 | # Logs
 9 | *.log
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | 
14 | # Environment variables
15 | .env*
16 | 
17 | # OS files
18 | .DS_Store
19 | Thumbs.db
20 | 
21 | # Editor directories and files
22 | .vscode/
23 | .idea/
24 | *.swp
25 | *.swo
26 | 
27 | # Coverage directory
28 | coverage/
29 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | 
  2 | # Godot MCP
  3 | 
  4 | [![Github-sponsors](https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=#EA4AAA)](https://github.com/sponsors/Coding-Solo)
  5 | 
  6 | [![](https://badge.mcpx.dev?type=server 'MCP Server')](https://modelcontextprotocol.io/introduction)
  7 | [![Made with Godot](https://img.shields.io/badge/Made%20with-Godot-478CBF?style=flat&logo=godot%20engine&logoColor=white)](https://godotengine.org)
  8 | [![](https://img.shields.io/badge/Node.js-339933?style=flat&logo=nodedotjs&logoColor=white 'Node.js')](https://nodejs.org/en/download/)
  9 | [![](https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=white 'TypeScript')](https://www.typescriptlang.org/)
 10 | 
 11 | [![](https://img.shields.io/github/last-commit/Coding-Solo/godot-mcp 'Last Commit')](https://github.com/Coding-Solo/godot-mcp/commits/main)
 12 | [![](https://img.shields.io/github/stars/Coding-Solo/godot-mcp 'Stars')](https://github.com/Coding-Solo/godot-mcp/stargazers)
 13 | [![](https://img.shields.io/github/forks/Coding-Solo/godot-mcp 'Forks')](https://github.com/Coding-Solo/godot-mcp/network/members)
 14 | [![](https://img.shields.io/badge/License-MIT-red.svg 'MIT License')](https://opensource.org/licenses/MIT)
 15 | 
 16 | ```text
 17 |                            (((((((             (((((((                          
 18 |                         (((((((((((           (((((((((((                      
 19 |                         (((((((((((((       (((((((((((((                       
 20 |                         (((((((((((((((((((((((((((((((((                       
 21 |                         (((((((((((((((((((((((((((((((((                       
 22 |          (((((      (((((((((((((((((((((((((((((((((((((((((      (((((        
 23 |        (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((      
 24 |      ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((    
 25 |     ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((    
 26 |       (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((     
 27 |         (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((       
 28 |          (((((((((((@@@@@@@(((((((((((((((((((((((((((@@@@@@@(((((((((((        
 29 |          (((((((((@@@@,,,,,@@@(((((((((((((((((((((@@@,,,,,@@@@(((((((((        
 30 |          ((((((((@@@,,,,,,,,,@@(((((((@@@@@(((((((@@,,,,,,,,,@@@((((((((        
 31 |          ((((((((@@@,,,,,,,,,@@(((((((@@@@@(((((((@@,,,,,,,,,@@@((((((((        
 32 |          (((((((((@@@,,,,,,,@@((((((((@@@@@((((((((@@,,,,,,,@@@(((((((((        
 33 |          ((((((((((((@@@@@@(((((((((((@@@@@(((((((((((@@@@@@((((((((((((        
 34 |          (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((        
 35 |          (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((        
 36 |          @@@@@@@@@@@@@((((((((((((@@@@@@@@@@@@@((((((((((((@@@@@@@@@@@@@        
 37 |          ((((((((( @@@(((((((((((@@(((((((((((@@(((((((((((@@@ (((((((((        
 38 |          (((((((((( @@((((((((((@@@(((((((((((@@@((((((((((@@ ((((((((((        
 39 |           (((((((((((@@@@@@@@@@@@@@(((((((((((@@@@@@@@@@@@@@(((((((((((         
 40 |            (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((          
 41 |               (((((((((((((((((((((((((((((((((((((((((((((((((((((             
 42 |                  (((((((((((((((((((((((((((((((((((((((((((((((                
 43 |                         (((((((((((((((((((((((((((((((((                       
 44 |                                                                                 
 45 | 
 46 |                           /$$      /$$  /$$$$$$  /$$$$$$$ 
 47 |                          | $$$    /$$$ /$$__  $$| $$__  $$
 48 |                          | $$$$  /$$$$| $$  \__/| $$  \ $$
 49 |                          | $$ $$/$$ $$| $$      | $$$$$$$/
 50 |                          | $$  $$$| $$| $$      | $$____/ 
 51 |                          | $$\  $ | $$| $$    $$| $$      
 52 |                          | $$ \/  | $$|  $$$$$$/| $$      
 53 |                          |__/     |__/ \______/ |__/       
 54 | ```
 55 | 
 56 | A Model Context Protocol (MCP) server for interacting with the Godot game engine.
 57 | 
 58 | ## Introduction
 59 | 
 60 | Godot MCP enables AI assistants to launch the Godot editor, run projects, capture debug output, and control project execution - all through a standardized interface.
 61 | 
 62 | This direct feedback loop helps AI assistants like Claude understand what works and what doesn't in real Godot projects, leading to better code generation and debugging assistance.
 63 | 
 64 | ## Features
 65 | 
 66 | - **Launch Godot Editor**: Open the Godot editor for a specific project
 67 | - **Run Godot Projects**: Execute Godot projects in debug mode
 68 | - **Capture Debug Output**: Retrieve console output and error messages
 69 | - **Control Execution**: Start and stop Godot projects programmatically
 70 | - **Get Godot Version**: Retrieve the installed Godot version
 71 | - **List Godot Projects**: Find Godot projects in a specified directory
 72 | - **Project Analysis**: Get detailed information about project structure
 73 | - **Scene Management**:
 74 |   - Create new scenes with specified root node types
 75 |   - Add nodes to existing scenes with customizable properties
 76 |   - Load sprites and textures into Sprite2D nodes
 77 |   - Export 3D scenes as MeshLibrary resources for GridMap
 78 |   - Save scenes with options for creating variants
 79 | - **UID Management** (for Godot 4.4+):
 80 |   - Get UID for specific files
 81 |   - Update UID references by resaving resources
 82 | 
 83 | ## Requirements
 84 | 
 85 | - [Godot Engine](https://godotengine.org/download) installed on your system
 86 | - Node.js and npm
 87 | - An AI assistant that supports MCP (Cline, Cursor, etc.)
 88 | 
 89 | ## Installation and Configuration
 90 | 
 91 | ### Step 1: Install and Build
 92 | 
 93 | First, clone the repository and build the MCP server:
 94 | 
 95 | ```bash
 96 | git clone https://github.com/Coding-Solo/godot-mcp.git
 97 | cd godot-mcp
 98 | npm install
 99 | npm run build
100 | ```
101 | 
102 | ### Step 2: Configure with Your AI Assistant
103 | 
104 | #### Option A: Configure with Cline
105 | 
106 | Add to your Cline MCP settings file (`~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`):
107 | 
108 | ```json
109 | {
110 |   "mcpServers": {
111 |     "godot": {
112 |       "command": "node",
113 |       "args": ["/absolute/path/to/godot-mcp/build/index.js"],
114 |       "env": {
115 |         "DEBUG": "true"                  // Optional: Enable detailed logging
116 |       },
117 |       "disabled": false,
118 |       "autoApprove": [
119 |         "launch_editor",
120 |         "run_project",
121 |         "get_debug_output",
122 |         "stop_project",
123 |         "get_godot_version",
124 |         "list_projects",
125 |         "get_project_info",
126 |         "create_scene",
127 |         "add_node",
128 |         "load_sprite",
129 |         "export_mesh_library",
130 |         "save_scene",
131 |         "get_uid",
132 |         "update_project_uids"
133 |       ]
134 |     }
135 |   }
136 | }
137 | ```
138 | 
139 | #### Option B: Configure with Cursor
140 | 
141 | **Using the Cursor UI:**
142 | 
143 | 1. Go to **Cursor Settings** > **Features** > **MCP**
144 | 2. Click on the **+ Add New MCP Server** button
145 | 3. Fill out the form:
146 |    - Name: `godot` (or any name you prefer)
147 |    - Type: `command`
148 |    - Command: `node /absolute/path/to/godot-mcp/build/index.js`
149 | 4. Click "Add"
150 | 5. You may need to press the refresh button in the top right corner of the MCP server card to populate the tool list
151 | 
152 | **Using Project-Specific Configuration:**
153 | 
154 | Create a file at `.cursor/mcp.json` in your project directory with the following content:
155 | 
156 | ```json
157 | {
158 |   "mcpServers": {
159 |     "godot": {
160 |       "command": "node",
161 |       "args": ["/absolute/path/to/godot-mcp/build/index.js"],
162 |       "env": {
163 |         "DEBUG": "true"                  // Enable detailed logging
164 |       }
165 |     }
166 |   }
167 | }
168 | ```
169 | 
170 | ### Step 3: Optional Environment Variables
171 | 
172 | You can customize the server behavior with these environment variables:
173 | 
174 | - `GODOT_PATH`: Path to the Godot executable (overrides automatic detection)
175 | - `DEBUG`: Set to "true" to enable detailed server-side debug logging
176 | 
177 | ## Example Prompts
178 | 
179 | Once configured, your AI assistant will automatically run the MCP server when needed. You can use prompts like:
180 | 
181 | ```text
182 | "Launch the Godot editor for my project at /path/to/project"
183 | 
184 | "Run my Godot project and show me any errors"
185 | 
186 | "Get information about my Godot project structure"
187 | 
188 | "Analyze my Godot project structure and suggest improvements"
189 | 
190 | "Help me debug this error in my Godot project: [paste error]"
191 | 
192 | "Write a GDScript for a character controller with double jump and wall sliding"
193 | 
194 | "Create a new scene with a Player node in my Godot project"
195 | 
196 | "Add a Sprite2D node to my player scene and load the character texture"
197 | 
198 | "Export my 3D models as a MeshLibrary for use with GridMap"
199 | 
200 | "Create a UI scene with buttons and labels for my game's main menu"
201 | 
202 | "Get the UID for a specific script file in my Godot 4.4 project"
203 | 
204 | "Update UID references in my Godot project after upgrading to 4.4"
205 | ```
206 | 
207 | ## Implementation Details
208 | 
209 | ### Architecture
210 | 
211 | The Godot MCP server uses a bundled GDScript approach for complex operations:
212 | 
213 | 1. **Direct Commands**: Simple operations like launching the editor or getting project info use Godot's built-in CLI commands directly.
214 | 2. **Bundled Operations Script**: Complex operations like creating scenes or adding nodes use a single, comprehensive GDScript file (`godot_operations.gd`) that handles all operations.
215 | 
216 | This architecture provides several benefits:
217 | 
218 | - **No Temporary Files**: Eliminates the need for temporary script files, keeping your system clean
219 | - **Simplified Codebase**: Centralizes all Godot operations in one (somewhat) organized file
220 | - **Better Maintainability**: Makes it easier to add new operations or modify existing ones
221 | - **Improved Error Handling**: Provides consistent error reporting across all operations
222 | - **Reduced Overhead**: Minimizes file I/O operations for better performance
223 | 
224 | The bundled script accepts operation type and parameters as JSON, allowing for flexible and dynamic operation execution without generating temporary files for each operation.
225 | 
226 | ## Troubleshooting
227 | 
228 | - **Godot Not Found**: Set the GODOT_PATH environment variable to your Godot executable
229 | - **Connection Issues**: Ensure the server is running and restart your AI assistant
230 | - **Invalid Project Path**: Ensure the path points to a directory containing a project.godot file
231 | - **Build Issues**: Make sure all dependencies are installed by running `npm install`
232 | - **For Cursor Specifically**:
233 | -   Ensure the MCP server shows up and is enabled in Cursor settings (Settings > MCP)
234 | -   MCP tools can only be run using the Agent chat profile (Cursor Pro or Business subscription)
235 | -   Use "Yolo Mode" to automatically run MCP tool requests
236 | 
237 | ## License
238 | 
239 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
240 | 
241 | [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/coding-solo-godot-mcp-badge.png)](https://mseep.ai/app/coding-solo-godot-mcp)
242 | 
```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Contributing to Godot MCP
  2 | 
  3 | Thank you for considering contributing to Godot MCP! This document outlines the process for contributing to the project.
  4 | 
  5 | ## Code of Conduct
  6 | 
  7 | By participating in this project, you agree to maintain a respectful and inclusive environment for everyone.
  8 | 
  9 | ## How Can I Contribute?
 10 | 
 11 | ### Reporting Bugs
 12 | 
 13 | - Check if the bug has already been reported in the Issues section
 14 | - Use the bug report template if available
 15 | - Include detailed steps to reproduce the bug
 16 | - Include any relevant logs or screenshots
 17 | - Specify your environment (OS, Godot version, etc.)
 18 | 
 19 | ### Suggesting Enhancements
 20 | 
 21 | - Check if the enhancement has already been suggested in the Issues section
 22 | - Use the feature request template if available
 23 | - Clearly describe the enhancement and its benefits
 24 | - Consider how the enhancement fits into the project's scope
 25 | 
 26 | ### Pull Requests
 27 | 
 28 | 1. Fork the repository
 29 | 2. Create a new branch for your feature or bugfix (`git checkout -b feature/amazing-feature`)
 30 | 3. Make your changes
 31 | 4. Run tests if available
 32 | 5. Commit your changes with clear commit messages
 33 | 6. Push to your branch (`git push origin feature/amazing-feature`)
 34 | 7. Open a Pull Request
 35 | 
 36 | ## Development Process
 37 | 
 38 | ### Setting Up the Development Environment
 39 | 
 40 | 1. Clone the repository
 41 | 2. Install dependencies with `npm install`
 42 | 3. Build the project with `npm run build`
 43 | 4. For development with auto-rebuild, use `npm run watch`
 44 | 
 45 | ### Project Structure
 46 | 
 47 | ```
 48 | godot-mcp/
 49 | ├── src/             # Source code
 50 | │   └── index.ts     # Main server implementation
 51 | ├── build/           # Compiled JavaScript (generated)
 52 | ├── tests/           # Test files (future)
 53 | ├── examples/        # Example Godot projects (future)
 54 | ├── LICENSE          # MIT License
 55 | ├── README.md        # Documentation
 56 | ├── CONTRIBUTING.md  # Contribution guidelines
 57 | ├── package.json     # Project configuration
 58 | └── tsconfig.json    # TypeScript configuration
 59 | ```
 60 | 
 61 | ### Code Style
 62 | 
 63 | - Follow the existing code style in the project
 64 | - Use TypeScript for type safety
 65 | - Include JSDoc comments for all functions and classes
 66 | - Write clear and descriptive variable and function names
 67 | - Use meaningful interfaces for complex objects
 68 | - Handle errors gracefully with detailed error messages
 69 | 
 70 | ### Debugging
 71 | 
 72 | For debugging the MCP server:
 73 | 
 74 | 1. Set the `DEBUG` environment variable to `true`
 75 | 2. Use the MCP Inspector for interactive debugging:
 76 |    ```bash
 77 |    npm run inspector
 78 |    ```
 79 | 3. Check the logs for detailed information about what's happening
 80 | 
 81 | ### Adding New Tools
 82 | 
 83 | When adding new tools to the MCP server:
 84 | 
 85 | 1. Define the tool in the `setupToolHandlers` method
 86 | 2. Create a handler method for the tool
 87 | 3. Add proper input validation and error handling
 88 | 4. Update the README.md with documentation for the new tool
 89 | 5. Update the Features section in the README.md
 90 | 6. Update the autoApprove section in the configuration examples
 91 | 7. Add tests for the new functionality
 92 | 
 93 | #### Recently Added Tools
 94 | 
 95 | The following tools have been recently added:
 96 | 
 97 | - **get_project_info**: Retrieves metadata about a Godot project
 98 |   - Analyzes project structure
 99 |   - Returns information about scenes, scripts, and assets
100 |   - Helps LLMs understand the organization of Godot projects
101 |   
102 | - **capture_screenshot**: Takes a screenshot of a running Godot project
103 |   - Requires an active Godot process
104 |   - Saves the screenshot to the specified path
105 |   - Useful for visual debugging and feedback
106 | 
107 | Example:
108 | 
109 | ```typescript
110 | // In setupToolHandlers
111 | {
112 |   name: 'your_new_tool',
113 |   description: 'Description of what your tool does',
114 |   inputSchema: {
115 |     type: 'object',
116 |     properties: {
117 |       param1: {
118 |         type: 'string',
119 |         description: 'Description of parameter 1',
120 |       },
121 |     },
122 |     required: ['param1'],
123 |   },
124 | }
125 | 
126 | // Add handler method
127 | private async handleYourNewTool(args: any) {
128 |   // Validate input
129 |   if (!args.param1) {
130 |     return this.createErrorResponse(
131 |       'Parameter 1 is required',
132 |       ['Provide a valid value for parameter 1']
133 |     );
134 |   }
135 | 
136 |   try {
137 |     // Implement tool functionality
138 |     // ...
139 | 
140 |     return {
141 |       content: [
142 |         {
143 |           type: 'text',
144 |           text: 'Result of your tool',
145 |         },
146 |       ],
147 |     };
148 |   } catch (error: any) {
149 |     return this.createErrorResponse(
150 |       `Failed to execute tool: ${error?.message || 'Unknown error'}`,
151 |       [
152 |         'Possible solution 1',
153 |         'Possible solution 2'
154 |       ]
155 |     );
156 |   }
157 | }
158 | ```
159 | 
160 | ### Cross-Platform Compatibility
161 | 
162 | When making changes, ensure they work across different platforms:
163 | 
164 | - Use path utilities from Node.js (`path.join`, etc.) instead of hardcoded path separators
165 | - Test on different operating systems if possible
166 | - Consider different Godot installation locations
167 | - Use environment variables for configuration
168 | 
169 | ## Testing
170 | 
171 | - Add tests for new features when possible
172 | - Ensure all tests pass before submitting a Pull Request
173 | - Test on different platforms if possible
174 | - Test with different Godot versions
175 | 
176 | ## Documentation
177 | 
178 | - Keep README.md up to date with new features
179 | - Document all tools and their parameters
180 | - Include examples for new functionality
181 | - Update the troubleshooting section with common issues
182 | 
183 | ## Questions?
184 | 
185 | If you have any questions about contributing, feel free to open an issue for discussion.
186 | 
187 | Thank you for your contributions!
188 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "ESNext",
 5 |     "moduleResolution": "node",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "allowJs": true,
13 |     "resolveJsonModule": true
14 |   },
15 |   "include": ["src/**/*"],
16 |   "exclude": ["node_modules"]
17 | }
18 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "godot-mcp",
 3 |   "version": "0.1.0",
 4 |   "description": "MCP server for interfacing with Godot game engine. Provides tools for launching the editor, running projects, and capturing debug output.",
 5 |   "type": "module",
 6 |   "bin": {
 7 |     "godot-mcp": "./build/index.js"
 8 |   },
 9 |   "files": [
10 |     "build"
11 |   ],
12 |   "scripts": {
13 |     "build": "tsc && node scripts/build.js",
14 |     "prepare": "npm run build",
15 |     "watch": "tsc --watch",
16 |     "inspector": "npx @modelcontextprotocol/inspector build/index.js"
17 |   },
18 |   "dependencies": {
19 |     "@modelcontextprotocol/sdk": "0.6.0",
20 |     "axios": "^1.7.9",
21 |     "fs-extra": "^11.2.0"
22 |   },
23 |   "devDependencies": {
24 |     "@types/node": "^20.11.24",
25 |     "typescript": "^5.3.3"
26 |   },
27 |   "license": "MIT",
28 |   "repository": {
29 |     "type": "git",
30 |     "url": "https://github.com/Coding-Solo/godot-mcp.git"
31 |   },
32 |   "keywords": [
33 |     "godot",
34 |     "mcp",
35 |     "ai",
36 |     "claude",
37 |     "cline"
38 |   ]
39 | }
40 | 
```

--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------

```yaml
 1 | # These are supported funding model platforms
 2 | 
 3 | github: [Coding-Solo] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
 4 | patreon: # Replace with a single Patreon username
 5 | open_collective: # Replace with a single Open Collective username
 6 | ko_fi: # Replace with a single Ko-fi username
 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
 9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14 | thanks_dev: # Replace with a single thanks.dev username
15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
16 | 
```

--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------

```javascript
 1 | import fs from 'fs-extra';
 2 | import path from 'path';
 3 | import { fileURLToPath } from 'url';
 4 | 
 5 | // Get the directory name
 6 | const __filename = fileURLToPath(import.meta.url);
 7 | const __dirname = path.dirname(__filename);
 8 | 
 9 | // Make the build/index.js file executable
10 | fs.chmodSync(path.join(__dirname, '..', 'build', 'index.js'), '755');
11 | 
12 | // Copy the scripts directory to the build directory
13 | try {
14 |   // Ensure the build/scripts directory exists
15 |   fs.ensureDirSync(path.join(__dirname, '..', 'build', 'scripts'));
16 |   
17 |   // Copy the godot_operations.gd file
18 |   fs.copyFileSync(
19 |     path.join(__dirname, '..', 'src', 'scripts', 'godot_operations.gd'),
20 |     path.join(__dirname, '..', 'build', 'scripts', 'godot_operations.gd')
21 |   );
22 |   
23 |   console.log('Successfully copied godot_operations.gd to build/scripts');
24 | } catch (error) {
25 |   console.error('Error copying scripts:', error);
26 |   process.exit(1);
27 | }
28 | 
29 | console.log('Build scripts completed successfully!');
30 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
   1 | #!/usr/bin/env node
   2 | /**
   3 |  * Godot MCP Server
   4 |  *
   5 |  * This MCP server provides tools for interacting with the Godot game engine.
   6 |  * It enables AI assistants to launch the Godot editor, run Godot projects,
   7 |  * capture debug output, and control project execution.
   8 |  */
   9 | 
  10 | import { fileURLToPath } from 'url';
  11 | import { join, dirname, basename, normalize } from 'path';
  12 | import { existsSync, readdirSync, mkdirSync } from 'fs';
  13 | import { spawn } from 'child_process';
  14 | import { promisify } from 'util';
  15 | import { exec } from 'child_process';
  16 | 
  17 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  18 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  19 | import {
  20 |   CallToolRequestSchema,
  21 |   ErrorCode,
  22 |   ListToolsRequestSchema,
  23 |   McpError,
  24 | } from '@modelcontextprotocol/sdk/types.js';
  25 | 
  26 | // Check if debug mode is enabled
  27 | const DEBUG_MODE: boolean = process.env.DEBUG === 'true';
  28 | const GODOT_DEBUG_MODE: boolean = true; // Always use GODOT DEBUG MODE
  29 | 
  30 | const execAsync = promisify(exec);
  31 | 
  32 | // Derive __filename and __dirname in ESM
  33 | const __filename = fileURLToPath(import.meta.url);
  34 | const __dirname = dirname(__filename);
  35 | 
  36 | /**
  37 |  * Interface representing a running Godot process
  38 |  */
  39 | interface GodotProcess {
  40 |   process: any;
  41 |   output: string[];
  42 |   errors: string[];
  43 | }
  44 | 
  45 | /**
  46 |  * Interface for server configuration
  47 |  */
  48 | interface GodotServerConfig {
  49 |   godotPath?: string;
  50 |   debugMode?: boolean;
  51 |   godotDebugMode?: boolean;
  52 |   strictPathValidation?: boolean; // New option to control path validation behavior
  53 | }
  54 | 
  55 | /**
  56 |  * Interface for operation parameters
  57 |  */
  58 | interface OperationParams {
  59 |   [key: string]: any;
  60 | }
  61 | 
  62 | /**
  63 |  * Main server class for the Godot MCP server
  64 |  */
  65 | class GodotServer {
  66 |   private server: Server;
  67 |   private activeProcess: GodotProcess | null = null;
  68 |   private godotPath: string | null = null;
  69 |   private operationsScriptPath: string;
  70 |   private validatedPaths: Map<string, boolean> = new Map();
  71 |   private strictPathValidation: boolean = false;
  72 | 
  73 |   /**
  74 |    * Parameter name mappings between snake_case and camelCase
  75 |    * This allows the server to accept both formats
  76 |    */
  77 |   private parameterMappings: Record<string, string> = {
  78 |     'project_path': 'projectPath',
  79 |     'scene_path': 'scenePath',
  80 |     'root_node_type': 'rootNodeType',
  81 |     'parent_node_path': 'parentNodePath',
  82 |     'node_type': 'nodeType',
  83 |     'node_name': 'nodeName',
  84 |     'texture_path': 'texturePath',
  85 |     'node_path': 'nodePath',
  86 |     'output_path': 'outputPath',
  87 |     'mesh_item_names': 'meshItemNames',
  88 |     'new_path': 'newPath',
  89 |     'file_path': 'filePath',
  90 |     'directory': 'directory',
  91 |     'recursive': 'recursive',
  92 |     'scene': 'scene',
  93 |   };
  94 | 
  95 |   /**
  96 |    * Reverse mapping from camelCase to snake_case
  97 |    * Generated from parameterMappings for quick lookups
  98 |    */
  99 |   private reverseParameterMappings: Record<string, string> = {};
 100 | 
 101 |   constructor(config?: GodotServerConfig) {
 102 |     // Initialize reverse parameter mappings
 103 |     for (const [snakeCase, camelCase] of Object.entries(this.parameterMappings)) {
 104 |       this.reverseParameterMappings[camelCase] = snakeCase;
 105 |     }
 106 |     // Apply configuration if provided
 107 |     let debugMode = DEBUG_MODE;
 108 |     let godotDebugMode = GODOT_DEBUG_MODE;
 109 | 
 110 |     if (config) {
 111 |       if (config.debugMode !== undefined) {
 112 |         debugMode = config.debugMode;
 113 |       }
 114 |       if (config.godotDebugMode !== undefined) {
 115 |         godotDebugMode = config.godotDebugMode;
 116 |       }
 117 |       if (config.strictPathValidation !== undefined) {
 118 |         this.strictPathValidation = config.strictPathValidation;
 119 |       }
 120 | 
 121 |       // Store and validate custom Godot path if provided
 122 |       if (config.godotPath) {
 123 |         const normalizedPath = normalize(config.godotPath);
 124 |         this.godotPath = normalizedPath;
 125 |         this.logDebug(`Custom Godot path provided: ${this.godotPath}`);
 126 | 
 127 |         // Validate immediately with sync check
 128 |         if (!this.isValidGodotPathSync(this.godotPath)) {
 129 |           console.warn(`[SERVER] Invalid custom Godot path provided: ${this.godotPath}`);
 130 |           this.godotPath = null; // Reset to trigger auto-detection later
 131 |         }
 132 |       }
 133 |     }
 134 | 
 135 |     // Set the path to the operations script
 136 |     this.operationsScriptPath = join(__dirname, 'scripts', 'godot_operations.gd');
 137 |     if (debugMode) console.debug(`[DEBUG] Operations script path: ${this.operationsScriptPath}`);
 138 | 
 139 |     // Initialize the MCP server
 140 |     this.server = new Server(
 141 |       {
 142 |         name: 'godot-mcp',
 143 |         version: '0.1.0',
 144 |       },
 145 |       {
 146 |         capabilities: {
 147 |           tools: {},
 148 |         },
 149 |       }
 150 |     );
 151 | 
 152 |     // Set up tool handlers
 153 |     this.setupToolHandlers();
 154 | 
 155 |     // Error handling
 156 |     this.server.onerror = (error) => console.error('[MCP Error]', error);
 157 | 
 158 |     // Cleanup on exit
 159 |     process.on('SIGINT', async () => {
 160 |       await this.cleanup();
 161 |       process.exit(0);
 162 |     });
 163 |   }
 164 | 
 165 |   /**
 166 |    * Log debug messages if debug mode is enabled
 167 |    */
 168 |   private logDebug(message: string): void {
 169 |     if (DEBUG_MODE) {
 170 |       console.debug(`[DEBUG] ${message}`);
 171 |     }
 172 |   }
 173 | 
 174 |   /**
 175 |    * Create a standardized error response with possible solutions
 176 |    */
 177 |   private createErrorResponse(message: string, possibleSolutions: string[] = []): any {
 178 |     // Log the error
 179 |     console.error(`[SERVER] Error response: ${message}`);
 180 |     if (possibleSolutions.length > 0) {
 181 |       console.error(`[SERVER] Possible solutions: ${possibleSolutions.join(', ')}`);
 182 |     }
 183 | 
 184 |     const response: any = {
 185 |       content: [
 186 |         {
 187 |           type: 'text',
 188 |           text: message,
 189 |         },
 190 |       ],
 191 |       isError: true,
 192 |     };
 193 | 
 194 |     if (possibleSolutions.length > 0) {
 195 |       response.content.push({
 196 |         type: 'text',
 197 |         text: 'Possible solutions:\n- ' + possibleSolutions.join('\n- '),
 198 |       });
 199 |     }
 200 | 
 201 |     return response;
 202 |   }
 203 | 
 204 |   /**
 205 |    * Validate a path to prevent path traversal attacks
 206 |    */
 207 |   private validatePath(path: string): boolean {
 208 |     // Basic validation to prevent path traversal
 209 |     if (!path || path.includes('..')) {
 210 |       return false;
 211 |     }
 212 | 
 213 |     // Add more validation as needed
 214 |     return true;
 215 |   }
 216 | 
 217 |   /**
 218 |    * Synchronous validation for constructor use
 219 |    * This is a quick check that only verifies file existence, not executable validity
 220 |    * Full validation will be performed later in detectGodotPath
 221 |    * @param path Path to check
 222 |    * @returns True if the path exists or is 'godot' (which might be in PATH)
 223 |    */
 224 |   private isValidGodotPathSync(path: string): boolean {
 225 |     try {
 226 |       this.logDebug(`Quick-validating Godot path: ${path}`);
 227 |       return path === 'godot' || existsSync(path);
 228 |     } catch (error) {
 229 |       this.logDebug(`Invalid Godot path: ${path}, error: ${error}`);
 230 |       return false;
 231 |     }
 232 |   }
 233 | 
 234 |   /**
 235 |    * Validate if a Godot path is valid and executable
 236 |    */
 237 |   private async isValidGodotPath(path: string): Promise<boolean> {
 238 |     // Check cache first
 239 |     if (this.validatedPaths.has(path)) {
 240 |       return this.validatedPaths.get(path)!;
 241 |     }
 242 | 
 243 |     try {
 244 |       this.logDebug(`Validating Godot path: ${path}`);
 245 | 
 246 |       // Check if the file exists (skip for 'godot' which might be in PATH)
 247 |       if (path !== 'godot' && !existsSync(path)) {
 248 |         this.logDebug(`Path does not exist: ${path}`);
 249 |         this.validatedPaths.set(path, false);
 250 |         return false;
 251 |       }
 252 | 
 253 |       // Try to execute Godot with --version flag
 254 |       const command = path === 'godot' ? 'godot --version' : `"${path}" --version`;
 255 |       await execAsync(command);
 256 | 
 257 |       this.logDebug(`Valid Godot path: ${path}`);
 258 |       this.validatedPaths.set(path, true);
 259 |       return true;
 260 |     } catch (error) {
 261 |       this.logDebug(`Invalid Godot path: ${path}, error: ${error}`);
 262 |       this.validatedPaths.set(path, false);
 263 |       return false;
 264 |     }
 265 |   }
 266 | 
 267 |   /**
 268 |    * Detect the Godot executable path based on the operating system
 269 |    */
 270 |   private async detectGodotPath() {
 271 |     // If godotPath is already set and valid, use it
 272 |     if (this.godotPath && await this.isValidGodotPath(this.godotPath)) {
 273 |       this.logDebug(`Using existing Godot path: ${this.godotPath}`);
 274 |       return;
 275 |     }
 276 | 
 277 |     // Check environment variable next
 278 |     if (process.env.GODOT_PATH) {
 279 |       const normalizedPath = normalize(process.env.GODOT_PATH);
 280 |       this.logDebug(`Checking GODOT_PATH environment variable: ${normalizedPath}`);
 281 |       if (await this.isValidGodotPath(normalizedPath)) {
 282 |         this.godotPath = normalizedPath;
 283 |         this.logDebug(`Using Godot path from environment: ${this.godotPath}`);
 284 |         return;
 285 |       } else {
 286 |         this.logDebug(`GODOT_PATH environment variable is invalid`);
 287 |       }
 288 |     }
 289 | 
 290 |     // Auto-detect based on platform
 291 |     const osPlatform = process.platform;
 292 |     this.logDebug(`Auto-detecting Godot path for platform: ${osPlatform}`);
 293 | 
 294 |     const possiblePaths: string[] = [
 295 |       'godot', // Check if 'godot' is in PATH first
 296 |     ];
 297 | 
 298 |     // Add platform-specific paths
 299 |     if (osPlatform === 'darwin') {
 300 |       possiblePaths.push(
 301 |         '/Applications/Godot.app/Contents/MacOS/Godot',
 302 |         '/Applications/Godot_4.app/Contents/MacOS/Godot',
 303 |         `${process.env.HOME}/Applications/Godot.app/Contents/MacOS/Godot`,
 304 |         `${process.env.HOME}/Applications/Godot_4.app/Contents/MacOS/Godot`,
 305 |         `${process.env.HOME}/Library/Application Support/Steam/steamapps/common/Godot Engine/Godot.app/Contents/MacOS/Godot`
 306 |       );
 307 |     } else if (osPlatform === 'win32') {
 308 |       possiblePaths.push(
 309 |         'C:\\Program Files\\Godot\\Godot.exe',
 310 |         'C:\\Program Files (x86)\\Godot\\Godot.exe',
 311 |         'C:\\Program Files\\Godot_4\\Godot.exe',
 312 |         'C:\\Program Files (x86)\\Godot_4\\Godot.exe',
 313 |         `${process.env.USERPROFILE}\\Godot\\Godot.exe`
 314 |       );
 315 |     } else if (osPlatform === 'linux') {
 316 |       possiblePaths.push(
 317 |         '/usr/bin/godot',
 318 |         '/usr/local/bin/godot',
 319 |         '/snap/bin/godot',
 320 |         `${process.env.HOME}/.local/bin/godot`
 321 |       );
 322 |     }
 323 | 
 324 |     // Try each possible path
 325 |     for (const path of possiblePaths) {
 326 |       const normalizedPath = normalize(path);
 327 |       if (await this.isValidGodotPath(normalizedPath)) {
 328 |         this.godotPath = normalizedPath;
 329 |         this.logDebug(`Found Godot at: ${normalizedPath}`);
 330 |         return;
 331 |       }
 332 |     }
 333 | 
 334 |     // If we get here, we couldn't find Godot
 335 |     this.logDebug(`Warning: Could not find Godot in common locations for ${osPlatform}`);
 336 |     console.warn(`[SERVER] Could not find Godot in common locations for ${osPlatform}`);
 337 |     console.warn(`[SERVER] Set GODOT_PATH=/path/to/godot environment variable or pass { godotPath: '/path/to/godot' } in the config to specify the correct path.`);
 338 | 
 339 |     if (this.strictPathValidation) {
 340 |       // In strict mode, throw an error
 341 |       throw new Error(`Could not find a valid Godot executable. Set GODOT_PATH or provide a valid path in config.`);
 342 |     } else {
 343 |       // Fallback to a default path in non-strict mode; this may not be valid and requires user configuration for reliability
 344 |       if (osPlatform === 'win32') {
 345 |         this.godotPath = normalize('C:\\Program Files\\Godot\\Godot.exe');
 346 |       } else if (osPlatform === 'darwin') {
 347 |         this.godotPath = normalize('/Applications/Godot.app/Contents/MacOS/Godot');
 348 |       } else {
 349 |         this.godotPath = normalize('/usr/bin/godot');
 350 |       }
 351 | 
 352 |       this.logDebug(`Using default path: ${this.godotPath}, but this may not work.`);
 353 |       console.warn(`[SERVER] Using default path: ${this.godotPath}, but this may not work.`);
 354 |       console.warn(`[SERVER] This fallback behavior will be removed in a future version. Set strictPathValidation: true to opt-in to the new behavior.`);
 355 |     }
 356 |   }
 357 | 
 358 |   /**
 359 |    * Set a custom Godot path
 360 |    * @param customPath Path to the Godot executable
 361 |    * @returns True if the path is valid and was set, false otherwise
 362 |    */
 363 |   public async setGodotPath(customPath: string): Promise<boolean> {
 364 |     if (!customPath) {
 365 |       return false;
 366 |     }
 367 | 
 368 |     // Normalize the path to ensure consistent format across platforms
 369 |     // (e.g., backslashes to forward slashes on Windows, resolving relative paths)
 370 |     const normalizedPath = normalize(customPath);
 371 |     if (await this.isValidGodotPath(normalizedPath)) {
 372 |       this.godotPath = normalizedPath;
 373 |       this.logDebug(`Godot path set to: ${normalizedPath}`);
 374 |       return true;
 375 |     }
 376 | 
 377 |     this.logDebug(`Failed to set invalid Godot path: ${normalizedPath}`);
 378 |     return false;
 379 |   }
 380 | 
 381 |   /**
 382 |    * Clean up resources when shutting down
 383 |    */
 384 |   private async cleanup() {
 385 |     this.logDebug('Cleaning up resources');
 386 |     if (this.activeProcess) {
 387 |       this.logDebug('Killing active Godot process');
 388 |       this.activeProcess.process.kill();
 389 |       this.activeProcess = null;
 390 |     }
 391 |     await this.server.close();
 392 |   }
 393 | 
 394 |   /**
 395 |    * Check if the Godot version is 4.4 or later
 396 |    * @param version The Godot version string
 397 |    * @returns True if the version is 4.4 or later
 398 |    */
 399 |   private isGodot44OrLater(version: string): boolean {
 400 |     const match = version.match(/^(\d+)\.(\d+)/);
 401 |     if (match) {
 402 |       const major = parseInt(match[1], 10);
 403 |       const minor = parseInt(match[2], 10);
 404 |       return major > 4 || (major === 4 && minor >= 4);
 405 |     }
 406 |     return false;
 407 |   }
 408 | 
 409 |   /**
 410 |    * Normalize parameters to camelCase format
 411 |    * @param params Object with either snake_case or camelCase keys
 412 |    * @returns Object with all keys in camelCase format
 413 |    */
 414 |   private normalizeParameters(params: OperationParams): OperationParams {
 415 |     if (!params || typeof params !== 'object') {
 416 |       return params;
 417 |     }
 418 |     
 419 |     const result: OperationParams = {};
 420 |     
 421 |     for (const key in params) {
 422 |       if (Object.prototype.hasOwnProperty.call(params, key)) {
 423 |         let normalizedKey = key;
 424 |         
 425 |         // If the key is in snake_case, convert it to camelCase using our mapping
 426 |         if (key.includes('_') && this.parameterMappings[key]) {
 427 |           normalizedKey = this.parameterMappings[key];
 428 |         }
 429 |         
 430 |         // Handle nested objects recursively
 431 |         if (typeof params[key] === 'object' && params[key] !== null && !Array.isArray(params[key])) {
 432 |           result[normalizedKey] = this.normalizeParameters(params[key] as OperationParams);
 433 |         } else {
 434 |           result[normalizedKey] = params[key];
 435 |         }
 436 |       }
 437 |     }
 438 |     
 439 |     return result;
 440 |   }
 441 | 
 442 |   /**
 443 |    * Convert camelCase keys to snake_case
 444 |    * @param params Object with camelCase keys
 445 |    * @returns Object with snake_case keys
 446 |    */
 447 |   private convertCamelToSnakeCase(params: OperationParams): OperationParams {
 448 |     const result: OperationParams = {};
 449 |     
 450 |     for (const key in params) {
 451 |       if (Object.prototype.hasOwnProperty.call(params, key)) {
 452 |         // Convert camelCase to snake_case
 453 |         const snakeKey = this.reverseParameterMappings[key] || key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
 454 |         
 455 |         // Handle nested objects recursively
 456 |         if (typeof params[key] === 'object' && params[key] !== null && !Array.isArray(params[key])) {
 457 |           result[snakeKey] = this.convertCamelToSnakeCase(params[key] as OperationParams);
 458 |         } else {
 459 |           result[snakeKey] = params[key];
 460 |         }
 461 |       }
 462 |     }
 463 |     
 464 |     return result;
 465 |   }
 466 | 
 467 |   /**
 468 |    * Execute a Godot operation using the operations script
 469 |    * @param operation The operation to execute
 470 |    * @param params The parameters for the operation
 471 |    * @param projectPath The path to the Godot project
 472 |    * @returns The stdout and stderr from the operation
 473 |    */
 474 |   private async executeOperation(
 475 |     operation: string,
 476 |     params: OperationParams,
 477 |     projectPath: string
 478 |   ): Promise<{ stdout: string; stderr: string }> {
 479 |     this.logDebug(`Executing operation: ${operation} in project: ${projectPath}`);
 480 |     this.logDebug(`Original operation params: ${JSON.stringify(params)}`);
 481 | 
 482 |     // Convert camelCase parameters to snake_case for Godot script
 483 |     const snakeCaseParams = this.convertCamelToSnakeCase(params);
 484 |     this.logDebug(`Converted snake_case params: ${JSON.stringify(snakeCaseParams)}`);
 485 | 
 486 | 
 487 |     // Ensure godotPath is set
 488 |     if (!this.godotPath) {
 489 |       await this.detectGodotPath();
 490 |       if (!this.godotPath) {
 491 |         throw new Error('Could not find a valid Godot executable path');
 492 |       }
 493 |     }
 494 | 
 495 |     try {
 496 |       // Serialize the snake_case parameters to a valid JSON string
 497 |       const paramsJson = JSON.stringify(snakeCaseParams);
 498 |       // Escape single quotes in the JSON string to prevent command injection
 499 |       const escapedParams = paramsJson.replace(/'/g, "'\\''");
 500 |       // On Windows, cmd.exe does not strip single quotes, so we use
 501 |       // double quotes and escape them to ensure the JSON is parsed
 502 |       // correctly by Godot.
 503 |       const isWindows = process.platform === 'win32';
 504 |       const quotedParams = isWindows
 505 |         ? `\"${paramsJson.replace(/\"/g, '\\"')}\"`
 506 |         : `'${escapedParams}'`;
 507 | 
 508 | 
 509 |       // Add debug arguments if debug mode is enabled
 510 |       const debugArgs = GODOT_DEBUG_MODE ? ['--debug-godot'] : [];
 511 | 
 512 |       // Construct the command with the operation and JSON parameters
 513 |       const cmd = [
 514 |         `"${this.godotPath}"`,
 515 |         '--headless',
 516 |         '--path',
 517 |         `"${projectPath}"`,
 518 |         '--script',
 519 |         `"${this.operationsScriptPath}"`,
 520 |         operation,
 521 |         quotedParams, // Pass the JSON string as a single argument
 522 |         ...debugArgs,
 523 |       ].join(' ');
 524 | 
 525 |       this.logDebug(`Command: ${cmd}`);
 526 | 
 527 |       const { stdout, stderr } = await execAsync(cmd);
 528 | 
 529 |       return { stdout, stderr };
 530 |     } catch (error: unknown) {
 531 |       // If execAsync throws, it still contains stdout/stderr
 532 |       if (error instanceof Error && 'stdout' in error && 'stderr' in error) {
 533 |         const execError = error as Error & { stdout: string; stderr: string };
 534 |         return {
 535 |           stdout: execError.stdout,
 536 |           stderr: execError.stderr,
 537 |         };
 538 |       }
 539 | 
 540 |       throw error;
 541 |     }
 542 |   }
 543 | 
 544 |   /**
 545 |    * Get the structure of a Godot project
 546 |    * @param projectPath Path to the Godot project
 547 |    * @returns Object representing the project structure
 548 |    */
 549 |   private async getProjectStructure(projectPath: string): Promise<any> {
 550 |     try {
 551 |       // Get top-level directories in the project
 552 |       const entries = readdirSync(projectPath, { withFileTypes: true });
 553 | 
 554 |       const structure: any = {
 555 |         scenes: [],
 556 |         scripts: [],
 557 |         assets: [],
 558 |         other: [],
 559 |       };
 560 | 
 561 |       for (const entry of entries) {
 562 |         if (entry.isDirectory()) {
 563 |           const dirName = entry.name.toLowerCase();
 564 | 
 565 |           // Skip hidden directories
 566 |           if (dirName.startsWith('.')) {
 567 |             continue;
 568 |           }
 569 | 
 570 |           // Count files in common directories
 571 |           if (dirName === 'scenes' || dirName.includes('scene')) {
 572 |             structure.scenes.push(entry.name);
 573 |           } else if (dirName === 'scripts' || dirName.includes('script')) {
 574 |             structure.scripts.push(entry.name);
 575 |           } else if (
 576 |             dirName === 'assets' ||
 577 |             dirName === 'textures' ||
 578 |             dirName === 'models' ||
 579 |             dirName === 'sounds' ||
 580 |             dirName === 'music'
 581 |           ) {
 582 |             structure.assets.push(entry.name);
 583 |           } else {
 584 |             structure.other.push(entry.name);
 585 |           }
 586 |         }
 587 |       }
 588 | 
 589 |       return structure;
 590 |     } catch (error) {
 591 |       this.logDebug(`Error getting project structure: ${error}`);
 592 |       return { error: 'Failed to get project structure' };
 593 |     }
 594 |   }
 595 | 
 596 |   /**
 597 |    * Find Godot projects in a directory
 598 |    * @param directory Directory to search
 599 |    * @param recursive Whether to search recursively
 600 |    * @returns Array of Godot projects
 601 |    */
 602 |   private findGodotProjects(directory: string, recursive: boolean): Array<{ path: string; name: string }> {
 603 |     const projects: Array<{ path: string; name: string }> = [];
 604 | 
 605 |     try {
 606 |       // Check if the directory itself is a Godot project
 607 |       const projectFile = join(directory, 'project.godot');
 608 |       if (existsSync(projectFile)) {
 609 |         projects.push({
 610 |           path: directory,
 611 |           name: basename(directory),
 612 |         });
 613 |       }
 614 | 
 615 |       // If not recursive, only check immediate subdirectories
 616 |       if (!recursive) {
 617 |         const entries = readdirSync(directory, { withFileTypes: true });
 618 |         for (const entry of entries) {
 619 |           if (entry.isDirectory()) {
 620 |             const subdir = join(directory, entry.name);
 621 |             const projectFile = join(subdir, 'project.godot');
 622 |             if (existsSync(projectFile)) {
 623 |               projects.push({
 624 |                 path: subdir,
 625 |                 name: entry.name,
 626 |               });
 627 |             }
 628 |           }
 629 |         }
 630 |       } else {
 631 |         // Recursive search
 632 |         const entries = readdirSync(directory, { withFileTypes: true });
 633 |         for (const entry of entries) {
 634 |           if (entry.isDirectory()) {
 635 |             const subdir = join(directory, entry.name);
 636 |             // Skip hidden directories
 637 |             if (entry.name.startsWith('.')) {
 638 |               continue;
 639 |             }
 640 |             // Check if this directory is a Godot project
 641 |             const projectFile = join(subdir, 'project.godot');
 642 |             if (existsSync(projectFile)) {
 643 |               projects.push({
 644 |                 path: subdir,
 645 |                 name: entry.name,
 646 |               });
 647 |             } else {
 648 |               // Recursively search this directory
 649 |               const subProjects = this.findGodotProjects(subdir, true);
 650 |               projects.push(...subProjects);
 651 |             }
 652 |           }
 653 |         }
 654 |       }
 655 |     } catch (error) {
 656 |       this.logDebug(`Error searching directory ${directory}: ${error}`);
 657 |     }
 658 | 
 659 |     return projects;
 660 |   }
 661 | 
 662 |   /**
 663 |    * Set up the tool handlers for the MCP server
 664 |    */
 665 |   private setupToolHandlers() {
 666 |     // Define available tools
 667 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
 668 |       tools: [
 669 |         {
 670 |           name: 'launch_editor',
 671 |           description: 'Launch Godot editor for a specific project',
 672 |           inputSchema: {
 673 |             type: 'object',
 674 |             properties: {
 675 |               projectPath: {
 676 |                 type: 'string',
 677 |                 description: 'Path to the Godot project directory',
 678 |               },
 679 |             },
 680 |             required: ['projectPath'],
 681 |           },
 682 |         },
 683 |         {
 684 |           name: 'run_project',
 685 |           description: 'Run the Godot project and capture output',
 686 |           inputSchema: {
 687 |             type: 'object',
 688 |             properties: {
 689 |               projectPath: {
 690 |                 type: 'string',
 691 |                 description: 'Path to the Godot project directory',
 692 |               },
 693 |               scene: {
 694 |                 type: 'string',
 695 |                 description: 'Optional: Specific scene to run',
 696 |               },
 697 |             },
 698 |             required: ['projectPath'],
 699 |           },
 700 |         },
 701 |         {
 702 |           name: 'get_debug_output',
 703 |           description: 'Get the current debug output and errors',
 704 |           inputSchema: {
 705 |             type: 'object',
 706 |             properties: {},
 707 |             required: [],
 708 |           },
 709 |         },
 710 |         {
 711 |           name: 'stop_project',
 712 |           description: 'Stop the currently running Godot project',
 713 |           inputSchema: {
 714 |             type: 'object',
 715 |             properties: {},
 716 |             required: [],
 717 |           },
 718 |         },
 719 |         {
 720 |           name: 'get_godot_version',
 721 |           description: 'Get the installed Godot version',
 722 |           inputSchema: {
 723 |             type: 'object',
 724 |             properties: {},
 725 |             required: [],
 726 |           },
 727 |         },
 728 |         {
 729 |           name: 'list_projects',
 730 |           description: 'List Godot projects in a directory',
 731 |           inputSchema: {
 732 |             type: 'object',
 733 |             properties: {
 734 |               directory: {
 735 |                 type: 'string',
 736 |                 description: 'Directory to search for Godot projects',
 737 |               },
 738 |               recursive: {
 739 |                 type: 'boolean',
 740 |                 description: 'Whether to search recursively (default: false)',
 741 |               },
 742 |             },
 743 |             required: ['directory'],
 744 |           },
 745 |         },
 746 |         {
 747 |           name: 'get_project_info',
 748 |           description: 'Retrieve metadata about a Godot project',
 749 |           inputSchema: {
 750 |             type: 'object',
 751 |             properties: {
 752 |               projectPath: {
 753 |                 type: 'string',
 754 |                 description: 'Path to the Godot project directory',
 755 |               },
 756 |             },
 757 |             required: ['projectPath'],
 758 |           },
 759 |         },
 760 |         {
 761 |           name: 'create_scene',
 762 |           description: 'Create a new Godot scene file',
 763 |           inputSchema: {
 764 |             type: 'object',
 765 |             properties: {
 766 |               projectPath: {
 767 |                 type: 'string',
 768 |                 description: 'Path to the Godot project directory',
 769 |               },
 770 |               scenePath: {
 771 |                 type: 'string',
 772 |                 description: 'Path where the scene file will be saved (relative to project)',
 773 |               },
 774 |               rootNodeType: {
 775 |                 type: 'string',
 776 |                 description: 'Type of the root node (e.g., Node2D, Node3D)',
 777 |                 default: 'Node2D',
 778 |               },
 779 |             },
 780 |             required: ['projectPath', 'scenePath'],
 781 |           },
 782 |         },
 783 |         {
 784 |           name: 'add_node',
 785 |           description: 'Add a node to an existing scene',
 786 |           inputSchema: {
 787 |             type: 'object',
 788 |             properties: {
 789 |               projectPath: {
 790 |                 type: 'string',
 791 |                 description: 'Path to the Godot project directory',
 792 |               },
 793 |               scenePath: {
 794 |                 type: 'string',
 795 |                 description: 'Path to the scene file (relative to project)',
 796 |               },
 797 |               parentNodePath: {
 798 |                 type: 'string',
 799 |                 description: 'Path to the parent node (e.g., "root" or "root/Player")',
 800 |                 default: 'root',
 801 |               },
 802 |               nodeType: {
 803 |                 type: 'string',
 804 |                 description: 'Type of node to add (e.g., Sprite2D, CollisionShape2D)',
 805 |               },
 806 |               nodeName: {
 807 |                 type: 'string',
 808 |                 description: 'Name for the new node',
 809 |               },
 810 |               properties: {
 811 |                 type: 'object',
 812 |                 description: 'Optional properties to set on the node',
 813 |               },
 814 |             },
 815 |             required: ['projectPath', 'scenePath', 'nodeType', 'nodeName'],
 816 |           },
 817 |         },
 818 |         {
 819 |           name: 'load_sprite',
 820 |           description: 'Load a sprite into a Sprite2D node',
 821 |           inputSchema: {
 822 |             type: 'object',
 823 |             properties: {
 824 |               projectPath: {
 825 |                 type: 'string',
 826 |                 description: 'Path to the Godot project directory',
 827 |               },
 828 |               scenePath: {
 829 |                 type: 'string',
 830 |                 description: 'Path to the scene file (relative to project)',
 831 |               },
 832 |               nodePath: {
 833 |                 type: 'string',
 834 |                 description: 'Path to the Sprite2D node (e.g., "root/Player/Sprite2D")',
 835 |               },
 836 |               texturePath: {
 837 |                 type: 'string',
 838 |                 description: 'Path to the texture file (relative to project)',
 839 |               },
 840 |             },
 841 |             required: ['projectPath', 'scenePath', 'nodePath', 'texturePath'],
 842 |           },
 843 |         },
 844 |         {
 845 |           name: 'export_mesh_library',
 846 |           description: 'Export a scene as a MeshLibrary resource',
 847 |           inputSchema: {
 848 |             type: 'object',
 849 |             properties: {
 850 |               projectPath: {
 851 |                 type: 'string',
 852 |                 description: 'Path to the Godot project directory',
 853 |               },
 854 |               scenePath: {
 855 |                 type: 'string',
 856 |                 description: 'Path to the scene file (.tscn) to export',
 857 |               },
 858 |               outputPath: {
 859 |                 type: 'string',
 860 |                 description: 'Path where the mesh library (.res) will be saved',
 861 |               },
 862 |               meshItemNames: {
 863 |                 type: 'array',
 864 |                 items: {
 865 |                   type: 'string',
 866 |                 },
 867 |                 description: 'Optional: Names of specific mesh items to include (defaults to all)',
 868 |               },
 869 |             },
 870 |             required: ['projectPath', 'scenePath', 'outputPath'],
 871 |           },
 872 |         },
 873 |         {
 874 |           name: 'save_scene',
 875 |           description: 'Save changes to a scene file',
 876 |           inputSchema: {
 877 |             type: 'object',
 878 |             properties: {
 879 |               projectPath: {
 880 |                 type: 'string',
 881 |                 description: 'Path to the Godot project directory',
 882 |               },
 883 |               scenePath: {
 884 |                 type: 'string',
 885 |                 description: 'Path to the scene file (relative to project)',
 886 |               },
 887 |               newPath: {
 888 |                 type: 'string',
 889 |                 description: 'Optional: New path to save the scene to (for creating variants)',
 890 |               },
 891 |             },
 892 |             required: ['projectPath', 'scenePath'],
 893 |           },
 894 |         },
 895 |         {
 896 |           name: 'get_uid',
 897 |           description: 'Get the UID for a specific file in a Godot project (for Godot 4.4+)',
 898 |           inputSchema: {
 899 |             type: 'object',
 900 |             properties: {
 901 |               projectPath: {
 902 |                 type: 'string',
 903 |                 description: 'Path to the Godot project directory',
 904 |               },
 905 |               filePath: {
 906 |                 type: 'string',
 907 |                 description: 'Path to the file (relative to project) for which to get the UID',
 908 |               },
 909 |             },
 910 |             required: ['projectPath', 'filePath'],
 911 |           },
 912 |         },
 913 |         {
 914 |           name: 'update_project_uids',
 915 |           description: 'Update UID references in a Godot project by resaving resources (for Godot 4.4+)',
 916 |           inputSchema: {
 917 |             type: 'object',
 918 |             properties: {
 919 |               projectPath: {
 920 |                 type: 'string',
 921 |                 description: 'Path to the Godot project directory',
 922 |               },
 923 |             },
 924 |             required: ['projectPath'],
 925 |           },
 926 |         },
 927 |       ],
 928 |     }));
 929 | 
 930 |     // Handle tool calls
 931 |     this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
 932 |       this.logDebug(`Handling tool request: ${request.params.name}`);
 933 |       switch (request.params.name) {
 934 |         case 'launch_editor':
 935 |           return await this.handleLaunchEditor(request.params.arguments);
 936 |         case 'run_project':
 937 |           return await this.handleRunProject(request.params.arguments);
 938 |         case 'get_debug_output':
 939 |           return await this.handleGetDebugOutput();
 940 |         case 'stop_project':
 941 |           return await this.handleStopProject();
 942 |         case 'get_godot_version':
 943 |           return await this.handleGetGodotVersion();
 944 |         case 'list_projects':
 945 |           return await this.handleListProjects(request.params.arguments);
 946 |         case 'get_project_info':
 947 |           return await this.handleGetProjectInfo(request.params.arguments);
 948 |         case 'create_scene':
 949 |           return await this.handleCreateScene(request.params.arguments);
 950 |         case 'add_node':
 951 |           return await this.handleAddNode(request.params.arguments);
 952 |         case 'load_sprite':
 953 |           return await this.handleLoadSprite(request.params.arguments);
 954 |         case 'export_mesh_library':
 955 |           return await this.handleExportMeshLibrary(request.params.arguments);
 956 |         case 'save_scene':
 957 |           return await this.handleSaveScene(request.params.arguments);
 958 |         case 'get_uid':
 959 |           return await this.handleGetUid(request.params.arguments);
 960 |         case 'update_project_uids':
 961 |           return await this.handleUpdateProjectUids(request.params.arguments);
 962 |         default:
 963 |           throw new McpError(
 964 |             ErrorCode.MethodNotFound,
 965 |             `Unknown tool: ${request.params.name}`
 966 |           );
 967 |       }
 968 |     });
 969 |   }
 970 | 
 971 |   /**
 972 |    * Handle the launch_editor tool
 973 |    * @param args Tool arguments
 974 |    */
 975 |   private async handleLaunchEditor(args: any) {
 976 |     // Normalize parameters to camelCase
 977 |     args = this.normalizeParameters(args);
 978 |     
 979 |     if (!args.projectPath) {
 980 |       return this.createErrorResponse(
 981 |         'Project path is required',
 982 |         ['Provide a valid path to a Godot project directory']
 983 |       );
 984 |     }
 985 | 
 986 |     if (!this.validatePath(args.projectPath)) {
 987 |       return this.createErrorResponse(
 988 |         'Invalid project path',
 989 |         ['Provide a valid path without ".." or other potentially unsafe characters']
 990 |       );
 991 |     }
 992 | 
 993 |     try {
 994 |       // Ensure godotPath is set
 995 |       if (!this.godotPath) {
 996 |         await this.detectGodotPath();
 997 |         if (!this.godotPath) {
 998 |           return this.createErrorResponse(
 999 |             'Could not find a valid Godot executable path',
1000 |             [
1001 |               'Ensure Godot is installed correctly',
1002 |               'Set GODOT_PATH environment variable to specify the correct path',
1003 |             ]
1004 |           );
1005 |         }
1006 |       }
1007 | 
1008 |       // Check if the project directory exists and contains a project.godot file
1009 |       const projectFile = join(args.projectPath, 'project.godot');
1010 |       if (!existsSync(projectFile)) {
1011 |         return this.createErrorResponse(
1012 |           `Not a valid Godot project: ${args.projectPath}`,
1013 |           [
1014 |             'Ensure the path points to a directory containing a project.godot file',
1015 |             'Use list_projects to find valid Godot projects',
1016 |           ]
1017 |         );
1018 |       }
1019 | 
1020 |       this.logDebug(`Launching Godot editor for project: ${args.projectPath}`);
1021 |       const process = spawn(this.godotPath, ['-e', '--path', args.projectPath], {
1022 |         stdio: 'pipe',
1023 |       });
1024 | 
1025 |       process.on('error', (err: Error) => {
1026 |         console.error('Failed to start Godot editor:', err);
1027 |       });
1028 | 
1029 |       return {
1030 |         content: [
1031 |           {
1032 |             type: 'text',
1033 |             text: `Godot editor launched successfully for project at ${args.projectPath}.`,
1034 |           },
1035 |         ],
1036 |       };
1037 |     } catch (error: unknown) {
1038 |       const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1039 |       return this.createErrorResponse(
1040 |         `Failed to launch Godot editor: ${errorMessage}`,
1041 |         [
1042 |           'Ensure Godot is installed correctly',
1043 |           'Check if the GODOT_PATH environment variable is set correctly',
1044 |           'Verify the project path is accessible',
1045 |         ]
1046 |       );
1047 |     }
1048 |   }
1049 | 
1050 |   /**
1051 |    * Handle the run_project tool
1052 |    * @param args Tool arguments
1053 |    */
1054 |   private async handleRunProject(args: any) {
1055 |     // Normalize parameters to camelCase
1056 |     args = this.normalizeParameters(args);
1057 |     
1058 |     if (!args.projectPath) {
1059 |       return this.createErrorResponse(
1060 |         'Project path is required',
1061 |         ['Provide a valid path to a Godot project directory']
1062 |       );
1063 |     }
1064 | 
1065 |     if (!this.validatePath(args.projectPath)) {
1066 |       return this.createErrorResponse(
1067 |         'Invalid project path',
1068 |         ['Provide a valid path without ".." or other potentially unsafe characters']
1069 |       );
1070 |     }
1071 | 
1072 |     try {
1073 |       // Check if the project directory exists and contains a project.godot file
1074 |       const projectFile = join(args.projectPath, 'project.godot');
1075 |       if (!existsSync(projectFile)) {
1076 |         return this.createErrorResponse(
1077 |           `Not a valid Godot project: ${args.projectPath}`,
1078 |           [
1079 |             'Ensure the path points to a directory containing a project.godot file',
1080 |             'Use list_projects to find valid Godot projects',
1081 |           ]
1082 |         );
1083 |       }
1084 | 
1085 |       // Kill any existing process
1086 |       if (this.activeProcess) {
1087 |         this.logDebug('Killing existing Godot process before starting a new one');
1088 |         this.activeProcess.process.kill();
1089 |       }
1090 | 
1091 |       const cmdArgs = ['-d', '--path', args.projectPath];
1092 |       if (args.scene && this.validatePath(args.scene)) {
1093 |         this.logDebug(`Adding scene parameter: ${args.scene}`);
1094 |         cmdArgs.push(args.scene);
1095 |       }
1096 | 
1097 |       this.logDebug(`Running Godot project: ${args.projectPath}`);
1098 |       const process = spawn(this.godotPath!, cmdArgs, { stdio: 'pipe' });
1099 |       const output: string[] = [];
1100 |       const errors: string[] = [];
1101 | 
1102 |       process.stdout?.on('data', (data: Buffer) => {
1103 |         const lines = data.toString().split('\n');
1104 |         output.push(...lines);
1105 |         lines.forEach((line: string) => {
1106 |           if (line.trim()) this.logDebug(`[Godot stdout] ${line}`);
1107 |         });
1108 |       });
1109 | 
1110 |       process.stderr?.on('data', (data: Buffer) => {
1111 |         const lines = data.toString().split('\n');
1112 |         errors.push(...lines);
1113 |         lines.forEach((line: string) => {
1114 |           if (line.trim()) this.logDebug(`[Godot stderr] ${line}`);
1115 |         });
1116 |       });
1117 | 
1118 |       process.on('exit', (code: number | null) => {
1119 |         this.logDebug(`Godot process exited with code ${code}`);
1120 |         if (this.activeProcess && this.activeProcess.process === process) {
1121 |           this.activeProcess = null;
1122 |         }
1123 |       });
1124 | 
1125 |       process.on('error', (err: Error) => {
1126 |         console.error('Failed to start Godot process:', err);
1127 |         if (this.activeProcess && this.activeProcess.process === process) {
1128 |           this.activeProcess = null;
1129 |         }
1130 |       });
1131 | 
1132 |       this.activeProcess = { process, output, errors };
1133 | 
1134 |       return {
1135 |         content: [
1136 |           {
1137 |             type: 'text',
1138 |             text: `Godot project started in debug mode. Use get_debug_output to see output.`,
1139 |           },
1140 |         ],
1141 |       };
1142 |     } catch (error: unknown) {
1143 |       const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1144 |       return this.createErrorResponse(
1145 |         `Failed to run Godot project: ${errorMessage}`,
1146 |         [
1147 |           'Ensure Godot is installed correctly',
1148 |           'Check if the GODOT_PATH environment variable is set correctly',
1149 |           'Verify the project path is accessible',
1150 |         ]
1151 |       );
1152 |     }
1153 |   }
1154 | 
1155 |   /**
1156 |    * Handle the get_debug_output tool
1157 |    */
1158 |   private async handleGetDebugOutput() {
1159 |     if (!this.activeProcess) {
1160 |       return this.createErrorResponse(
1161 |         'No active Godot process.',
1162 |         [
1163 |           'Use run_project to start a Godot project first',
1164 |           'Check if the Godot process crashed unexpectedly',
1165 |         ]
1166 |       );
1167 |     }
1168 | 
1169 |     return {
1170 |       content: [
1171 |         {
1172 |           type: 'text',
1173 |           text: JSON.stringify(
1174 |             {
1175 |               output: this.activeProcess.output,
1176 |               errors: this.activeProcess.errors,
1177 |             },
1178 |             null,
1179 |             2
1180 |           ),
1181 |         },
1182 |       ],
1183 |     };
1184 |   }
1185 | 
1186 |   /**
1187 |    * Handle the stop_project tool
1188 |    */
1189 |   private async handleStopProject() {
1190 |     if (!this.activeProcess) {
1191 |       return this.createErrorResponse(
1192 |         'No active Godot process to stop.',
1193 |         [
1194 |           'Use run_project to start a Godot project first',
1195 |           'The process may have already terminated',
1196 |         ]
1197 |       );
1198 |     }
1199 | 
1200 |     this.logDebug('Stopping active Godot process');
1201 |     this.activeProcess.process.kill();
1202 |     const output = this.activeProcess.output;
1203 |     const errors = this.activeProcess.errors;
1204 |     this.activeProcess = null;
1205 | 
1206 |     return {
1207 |       content: [
1208 |         {
1209 |           type: 'text',
1210 |           text: JSON.stringify(
1211 |             {
1212 |               message: 'Godot project stopped',
1213 |               finalOutput: output,
1214 |               finalErrors: errors,
1215 |             },
1216 |             null,
1217 |             2
1218 |           ),
1219 |         },
1220 |       ],
1221 |     };
1222 |   }
1223 | 
1224 |   /**
1225 |    * Handle the get_godot_version tool
1226 |    */
1227 |   private async handleGetGodotVersion() {
1228 |     try {
1229 |       // Ensure godotPath is set
1230 |       if (!this.godotPath) {
1231 |         await this.detectGodotPath();
1232 |         if (!this.godotPath) {
1233 |           return this.createErrorResponse(
1234 |             'Could not find a valid Godot executable path',
1235 |             [
1236 |               'Ensure Godot is installed correctly',
1237 |               'Set GODOT_PATH environment variable to specify the correct path',
1238 |             ]
1239 |           );
1240 |         }
1241 |       }
1242 | 
1243 |       this.logDebug('Getting Godot version');
1244 |       const { stdout } = await execAsync(`"${this.godotPath}" --version`);
1245 |       return {
1246 |         content: [
1247 |           {
1248 |             type: 'text',
1249 |             text: stdout.trim(),
1250 |           },
1251 |         ],
1252 |       };
1253 |     } catch (error: unknown) {
1254 |       const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1255 |       return this.createErrorResponse(
1256 |         `Failed to get Godot version: ${errorMessage}`,
1257 |         [
1258 |           'Ensure Godot is installed correctly',
1259 |           'Check if the GODOT_PATH environment variable is set correctly',
1260 |         ]
1261 |       );
1262 |     }
1263 |   }
1264 | 
1265 |   /**
1266 |    * Handle the list_projects tool
1267 |    */
1268 |   private async handleListProjects(args: any) {
1269 |     // Normalize parameters to camelCase
1270 |     args = this.normalizeParameters(args);
1271 |     
1272 |     if (!args.directory) {
1273 |       return this.createErrorResponse(
1274 |         'Directory is required',
1275 |         ['Provide a valid directory path to search for Godot projects']
1276 |       );
1277 |     }
1278 | 
1279 |     if (!this.validatePath(args.directory)) {
1280 |       return this.createErrorResponse(
1281 |         'Invalid directory path',
1282 |         ['Provide a valid path without ".." or other potentially unsafe characters']
1283 |       );
1284 |     }
1285 | 
1286 |     try {
1287 |       this.logDebug(`Listing Godot projects in directory: ${args.directory}`);
1288 |       if (!existsSync(args.directory)) {
1289 |         return this.createErrorResponse(
1290 |           `Directory does not exist: ${args.directory}`,
1291 |           ['Provide a valid directory path that exists on the system']
1292 |         );
1293 |       }
1294 | 
1295 |       const recursive = args.recursive === true;
1296 |       const projects = this.findGodotProjects(args.directory, recursive);
1297 | 
1298 |       return {
1299 |         content: [
1300 |           {
1301 |             type: 'text',
1302 |             text: JSON.stringify(projects, null, 2),
1303 |           },
1304 |         ],
1305 |       };
1306 |     } catch (error: any) {
1307 |       return this.createErrorResponse(
1308 |         `Failed to list projects: ${error?.message || 'Unknown error'}`,
1309 |         [
1310 |           'Ensure the directory exists and is accessible',
1311 |           'Check if you have permission to read the directory',
1312 |         ]
1313 |       );
1314 |     }
1315 |   }
1316 | 
1317 |   /**
1318 |    * Get the structure of a Godot project asynchronously by counting files recursively
1319 |    * @param projectPath Path to the Godot project
1320 |    * @returns Promise resolving to an object with counts of scenes, scripts, assets, and other files
1321 |    */
1322 |   private getProjectStructureAsync(projectPath: string): Promise<any> {
1323 |     return new Promise((resolve) => {
1324 |       try {
1325 |         const structure = {
1326 |           scenes: 0,
1327 |           scripts: 0,
1328 |           assets: 0,
1329 |           other: 0,
1330 |         };
1331 | 
1332 |         const scanDirectory = (currentPath: string) => {
1333 |           const entries = readdirSync(currentPath, { withFileTypes: true });
1334 |           
1335 |           for (const entry of entries) {
1336 |             const entryPath = join(currentPath, entry.name);
1337 |             
1338 |             // Skip hidden files and directories
1339 |             if (entry.name.startsWith('.')) {
1340 |               continue;
1341 |             }
1342 |             
1343 |             if (entry.isDirectory()) {
1344 |               // Recursively scan subdirectories
1345 |               scanDirectory(entryPath);
1346 |             } else if (entry.isFile()) {
1347 |               // Count file by extension
1348 |               const ext = entry.name.split('.').pop()?.toLowerCase();
1349 |               
1350 |               if (ext === 'tscn') {
1351 |                 structure.scenes++;
1352 |               } else if (ext === 'gd' || ext === 'gdscript' || ext === 'cs') {
1353 |                 structure.scripts++;
1354 |               } else if (['png', 'jpg', 'jpeg', 'webp', 'svg', 'ttf', 'wav', 'mp3', 'ogg'].includes(ext || '')) {
1355 |                 structure.assets++;
1356 |               } else {
1357 |                 structure.other++;
1358 |               }
1359 |             }
1360 |           }
1361 |         };
1362 |         
1363 |         // Start scanning from the project root
1364 |         scanDirectory(projectPath);
1365 |         resolve(structure);
1366 |       } catch (error) {
1367 |         this.logDebug(`Error getting project structure asynchronously: ${error}`);
1368 |         resolve({ 
1369 |           error: 'Failed to get project structure',
1370 |           scenes: 0,
1371 |           scripts: 0,
1372 |           assets: 0,
1373 |           other: 0
1374 |         });
1375 |       }
1376 |     });
1377 |   }
1378 | 
1379 |   /**
1380 |    * Handle the get_project_info tool
1381 |    */
1382 |   private async handleGetProjectInfo(args: any) {
1383 |     // Normalize parameters to camelCase
1384 |     args = this.normalizeParameters(args);
1385 |     
1386 |     if (!args.projectPath) {
1387 |       return this.createErrorResponse(
1388 |         'Project path is required',
1389 |         ['Provide a valid path to a Godot project directory']
1390 |       );
1391 |     }
1392 |   
1393 |     if (!this.validatePath(args.projectPath)) {
1394 |       return this.createErrorResponse(
1395 |         'Invalid project path',
1396 |         ['Provide a valid path without ".." or other potentially unsafe characters']
1397 |       );
1398 |     }
1399 |   
1400 |     try {
1401 |       // Ensure godotPath is set
1402 |       if (!this.godotPath) {
1403 |         await this.detectGodotPath();
1404 |         if (!this.godotPath) {
1405 |           return this.createErrorResponse(
1406 |             'Could not find a valid Godot executable path',
1407 |             [
1408 |               'Ensure Godot is installed correctly',
1409 |               'Set GODOT_PATH environment variable to specify the correct path',
1410 |             ]
1411 |           );
1412 |         }
1413 |       }
1414 |   
1415 |       // Check if the project directory exists and contains a project.godot file
1416 |       const projectFile = join(args.projectPath, 'project.godot');
1417 |       if (!existsSync(projectFile)) {
1418 |         return this.createErrorResponse(
1419 |           `Not a valid Godot project: ${args.projectPath}`,
1420 |           [
1421 |             'Ensure the path points to a directory containing a project.godot file',
1422 |             'Use list_projects to find valid Godot projects',
1423 |           ]
1424 |         );
1425 |       }
1426 |   
1427 |       this.logDebug(`Getting project info for: ${args.projectPath}`);
1428 |   
1429 |       // Get Godot version
1430 |       const execOptions = { timeout: 10000 }; // 10 second timeout
1431 |       const { stdout } = await execAsync(`"${this.godotPath}" --version`, execOptions);
1432 |   
1433 |       // Get project structure using the recursive method
1434 |       const projectStructure = await this.getProjectStructureAsync(args.projectPath);
1435 |   
1436 |       // Extract project name from project.godot file
1437 |       let projectName = basename(args.projectPath);
1438 |       try {
1439 |         const fs = require('fs');
1440 |         const projectFileContent = fs.readFileSync(projectFile, 'utf8');
1441 |         const configNameMatch = projectFileContent.match(/config\/name="([^"]+)"/);
1442 |         if (configNameMatch && configNameMatch[1]) {
1443 |           projectName = configNameMatch[1];
1444 |           this.logDebug(`Found project name in config: ${projectName}`);
1445 |         }
1446 |       } catch (error) {
1447 |         this.logDebug(`Error reading project file: ${error}`);
1448 |         // Continue with default project name if extraction fails
1449 |       }
1450 |   
1451 |       return {
1452 |         content: [
1453 |           {
1454 |             type: 'text',
1455 |             text: JSON.stringify(
1456 |               {
1457 |                 name: projectName,
1458 |                 path: args.projectPath,
1459 |                 godotVersion: stdout.trim(),
1460 |                 structure: projectStructure,
1461 |               },
1462 |               null,
1463 |               2
1464 |             ),
1465 |           },
1466 |         ],
1467 |       };
1468 |     } catch (error: any) {
1469 |       return this.createErrorResponse(
1470 |         `Failed to get project info: ${error?.message || 'Unknown error'}`,
1471 |         [
1472 |           'Ensure Godot is installed correctly',
1473 |           'Check if the GODOT_PATH environment variable is set correctly',
1474 |           'Verify the project path is accessible',
1475 |         ]
1476 |       );
1477 |     }
1478 |   }
1479 | 
1480 |   /**
1481 |    * Handle the create_scene tool
1482 |    */
1483 |   private async handleCreateScene(args: any) {
1484 |     // Normalize parameters to camelCase
1485 |     args = this.normalizeParameters(args);
1486 |     
1487 |     if (!args.projectPath || !args.scenePath) {
1488 |       return this.createErrorResponse(
1489 |         'Project path and scene path are required',
1490 |         ['Provide valid paths for both the project and the scene']
1491 |       );
1492 |     }
1493 | 
1494 |     if (!this.validatePath(args.projectPath) || !this.validatePath(args.scenePath)) {
1495 |       return this.createErrorResponse(
1496 |         'Invalid path',
1497 |         ['Provide valid paths without ".." or other potentially unsafe characters']
1498 |       );
1499 |     }
1500 | 
1501 |     try {
1502 |       // Check if the project directory exists and contains a project.godot file
1503 |       const projectFile = join(args.projectPath, 'project.godot');
1504 |       if (!existsSync(projectFile)) {
1505 |         return this.createErrorResponse(
1506 |           `Not a valid Godot project: ${args.projectPath}`,
1507 |           [
1508 |             'Ensure the path points to a directory containing a project.godot file',
1509 |             'Use list_projects to find valid Godot projects',
1510 |           ]
1511 |         );
1512 |       }
1513 | 
1514 |       // Prepare parameters for the operation (already in camelCase)
1515 |       const params = {
1516 |         scenePath: args.scenePath,
1517 |         rootNodeType: args.rootNodeType || 'Node2D',
1518 |       };
1519 | 
1520 |       // Execute the operation
1521 |       const { stdout, stderr } = await this.executeOperation('create_scene', params, args.projectPath);
1522 | 
1523 |       if (stderr && stderr.includes('Failed to')) {
1524 |         return this.createErrorResponse(
1525 |           `Failed to create scene: ${stderr}`,
1526 |           [
1527 |             'Check if the root node type is valid',
1528 |             'Ensure you have write permissions to the scene path',
1529 |             'Verify the scene path is valid',
1530 |           ]
1531 |         );
1532 |       }
1533 | 
1534 |       return {
1535 |         content: [
1536 |           {
1537 |             type: 'text',
1538 |             text: `Scene created successfully at: ${args.scenePath}\n\nOutput: ${stdout}`,
1539 |           },
1540 |         ],
1541 |       };
1542 |     } catch (error: any) {
1543 |       return this.createErrorResponse(
1544 |         `Failed to create scene: ${error?.message || 'Unknown error'}`,
1545 |         [
1546 |           'Ensure Godot is installed correctly',
1547 |           'Check if the GODOT_PATH environment variable is set correctly',
1548 |           'Verify the project path is accessible',
1549 |         ]
1550 |       );
1551 |     }
1552 |   }
1553 | 
1554 |   /**
1555 |    * Handle the add_node tool
1556 |    */
1557 |   private async handleAddNode(args: any) {
1558 |     // Normalize parameters to camelCase
1559 |     args = this.normalizeParameters(args);
1560 |     
1561 |     if (!args.projectPath || !args.scenePath || !args.nodeType || !args.nodeName) {
1562 |       return this.createErrorResponse(
1563 |         'Missing required parameters',
1564 |         ['Provide projectPath, scenePath, nodeType, and nodeName']
1565 |       );
1566 |     }
1567 | 
1568 |     if (!this.validatePath(args.projectPath) || !this.validatePath(args.scenePath)) {
1569 |       return this.createErrorResponse(
1570 |         'Invalid path',
1571 |         ['Provide valid paths without ".." or other potentially unsafe characters']
1572 |       );
1573 |     }
1574 | 
1575 |     try {
1576 |       // Check if the project directory exists and contains a project.godot file
1577 |       const projectFile = join(args.projectPath, 'project.godot');
1578 |       if (!existsSync(projectFile)) {
1579 |         return this.createErrorResponse(
1580 |           `Not a valid Godot project: ${args.projectPath}`,
1581 |           [
1582 |             'Ensure the path points to a directory containing a project.godot file',
1583 |             'Use list_projects to find valid Godot projects',
1584 |           ]
1585 |         );
1586 |       }
1587 | 
1588 |       // Check if the scene file exists
1589 |       const scenePath = join(args.projectPath, args.scenePath);
1590 |       if (!existsSync(scenePath)) {
1591 |         return this.createErrorResponse(
1592 |           `Scene file does not exist: ${args.scenePath}`,
1593 |           [
1594 |             'Ensure the scene path is correct',
1595 |             'Use create_scene to create a new scene first',
1596 |           ]
1597 |         );
1598 |       }
1599 | 
1600 |       // Prepare parameters for the operation (already in camelCase)
1601 |       const params: any = {
1602 |         scenePath: args.scenePath,
1603 |         nodeType: args.nodeType,
1604 |         nodeName: args.nodeName,
1605 |       };
1606 | 
1607 |       // Add optional parameters
1608 |       if (args.parentNodePath) {
1609 |         params.parentNodePath = args.parentNodePath;
1610 |       }
1611 | 
1612 |       if (args.properties) {
1613 |         params.properties = args.properties;
1614 |       }
1615 | 
1616 |       // Execute the operation
1617 |       const { stdout, stderr } = await this.executeOperation('add_node', params, args.projectPath);
1618 | 
1619 |       if (stderr && stderr.includes('Failed to')) {
1620 |         return this.createErrorResponse(
1621 |           `Failed to add node: ${stderr}`,
1622 |           [
1623 |             'Check if the node type is valid',
1624 |             'Ensure the parent node path exists',
1625 |             'Verify the scene file is valid',
1626 |           ]
1627 |         );
1628 |       }
1629 | 
1630 |       return {
1631 |         content: [
1632 |           {
1633 |             type: 'text',
1634 |             text: `Node '${args.nodeName}' of type '${args.nodeType}' added successfully to '${args.scenePath}'.\n\nOutput: ${stdout}`,
1635 |           },
1636 |         ],
1637 |       };
1638 |     } catch (error: any) {
1639 |       return this.createErrorResponse(
1640 |         `Failed to add node: ${error?.message || 'Unknown error'}`,
1641 |         [
1642 |           'Ensure Godot is installed correctly',
1643 |           'Check if the GODOT_PATH environment variable is set correctly',
1644 |           'Verify the project path is accessible',
1645 |         ]
1646 |       );
1647 |     }
1648 |   }
1649 | 
1650 |   /**
1651 |    * Handle the load_sprite tool
1652 |    */
1653 |   private async handleLoadSprite(args: any) {
1654 |     // Normalize parameters to camelCase
1655 |     args = this.normalizeParameters(args);
1656 |     
1657 |     if (!args.projectPath || !args.scenePath || !args.nodePath || !args.texturePath) {
1658 |       return this.createErrorResponse(
1659 |         'Missing required parameters',
1660 |         ['Provide projectPath, scenePath, nodePath, and texturePath']
1661 |       );
1662 |     }
1663 | 
1664 |     if (
1665 |       !this.validatePath(args.projectPath) ||
1666 |       !this.validatePath(args.scenePath) ||
1667 |       !this.validatePath(args.nodePath) ||
1668 |       !this.validatePath(args.texturePath)
1669 |     ) {
1670 |       return this.createErrorResponse(
1671 |         'Invalid path',
1672 |         ['Provide valid paths without ".." or other potentially unsafe characters']
1673 |       );
1674 |     }
1675 | 
1676 |     try {
1677 |       // Check if the project directory exists and contains a project.godot file
1678 |       const projectFile = join(args.projectPath, 'project.godot');
1679 |       if (!existsSync(projectFile)) {
1680 |         return this.createErrorResponse(
1681 |           `Not a valid Godot project: ${args.projectPath}`,
1682 |           [
1683 |             'Ensure the path points to a directory containing a project.godot file',
1684 |             'Use list_projects to find valid Godot projects',
1685 |           ]
1686 |         );
1687 |       }
1688 | 
1689 |       // Check if the scene file exists
1690 |       const scenePath = join(args.projectPath, args.scenePath);
1691 |       if (!existsSync(scenePath)) {
1692 |         return this.createErrorResponse(
1693 |           `Scene file does not exist: ${args.scenePath}`,
1694 |           [
1695 |             'Ensure the scene path is correct',
1696 |             'Use create_scene to create a new scene first',
1697 |           ]
1698 |         );
1699 |       }
1700 | 
1701 |       // Check if the texture file exists
1702 |       const texturePath = join(args.projectPath, args.texturePath);
1703 |       if (!existsSync(texturePath)) {
1704 |         return this.createErrorResponse(
1705 |           `Texture file does not exist: ${args.texturePath}`,
1706 |           [
1707 |             'Ensure the texture path is correct',
1708 |             'Upload or create the texture file first',
1709 |           ]
1710 |         );
1711 |       }
1712 | 
1713 |       // Prepare parameters for the operation (already in camelCase)
1714 |       const params = {
1715 |         scenePath: args.scenePath,
1716 |         nodePath: args.nodePath,
1717 |         texturePath: args.texturePath,
1718 |       };
1719 | 
1720 |       // Execute the operation
1721 |       const { stdout, stderr } = await this.executeOperation('load_sprite', params, args.projectPath);
1722 | 
1723 |       if (stderr && stderr.includes('Failed to')) {
1724 |         return this.createErrorResponse(
1725 |           `Failed to load sprite: ${stderr}`,
1726 |           [
1727 |             'Check if the node path is correct',
1728 |             'Ensure the node is a Sprite2D, Sprite3D, or TextureRect',
1729 |             'Verify the texture file is a valid image format',
1730 |           ]
1731 |         );
1732 |       }
1733 | 
1734 |       return {
1735 |         content: [
1736 |           {
1737 |             type: 'text',
1738 |             text: `Sprite loaded successfully with texture: ${args.texturePath}\n\nOutput: ${stdout}`,
1739 |           },
1740 |         ],
1741 |       };
1742 |     } catch (error: any) {
1743 |       return this.createErrorResponse(
1744 |         `Failed to load sprite: ${error?.message || 'Unknown error'}`,
1745 |         [
1746 |           'Ensure Godot is installed correctly',
1747 |           'Check if the GODOT_PATH environment variable is set correctly',
1748 |           'Verify the project path is accessible',
1749 |         ]
1750 |       );
1751 |     }
1752 |   }
1753 | 
1754 |   /**
1755 |    * Handle the export_mesh_library tool
1756 |    */
1757 |   private async handleExportMeshLibrary(args: any) {
1758 |     // Normalize parameters to camelCase
1759 |     args = this.normalizeParameters(args);
1760 |     
1761 |     if (!args.projectPath || !args.scenePath || !args.outputPath) {
1762 |       return this.createErrorResponse(
1763 |         'Missing required parameters',
1764 |         ['Provide projectPath, scenePath, and outputPath']
1765 |       );
1766 |     }
1767 | 
1768 |     if (
1769 |       !this.validatePath(args.projectPath) ||
1770 |       !this.validatePath(args.scenePath) ||
1771 |       !this.validatePath(args.outputPath)
1772 |     ) {
1773 |       return this.createErrorResponse(
1774 |         'Invalid path',
1775 |         ['Provide valid paths without ".." or other potentially unsafe characters']
1776 |       );
1777 |     }
1778 | 
1779 |     try {
1780 |       // Check if the project directory exists and contains a project.godot file
1781 |       const projectFile = join(args.projectPath, 'project.godot');
1782 |       if (!existsSync(projectFile)) {
1783 |         return this.createErrorResponse(
1784 |           `Not a valid Godot project: ${args.projectPath}`,
1785 |           [
1786 |             'Ensure the path points to a directory containing a project.godot file',
1787 |             'Use list_projects to find valid Godot projects',
1788 |           ]
1789 |         );
1790 |       }
1791 | 
1792 |       // Check if the scene file exists
1793 |       const scenePath = join(args.projectPath, args.scenePath);
1794 |       if (!existsSync(scenePath)) {
1795 |         return this.createErrorResponse(
1796 |           `Scene file does not exist: ${args.scenePath}`,
1797 |           [
1798 |             'Ensure the scene path is correct',
1799 |             'Use create_scene to create a new scene first',
1800 |           ]
1801 |         );
1802 |       }
1803 | 
1804 |       // Prepare parameters for the operation (already in camelCase)
1805 |       const params: any = {
1806 |         scenePath: args.scenePath,
1807 |         outputPath: args.outputPath,
1808 |       };
1809 | 
1810 |       // Add optional parameters
1811 |       if (args.meshItemNames && Array.isArray(args.meshItemNames)) {
1812 |         params.meshItemNames = args.meshItemNames;
1813 |       }
1814 | 
1815 |       // Execute the operation
1816 |       const { stdout, stderr } = await this.executeOperation('export_mesh_library', params, args.projectPath);
1817 | 
1818 |       if (stderr && stderr.includes('Failed to')) {
1819 |         return this.createErrorResponse(
1820 |           `Failed to export mesh library: ${stderr}`,
1821 |           [
1822 |             'Check if the scene contains valid 3D meshes',
1823 |             'Ensure the output path is valid',
1824 |             'Verify the scene file is valid',
1825 |           ]
1826 |         );
1827 |       }
1828 | 
1829 |       return {
1830 |         content: [
1831 |           {
1832 |             type: 'text',
1833 |             text: `MeshLibrary exported successfully to: ${args.outputPath}\n\nOutput: ${stdout}`,
1834 |           },
1835 |         ],
1836 |       };
1837 |     } catch (error: any) {
1838 |       return this.createErrorResponse(
1839 |         `Failed to export mesh library: ${error?.message || 'Unknown error'}`,
1840 |         [
1841 |           'Ensure Godot is installed correctly',
1842 |           'Check if the GODOT_PATH environment variable is set correctly',
1843 |           'Verify the project path is accessible',
1844 |         ]
1845 |       );
1846 |     }
1847 |   }
1848 | 
1849 |   /**
1850 |    * Handle the save_scene tool
1851 |    */
1852 |   private async handleSaveScene(args: any) {
1853 |     // Normalize parameters to camelCase
1854 |     args = this.normalizeParameters(args);
1855 |     
1856 |     if (!args.projectPath || !args.scenePath) {
1857 |       return this.createErrorResponse(
1858 |         'Missing required parameters',
1859 |         ['Provide projectPath and scenePath']
1860 |       );
1861 |     }
1862 | 
1863 |     if (!this.validatePath(args.projectPath) || !this.validatePath(args.scenePath)) {
1864 |       return this.createErrorResponse(
1865 |         'Invalid path',
1866 |         ['Provide valid paths without ".." or other potentially unsafe characters']
1867 |       );
1868 |     }
1869 | 
1870 |     // If newPath is provided, validate it
1871 |     if (args.newPath && !this.validatePath(args.newPath)) {
1872 |       return this.createErrorResponse(
1873 |         'Invalid new path',
1874 |         ['Provide a valid new path without ".." or other potentially unsafe characters']
1875 |       );
1876 |     }
1877 | 
1878 |     try {
1879 |       // Check if the project directory exists and contains a project.godot file
1880 |       const projectFile = join(args.projectPath, 'project.godot');
1881 |       if (!existsSync(projectFile)) {
1882 |         return this.createErrorResponse(
1883 |           `Not a valid Godot project: ${args.projectPath}`,
1884 |           [
1885 |             'Ensure the path points to a directory containing a project.godot file',
1886 |             'Use list_projects to find valid Godot projects',
1887 |           ]
1888 |         );
1889 |       }
1890 | 
1891 |       // Check if the scene file exists
1892 |       const scenePath = join(args.projectPath, args.scenePath);
1893 |       if (!existsSync(scenePath)) {
1894 |         return this.createErrorResponse(
1895 |           `Scene file does not exist: ${args.scenePath}`,
1896 |           [
1897 |             'Ensure the scene path is correct',
1898 |             'Use create_scene to create a new scene first',
1899 |           ]
1900 |         );
1901 |       }
1902 | 
1903 |       // Prepare parameters for the operation (already in camelCase)
1904 |       const params: any = {
1905 |         scenePath: args.scenePath,
1906 |       };
1907 | 
1908 |       // Add optional parameters
1909 |       if (args.newPath) {
1910 |         params.newPath = args.newPath;
1911 |       }
1912 | 
1913 |       // Execute the operation
1914 |       const { stdout, stderr } = await this.executeOperation('save_scene', params, args.projectPath);
1915 | 
1916 |       if (stderr && stderr.includes('Failed to')) {
1917 |         return this.createErrorResponse(
1918 |           `Failed to save scene: ${stderr}`,
1919 |           [
1920 |             'Check if the scene file is valid',
1921 |             'Ensure you have write permissions to the output path',
1922 |             'Verify the scene can be properly packed',
1923 |           ]
1924 |         );
1925 |       }
1926 | 
1927 |       const savePath = args.newPath || args.scenePath;
1928 |       return {
1929 |         content: [
1930 |           {
1931 |             type: 'text',
1932 |             text: `Scene saved successfully to: ${savePath}\n\nOutput: ${stdout}`,
1933 |           },
1934 |         ],
1935 |       };
1936 |     } catch (error: any) {
1937 |       return this.createErrorResponse(
1938 |         `Failed to save scene: ${error?.message || 'Unknown error'}`,
1939 |         [
1940 |           'Ensure Godot is installed correctly',
1941 |           'Check if the GODOT_PATH environment variable is set correctly',
1942 |           'Verify the project path is accessible',
1943 |         ]
1944 |       );
1945 |     }
1946 |   }
1947 | 
1948 |   /**
1949 |    * Handle the get_uid tool
1950 |    */
1951 |   private async handleGetUid(args: any) {
1952 |     // Normalize parameters to camelCase
1953 |     args = this.normalizeParameters(args);
1954 |     
1955 |     if (!args.projectPath || !args.filePath) {
1956 |       return this.createErrorResponse(
1957 |         'Missing required parameters',
1958 |         ['Provide projectPath and filePath']
1959 |       );
1960 |     }
1961 | 
1962 |     if (!this.validatePath(args.projectPath) || !this.validatePath(args.filePath)) {
1963 |       return this.createErrorResponse(
1964 |         'Invalid path',
1965 |         ['Provide valid paths without ".." or other potentially unsafe characters']
1966 |       );
1967 |     }
1968 | 
1969 |     try {
1970 |       // Ensure godotPath is set
1971 |       if (!this.godotPath) {
1972 |         await this.detectGodotPath();
1973 |         if (!this.godotPath) {
1974 |           return this.createErrorResponse(
1975 |             'Could not find a valid Godot executable path',
1976 |             [
1977 |               'Ensure Godot is installed correctly',
1978 |               'Set GODOT_PATH environment variable to specify the correct path',
1979 |             ]
1980 |           );
1981 |         }
1982 |       }
1983 | 
1984 |       // Check if the project directory exists and contains a project.godot file
1985 |       const projectFile = join(args.projectPath, 'project.godot');
1986 |       if (!existsSync(projectFile)) {
1987 |         return this.createErrorResponse(
1988 |           `Not a valid Godot project: ${args.projectPath}`,
1989 |           [
1990 |             'Ensure the path points to a directory containing a project.godot file',
1991 |             'Use list_projects to find valid Godot projects',
1992 |           ]
1993 |         );
1994 |       }
1995 | 
1996 |       // Check if the file exists
1997 |       const filePath = join(args.projectPath, args.filePath);
1998 |       if (!existsSync(filePath)) {
1999 |         return this.createErrorResponse(
2000 |           `File does not exist: ${args.filePath}`,
2001 |           ['Ensure the file path is correct']
2002 |         );
2003 |       }
2004 | 
2005 |       // Get Godot version to check if UIDs are supported
2006 |       const { stdout: versionOutput } = await execAsync(`"${this.godotPath}" --version`);
2007 |       const version = versionOutput.trim();
2008 | 
2009 |       if (!this.isGodot44OrLater(version)) {
2010 |         return this.createErrorResponse(
2011 |           `UIDs are only supported in Godot 4.4 or later. Current version: ${version}`,
2012 |           [
2013 |             'Upgrade to Godot 4.4 or later to use UIDs',
2014 |             'Use resource paths instead of UIDs for this version of Godot',
2015 |           ]
2016 |         );
2017 |       }
2018 | 
2019 |       // Prepare parameters for the operation (already in camelCase)
2020 |       const params = {
2021 |         filePath: args.filePath,
2022 |       };
2023 | 
2024 |       // Execute the operation
2025 |       const { stdout, stderr } = await this.executeOperation('get_uid', params, args.projectPath);
2026 | 
2027 |       if (stderr && stderr.includes('Failed to')) {
2028 |         return this.createErrorResponse(
2029 |           `Failed to get UID: ${stderr}`,
2030 |           [
2031 |             'Check if the file is a valid Godot resource',
2032 |             'Ensure the file path is correct',
2033 |           ]
2034 |         );
2035 |       }
2036 | 
2037 |       return {
2038 |         content: [
2039 |           {
2040 |             type: 'text',
2041 |             text: `UID for ${args.filePath}: ${stdout.trim()}`,
2042 |           },
2043 |         ],
2044 |       };
2045 |     } catch (error: any) {
2046 |       return this.createErrorResponse(
2047 |         `Failed to get UID: ${error?.message || 'Unknown error'}`,
2048 |         [
2049 |           'Ensure Godot is installed correctly',
2050 |           'Check if the GODOT_PATH environment variable is set correctly',
2051 |           'Verify the project path is accessible',
2052 |         ]
2053 |       );
2054 |     }
2055 |   }
2056 | 
2057 |   /**
2058 |    * Handle the update_project_uids tool
2059 |    */
2060 |   private async handleUpdateProjectUids(args: any) {
2061 |     // Normalize parameters to camelCase
2062 |     args = this.normalizeParameters(args);
2063 |     
2064 |     if (!args.projectPath) {
2065 |       return this.createErrorResponse(
2066 |         'Project path is required',
2067 |         ['Provide a valid path to a Godot project directory']
2068 |       );
2069 |     }
2070 | 
2071 |     if (!this.validatePath(args.projectPath)) {
2072 |       return this.createErrorResponse(
2073 |         'Invalid project path',
2074 |         ['Provide a valid path without ".." or other potentially unsafe characters']
2075 |       );
2076 |     }
2077 | 
2078 |     try {
2079 |       // Ensure godotPath is set
2080 |       if (!this.godotPath) {
2081 |         await this.detectGodotPath();
2082 |         if (!this.godotPath) {
2083 |           return this.createErrorResponse(
2084 |             'Could not find a valid Godot executable path',
2085 |             [
2086 |               'Ensure Godot is installed correctly',
2087 |               'Set GODOT_PATH environment variable to specify the correct path',
2088 |             ]
2089 |           );
2090 |         }
2091 |       }
2092 | 
2093 |       // Check if the project directory exists and contains a project.godot file
2094 |       const projectFile = join(args.projectPath, 'project.godot');
2095 |       if (!existsSync(projectFile)) {
2096 |         return this.createErrorResponse(
2097 |           `Not a valid Godot project: ${args.projectPath}`,
2098 |           [
2099 |             'Ensure the path points to a directory containing a project.godot file',
2100 |             'Use list_projects to find valid Godot projects',
2101 |           ]
2102 |         );
2103 |       }
2104 | 
2105 |       // Get Godot version to check if UIDs are supported
2106 |       const { stdout: versionOutput } = await execAsync(`"${this.godotPath}" --version`);
2107 |       const version = versionOutput.trim();
2108 | 
2109 |       if (!this.isGodot44OrLater(version)) {
2110 |         return this.createErrorResponse(
2111 |           `UIDs are only supported in Godot 4.4 or later. Current version: ${version}`,
2112 |           [
2113 |             'Upgrade to Godot 4.4 or later to use UIDs',
2114 |             'Use resource paths instead of UIDs for this version of Godot',
2115 |           ]
2116 |         );
2117 |       }
2118 | 
2119 |       // Prepare parameters for the operation (already in camelCase)
2120 |       const params = {
2121 |         projectPath: args.projectPath,
2122 |       };
2123 | 
2124 |       // Execute the operation
2125 |       const { stdout, stderr } = await this.executeOperation('resave_resources', params, args.projectPath);
2126 | 
2127 |       if (stderr && stderr.includes('Failed to')) {
2128 |         return this.createErrorResponse(
2129 |           `Failed to update project UIDs: ${stderr}`,
2130 |           [
2131 |             'Check if the project is valid',
2132 |             'Ensure you have write permissions to the project directory',
2133 |           ]
2134 |         );
2135 |       }
2136 | 
2137 |       return {
2138 |         content: [
2139 |           {
2140 |             type: 'text',
2141 |             text: `Project UIDs updated successfully.\n\nOutput: ${stdout}`,
2142 |           },
2143 |         ],
2144 |       };
2145 |     } catch (error: any) {
2146 |       return this.createErrorResponse(
2147 |         `Failed to update project UIDs: ${error?.message || 'Unknown error'}`,
2148 |         [
2149 |           'Ensure Godot is installed correctly',
2150 |           'Check if the GODOT_PATH environment variable is set correctly',
2151 |           'Verify the project path is accessible',
2152 |         ]
2153 |       );
2154 |     }
2155 |   }
2156 | 
2157 |   /**
2158 |    * Run the MCP server
2159 |    */
2160 |   async run() {
2161 |     try {
2162 |       // Detect Godot path before starting the server
2163 |       await this.detectGodotPath();
2164 | 
2165 |       if (!this.godotPath) {
2166 |         console.error('[SERVER] Failed to find a valid Godot executable path');
2167 |         console.error('[SERVER] Please set GODOT_PATH environment variable or provide a valid path');
2168 |         process.exit(1);
2169 |       }
2170 | 
2171 |       // Check if the path is valid
2172 |       const isValid = await this.isValidGodotPath(this.godotPath);
2173 | 
2174 |       if (!isValid) {
2175 |         if (this.strictPathValidation) {
2176 |           // In strict mode, exit if the path is invalid
2177 |           console.error(`[SERVER] Invalid Godot path: ${this.godotPath}`);
2178 |           console.error('[SERVER] Please set a valid GODOT_PATH environment variable or provide a valid path');
2179 |           process.exit(1);
2180 |         } else {
2181 |           // In compatibility mode, warn but continue with the default path
2182 |           console.warn(`[SERVER] Warning: Using potentially invalid Godot path: ${this.godotPath}`);
2183 |           console.warn('[SERVER] This may cause issues when executing Godot commands');
2184 |           console.warn('[SERVER] This fallback behavior will be removed in a future version. Set strictPathValidation: true to opt-in to the new behavior.');
2185 |         }
2186 |       }
2187 | 
2188 |       console.log(`[SERVER] Using Godot at: ${this.godotPath}`);
2189 | 
2190 |       const transport = new StdioServerTransport();
2191 |       await this.server.connect(transport);
2192 |       console.error('Godot MCP server running on stdio');
2193 |     } catch (error: unknown) {
2194 |       const errorMessage = error instanceof Error ? error.message : 'Unknown error';
2195 |       console.error('[SERVER] Failed to start:', errorMessage);
2196 |       process.exit(1);
2197 |     }
2198 |   }
2199 | }
2200 | 
2201 | // Create and run the server
2202 | const server = new GodotServer();
2203 | server.run().catch((error: unknown) => {
2204 |   const errorMessage = error instanceof Error ? error.message : 'Unknown error';
2205 |   console.error('Failed to run server:', errorMessage);
2206 |   process.exit(1);
2207 | });
2208 | 
```