브릿지 & 원격 제어

로컬 CLI 세션을 양방향 클라우드 연결 환경으로 전환하는 방법.

01

개요

브릿지 시스템은 로컬 CLI 세션을 클라우드 기반 인터페이스(예: claude.ai)와 양방향으로 연결합니다. 단순한 출력 스트리밍이 아니라 권한, 도구 호출, 파일 편집까지 원격에서 제어할 수 있는 완전한 양방향 파이프라인입니다.

다루는 소스 파일: bridge/server.tsbridge/transport.tsbridge/flushGate.tsbridge/permissions.tsremote-control/

핵심 구성 요소:

02

아키텍처: 데이터 흐름

브릿지는 두 방향의 데이터 흐름을 서로 다른 전송 메커니즘으로 처리합니다.

flowchart LR A["로컬 CLI 세션"] -->|"아웃바운드: SSE 스트림"| B["클라우드 인터페이스"] B -->|"인바운드: HTTP POST"| A A -->|"상태 업데이트"| C["FlushGate"] C -->|"배압 제어"| B style A fill:#c47a50,color:#1a1816 style B fill:#7d9ab8,color:#1a1816 style C fill:#6e9468,color:#1a1816

아웃바운드 흐름 (CLI → 클라우드)

CLI의 모든 상태 변경(도구 실행 결과, 어시스턴트 응답 스트리밍, 권한 요청 등)은 SSE(Server-Sent Events) 스트림을 통해 클라우드로 전송됩니다. 이 방향은 연속적이고 실시간입니다.

인바운드 흐름 (클라우드 → CLI)

사용자 입력, 권한 승인/거부, 중단 요청 등은 HTTP POST 요청으로 CLI에 전달됩니다. 각 요청은 독립적이며 멱등성을 가집니다.

// bridge/server.ts — 인바운드 메시지 디스패치
switch (message.type) {
  case 'user_input':
    handleUserInput(message.text)
    break
  case 'permission_response':
    resolvePermission(message.requestId, message.granted)
    break
  case 'abort':
    abortCurrentTurn()
    break
}
03

Bridge v1 vs v2

브릿지 시스템은 두 번의 주요 반복을 거쳤습니다. v1은 단방향 스트리밍에 초점을 맞췄고, v2는 완전한 양방향 제어를 실현했습니다.

특성 Bridge v1 Bridge v2
전송 순수 SSE HybridTransport (SSE + HTTP POST)
방향 단방향 (출력만) 양방향 (입력 + 출력)
권한 처리 로컬 전용 원격 승인/거부 지원
배압 제어 없음 FlushGate
도구 호출 로컬 실행만 원격 트리거 + 결과 스트리밍
주의사항

v1과 v2는 동시에 활성화될 수 없습니다. 클라이언트가 v2 핸드셰이크를 보내면 서버는 v1 SSE 엔드포인트 응답을 거부합니다. 레거시 클라이언트가 v2 서버에 연결하면 업그레이드 필요 응답을 받습니다.

04

전송 레이어: HybridTransport & SSETransport

전송 레이어는 브릿지의 물리적 통신을 추상화합니다.

SSETransport

아웃바운드 전용 전송으로, 클라이언트가 /events 엔드포인트에 GET 요청을 보내면 서버는 SSE 스트림을 열어 상태 변경을 실시간으로 푸시합니다.

// bridge/transport.ts — SSE 이벤트 전송
class SSETransport {
  send(event: BridgeEvent): void {
    const data = JSON.stringify(event)
    this.response.write(`data: ${data}\n\n`)
  }

  keepAlive(): void {
    this.response.write(`: keepalive\n\n`)  // SSE 주석으로 연결 유지
  }
}

HybridTransport

v2에서 도입된 복합 전송입니다. SSETransport를 래핑하면서 인바운드 HTTP POST 핸들러를 추가합니다. 양방향 통신의 핵심 추상화 레이어입니다.

// bridge/transport.ts — HybridTransport 구조
class HybridTransport {
  private sse: SSETransport
  private flushGate: FlushGate

  async send(event: BridgeEvent): Promise<void> {
    await this.flushGate.waitForFlush()  // 배압 대기
    this.sse.send(event)
  }

  handleInbound(req: IncomingMessage): void {
    const message = parseBody(req)
    this.dispatch(message)
  }
}
05

FlushGate: 배압 메커니즘

FlushGate는 클라이언트가 이전 메시지를 소비했음을 확인할 때까지 다음 메시지 전송을 차단하는 배압(backpressure) 메커니즘입니다. 빠른 도구 실행이 느린 네트워크 연결을 압도하는 것을 방지합니다.

// bridge/flushGate.ts — 배압 제어
class FlushGate {
  private pending: Promise<void> | null = null
  private resolve: (() => void) | null = null

  async waitForFlush(): Promise<void> {
    if (this.pending) {
      await this.pending  // 이전 flush 완료 대기
    }
    this.pending = new Promise(r => this.resolve = r)
  }

  ack(): void {
    this.resolve?.()     // 클라이언트가 ACK를 보내면 게이트 해제
    this.pending = null
  }
}
딥 다이브 — 왜 FlushGate가 필요한가?

도구 호출이 연속으로 빠르게 완료되는 경우(예: 여러 파일 읽기), CLI는 초당 수십 개의 이벤트를 생성할 수 있습니다. 클라우드 클라이언트가 이를 렌더링하는 속도는 네트워크 지연과 UI 렌더링 시간에 제약됩니다. FlushGate 없이는 메시지가 서버 측 버퍼에 쌓여 메모리 증가와 UI 끊김을 유발합니다.

ACK 메커니즘은 TCP의 흐름 제어와 유사한 애플리케이션 레벨 배압을 제공합니다. 클라이언트가 /ack 엔드포인트에 POST를 보내면 FlushGate가 해제되어 다음 이벤트 전송이 허용됩니다.

06

권한 브릿지

원격 인터페이스에서 도구 실행 권한을 승인/거부할 수 있어야 합니다. 권한 브릿지는 로컬 권한 시스템과 원격 클라이언트 사이의 프록시 역할을 합니다.

// bridge/permissions.ts — 원격 권한 요청 흐름
async function requestPermissionRemotely(
  tool: ToolName,
  args: ToolArgs,
): Promise<PermissionResult> {
  const requestId = randomUUID()
  // 1. 클라우드에 권한 요청 이벤트 전송
  bridge.send({ type: 'permission_request', requestId, tool, args })
  // 2. 사용자 응답 대기 (승인 또는 거부)
  return await waitForPermissionResponse(requestId)
}

권한 요청은 도구 이름, 인수, 요청 ID를 포함합니다. 클라우드 UI는 이를 표시하고 사용자의 승인/거부를 permission_response 메시지로 반환합니다. 타임아웃 시 자동 거부됩니다.

07

원격 제어 커맨드: 5개 자격 게이트

원격 제어 모드는 보안에 민감하므로 5개의 자격 게이트를 모두 통과해야 활성화됩니다.

  1. 인증 게이트: 유효한 OAuth 토큰이 있고, 토큰이 원격 제어 범위를 포함해야 함
  2. 구독 게이트: 사용자의 구독 플랜이 원격 제어를 지원해야 함 (Max/Team/Enterprise)
  3. 기능 플래그 게이트: GrowthBook 원격 제어 기능 플래그가 활성화되어야 함
  4. 세션 게이트: 세션이 --remote-control 플래그로 시작되어야 함
  5. 네트워크 게이트: 브릿지 서버가 성공적으로 바인딩되고 클라우드와 핸드셰이크를 완료해야 함
// remote-control/gates.ts — 5개 게이트 검증
async function validateRemoteControlEligibility(): Promise<GateResult> {
  const gates = [
    checkAuthGate(),        // OAuth 토큰 + 범위
    checkSubscriptionGate(), // 플랜 레벨
    checkFeatureFlagGate(),  // GrowthBook
    checkSessionGate(),      // --remote-control 플래그
    checkNetworkGate(),      // 브릿지 핸드셰이크
  ]
  const results = await Promise.all(gates)
  return results.every(r => r.passed)
    ? { eligible: true }
    : { eligible: false, reason: results.find(r => !r.passed)!.reason }
}
주의사항

게이트는 순서에 독립적이지만 Promise.all로 병렬 실행됩니다. 하나라도 실패하면 첫 번째 실패 이유가 사용자에게 표시됩니다. 네트워크 게이트는 다른 게이트보다 느릴 수 있으므로 전체 검증 시간은 네트워크 게이트의 지연에 의해 결정됩니다.

08

CCR 통합 & 독립 브릿지 서버

CCR(Claude Code Remote) 환경에서 브릿지는 컨테이너 내부에서 실행되며, 업스트림 프록시를 통해 외부와 통신합니다.

CCR 통합

CCR 컨테이너 내부에서 브릿지 서버는 자동으로 시작됩니다. 업스트림 프록시가 WebSocket 연결을 중계하며, 브릿지는 프록시의 로컬 포트에 바인딩합니다. 환경 변수 CLAUDE_CODE_BRIDGE_PORT가 포트를 지정합니다.

독립 브릿지 서버

로컬 개발 환경에서도 claude bridge-server 명령으로 독립적인 브릿지 서버를 실행할 수 있습니다. 이를 통해 커스텀 클라이언트(IDE 확장, 웹 UI 등)가 CLI 세션에 연결할 수 있습니다.

// 독립 브릿지 서버 시작
$ claude bridge-server --port 3456

// 클라이언트 연결
// SSE: GET http://localhost:3456/events
// 입력: POST http://localhost:3456/message
// ACK:  POST http://localhost:3456/ack
딥 다이브 — 브릿지 보안 모델

독립 브릿지 서버는 localhost에만 바인딩되므로 외부 네트워크 접근이 차단됩니다. 추가로 세션별 토큰이 발급되어 모든 요청에 Authorization: Bearer <token> 헤더가 필요합니다. 토큰 없는 요청은 즉시 401 Unauthorized로 거부됩니다.

09

핵심 요약

핵심 포인트

  • 브릿지 시스템은 아웃바운드(SSE 스트림)와 인바운드(HTTP POST)의 비대칭 설계로 양방향 통신을 구현합니다
  • Bridge v2는 v1의 단방향 스트리밍을 완전한 양방향 제어(권한, 입력, 도구 호출)로 확장했습니다
  • HybridTransport는 SSETransport를 래핑하면서 인바운드 핸들러를 추가하는 복합 전송 추상화입니다
  • FlushGate는 애플리케이션 레벨 배압을 제공하여 빠른 CLI 출력이 느린 클라이언트를 압도하는 것을 방지합니다
  • 원격 제어는 5개의 자격 게이트(인증, 구독, 기능 플래그, 세션, 네트워크)를 모두 통과해야 활성화됩니다
  • 권한 브릿지는 로컬 권한 시스템의 프록시로, 원격 UI에서 도구 실행을 승인/거부할 수 있게 합니다
  • 독립 브릿지 서버는 localhost 바인딩 + 세션 토큰으로 보안을 유지하며, 커스텀 클라이언트 연동을 지원합니다
  • CCR 환경에서는 업스트림 프록시를 통해 브릿지가 외부와 통신하며, 포트는 환경 변수로 설정됩니다
10

지식 확인

퀴즈 — 5문제

Q1. Bridge v2에서 아웃바운드(CLI → 클라우드)와 인바운드(클라우드 → CLI) 흐름에 각각 사용되는 전송 메커니즘은?

  • A) 양방향 모두 WebSocket
  • B) 양방향 모두 HTTP POST
  • C) 아웃바운드는 SSE 스트림, 인바운드는 HTTP POST
  • D) 아웃바운드는 HTTP POST, 인바운드는 SSE 스트림
v2의 HybridTransport는 비대칭 설계입니다. 연속적인 상태 업데이트는 SSE 스트림으로 푸시하고, 이산적인 사용자 입력과 권한 응답은 HTTP POST로 받습니다.

Q2. FlushGate의 주된 목적은 무엇인가요?

  • A) 네트워크 연결이 끊어졌을 때 메시지를 재전송하기 위해
  • B) 클라이언트가 이전 메시지를 소비할 때까지 다음 전송을 대기하여 배압을 제어하기 위해
  • C) 메시지를 배치로 묶어 네트워크 효율을 높이기 위해
  • D) 암호화된 채널을 통해 메시지를 안전하게 전달하기 위해
FlushGate는 애플리케이션 레벨 배압 메커니즘입니다. 클라이언트가 /ack 엔드포인트에 POST를 보내야 게이트가 해제되어 다음 이벤트 전송이 허용됩니다. 이를 통해 빠른 도구 실행이 느린 클라이언트를 압도하는 것을 방지합니다.

Q3. 원격 제어 모드 활성화에 필요한 5개 자격 게이트에 포함되지 않는 것은?

  • A) 로컬 파일시스템 쓰기 권한 확인
  • B) OAuth 토큰의 원격 제어 범위 확인
  • C) GrowthBook 기능 플래그 확인
  • D) 브릿지 서버와 클라우드 간 핸드셰이크 완료 확인
5개 게이트는 인증, 구독, 기능 플래그, 세션(--remote-control 플래그), 네트워크(핸드셰이크)입니다. 로컬 파일시스템 쓰기 권한은 별도의 권한 시스템에서 처리되며 원격 제어 자격 게이트에는 포함되지 않습니다.

Q4. 독립 브릿지 서버의 보안 모델은 어떻게 구성되어 있나요?

  • A) TLS 인증서를 통한 상호 인증
  • B) IP 화이트리스트 기반 접근 제어
  • C) OAuth 토큰을 이용한 클라이언트 인증
  • D) localhost 바인딩 + 세션별 Bearer 토큰
독립 브릿지 서버는 두 가지 보안 레이어를 사용합니다: (1) localhost에만 바인딩하여 외부 네트워크 접근을 차단하고, (2) 세션별 토큰을 발급하여 모든 요청에 Authorization: Bearer 헤더를 요구합니다.

Q5. Bridge v1에서 v2로의 주요 변경점은 무엇인가요?

  • A) SSE에서 WebSocket으로 전송 프로토콜 변경
  • B) JSON에서 Protobuf으로 직렬화 형식 변경
  • C) 단방향 스트리밍에서 양방향 제어(권한, 입력, 도구 호출)로의 확장
  • D) 로컬 전용에서 클라우드 전용 아키텍처로 전환
v1은 CLI 출력을 클라우드에 스트리밍하는 단방향 설계였습니다. v2는 HybridTransport를 도입하여 클라우드에서 CLI로의 입력, 권한 승인/거부, 도구 호출 트리거 등 완전한 양방향 제어를 실현했습니다.
0 / 5