How Does the OpenAI Codex Plugin Communicate with Codex via the App Server Protocol?

The Codex plugin communicates with Codex through a JSON-RPC-based App Server protocol implemented by the CodexAppServerClient class, using STDIN/STDOUT transport for bidirectional request-response and notification handling.

The openai/codex-plugin-cc repository implements a lightweight, line-delimited JSON-RPC protocol that enables plugin commands to spawn Codex processes, execute turns, run code reviews, and receive asynchronous updates. This article explains the protocol architecture, transport mechanisms, and implementation details drawn directly from the source code.

App Server Protocol Architecture

The protocol stack consists of three layers: the TypeScript interface definitions, the core client implementation, and high-level workflow helpers. This design separates protocol semantics from transport concerns, allowing the same client code to work with both spawned processes and brokered connections.

Protocol Definition Layer

All method signatures, parameter shapes, and notification types are defined in plugins/codex/scripts/lib/app-server-protocol.d.ts. This file serves as the contract between the plugin and the Codex service.

Key RPC methods include:

  • initialize — Handshake with InitializeParams and InitializeResponse
  • thread/start — Begin a conversation thread
  • turn/start — Execute a normal Codex turn with TurnStartParams
  • turn/interrupt — Cancel an in-progress turn
  • review/start — Initiate a code review session

The protocol also defines AppServerNotification for server-pushed updates like progress events and message deltas.

Core Client Implementation

The CodexAppServerClient class in plugins/codex/scripts/lib/app-server.mjs encapsulates all communication logic. It extends AppServerClientBase, which handles raw I/O operations.

Connection Establishment

The static connect() method determines whether to spawn a new app-server process or connect through a persistent broker:

export class CodexAppServerClient {
  // Decision point: spawn vs. broker
  // https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L335
}

Broker mode activates when the environment variable CODEX_COMPANION_APP_SERVER_ENDPOINT is set. The broker lifecycle management resides in plugins/codex/scripts/lib/broker-lifecycle.mjs, while endpoint parsing is handled by broker-endpoint.mjs.

Transport and Framing

AppServerClientBase manages low-level communication over the child process's STDIN/STDOUT streams:

  • Each request receives a monotonically increasing numeric id via nextId
  • Pending requests are tracked in this.pending as promise resolvers
  • Responses are matched to pending requests by id
  • Notifications route to a handler set via setNotificationHandler
class AppServerClientBase {
  // JSON-RPC framing and pending-request map
  // https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L57-L66
}

Request and Notification API

The client exposes two primary methods for communication:

client.request(method, params)   // Returns Promise<RpcResult>
client.notify(method, params)    // Fire-and-forget notification

These serialize payloads to line-delimited JSON, write to STDIN, and parse responses:

// https://github.com/open/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L80-L98

High-Level Workflow Helpers

The companion script plugins/codex/scripts/codex-companion.mjs provides convenience wrappers that plugin commands invoke directly.

runAppServerTurn

Executes a standard Codex turn with automatic client lifecycle management:

export async function runAppServerTurn(cwd, options = {}) {
  return withAppServer(cwd, async (client) => {
    // https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L1095-L1101
  });
}

runAppServerReview

Initiates a code review session with streaming updates:

export async function runAppServerReview(cwd, options = {}) {
  return withAppServer(cwd, async (client) => {
    // https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L1002-L1008
  });
}

Both helpers use withAppServer() to ensure proper cleanup regardless of success or failure.

Handling Asynchronous Notifications

Codex pushes progress updates, message fragments, and state changes via notifications. The plugin registers a handler to process these:

client.setNotificationHandler((msg) => {
  if (msg.method === "item/agentMessage/delta") {
    console.log("Agent message:", msg.params);
  }
});

The msg type is AppServerNotification from the protocol definition:

// Notification type definitions
// https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server-protocol.d.ts#L74-L75

Complete Usage Example

Below is a minimal implementation showing initialization, turn execution, and notification handling:

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

async function startTurn(workspaceRoot: string) {
  // Establish connection (spawn or broker)
  const client = await CodexAppServerClient.connect(workspaceRoot);
  
  // Protocol handshake
  const init = await client.request("initialize", {
    clientInfo: {
      title: "My Plugin",
      name: "MyAgent",
      version: "1.0.0"
    },
    capabilities: {
      experimentalApi: false,
      requestAttestation: false,
      optOutNotificationMethods: []
    }
  });
  
  // Start the turn
  const turn = await client.request("turn/start", {
    /* TurnStartParams */
  });
  
  console.log("Turn started:", turn);
}

Key Source Files

File Purpose
plugins/codex/scripts/lib/app-server.mjs Core CodexAppServerClient implementation, transport layer, RPC framing
plugins/codex/scripts/lib/app-server-protocol.d.ts TypeScript definitions for all protocol methods and types
plugins/codex/scripts/codex-companion.mjs High-level helpers: runAppServerTurn, runAppServerReview
plugins/codex/scripts/lib/broker-endpoint.mjs Environment variable parsing for broker connections
plugins/codex/scripts/lib/broker-lifecycle.mjs Persistent broker session management

Summary

  • JSON-RPC over STDIN/STDOUT forms the transport substrate for all Codex plugin communication
  • CodexAppServerClient.connect() abstracts away spawn vs. broker connection modes
  • Request/response matching uses numeric IDs with a pending promise map
  • Notifications enable asynchronous server-pushed updates via configurable handlers
  • High-level helpers in codex-companion.mjs provide the interface that plugin commands actually invoke

Frequently Asked Questions

What triggers broker mode instead of spawning a new process?

Broker mode activates when the CODEX_COMPANION_APP_SERVER_ENDPOINT environment variable is set. This variable specifies the broker address, causing CodexAppServerClient.connect() to establish a persistent connection rather than spawning a fresh app-server process. Broker mode reduces startup latency when multiple plugin instances run concurrently.

How does the client handle malformed or unexpected JSON-RPC messages?

The AppServerClientBase implementation validates incoming JSON against expected structures. Messages lacking required fields or containing mismatched response IDs are logged and discarded. The pending request map ensures that orphaned promises eventually timeout rather than hanging indefinitely.

Can multiple concurrent requests be in flight simultaneously?

Yes. The client maintains this.pending as a Map from request ID to promise resolver, allowing multiple overlapping requests. Each request receives a unique ID from nextId, and responses are dispatched to the correct resolver by matching that ID. This enables parallel operations like initializing while streaming notifications.

Where are notification handler types defined?

Notification handler signatures and the AppServerNotification discriminated union are declared in app-server-protocol.d.ts. The runtime handler is registered via client.setNotificationHandler(), and the TypeScript compiler ensures type safety for the callback parameter based on the protocol definitions.

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 →