Codex App Server JSON-RPC Protocol Implementation: Architecture and Usage

The Codex app server JSON-RPC protocol implementation uses JSON-RPC 1.0 over a line-delimited JSON stream (JSONL), featuring TypeScript protocol definitions, a base client for request correlation, and dual transport adapters for child processes and Unix domain sockets.

The openai/codex-plugin-cc repository contains a lightweight client implementation that enables the Codex plugin to communicate with the app server for interactive code review and thread management. This protocol handles method invocation, error propagation, and asynchronous notifications through a structured JSON message exchange.

Protocol Architecture and Definitions

The implementation adheres to the JSON-RPC 1.0 specification transmitted over JSONL (line-delimited JSON). This transport choice enables real-time streaming communication without HTTP overhead, making it suitable for local process spawning and socket-based broker connections.

TypeScript Protocol Definitions

All RPC methods, parameters, return types, and notification schemas are strictly defined in plugins/codex/scripts/lib/app-server-protocol.d.ts. This file exports the AppServerMethodMap interface, which acts as the source of truth for available operations such as thread/start and their corresponding parameter types (ThreadStartParams).

Key file: plugins/codex/scripts/lib/app-server-protocol.d.ts

Extending the protocol requires adding entries to AppServerMethodMap; the base client automatically handles serialization and type enforcement based on these definitions.

Core Client Implementation

The client architecture separates transport logic from protocol handling through inheritance, allowing the same JSON-RPC semantics to operate over different communication channels.

Base Client: AppServerClientBase

The abstract class AppServerClientBase in plugins/codex/scripts/lib/app-server.mjs implements the core JSON-RPC state machine. It manages:

  • Request ID generation: Monotonically increasing numeric identifiers
  • Pending request tracking: A Map storing {resolve, reject} pairs keyed by request ID
  • Message parsing: Line-by-line JSON validation and routing

Key method: request(method, params) (lines 86‑98) generates a unique ID, stores the pending promise, and writes the JSON-encoded line to the transport.

Key method: handleLine (lines 124‑160) parses incoming JSON and dispatches to:

  • Response handling (lines 136‑149) for IDs without methods
  • Notification handling (lines 151‑154) for methods without IDs
  • Server request rejection (lines 156‑160) via handleServerRequest

Transport Adapters

Two concrete subclasses extend the base client to handle specific transport mechanisms:

SpawnedCodexAppServerClient

  • Launches a local codex app-server process
  • Communicates over the child process's stdin/stdout pipes
  • Implements sendMessage for stream-based writing

BrokerCodexAppServerClient

  • Connects to a Unix-domain socket when the environment variable CODEX_COMPANION_APP_SERVER_ENDPOINT is set
  • Used for attaching to existing broker processes
  • Shares the same sendMessage interface but writes to a socket

Source file: plugins/codex/scripts/lib/app-server.mjs (sections class SpawnedCodexAppServerClient and class BrokerCodexAppServerClient)

Request-Response Lifecycle

Understanding the message flow is essential for implementing custom clients or debugging communication issues.

Connection and Initialization

The static factory method CodexAppServerClient.connect() (lines 352‑360) determines the transport mode based on environment variables and instantiates the appropriate adapter. After establishing the transport, the client automatically sends an initialize request containing clientInfo and capabilities, followed by a client/ready notification (lines 225‑229).

Making RPC Requests

Call the request method with a method name defined in AppServerMethodMap:

const thread = await client.request("thread/start", {
  title: "Bug fix session",
  language: "typescript"
});

The client automatically assigns a numeric ID, serializes the message to JSON, and appends a newline. The promise resolves when a matching response ID arrives or rejects if an error object is returned.

Handling Asynchronous Messages

The protocol supports two server-initiated message types:

  • Notifications: Messages containing a method field but no id are forwarded to the optional notification handler set via setNotificationHandler()
  • Server Requests: Messages containing both id and method receive an automatic JSON-RPC error response with code -32601 (Method Not Found), as the client does not expose callable methods to the server

Error Handling Strategy

The implementation distinguishes between transport failures and protocol-level errors through specific error construction.

ProtocolError Construction

The createProtocolError() function (lines 48‑55) generates error objects with an optional rpcCode property. Standard JSON-RPC error codes are used, including:

  • -32001: Broker busy
  • -32601: Method not found (for server requests)

Source: createProtocolError in plugins/codex/scripts/lib/app-server.mjs

JSON Parsing Resilience

Malformed JSON lines trigger protocol errors with descriptive messages including the offending line content (lines 124‑128). These errors propagate through the notification stream rather than crashing the client, allowing graceful degradation during transport interruptions.

Practical Usage Examples

Connecting to a Spawned App Server

import { CodexAppServerClient } from 
  "plugins/codex/scripts/lib/app-server.mjs";

async function startReview() {
  // Connect spawns a local process unless broker endpoint is set
  const client = await CodexAppServerClient.connect(process.cwd());
  
  // Handle server notifications
  client.setNotificationHandler((msg) => {
    console.log("Notification:", msg.method, msg.params);
  });
  
  // Start a review thread
  const thread = await client.request("thread/start", {
    title: "Security Review",
    language: "python"
  });
  
  console.log("Thread ID:", thread.id);
  await client.close();
}

Connecting via Broker Endpoint

// Set environment variable before importing
process.env.CODEX_COMPANION_APP_SERVER_ENDPOINT = "/tmp/codex-broker.sock";

const client = await CodexAppServerClient.connect(process.cwd(), {
  reuseExistingBroker: true
});

// Now communicates through Unix socket instead of spawned process

Summary

  • Protocol: JSON-RPC 1.0 over JSONL in plugins/codex/scripts/lib/app-server.mjs
  • Definitions: TypeScript interfaces in plugins/codex/scripts/lib/app-server-protocol.d.ts
  • Transports: Child process spawning (SpawnedCodexAppServerClient) and Unix socket connections (BrokerCodexAppServerClient)
  • Correlation: Numeric request IDs map promises to responses via the pending Map in the base client
  • Errors: Structured ProtocolError objects with standard JSON-RPC codes generated by createProtocolError()

Frequently Asked Questions

What version of JSON-RPC does the Codex app server use?

The implementation uses JSON-RPC 1.0 transported over line-delimited JSON streams. This version predates the 2.0 specification but provides sufficient functionality for request/response correlation and notification handling through the method, params, id, and error fields.

How does the client handle server-initiated requests?

When the server sends a message containing both an id and a method field, the client treats it as an incoming server request. The handleServerRequest method (lines 156‑160) automatically responds with a JSON-RPC error object containing code -32601 (Method Not Found), as the client implementation does not expose callable methods to the server.

What transport mechanisms are supported?

The protocol supports two transport mechanisms: stdin/stdout pipes via SpawnedCodexAppServerClient for locally spawned processes, and Unix domain sockets via BrokerCodexAppServerClient when the CODEX_COMPANION_APP_SERVER_ENDPOINT environment variable points to a socket path. Both extend the same base class and implement the abstract sendMessage method.

Where are protocol errors defined and handled?

Protocol errors are constructed by the createProtocolError() function in plugins/codex/scripts/lib/app-server.mjs (lines 48‑55). These errors include an optional rpcCode field following JSON-RPC conventions. Transport-level JSON parsing failures are caught in the handleLine method and converted to protocol errors containing the raw line content for debugging purposes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →