콘텐츠로 이동

OpenClaw Gateway 프로토콜: WebSocket 연결 가이드

여러 플랫폼에서 동작하는 분산 시스템을 구축하다 보면, 각 클라이언트마다 통신 방식이 달라 고생하는 경우가 많아요. CLI부터 모바일 앱, 그리고 다양한 노드들까지 하나의 프로토콜로 깔끔하게 관리하고 싶다면 OpenClaw의 Gateway 프로토콜이 좋은 해답이 될 거예요.

OpenClaw의 Gateway WS 프로토콜은 단일 제어 평면(control plane)과 노드 전송을 담당합니다. CLI, 웹 UI, macOS 앱, 모바일 노드 등 모든 클라이언트가 WebSocket을 통해 연결되며, 핸드셰이크 시점에 자신의 role과 scope를 선언하는 구조예요.

  • WebSocket을 사용하며, JSON 페이로드를 담은 텍스트 프레임을 주고받아요.
  • 첫 번째 프레임은 반드시 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"]
}
}
{
"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": "…"
}
}
}
  • Request: {type:"req", id, method, params}
  • Response: {type:"res", id, ok, payload|error}
  • Event: {type:"event", event, payload, seq?, stateVersion?}

사이드 이펙트가 있는 메서드는 idempotency keys가 필요해요 (스키마 참조).

  • operator: 제어 평면 클라이언트 (CLI/UI/자동화 도구).
  • node: 기능 호스트 (camera/screen/canvas/system.run).

자주 사용되는 스코프들입니다:

  • operator.read
  • operator.write
  • operator.admin
  • operator.approvals
  • operator.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로 취급하며 서버 측 허용 목록을 통해 강제합니다.

  • system-presence는 디바이스 식별자를 키로 하는 항목들을 반환해요.
  • 프레즌스 항목에는 deviceId, roles, scopes가 포함되어 있어서, 하나의 디바이스가 operator와 node 역할을 동시에 수행하더라도 UI에서는 한 줄로 합쳐서 보여줄 수 있습니다.
  • 노드는 자동 허용 체크를 위해 현재 실행 가능한 스킬 목록을 가져오는 skills.bins를 호출할 수 있어요.
  • 오퍼레이터는 에이전트의 런타임 도구 카탈로그를 가져오기 위해 tools.catalog (operator.read)를 호출할 수 있습니다. 응답에는 그룹화된 도구와 출처 메타데이터가 포함돼요:
    • source: core 또는 plugin
    • pluginId: source="plugin"일 때의 플러그인 소유자
    • optional: 플러그인 도구의 선택 가능 여부
  • 오퍼레이터는 세션의 실질적인 런타임 도구 인벤토리를 가져오기 위해 tools.effective (operator.read)를 호출할 수 있습니다.
    • sessionKey가 필수입니다.
    • Gateway는 호출자가 제공한 인증 정보 대신 서버 측 세션에서 신뢰할 수 있는 런타임 컨텍스트를 유도합니다.
    • 응답은 세션 범위로 제한되며, 코어, 플러그인, 채널 도구를 포함해 현재 대화에서 즉시 사용할 수 있는 것들을 반영합니다.
  • 실행 요청에 승인이 필요한 경우, 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인 경우, 외부 전달 경로를 찾을 수 없을 때(예: 내부/웹챗 세션 또는 모호한 멀티 채널 설정) 세션 전용 실행으로 폴백을 허용합니다.
  • PROTOCOL_VERSION은 src/gateway/protocol/schema.ts에 정의되어 있습니다.
  • 클라이언트는 minProtocol과 maxProtocol을 보내며, 서버는 버전이 맞지 않으면 연결을 거절합니다.
  • 스키마와 모델은 TypeBox 정의로부터 생성됩니다:
    • pnpm protocol:gen
    • pnpm protocol:gen:swift
    • pnpm protocol:check
  • 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.challenge nonce에 서명해야 합니다.

디바이스 인증 마이그레이션 진단

섹션 제목: “디바이스 인증 마이그레이션 진단”

챌린지 서명 방식을 사용하지 않는 이전 클라이언트를 위해, connect는 이제 error.details.code에 DEVICE_AUTH_* 코드를 담아 반환합니다.

주요 마이그레이션 실패 사례:

Messagedetails.codedetails.reasonMeaning
device nonce requiredDEVICE_AUTH_NONCE_REQUIREDdevice-nonce-missing클라이언트가 device.nonce를 누락함.
device nonce mismatchDEVICE_AUTH_NONCE_MISMATCHdevice-nonce-mismatch잘못되었거나 만료된 nonce로 서명함.
device signature invalidDEVICE_AUTH_SIGNATURE_INVALIDdevice-signature서명 페이로드가 v2 형식과 일치하지 않음.
device signature expiredDEVICE_AUTH_SIGNATURE_EXPIREDdevice-signature-stale서명된 타임스탬프가 허용 오차를 벗어남.
device identity mismatchDEVICE_AUTH_DEVICE_ID_MISMATCHdevice-id-mismatchdevice.id가 공개 키 지문과 일치하지 않음.
device public key invalidDEVICE_AUTH_PUBLIC_KEY_INVALIDdevice-public-key공개 키 형식 또는 정규화 실패.

마이그레이션 목표:

  • 항상 connect.challenge를 기다리세요.
  • 서버 nonce를 포함하는 v2 페이로드에 서명하세요.
  • connect.params.device.nonce에 동일한 nonce를 보내세요.
  • 권장되는 서명 페이로드는 v3입니다. 이는 device/client/role/scopes/token/nonce 필드 외에도 platform과 deviceFamily를 결합합니다.
  • 호환성을 위해 레거시 v2 서명도 계속 허용되지만, 재연결 시 커맨드 정책 제어를 위해 페어링된 디바이스 메타데이터 고정(pinning)이 적용됩니다.
  • WS 연결에 TLS가 지원됩니다.
  • 클라이언트는 선택적으로 Gateway 인증서 지문을 고정(pinning)할 수 있습니다 (gateway.tls 설정 및 gateway.remote.tlsFingerprint 또는 CLI --tls-fingerprint 참조).

이 프로토콜은 상태, 채널, 모델, 채팅, 에이전트, 세션, 노드, 승인 등 Gateway API의 모든 기능을 노출합니다. 구체적인 인터페이스는 src/gateway/protocol/schema.ts의 TypeBox 스키마에 정의되어 있습니다.

AI Setup Assistant

OpenClaw

OpenClaw Expert

아직 막혀 있나요?

이 문서에서 답을 못 찾았다면 OpenClaw Expert에게 바로 물어보세요.