La documentación y las tablas comparativas te dicen lo que un broker puede hacer. Lo que importa es lo que tu broker, con tu configuración y tus credenciales, permite de verdad. La forma más rápida y fiable de saberlo es sondearlo: conectarte con MQTT 5, leer las propiedades del CONNACK y después probar cada función y mirar los reason codes que devuelve.
Este artículo explica qué señales revisar y te da un script en Python (paho-mqtt 2.x) que imprime un informe de capacidades para cualquier broker.
Lo que un broker te dice en el CONNACK
Un broker MQTT 5 anuncia sus límites en el paquete CONNACK. La regla importante: una propiedad solo se envía cuando difiere del valor por defecto. Que falte RetainAvailable no significa «desconocido», significa «sí».
| Propiedad del CONNACK | Valor por defecto si falta | Qué se rompe si está restringida |
|---|---|---|
| Maximum QoS | 2 | Publicar por encima provoca 0x9B QoS not supported y se cierra la conexión |
| Retain Available | sí | Las publicaciones retenidas reciben 0x9A Retain not supported |
| Wildcard Subscription Available | sí | Las suscripciones con + / # reciben 0xA2 Wildcard Subscriptions not supported |
| Shared Subscription Available | sí | Las suscripciones $share/... reciben 0x9E Shared Subscriptions not supported |
| Subscription Identifiers Available | sí | Las suscripciones con identificador reciben 0xA1 |
| Receive Maximum | 65.535 | Cuántos mensajes QoS 1/2 puedes tener en vuelo hacia el broker |
| Maximum Packet Size | sin límite (máximo del protocolo) | Los paquetes más grandes reciben 0x95 Packet too large |
| Topic Alias Maximum | 0 (no se aceptan alias) | Usar un alias que el broker no permitió es 0x94 Topic Alias invalid |
| Server Keep Alive | tu valor | El broker sobrescribe tu keep-alive; tu cliente debe usar su valor |
| Assigned Client Identifier | — | Se envía cuando te conectas con un client ID vacío |
MQTT 3.1.1 no tiene nada de esto. Un broker 3.1.1 solo te dice si aceptó la conexión, así que todo hay que comprobarlo probándolo. Puedes consultar cualquiera de estos códigos en la referencia de reason codes.
El script de sondeo
El script siguiente ejecuta seis comprobaciones: versiones del protocolo, propiedades del CONNACK, QoS concedida por suscripción, suscripciones con wildcard y compartidas, mensajes retenidos y una entrega QoS 2 de extremo a extremo. Configúralo con variables de entorno y ejecútalo contra cada entorno que te importe.
"""Report which MQTT features a broker supports. Requires paho-mqtt >= 2.0."""
import os, threading, time, uuid
import paho.mqtt.client as mqtt
HOST = os.environ.get("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.environ.get("MQTT_PORT", "1883"))
USER, PASS = os.environ.get("MQTT_USER"), os.environ.get("MQTT_PASS")
BASE = f"testmqtt/probe/{uuid.uuid4().hex[:8]}"
DEFAULTS = { # value that applies when the broker omits the property
"MaximumQoS": 2, "RetainAvailable": 1, "WildcardSubscriptionAvailable": 1,
"SharedSubscriptionAvailable": 1, "SubscriptionIdentifierAvailable": 1,
"ReceiveMaximum": 65535, "MaximumPacketSize": "no limit",
"TopicAliasMaximum": 0, "ServerKeepAlive": "client value",
}
def connect(protocol=mqtt.MQTTv5, timeout=10):
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=f"probe-{uuid.uuid4().hex[:8]}", protocol=protocol)
if USER:
c.username_pw_set(USER, PASS)
if PORT == 8883:
c.tls_set()
done, res = threading.Event(), {}
def on_connect(client, userdata, flags, reason_code, properties):
res.update(rc=reason_code, props=properties)
done.set()
c.on_connect = on_connect
c.connect(HOST, PORT, keepalive=30)
c.loop_start()
if not done.wait(timeout):
c.loop_stop()
raise TimeoutError("no CONNACK")
return c, res["rc"], res["props"]
def close(*clients):
for c in clients:
c.disconnect()
c.loop_stop()
def subscribe(c, topic_filter, qos=2, timeout=5):
done, res = threading.Event(), {}
def on_subscribe(client, userdata, mid, reason_code_list, properties):
res["rc"] = reason_code_list[0]
done.set()
c.on_subscribe = on_subscribe
c.subscribe(topic_filter, qos=qos)
return res["rc"] if done.wait(timeout) else "no SUBACK"
def show(label, value):
print(f" {label:34} {value}")
def main():
print(f"Broker {HOST}:{PORT} (test topics under {BASE})\n")
print("Protocol versions")
for name, proto in (("MQTT 3.1.1", mqtt.MQTTv311), ("MQTT 5", mqtt.MQTTv5)):
try:
c, rc, _ = connect(proto)
show(name, "OK" if not rc.is_failure else f"refused: {rc}")
close(c)
except Exception as e:
show(name, f"FAILED ({e})")
c, rc, props = connect()
if rc.is_failure:
raise SystemExit(f"MQTT 5 connect refused: {rc}")
print("\nCONNACK properties")
for name, default in DEFAULTS.items():
value = getattr(props, name, None)
show(name, value if value is not None else f"{default} (default)")
print("\nSubscriptions (granted QoS or reason code)")
for q in (0, 1, 2):
show(f"QoS {q} subscription", subscribe(c, f"{BASE}/qos/{q}", qos=q))
show("Wildcard filter (+)", subscribe(c, f"{BASE}/+/wild", qos=1))
show("Shared subscription ($share)", subscribe(c, f"$share/probe/{BASE}/shared", qos=1))
print("\nActive tests")
got = {}
received = threading.Event()
def on_message(client, userdata, msg):
got[msg.topic] = msg
received.set()
c.on_message = on_message
# End-to-end QoS 2: publish at 2 to a QoS 2 subscription, check delivered QoS
received.clear()
c.publish(f"{BASE}/qos/2", b"qos2-probe", qos=2)
if received.wait(5):
show("QoS 2 delivery", f"delivered at QoS {got[f'{BASE}/qos/2'].qos}")
else:
show("QoS 2 delivery", "NOT delivered")
# Retained message: publish, then subscribe with a fresh client
topic = f"{BASE}/retain"
try:
c.publish(topic, b"retained-probe", qos=1, retain=True).wait_for_publish(5)
sub, _, _ = connect()
hit = threading.Event()
sub.on_message = lambda client, u, m: m.retain and hit.set()
subscribe(sub, topic, qos=1)
show("Retained message", "OK" if hit.wait(5) else "NOT delivered")
close(sub)
c.publish(topic, b"", qos=1, retain=True).wait_for_publish(5) # clear it
except Exception as e:
show("Retained message", f"FAILED ({e})")
close(c)
if __name__ == "__main__":
main()Ejecútalo con tus datos de conexión:
pip install "paho-mqtt>=2.0"
MQTT_HOST=broker.connectmq.com MQTT_PORT=8883 \
MQTT_USER="$MQTT_USER" MQTT_PASS="$MQTT_PASS" python probe.pyLos valores de QoS concedida se imprimen como objetos ReasonCode de paho: Granted QoS 2 significa que el broker aceptó la suscripción en ese nivel, mientras que un código 0x80 o superior (por ejemplo Not authorized o Shared Subscriptions not supported) es un rechazo. Si la suscripción compartida se acepta pero quieres saber si los mensajes se reparten de verdad, usa el test de suscripciones compartidas.
Cómo leer el informe
Hallazgos típicos y qué hacer con ellos:
- MQTT 5 rechazado, 3.1.1 OK: versión antigua del broker o un listener limitado a 3.1.1. Tu cliente MQTT 5 debe hacer fallback, o hay que actualizar el broker. Consulta MQTT 5 vs 3.1.1.
- MaximumQoS 1: habitual en servicios IoT en la nube y en brokers configurados para rendimiento. Una publicación QoS 2 desconectará tu cliente con
0x9B; diseña para QoS 1 con handlers idempotentes. - Suscripción concedida con una QoS menor que la solicitada: es legal. El broker rebaja en lugar de rechazar, y los mensajes llegan con el nivel concedido. Comprueba la QoS concedida en tu cliente, no solo la solicitada.
- RetainAvailable 0 o mensaje retenido no entregado: los dispositivos que dependen del estado retenido (último valor al suscribirse) parecerán rotos. Detalles en retain no soportado.
- TopicAliasMaximum 0: el broker no acepta topic aliases de los clientes; las librerías que asignan alias automáticamente deben desactivarlo.
- MaximumPacketSize definido: compáralo con tu payload más grande más el topic y las propiedades usando la calculadora de tamaño de paquete.
- Not authorized en una suscripción: no falta una función, es una ACL. Ejecuta la sonda con el usuario de tus dispositivos y consulta probar permisos ACL de MQTT.
La misma comprobación en JavaScript
Con MQTT.js 5, las propiedades del CONNACK están en el paquete que recibe el evento connect:
import mqtt from 'mqtt';
const client = mqtt.connect('mqtts://broker.connectmq.com:8883', {
protocolVersion: 5,
username: process.env.MQTT_USER,
password: process.env.MQTT_PASS,
});
client.on('connect', (connack) => {
// Absent keys mean the spec default applies
console.log(connack.properties ?? '(no properties: all defaults)');
client.subscribe('$share/probe/testmqtt/probe/js', { qos: 2 }, (err, granted) => {
console.log(err ?? granted); // granted[0].qos is the granted QoS or a reason code
client.end();
});
});
client.on('error', (err) => console.error('connect failed:', err.message));Aquí los nombres de las propiedades van en camelCase: maximumQoS, retainAvailable, maximumPacketSize, topicAliasMaximum, wildcardSubscriptionAvailable, sharedSubscriptionAvailable, subscriptionIdentifiersAvailable, receiveMaximum y serverKeepAlive.
¿Sin código? Usa mqtt test
La HiveMQ MQTT CLI trae esta sonda integrada: mqtt test -h broker.connectmq.com imprime las capacidades de MQTT 3 y MQTT 5, incluida la longitud máxima de topic y el tamaño de payload medidos por prueba. La guía de mqtt-cli explica la salida. Para una comprobación manual rápida, conéctate con el cliente MQTT online usando MQTT 5 y prueba una publicación retenida y una suscripción $share/.
Inclúyelo en tu pipeline
Las capacidades cambian sin avisar: una actualización del broker, un listener nuevo, otro plan en un servicio gestionado. Guarda la salida de la sonda en tu repositorio y compárala en CI:
python probe.py | sed -E 's#testmqtt/probe/[0-9a-f]+#testmqtt/probe/X#' > capabilities.txt
git diff --exit-code capabilities.txtEl sed elimina el sufijo aleatorio del topic para que en el diff solo aparezcan cambios reales. Combínalo con las comprobaciones de cómo probar un broker MQTT y sabrás que falta una función antes de que lo descubra un dispositivo en campo.
Mira los topics de la sonda en el navegador
Una sonda de cinco minutos vale más que una tarde depurando desconexiones «aleatorias». Lee el CONNACK, prueba cada función de la que dependes y guarda el informe en control de versiones para que las diferencias entre brokers y entornos nunca sean una sorpresa.
Preguntas frecuentes
¿Cómo sé si un broker soporta MQTT 5?
Conéctate con la versión de protocolo 5. Un broker que la soporta responde con un CONNACK de MQTT 5, normalmente con propiedades. Uno que no la soporta devuelve 'unacceptable protocol version' (0x01 en 3.1.1, 0x84 en MQTT 5) o cierra la conexión.
Si falta una propiedad en el CONNACK, ¿la función no está soportada?
No, al contrario. En MQTT 5, una propiedad ausente significa que se aplica el valor por defecto: retain, wildcards, suscripciones compartidas e identificadores de suscripción están disponibles, la QoS máxima es 2 y receive maximum es 65.535. Los brokers solo envían las propiedades que restringen o cambian esos valores.
¿Puede un broker soportar una función pero negársela a mi cliente?
Sí. Las ACLs, la configuración por listener y los planes de los servicios gestionados pueden restringir QoS, retain, wildcards o topics para usuarios concretos. Por eso la sonda debe ejecutarse con las mismas credenciales y el mismo puerto que tus dispositivos.


