scripts
Manage GDScript files in Godot projects: create templates, read, write, attach to scene nodes, list, and delete scripts.
Instructions
GDScript file CRUD.
Actions (required params -> optional):
create (script_path -> extends="Node", content, project_path): generate template
read (script_path -> project_path)
write (script_path, content -> project_path): replace entire file
attach (script_path, scene_path, node_name -> project_path): link to scene node
list (-> project_path)
delete (script_path -> project_path)
script_path: relative to project root (e.g., "scripts/player.gd").
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | Action to perform | |
| project_path | No | Path to Godot project directory | |
| script_path | No | Path to GDScript file | |
| extends | No | Base class for create (default: Node) | |
| content | No | Script content (for create/write) | |
| scene_path | No | Scene file path (for attach) | |
| node_name | No | Target node name (for attach) |
Implementation Reference
- src/tools/composite/scripts.ts:246-282 (handler)Main handler function for the 'scripts' tool. Dispatches to action-specific functions (create, read, write, attach, list, delete).
export async function handleScripts(action: string, args: Record<string, unknown>, config: GodotConfig) { const baseDir = config.projectPath || process.cwd() // Validate args.project_path against the trusted baseDir to prevent path traversal vulnerabilities const projectPath = args.project_path ? safeResolve(baseDir, args.project_path as string) : config.projectPath if (!projectPath && action !== 'list') { // List handles missing projectPath internally, but others need it for safeResolve base // Though list also throws if missing. Let's rely on standard checks inside but ensure projectPath is available for resolution. // Actually, all actions check projectPath. We can resolve it early. } // Helper to resolve path securely const resolvePath = (path: string) => { if (!projectPath) { // Should be caught by action-specific checks, but fallback for safety throw new GodotMCPError('No project path specified', 'INVALID_ARGS', 'Provide project_path argument.') } return safeResolve(projectPath, path) } switch (action) { case 'create': return createScript(args, resolvePath) case 'read': return readScript(args, resolvePath) case 'write': return writeScript(args, resolvePath) case 'attach': return attachScript(args, resolvePath) case 'list': return listScripts(baseDir, projectPath ?? undefined) case 'delete': return deleteScript(args, resolvePath) default: throwUnknownAction(action, ['create', 'read', 'write', 'attach', 'list', 'delete']) } } - src/tools/registry.ts:134-156 (schema)Input/output schema definition for the 'scripts' tool, including description, annotations, and input properties.
{ name: 'scripts', description: 'GDScript file CRUD.\n\nActions (required params -> optional):\n- create (script_path -> extends="Node", content, project_path): generate template\n- read (script_path -> project_path)\n- write (script_path, content -> project_path): replace entire file\n- attach (script_path, scene_path, node_name -> project_path): link to scene node\n- list (-> project_path)\n- delete (script_path -> project_path)\n\nscript_path: relative to project root (e.g., "scripts/player.gd").', annotations: createAnnotations('Scripts', { destructive: true }), inputSchema: { type: 'object' as const, properties: { action: { type: 'string', enum: ['create', 'read', 'write', 'attach', 'list', 'delete'], description: 'Action to perform', }, project_path: { type: 'string', description: 'Path to Godot project directory' }, script_path: { type: 'string', description: 'Path to GDScript file' }, extends: { type: 'string', description: 'Base class for create (default: Node)' }, content: { type: 'string', description: 'Script content (for create/write)' }, scene_path: { type: 'string', description: 'Scene file path (for attach)' }, node_name: { type: 'string', description: 'Target node name (for attach)' }, }, required: ['action'], }, }, - src/tools/registry.ts:511-545 (registration)Registration of all tools with the MCP server. The 'scripts' handler is mapped in TOOL_HANDLERS at line 493 and the schema is included in P0_TOOLS (lines 134-156).
export function registerTools(server: Server, config: GodotConfig): void { server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS, })) server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args = {} } = request.params try { let result: { content: Array<{ type: string; text: string }>; isError?: boolean } if (name === 'help') { result = await handleHelp( (args.action as string) || (args.tool_name as string), args as Record<string, unknown>, ) } else { const handler = TOOL_HANDLERS[name] if (!handler) { const validTools = TOOLS.map((t) => t.name) const closest = findClosestMatch(name, validTools) const suggestion = closest ? ` Did you mean '${closest}'?` : '' throw new GodotMCPError( `Unknown tool: ${name}.${suggestion}`, 'INVALID_ACTION', `Available tools: ${validTools.join(', ')}`, ) } result = await handler(args.action as string, args as Record<string, unknown>, config) } return wrapToolResult(name, result) } catch (error) { return formatError(error) } }) } - src/tools/registry.ts:25-25 (registration)Import of the handleScripts function from the composite scripts module.
import { handleScripts } from './composite/scripts.js' - Helper for creating a new GDScript file with optional template content.
async function createScript(args: Record<string, unknown>, resolvePath: (path: string) => string) { const scriptPath = args.script_path as string if (!scriptPath) throw new GodotMCPError('No script_path specified', 'INVALID_ARGS', 'Provide script_path (e.g., "player.gd").') const extendsType = (args.extends as string) || 'Node' const content = (args.content as string) || getTemplate(extendsType) const fullPath = resolvePath(scriptPath) if (await pathExists(fullPath)) { throw new GodotMCPError( `Script already exists: ${scriptPath}`, 'SCRIPT_ERROR', 'Use write action to modify existing scripts.', ) } await mkdir(dirname(fullPath), { recursive: true }) await writeFile(fullPath, content, 'utf-8') return formatSuccess(`Created script: ${scriptPath}\nExtends: ${extendsType}`) }