JSON-RPC Protocol Used by the Codex App Server: Implementation and Message Specification
The Codex App Server uses a strict JSON-RPC 2.0 protocol defined in app-server-protocol.d.ts, where clients send Request objects with unique IDs to receive typed Response objects, while Notifications enable fire-and-forget messaging without return values.
The openai/codex-plugin-cc repository implements a lightweight yet type-safe communication layer between the Codex App Server and client applications like the Codex Companion CLI. This JSON-RPC protocol governs how commands such as run, status, and cancel are transmitted, validated, and routed across WebSocket and HTTP connections.
JSON-RPC 2.0 Protocol Specification
The protocol strictly adheres to the JSON-RPC 2.0 specification, enforcing a version string of "2.0" on every message. Located at plugins/codex/scripts/lib/app-server-protocol.d.ts, the TypeScript definitions declare a discriminated union type Message that encompasses all valid communication patterns:
export type Message = Request | Notification | Response | ErrorMessage;
export interface Request {
jsonrpc: "2.0";
id: number | string;
method: string;
params?: unknown;
}
export interface Notification {
jsonrpc: "2.0";
method: string;
params?: unknown;
}
export interface Response {
jsonrpc: "2.0";
id: number | string;
result?: unknown;
error?: ErrorObject;
}
export interface ErrorObject {
code: number;
message: string;
data?: unknown;
}
This schema ensures that every payload carries the mandatory jsonrpc field while differentiating between calls that require responses (Requests) and those that do not (Notifications).
Core Message Types and Type Definitions
Request Messages
Request objects represent the primary mechanism for clients to invoke server-side operations. Each Request must include a unique id (string or number) that the server echoes back in the corresponding Response, enabling asynchronous correlation. The method field specifies the command to execute—such as run for executing code or status for checking job progress—while params optionally carries method-specific arguments.
Notification Messages
Notifications follow the same structure as Requests but omit the id field entirely. These fire-and-forget messages inform the server of events without expecting acknowledgment. In plugins/codex/scripts/lib/app-server.mjs, the dispatcher inspects incoming JSON to detect the presence of an id property, routing Notifications to handlers that do not generate return traffic.
Response and Error Handling
Response objects map to previous Requests via matching id values. A valid Response contains either a result field with the operation output or an error field containing an ErrorObject with code, message, and optional data. The app-server-broker.mjs file manages these mappings, ensuring that results from worker processes reach the correct client connection.
Server Architecture and Routing
The app-server.mjs file implements the core dispatch logic that parses incoming JSON strings into the Message union. Upon receiving a payload, the server validates the jsonrpc version, then branches based on message type:
- Requests: Routed to command handlers (e.g.,
runCommand) that generate asynchronous results, eventually wrapped in aResponseobject and sent back to the client. - Notifications: Handled immediately by dedicated functions in
app-server-broker.mjswithout generating return values.
The broker file (plugins/codex/scripts/app-server-broker.mjs) manages multiple concurrent client connections, maintaining request ID mappings to ensure that Responses reach the initiators even when multiple clients interact with the server simultaneously.
Client Implementation Examples
Sending Commands from codex-companion.mjs
The Codex Companion CLI constructs JSON-RPC Requests to execute commands. Below is a TypeScript example demonstrating how to build and send a run request:
import { Request } from "./app-server-protocol";
function sendRunCommand(socket: WebSocket, command: string): void {
const request: Request = {
jsonrpc: "2.0",
id: Date.now(), // Unique identifier for response matching
method: "run",
params: { command },
};
const payload = JSON.stringify(request);
socket.send(payload); // Transmitted to app-server.mjs
}
Processing Results
Clients listen for incoming messages and correlate them with pending requests using the id field:
socket.addEventListener("message", (event) => {
const msg = JSON.parse(event.data);
if (msg.id === pendingRequestId) {
if (msg.result) {
console.log("Execution successful:", msg.result);
} else if (msg.error) {
console.error(`Error ${msg.error.code}: ${msg.error.message}`);
}
}
});
Server-Side Message Dispatch
The server implementation parses raw strings and routes them according to the protocol definition:
import { Message, Request, Response } from "./app-server-protocol";
function handleMessage(raw: string, ws: WebSocket): void {
const msg: Message = JSON.parse(raw);
if ("id" in msg) {
// Request: requires a Response
const response: Response = {
jsonrpc: "2.0",
id: msg.id,
result: executeMethod(msg.method, msg.params),
};
ws.send(JSON.stringify(response));
} else {
// Notification: fire-and-forget
handleNotification(msg);
}
}
Summary
- The Codex App Server strictly implements JSON-RPC 2.0 as defined in
plugins/codex/scripts/lib/app-server-protocol.d.ts. - Requests include unique
idfields and expectResponseobjects containing eitherresultorerrordata. - Notifications lack
idfields and execute without server acknowledgment, handled byapp-server-broker.mjs. - The server dispatcher in
app-server.mjsvalidates thejsonrpcversion and routes messages based on the presence of anidproperty. - Client implementations like
codex-companion.mjsserialize TypeScript objects into JSON-RPC payloads for commands such asrun,status, andcancel.
Frequently Asked Questions
What version of JSON-RPC does the Codex App Server use?
The Codex App Server implements JSON-RPC 2.0, as enforced by the jsonrpc: "2.0" field requirement in the TypeScript interfaces defined in app-server-protocol.d.ts. This ensures compatibility with standard JSON-RPC clients and strict validation of incoming message structures.
How does the server distinguish between requests and notifications?
The server checks for the presence of an id field in the parsed JSON object. According to the protocol definition in app-server-protocol.d.ts, Requests contain an id (string or number), while Notifications omit this field entirely. The dispatch logic in app-server.mjs uses this check to determine whether to generate a Response.
Where are the protocol types defined?
All message type definitions—including Request, Notification, Response, and ErrorObject—are located in plugins/codex/scripts/lib/app-server-protocol.d.ts. This file serves as the central schema reference for both server and client implementations in the repository.
Can clients send multiple concurrent requests?
Yes, clients can send multiple concurrent requests by ensuring each Request carries a unique id value. The server maintains these IDs through the broker layer (app-server-broker.mjs), matching asynchronous responses to the correct client connections and original request identifiers.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →