Skip to main content
Glama

tilemap

Manage TileSets and TileMaps in Godot: create tilesets, add texture sources, set tiles, paint, and list. Use help for full documentation.

Instructions

TileSet and TileMap management. Actions: create_tileset|add_source|set_tile|paint|list. 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 (for list, paint)
tileset_pathNoPath to TileSet .tres file (for create_tileset, add_source)
texture_pathNoTexture source path (for add_source)
tile_sizeNoTile size in pixels (default: 16, for create_tileset)

Implementation Reference

  • The handleTilemap function executes tilemap tool logic with actions: create_tileset, add_source, set_tile, paint, list. Creates TileSet .tres files, adds texture sources, lists TileMapLayer nodes in scenes, and provides guidance for tile painting/setup.
    export async function handleTilemap(action: string, args: Record<string, unknown>, config: GodotConfig) {
      const projectPath = (args.project_path as string) || config.projectPath
    
      switch (action) {
        case 'create_tileset': {
          const tilesetPath = args.tileset_path as string
          if (!tilesetPath)
            throw new GodotMCPError(
              'No tileset_path specified',
              'INVALID_ARGS',
              'Provide tileset_path (e.g., "tilesets/main.tres").',
            )
          const tileSize = (args.tile_size as number) || 16
    
          const fullPath = safeResolve(projectPath || process.cwd(), tilesetPath)
    
          // Performance optimization: using async pathExists instead of existsSync
          // to avoid blocking the Node.js event loop during I/O operations
          if (await pathExists(fullPath)) {
            throw new GodotMCPError(`TileSet already exists: ${tilesetPath}`, 'TILEMAP_ERROR', 'Use a different path.')
          }
    
          const content = [
            `[gd_resource type="TileSet" format=3]`,
            '',
            `[resource]`,
            `tile_shape = 0`,
            `tile_size = Vector2i(${tileSize}, ${tileSize})`,
            '',
          ].join('\n')
    
          // Performance optimization: using async file writing instead of sync
          // to avoid blocking the Node.js event loop during I/O operations
          await mkdir(dirname(fullPath), { recursive: true })
          await writeFile(fullPath, content, 'utf-8')
          return formatSuccess(`Created TileSet: ${tilesetPath} (tile size: ${tileSize}x${tileSize})`)
        }
    
        case 'add_source': {
          const tilesetPath = args.tileset_path as string
          const texturePath = args.texture_path as string
          if (!tilesetPath || !texturePath) {
            throw new GodotMCPError('tileset_path and texture_path required', 'INVALID_ARGS', 'Both are required.')
          }
    
          const fullPath = safeResolve(projectPath || process.cwd(), tilesetPath)
    
          // Performance optimization: using async pathExists instead of existsSync
          if (!(await pathExists(fullPath)))
            throw new GodotMCPError(`TileSet not found: ${tilesetPath}`, 'TILEMAP_ERROR', 'Create the tileset first.')
    
          // Performance optimization: using async file reading instead of sync
          let content = await readFile(fullPath, 'utf-8')
          // ⚡ Bolt: Using replaceAll('\\', '/') avoids RegExp allocation overhead
          const resPath = `res://${texturePath.replaceAll('\\', '/')}`
    
          // Count existing sources to get next ID
          const sourceCount = (content.match(/\[ext_resource/g) || []).length
          const sourceId = `source_${sourceCount}`
    
          // Add ext_resource reference
          const extRes = `[ext_resource type="Texture2D" path="${resPath}" id="${sourceId}"]`
          content = content.replace('[resource]', `${extRes}\n\n[resource]`)
    
          // Performance optimization: using async file writing instead of sync
          await writeFile(fullPath, content, 'utf-8')
          return formatSuccess(`Added texture source: ${texturePath} (id: ${sourceId})`)
        }
    
        case 'set_tile': {
          return formatSuccess(
            'Tile configuration requires editing TileSet .tres resource data.\n' +
              'For complex tile setup, use Godot editor.\n' +
              'Basic format: sources/N/tiles/coords/terrain_set, animation_columns, etc.',
          )
        }
    
        case 'paint': {
          const scenePath = args.scene_path as string
          if (!scenePath)
            throw new GodotMCPError('No scene_path specified', 'INVALID_ARGS', 'Provide scene_path with TileMapLayer node.')
    
          return formatSuccess(
            'TileMap painting requires modifying tile_map_data which is binary-encoded.\n' +
              'For procedural tile placement, create a GDScript that sets cells at runtime:\n' +
              '```gdscript\nvar tilemap = $TileMapLayer\ntilemap.set_cell(Vector2i(x, y), source_id, atlas_coords)\n```',
          )
        }
    
        case 'list': {
          const scenePath = args.scene_path as string
          if (!scenePath) throw new GodotMCPError('No scene_path specified', 'INVALID_ARGS', 'Provide scene_path.')
    
          const fullPath = safeResolve(projectPath || process.cwd(), scenePath)
    
          // Performance optimization: using async pathExists instead of existsSync
          if (!(await pathExists(fullPath)))
            throw new GodotMCPError(`Scene not found: ${scenePath}`, 'SCENE_ERROR', 'Check the file path.')
    
          // Performance optimization: using async file reading instead of sync
          const content = await readFile(fullPath, 'utf-8')
          const tilemaps: string[] = []
          const tmRegex = /\[node name="([^"]+)" type="TileMapLayer"/g
          for (const match of content.matchAll(tmRegex)) {
            tilemaps.push(match[1])
          }
    
          return formatJSON({ scene: scenePath, tilemapLayers: tilemaps })
        }
    
        default:
          throwUnknownAction(action, ['create_tileset', 'add_source', 'set_tile', 'paint', 'list'])
      }
    }
  • Input schema definition for the 'tilemap' tool, defining actions (create_tileset, add_source, set_tile, paint, list), parameters (project_path, scene_path, tileset_path, texture_path, tile_size), and annotations.
    {
      name: 'tilemap',
      description:
        'TileSet and TileMap management. Actions: create_tileset|add_source|set_tile|paint|list. Use help tool for full docs.',
      annotations: createAnnotations('TileMap'),
      inputSchema: {
        type: 'object' as const,
        properties: {
          action: {
            type: 'string',
            enum: ['create_tileset', 'add_source', 'set_tile', 'paint', 'list'],
            description: 'Action to perform',
          },
          project_path: { type: 'string', description: 'Path to Godot project directory' },
          scene_path: { type: 'string', description: 'Path to scene file (for list, paint)' },
          tileset_path: { type: 'string', description: 'Path to TileSet .tres file (for create_tileset, add_source)' },
          texture_path: { type: 'string', description: 'Texture source path (for add_source)' },
          tile_size: { type: 'number', description: 'Tile size in pixels (default: 16, for create_tileset)' },
        },
        required: ['action'],
      },
    },
  • Import of handleTilemap from './composite/tilemap.js' in the tool registry.
    import { handleTilemap } from './composite/tilemap.js'
  • Mapping of 'tilemap' tool name to its handler function handleTilemap in the TOOL_HANDLERS dispatch object.
    tilemap: handleTilemap,
  • Registration of 'tilemap' as a valid help topic, enabling users to get full documentation for the tilemap tool via the help tool.
    'tilemap',
Behavior2/5

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

Annotations indicate readOnlyHint=false, destructiveHint=false, but the description does not disclose any behavioral traits such as side effects, permissions, or reversibility. It adds no context beyond the annotations.

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 very short (two sentences) and front-loads purpose. It is concise, but the second sentence could be seen as redundant since actions are in the schema. Still, no wasted text.

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?

For a tool with 6 parameters and multiple actions, the description is incomplete. It does not explain each action's purpose, when to use them, or the output, and it externally refers to the help tool for full documentation.

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?

Schema coverage is 100%, so the description does not need to add much. However, the description only repeats the action list from the enum and adds no extra meaning or context for the parameters.

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

Purpose3/5

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

The description states it manages TileSet and TileMap and lists actions, but it is vague and lacks details to distinguish from sibling tools like 'nodes' or 'scenes'. The reference to the help tool implies the description is incomplete.

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?

The description provides no guidance on when to use this tool versus alternatives. It merely lists actions and directs to the help tool for full docs, leaving the agent without context for appropriate usage.

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