mc-bridge
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mc-bridgelist nearby entities within 32 blocks"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mc-agent-bridge
๐ Part of mc-agent; the guide lives at https://guajun.github.io/mc-agent/.
A small, Harness-neutral Minecraft Toolkit. It runs next to the game server,
owns one connection to the mc-agent-interface mod, and exposes the game as a
stable JSON surface any Harness can call over MCP or the CLI: health and
capabilities, player context, entities, commands, chat, events, context
bundles, snapshots and world-save metadata.
The Toolkit contains no model calls, no agent loop and no conversation state, and it does not know what any particular Harness wants to do. It also does not decide what an Agent should do: it answers questions and executes requested primitives, and gets out of the way.
The default target is the mod's server vantage - the authoritative one, and the only one that can snapshot entity tick order.
MCP client / CLI / loop <-> bridge daemon <-> server-vantage mod <-> server
(any Harness, no SDKs) this repo mc-agent-interface (Fabric)See docs/toolkit.md for the install-and-use guide and the full tool reference.
Why a separate daemon
The mod accepts exactly one kind of client: a TCP line connection. Rather than letting every agent session open its own socket to the game, a single long-lived daemon owns that connection and re-serves it on loopback as a JSON-lines API.
That buys three things:
Swappable Harnesses. Hermes today, Codex tomorrow, a shell script next week. They all speak the same API and none of them touch the game.
Survivable Harness sessions. A Harness that restarts, or an MCP server that is spawned per session, does not disturb the game connection.
Event replay. The daemon keeps a ring buffer of recent events, so an agent can ask "what happened while I was thinking?" with a cursor instead of needing to be alive at the exact moment something happened.
MCP is offered as an optional front-end (mc-bridge mcp), not as the core. MCP
is a pull-based tool interface: a client spawns the server, so the game cannot
wake an agent through it. Waking an agent on a game event is the job of an agent
loop that subscribes to the daemon's event stream - see mc-agent-loop.
Related MCP server: Minecraft MCP Server Pro
Requirements
Python 3.11+
The
mc-agent-interfaceFabric mod running in the same environment as the game server (a dedicated server, or the integrated server inside a single-player client)
Install
pip install -e . # core: daemon, local API, CLI
pip install -e ".[mcp]" # plus the MCP front-endQuick start
# 1. Start the daemon (keep it running while the game is open)
mc-bridge run
# 2. In another shell, check the connection and what this instance can do
mc-bridge call status
mc-bridge call capabilities
# 3. Use only the operations the instance advertises
mc-bridge call state
mc-bridge call save
mc-bridge call entities '{"radius": 32}'
mc-bridge call command_output '{"command": "data get entity <name> Motion"}'
# 4. Watch the event stream
mc-bridge watch --events game,chat,markFinding the game
The default target is the server vantage. Its entrypoint writes the port it
bound to into <gameDir>/mc-agent-server/port.txt; the daemon reads the file
on every reconnect, so a port that moves is picked up automatically.
Resolution order for the server vantage:
--mod-port(an explicit port always wins)--port-fileorMC_AGENT_PORT_FILE(the exactport.txt)<--server-dir | $MC_AGENT_SERVER_DIR | cwd>/mc-agent-server/port.txt<--server-dir>/port.txtwhen a server directory was named explicitly
If nothing resolves, the daemon prints an actionable error and keeps watching - it never guesses a port and never falls back to client-vantage. You can check discovery without starting the daemon:
mc-bridge discover
# {"vantage": "server", "port": null, "source": "unresolved",
# "error": "cannot find the server-vantage port file ... --port-file ... --vantage client"}Legacy client-vantage setups (the mod in a Minecraft client) opt in explicitly:
mc-bridge run --vantage client # ./port.txt, ./mc-agent/port.txt, else 25580
mc-bridge run --vantage client --port-file /path/to/mc-agent/port.txtThe local API
Newline-delimited JSON over loopback. Requests carry an id; events are pushed
to subscribed connections.
{"id": "1", "method": "state", "params": {}}
{"type": "response", "id": "1", "ok": true, "result": {"type": "state", "tick": 4211, "x": 12.5}}
{"type": "event", "event": "chat", "data": {"type": "chat", "text": "hi", "sender": "someone", "seq": 7}}Method | Params | Maps to mod line |
| - | local only |
| - | local only: connection, vantage, port source, discovery, buffer, clients |
| - |
|
| - |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| - |
|
|
|
|
| - |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| replay from the buffer |
|
|
|
| - | local composition of |
|
|
|
| - |
|
|
| freeze โ |
|
| validate โ require an endpoint proof โ resolve/freeze tick โ load box โ baseline/dimension/collision checks โ narrow collision kill with ticks running and re-freeze when replacing โ |
|
| re-snapshot and compare dimension, UUID order, counts, positions, velocities and NBT with a fork |
|
| re-snapshot and compare |
| - | shut the daemon down |
Methods are checked against the connected mod's CAPS before anything is sent. A
client-only method on a server-vantage connection (or a server-only method on a
client connection) returns a structured error naming the missing capability; for
player and context it also names the mod issue they originated from. Both
capabilities ship in mc-agent-interface-mod 0.6.0 (issues #1 and #2 for
server-side player context and chat context bundles); they appear as soon as
the connected mod advertises them, with no code change here.
Every client connection can also call subscribe / unsubscribe with a list of
event categories: hello, chat, game, mark, sample, error, other,
or * for everything.
events returns {"events": [...], "next": <cursor>, "dropped": <bool>}.
Feed next back as since to poll incrementally; dropped warns that the
ring buffer discarded events you never saw.
MCP front-end
mc-bridge mcp # stdio, for MCP clients that spawn servers
mc-bridge mcp --transport streamable-httpmc-bridge mcp --vantage client # legacy client surface onlyThe tool list is filtered by the connected instance's CAPS, so a server-vantage
session gets only the operations it can serve: health, capabilities, state,
entities, commands, command output, wait, mark, events, save, snapshots, fork,
restore, verify and order - and mc_player/mc_context when the mod advertises
them - and never mc_chat, mc_screen, mc_connect or the other client-only
tools.
Before the daemon answers, the front-end registers the documented default
server surface; once it answers, the live CAPS reply wins. Callers should
still start with mc_capabilities, which returns the raw CAPS list plus the
filtered surface.supported / surface.unsupported view with reasons.
mc_command and mc_command_output exist because a command's answer is chat,
not a return value: the first one just sends it, the second sends it and returns
the answer - from the command reply on the server vantage, or from game/chat
events on the client vantage. Anything that reports data - data get entity <name> Motion, player <name> ..., mod commands - should use the second.
Verified against mcp 2.x (where the SDK renamed FastMCP to MCPServer) and
1.x; the front-end picks whichever class the installed SDK provides. It imports
only the MCP SDK - no agent-framework SDK and no model client.
The equivalent of the older mc-codex-bridge design was one special-purpose
daemon per agent. Here the daemon is neutral and each agent attaches however it
likes: MCP, the JSON-lines API, or a loop built on this package.
Forwarding events to a webhook
mc-bridge forward is an optional, receiver-neutral event forwarder. It
subscribes to the daemon's event stream over loopback and POSTs selected events
to one HTTP(S) URL. It contains no agent runtime and makes no model calls, and
only outbound requests leave the machine, so the mod and local API keep their
loopback bindings.
export MC_AGENT_WEBHOOK_URL="https://listener.example/hooks/mc-agent"
export MC_AGENT_WEBHOOK_SECRET="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')"
mc-bridge forward --events chat,game,mark,errorForwarding is off by default: without both a URL and a secret the command
refuses to start. Credentials come from the environment or a JSON config file,
never from command-line flags, so they cannot land in shell history. Queue and
retry state live in memory only - there is no durable delivery across machine
restarts, and a restarted forwarder does not replay what it missed. Receivers
should therefore treat eventId as the idempotency key.
Request contract
Every delivery is an HTTP POST with a JSON body:
{
"eventId": "9f2c0a1b...:7", // stable; unchanged across retries
"sequence": 7, // the daemon's buffered sequence
"streamId": "9f2c0a1b...", // identifies the daemon run
"event": "chat", // bridge category: chat, game, mark, error, ...
"type": "chat", // the raw server event type
"category": "chat",
"timestamp": 1730000000123, // event receipt time, epoch milliseconds
"tick": 4211, // game tick when available, else null
"sender": "Alice", // sender identity when the event has one
"context_id": "ctx-42", // server-vantage chat context reference
"data": { "...": "the original server event, verbatim" }
}Header | Meaning |
|
|
| Unix seconds used in the signature |
| Same |
| Raw event type, for routing without parsing the body first |
| 1 for the first try, 2 for the first retry, ... |
A receiver verifies a request by checking that the signing timestamp is inside its replay window (300 seconds is the documented default) and comparing the signature in constant time. A minimal Python receiver side check:
import hashlib, hmac, time
def verify(secret, header_timestamp, raw_body, signature, window=300):
if abs(time.time() - int(header_timestamp)) > window:
return False
expected = "sha256=" + hmac.new(
secret.encode(), f"{header_timestamp}.".encode() + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)Retries and status
Network failures, truncated or malformed HTTP responses, HTTP 5xx, 408, 425
and 429 are retried with bounded exponential backoff (default 1s base, 30s
cap, 5 attempts); other statuses are reported as permanent and not retried.
Redirects are never followed - following one would turn the signed POST into
a GET and drop the body - so point the forwarder at the final receiver URL;
a 3xx is a permanent failure. A retry re-signs with a fresh timestamp but
reuses the same body and eventId. The forwarder logs every failure,
permanent rejection and queue overflow, and WebhookForwarder.status() exposes
queued, delivered, retries, failed, dropped, lastEventId and a
sanitized lastError. Logs never contain the shared secret or the full
receiver URL - the URL's path and query are redacted too, because hosted
webhook URLs often carry a token there.
Configuring a particular receiver or Harness to consume the webhook (routes, skills, credentials) is deliberately out of scope; the contract above is all a receiver needs. The event source, this forwarder and the mod are expected to run on the same machine.
Forking a live world
A save file has the blocks and the entity NBT, but not the tick order:
entities are appended to the level's tick list as chunks load, and that order
decides the result of anything computed entity by entity - pushes, cramming,
explosions. fork therefore freezes the game, asks the mod for the order, and
copies the world files; restore puts the entities back in that order under
enough guard rails that "commands issued" is not mistaken for "state restored".
# 1. What has already been snapshotted on this instance?
mc-bridge call snapshots
# 2. Fork: freeze -> save-all flush -> SNAPSHOT -> copy the world -> unfreeze
mc-bridge call fork '{"name": "before-fight", "radius": 64}'
# {
# "snapshotDir": "C:/mc-agent/snapshots/before-fight",
# "forkDir": "C:/mc-agent/snapshots/before-fight/world",
# "worldDir": "C:/.../saves/้ๅญ็กซๆนๆช",
# "manifest": {"files": 42, "bytes": 88123456, "skipped": ["advancements", "logs", ...]},
# "orderHash": "9f2c0a1b2c3d4e5f",
# "entities": 521
# }
# 3. Move forkDir into the lab instance's saves/, launch it with the mod, and
# point a bridge at that instance (here on a second API port). Dry-run first:
mc-bridge --api-port 8799 call restore '{"directory": "C:/mc-agent/snapshots/before-fight", "target": "lab", "expect_world_dir": "C:/lab/saves/world"}'
# 4. Apply under guard: endpoint + dimension + duplicate checks, freeze-first
# so the evidence cannot age, sequential summons in order, then compare full
# state (uuid order, counts, pos, vel, NBT) and restore the tick state.
mc-bridge --api-port 8799 call restore '{"directory": "C:/mc-agent/snapshots/before-fight", "target": "lab", "expect_world_dir": "C:/lab/saves/world", "dry_run": false}'
# "ok": true only when the post-restore comparison matched and the tick state
# is back ("verdict": "ok", "verified": true). With "verify": false the
# result is "verdict": "unverified", "ok": null: commands issued, but never a
# claim that the world was faithfully restored.
# 5. The hash-only acceptance test remains available, and the full-state
# comparison can be run on its own after a manual restore:
mc-bridge --api-port 8799 call order '{"directory": "C:/mc-agent/snapshots/before-fight"}'
mc-bridge --api-port 8799 call verify '{"directory": "C:/mc-agent/snapshots/before-fight"}'restore is a dry run until you say otherwise: it returns the commands, the
validation warnings and the endpoint report without sending one. The apply path
requires an endpoint proof (expect_world_dir, expect_instance or
expect_level; an explicit allow_unproven_destination=true is the only
override) and refuses a failed STATE, resolves the prior tick state before
freezing (an unreadable /tick query refuses unless prior_tick_state is
stated), freezes before taking evidence so nothing moves under it, refuses to
duplicate leftovers unless replace_existing=true kills only the detected
collisions and their drops at their own positions (never a padded volume) with
ticks running and re-freezes, and treats a summon whose entity does not appear
in the post-restore snapshot as a failure (the game's own message is attached
as evidence, never parsed). A state verdict is only ok: true when that
comparison ran and matched and the prior tick state is back; verify=false
returns unverified/null, not success. docs/restore.md has the full
workflow and the division between what the bridge automates and what the
caller still owns.
Both restore and order take a target; it is a label naming the lab
instance for the record and the throwaway snapshot names, never a router - a
bridge owns one mod connection, so prove the destination with
expect_instance/expect_world_dir/expect_level instead.
The copy is the instance's whole world directory minus the things a lab must not
inherit - session.lock, the player data (playerdata/ before 1.21, players/
in 26.2), stats/, advancements/, logs/ - so a fork keeps level.dat, the
world's data/ and datapacks/, and every dimension's region, entity and POI
files (26.2 keeps those under dimensions/<namespace>/<dimension>/). The
returned manifest says how many files and bytes landed where. The live world is
unfrozen even when a step fails, so a failed fork cannot leave the game stopped;
if only the entities are interesting, pass "regions": false.
Configuration
Variable | Default | Meaning |
| - | explicit path to the mod's |
| - | game/server directory for server-vantage discovery |
|
| host the MCP front-end dials |
|
| port the MCP front-end dials |
| - | receiver URL; forwarding needs this and a secret |
| - | shared secret for HMAC-SHA256 signing |
|
| comma separated categories, |
|
| bounded in-memory delivery queue |
|
| delivery attempts per event, first try included |
|
| base retry backoff in seconds |
|
| retry delay cap in seconds |
|
| per-request network timeout in seconds |
| - | JSON file with |
The API binds to loopback only. It can run arbitrary commands as the server's command source, so treat the machine it runs on as trusted.
Embedding the bridge
import asyncio
from mc_agent_bridge.local_api import LocalApiClient
async def main() -> None:
client = LocalApiClient(port=8765)
await client.connect()
await client.call("subscribe", {"events": ["chat", "error"]})
print(await client.call("state"))
queue = await client.events()
while True:
print(await queue.get())
asyncio.run(main())Tests
The test-suite runs against a fake mod, so no game is required:
PYTHONPATH=src python -m unittest discover -s tests -t .Protocol details
See docs/rfc/0001-agent-interface.md in the mc-agent meta repository for the
rationale, the wire format, and the open questions (event callbacks, richer
subscriptions, non-chat triggers).
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
A registry of AI agent tools โ MCP servers, APIs, CLIs, SDKs โ kept current by automated ingestion.
One AI endpoint to search and call 22k+ MCP servers; 50+ hosted tools work instantly, no key.
Connect AI agents to Flato's editable canvas runtime through a hosted MCP server.
Find, compare, and audit software for AI agents. Scored registry of tools and MCP servers.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to control a Minecraft bot for movement, building, crafting, and instant schematic-based structure spawning via MCP tools.37 npm2Apache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to control Minecraft via MCP with configurable versions, creative building commands, survival helpers, and an autonomous agent loop.Apache 2.0
- AlicenseAqualityAmaintenanceEnables AI agents to control a Minecraft bot inside a Java Edition world via MCP, providing tools for movement, building, mining, combat, farming, trading, inventory, and world perception.5537 npm1MIT
- FlicenseNot gradedqualityAmaintenanceEnables LLMs to control a Minecraft bot and interact with the game through MCP tools, including observing state, moving, mining, crafting, building, farming, managing inventory, enchanting, and chatting in-game.1-