Skip to content

Plugin Data Protocol

Tobias Vorwachs edited this page Oct 27, 2026 · 6 revisions

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.

Flows

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

Data packets

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.

Basics

The transport

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.

Server limits

A standard Mumble server (murmur / mumble-server) enforces these limits and silently drops anything above them:

  • Size: at most 1000 bytes of data per message and at most 100 bytes for the dataID. 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 pluginmessagelimit and pluginmessageburst). 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 share share, watch party state) 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.

Common rules

  • 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.

Version rules

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.

Size limits

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

What is not plugin data

  • 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.

What a bot can do

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.hello to everyone after connecting and to people who join. Announce only features you really implement.
  • Announce now playing. A music bot can send mumcord.nowplaying to 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.nowgaming with a steam://connect/ link, so Mumcord users get a "Join game" button.
  • Follow or control a watch party. Announce watch-party, sit in the channel, read start, state, cmd and pl messages to log what is being watched, or join and send pl edits to add videos to the playlist (for example from a chat command).
  • Read share announcements. Announce screen-share and read share messages 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.history requests, 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.

Example: sending plugin data

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 - _

What official Mumble clients see

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.

Clone this wiki locally