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: 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.
Go CLI / agent loop <-> bridge daemon <-> mod <-> server
(any Harness) (no model SDK) mc-agent-interface (Fabric)See docs/toolkit.md for the install-and-use guide and the full tool reference.
Install the product
One Go binary, no Python, no Go toolchain and no MCP at run time. Supported and install-verified: Windows amd64, Linux glibc amd64, macOS arm64.
# Linux amd64 / macOS arm64
curl -fsSL https://raw.githubusercontent.com/guajun/mc-agent-bridge/v0.5.0/install/install.sh \
| sh -s -- --version 0.5.0 --skill-harness codex# Windows amd64
iwr -useb https://raw.githubusercontent.com/guajun/mc-agent-bridge/v0.5.0/install/install.ps1 -OutFile install.ps1
./install.ps1 -Version 0.5.0 -SkillHarness codexThen mc-agent version and mc-agent doctor. Upgrade, uninstall, explicit
Skill directories and the supported matrix are documented in
docs/install.md and docs/release.md.
Go runtime (bridge #11), MCP removal (bridge #12) and release packaging (bridge #13). The product CLI/daemon is the single Go binary in go/:
mc-agent daemon ...,mc-agent capabilities|state|command|events|..., the authenticated same-port TLS transport plus the explicit legacy loopback adapter, a token-protected local IPC boundary, an unknown-write ledger, and optional signed webhooks. MCP is removed: there is no MCP server, nomcpextra, nomc-bridge mcpsubcommand, and no MCP configuration in the install or smoke paths. The Python bridge insrc/mc_agent_bridge/remains the explicit legacy path (and the local fork/restore tooling until it is migrated or retired); its local API client now carries the Go daemon's IPC token. The per-operation migration table is in go/README.md. Versioned archives, checksums, the pinned Skill bundle and the tested OS/CPU matrix are documented in docs/release.md.
Related MCP server: minecraft-mcp-server
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 does not disturb the game connection; the daemon reconnects and reports gaps.
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.
There is no MCP front-end: MCP is pull-based and per-session, so it cannot wake an agent on a game event. Waking an agent is the job of an agent loop that subscribes to the daemon's event stream - the CLI and the optional webhook are the supported entries.
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 . # legacy Python daemon, local API and CLIQuick 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.
Front-ends: Go CLI (product) and the legacy Python daemon
MCP was removed in bridge #12. The product entry point is the Go binary -
see go/README.md for mc-agent daemon, the CLI commands and
the migration table. The Python mc-bridge commands in this repository
(run, call, watch, discover, forward) remain for the legacy daemon
and for local fork/restore, which the Go runtime deliberately refuses over
the remote transport. The Python local API client already speaks the Go
daemon's loopback API (including its IPC token), so a legacy Harness or an
agent loop can attach to either daemon with the same JSON-lines API.
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: the CLI, the JSON-lines API, or an agent 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 CLI and agent loops dial |
|
| port the CLI and agent loops dial |
| - | 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
Entity NBT capture
mc-agent entities [--dimension minecraft:overworld] is an authoritative
server-only read of non-player NBT in tick order. It requires the Mod's explicit
entities:nbt capability (Mod 0.9.0); older Mods/client projections are refused
rather than presented as NBT. snapshot --name NAME [--dimension ID] writes
the same records on the game host (requires snapshot:entity-nbt). Both default to overworld and remove the
implicit first-player radius; --radius is rejected even at zero. Use vanilla
commands or local analysis for ordinary filtering. record-start --radius
remains a separate legacy client sampling operation.
NBT records use schema: entity-nbt/1, retain the order/type/position/passenger
information required for sequential summon, and retain vel for existing
fidelity tools. Entity ID/yaw/pitch projections are removed. Inline captures
are subject to the negotiated frame limit (8 MiB default); they do not include
world files, non-ticking entities, or a complete hot-fork guarantee.
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 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.5564 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-
- AlicenseNot gradedqualityBmaintenanceProvides external AI agents with reliable, observable tools to inspect, navigate, and act in a Minecraft world, enabling autonomous playthroughs such as beating the game.8MIT