# Directory Structure ``` ├── .gitignore ├── .python-version ├── pyproject.toml ├── rcon.py ├── README.md └── uv.lock ``` # Files -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- ``` 1 | 3.12 2 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | # Virtual environments 10 | .venv 11 | 12 | # Byte-compiled / optimized / DLL files 13 | __pycache__/ 14 | *.py[cod] 15 | *$py.class 16 | 17 | # C extensions 18 | *.so 19 | 20 | # Distribution / packaging 21 | .Python 22 | build/ 23 | develop-eggs/ 24 | dist/ 25 | downloads/ 26 | eggs/ 27 | .eggs/ 28 | lib/ 29 | lib64/ 30 | parts/ 31 | sdist/ 32 | var/ 33 | wheels/ 34 | share/python-wheels/ 35 | *.egg-info/ 36 | .installed.cfg 37 | *.egg 38 | MANIFEST 39 | 40 | # PyInstaller 41 | # Usually these files are written by a python script from a template 42 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 43 | *.manifest 44 | *.spec 45 | 46 | # Installer logs 47 | pip-log.txt 48 | pip-delete-this-directory.txt 49 | 50 | # Unit test / coverage reports 51 | htmlcov/ 52 | .tox/ 53 | .nox/ 54 | .coverage 55 | .coverage.* 56 | .cache 57 | nosetests.xml 58 | coverage.xml 59 | *.cover 60 | *.py,cover 61 | .hypothesis/ 62 | .pytest_cache/ 63 | cover/ 64 | 65 | # Translations 66 | *.mo 67 | *.pot 68 | 69 | # Django stuff: 70 | *.log 71 | local_settings.py 72 | db.sqlite3 73 | db.sqlite3-journal 74 | 75 | # Flask stuff: 76 | instance/ 77 | .webassets-cache 78 | 79 | # Scrapy stuff: 80 | .scrapy 81 | 82 | # Sphinx documentation 83 | docs/_build/ 84 | 85 | # PyBuilder 86 | .pybuilder/ 87 | target/ 88 | 89 | # Jupyter Notebook 90 | .ipynb_checkpoints 91 | 92 | # IPython 93 | profile_default/ 94 | ipython_config.py 95 | 96 | # pyenv 97 | # For a library or package, you might want to ignore these files since the code is 98 | # intended to run in multiple environments; otherwise, check them in: 99 | # .python-version 100 | 101 | # pipenv 102 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 103 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 104 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 105 | # install all needed dependencies. 106 | #Pipfile.lock 107 | 108 | # UV 109 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 110 | # This is especially recommended for binary packages to ensure reproducibility, and is more 111 | # commonly ignored for libraries. 112 | #uv.lock 113 | 114 | # poetry 115 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 116 | # This is especially recommended for binary packages to ensure reproducibility, and is more 117 | # commonly ignored for libraries. 118 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 119 | #poetry.lock 120 | 121 | # pdm 122 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 123 | #pdm.lock 124 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 125 | # in version control. 126 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 127 | .pdm.toml 128 | .pdm-python 129 | .pdm-build/ 130 | 131 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 132 | __pypackages__/ 133 | 134 | # Celery stuff 135 | celerybeat-schedule 136 | celerybeat.pid 137 | 138 | # SageMath parsed files 139 | *.sage.py 140 | 141 | # Environments 142 | .env 143 | .venv 144 | env/ 145 | venv/ 146 | ENV/ 147 | env.bak/ 148 | venv.bak/ 149 | 150 | # Spyder project settings 151 | .spyderproject 152 | .spyproject 153 | 154 | # Rope project settings 155 | .ropeproject 156 | 157 | # mkdocs documentation 158 | /site 159 | 160 | # mypy 161 | .mypy_cache/ 162 | .dmypy.json 163 | dmypy.json 164 | 165 | # Pyre type checker 166 | .pyre/ 167 | 168 | # pytype static type analyzer 169 | .pytype/ 170 | 171 | # Cython debug symbols 172 | cython_debug/ 173 | 174 | # PyCharm 175 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 176 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 177 | # and can be added to the global gitignore or merged into this file. For a more nuclear 178 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 179 | #.idea/ 180 | 181 | # Ruff stuff: 182 | .ruff_cache/ 183 | 184 | # PyPI configuration file 185 | .pypirc 186 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Minecraft Docker MCP 2 | 3 | An MCP server for Minecraft-in-Docker that enables AI interactions with a running Minecraft server using itzg's docker-minecraft-server container. 4 | 5 | * Expose server administration to AI clients like Claude Desktop, Cursor, and Zed. 6 | * Allow models to programmatically create minecraft builds in game 7 | 8 | LLMs have largely been trained on `rcon` commands, so there's a wide breadth of ability inherent in just exposing `rcon` to the model. 9 | 10 | If you're already using the `itzg/minecraft-server` docker image, this MCP server will allow you to interact with your server via clients like Claude Desktop, Cursor, and Zed. The only requirement is that `mc` is the name of the container. 11 | 12 | ## Prerequisites 13 | 14 | - A running Minecraft server in a Docker container named `mc` 15 | - RCON enabled and properly configured 16 | 17 | ```bash 18 | docker run -d --name mc -p 25565:25565 -e EULA=TRUE itzg/minecraft-server 19 | ``` 20 | 21 | To ensure you're able to use this server, try running an `rcon` command to see if you get a response. 22 | 23 | ```bash 24 | docker exec -it mc rcon "list" 25 | ``` 26 | 27 | If you get a response, you're all set! If you don't, please refer to the [itzg/docker-minecraft-server](https://github.com/itzg/docker-minecraft-server) repository for troubleshooting. 28 | 29 | ## MCP Integration 30 | 31 | This MCP server leverages itzg's docker-minecraft-server container's built-in RCON functionality to interact with the Minecraft server. The container provides the `rcon` command within the running container, making it an ideal target for MCP interactions. 32 | 33 | ### Connecting to Claude Desktop 34 | 35 | Clone this repository and install the `rcon.py` tool using the MCP CLI. 36 | 37 | ``` 38 | mcp install rcon.py 39 | ``` ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- ```toml 1 | [project] 2 | name = "minecraft-docker-mcp" 3 | version = "0.1.0" 4 | description = "Model Context Protocol Server for Minecraft Administration" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "httpx>=0.28.1", 9 | "mcp[cli]>=1.2.1", 10 | ] 11 | ``` -------------------------------------------------------------------------------- /rcon.py: -------------------------------------------------------------------------------- ```python 1 | # server.py 2 | import subprocess 3 | from typing import Optional 4 | # from enum import Enum 5 | from mcp.server.fastmcp import FastMCP 6 | 7 | mcp = FastMCP("Minecraft Admin") 8 | 9 | 10 | @mcp.tool() 11 | def rcon(command: str) -> str: 12 | """Issue commands to the Minecraft server via RCON. Best practices and common patterns include: 13 | 14 | ## Player Location & Building 15 | 1. ALWAYS get player coordinates first before building: 16 | - `data get entity player_name Pos` 17 | - This returns coordinates in format: [X.XXd, Y.XXd, Z.XXd] 18 | - Store these coordinates and use them as the base for building 19 | 20 | 2. Building Commands: 21 | - Direct placement: `setblock x y z block_type` 22 | - Fill command: `fill x1 y1 z1 x2 y2 z2 block_type [replace|keep|outline|hollow]` 23 | - Clone command: `clone x1 y1 z1 x2 y2 z2 dest_x dest_y dest_z` 24 | 25 | 3. Entity Commands: 26 | - Summon entities: `summon <entity_type> <x> <y> <z>` 27 | - Teleport entities: `tp @e[type=<entity_type>] <x> <y> <z>` 28 | - Execute as entities: `execute as @e[type=<entity_type>] at @s run <command>` 29 | 30 | 4. View/Perspective Commands: 31 | - Teleport to location: `tp @p x y z` 32 | - Spectate entity: `spectate <target> [player]` 33 | - Execute from position: `execute positioned x y z run <command>` 34 | 35 | ## Common Command Patterns 36 | Item Commands: 37 | - give rgbkrk coal 12 38 | - give rgbkrk iron_axe[enchantments={levels:{"minecraft:sharpness":5,"minecraft:efficiency":5,"minecraft:fortune":5}}] 1 39 | - give @a iron_pickaxe[unbreakable={}] 40 | 41 | Effect Commands: 42 | - effect give @a speed 300 2 43 | - effect give LoganTheParrot minecraft:night_vision 1000 1 44 | - effect give rgbkrk water_breathing infinite 1 true 45 | 46 | Potion Commands: 47 | - Basic item: give rgbkrk potion[minecraft:potion_contents={potion:"minecraft:fire_resistance"}] 48 | - Multiple items: give rgbkrk potion[minecraft:potion_contents={potion:"minecraft:strength"}] 5 49 | - Splash/lingering variants: give rgbkrk splash_potion[minecraft:potion_contents={potion:"minecraft:poison"}] 50 | 51 | ## Targeting Players 52 | - Use `@a` for all players 53 | - Use a player name to target a specific player (e.g. rgbkrk) 54 | - Can get specific player coordinates: `data get entity player_name Pos` 55 | - Position returns format: [X.XXd, Y.XXd, Z.XXd] 56 | 57 | ## Block Placement Best Practices 58 | 1. Get player coordinates first 59 | 2. Calculate relative positions from stored coordinates 60 | 3. Build structures using absolute coordinates 61 | 4. Test for block type existence before using (some modded blocks may not exist) 62 | 63 | ## Block States 64 | - Use square brackets for block states: `block_type[property=value]` 65 | - Example: `lantern[hanging=true]` 66 | - Multiple properties use comma separation 67 | 68 | ## Relative vs Absolute Coordinates 69 | - Absolute: Uses exact coordinates (x y z) 70 | - Relative to player: Uses tilde notation (~) 71 | - `~` means current position 72 | - `~1` means one block offset 73 | - `~-1` means one block negative offset 74 | 75 | ## Common Gotchas 76 | - NEVER build large structures relative to the player's current position. GET the location needed first. 77 | - RCON needs player context for certain commands like `locate` 78 | - Block placement might need block states specified 79 | - Fill commands include both start and end coordinates 80 | - Coordinates are exclusive (e.g., ~0 to ~15 creates a 16-block span) 81 | - Test for block existence before using modded or unusual blocks 82 | 83 | """ 84 | return subprocess.check_output(["docker", "exec", "mc", "rcon-cli", command]).decode("utf-8") 85 | 86 | 87 | @mcp.tool() 88 | def list_players() -> str: 89 | """List all currently connected players on the Minecraft server.""" 90 | return rcon("list") 91 | 92 | 93 | @mcp.tool() 94 | def help(command: Optional[str] = None) -> str: 95 | """Get help for Minecraft commands.""" 96 | return rcon(f"help {command}" if command else "help") 97 | 98 | 99 | # class WeatherType(Enum): 100 | # CLEAR = "clear" 101 | # RAIN = "rain" 102 | # THUNDER = "thunder" 103 | 104 | # @mcp.tool() 105 | # def weather(type: WeatherType = WeatherType.CLEAR) -> str: 106 | # """Set the weather in the Minecraft world. 107 | 108 | # Args: 109 | # type: Weather type (clear, rain, thunder) 110 | # """ 111 | # return rcon(f"weather {type.value}") 112 | 113 | 114 | @mcp.tool() 115 | def server_stats() -> str: 116 | """Get server statistics including CPU, memory usage, and uptime.""" 117 | try: 118 | stats = subprocess.check_output( 119 | ["docker", "stats", "mc", "--no-stream", "--format", "{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"] 120 | ).decode("utf-8").strip() 121 | return f"Server Stats:\n{stats}" 122 | except subprocess.CalledProcessError as e: 123 | return f"Error getting server stats: {str(e)}" 124 | 125 | @mcp.tool() 126 | def server_logs(lines: int = 10) -> str: 127 | """Get recent server logs. 128 | 129 | Args: 130 | lines: Number of recent log lines to fetch (default: 10) 131 | """ 132 | try: 133 | logs = subprocess.check_output( 134 | ["docker", "logs", "--tail", str(lines), "mc"] 135 | ).decode("utf-8") 136 | return logs 137 | except subprocess.CalledProcessError as e: 138 | return f"Error fetching logs: {str(e)}" 139 | 140 | @mcp.tool() 141 | def check_server_status() -> str: 142 | """Check if the Minecraft server is running and responsive.""" 143 | try: 144 | status = subprocess.check_output( 145 | ["docker", "inspect", "-f", "{{.State.Status}}", "mc"] 146 | ).decode("utf-8").strip() 147 | 148 | if status == "running": 149 | # Try to execute a simple rcon command to verify server is responsive 150 | try: 151 | response = rcon("list") 152 | return f"Server is running and responsive.\nStatus: {status}\n{response}" 153 | except Exception as _: 154 | return f"Server is running but may not be fully initialized.\nStatus: {status}" 155 | else: 156 | return f"Server is not running. Status: {status}" 157 | except subprocess.CalledProcessError as e: 158 | return f"Error checking server status: {str(e)}" ```