Connection states
How a connection moves from handshake to play, and how the library tracks it.
A Minecraft connection is a small state machine. The same packet ID means different things in different states, so both peers must agree on where they are. mc-protocol tracks this for you and switches automatically when it sees the lifecycle packets below.
+-----------+ Intension(1) +--------+
| handshake | ---------------> | status |
+-----------+ +--------+
|
| Intension(2 or 3)
v
+-----------+ LoginAcknowledged +--------+ FinishConfiguration +------+
| login | ------------------> | config | --------------------> | play |
+-----------+ +--------+ +------+
^ |
| StartConfiguration / |
+--- ConfigurationAcknowledged +
The five states
| State | Purpose | Packets |
|---|---|---|
handshake |
Declares protocol version, target host, and where to go next. Also includes the legacy server-list ping. | 2 packets |
status |
Server list ping: description, player count, latency. No login. | 4 packets |
login |
Username exchange, optional encryption, compression setup. | 11 packets |
config |
Registries, tags, feature flags, resource packs. Everything the client needs before it can render a world. | 30 packets |
play |
The game. Chunks, entities, movement, chat, inventory. | 210 packets |
Two directions, tracked separately
Each Client has an incomingState and an outgoingState. Most of the time they match, but during the play → config round trip they briefly differ: the server sends StartConfiguration and switches its outgoing state to config, while its incoming state stays play until the client answers with ConfigurationAcknowledged.
client.on("stateChange", ({ direction, from, to }) => {
console.log(`${direction}: ${from} -> ${to}`);
});
Transitions the library performs
These happen inside Client whenever the packet is sent or received, on either side of the connection:
| Packet | Effect |
|---|---|
Intension |
nextState 1 → status; 2 or 3 → login. Also picks the packet tables for protocolVersion; see Versions. |
Hello (serverbound) |
Records username and uuid on client.session from either the write names (username / playerUUID) or the parsed names (name / uuid). |
LoginFinished |
Records the game profile on client.session. |
LoginCompression |
Enables zlib compression at the given threshold for both directions. Emits compression. |
LoginAcknowledged |
→ config. Sets client.loggedIn = true and emits login. |
FinishConfiguration (serverbound) |
→ play. Sets client.joined = true and emits playerJoin the first time. |
StartConfiguration |
Server’s outgoing / client’s incoming → config. |
ConfigurationAcknowledged |
Server’s incoming / client’s outgoing → config. AcknowledgeConfiguration is accepted as the same packet name. |
You never call a “set state” method. Send or receive the right packet and the state follows.
Packet lookup depends on state
sendPacket("KeepAlive", …) resolves the name against the current outgoing state. KeepAlive exists in both config and play with different IDs; the library picks the right one. If the name does not exist in the current state you get:
Error: Unknown packet: play/clientbound/StatusResponse
The string tells you which state and bound it searched, which is usually enough to spot a sequencing mistake.