MMCP Specification
MUD Master Chat Protocol (MMCP)
Sources:
- Original specification mirrored at MUSHclient.com
- More recent version at Mudhalla
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
peeris any remote MUD client with which a direct MMCP TCP/IP connection has been established. - The
declaredIPv4 address and port number are the self-reported values that the remote peer transmits inside the initial handshake string; therealIPv4 address and port number are the actual values returned bygetpeernamein C sockets. Note that the declared IPv4 address may also be the literal string<Unknown>. - The
calleris the peer that initiates the TCP/IP connection; theanswereris the peer that accepts it. - The
incomingconnection means the remote peer is the caller; theoutgoingconnection means the remote peer is the answerer. - The
senderis the peer that transmits a particular command or message; thereceiveris the peer that receives and processes that command or message. - A
COMMAND BYTEis 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:
- Extract the first 5 bytes of the initial string and ensure the result is
CHAT:(upper-case only). - 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. - 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.
- 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_BLOCKdata) 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_BLOCKtransfers or spam can exhaust resources. - Message Amplification: In topologies with many interconnected peers,
CHAT_TEXT_EVERYBODYand 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.