Repository navigation
Plugin Data Protocol
Mumcord clients talk to each other through Mumble's standard plugin data channel (PluginDataTransmission, Mumble 1.4 and newer). This page is the overview: what plugin data is, the rules every message follows, and the limits. Each flow and each data packet has its own page, linked below.
Audience: developers of bots and integrations. Everything here works on an unmodified, standard Mumble server; Mumcord uses no private protocol extensions and nothing on the server side.
The source of truth is the code. Each page names the files that define the format.
A flow is a conversation between clients: who sends what to whom, in which order, with which timeouts and error cases. Each page has a sequence diagram.
| Flow | What it does | Packets |
|---|---|---|
| Flow-Hello | Clients recognise each other and exchange features | hello |
| Flow-Now-Playing | Share the music track being played | nowplaying |
| Flow-Now-Gaming | Share the game being played, with a join link | nowgaming |
| Flow-History-Sync | Fetch missed channel messages from other clients, in chunks | history |
| Flow-Direct-Connection | Set up a direct encrypted QUIC connection and its streams | p2p |
| Flow-Full-Image | Download the full-size version of a chat image | image, p2p |
| Flow-Watch-Party | YouTube watch party with a shared playlist | watch |
| Flow-Screen-Share | Share a screen with up to 8 viewers | screen, p2p |
A packet page describes one dataID: fields and types, message kinds, example payloads with fake values, version rules and limits.
dataID |
Page | Encoding | Version sent | Versions accepted | Sent to | Feature gate | Defined in |
|---|---|---|---|---|---|---|---|
mumcord.hello |
Packet-mumcord-hello | JSON | 1 | 1 and higher | everyone on the server, later joiners | none | crates/engine/src/mumcord.rs |
mumcord.nowplaying |
Packet-mumcord-nowplaying | JSON | 1 | 1 and higher | everyone on the server | none | crates/engine/src/now_playing/payload.rs |
mumcord.nowgaming |
Packet-mumcord-nowgaming | JSON | 1 | 1 and higher | everyone on the server | none | crates/engine/src/now_gaming/payload.rs |
mumcord.image |
Packet-mumcord-image | binary | none | kinds 1 to 5 | one person (the image's sender) | full-image |
crates/engine/src/full_image/wire.rs |
mumcord.history |
Packet-mumcord-history | JSON header + binary body | 1 | 1 and higher | everyone on the server, then single holders | none | crates/engine/src/history_sync/wire.rs |
mumcord.p2p |
Packet-mumcord-p2p | JSON | 1 | 1 and higher | one person | p2p |
crates/p2p/src/signal.rs |
mumcord.watch |
Packet-mumcord-watch | JSON | 2 | 1 and higher | Mumcord users in the own channel | watch-party |
crates/engine/src/watch_party.rs |
mumcord.screen |
Packet-mumcord-screen | JSON | 1 | 1 and higher | Mumcord users in the own channel | screen-share |
crates/engine/src/screen_share/signal.rs |
Page names use dashes instead of dots (Packet-mumcord-hello for mumcord.hello) because dots are awkward in wiki page names.
A plugin data message is the Mumble control message PluginDataTransmission (message type 26 in Mumble.proto):
| Field | Number | Meaning |
|---|---|---|
senderSession |
1 | Set by the server to the real sender. Clients never trust a sender named inside a payload. |
receiverSessions |
2 | The sessions the server forwards the message to. There is no "whole channel" or "whole server" target; the sender lists every receiver. |
data |
3 | The payload bytes (JSON or binary, see each id). |
dataID |
4 | A string naming the payload type, here always mumcord.<name>. |
The server does not look into data. It only forwards it.
A standard Mumble server (murmur / mumble-server) enforces these limits and silently drops anything above them:
-
Size: at most 1000 bytes of
dataper message and at most 100 bytes for thedataID. Mumcord's encoders always stay at or below 1000 bytes; longer content is chunked or shortened. -
Rate: plugin data has its own bucket per user: 4 messages per second with a burst of 15 (murmur defaults
pluginmessagelimitandpluginmessageburst). Excess messages are dropped silently, and the bucket drains only in whole tokens. Mumcord has a central pacer for all plugin data: a burst of 12, then at least 300 ms between messages, which stays below the limit and leaves room for small messages like playback commands. Because a dropped message is not reported, the two features that announce themselves to newcomers (screen shareshare, watch partystate) send the announcement a second time about 3 seconds later.
A bot that sends faster than the limit loses messages without any error. Pace your sends.
-
Versioning. Every JSON payload has a version field
v. Binary payloads are versioned by their message kinds. The table above says which versions each id accepts. -
Unknown fields are ignored. Receivers read the fields they know and skip the rest, so newer versions can add optional fields. A message of an unknown type (
t) or an unknown binary kind is dropped as a whole. - Feature gating. Most Mumcord-only messages are sent only to people who announced the matching feature in their hello (see Flow-Hello). Official clients never announce anything, so they never receive these messages.
- Untrusted input. Everything received comes from another client and is untrusted. Mumcord checks the size before parsing, caps every list and string, validates ids, links and addresses, sanitizes text before it is shown, and ignores messages from people who are not where they would have to be (for example not in the same channel). Bots and other clients must do the same.
- Own messages. The server does not echo a message back to its sender. A client that wants to show its own state does so locally.
Every id accepts version 1 and higher; only v below 1 or a message missing required fields is dropped. Unknown fields are ignored. The rule for changes: a minor addition (a new optional field) keeps v at 1, so older clients keep working; a breaking change with different meaning gets a new dataID (or a new feature name), and clients send both during the transition.
Every plugin data message is at most 1000 bytes of data (PLUGIN_DATA_MAX, crates/protocol/src/client.rs). Bulk features cut their content into packets of about 900 body bytes (BODY_BUDGET = 1000 - 100 for the header) or less. The server drops anything larger.
| Area | Limit | Value | Source |
|---|---|---|---|
| Any plugin data | Bytes per message | 1000 | protocol/src/client.rs |
| Sending pace | Bulk packets | central pacer: burst 12, then 300 ms apart | see Server limits above |
| Now playing | Title, artist, album | 120 characters each | now_playing/payload.rs |
| Now playing | Cover link | 300 bytes | now_playing/payload.rs |
| Now gaming | Game name | 100 characters | now_gaming/payload.rs |
| Hello | Features | 16 names, 32 characters each | mumcord.rs |
| History sync | Messages per request | 500 (MAX_MESSAGES) |
history_sync/wire.rs |
| History sync | Body per stored message | 8 KiB (MAX_BODY); larger ones such as inline images are not synced |
history_sync/core.rs |
| History sync | Message stream per answer | 256 KiB (MAX_STREAM) |
history_sync/core.rs |
| History sync | Hash length | 8 bytes (HASH_LEN) |
history_sync/wire.rs |
| History sync | Hashes per chunk | 112 (900 / 8) | history_sync/wire.rs |
| History sync | Holders asked | 3 (MAX_HOLDERS) |
history_sync/core.rs |
| History sync | Answers prepared at once | 3 (MAX_SERVES) |
history_sync/core.rs |
| History sync | Age | 7 days | history_sync/core.rs |
| Full image | Image bytes per chunk | 960 | full_image/wire.rs |
| Full image | Image size | 512 KiB, 3840 pixels per side | full_image/check.rs |
| Full image | Requests waiting at the sender | 4 | full_image/transfer.rs |
| Full image | Requests per person per minute | 6 | full_image/transfer.rs |
| Full image | Downloads at once | 3 | full_image/transfer.rs |
| Direct connection | Candidates per offer | 8 | p2p/src/signal.rs |
| Watch party | Playlist entries | 50 | watch_party/playlist.rs |
| Watch party | Tracked participants | 32 | watch_party.rs |
| Watch party | Playlist edit bundle | split to fit 1000 bytes, sent at most once per second | watch_party/playlist.rs |
| Screen share | Second share to a newcomer |
after about 3 seconds (REANNOUNCE) |
screen_share/state.rs |
| Watch party | Second state to a newcomer |
after about 3 seconds (REANNOUNCE) |
watch_party.rs |
| Screen share | Viewers per sharer | 8 (the ninth gets full) |
screen_share/signal.rs |
| Screen share | Video frame payload | 4 MiB | screen_share/wire.rs |
| Screen share | Audio packet payload | 4 KiB | screen_share/wire.rs |
| Screen share | Picture size | 7680 x 4320 | screen_share/wire.rs |
-
Positional audio uses Mumble's standard plugin context and identity fields (
UserState), the same as the official Mumble client. - Profiles, avatars, friends, following channels use standard Mumble features (comments, textures, certificates, channel listening) or are local only. There is no plugin data for them, so there is no "request a profile" message.
A bot is just another Mumble client, so it can take part in any of these protocols with a Mumble library that can send and receive PluginDataTransmission. Mumcord cannot tell a bot from a Mumcord client; it treats both as untrusted.
Ideas:
-
Be recognised. Send a
mumcord.helloto everyone after connecting and to people who join. Announce only features you really implement. -
Announce now playing. A music bot can send
mumcord.nowplayingto everyone on the server whenever its track changes and every 30 seconds, so Mumcord users see the track, progress bar and cover on the bot. -
Announce a game server. Send
mumcord.nowgamingwith asteam://connect/link, so Mumcord users get a "Join game" button. -
Follow or control a watch party. Announce
watch-party, sit in the channel, readstart,state,cmdandplmessages to log what is being watched, orjoinand sendpledits to add videos to the playlist (for example from a chat command). -
Read share announcements. Announce
screen-shareand readsharemessages to post "Alice started sharing" in a log. Watching the stream itself needs a full QUIC client with Mumble certificate checks and a VP9 decoder. -
Serve history. A bot that logs a channel could answer
mumcord.historyrequests, so Mumcord users get earlier messages even when no other Mumcord user is online. It must follow the same privacy rules (channel messages only, only before the requester arrived) and the hash format exactly.
Rules for bots:
- Stay below the server rate limit (4 messages per second, burst 15, whole tokens only) and the 1000-byte size limit.
- Send Mumcord-only messages only to people who announced the matching feature.
- Validate everything you receive: sizes, versions, ids, links, addresses and session lists.
- Keep the user's privacy: do not log or forward chat contents, addresses or listening and gaming data beyond what your users expect.
Pseudo-code for a library that exposes the generated Mumble.proto messages:
import json
def send_plugin_data(conn, receivers, data_id, payload_bytes):
if len(payload_bytes) > 1000:
raise ValueError("standard servers drop plugin data over 1000 bytes")
msg = MumbleProto.PluginDataTransmission()
msg.receiverSessions.extend(receivers) # every receiver must be listed
msg.dataID = data_id
msg.data = payload_bytes
conn.send_message(26, msg) # 26 = PluginDataTransmission
def others(conn):
return [s for s in conn.users if s != conn.own_session]
# After the connection synced: say hello and announce the bot's track.
hello = {"app": "mumcord", "v": 1, "features": ["now-playing"]}
send_plugin_data(conn, others(conn), "mumcord.hello", json.dumps(hello).encode())
track = {"v": 1, "title": "Example Song", "artist": "Example Artist",
"pos_ms": 0, "dur_ms": 215000, "playing": True, "ts": now_unix_ms()}
send_plugin_data(conn, others(conn), "mumcord.nowplaying", json.dumps(track).encode())
# Receiving: the server sets the real sender.
def on_plugin_data(msg):
if msg.dataID != "mumcord.watch" or len(msg.data) > 1000:
return
if msg.senderSession not in users_in_my_channel():
return
try:
packet = json.loads(msg.data)
except ValueError:
return
if packet.get("v", 0) >= 1 and packet.get("t") == "start":
log_video(packet.get("video")) # validate: 11 chars of A-Z a-z 0-9 - _Nothing. Official Mumble clients ignore plugin data with ids they do not know, and Mumcord sends feature-gated messages only to people who announced the feature, which official clients never do. Hellos, now playing, now gaming and history requests do reach official clients, and they drop them silently. Where it helps, Mumcord adds a fallback that official clients can see: the "🎵" and "🎮" lines in the user's comment and the thumbnail in image messages.
Mumcord is licensed under the AGPL-3.0 (versions up to 0.1.1: MIT) · Source code · Website · Website source