协议消息
当一个 WebSocket 客户端连接到 Agent 时,框架会自动发送几条 JSON 文本帧 — 包括身份、状态和 MCP 服务器列表。对于无法处理这些消息的客户端,你可以按连接禁用这些协议消息。
概览
每次新连接时,Agent 会发送三条协议消息:
| 消息类型 | 内容 |
|---|---|
| cf_agent_identity | Agent 名称和类 |
| cf_agent_state | 当前 agent 状态 |
| cf_agent_mcp_servers | 已连接的 MCP 服务器列表 |
状态和 MCP 消息在发生变化时,也会广播给所有连接。
对于大多数 Web 客户端来说这没问题 — 客户端 SDK 和 useAgent hook 会自动消费这些消息。然而,有些客户端无法处理 JSON 文本帧:
- 仅支持二进制的客户端 — MQTT 设备、IoT 传感器、自定义二进制协议
- 轻量级客户端 — 拥有最简 WebSocket 栈的嵌入式系统
- 非浏览器客户端 — 通过 WebSocket 连接的硬件设备
对于这些连接,你可以禁用协议消息,同时保持其他功能(RPC、常规消息、this.broadcast() 广播)正常工作。
禁用协议消息
重写 shouldSendProtocolMessages 来控制哪些连接接收协议消息。返回 false 即可禁用。
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";
}
}
TypeScript
import { Agent, type Connection, type ConnectionContext } from "agents";
export class IoTAgent extends Agent<Env, State> {
shouldSendProtocolMessages(
connection: Connection,
ctx: ConnectionContext,
): boolean {
const url = new URL(ctx.request.url);
return url.searchParams.get("protocol") !== "false";
}
}
Explain Code
这个 hook 会在 onConnect 期间、消息发送之前运行。当它返回 false 时:
- 连接时不会发送
cf_agent_identity、cf_agent_state或cf_agent_mcp_servers消息 - 该连接之后也会被排除在状态和 MCP 广播之外
- RPC 调用、常规
onMessage处理和this.broadcast()仍然正常工作
使用 WebSocket 子协议
你也可以检查 WebSocket 子协议头,这是在 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";
}
}
TypeScript
export class MqttAgent extends Agent<Env, State> {
shouldSendProtocolMessages(
connection: Connection,
ctx: ConnectionContext,
): boolean {
// MQTT-over-WebSocket clients negotiate via subprotocol
const subprotocol = ctx.request.headers.get("Sec-WebSocket-Protocol");
return subprotocol !== "mqtt";
}
}
Explain Code
检查协议状态
使用 isConnectionProtocolEnabled 检查某个连接是否启用了协议消息:
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),
};
}
}
Explain Code
TypeScript
export class MyAgent extends Agent<Env, State> {
@callable()
async getConnectionInfo() {
const { connection } = getCurrentAgent();
if (!connection) return null;
return {
protocolEnabled: this.isConnectionProtocolEnabled(connection),
readonly: this.isConnectionReadonly(connection),
};
}
}
Explain Code
哪些功能受影响、哪些不受影响
下表展示了禁用协议消息后,某个连接仍可正常使用的功能:
| 操作 | 是否可用 |
|---|---|
| 连接时接收 cf_agent_identity | 否 |
| 连接时和广播时接收 cf_agent_state | 否 |
| 连接时和广播时接收 cf_agent_mcp_servers | 否 |
| 收发常规 WebSocket 消息 | 是 |
| 调用 @callable() RPC 方法 | 是 |
| 接收 this.broadcast() 消息 | 是 |
| 发送二进制数据 | 是 |
| 通过 RPC 修改 agent 状态 | 是 |
与 readonly 组合使用
一个连接可以同时是只读和禁用协议消息。这对于只观察、不修改状态的二进制设备很有用:
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()})`;
}
}
Explain Code
TypeScript
export class SensorHub extends Agent<Env, SensorState> {
shouldSendProtocolMessages(
connection: Connection,
ctx: ConnectionContext,
): boolean {
const url = new URL(ctx.request.url);
// Binary sensors don't handle JSON protocol frames
return url.searchParams.get("type") !== "sensor";
}
shouldConnectionBeReadonly(
connection: Connection,
ctx: ConnectionContext,
): boolean {
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: string, value: number) {
// 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()})`;
}
}
Explain Code
两个标志都存储在连接的 WebSocket attachment 中,且对 connection.state 不可见 — 它们之间不会互相干扰,也不会与用户定义的连接状态冲突。
API 参考
shouldSendProtocolMessages
一个可重写的 hook,用于决定连接时该连接是否接收协议消息。
| 参数 | 类型 | 描述 |
|---|---|---|
| connection | Connection | 正在连接的客户端 |
| ctx | ConnectionContext | 包含升级请求 |
| 返回 | boolean | false 表示禁用协议消息 |
默认值:返回 true(所有连接都接收协议消息)。
这个 hook 在连接时只评估一次。结果会持久化到连接的 WebSocket attachment 中,可以挺过休眠。
isConnectionProtocolEnabled
检查某个连接当前是否启用了协议消息。
| 参数 | 类型 | 描述 |
|---|---|---|
| connection | Connection | 要检查的连接 |
| 返回 | boolean | true 表示协议消息已启用 |
随时可以调用,包括 agent 从休眠中唤醒之后。
工作原理
协议状态以内部标志的形式存储在连接的 WebSocket attachment 中 — 这与只读连接使用的机制相同。这意味着:
- 挺过休眠 — 标志会被序列化,在 agent 唤醒时恢复
- 无需清理 — 连接关闭时,连接状态会自动丢弃
- 零开销 — 不需要数据库表或查询,只用连接内置的 attachment
- 不受用户代码影响 —
connection.state和connection.setState()不会暴露或覆盖该标志
与可以通过 setConnectionReadonly() 动态切换的只读模式不同,协议状态在连接时设置一次,之后不能改变。要更改连接的协议状态,客户端必须断开重连。