OpenClaw Gateway 프로토콜: WebSocket 연결 가이드
여러 플랫폼에서 동작하는 분산 시스템을 구축하다 보면, 각 클라이언트마다 통신 방식이 달라 고생하는 경우가 많아요. CLI부터 모바일 앱, 그리고 다양한 노드들까지 하나의 프로토콜로 깔끔하게 관리하고 싶다면 OpenClaw의 Gateway 프로토콜이 좋은 해답이 될 거예요.
OpenClaw의 Gateway WS 프로토콜은 단일 제어 평면(control plane)과 노드 전송을 담당합니다. CLI, 웹 UI, macOS 앱, 모바일 노드 등 모든 클라이언트가 WebSocket을 통해 연결되며, 핸드셰이크 시점에 자신의 role과 scope를 선언하는 구조예요.
전송 (Transport)
섹션 제목: “전송 (Transport)”- WebSocket을 사용하며, JSON 페이로드를 담은 텍스트 프레임을 주고받아요.
- 첫 번째 프레임은 반드시
connect요청이어야 합니다.
핸드셰이크 (Handshake / connect)
섹션 제목: “핸드셰이크 (Handshake / connect)”Gateway → Client (연결 전 챌린지):
{ "type": "event", "event": "connect.challenge", "payload": { "nonce": "…", "ts": 1737264000000 }}Client → Gateway:
{ "type": "req", "id": "…", "method": "connect", "params": { "minProtocol": 3, "maxProtocol": 3, "client": { "id": "cli", "version": "1.2.3", "platform": "macos", "mode": "operator" }, "role": "operator", "scopes": ["operator.read", "operator.write"], "caps": [], "commands": [], "permissions": {}, "auth": { "token": "…" }, "locale": "en-US", "userAgent": "openclaw-cli/1.2.3", "device": { "id": "device_fingerprint", "publicKey": "…", "signature": "…", "signedAt": 1737264000000, "nonce": "…" } }}Gateway → Client:
{ "type": "res", "id": "…", "ok": true, "payload": { "type": "hello-ok", "protocol": 3, "policy": { "tickIntervalMs": 15000 } }}디바이스 토큰이 발행되면 hello-ok에 다음 내용도 포함됩니다:
{ "auth": { "deviceToken": "…", "role": "operator", "scopes": ["operator.read", "operator.write"] }}노드 예시 (Node example)
섹션 제목: “노드 예시 (Node example)”{ "type": "req", "id": "…", "method": "connect", "params": { "minProtocol": 3, "maxProtocol": 3, "client": { "id": "ios-node", "version": "1.2.3", "platform": "ios", "mode": "node" }, "role": "node", "scopes": [], "caps": ["camera", "canvas", "screen", "location", "voice"], "commands": ["camera.snap", "canvas.navigate", "screen.record", "location.get"], "permissions": { "camera.capture": true, "screen.record": false }, "auth": { "token": "…" }, "locale": "en-US", "userAgent": "openclaw-ios/1.2.3", "device": { "id": "device_fingerprint", "publicKey": "…", "signature": "…", "signedAt": 1737264000000, "nonce": "…" } }}프레이밍 (Framing)
섹션 제목: “프레이밍 (Framing)”- Request:
{type:"req", id, method, params} - Response:
{type:"res", id, ok, payload|error} - Event:
{type:"event", event, payload, seq?, stateVersion?}
사이드 이펙트가 있는 메서드는 idempotency keys가 필요해요 (스키마 참조).
역할 및 스코프 (Roles + scopes)
섹션 제목: “역할 및 스코프 (Roles + scopes)”역할 (Roles)
섹션 제목: “역할 (Roles)”operator: 제어 평면 클라이언트 (CLI/UI/자동화 도구).node: 기능 호스트 (camera/screen/canvas/system.run).
스코프 (Scopes - operator)
섹션 제목: “스코프 (Scopes - operator)”자주 사용되는 스코프들입니다:
operator.readoperator.writeoperator.adminoperator.approvalsoperator.pairing
메서드 스코프는 첫 번째 관문일 뿐이에요. chat.send를 통해 전달되는 일부 슬래시 커맨드들은 그 위에 더 엄격한 커맨드 레벨 체크를 적용합니다. 예를 들어, 영구적인 /config set이나 /config unset 쓰기 작업은 operator.admin 권한이 필요해요.
기능/커맨드/권한 (Caps/commands/permissions - node)
섹션 제목: “기능/커맨드/권한 (Caps/commands/permissions - node)”노드는 연결 시점에 자신의 기능들을 선언합니다:
caps: 상위 레벨의 기능 카테고리.commands: 실행 가능한 커맨드 허용 목록.permissions: 세부적인 토글 (예:screen.record,camera.capture).
Gateway는 이를 claims로 취급하며 서버 측 허용 목록을 통해 강제합니다.
프레즌스 (Presence)
섹션 제목: “프레즌스 (Presence)”system-presence는 디바이스 식별자를 키로 하는 항목들을 반환해요.- 프레즌스 항목에는
deviceId,roles,scopes가 포함되어 있어서, 하나의 디바이스가 operator와 node 역할을 동시에 수행하더라도 UI에서는 한 줄로 합쳐서 보여줄 수 있습니다.
노드 헬퍼 메서드
섹션 제목: “노드 헬퍼 메서드”- 노드는 자동 허용 체크를 위해 현재 실행 가능한 스킬 목록을 가져오는
skills.bins를 호출할 수 있어요.
오퍼레이터 헬퍼 메서드
섹션 제목: “오퍼레이터 헬퍼 메서드”- 오퍼레이터는 에이전트의 런타임 도구 카탈로그를 가져오기 위해
tools.catalog(operator.read)를 호출할 수 있습니다. 응답에는 그룹화된 도구와 출처 메타데이터가 포함돼요:source:core또는pluginpluginId:source="plugin"일 때의 플러그인 소유자optional: 플러그인 도구의 선택 가능 여부
- 오퍼레이터는 세션의 실질적인 런타임 도구 인벤토리를 가져오기 위해
tools.effective(operator.read)를 호출할 수 있습니다.sessionKey가 필수입니다.- Gateway는 호출자가 제공한 인증 정보 대신 서버 측 세션에서 신뢰할 수 있는 런타임 컨텍스트를 유도합니다.
- 응답은 세션 범위로 제한되며, 코어, 플러그인, 채널 도구를 포함해 현재 대화에서 즉시 사용할 수 있는 것들을 반영합니다.
실행 승인 (Exec approvals)
섹션 제목: “실행 승인 (Exec approvals)”- 실행 요청에 승인이 필요한 경우, Gateway는
exec.approval.requested를 브로드캐스트합니다. - 오퍼레이터 클라이언트는
exec.approval.resolve를 호출하여 이를 해결합니다 (operator.approvals스코프 필요). host=node인 경우,exec.approval.request에 반드시systemRunPlan(argv/cwd/rawCommand/세션 메타데이터 등)이 포함되어야 해요.systemRunPlan이 없는 요청은 거절됩니다.
에이전트 전달 폴백 (Agent delivery fallback)
섹션 제목: “에이전트 전달 폴백 (Agent delivery fallback)”agent요청에deliver=true를 포함하여 외부 전달을 요청할 수 있습니다.bestEffortDeliver=false인 경우 엄격하게 동작합니다. 해결되지 않거나 내부 전용인 전달 대상은INVALID_REQUEST를 반환해요.bestEffortDeliver=true인 경우, 외부 전달 경로를 찾을 수 없을 때(예: 내부/웹챗 세션 또는 모호한 멀티 채널 설정) 세션 전용 실행으로 폴백을 허용합니다.
버전 관리 (Versioning)
섹션 제목: “버전 관리 (Versioning)”PROTOCOL_VERSION은src/gateway/protocol/schema.ts에 정의되어 있습니다.- 클라이언트는
minProtocol과maxProtocol을 보내며, 서버는 버전이 맞지 않으면 연결을 거절합니다. - 스키마와 모델은 TypeBox 정의로부터 생성됩니다:
pnpm protocol:genpnpm protocol:gen:swiftpnpm protocol:check
인증 (Auth)
섹션 제목: “인증 (Auth)”OPENCLAW_GATEWAY_TOKEN(또는--token)이 설정된 경우,connect.params.auth.token이 일치해야 하며 그렇지 않으면 소켓이 닫힙니다.- 페어링 후 Gateway는 연결 역할과 스코프에 맞춰 제한된 디바이스 토큰을 발행합니다. 이 토큰은
hello-ok.auth.deviceToken으로 반환되며, 클라이언트는 추후 재연결을 위해 이를 저장해야 합니다. - 디바이스 토큰은
device.token.rotate및device.token.revoke를 통해 교체하거나 취소할 수 있습니다 (operator.pairing스코프 필요). - 인증 실패 시
error.details.code와 함께 다음과 같은 복구 힌트가 제공됩니다:error.details.canRetryWithDeviceToken(boolean)error.details.recommendedNextStep(retry_with_device_token,update_auth_configuration,update_auth_credentials,wait_then_retry,review_auth_configuration)
AUTH_TOKEN_MISMATCH발생 시 클라이언트 동작:- 신뢰할 수 있는 클라이언트는 캐시된 디바이스 토큰으로 한 번 더 재시도할 수 있습니다.
- 재시도마저 실패하면 자동 재연결 루프를 중단하고 사용자에게 안내를 표시해야 합니다.
디바이스 식별 및 페어링 (Device identity + pairing)
섹션 제목: “디바이스 식별 및 페어링 (Device identity + pairing)”- 노드는 키 쌍 지문(fingerprint)에서 유도된 고유한 디바이스 식별자(
device.id)를 포함해야 합니다. - Gateway는 디바이스와 역할별로 토큰을 발행합니다.
- 로컬 자동 승인이 활성화되지 않은 경우, 새로운 디바이스 ID에 대해서는 페어링 승인이 필요합니다.
- 로컬 연결에는 루프백 주소와 Gateway 호스트 자신의 tailnet 주소가 포함됩니다 (동일 호스트의 tailnet 바인딩도 자동 승인 가능).
- 모든 WS 클라이언트는
connect시점에device식별 정보를 포함해야 합니다 (operator 및 node 공통). 제어 UI는 다음 모드에서만 이를 생략할 수 있습니다:gateway.controlUi.allowInsecureAuth=true: localhost 전용 비보안 HTTP 호환용.gateway.controlUi.dangerouslyDisableDeviceAuth=true: 비상용, 보안 수준이 크게 낮아집니다.
- 모든 연결은 서버가 제공한
connect.challengenonce에 서명해야 합니다.
디바이스 인증 마이그레이션 진단
섹션 제목: “디바이스 인증 마이그레이션 진단”챌린지 서명 방식을 사용하지 않는 이전 클라이언트를 위해, connect는 이제 error.details.code에 DEVICE_AUTH_* 코드를 담아 반환합니다.
주요 마이그레이션 실패 사례:
| Message | details.code | details.reason | Meaning |
|---|---|---|---|
device nonce required | DEVICE_AUTH_NONCE_REQUIRED | device-nonce-missing | 클라이언트가 device.nonce를 누락함. |
device nonce mismatch | DEVICE_AUTH_NONCE_MISMATCH | device-nonce-mismatch | 잘못되었거나 만료된 nonce로 서명함. |
device signature invalid | DEVICE_AUTH_SIGNATURE_INVALID | device-signature | 서명 페이로드가 v2 형식과 일치하지 않음. |
device signature expired | DEVICE_AUTH_SIGNATURE_EXPIRED | device-signature-stale | 서명된 타임스탬프가 허용 오차를 벗어남. |
device identity mismatch | DEVICE_AUTH_DEVICE_ID_MISMATCH | device-id-mismatch | device.id가 공개 키 지문과 일치하지 않음. |
device public key invalid | DEVICE_AUTH_PUBLIC_KEY_INVALID | device-public-key | 공개 키 형식 또는 정규화 실패. |
마이그레이션 목표:
- 항상
connect.challenge를 기다리세요. - 서버 nonce를 포함하는 v2 페이로드에 서명하세요.
connect.params.device.nonce에 동일한 nonce를 보내세요.- 권장되는 서명 페이로드는
v3입니다. 이는 device/client/role/scopes/token/nonce 필드 외에도platform과deviceFamily를 결합합니다. - 호환성을 위해 레거시
v2서명도 계속 허용되지만, 재연결 시 커맨드 정책 제어를 위해 페어링된 디바이스 메타데이터 고정(pinning)이 적용됩니다.
TLS 및 피닝 (TLS + pinning)
섹션 제목: “TLS 및 피닝 (TLS + pinning)”- WS 연결에 TLS가 지원됩니다.
- 클라이언트는 선택적으로 Gateway 인증서 지문을 고정(pinning)할 수 있습니다 (
gateway.tls설정 및gateway.remote.tlsFingerprint또는 CLI--tls-fingerprint참조).
범위 (Scope)
섹션 제목: “범위 (Scope)”이 프로토콜은 상태, 채널, 모델, 채팅, 에이전트, 세션, 노드, 승인 등 Gateway API의 모든 기능을 노출합니다. 구체적인 인터페이스는 src/gateway/protocol/schema.ts의 TypeBox 스키마에 정의되어 있습니다.
다음 단계
섹션 제목: “다음 단계”OpenClaw Expert
아직 막혀 있나요?
이 문서에서 답을 못 찾았다면 OpenClaw Expert에게 바로 물어보세요.