agent-client-protocol-trace-viewer is a debugging tool that visualizes message flow between ACP components as an interactive sequence diagram.
When debugging ACP proxy chains, understanding the flow of messages between components is critical. The trace viewer provides:
- Visual sequence diagrams showing messages flowing between Client, Proxies, and Agent
- Request/response correlation with rainbow colors linking each request to its response
- Timeline spans showing when components are actively processing requests
- Delta times on each component's timeline showing elapsed time between events
- Content preview for
session/updatenotifications showing message text inline - Filter controls to show/hide ACP, MCP, and response messages
- Adjustable layout with a slider to control swimlane width
The system has two parts:
- Event capture (in
agent-client-protocol-conductor) - appends structured JSON events to a.jsonsfile - Viewer (in
agent-client-protocol-trace-viewer) - serves an interactive HTML/JS visualization
# Run conductor with tracing enabled
agent-client-protocol-conductor agent --trace ./trace.jsons "proxy1" "proxy2" "agent"
# View the trace (works during or after the run)
agent-client-protocol-trace-viewer ./trace.jsons
# Opens browser to http://localhost:PORTEvents are stored as newline-delimited JSON (.jsons file). Each line is a self-contained event.
All events share these fields:
interface BaseEvent {
// Monotonic timestamp (seconds since trace start, f64)
ts: number;
// Event type discriminator
type: "request" | "response" | "notification" | "trace";
}A JSON-RPC request from one component to another.
interface RequestEvent extends BaseEvent {
type: "request";
// Protocol: "acp" for ACP messages, "mcp" for MCP-over-ACP messages
protocol: "acp" | "mcp";
// Source component (e.g., "client", "proxy:0", "proxy:1", "agent")
from: string;
// Destination component
to: string;
// JSON-RPC request ID (for correlating with response)
id: number | string;
// JSON-RPC method name
method: string;
// ACP session ID, if known (null before session/new completes)
session: string | null;
// Full request params (may be large)
params: object;
}A JSON-RPC response to a prior request.
interface ResponseEvent extends BaseEvent {
type: "response";
// Source component (who sent the response)
from: string;
// Destination component (who receives the response)
to: string;
// JSON-RPC request ID this responds to
id: number | string;
// True if this is an error response
is_error: boolean;
// Response result or error object
payload: object;
}A JSON-RPC notification (no response expected).
interface NotificationEvent extends BaseEvent {
type: "notification";
// Protocol: "acp" for ACP messages, "mcp" for MCP-over-ACP messages
protocol: "acp" | "mcp";
from: string;
to: string;
method: string;
session: string | null;
params: object;
}A tracing log line from a component (captured from stderr or tracing subscriber).
interface TraceEvent extends BaseEvent {
type: "trace";
// Which component emitted this log
component: string;
// Log level
level: "trace" | "debug" | "info" | "warn" | "error";
// Log message
message: string;
// Optional structured fields from tracing spans
fields?: Record<string, unknown>;
}Components are identified by strings:
"client"- the upstream client (editor/IDE)"proxy:0","proxy:1", etc. - proxy components by index"agent"- the final agent component
The viewer displays these as swimlane columns in the sequence diagram.
The main view is a vertical timeline with component columns (swimlanes):
client proxy:0 agent
│ │ │
│──init────>│ 14ms │
│ 97ms │──init────────>│
│ │ 74ms │<── orange (request/response pair)
│ │<╌╌╌╌╌╌╌╌╌╌╌╌╌╌│
│<╌╌╌╌╌╌╌╌╌╌│ │
│ │ │
│──prompt──>│ │
│ ║ 15ms │<── thickened timeline = processing
│ │──prompt──────>│
│ │ 235ms │
│ │<──tools/list──● (MCP: circular arrowhead)
│ │╌╌╌╌╌╌╌╌╌╌╌╌╌╌>│
│ │ │
- Requests/notifications - solid arrows with method name label, colored by request/response pair
- Responses - dashed arrows in the same color as their originating request
- MCP vs ACP - MCP messages use circular arrowheads; ACP uses triangular
- Timeline spans - when a component receives a request, its timeline thickens (in the pair's color) until the response is sent
- Delta times - elapsed time shown on each component's timeline between its events
- Content preview -
session/updatenotifications show truncated message content below the arrow - Error responses - highlighted in red
- Click message - expands full JSON payload in resizable bottom panel
- Filter checkboxes - show/hide ACP messages, MCP messages, responses
- Width slider - adjust swimlane column width (80-300px)
Each request/response pair gets a unique color from a rainbow palette. This makes it easy to visually trace a request through the proxy chain to its response, even when many messages are interleaved.
The trace shows the logical message flow between components, hiding conductor implementation details. The conductor transforms internal messages before logging:
| Internal Message | Logged As |
|---|---|
_proxy/successor/foo from proxy N |
foo from proxy N to proxy N+1 (protocol: acp) |
_mcp/request with inner method bar |
bar from agent to proxy (protocol: mcp) |
_mcp/notify with inner method baz |
baz from agent to proxy (protocol: mcp) |
This means the viewer shows what conceptually happened without exposing the routing machinery.
CLI usage:
agent-client-protocol-conductor agent --trace ./trace.jsons "proxy1" "proxy2" "agent"Programmatic usage:
Conductor::new(name, components, mcp_bridge_mode)
.trace_to("./trace.jsons")
.run(transport)
.awaitImplementation:
- Capture happens in
handle_conductor_messagewhere all messages flow - Conductor transforms messages to idealized form before logging
- Events appended as JSON lines (one
writeln!per event) - File opened in append mode, flushed after each event
- Single binary with embedded static assets (HTML/JS/CSS via
include_str!) - Axum HTTP server with two endpoints:
GET /- serves the viewer HTML (single-page app)GET /events- returns trace events as JSON array
- Vanilla JS renders SVG sequence diagram (no build step, no npm)
- Auto-opens browser on launch (disable with
--no-open)