npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

claude-remote-protocol

v0.3.2

Published

Reverse-engineered TypeScript client for the Claude Code Remote (CCR) WebSocket protocol. Connect to Claude.ai sessions, send messages, handle tool permissions, and receive streaming responses.

Readme

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