From 8b740f3ec11522935a4952642902bb4d9760d3f6 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sat, 28 Mar 2026 20:10:15 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20external=20brain=20tools=20=E2=80=94=20?= =?UTF-8?q?queue,=20tool=5Fexecute,=20brain=5Fmode=5Fset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CLI can now BE the brain: - brain_mode_set: switch gateway to external mode - queue_get: poll incoming player commands - queue_complete: send response back to player - tool_execute: call any gateway tool directly (rcon, display, npc, etc.) 28 MCP tools total. When in external mode, Opus in the CLI processes player commands instead of Codex/Anthropic. Co-Authored-By: Claude Opus 4.6 (1M context) --- mcp-server/server.py | 91 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/mcp-server/server.py b/mcp-server/server.py index 39013ee..2d6a688 100644 --- a/mcp-server/server.py +++ b/mcp-server/server.py @@ -125,6 +125,97 @@ async def gateway_health() -> str: return f"Error: {e}" +# --- Brain mode + command queue --- + + +@mcp.tool() +async def brain_mode_set(mode: str) -> str: + """Switch gateway between internal (AI loop) and external (CLI brain) mode. + + In external mode, player commands are queued for YOU to handle. + In internal mode, the gateway's own AI (Codex/Anthropic) handles them. + + Args: + mode: "internal" or "external" + """ + try: + data = await _patch(f"/v2/brain-mode?mode={mode}") + return json.dumps(data, indent=2) + except Exception as e: + return f"Error: {e}" + + +@mcp.tool() +async def queue_get() -> str: + """Get pending player commands waiting for you to handle. + + Only works when brain_mode is 'external'. Returns commands with + player name, mode, message, world context, and player context. + Process each command by calling tools, then complete it with queue_complete. + """ + try: + data = await _get("/v2/queue") + return json.dumps(data, indent=2) + except Exception as e: + return f"Error: {e}" + + +@mcp.tool() +async def queue_complete(cmd_id: str, response_text: str) -> str: + """Complete a queued command — sends the response back to the player. + + Args: + cmd_id: Command ID from queue_get + response_text: The message to send to the player + """ + try: + data = await _post(f"/v2/queue/complete/{cmd_id}", { + "response_text": response_text, + "player_message": response_text, + }) + return json.dumps(data, indent=2) + except Exception as e: + return f"Error: {e}" + + +@mcp.tool() +async def tool_execute( + tool: str, + params: str = "{}", + player: str = "", + server: str = "dev", +) -> str: + """Execute a Mortdecai gateway tool directly. YOU are the brain. + + Available tools: rcon_execute, rcon_query, display_send, display_interactive, + npc_spawn, npc_bulk_spawn, npc_despawn, npc_bulk_despawn, npc_list, + npc_command, npc_script_write, npc_script_read, eye_players, eye_world, + eye_events, memory_read, memory_write, history_read, logs_read, + sound_play, world_query, schem_list, schem_place, schem_download, + creative_name, perms_manage + + Args: + tool: Tool name (e.g. "rcon_execute") + params: JSON string of tool parameters + player: Player context (for session lookup) + server: Server target — dev or prod + """ + try: + tool_params = json.loads(params) if isinstance(params, str) else params + except json.JSONDecodeError: + return f"Invalid JSON params: {params}" + try: + data = await _post("/v2/tools/execute", { + "tool": tool, + "params": tool_params, + "player": player, + "server": server, + }) + return json.dumps(data, indent=2) + except Exception as e: + return f"Error: {e}" + + # --- Player commands (through gateway) ---