Skip to Content

MMCP Specification

MUD Master Chat Protocol (MMCP)

Sources:

MMCP is a decentralized chat protocol that allows MUD clients to communicate out-of-band with each other over a TCP/IP connection.

Terminology

  • A peer is any remote MUD client with which a direct MMCP TCP/IP connection has been established.
  • The declared IPv4 address and port number are the self-reported values that the remote peer transmits inside the initial handshake string; the real IPv4 address and port number are the actual values returned by getpeername in C sockets. Note that the declared IPv4 address may also be the literal string <Unknown>.
  • The caller is the peer that initiates the TCP/IP connection; the answerer is the peer that accepts it.
  • The incoming connection means the remote peer is the caller; the outgoing connection means the remote peer is the answerer.
  • The sender is the peer that transmits a particular command or message; the receiver is the peer that receives and processes that command or message.
  • A COMMAND BYTE is a single byte value in the range 1-254 that begins a standard chat data block.
  • The END OF COMMAND (CHAT_END_OF_COMMAND) is a single byte value of 255 that terminates a standard chat data block.

Normative Language

The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, NOT RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in BCP 14 RFC 2119 RFC 8174 when, and only when, they appear in all capitals, as shown here.

Establishing a Connection

Initial Handshake From the Caller

Format:

CHAT:<chat name>\n<IPv4 address><port number>

Sprintf syntax:

CHAT:%s\n%s%-5u

When a TCP/IP connection is established, the caller begins a handshake sequence with the answerer by sending an initial string. This consists of the literal string CHAT:, followed by the caller’s chat name, a line feed (\n), and the caller’s declared IPv4 address immediately followed by the caller’s declared port number. The declared IPv4 address and port number are concatenated without a separator and can differ from the caller’s real IPv4 address and port number. The IPv4 address is a variable length, dotted-decimal string or the literal string <Unknown>. The port number is a fixed width, left-aligned value which occupies the final 5 bytes of the handshake string, padded with trailing spaces if necessary.

Note that client implementers MUST remove any line feed (\n) bytes from chat names since they are derived from user input; the handshake will fail otherwise.

After sending the initial string, the caller waits for a response from the answerer.

Handshake Response From the Answerer

Upon detecting an incoming connection from the caller, the answerer accepts the socket and waits for the initial CHAT: string. The answerer then validates the string (see below) and responds to the caller with one of the following messages:

  • YES:<chat name>\n: this is the acceptance message; it MUST be sent by the answerer to accept the caller when validation succeeds. Note that <chat name> in this context refers to the answerer’s chat name.
  • NO (without a newline byte): this is the rejection message; it SHOULD be sent by the answerer when validation fails and before the answerer closes the socket.

When parsing the initial handshake string for validation, the answerer MUST obtain the port number by reading the final 5 bytes of the string and trimming any trailing spaces. It MUST also use the position of the line feed byte (\n) as the anchor for extracting the chat name and IPv4 address.

It is recommended for the answerer to use the following sequence of steps when validating the initial string from the caller:

  1. Extract the first 5 bytes of the initial string and ensure the result is CHAT: (upper-case only).
  2. Extract the bytes after the CHAT: until the line feed byte (\n) is encountered; this is the chat name. The result MUST NOT contain any tilde (~) bytes.
  3. Extract the final 5 bytes of the string and trim any trailing spaces from the result; this is the port number. Ensure the result is not empty and contains numeric values only.
  4. Extract the bytes between the line feed byte (\n) and the port number; this is the IPv4 address. Ensure the result is a valid IPv4 dotted-decimal address or the literal string <Unknown>; it MUST NOT contain any tilde (~) or comma (,) bytes.

Post-Handshake

Upon successful validation of the initial handshake string, the answerer MAY send its version using a CHAT_VERSION command (described below); the caller MAY do the same upon receiving the acceptance message from the answerer.

Note that version exchange is not required during handshake and MAY occur at any time. However, it is RECOMMENDED that the caller and answerer perform the version exchange immediately after a successful handshake.

Chat Data Blocks

Format:

<COMMAND BYTE><data><END OF COMMAND>

A standard chat data block begins with a COMMAND BYTE (see below), followed by variable-length data, and ends with the CHAT_END_OF_COMMAND byte. Command boundaries are determined solely by the CHAT_END_OF_COMMAND byte. The only exceptions are the initial handshake exchange and CHAT_FILE_BLOCK data blocks, which omit the CHAT_END_OF_COMMAND byte.

Command Byte Values

In the following table, Portable indicates commands intended to function across multiple client implementations.

Command Name Byte Value Notes
CHAT_NAME_CHANGE 1 Portable; all clients SHOULD support.
CHAT_REQUEST_CONNECTIONS 2 Portable; all clients SHOULD support.
CHAT_CONNECTION_LIST 3 Portable; all clients SHOULD support.
CHAT_TEXT_EVERYBODY 4 Portable; all clients SHOULD support.
CHAT_TEXT_PERSONAL 5 Portable; all clients SHOULD support.
CHAT_TEXT_GROUP 6 Portable; all clients SHOULD support.
CHAT_MESSAGE 7 Portable; all clients SHOULD support.
CHAT_DO_NOT_DISTURB 8 Portable; most clients lack support.
CHAT_SEND_ACTION 9 Not portable; used by Mud Master.
CHAT_SEND_ALIAS 10 Not portable; used by Mud Master.
CHAT_SEND_MACRO 11 Not portable; used by Mud Master.
CHAT_SEND_VARIABLE 12 Not portable; used by Mud Master.
CHAT_SEND_EVENT 13 Not portable; used by Mud Master.
CHAT_SEND_GAG 14 Not portable; used by Mud Master.
CHAT_SEND_HIGHLIGHT 15 Not portable; used by Mud Master.
CHAT_SEND_LIST 16 Not portable; used by Mud Master.
CHAT_SEND_ARRAY 17 Not portable; used by Mud Master.
CHAT_SEND_BARITEM 18 Not portable; used by Mud Master.
CHAT_VERSION 19 Portable; all clients SHOULD support.
CHAT_FILE_START 20 Portable; all clients SHOULD support.
CHAT_FILE_DENY 21 Portable; all clients SHOULD support.
CHAT_FILE_BLOCK_REQUEST 22 Portable; some clients MAY support.
CHAT_FILE_BLOCK 23 Portable; some clients MAY support.
CHAT_FILE_END 24 Portable; some clients MAY support.
CHAT_FILE_CANCEL 25 Portable; some clients MAY support.
CHAT_PING_REQUEST 26 Portable; all clients SHOULD support.
CHAT_PING_RESPONSE 27 Portable; all clients SHOULD support.
CHAT_PEEK_CONNECTIONS 28 Portable; all clients SHOULD support.
CHAT_PEEK_LIST 29 Portable; all clients SHOULD support.
CHAT_SNOOP_START 30 Portable; some clients MAY support.
CHAT_SNOOP_DATA 31 Portable; some clients MAY support.
CHAT_SNOOP_COLOR 32 Not portable; used by Mud Master.
CHAT_SEND_SUBSTITUTE 33 Not portable; used by Mud Master.
CHAT_SIDE_CHANNEL 40 Not portable; used by Mudlet.
CHAT_CHANNEL_DATA 240 Not portable; used by Mudlet.
CHAT_END_OF_COMMAND 255 Terminates all MMCP commands.

MMCP Commands

CHAT_NAME_CHANGE (1)

Sender

Format:

<CHAT_NAME_CHANGE><new name><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%s%c

Informs connected peers that the sender’s name has changed. The new sender name MUST NOT contain any tilde (~) or line feed (\n) bytes and MUST be broadcast by the sender to all active peers.

Receiver

Replace the stored name for the peer with the new name; any tilde (~) or line feed (\n) bytes MUST first be removed from the new name.

CHAT_REQUEST_CONNECTIONS (2)

Sender

Format:

<CHAT_REQUEST_CONNECTIONS><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%c

Requests the receiver’s public connections for automatic outgoing connections.

Receiver

Respond with a CHAT_CONNECTION_LIST containing all public connections.

CHAT_CONNECTION_LIST (3)

Sender

Format:

<CHAT_CONNECTION_LIST><IP/port list><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%s%c

Sends a comma-delimited list of public connections (IP address and port pairs).

List format:

<ip1>,<port1>,<ip2>,<port2>,...

Example:

28.25.102.48,4050,100.284.27.65,4000,<Unknown>,4050

Connection lists MUST conform to the following rules:

  • If no public connections exist, an empty list SHOULD be sent.
  • The list MUST NOT end with a comma (,).
  • The IPv4 address field MUST NOT be empty.
  • The IPv4 address MUST NOT contain any tilde (~) or comma (,) bytes.
  • An unknown IPv4 address MUST be represented by the literal string <Unknown> (case-sensitive, including the angle brackets).
  • The port number field MUST NOT be empty.
  • The port number MUST NOT be padded.
  • The port number field MUST be numeric only.

Receiver

Parse the list and attempt outbound connections to all valid entries.

CHAT_TEXT_EVERYBODY (4)

Sender

Format:

<CHAT_TEXT_EVERYBODY><text><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%s%c

Example:

\n%s chats to everybody, '%s'\n

Broadcasts a message to all peers. The sender MUST generate the complete display text, including newlines and prefix.

Receiver

If the sender is not being ignored, display the text.

The message MUST be relayed to other peers according to the following rules:

  • If the message arrives on an incoming connection (i.e., the remote peer is a caller): relay to all other connections (both incoming and outgoing) except the original sender.
  • If the message arrives on an outgoing connection (i.e., the remote peer is an answerer): relay only to incoming connections (callers being served by the receiver).

Note that the relayed message MUST NOT be sent back to the connection from which it was received.

CHAT_TEXT_PERSONAL (5)

Sender

Format:

<CHAT_TEXT_PERSONAL><text><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%s%c

Example:

\n%s chats to you, '%s'\n

Sends a private message to the receiver. The sender MUST generate the complete display text, including newlines and prefix.

Receiver

If the sender is not being ignored, display the text.

CHAT_TEXT_GROUP (6)

Sender

Format:

<CHAT_TEXT_GROUP><group name><text><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%-15s%s%c

Example:

\n%s chats to the group, '%s'\n

Sends a message to a named group. The sender MUST generate the complete display text, including newlines and prefix. Before sending the message, the sender MUST ensure that the group name is left-aligned and padded with trailing spaces to exactly 15 bytes.

Receiver

If the sender is not being ignored, display the name and text of the group message. The group name is extracted by reading the first 15 bytes of the message and trimming any trailing spaces; the remainder of the message is the text.

The message MUST be relayed to other peers according to the following rules:

  • If the message arrives on an incoming connection (i.e., the remote peer is a caller): do not relay.
  • If the message arrives on an outgoing connection (i.e., the remote peer is an answerer): relay only to incoming connections (callers being served by the receiver). This is subject to local group filtering (see note below).

Note that the relayed message MUST NOT be sent back to the connection from which it was received.

Note (Implementation-specific behavior in MUSHclient)

MUSHclient loops through its connections and only sends the message to the connections the user manually tagged with that group name (case-insensitive), regardless of whether the user composed the group chat message or is simply relaying it. Group membership in MUSHclient is therefore asymmetric. Alice may have Bob tagged as “Warriors” while Bob has Alice tagged as “Friends”. If this happens, Alice sending a group chat message to “Friends” or Bob sending a group chat message to “Warriors” will result in the message failing to send.

CHAT_MESSAGE (7)

Sender

Format:

<CHAT_MESSAGE><message><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%s%c

Example:

\n<CHAT> %s has refused your connection because your name is too long.\n

Sends a system/notification message. The sender MUST generate the complete display text, including newlines and prefix.

Receiver

Display the message.

CHAT_VERSION (19)

Sender

Format:

<CHAT_VERSION><version string><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%s%c

Sends client name and version string.

CHAT_PING_REQUEST (26)

Sender

Format:

<CHAT_PING_REQUEST><timing data><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%s%c

Sends timing data (such as a Unix timestamp). The receiver MUST return the data unmodified.

Receiver

Return the timing data unmodified to the sender using the CHAT_PING_RESPONSE command (see below).

CHAT_PING_RESPONSE (27)

Sender

Format:

<CHAT_PING_RESPONSE><timing data><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%s%c

Returns the unmodified timing data to the sender of a CHAT_PING_REQUEST command.

Receiver

Use the returned timing data to calculate the round-trip time.

CHAT_PEEK_CONNECTIONS (28)

Sender

Format:

<CHAT_PEEK_CONNECTIONS><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%c

Requests the receiver’s public connections for viewing only.

Receiver

Respond with a CHAT_PEEK_LIST containing all public connections.

CHAT_PEEK_LIST (29)

Sender

Format:

<CHAT_PEEK_LIST><list><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%s%c

Sends a tilde-delimited list of public connections (IP, port, name triples).

List format:

<ip1>~<port1>~<name1>~<ip2>~<port2>~<name2>~...

Example:

204.285.28.18~4050~Omawarisan~<Unknown>~4050~Baalzebul~

Peek lists MUST conform to the following rules:

  • If no public connections exist, an empty list SHOULD be sent.
  • The list MUST end with a tilde (~) unless it is empty.
  • The IPv4 address field MUST NOT be empty.
  • The IPv4 address MUST NOT contain any tilde (~) or comma (,) bytes.
  • An unknown IPv4 address MUST be represented by the literal string <Unknown> (case-sensitive, including the angle brackets).
  • The port number field MUST NOT be empty.
  • The port number MUST NOT be padded.
  • The port number field MUST be numeric only.
  • The name field MUST NOT be empty.
  • The name MUST NOT contain any tilde (~) or line feed (\n) bytes.

Receiver

Display the list; do not attempt connections.

CHAT_SNOOP_START (30)

Sender

Format:

<CHAT_SNOOP_START><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%c

Toggles snooping on the receiver. When snooping is enabled, the receiver forwards all text it sees from the MUD to the sender. The receiver MAY choose whether to honor the request.

Receiver

Decide whether to honor the snooping request. Notify the sender of the decision using a CHAT_MESSAGE command. If snooping is enabled, forward all data seen on the MUD to the sender using CHAT_SNOOP_DATA commands until snooping is toggled off.

CHAT_SNOOP_DATA (31)

Sender

Format:

<CHAT_SNOOP_DATA><message><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%s%c

Sends MUD output to the snoop recipient.

Receiver

Display the message. Never forward snoop data to avoid loops.

File Transfer Commands

CHAT_FILE_START (20)

Sender

Format:

<CHAT_FILE_START><filename>,<length><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%s,%lu%c

Initiates a file transfer. The filename must be the base name only; the length is the file size in bytes.

Receiver

Validate sender, filename, and length. Reject with CHAT_FILE_DENY if unacceptable. Otherwise accept and immediately request the first block with CHAT_FILE_BLOCK_REQUEST.

CHAT_FILE_DENY (21)

Sender

Format:

<CHAT_FILE_DENY><reason><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%s%c

Denies an incoming file transfer and sends the reason for the denial.

Receiver

Display the reason and clean up any partial files.

CHAT_FILE_BLOCK_REQUEST (22)

Sender

Format:

<CHAT_FILE_BLOCK_REQUEST><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%c

Requests the next block (receiver-driven transfer).

Receiver

Send next file block via CHAT_FILE_BLOCK. On EOF, send CHAT_FILE_END instead.

CHAT_FILE_BLOCK (23)

Sender

Format:

<CHAT_FILE_BLOCK><data>

Sends a file block, padded with trailing null bytes if necessary. A file block MUST be exactly 500 bytes long. This command SHALL NOT be terminated with a CHAT_END_OF_COMMAND byte because the file block size is fixed, so the message length is known.

Note that file block data is binary and MAY contain any byte value including 255.

Receiver

Write the data, then request the next block using CHAT_FILE_BLOCK_REQUEST.

Note that the receiver MUST use the cumulative length from CHAT_FILE_START to determine where the data ends, because the end of the data MAY come before the end of the final block.

CHAT_FILE_END (24)

Sender

Format:

<CHAT_FILE_END><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%c

Signals completion of a file transfer.

Receiver

Close the completed file.

CHAT_FILE_CANCEL (25)

Sender

Format:

<CHAT_FILE_CANCEL><CHAT_END_OF_COMMAND>

Sprintf syntax:

%c%c

Aborts an in-progress file transfer (used by either side).

Implementation Notes

Character Encoding

The protocol is byte-oriented and does not mandate a character encoding. Historically, most implementations used ASCII or the local 8-bit code page.

The original Mud Master program restricted chat names to printable ASCII characters 32-122 inclusive, excluding {, |, }, and ~. It is RECOMMENDED that new implementations use ASCII character encoding and apply the Mud Master chat name restrictions for compatibility with all existing implementations.

String Sanitization Recommendations

  • Remove or replace all byte values of 255 in user-controlled strings (except CHAT_FILE_BLOCK data) to prevent protocol stream corruption due to malformed commands.
  • Strip ANSI/VT100 terminal escape sequences from user input to avoid terminal interference.
  • Remove line feeds (\n) from chat names and group names.
  • Remove or replace tildes (~) in chat names to avoid delimiter collision and preserve CHAT_PEEK_LIST parsing.

Chat Name Length

Original Mud Master allowed <= 30 bytes; TinTin++ enforces <= 20. Use the stricter limit of 20 for maximum compatibility.

Connection List vs. Peek List Formatting

Connection lists use commas as separators and never end with a comma. Peek lists use tildes and always terminate with one tilde.

IPv6 Support

The protocol does not support IPv6. All addresses exchanged in the handshake and connection lists use IPv4 dotted-decimal format or the literal string <Unknown>. A declared address may differ from the actual socket address due to firewall or NAT handling.

Message Loops

The protocol lacks built-in loop detection. In complex topologies, this can lead to infinite message relaying. MUSHclient mitigates this by discarding recently sent messages for a short period (currently 5 seconds). It is RECOMMENDED that relay-only implementations replace IPv4 addresses in the connection and peek lists with <Unknown> and apply rate limiting.

Security Considerations

MMCP is an unauthenticated, unencrypted, plaintext protocol. Implementations SHOULD be aware of the following risks:

  • Spoofing: Any peer can claim any chat name or use <Unknown> for IP. There is no verification of the declared address against the real socket.
  • Denial of Service: No limits on message size, file transfers, or connection-list spam. Large CHAT_FILE_BLOCK transfers or spam can exhaust resources.
  • Message Amplification: In topologies with many interconnected peers, CHAT_TEXT_EVERYBODY and relayed messages can loop or multiply.
  • Protocol Corruption: User-controlled strings containing byte 255 (CHAT_END_OF_COMMAND) can break parsing (already addressed in sanitization recommendations).

Recommendations:

  • Only accept connections from trusted peers or implement local ignore/ban lists.
  • Enforce reasonable limits on file size (e.g., 10-50 MiB) and connection-list length.
  • Consider message deduplication (e.g., discard recently relayed messages for 5-10 seconds, as MUSHclient does).
  • For sensitive use cases, tunnel MMCP over TLS or SSH (non-standard extension).

Implementations that add security extensions should document them clearly as non-standard.