physics
Configure collision layers, body properties, and layer names to manage scene interactions in Godot.
Instructions
Physics config. Actions: layers|collision_setup|body_config|set_layer_name. Use help tool for full docs.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | Action to perform | |
| project_path | No | Path to Godot project directory | |
| scene_path | No | Path to scene file | |
| name | No | Node name | |
| collision_layer | No | Collision layer bitmask | |
| collision_mask | No | Collision mask bitmask | |
| dimension | No | 2d or 3d (for set_layer_name) | |
| layer_number | No | Layer number (for set_layer_name) | |
| gravity_scale | No | Gravity scale (for body_config) | |
| mass | No | Mass (for body_config) |
Implementation Reference
- src/tools/composite/physics.ts:15-167 (handler)Main handler function for the 'physics' tool. Supports actions: layers, collision_setup, body_config, set_layer_name. Reads/writes project.godot and scene files for physics layer names, collision layers/masks, and physics body properties.
export async function handlePhysics(action: string, args: Record<string, unknown>, config: GodotConfig) { const projectPath = (args.project_path as string) || config.projectPath || '' switch (action) { case 'layers': { if (!projectPath) throw new GodotMCPError('No project path specified', 'INVALID_ARGS', 'Provide project_path.') const configPath = join(safeResolve(config.projectPath || process.cwd(), projectPath), 'project.godot') if (!(await pathExists(configPath))) throw new GodotMCPError('No project.godot found', 'PROJECT_NOT_FOUND', 'Verify project path.') const settings = await parseProjectSettingsAsync(configPath) const layers2d: Record<string, string> = {} const layers3d: Record<string, string> = {} for (const [key, value] of settings.sections.get('layer_names') || []) { if (key.startsWith('2d_physics/layer_')) { layers2d[key] = value.replace(/"/g, '') } else if (key.startsWith('3d_physics/layer_')) { layers3d[key] = value.replace(/"/g, '') } } return formatJSON({ layers2d, layers3d }) } case 'collision_setup': { const scenePath = args.scene_path as string if (!scenePath) throw new GodotMCPError('No scene_path specified', 'INVALID_ARGS', 'Provide scene_path.') const nodeName = args.name as string if (!nodeName) throw new GodotMCPError('No node name specified', 'INVALID_ARGS', 'Provide node name.') if (scenePath.includes('\n') || scenePath.includes('\r') || nodeName.includes('\n') || nodeName.includes('\r')) { throw new GodotMCPError('Invalid arguments: newlines not allowed', 'INVALID_ARGS') } const collisionLayer = args.collision_layer const collisionMask = args.collision_mask const fullPath = safeResolve(safeResolve(config.projectPath || process.cwd(), projectPath), scenePath) if (!(await pathExists(fullPath))) throw new GodotMCPError(`Scene not found: ${scenePath}`, 'SCENE_ERROR', 'Check file path.') let content = await readFile(fullPath, 'utf-8') const nodeRegex = new RegExp(`(\\[node name="${escapeRegExp(nodeName)}"[^\\]]*\\])`) const match = content.match(nodeRegex) if (!match) throw new GodotMCPError(`Node "${nodeName}" not found`, 'NODE_ERROR', 'Check node name.') if (match.index === undefined) throw new GodotMCPError(`Node "${nodeName}" not found`, 'NODE_ERROR', 'Check node name.') const insertPoint = match.index + match[0].length let props = '' if (collisionLayer !== undefined) { const val = toGodotValue(collisionLayer) if (val.includes('\n') || val.includes('\r')) { throw new GodotMCPError('Invalid collision_layer: newlines not allowed', 'INVALID_ARGS') } props += `\ncollision_layer = ${val}` } if (collisionMask !== undefined) { const val = toGodotValue(collisionMask) if (val.includes('\n') || val.includes('\r')) { throw new GodotMCPError('Invalid collision_mask: newlines not allowed', 'INVALID_ARGS') } props += `\ncollision_mask = ${val}` } content = `${content.slice(0, insertPoint)}${props}${content.slice(insertPoint)}` await writeFile(fullPath, content, 'utf-8') return formatSuccess( `Set collision on ${nodeName}: layer=${collisionLayer ?? 'unchanged'}, mask=${collisionMask ?? 'unchanged'}`, ) } case 'body_config': { const scenePath = args.scene_path as string if (!scenePath) throw new GodotMCPError('No scene_path specified', 'INVALID_ARGS', 'Provide scene_path.') const nodeName = args.name as string if (!nodeName) throw new GodotMCPError('No node name specified', 'INVALID_ARGS', 'Provide node name.') if (scenePath.includes('\n') || scenePath.includes('\r') || nodeName.includes('\n') || nodeName.includes('\r')) { throw new GodotMCPError('Invalid arguments: newlines not allowed', 'INVALID_ARGS') } const fullPath = safeResolve(safeResolve(config.projectPath || process.cwd(), projectPath), scenePath) if (!(await pathExists(fullPath))) throw new GodotMCPError(`Scene not found: ${scenePath}`, 'SCENE_ERROR', 'Check file path.') let content = await readFile(fullPath, 'utf-8') const nodeRegex = new RegExp(`(\\[node name="${escapeRegExp(nodeName)}"[^\\]]*\\])`) const match = content.match(nodeRegex) if (!match) throw new GodotMCPError(`Node "${nodeName}" not found`, 'NODE_ERROR', 'Check node name.') let props = '' const physicsProps = ['gravity_scale', 'mass', 'linear_damp', 'angular_damp', 'freeze'] for (const prop of physicsProps) { if (args[prop] !== undefined) { const val = toGodotValue(args[prop]) if (val.includes('\n') || val.includes('\r')) { throw new GodotMCPError(`Invalid ${prop}: newlines not allowed`, 'INVALID_ARGS') } props += `\n${prop} = ${val}` } } if (match.index === undefined) throw new GodotMCPError(`Node "${nodeName}" not found`, 'NODE_ERROR', 'Check node name.') const insertPoint = match.index + match[0].length content = `${content.slice(0, insertPoint)}${props}${content.slice(insertPoint)}` await writeFile(fullPath, content, 'utf-8') return formatSuccess(`Configured physics body: ${nodeName}`) } case 'set_layer_name': { if (!projectPath) throw new GodotMCPError('No project path specified', 'INVALID_ARGS', 'Provide project_path.') const layerNumRaw = args.layer_number !== undefined ? String(args.layer_number) : '1' const dimension = (args.dimension as string) || '2d' const name = args.name as string if (!name) throw new GodotMCPError('No name specified', 'INVALID_ARGS', 'Provide layer name.') if ( name.includes('\n') || name.includes('\r') || dimension.includes('\n') || dimension.includes('\r') || layerNumRaw.includes('\n') || layerNumRaw.includes('\r') ) { throw new GodotMCPError('Invalid arguments: newlines not allowed', 'INVALID_ARGS') } const layerNum = Number.parseInt(layerNumRaw, 10) if (Number.isNaN(layerNum)) { throw new GodotMCPError('Invalid layer_number: must be a number', 'INVALID_ARGS') } const configPath = join(safeResolve(config.projectPath || process.cwd(), projectPath), 'project.godot') if (!(await pathExists(configPath))) throw new GodotMCPError('No project.godot found', 'PROJECT_NOT_FOUND', 'Verify project path.') const content = await readFile(configPath, 'utf-8') const key = `layer_names/${dimension}_physics/layer_${layerNum}` const updated = setSettingInContent(content, key, `"${name}"`) await writeFile(configPath, updated, 'utf-8') return formatSuccess(`Set ${dimension} physics layer ${layerNum}: "${name}"`) } default: throwUnknownAction(action, ['layers', 'collision_setup', 'body_config', 'set_layer_name']) } } - src/tools/registry.ts:369-393 (schema)Physics tool schema definition in P2_TOOLS array. Defines name, description, annotations, and inputSchema with all properties (action, project_path, scene_path, name, collision_layer, collision_mask, dimension, layer_number, gravity_scale, mass).
name: 'physics', description: 'Physics config. Actions: layers|collision_setup|body_config|set_layer_name. Use help tool for full docs.', annotations: createAnnotations('Physics'), inputSchema: { type: 'object' as const, properties: { action: { type: 'string', enum: ['layers', 'collision_setup', 'body_config', 'set_layer_name'], description: 'Action to perform', }, project_path: { type: 'string', description: 'Path to Godot project directory' }, scene_path: { type: 'string', description: 'Path to scene file' }, name: { type: 'string', description: 'Node name' }, collision_layer: { type: 'number', description: 'Collision layer bitmask' }, collision_mask: { type: 'number', description: 'Collision mask bitmask' }, dimension: { type: 'string', description: '2d or 3d (for set_layer_name)' }, layer_number: { type: 'number', description: 'Layer number (for set_layer_name)' }, gravity_scale: { type: 'number', description: 'Gravity scale (for body_config)' }, mass: { type: 'number', description: 'Mass (for body_config)' }, }, required: ['action'], }, }, - src/tools/registry.ts:502-502 (registration)Registration of the physics handler in TOOL_HANDLERS record, mapping 'physics' key to handlePhysics function.
physics: handlePhysics, - src/tools/registry.ts:21-22 (registration)Import of the handlePhysics function from composite/physics.ts.
import { handlePhysics } from './composite/physics.js' import { handleProject } from './composite/project.js' - src/tools/composite/help.ts:18-28 (helper)Physics included as a valid help topic in the VALID_TOPICS array.
'help', 'resources', 'input_map', 'signals', 'animation', 'tilemap', 'shader', 'physics', 'audio', 'navigation', 'ui',