Skip to main content
Glama

edit_level_metadata

Modify VibeTide level metadata like name, description, enemy count, and spawn rates without altering the tile layout for efficient parameter adjustments.

Instructions

Edit only the metadata of a VibeTide level without changing the tile layout.

This is much more efficient than edit_entire_level when you only want to change
game parameters like enemy count, spawn rates, name, or description.

Args:
    encoded_level: The encoded level string to modify
    new_name: New name for the level (optional)
    new_description: New description for the level (optional)
    max_enemies: Maximum enemies parameter (0-10, optional)
    enemy_spawn_chance: Enemy spawn chance percentage (0-100, optional)
    coin_spawn_chance: Coin spawn chance percentage (0-100, optional)

Returns:
    The modified level data with new encoded string

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
encoded_levelYes
new_nameNo
new_descriptionNo
max_enemiesNo
enemy_spawn_chanceNo
coin_spawn_chanceNo

Implementation Reference

  • The main handler function for the 'edit_level_metadata' tool. Decorated with @mcp.tool() for automatic registration. Decodes the level, updates specified metadata fields, re-encodes, and returns the updated level data.
    @mcp.tool()
    async def edit_level_metadata(
        encoded_level: str,
        new_name: Optional[str] = None,
        new_description: Optional[str] = None,
        max_enemies: Optional[int] = None,
        enemy_spawn_chance: Optional[float] = None,
        coin_spawn_chance: Optional[float] = None,
    ) -> Dict[str, Any]:
        """Edit only the metadata of a VibeTide level without changing the tile layout.
    
        This is much more efficient than edit_entire_level when you only want to change
        game parameters like enemy count, spawn rates, name, or description.
    
        Args:
            encoded_level: The encoded level string to modify
            new_name: New name for the level (optional)
            new_description: New description for the level (optional)
            max_enemies: Maximum enemies parameter (0-10, optional)
            enemy_spawn_chance: Enemy spawn chance percentage (0-100, optional)
            coin_spawn_chance: Coin spawn chance percentage (0-100, optional)
    
        Returns:
            The modified level data with new encoded string
        """
        try:
            # Decode the existing level to get current data
            level_data = level_encoder.decode(encoded_level)
    
            # Update only the specified parameters (keep existing if not specified)
            if new_name is not None:
                level_data["name"] = new_name
            if new_description is not None:
                level_data["description"] = new_description
            if max_enemies is not None:
                level_data["maxEnemies"] = max_enemies
            if enemy_spawn_chance is not None:
                level_data["enemySpawnChance"] = enemy_spawn_chance
            if coin_spawn_chance is not None:
                level_data["coinSpawnChance"] = coin_spawn_chance
    
            # Re-encode with the updated metadata
            new_encoded_level = level_encoder.encode(level_data)
    
            level_url = f"{VIBE_TIDE_CONFIG['web_player_url']}?level={new_encoded_level}"
            level_data["level_url"] = level_url
    
            return {
                "success": True,
                "level_data": level_data,
                "encoded_level": new_encoded_level,
                "play_url": level_url,
                "change_summary": "Updated metadata only - tiles unchanged",
                "message": f"Successfully updated level metadata",
            }
    
        except Exception as e:
            logger.error(f"Failed to edit level metadata: {e}")
            return {"success": False, "error": f"Failed to edit level metadata: {str(e)}"}
  • Input schema and output description defined in the function docstring, used by FastMCP to generate JSON schema for the tool.
    Args:
        encoded_level: The encoded level string to modify
        new_name: New name for the level (optional)
        new_description: New description for the level (optional)
        max_enemies: Maximum enemies parameter (0-10, optional)
        enemy_spawn_chance: Enemy spawn chance percentage (0-100, optional)
        coin_spawn_chance: Coin spawn chance percentage (0-100, optional)
    
    Returns:
        The modified level data with new encoded string
  • LevelEncoder class and global instance used by edit_level_metadata for decoding and encoding level data.
    class LevelEncoder:
        """Fast level encoding/decoding without agent tools"""
    
        def __init__(self):
            # Character mapping for tiles (URL-safe)
            self.tile_chars = {
                0: ".",  # Empty - most common
                1: "G",  # Grass
                2: "R",  # Rock
                3: "Y",  # Yellow
                4: "I",  # Ice
                5: "F",  # Fire/Red
                6: "S",  # Spikes
                7: "W",  # Water
            }
            self.char_tiles = {char: tile for tile, char in self.tile_chars.items()}
    
        def encode(self, level_data: Dict[str, Any]) -> str:
            """Encode level data to URL-safe string"""
            try:
                tiles = level_data.get("tiles", [])
                if not tiles:
                    raise ValueError("No tiles found")
    
                height = len(tiles)
                width = len(tiles[0]) if tiles else 0
    
                # Convert 2D array to character string
                tile_string = ""
                for row in tiles:
                    for tile in row:
                        tile_string += self.tile_chars.get(tile, ".")
    
                # Apply run-length encoding for empty tiles
                tile_string = self._run_length_encode(tile_string)
    
                # Create base format
                encoded = f"{width}x{height}:{tile_string}"
    
                # Add game parameters if provided
                params = {}
                for param in ["maxEnemies", "enemySpawnChance", "coinSpawnChance"]:
                    if param in level_data and level_data[param] is not None:
                        params[param] = level_data[param]
    
                if params:
                    params_json = json.dumps(params)
                    encoded += f"|{self._base64url_encode(params_json)}"
    
                return self._base64url_encode(encoded)
    
            except Exception as e:
                logger.error(f"Failed to encode level: {e}")
                raise ValueError(f"Level encoding failed: {str(e)}")
    
        def decode(self, encoded_string: str) -> Dict[str, Any]:
            """Decode URL-safe string back to level data"""
            try:
                encoded_string = encoded_string.strip()
                if not encoded_string:
                    raise ValueError("Empty encoded string")
    
                # Decode from base64url
                decoded = self._base64url_decode(encoded_string)
    
                # Parse parameters if present
                main_data = decoded
                game_params = {}
    
                if "|" in decoded:
                    parts = decoded.split("|", 1)
                    if len(parts) == 2:
                        main_data, params_data = parts
                        try:
                            params_json = self._base64url_decode(params_data)
                            game_params = json.loads(params_json)
                        except Exception as e:
                            logger.warning(f"Failed to parse parameters: {e}")
    
                # Parse format: widthxheight:encoded_data
                if ":" not in main_data:
                    raise ValueError("Invalid format: missing colon")
    
                dimensions, tile_data = main_data.split(":", 1)
    
                if "x" not in dimensions:
                    raise ValueError("Invalid format: missing dimensions")
    
                width, height = map(int, dimensions.split("x"))
    
                if width < 1 or height < 1:
                    raise ValueError(f"Invalid dimensions: {width}x{height}")
    
                # Decode run-length encoding
                tile_string = self._run_length_decode(tile_data)
    
                # Convert back to 2D array
                tiles = []
                index = 0
    
                for y in range(height):
                    tiles.append([])
                    for x in range(width):
                        char = tile_string[index] if index < len(tile_string) else "."
                        tiles[y].append(self.char_tiles.get(char, 0))
                        index += 1
    
                result = {
                    "tiles": tiles,
                    "width": width,
                    "height": height,
                    "name": self._generate_level_name(tiles),
                }
    
                # Add game parameters
                for param in ["maxEnemies", "enemySpawnChance", "coinSpawnChance"]:
                    if param in game_params:
                        result[param] = game_params[param]
    
                return result
    
            except Exception as e:
                logger.error(f"Failed to decode level: {e}")
                raise ValueError(f"Level decoding failed: {str(e)}")
    
        def _run_length_encode(self, s: str) -> str:
            """Run-length encoding for repeated empty tiles"""
            if not s:
                return s
    
            result = ""
            count = 1
            current = s[0]
    
            for i in range(1, len(s)):
                if s[i] == current and current == ".":
                    count += 1
                else:
                    if current == "." and count > 2:
                        result += f".{count}"
                    else:
                        result += current * count
                    current = s[i]
                    count = 1
    
            # Handle final sequence
            if current == "." and count > 2:
                result += f".{count}"
            else:
                result += current * count
    
            return result
    
        def _run_length_decode(self, s: str) -> str:
            """Decode run-length encoding"""
            result = ""
            i = 0
    
            while i < len(s):
                char = s[i]
    
                if char == "." and i + 1 < len(s) and s[i + 1].isdigit():
                    # Extract number
                    num_str = ""
                    j = i + 1
                    while j < len(s) and s[j].isdigit():
                        num_str += s[j]
                        j += 1
                    count = int(num_str)
                    result += "." * count
                    i = j
                else:
                    result += char
                    i += 1
    
            return result
    
        def _base64url_encode(self, s: str) -> str:
            """URL-safe base64 encoding"""
            encoded = base64.b64encode(s.encode("utf-8")).decode("ascii")
            return encoded.replace("+", "-").replace("/", "_").rstrip("=")
    
        def _base64url_decode(self, s: str) -> str:
            """URL-safe base64 decoding"""
            try:
                # Add padding if necessary
                padding = (4 - len(s) % 4) % 4
                s += "=" * padding
                s = s.replace("-", "+").replace("_", "/")
    
                decoded_bytes = base64.b64decode(s)
                return decoded_bytes.decode("utf-8")
            except Exception as e:
                logger.error(f"Base64 decode error: {e}")
                raise ValueError(f"Invalid base64 encoding: {str(e)}")
    
        def _generate_level_name(self, tiles: List[List[int]]) -> str:
            """Generate a name based on level content"""
            stats = self._analyze_level_content(tiles)
    
            name = ""
            if stats["water"] > 0.3:
                name += "Aquatic "
            if stats["spikes"] > 0.1:
                name += "Dangerous "
            if stats["ice"] > 0.2:
                name += "Icy "
            if stats["platforms"] < 0.1:
                name += "Minimal "
            if stats["platforms"] > 0.4:
                name += "Dense "
    
            name += "Adventure"
            return name.strip()
    
        def _analyze_level_content(self, tiles: List[List[int]]) -> Dict[str, float]:
            """Analyze level content for statistics"""
            if not tiles:
                return {
                    "empty": 1.0,
                    "platforms": 0.0,
                    "ice": 0.0,
                    "dangerous": 0.0,
                    "spikes": 0.0,
                    "water": 0.0,
                }
    
            total = len(tiles) * len(tiles[0])
            counts = {i: 0 for i in range(8)}
    
            for row in tiles:
                for tile in row:
                    counts[tile] = counts.get(tile, 0) + 1
    
            return {
                "empty": counts[0] / total,
                "platforms": (counts[1] + counts[2] + counts[3]) / total,
                "ice": counts[4] / total,
                "dangerous": counts[5] / total,
                "spikes": counts[6] / total,
                "water": counts[7] / total,
            }

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/banjtheman/vibe_tide_mcp'

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