#
tokens: 4091/50000 5/5 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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)}"
```