01 · ARCHITECTURE

HOW A REQUEST FLOWS

v3.x runs bots on Mineflayer + mineflayer-pathfinder against a real Java-edition server — full physics, A* pathfinding, containers, and crafting. Bedrock and Java humans play in the same world via Geyser/Floodgate; the bots join as ordinary Java clients underneath.

AI agent (Claude / OpenClaw / DaVinci) ──▸ MCP · Streamable HTTP · minecraft-mcp-http.js :3100 ──▸ minecraft-bot-mcp-server.js (bot core)
Mineflayer + mineflayer-pathfinder ──▸ PaperMC :25565 ◂── Geyser / Floodgate ◂── Bedrock + Java human clients
02 · HTTP SURFACE

ROUTES ON :3100

POST /mcp MCP Streamable HTTP endpoint — the 34 tools live here for any MCP-speaking client.
GET /health Liveness + per-bot summary (spawned, position, health, food, impl).
GET /api/status Full status payload the dashboard polls every 2s (position, task, guard mode, chat log, last error).
GET /dashboard Zero-dependency HTML dashboard UI, served directly by the HTTP server.

If MCP_AUTH_TOKEN is set it gates every route above, fail-closed — send it as Authorization: Bearer <token>, or as ?token=<token> for the browser dashboard.

03 · REPOSITORY LAYOUT

WHAT'S IN THE REPO

minecraft-bot-mcp-server.js Current Mineflayer bot core — BotEntry, task-queue runner, all 34 tool handlers, reconnect loop.
minecraft-bot-mcp-server.bedrock.js Legacy v2 bedrock-protocol bot core, kept for rollback via MCP_BOT_IMPL=bedrock.
minecraft-mcp-http.js HTTP transport — picks the bot core, wires Streamable HTTP, runs the auto-reconnect default bot, exposes /mcp /health /api/status /dashboard.
minecraft-chat-bridge.js Bidirectional bridge between Minecraft chat and Discord/Telegram.
dashboard/index.html The zero-dep dashboard UI served at /dashboard.
scripts/deploy-java.sh Single-shot bare-metal installer: Java 21 + Paper + Geyser + systemd. Idempotent.
scripts/install-linux.sh / install-windows.ps1 Docker-based installers for Linux (bash) and Windows (PowerShell).
Dockerfile, docker-compose*.yml Container packaging — external-server mode by default, --full-stack bundles Paper+Geyser+Floodgate.
test/ node:test unit suite — queue runner, summary dispatcher, bridge dedup, lifecycle.
.github/workflows/ci.yml GitHub Actions: lint + tests on Node 20 & 22, plus shellcheck on scripts.
04 · CONFIGURATION

BOT CORE + TRANSPORT

MINECRAFT_HOST Java server host. Default 127.0.0.1.
MINECRAFT_PORT Java server port. Default 25565.
MINECRAFT_AUTH offline for Floodgate/cracked servers (default), microsoft for online-mode Java.
MINECRAFT_VERSION Auto-detected; pin it if Mineflayer mis-detects (e.g. 1.21.4).
MCP_PORT HTTP transport port. Default 3100.
MCP_HOST Bind address. Default 0.0.0.0.
MCP_AUTH_TOKEN Optional bearer token; when set, gates every route, fail-closed.
MCP_DEFAULT_BOT Auto-connected on boot with capped backoff retry. Default MCPTestBot; empty string disables it.
MCP_BOT_IMPL mineflayer (default) or bedrock for the v2 fallback; any other value is treated as a Node require path.

CHAT BRIDGE

A bidirectional relay between Minecraft chat and Discord/Telegram. Inbound: POST a message to the bridge's /relay route and it calls minecraft_chat as its own bot username. Outbound: the bridge polls /api/status and forwards new lines, seeding its dedup set on first poll so it never floods the channel with backlog.

MCP_SERVER host:port of the MCP HTTP server. Default 127.0.0.1:3100.
BRIDGE_PORT Inbound POST /relay listener. Default 3101.
BRIDGE_BOT_USERNAME Bot name used when relaying into Minecraft. Default ChatBridge.
DISCORD_WEBHOOK_URL Enables Discord output.
TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID Enables Telegram output.
OUTBOUND_POLL_MS Poll interval for Minecraft → Discord/Telegram. Default 3000; 0 disables outbound.
OUTBOUND_IGNORE Comma-separated usernames never forwarded outbound (echo-loop protection). The bridge's own username is always excluded.

Discord posts can't ping @everyone/@here/roles. Telegram text is HTML-escaped. TLS verification is on for both.

05 · DEVELOPMENT & TESTING

RUNNING THE TEST SUITE

git clone https://github.com/christopherdeck/minecraft-mcp.git
cd minecraft-mcp
npm install
npm run lint    # node --check on every source JS file
npm test        # node:test suite (~30 tests, runs in a couple seconds)

CI runs the same lint + test matrix on Node 20 and 22, plus shellcheck + bash -n on scripts/*.sh. Coverage: task-queue step order and status labels, /health & /api/status payload shape, chat-bridge dedup and echo-loop protection, and bot lifecycle (pre-spawn error handling, reconnect backoff). Tests use an injectable step runner and fake EventEmitter bots, so the whole suite runs without a live Minecraft server.

06 · OPERATIONS

LOGS & ROLLBACK

sudo journalctl -u paper-server -f          # the Java server
sudo journalctl -u minecraft-bot-mcp -f     # the MCP + bot core
sudo journalctl -u minecraft-chat-bridge -f # the bridge (if installed)

The v2 bedrock-protocol core is preserved for emergencies. Roll back without touching source: bring the Bedrock server up, stop Paper, then drop in an env override —

sudo systemctl edit minecraft-bot-mcp
# paste:
[Service]
Environment=MCP_BOT_IMPL=bedrock
Environment=MINECRAFT_HOST=127.0.0.1
Environment=MINECRAFT_PORT=19132

sudo systemctl daemon-reload && sudo systemctl restart minecraft-bot-mcp
# /health now reports impl: "bedrock" per bot. Roll forward with:
sudo systemctl revert minecraft-bot-mcp && sudo systemctl restart minecraft-bot-mcp

scripts/deploy-java.sh only overwrites the main unit file, never drop-ins — re-running it on a rolled-back system is safe.

07 · VERSION HISTORY

WHY IT LOOKS LIKE THIS

v3.x — Pivot to Mineflayer / Java (current, supported)
bedrock-protocol is a raw-packet library — reimplementing Mineflayer-grade physics, pathfinding, containers and crafting on top of it was an unending fight. v3 swaps the bot core for Mineflayer + mineflayer-pathfinder against a Java server; Bedrock clients still play via Geyser, so human UX is unchanged and the MCP tool surface stayed identical to v2.
v2.x — Last-mile Bedrock packet fixes
Fixed up to actually join and act on modern (1.20+) server-authoritative Bedrock: rebuilt player_auth_input against current minecraft-data, removed client-side move_player, replaced use_item with inventory_transaction, and loaded the block palette from Bedrock's blockStates.json. Still limited by bedrock-protocol's level of abstraction.
v1 / pre-history
Initial Bedrock implementation, plus a targetDx-not-defined fix in faceBlock and minecraft_move.