Skip to main content
Glama

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

TableJSON Schema
NameRequiredDescriptionDefault
actionYesAction to perform
project_pathNoPath to Godot project directory
scene_pathNoPath to scene file
nameNoNode name
collision_layerNoCollision layer bitmask
collision_maskNoCollision mask bitmask
dimensionNo2d or 3d (for set_layer_name)
layer_numberNoLayer number (for set_layer_name)
gravity_scaleNoGravity scale (for body_config)
massNoMass (for body_config)

Implementation Reference

  • 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'])
      }
    }
  • 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'],
      },
    },
  • Registration of the physics handler in TOOL_HANDLERS record, mapping 'physics' key to handlePhysics function.
    physics: handlePhysics,
  • Import of the handlePhysics function from composite/physics.ts.
    import { handlePhysics } from './composite/physics.js'
    import { handleProject } from './composite/project.js'
  • Physics included as a valid help topic in the VALID_TOPICS array.
    'help',
    'resources',
    'input_map',
    'signals',
    'animation',
    'tilemap',
    'shader',
    'physics',
    'audio',
    'navigation',
    'ui',
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description lists actions but does not disclose behavioral traits beyond annotations (readOnlyHint=false, destructiveHint=false). It omits effects like state changes, permissions, or side effects, leaving the agent underinformed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely short (two sentences) and front-loaded with the core purpose. Every word serves a function, but the extreme brevity sacrifices completeness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 10 parameters and no output schema, the description is severely lacking. It does not explain how actions interrelate, what inputs are required per action, or what the tool returns. The tool's complexity demands more context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Input schema covers 100% of parameters with descriptions, so baseline is 3. The description adds no additional meaning beyond listing the action enum values already present in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool is for 'Physics config' and enumerates the four possible actions, providing a distinct purpose among sibling tools like 'animation' or 'audio'. However, it lacks detail on what each action accomplishes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is given on when to use this tool versus alternatives, nor are there prerequisites or exclusions. The phrase 'Use help tool for full docs' suggests reliance on external documentation.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

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

curl -X GET 'https://glama.ai/api/mcp/v1/servers/n24q02m/better-godot-mcp'

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