From c89107af1be1fa3f9efdbf8f2e376a055dd6071d Mon Sep 17 00:00:00 2001 From: Martijn Jansen Date: Sun, 15 Feb 2026 11:23:05 +0100 Subject: [PATCH] chore: mcp server update --- .claude/settings.local.json | 3 +- .../__pycache__/server.cpython-314.pyc | Bin 18537 -> 27354 bytes minecraft-mcp/src/minecraft_mcp/server.py | 127 +++++++++++++++++- 3 files changed, 126 insertions(+), 4 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1490c05..51d6bd8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -25,7 +25,8 @@ "mcp__minecraft-bridge__clear_area", "mcp__minecraft-bridge__fill", "mcp__minecraft-bridge__teleport", - "mcp__minecraft-bridge__save_schematic" + "mcp__minecraft-bridge__save_schematic", + "mcp__minecraft-bridge__get_biome" ] } } diff --git a/minecraft-mcp/src/minecraft_mcp/__pycache__/server.cpython-314.pyc b/minecraft-mcp/src/minecraft_mcp/__pycache__/server.cpython-314.pyc index 9bd2de0f144863f690d8bb9e0f85f046017a0f79..57660aeb079b0b28d0cf842939065d19fd52d663 100644 GIT binary patch literal 27354 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 F{|~5fu`~bx delta 6317 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 Cf)zgi diff --git a/minecraft-mcp/src/minecraft_mcp/server.py b/minecraft-mcp/src/minecraft_mcp/server.py index ebd8833..06cd6e1 100644 --- a/minecraft-mcp/src/minecraft_mcp/server.py +++ b/minecraft-mcp/src/minecraft_mcp/server.py @@ -1,5 +1,8 @@ +import asyncio import json +import math import os +import time from typing import Any import httpx @@ -10,6 +13,17 @@ BASE_URL = f"http://localhost:{MINECRAFT_PORT}" mcp = FastMCP("minecraft-bridge") +# --------------------------------------------------------------------------- +# Build-session state +# --------------------------------------------------------------------------- + +_build_session_active = False +_build_session_lock = asyncio.Lock() +_last_build_time: float = 0.0 +_BUILD_SESSION_TIMEOUT = 3.0 # seconds of inactivity before session ends +_build_session_origin: tuple[float, float, float] | None = None +_watcher_task: asyncio.Task | None = None + async def _request(method: str, path: str, **kwargs) -> Any: """Make an HTTP request to the Minecraft mod server.""" @@ -35,6 +49,98 @@ def _fmt(data: Any) -> str: return json.dumps(data, indent=2) +# --------------------------------------------------------------------------- +# Build-session helpers (internal, not exposed as tools) +# --------------------------------------------------------------------------- + + +async def _set_bot_state(state: str) -> None: + await _request("POST", "/set_state", json={"state": state}) + + +async def _teleport_bot(x: float, y: float, z: float) -> None: + await _request("POST", "/teleport", json={"x": x, "y": y, "z": z}) + + +async def _look_at_pos(x: float, y: float, z: float) -> None: + await _request("POST", "/look_at", json={"x": x, "y": y, "z": z}) + + +async def _get_bot_position() -> tuple[float, float, float] | None: + data = await _request("GET", "/status") + if isinstance(data, dict) and "position" in data: + p = data["position"] + return (p["x"], p["y"], p["z"]) + return None + + +def _compute_viewing_position( + build_x: float, build_y: float, build_z: float, + bot_x: float, bot_y: float, bot_z: float, +) -> tuple[float, float, float]: + """Compute a spot 5 blocks from the build, on the side the bot is on.""" + dx = bot_x - build_x + dz = bot_z - build_z + dist = math.hypot(dx, dz) + if dist < 0.01: + dx, dz = 1.0, 0.0 + dist = 1.0 + scale = 5.0 / dist + return (build_x + dx * scale, build_y, build_z + dz * scale) + + +async def _ensure_build_session(x: float, y: float, z: float) -> None: + """Start or continue a build session, managing state/teleport/look.""" + global _build_session_active, _last_build_time, _build_session_origin + global _watcher_task + + async with _build_session_lock: + _last_build_time = time.monotonic() + + if _build_session_active: + # Session already running — just look at the new target + _build_session_origin = (x, y, z) + await _look_at_pos(x, y, z) + return + + # --- Start a new session --- + _build_session_active = True + _build_session_origin = (x, y, z) + + await _set_bot_state("building") + + pos = await _get_bot_position() + if pos is not None: + bx, by, bz = pos + dist = math.sqrt((bx - x) ** 2 + (by - y) ** 2 + (bz - z) ** 2) + if dist > 16: + vx, vy, vz = _compute_viewing_position(x, y, z, bx, by, bz) + await _teleport_bot(vx, vy, vz) + + await _look_at_pos(x, y, z) + + # Start the watcher if not already running + if _watcher_task is None or _watcher_task.done(): + _watcher_task = asyncio.create_task(_build_session_watcher()) + + +async def _build_session_watcher() -> None: + """Background task that ends the build session after inactivity.""" + global _build_session_active, _build_session_origin + + while True: + await asyncio.sleep(1.0) + async with _build_session_lock: + if not _build_session_active: + return + elapsed = time.monotonic() - _last_build_time + if elapsed >= _BUILD_SESSION_TIMEOUT: + await _set_bot_state("idle") + _build_session_active = False + _build_session_origin = None + return + + # --------------------------------------------------------------------------- # Observation tools (GET) # --------------------------------------------------------------------------- @@ -106,7 +212,8 @@ async def get_weather() -> str: @mcp.tool() async def move_to(x: float, y: float, z: float) -> str: - """Move the bot to the given coordinates by pathfinding. Returns distance moved.""" + """Move the bot to the given coordinates by pathfinding. Returns distance moved. + Use this to walk up to players or move closer to inspect builds.""" return _fmt(await _request("POST", "/move_to", json={"x": x, "y": y, "z": z})) @@ -119,6 +226,7 @@ async def teleport(x: float, y: float, z: float) -> str: @mcp.tool() async def place_block(x: int, y: int, z: int, block_type: str) -> str: """Place a block at the given coordinates. block_type is a Minecraft block ID like 'stone' or 'oak_planks'.""" + await _ensure_build_session(x, y, z) return _fmt(await _request("POST", "/place_block", json={ "x": x, "y": y, "z": z, "type": block_type, })) @@ -127,12 +235,18 @@ async def place_block(x: int, y: int, z: int, block_type: str) -> str: @mcp.tool() async def place_blocks(blocks: list[dict]) -> str: """Place multiple blocks at once. Each entry is {x, y, z, type}. Max 10,000 blocks per call.""" + if blocks: + cx = sum(b.get("x", 0) for b in blocks) / len(blocks) + cy = sum(b.get("y", 0) for b in blocks) / len(blocks) + cz = sum(b.get("z", 0) for b in blocks) / len(blocks) + await _ensure_build_session(cx, cy, cz) return _fmt(await _request("POST", "/place_blocks", json={"blocks": blocks})) @mcp.tool() async def fill(x1: int, y1: int, z1: int, x2: int, y2: int, z2: int, block_type: str) -> str: """Fill a bounding box with a single block type. Max volume 32x32x32.""" + await _ensure_build_session((x1 + x2) / 2, (y1 + y2) / 2, (z1 + z2) / 2) return _fmt(await _request("POST", "/fill", json={ "x1": x1, "y1": y1, "z1": z1, "x2": x2, "y2": y2, "z2": z2, @@ -143,12 +257,14 @@ async def fill(x1: int, y1: int, z1: int, x2: int, y2: int, z2: int, block_type: @mcp.tool() async def break_block(x: int, y: int, z: int) -> str: """Break (destroy) the block at the given coordinates.""" + await _ensure_build_session(x, y, z) return _fmt(await _request("POST", "/break_block", json={"x": x, "y": y, "z": z})) @mcp.tool() async def clear_area(x1: int, y1: int, z1: int, x2: int, y2: int, z2: int) -> str: """Clear all blocks in a bounding box (replace with air). Max volume 64x64x64.""" + await _ensure_build_session((x1 + x2) / 2, (y1 + y2) / 2, (z1 + z2) / 2) return _fmt(await _request("POST", "/clear_area", json={ "x1": x1, "y1": y1, "z1": z1, "x2": x2, "y2": y2, "z2": z2, @@ -176,6 +292,7 @@ async def place_structure( Max 64 blocks in any dimension. """ + await _ensure_build_session(origin_x, origin_y, origin_z) return _fmt(await _request("POST", "/place_structure", json={ "origin": {"x": origin_x, "y": origin_y, "z": origin_z}, "palette": palette, @@ -199,6 +316,7 @@ async def save_schematic(name: str, x1: int, y1: int, z1: int, x2: int, y2: int, async def load_schematic(name: str, x: int, y: int, z: int) -> str: """Load a previously saved schematic and place it at the given origin coordinates. Name may only contain letters, numbers, underscore, and hyphen.""" + await _ensure_build_session(x, y, z) return _fmt(await _request("POST", "/load_schematic", json={ "name": name, "x": x, "y": y, "z": z, }, timeout=30.0)) @@ -206,7 +324,8 @@ async def load_schematic(name: str, x: int, y: int, z: int) -> str: @mcp.tool() async def set_state(state: str) -> str: - """Set the bot's visual state. Valid states: idle, thinking, building, moving.""" + """Set the bot's visual state. Valid states: idle, thinking, building, moving. + Note: building state is auto-managed when using build tools. Use this manually for thinking or moving.""" return _fmt(await _request("POST", "/set_state", json={"state": state})) @@ -218,7 +337,9 @@ async def look_at(x: float, y: float, z: float) -> str: @mcp.tool() async def chat(message: str) -> str: - """Send a chat message in-game as the bot.""" + """Send a chat message in-game as the bot. + Use this to narrate what you're doing, react to builds, or respond to players. + Keep messages short and in-character.""" return _fmt(await _request("POST", "/chat", json={"message": message}))