- Lua 52.4%
- Python 46%
- Nix 1.5%
- Shell 0.1%
| .forgejo/workflows | ||
| mcp_luanti_bot_api | ||
| mod_bot_api | ||
| .gitignore | ||
| .gitmodules | ||
| bot_api_gateway.py | ||
| Dockerfile.gateway | ||
| Dockerfile.mcp | ||
| entrypoint.gateway.sh | ||
| entrypoint.mcp.sh | ||
| flake.nix | ||
| LICENSE | ||
| README.md | ||
| requirements.txt | ||
| SKILL.md | ||
luantimcp
Control a running Luanti server from an MCP client, without implementing the Luanti network protocol.
Architecture
bot_api (Lua mod) --JSON-RPC 2.0 poll--> bot_api_gateway <--JSON-RPC 2.0--> mcp_luanti_bot_api <--stdio--> MCP client
(HTTP) (HTTP)
Three decoupled parts:
bot_api_gateway.py– a standalone, long-running Python HTTP server. It is the only stable network endpoint. The mod polls it for commands using JSON-RPC 2.0; the MCP server pushes commands to it. Run it once and leave it running.mod_bot_api/– a Luanti mod. It uses JSON-RPC 2.0 to communicate with the gateway, executes commands with thecore.*API, and posts results / game events back. Includes bundled mesh model (models/bot.b3d) and texture (textures/bot.png) for bot visualization.mcp_luanti_bot_api/– the MCP server package. It is spawned by the MCP client over stdio and forwards tool calls to the gateway using JSON-RPC 2.0.
Why a separate gateway? mcp_luanti_bot_api is started/stopped by the MCP client and
talks over stdio, so it is not a reliable server the mod can connect to. The
gateway decouples the two lifecycles.
luanti/ is the Luanti engine (git submodule), used as the API reference.
Project Structure
.
├── bot_api_gateway.py # JSON-RPC 2.0 HTTP gateway server
├── mcp_luanti_bot_api/ # MCP server package
│ ├── __init__.py
│ ├── __main__.py # Entry point: python -m mcp_luanti_bot_api
│ ├── client.py # HTTP client for gateway communication
│ ├── config.py # Configuration management
│ ├── handlers.py # MCP tool call handlers
│ ├── server.py # MCP server implementation
│ └── tools.py # MCP tool definitions
├── mod_bot_api/ # Luanti mod
│ ├── init.lua # Main mod implementation
│ ├── mod.conf # Mod metadata
│ ├── settingtypes.txt # Setting types
│ ├── models/
│ │ └── bot.b3d # Bot mesh model
│ └── textures/
│ └── bot.png # Bot texture
├── requirements.txt # Python dependencies
└── README.md
Setup
Prerequisites
- Python 3.8+
- Luanti server
- MCP client (e.g., Cursor, Claude Code)
1. Install the mod
Copy or symlink mod_bot_api/ into your world's worldmods/ (or the global mods/)
directory and enable it for the world. The mod includes bundled assets:
models/bot.b3d- Bot mesh modeltextures/bot.png- Bot texture
2. Configure Luanti (minetest.conf)
HTTP access is only granted to trusted mods. Add to your configuration:
secure.http_mods = bot_api
# URL of the gateway (default shown)
bot_api_url = http://127.0.0.1:8765
# Polling settings (optional - adjust based on network latency)
bot_api_poll_interval = 0.2
bot_api_poll_timeout = 35
# Bot visualization settings (optional)
bot_api_bot_forceload = true
bot_api_use_inventory = true
3. Install Python dependencies and start the gateway
pip install -r requirements.txt
python bot_api_gateway.py --host 127.0.0.1 --port 8765
Gateway options:
--host- Host to bind to (default: 127.0.0.1)--port- Port to listen on (default: 8765)--debug- Enable debug logging
4. Configure the MCP client
Create or update your .mcp.json to include the MCP server pointed at the gateway:
{
"mcpServers": {
"luanti-bot-api": {
"command": "python",
"args": ["-m", "mcp_luanti_bot_api", "--gateway-url", "http://127.0.0.1:8765"]
}
}
}
The mcp_luanti_bot_api supports these options:
--gateway-url- URL of the gateway (default: http://127.0.0.1:8765)--name- Default bot name for this instance (default: "bot")--debug- Enable debug logging--huge_context- Include memory-intensive tools (get_blocks, get_all_items, get_entities)
Then start the Luanti server. Call the status tool to confirm the mod is
connected to the gateway.
Configuration Reference
Gateway Configuration (bot_api_gateway.py)
| Parameter | Default | Description |
|---|---|---|
--host |
127.0.0.1 | Host to bind to |
--port |
8765 | Port to listen on |
--debug |
false | Enable debug logging |
Internal settings: Command timeout 20s, Poll hold 25s, Max events 500.
MCP Server Configuration (mcp_luanti_bot_api)
| Parameter | Default | Description |
|---|---|---|
--gateway-url |
http://127.0.0.1:8765 | URL of the gateway |
--name |
bot | Default bot name for this instance |
--debug |
false | Enable debug logging |
--huge_context |
false | Enable memory-intensive tools |
Environment variables: GATEWAY_URL, BOT_NAME, HUGE_CONTEXT.
Luanti Mod Configuration (minetest.conf)
| Setting | Default | Description |
|---|---|---|
secure.http_mods |
- | Must include bot_api for HTTP access |
bot_api_url |
http://127.0.0.1:8765 | Gateway URL |
bot_api_poll_interval |
0.2 | Seconds between polls |
bot_api_poll_timeout |
35 | Poll request timeout in seconds |
bot_api_bot_forceload |
true | Keep bot's mapblock loaded |
bot_api_use_inventory |
true | Enable bot inventory system |
Bots
A Luanti mod cannot create a real network player (players are network clients),
so the agent does not appear in the player list. Instead, spawn_bot
creates a server-side entity that behaves like a player:
- renders as a bundled mesh by default (works on any game) with a
floating nametag (color: #3a86ff). The mod uses bundled models from
mod_bot_api/models/bot.b3dwith texturemod_bot_api/textures/bot.png. - is affected by gravity and collision with a standard player collision box,
- stays active even with no player nearby (its mapblock is transiently
force-loaded; disable with
bot_api_bot_forceload = falsein minetest.conf), - has a virtual inventory (32 slots, disable with
bot_api_use_inventory = false) that must contain items to place blocks, move_botmakes it walk to a target (with walk animation) at 4.0 nodes/sec and auto-jumps over 1-node obstacles in its path;teleport_botmoves it instantly, andjumpmakes it hop on demand (jump speed: 6.0),- world actions can be attributed to it: pass
bottodig_block,place_block, orsayand the bot faces the spot, plays the mining animation, and the dig/place fires the normalon_dignode/on_placenodecallbacks with the bot as the actor. Digged nodes' drops are added to the bot's inventory.
Spawn it near a loaded area (e.g. close to a player) so the mapblock is active.
Example: spawn_bot {name:"alice", x:10, y:8, z:10} then
dig_block {x:11, y:8, z:10, bot:"alice"}.
Troubleshooting
Mod not connecting to gateway
- Verify
secure.http_mods = bot_apiis set in minetest.conf - Check that
bot_api_urlpoints to the correct gateway address - Ensure the gateway is running (
python bot_api_gateway.py --host 127.0.0.1 --port 8765) - Check gateway logs for connection attempts
MCP server not responding
- Verify the gateway URL in your
.mcp.jsonmatches the gateway's host/port - Ensure the gateway is running and accessible
- Check that
python -m mcp_luanti_bot_apistarts without errors - Use
--debugflag on both gateway and MCP server for detailed logging
Bot spawning fails
- Spawn near a loaded area (close to a player)
- Verify the mod is loaded (check Luanti server logs for BotAPI initialization)
- Ensure the bundled mesh and texture files exist in
mod_bot_api/models/andmod_bot_api/textures/
Commands timing out
- Increase
bot_api_poll_timeoutin minetest.conf (try 60) - Check network connectivity between mod, gateway, and MCP server
- Reduce
--huge_contextmemory usage if dealing with large datasets
Multiple bots
The gateway and mod are bot-agnostic (bots are keyed by name), so you can run
one mcp_luanti_bot_api per bot, each with its own --name, all pointing at the same
gateway:
python -m mcp_luanti_bot_api --gateway-url http://127.0.0.1:8765 --name alice
python -m mcp_luanti_bot_api --gateway-url http://127.0.0.1:8765 --name bob
Each instance defaults its bot-management tools (spawn_bot, move_bot, ...)
and action attribution (dig_block, place_block, say) to its own --name,
so a client just calls spawn_bot {x,y,z} / dig_block {x,y,z} without
repeating the name. Any tool can still target another bot explicitly via the
name / bot argument. The .mcp.json in this repo registers alice and
bob as an example.
Tools
Gateway Tools
| Tool | Description |
|---|---|
status |
Gateway/mod connection status, bot name, command queue info |
get_server_info |
Server uptime, gametime, time of day, max players, player count |
get_players |
List of connected player names and spawned bots |
Bot Management Tools
| Tool | Description |
|---|---|
spawn_bot |
Spawn a bot entity at position (x, y, z). Uses instance's --name by default. |
move_bot |
Walk a bot to position (with animation and auto-jump). Uses instance's --name. |
teleport_bot |
Instantly move a bot to position. Uses instance's --name. |
jump |
Make a bot jump if grounded. Uses instance's --name. |
despawn_bot |
Remove a spawned bot. Uses instance's --name. |
list_bots |
List all spawned bots with their positions |
get_inventory |
Get a bot's inventory contents (requires bot_api_use_inventory = true) |
give_item |
Give items to a bot's inventory (auto-drops if full) |
World Interaction Tools
| Tool | Description |
|---|---|
get_position |
Get position of a connected player |
teleport |
Teleport a connected player to position |
place_block |
Place a node at position (optionally as a bot with bot param) |
dig_block |
Dig/remove node at position (drops go to bot inventory if bot is actor) |
get_node |
Read the node name and param2 at a position |
say |
Broadcast chat message to all players (optionally as a bot) |
whisper |
Send private chat message to a specific player |
Information Tools
| Tool | Description |
|---|---|
get_chat |
Recent game events (chat messages, player join/leave) |
get_config |
Get bot_api configuration (visual settings, inventory status) |
Huge Context Tools (requires --huge_context flag)
| Tool | Description |
|---|---|
get_blocks |
All registered blocks/nodes (paginated, default 100 per page) |
get_entities |
All connected players and spawned bots with positions |
get_all_items |
All registered items (nodes, tools, craftitems) - paginated |
Note: Tools that default to the instance's bot name (--name flag) can target other bots
by explicitly passing the name or bot parameter. Example: spawn_bot {name:"alice", x:10, y:8, z:10}
creates a bot named "alice", and subsequent calls can target it with bot:"alice".