Session replay sends previously captured client-to-server messages against a live MCP server, collecting responses for analysis, testing, and reproduction.
Basic Workflow
- Capture a session:
qai proxy start \
--transport stdio \
--target-command "python my_server.py" \
--session-file session.json
Interact with your agent or client normally. The proxy saves all messages to session.json.
- Replay the captured messages:
qai proxy replay \
--session-file session.json \
--target-command "python my_server.py" \
--output results.json
The replay engine resends client-to-server messages and captures new responses.
Replay Command Reference
qai proxy replay --session-file FILE [OPTIONS]
Options:
| Option | Required | Type | Description |
|---|
--session-file | Yes | string | Path to the captured session file |
--target-command | Yes* | string | Command to start the target server (stdio) |
--target-url | Yes* | string | URL of the target server (SSE/HTTP) |
--output | No | string | Save replay results to JSON file |
--timeout | No | float | Per-message response timeout in seconds (default: 10.0) |
--no-handshake | No | flag | Skip synthetic handshake (only if session already includes initialize) |
* Either --target-command or --target-url is required.
Auto-Handshake
By default, replay sends a synthetic MCP handshake before replaying messages:
{
"jsonrpc": "2.0",
"id": "__handshake__",
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {
"name": "mcp-proxy-replay",
"version": "0.1.0"
}
}
}
This ensures the server is ready to accept tool calls. Use --no-handshake only if:
- Your captured session already starts with an
initialize request
- You want to replay the exact sequence as captured (including handshake)
Modifying Arguments Before Replay
Replay sends messages exactly as captured. To modify arguments:
- Export the session JSON or inspect it to find the target message
- Edit the message’s
payload field directly in the JSON file
- Replay the modified session
Example:
{
"proxy_id": "msg-001",
"sequence": 5,
"direction": "client_to_server",
"payload": {
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "search",
"arguments": {
"query": "modified search term"
}
}
}
}
Replay results are written to JSON with a detailed record of each message:
{
"results": [
{
"original_request": {
"id": "msg-001",
"method": "tools/call",
"jsonrpc_id": 1
},
"sent_message": {
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { ... }
},
"response": {
"jsonrpc": "2.0",
"id": 1,
"result": { ... }
},
"error": null,
"duration_ms": 145.2
}
]
}
Result fields:
| Field | Description |
|---|
original_request | The ProxyMessage from the captured session |
sent_message | The actual SessionMessage sent to the server |
response | The server’s response (null if notification or timeout) |
error | Error description if replay failed (null on success) |
duration_ms | Round-trip time in milliseconds |
Handling Timeouts
Use --timeout to adjust the per-message timeout:
qai proxy replay \
--session-file session.json \
--target-command "python slow_server.py" \
--timeout 30.0 \
--output results.json
Messages that exceed the timeout are recorded with a timeout error and continue to the next message.
Filtering Replayed Messages
Only client-to-server messages are replayed. Server-to-client messages in the session are skipped:
client_to_server messages (requests and notifications) are replayed in order
server_to_client messages (responses and notifications) are ignored
- Notifications (no JSON-RPC id) are sent without waiting for a response
Use Cases
Reproduce Security Findings
Capture a vulnerable interaction, then replay it to confirm the finding:
# 1. Capture the attack sequence
qai proxy start \
--transport stdio \
--target-command "python app.py" \
--session-file attack.json
# 2. Replay against the target to confirm
qai proxy replay \
--session-file attack.json \
--target-command "python app.py" \
--output confirmation.json
# 3. Compare responses
Test Compatibility
Verify a new server version behaves the same as the old:
# Replay sessions against new server
qai proxy replay \
--session-file v1_capture.json \
--target-command "python server_v2.py" \
--output v2_results.json
# Compare with original v1_results.json
Debug Intermittent Issues
Capture a failing interaction, then replay it repeatedly:
qai proxy replay \
--session-file failing_session.json \
--target-command "python server.py" \
--output replay_1.json
qai proxy replay \
--session-file failing_session.json \
--target-command "python server.py" \
--output replay_2.json
# Compare results to identify variability
Only client-to-server messages are replayed. Server responses are discarded from the original session and newly captured during replay. This allows testing the server against the same client sequence while isolating server-side changes.