WebSocket クライアントが Agent に接続すると、フレームワークは識別情報、状態、MCP サーバー一覧などの JSON テキストフレームを自動送信します。これらのプロトコルメッセージを扱えないクライアント向けに、接続ごとに抑制できます。
新しい接続のたびに、Agent は次の 3 つのプロトコルメッセージを送ります。
| メッセージ種別 | 内容 |
|---|---|
cf_agent_identity |
Agent の名前とクラス |
cf_agent_state |
現在のエージェント状態 |
cf_agent_mcp_servers |
接続中の MCP サーバー一覧 |
状態と MCP のメッセージは、変化するたびにすべての接続へブロードキャストされます。
ほとんどの Web クライアントでは問題ありません。クライアント SDK と useAgent フックが、これらのメッセージを自動で消費します。一方、JSON テキストフレームを扱えないクライアントもあります。
- バイナリ専用クライアント — MQTT デバイス、IoT センサー、独自のバイナリプロトコル
- 軽量クライアント — WebSocket スタックが最小限の組み込みシステム
- 非ブラウザークライアント — WebSocket で接続するハードウェアデバイス
こうした接続では、プロトコルメッセージだけを抑制し、それ以外(RPC、通常のメッセージ、this.broadcast() によるブロードキャスト)は通常どおり動かせます。
どの接続がプロトコルメッセージを受け取るかは、shouldSendProtocolMessages をオーバーライドして制御します。抑制するには false を返します。
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";
}
}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";
}
}このフックは onConnect 中、メッセージ送信前に実行されます。false を返すと次のようになります。
- 接続時に
cf_agent_identity、cf_agent_state、cf_agent_mcp_serversは送られません - 以降、状態と MCP のブロードキャストからも除外されます
- RPC 呼び出し、通常の
onMessage処理、this.broadcast()は通常どおり動作します
WebSocket サブプロトコルヘッダーを確認することもできます。WebSocket 上でプロトコルを交渉する標準的な方法です。
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";
}
}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";
}
}接続でプロトコルメッセージが有効かどうかは、isConnectionProtocolEnabled で確認します。
export class MyAgent extends Agent {
@callable()
async getConnectionInfo() {
const { connection } = getCurrentAgent();
if (!connection) return null;
return {
protocolEnabled: this.isConnectionProtocolEnabled(connection),
readonly: this.isConnectionReadonly(connection),
};
}
}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),
};
}
}次の表は、接続でプロトコルメッセージを抑制したときに、何がまだ動作するかを示します。
| 操作 | 動作する? |
|---|---|
接続時に cf_agent_identity を受信する |
いいえ |
接続時とブロードキャストで cf_agent_state を受信する |
いいえ |
接続時とブロードキャストで cf_agent_mcp_servers を受信する |
いいえ |
| 通常の WebSocket メッセージの送受信 | はい |
@callable() RPC メソッドの呼び出し |
はい |
this.broadcast() メッセージの受信 |
はい |
| バイナリデータの送信 | はい |
| RPC 経由でのエージェント状態の変更 | はい |
接続は、readonly かつプロトコル抑制の両方にできます。状態を観察するだけで変更すべきでないバイナリデバイスに向いています。
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()})`;
}
}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()})`;
}
}両方のフラグは接続の WebSocket attachment に保存され、connection.state からは見えません。互いに干渉せず、ユーザー定義の接続状態とも干渉しません。
接続時にプロトコルメッセージを受け取るかを決める、オーバーライド可能なフックです。
| パラメーター | 型 | 説明 |
|---|---|---|
connection |
Connection |
接続中のクライアント |
ctx |
ConnectionContext |
アップグレードリクエストを含みます |
| 戻り値 | boolean |
false でプロトコルメッセージを抑制 |
既定: true を返します(すべての接続がプロトコルメッセージを受け取ります)。
このフックは接続時に一度だけ評価されます。結果は接続の WebSocket attachment に保存され、ハイバネーション を越えて残ります。
接続でプロトコルメッセージが現在有効かを確認します。
| パラメーター | 型 | 説明 |
|---|---|---|
connection |
Connection |
確認する接続 |
| 戻り値 | boolean |
プロトコルメッセージが有効なら true |
いつでも呼べます。エージェントがハイバネーションから起きたあとも安全です。
プロトコル状態は、接続の WebSocket attachment 内の内部フラグとして保存されます。readonly 接続 と同じ仕組みです。つまり次のとおりです。
- ハイバネーションを越える — フラグはシリアライズされ、エージェント起床時に復元されます
- クリーンアップ不要 — 接続が閉じると、接続状態は自動で破棄されます
- オーバーヘッドなし — データベースのテーブルやクエリはなく、接続の組み込み attachment だけです
- ユーザーコードから安全 —
connection.stateとconnection.setState()は、フラグを公開も上書きもしません
readonly は setConnectionReadonly() で動的に切り替えられますが、プロトコル状態は接続時に一度だけ設定され、あとから変更できません。接続のプロトコル状態を変えるには、クライアントが切断して再接続する必要があります。