Skip to content

Latest commit

 

History

History
167 lines (128 loc) · 5.7 KB

trace-viewer.md

File metadata and controls

167 lines (128 loc) · 5.7 KB

Trace Viewer

agent-client-protocol-trace-viewer renders conductor message traces as an interactive sequence diagram. Capture lives in agent-client-protocol-conductor; the viewer can read the resulting file while the conductor is running or after it exits.

Capture and View

Conductor options are global and therefore precede the subcommand:

agent-client-protocol-conductor --trace ./trace.jsons agent \
  "proxy-one" "base-agent"

agent-client-protocol-trace-viewer ./trace.jsons

The standalone viewer chooses a loopback port and opens a browser. Use --port PORT to choose the port or --no-open to suppress browser launch.

The conductor can also host the viewer directly:

# In-memory live trace
agent-client-protocol-conductor --serve agent "proxy-one" "base-agent"

# File-backed live trace
agent-client-protocol-conductor --trace ./trace.jsons --serve agent \
  "proxy-one" "base-agent"

Starting file capture truncates an existing trace file. Each event is flushed as one JSON object followed by a newline.

Recording Privacy

Explicit recordings intentionally retain prompts, image/file content, and other protocol payloads. Treat both files and in-memory viewer data as sensitive. They are not the metadata-only HTTP transport diagnostics.

The conductor redacts, in the trace copy only, every value of an env or headers field, keeping the variable and header names. This covers MCP server declarations in session/new, session/load, session/resume, and session/fork (v1 and v2), terminal auth methods, terminal/create, and provider configuration. Fields are matched by name anywhere in a payload, so unrelated env or headers fields are redacted too.

It also redacts HTTP(S) url fields. URL userinfo is removed. Query values are replaced with [REDACTED] for these recognized keys, case-insensitively after percent decoding, ignoring hyphens and underscores:

  • token, access_token, refresh_token, id_token, api_key, key
  • secret, client_secret, password, passwd, pwd
  • auth, authorization, bearer, signature, sig, credential, credentials

Repeated and valueless credential parameters follow the same rule. Unparsable HTTP(S) URLs are redacted in full. Credential-free URLs remain unchanged; redacted URLs may be canonicalized, but non-secret query pair spelling, order, and encoding are retained. Redaction never changes the original wire message.

This is not a general-purpose payload sanitizer: arbitrary custom credential fields/query keys, URL path or fragment secrets, and secrets embedded in prompt text cannot all be detected. Review recordings before sharing them.

The --debug log applies the same redaction to protocol lines on component stdin/stdout and to JSON component configurations. It records stderr as-is.

Event Schema

The .jsons file contains exactly three event variants: request, response, and notification. It does not capture stderr or general tracing log records.

type TraceEvent = RequestEvent | ResponseEvent | NotificationEvent;

interface RequestEvent {
  type: "request";
  ts: number;
  protocol: "acp" | "mcp";
  from: string;
  to: string;
  id: unknown;
  method: string;
  session?: string;
  params: unknown;
}

interface ResponseEvent {
  type: "response";
  ts: number;
  from: string;
  to: string;
  id: unknown;
  is_error: boolean;
  error_domain?: "acp" | "mcp";
  payload: unknown;
}

interface NotificationEvent {
  type: "notification";
  ts: number;
  protocol: "acp" | "mcp";
  from: string;
  to: string;
  method: string;
  session?: string;
  params: unknown;
}

ts is monotonic seconds since capture began. JSON-RPC IDs may be strings, integers, or null; consumers must not assume they are numbers. The optional session field is omitted when the tracer has no session context; it is not serialized as null.

error_domain is present on error responses and says whether the error came from the ACP binding or the inner MCP peer. Older traces omit it.

from and to are Client, Proxy(N) for the proxy at index N (starting at 0), or Agent for the conductor's successor: the agent, or the outer conductor when the conductor runs as a proxy.

Idealized Message Flow

The trace shows logical component-to-component traffic rather than conductor plumbing:

  • proxy/successor is unwrapped and logged as its inner ACP method.
  • mcp/message is unwrapped and its inner method is marked with protocol mcp.
  • Responses are correlated with the request details retained by the trace writer.

The snooping bridges carry complete TransportFrame values, so enabling tracing relays each wire value unchanged, including malformed input that the viewer does not render.

Viewer Features

The web UI provides ACP/MCP/response filters, request-response color pairing, active-request spans, elapsed-time labels, session/update text previews, a resizable JSON detail panel, and adjustable swimlane width. The file-backed server rereads the trace on each poll so new events appear during capture.

Programmatic Capture

use agent_client_protocol_conductor::{ConductorImpl, ProxiesAndAgent};

let conductor = ConductorImpl::new_agent(
    "conductor",
    ProxiesAndAgent::new(agent).proxy(proxy),
)
.trace_to_path("./trace.jsons")?;

conductor.run(upstream_transport).await?;

Use trace_to with a custom implementation of agent_client_protocol_conductor::trace::WriteEvent to send events somewhere other than a file. with_trace_writer accepts an already configured TraceWriter.

The viewer crate also exposes serve_file and serve_memory. The memory-backed form returns a TraceHandle for pushing JSON events and a server future that the caller must drive.