Files
minecraft_protocol/04-status-ping.md
T
claude-timemachine d73c1c9537 minecraft_protocol: foundation + per-version protocol docs 1.7.10->26.2
8 topical docs (overview, data types, lifecycle, handshake, status/ping,
login+encryption, configuration, version-differences) + proxy-forwarding set
+ 16 per-version release-line docs, sourced from minecraft.wiki, ViaVersion
(source + commits), minecraft-data, node-minecraft-protocol, Velocity, BungeeCord.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:15:32 +02:00

14 KiB
Raw Blame History

04 — Server List Ping (SLP)

How the multiplayer server list fetches a server's MOTD, version, and player count. Covers modern SLP (1.7+) and the three legacy 0xFE variants.


1. Modern SLP (1.7+)

1.1 Packet sequence

sequenceDiagram
    participant C as Client
    participant S as Server

    C->>S: TCP connect (port 25565)
    C->>S: Handshake (0x00, nextState=1)
    C->>S: Status Request (0x00, no fields)
    S-->>C: Status Response (0x00, JSON string)
    C->>S: Ping Request (0x01, Long timestamp)
    S-->>C: Pong Response (0x01, echo Long)
    S->>C: (server closes connection)

The client may close after receiving the Status Response without sending Ping (e.g. when only scraping the MOTD). Notchian servers will wait up to ~30 s for the Ping Request before timing out if the client leaves the connection open.

1.2 Packet definitions

All packets use the standard Minecraft packet framing (VarInt length prefix + VarInt packet ID + fields).

Handshaking state → Status

Direction ID Name Fields
C→S 0x00 Handshake protocolVersion VarInt, serverAddress String(255), serverPort Unsigned Short, nextState VarInt (1=Status)

Source: minecraft.wiki/w/Java_Edition_protocol/Packets

The wiki uses Intent as the field name (as of 1.20.5+); older docs call it nextState. The value 1 always means "go to Status state."

Status state

Direction ID Name Fields
C→S 0x00 Status Request (none)
S→C 0x00 Status Response jsonResponse String(32767)
C→S 0x01 Ping Request timestamp Long
S→C 0x01 Pong Response timestamp Long (echoed)

Source: minecraft.wiki/w/Java_Edition_protocol/Packets

The Ping payload is a Long (8 bytes, signed, big-endian) — typically a millisecond epoch timestamp the client uses to measure round-trip latency. node-minecraft-protocol sends [0, 0] (two 32-bit zero words that compose one 64-bit zero) and measures wall-clock delta separately (src/ping.js:55, src/server/ping.js:62).

1.3 The Status Response JSON

The server writes the entire status as a single JSON string inside the Status Response packet.

Annotated example

{
  "version": {
    "name": "1.21.8",      // free string — shown in client UI on version mismatch
    "protocol": 772        // actual protocol number client compares
  },
  "players": {
    "max": 20,             // max player slots (can be overridden freely)
    "online": 1,           // current player count
    "sample": [            // optional list shown in hover tooltip
      {
        "name": "thinkofdeath",
        "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"  // must be well-formed UUID
      }
    ]
  },
  "description": {         // the MOTD — see §1.4 for evolution
    "text": "Hello, world!"
  },
  "favicon": "data:image/png;base64,<data>",  // 64×64 PNG, no newlines (1.13+)
  "enforcesSecureChat": false                 // added in 1.19.1
}

Source: minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping — example JSON

node-minecraft-protocol server-side builds this object at src/server/ping.js:31-39:

const response = {
  version: responseVersion,
  players: { max: server.maxPlayers, online: server.playerCount, sample: [] },
  description: server.motdMsg ?? { text: server.motd },
  favicon: server.favicon
}

Field reference

Field Type Required Notes
version.name String No (1.20+: omission shows "Old") Display label — not version-checked by client
version.protocol Integer No Client compares against its own; mismatch → red/orange label
players.max Integer No ??? shown if players omitted; max value 2³¹−1
players.online Integer No Same
players.sample Array No Objects with name (String) + id (UUID String)
description Text Component No See §1.4
favicon String No data:image/png;base64,…; exactly 64×64 px; no newlines since 1.13
enforcesSecureChat Boolean No Added 1.19.1 — client shows warning if server value differs from expectation
previewsChat Boolean No Added alongside enforcesSecureChat; deprecated/removed in later 1.19.x releases

Sources: minecraft.wiki SLP page; enforcesSecureChat version from wiki field table.

1.4 Description field — chat component evolution

The description field has gone through three forms:

Era Format Example
1.71.8 Plain string with § codes "§aA §bcoloured §cMOTD"
1.91.15 JSON Text Component object, but vanilla still embeds § codes inside {"text":"…"} {"text":"§aHello"}
1.16+ Full structured Text Component (Spigot/Paper); vanilla still uses §-embedded strings {"text":"Hello","color":"green"}

The wiki notes: "Notchian servers embed section-sign-based codes within the text value, while third-party servers such as Spigot and Paper will return full components." Both forms are valid — clients accept either.

node-minecraft-protocol's server prefers a structured object (server.motdMsg) but falls back to { text: server.motd } with a plain string (src/server/ping.js:38).

1.5 Version spoofing

version.name is a free string — the server can write anything here. version.protocol is the number the client actually compares against its own.

Common patterns:

Scenario version.name version.protocol
Matching client "1.21.8" 772
Outdated client (server newer) "1.21.8" 772 — client shows "Outdated client!"
Outdated server (client newer) "1.20.4" 765 — client shows "Outdated server!"
ViaVersion (accepts many) "Requires 1.21+ (1.7-1.20.6 supported)" client's own protocol number
Discovery ping (unknown client ver) "Paper 1.21" -1 (sentinel)

ViaVersion sets version.protocol to the connecting client's protocol number so the client always sees a match, then handles translation internally.

The wiki notes that for 1.6 legacy responses from a 1.7+ server, the protocol version in the response is always 127 (an incompatibility sentinel).


2. Legacy 0xFE Server List Ping

Pre-1.7 clients used a simple kick-packet hack. There are three variants, distinguished by how much data the client sends.

2.1 Overview

Client version Client sends Response separator
Beta 1.8 1.3.2 FE § (section sign, U+00A7)
1.4 1.5.2 FE 01 \0 (null, U+0000)
1.6.x FE 01 FA … (plugin message) \0 (null)

All responses are sent as a kick packet (0xFF) containing a UTF-16BE encoded string.

2.2 Variant A — Beta 1.8 through 1.3.2

Client → Server: single byte 0xFE

Server → Client (kick packet):

FF              packet ID
XX XX           UInt16BE: number of UTF-16 code units in the response string
[UTF-16BE data]

Response string format (fields joined with §):

<MOTD>§<online>§<max>

Example: A Minecraft Server§5§20

node-minecraft-protocol handles this as payload === undefined (neither 0 nor 1), falling into the else branch at src/server/ping.js:73:

sendPingResponse([server.motd, server.playerCount.toString(),
                  server.maxPlayers.toString()].join('\xa7'))

(\xa7 = §)

2.3 Variant B — 1.4.2 through 1.5.2

Client → Server: two bytes FE 01

Server → Client: same kick packet format, but fields joined with null (), and a §1 prefix to signal the extended format:

§1<NUL><protocol><NUL><version><NUL><MOTD><NUL><online><NUL><max>

Where <NUL> is U+0000 (null character).

node-minecraft-protocol handles this at src/server/ping.js:68-71 (packet.payload === 1):

sendPingResponse('\xa7' + [pingVersion, server.mcversion.version,
  server.mcversion.minecraftVersion, server.motd,
  server.playerCount.toString(), server.maxPlayers.toString()].join('\0'))

Note: pingVersion is the hardcoded constant 1 (the ping format version, not the Minecraft protocol version).

2.4 Variant C — 1.6.x

Client → Server (full hex):

FE                     packet ID
01                     payload
FA                     plugin message packet ID
00 0B                  string length: 11 (UTF-16BE code units)
00 4D 00 43 00 7C      "MC|PingHost" in UTF-16BE
00 50 00 69 00 6E
00 67 00 48 00 6F
00 73 00 74
XX XX                  rest-of-data byte length = 7 + len(hostname bytes)
XX                     client's protocol version (single byte)
XX XX                  hostname length in UTF-16BE code units
[hostname in UTF-16BE]
XX XX XX XX            server port (big-endian int32)

The wiki notes: "All Notchian servers only care about the first 3 bytes (FE 01 FA)." The hostname/port data is informational and typically ignored.

Server → Client — same kick packet, same null-delimited format as variant B:

FF
XX XX                  UInt16BE length
00 A7 00 31 00 00      U+00A7 "§" + U+0031 "1" + U+0000 null separator
[UTF-16BE: protocol<NUL>version<NUL>MOTD<NUL>online<NUL>max]

For a 1.7+ server responding to a 1.6 client, protocol is always 127 (incompatibility sentinel). Source: minecraft.wiki SLP — Legacy ping

2.5 Response wire encoding

All legacy responses share the same packet structure (src/server/ping.js:77-93):

function sendPingResponse (responseString) {
  function utf16be (s) {
    return endianToggle(Buffer.from(s, 'utf16le'), 16)  // swap to big-endian
  }
  const responseBuffer = utf16be(responseString)
  const length = responseString.length                  // char count, not byte count
  const lengthBuffer = Buffer.alloc(2)
  lengthBuffer.writeUInt16BE(length)
  const raw = Buffer.concat([Buffer.from('ff', 'hex'), lengthBuffer, responseBuffer])
  client.socket.write(raw)                              // bypasses packet framer
}

The response is written directly to the socket (not through the packet framer) because the length field counts UTF-16 code units, not bytes, and the normal framer would prepend a VarInt length which legacy clients don't expect.

2.6 Detection on the server

A 1.7+ server recognises a legacy ping by the 0xFE first byte, which is not a valid VarInt-prefixed packet. node-minecraft-protocol maps this to the legacy_server_list_ping event and distinguishes variants by packet.payload:

packet.payload Variant
undefined Beta 1.81.3.2 (FE only)
1 1.41.5.2 (FE 01) or 1.6.x (FE 01 FA …)

Source: src/server/ping.js:67-93


3. Protocol number reference

A selection of protocol numbers for version context:

Release Protocol
1.7.10 5
1.8.9 47
1.12.2 340
1.16.5 754
1.19.1 760
1.20.4 765
1.21.1 767
1.21.8 772 (example from wiki)

Current protocol: 775 (as of the Packets page at time of writing). Source: minecraft.wiki/w/Java_Edition_protocol/Packets


4. Sources

Source Used for
minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping JSON field table, legacy ping bytes, response formats, favicon 1.13 note, §1 sentinel
minecraft.wiki/w/Java_Edition_protocol/Packets Packet IDs, field types, Handshake Intent field name
node-minecraft-protocol src/ping.js Client-side modern SLP flow (lines 41-68)
node-minecraft-protocol src/server/ping.js Server-side response construction (lines 1-94)

5. VERIFY flags (open questions)

  1. <!-- VERIFY --> enforcesSecureChat exact version — wiki does not specify version inline; attributed to 1.19.1 based on the 1.19.1 secure-chat feature rollout. Confirm against wiki/w/Java_Edition_1.19.1.
  2. <!-- VERIFY --> previewsChat add/remove versions — present in 1.19.1 initial rollout, removed or no-op'd by 1.19.3. Confirm.
  3. <!-- VERIFY --> nextStateIntent field rename version — wiki Packets page uses "Intent" as of current; confirm when the rename happened (cosmetic doc change, wire format unchanged).
  4. <!-- VERIFY --> ViaVersion protocol echo vs 1 — confirm ViaVersion echoes the connecting client's protocol number (rather than using a fixed sentinel).
  5. <!-- VERIFY --> Protocol 772 vs 775 — wiki SLP example JSON shows "protocol": 772 but Packets page header shows current 775; resolve to exact MC versions.
  6. <!-- VERIFY --> Description plain-string support in 1.7 — confirm that "description": "plain string" (no wrapping object) was accepted by the official client in 1.7.x.