← all posts

Claude MCP – Turning a Minecraft Client into an MCP Server

A Fabric mod for Minecraft 26.3 that lets Claude Code or any other MCP client take over the running game, with an A* pathfinder, live Groovy, Java reflection and method hooks that patch the game without a restart.

minecraftmcpjavafabricai
Contents
  1. The idea
  2. A server inside the game
  3. Tasks, and the player always wins
  4. A pathfinder of my own
  5. Reflection: the whole game as an API
  6. Live Groovy and method hooks
  7. What 26.3 made interesting
  8. Testing a mod end to end
  9. Rules
  10. Try it

The idea

I wanted to type "chop down the nearest tree, build a crafting table and remember where it is" into Claude Code and watch it happen in my own Minecraft client. Not a bot on a separate account, not a server plugin. The actual game window on my PC, with me able to grab the keyboard at any moment.

Claude MCP is the result: a client-side Fabric mod for Minecraft 26.3 that starts an MCP server inside the game. Any MCP client can connect to it, which in my case means Claude Code and opencode with a local model. The agent can see the world and move around in it, mine, place, craft and fight. It can also do things no player can: call any Java method of the game, run Groovy code inside the client and hook methods while the game is running.

Claude walking through the world with the goto tool
The pathfinder at work. The HUD in the top left shows the running task: step 8 of 32 on the way to the target.

A server inside the game

The MCP server is an embedded Jetty with the official MCP Java SDK, speaking Streamable HTTP on 127.0.0.1:25599. On first start the mod writes a random token to config/claudemcp/token.txt, and every request needs it as a bearer token, compared in constant time. The server only exists while the game runs, and it never listens on anything but localhost.

Hooking it up to Claude Code is one script (register.ps1), which finds the Prism Launcher instance, reads the token and registers the server. For opencode it's a few lines of JSON. There are 73 tools in total, grouped into perception, actions, tasks, events, memory, reflection, scripts, method hooks, chat filters and NBT editing.

Tasks, and the player always wins

Most interesting actions take a while. Walking somewhere can take a minute, mining a block depends on the tool. So every long action runs as a task: the call returns an ID right away, and the agent polls task_status or blocks on task_wait. Tasks report progress, can be cancelled, and the End key stops all of them at once.

Tasks that use the player's "body" (moving, mining, interacting) are exclusive. Starting a new one cancels the old one, so the agent can't end up walking in two directions. And if I press WASD, jump or click, mixins on keyboard and mouse input notice and cancel the current movement task immediately. The agent is a passenger the moment I want the wheel back.

Events like chat, damage, death and finished tasks go into one stream with sequence numbers. get_events supports long polling, so the agent waits for something to happen instead of hammering the server.

A pathfinder of my own

Minecraft has no pathfinding the client could just call, and the existing mods for it are far larger than what an agent needs. So goto uses a small A* over block positions, written so it doesn't depend on Minecraft at all and can be unit-tested against made-up worlds.

It knows six kinds of moves: walk (including diagonals), jump up one block, fall up to three blocks, swim, climb, and optionally break blocks that are in the way. Every search has a node budget and a 250 ms time limit, so a target behind a mountain never freezes the game. Long routes are walked in pieces: when a search runs out of budget, the task follows the best partial path and searches again from where it ended up, up to eight times.

Two safety rules sit on top of every step:

// safety: never drown or die while walking
if (p.isUnderWater() && p.getAirSupply() < 120) surfacing = true;
...
if (p.getHealth() <= 5f && p.hurtTime > 0) {
    throw new IllegalStateException("stopped: low health (" + Mc.r2(p.getHealth()) + ") while taking damage");
}

An agent that keeps walking while a creeper eats its health bar is funny exactly once.

Reflection: the whole game as an API

The fixed tools cover the common cases. For everything else, there's reflection. Since 26.3, Minecraft ships with Mojang's own names instead of obfuscated ones, so java_find_class("LivingEntity") finds exactly what you'd expect. The agent can read and write fields, call methods and create objects on any class of the game or of other mods.

Live objects come back as handles like h:12, which later calls can pass as arguments. The hard part was overloads: JSON only knows numbers, strings, booleans, lists and maps, while a Java method may exist in five versions with int, double, float and boxed parameters. A small resolver scores every candidate by how well the JSON values convert to its parameter types and picks the cheapest one. When nothing fits, or two candidates tie, the error lists all of them and the agent can pass an exact signature. Like the pathfinder, it has no Minecraft dependency and its own unit tests.

Live Groovy and method hooks

script_eval runs Groovy inside the client with mc, player, level and a helper api already in scope. Short code runs synchronously on the client thread. Longer scripts run as cancellable tasks on a worker thread and touch game objects through api.onMain { … }. Scripts can be saved and attached to events: every tick, incoming chat, damage, HUD rendering, key presses.

Method hooks go one step further. java_hook patches any method of the game while it's running. On first use, the mod attaches a Java agent to its own process with Byte Buddy and then retransforms the target class with an advice that calls into Groovy before and after the original method:

// replace a return value
{ "class": "LivingEntity", "method": "getHealth",
  "after": "if (self instanceof net.minecraft.client.player.LocalPlayer) hook.setResult(7.5)" }

// rewrite an argument
{ "class": "ChatComponent", "method": "addClientSystemMessage",
  "before": "args[0] = net.minecraft.network.chat.Component.literal('[hook] ' + args[0].string)" }

Hooks run on whatever thread calls the method, often the render thread, so they need hard limits. Each call gets 50 ms (500 ms for the first three, while Groovy compiles), and a hook that fails five times in a row switches itself off and reports why. Removing the last hook from a method restores the original bytecode. JDK classes and the mod itself are off limits.

Live Groovy HUD, a method hook and a chat filter active at the same time
All at once: a Groovy HUD in the top right, a method hook rewriting chat lines ([java_hook]) and a chat filter replacing a message.

What 26.3 made interesting

Building against a version this new meant finding a few things out the hard way:

  • Renamed everything. With Mojang names instead of the old mappings, a lot of classes and methods are simply called something else than in 1.21 tutorials, for example GuiGraphicsExtractor, mc.gui.screen() and Hud.toggle().
  • SDL instead of GLFW. Key codes are SDL scancodes now. The reliable way to get the right one is InputConstants.getKey("key.keyboard.w") instead of hard-coding numbers.
  • Buttons ignore fake clicks. Synthetic mouseClicked events don't trigger every button, so click_widget calls the widget's onPress directly.
  • A crash on quit. The MCP SDK creates Reactor threads that aren't daemon threads. They kept the JVM alive after closing the game until Minecraft's shutdown watchdog killed it with a crash report. A custom Reactor scheduler factory that turns all of them into daemon threads fixed it.

Testing a mod end to end

Unit tests cover the parts without Minecraft: pathfinder, overload resolution, memory and event store, NBT paths. That's the fast loop.

The slow loop is gradlew runClientGameTest. It starts a real client, creates a test world, then talks to the mod over real MCP HTTP, exactly like Claude Code does, and runs through the core features. One trap there: tools that need the client thread wait for the next game tick, but the game test drives ticks itself. If the test thread just waits for an HTTP answer, the tick never comes and everything blocks. The test has to keep pumping ticks while it waits.

Rules

Scripts and reflection mean full code execution inside the game process. Both can be switched off in config/claudemcp/config.json, and the server only ever binds to localhost. The mod is client-side only and works in single player and on servers, including my own. Anticheat evasion is deliberately not part of it: on someone else's server, their rules apply.

Try it

Claude MCP is Apache-2.0 licensed and on GitHub. Drop the jar from the latest release and the Fabric API into a Fabric 26.3 instance, start the game once, then register the server:

.\register.ps1

Open a world and tell your agent what to do. The repository also has a Claude Code skill that teaches it how the tools fit together, which saves a lot of trial and error on the first tasks.