Skip to content
Cloudflare Docs

Protocol messages

When a WebSocket client connects to an Agent, the framework automatically sends several JSON text frames — identity, state, and MCP server lists. You can suppress these per-connection protocol messages for clients that cannot handle them.

Overview

On every new connection, the Agent sends three protocol messages:

Message typeContent
cf_agent_identityAgent name and class
cf_agent_stateCurrent agent state
cf_agent_mcp_serversConnected MCP server list

State and MCP messages are also broadcast to all connections whenever they change.

For most web clients this is fine — the Client SDK and useAgent hook consume these messages automatically. However, some clients cannot handle JSON text frames:

  • Binary-only clients — MQTT devices, IoT sensors, custom binary protocols
  • Lightweight clients — Embedded systems with minimal WebSocket stacks
  • Non-browser clients — Hardware devices connecting via WebSocket

For these connections, you can suppress protocol messages while keeping everything else (RPC, regular messages, broadcasts via this.broadcast()) working normally.

Suppressing protocol messages

Override shouldSendProtocolMessages to control which connections receive protocol messages. Return false to suppress them.

JavaScript
import { Agent } from "agents";
export class IoTAgent extends Agent {
shouldSendProtocolMessages(connection, ctx) {
const url = new URL(ctx.request.url);
return url.searchParams.get("protocol") !== "false";
}
}

This hook runs during onConnect, before any messages are sent. When it returns false:

  • No cf_agent_identity, cf_agent_state, or cf_agent_mcp_servers messages are sent on connect
  • The connection is excluded from state and MCP broadcasts going forward
  • RPC calls, regular onMessage handling, and this.broadcast() still work normally

Using WebSocket subprotocol

You can also check the WebSocket subprotocol header, which is the standard way to negotiate protocols over WebSocket:

JavaScript
export class MqttAgent extends Agent {
shouldSendProtocolMessages(connection, ctx) {
// MQTT-over-WebSocket clients negotiate via subprotocol
const subprotocol = ctx.request.headers.get("Sec-WebSocket-Protocol");
return subprotocol !== "mqtt";
}
}

Checking protocol status

Use isConnectionProtocolEnabled to check whether a connection has protocol messages enabled:

JavaScript
export class MyAgent extends Agent {
@callable()
async getConnectionInfo() {
const { connection } = getCurrentAgent();
if (!connection) return null;
return {
protocolEnabled: this.isConnectionProtocolEnabled(connection),
readonly: this.isConnectionReadonly(connection),
};
}
}

What is and is not suppressed

The following table shows what still works when protocol messages are suppressed for a connection:

ActionWorks?
Receive cf_agent_identity on connectNo
Receive cf_agent_state on connect and broadcastsNo
Receive cf_agent_mcp_servers on connect and broadcastsNo
Send and receive regular WebSocket messagesYes
Call @callable() RPC methodsYes
Receive this.broadcast() messagesYes
Send binary dataYes
Mutate agent state via RPCYes

Combining with readonly

A connection can be both readonly and protocol-suppressed. This is useful for binary devices that should observe but not modify state:

JavaScript
export class SensorHub extends Agent {
shouldSendProtocolMessages(connection, ctx) {
const url = new URL(ctx.request.url);
// Binary sensors don't handle JSON protocol frames
return url.searchParams.get("type") !== "sensor";
}
shouldConnectionBeReadonly(connection, ctx) {
const url = new URL(ctx.request.url);
// Sensors can only report data via RPC, not modify shared state
return url.searchParams.get("type") === "sensor";
}
@callable()
async reportReading(sensorId, value) {
// This RPC still works for readonly+no-protocol connections
// because it writes to SQL, not agent state
this
.sql`INSERT INTO readings (sensor_id, value, ts) VALUES (${sensorId}, ${value}, ${Date.now()})`;
}
}

Both flags are stored in the connection's WebSocket attachment and hidden from connection.state — they do not interfere with each other or with user-defined connection state.

API reference

shouldSendProtocolMessages

An overridable hook that determines if a connection should receive protocol messages when it connects.

ParameterTypeDescription
connectionConnectionThe connecting client
ctxConnectionContextContains the upgrade request
Returnsbooleanfalse to suppress protocol messages

Default: returns true (all connections receive protocol messages).

This hook is evaluated once on connect. The result is persisted in the connection's WebSocket attachment and survives hibernation.

isConnectionProtocolEnabled

Check if a connection currently has protocol messages enabled.

ParameterTypeDescription
connectionConnectionThe connection to check
Returnsbooleantrue if protocol messages are enabled

Safe to call at any time, including after the agent wakes from hibernation.

How it works

Protocol status is stored as an internal flag in the connection's WebSocket attachment — the same mechanism used by readonly connections. This means:

  • Survives hibernation — the flag is serialized and restored when the agent wakes up
  • No cleanup needed — connection state is automatically discarded when the connection closes
  • Zero overhead — no database tables or queries, just the connection's built-in attachment
  • Safe from user codeconnection.state and connection.setState() never expose or overwrite the flag

Unlike readonly which can be toggled dynamically with setConnectionReadonly(), protocol status is set once on connect and cannot be changed afterward. To change a connection's protocol status, the client must disconnect and reconnect.