chore: mcp server update

This commit is contained in:
2026-02-15 11:23:05 +01:00
parent 055b93817a
commit c89107af1b
3 changed files with 126 additions and 4 deletions
+2 -1
View File
@@ -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"
]
}
}
+124 -3
View File
@@ -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}))