Skip to content

jsdvjx/claude-remote-protocol

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

claude-remote-protocol

Reverse-engineered TypeScript client for the Claude Code Remote (CCR) WebSocket protocol. Built by analyzing live claude.ai frontend traffic via Chrome DevTools Protocol.

Warning: This is an unofficial library based on reverse-engineering the undocumented Claude Code Remote protocol. The protocol may change without notice. Use at your own risk.

Features

  • Full WebSocket session management with automatic reconnection
  • HTTP REST API client for sessions, events, and environments
  • Streaming assistant messages (thinking, text, tool_use)
  • Tool permission handling (allow/deny with input modification)
  • AskUserQuestion flow (single-select & multi-select)
  • Plan mode support (EnterPlanMode / ExitPlanMode)
  • MCP server status and message forwarding
  • Keep-alive and idle timeout management
  • Complete TypeScript types for the entire protocol

Install

npm install claude-remote-protocol

Requires Node.js >= 18 (uses native fetch and WebSocket).

For Node.js < 22 (no native WebSocket), you may need to polyfill globalThis.WebSocket with the ws package.

Quick Start

import { ClaudeClient } from "claude-remote-protocol";

// 1. Create client (pass credentials once)
const client = new ClaudeClient({
  organizationUuid: "your-org-uuid",
  sessionKey: "sk-ant-sid02-...",
  cfClearance: "...",
  userAgent: "Mozilla/5.0 ...",
});

// 2. Connect to an existing session
const session = await client.connect("session_01...", {
  onAssistantMessage(msg) {
    for (const block of msg.message.content) {
      if (block.type === "text") process.stdout.write(block.text ?? "");
    }
  },
  onResult(msg) {
    console.log(`Done: ${msg.num_turns} turns, $${msg.total_cost_usd}`);
  },
});

await session.sendMessage("Hello, Claude!");

// 3. Or create a new session (auto-selects active environment)
const newSession = await client.create({
  model: "claude-sonnet-4-6",
  title: "My Task",
  onAssistantMessage(msg) { /* ... */ },
});

await newSession.sendMessage("Start working on the task.");

// 4. Multiple sessions in parallel
const [s1, s2] = await Promise.all([
  client.connect("session_A", { onResult(m) { console.log("A done"); } }),
  client.connect("session_B", { onResult(m) { console.log("B done"); } }),
]);

// 5. Cleanup
client.disconnectAll();

Architecture

The protocol uses a dual-channel design:

Channel Direction Purpose
WebSocket (/v1/sessions/ws/{id}/subscribe) Server → Client Streaming responses, control requests
HTTP POST (/v1/sessions/{id}/events) Client → Server User messages, tool permissions, interrupts

Messages on the WebSocket are newline-delimited JSON.

Message Flow

Client                          Server
  │                                │
  │──── HTTP POST (user msg) ─────▶│
  │                                │
  │◀──── WS: assistant (stream) ──│  (thinking → text → tool_use)
  │◀──── WS: assistant (stream) ──│  (same msg_id, incremental)
  │◀──── WS: control_request ─────│  (can_use_tool permission)
  │                                │
  │──── WS: control_response ─────▶│  (allow/deny)
  │                                │
  │◀──── WS: assistant (stream) ──│  (tool_result → more text)
  │◀──── WS: result ──────────────│  (execution summary)

API Reference

ClaudeClient

Top-level entry point. Pass credentials once, manage multiple sessions.

const client = new ClaudeClient({
  organizationUuid: string,
  sessionKey: string,          // sk-ant-sid02-...
  cfClearance: string,         // Cloudflare cf_clearance cookie
  userAgent: string,           // must match browser that generated cfClearance
  cookie?: string,             // full cookie override (sessionKey/cfClearance ignored)
  baseUrl?: string,            // default: "https://claude.ai"
});

// Connect to existing session (returns connected SessionManager)
const session = await client.connect(sessionId, {
  replay?: boolean,
  maxRetries?: number,
  idleTimeout?: number,
  onAssistantMessage?, onResult?, onToolPermission?, onError?,
});

// Create new session + connect (auto-selects environment if omitted)
const session = await client.create({
  environmentId?: string,
  model?: string,           // default: "claude-sonnet-4-6"
  title?: string,
  sessionContext?: Partial<SessionContext>,
  // ...same callbacks as connect
});

await client.listSessions();
await client.listEnvironments();
client.getConnected(sessionId);   // get a previously connected session
client.disconnect(sessionId);     // disconnect one
client.disconnectAll();           // disconnect all

ClaudeApi

HTTP REST client for session management (also accessible via client.api).

const api = new ClaudeApi({
  organizationUuid: string,
  sessionKey: string,
  cfClearance: string,
  userAgent: string,
  cookie?: string,             // full cookie override
  baseUrl?: string,
});

await api.listSessions();
await api.getSession(sessionId);
await api.createSession({ environment_id, session_context });
await api.updateSession(sessionId, { title?, session_status? });
await api.listEvents(sessionId, afterId?);
await api.sendMessage({ sessionId, uuid, message });
await api.interrupt(sessionId);
await api.respondToolPermission({ sessionId, requestId, toolName, decision });
await api.setPermissionMode(sessionId, mode);
await api.listEnvironments();

SessionManager

High-level WebSocket session manager.

const session = new SessionManager({
  // Required
  organizationUuid: string,
  sessionKey: string,
  cfClearance: string,
  userAgent: string,
  sessionId: string,

  // Optional
  replay?: boolean,           // Replay missed messages on connect
  maxRetries?: number,        // Reconnect attempts (default: 5)
  idleTimeout?: number,       // Idle disconnect in ms (default: 300000)
  baseUrl?: string,
  wsHost?: string,            // Custom WebSocket host

  // Callbacks
  onAssistantMessage?: (msg: WsAssistantMessage) => void,
  onResult?: (msg: WsResultMessage) => void,
  onToolPermission?: ToolPermissionHandler,
  onHookCallback?: HookCallbackHandler,
  onStateChange?: (state: ConnectionState) => void,
  onError?: (error: Error) => void,
});

await session.connect();
await session.sendMessage("Hello!");
await session.interrupt();
await session.setModel("claude-sonnet-4-6");
await session.setPermissionMode("acceptEdits");
await session.setMaxThinkingTokens(10000);
session.disconnect();

Streaming Pattern

Assistant messages arrive incrementally with the same msg_id. Content blocks build up over time:

  1. First message: [thinking]
  2. Next: [thinking, text]
  3. Next: [thinking, text, tool_use]
  4. Final: stop_reason changes from null to "end_turn" or "tool_use"

Tool Permission Flow

When Claude wants to use a tool, the server sends a can_use_tool control request. Your handler decides whether to allow or deny:

onToolPermission: async (toolName, input, { toolUseID, signal, suggestions }) => {
  if (toolName === "Bash") {
    return { behavior: "deny", message: "Bash not allowed" };
  }
  return { behavior: "allow", updatedInput: input };
},

Content Block Types

Type Fields Description
text text Plain text output
thinking thinking, signature Extended thinking (encrypted signature)
tool_use id, name, input Tool invocation
tool_result tool_use_id, content, is_error Tool execution result
tool_reference tool_name Reference to a tool
image Image content
resource Resource reference

Protocol Documentation

See PROTOCOL.md for the complete protocol specification with all message types and flows.

Authentication

All credentials can be extracted from any claude.ai page (homepage, settings, any conversation — no need to open a specific session).

Step-by-step extraction

1. Open DevTools — Press F12 on any claude.ai page

2. Get sessionKey and cfClearance from Cookies:

  • Go to Application tab → Cookieshttps://claude.ai
  • Find sessionKey — value starts with sk-ant-sid02-...
  • Find cf_clearance — a long alphanumeric string

3. Get organizationUuid:

  • Option A: In ApplicationCookies, find lastActiveOrg — that's your org UUID
  • Option B: In Network tab, click any request to claude.ai/v1/..., check the x-organization-uuid request header

4. Get userAgent:

  • Go to Console tab, type navigator.userAgent and press Enter
  • Copy the full string (e.g. Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...)
  • Important: This must be the exact User-Agent of the browser that generated the cf_clearance cookie. Using a different User-Agent will result in Cloudflare 403 errors.

Notes

  • cf_clearance expires periodically (typically when Cloudflare re-challenges). If you get 403 errors, re-extract it.
  • sessionKey is long-lived but can be invalidated by logging out.
  • All four cookies are set at the claude.ai domain level, so they are the same on every page.

Usage

import { ClaudeClient } from "claude-remote-protocol";

const client = new ClaudeClient({
  organizationUuid: "ed81b697-...",
  sessionKey: "sk-ant-sid02-...",
  cfClearance: "DrW9nrPr...",
  userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...",
});

Programmatic extraction (CDP)

If you have a Chrome instance with remote debugging enabled (--remote-debugging-port=9222), you can extract credentials programmatically:

// Get cookies via Chrome DevTools Protocol
const resp = await fetch("http://localhost:9222/json");
const [tab] = await resp.json();
// Connect to tab via WebSocket, then:
// - Network.getCookies({ urls: ["https://claude.ai"] }) → sessionKey, cf_clearance, lastActiveOrg
// - Browser.getVersion() → userAgent

License

MIT

About

Reverse-engineered TypeScript client for the Claude Code Remote (CCR) WebSocket protocol

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors