Read a finalized trace
Use the operation result for the immediate response and the finalized trace for the complete observed execution.
Requirements
Start from the project in Your first MCP test. This example uses the same server fixture and needs no credentials.
Complete test
Save as tests/test_trace.py:
import sys
from pathlib import Path
import pytest
from m3 import MCPTestKit, StdioServer
from m3.types import ExecutionOutcome
pytestmark = pytest.mark.m3(suite_name="shipping")
def test_shipping_trace():
root = Path(__file__).parents[1]
server = StdioServer(
name="shipping",
command=sys.executable,
args=(str(root / "shipping_server.py"),),
cwd=str(root),
)
with MCPTestKit(env={}) as kit, kit.direct(server) as client:
result = client.call_tool("shipping_quote", {"weight_kg": 2, "zone": "local"})
assert result.structured_content == {"amount": 9.0, "currency": "USD"}
trace = client.final_trace
assert trace is not None
view = trace.view()
assert view.outcome is ExecutionOutcome.COMPLETED
call = view.tool_calls[0]
assert call.tool.value == "shipping_quote"
assert call.arguments.value == {"weight_kg": 2, "zone": "local"}
assert call.correlation.value == "wire_only"Run it from the project root:
m3 test -- tests/test_trace.pyThe result assertion checks the server response. The trace assertions check the call M3 observed. Moving client.final_trace inside the with block is an error because the trace is not final yet.
Entry timing
Every timeline entry has a timing with offsets in milliseconds from the start of the execution. timing.clock says where those offsets were measured:
clock | Measured by |
|---|---|
recorded | M3, when it recorded the event as it happened. |
wire | The MCP capture proxy, when the message crossed the transport. |
harness | The agent harness as each update arrived, timed from the start of the turn. Codex, Pi, and Claude Code. |
ingested | M3, when it received evidence that was observed earlier. The offset is an upper bound, not the time the call ran. |
mixed | Evidence from two of the above, such as a harness-reported call joined to its wire exchange. |
Harness and wire evidence reach the trace after the turn ends. M3 places them at the time their source recorded. The entry is marked ingested when the source's clock cannot be tied to the execution clock: ACP and OpenCode observations, which are timed when the turn ends, and traces saved by older versions. A wire tool call's server_latency_ms is the proxy's own request-to-response measurement in either case. When a harness-reported call joined to its wire exchange has only approximate timing on one side, the entry uses the other side's timing.
Next, open the saved run or read about evidence availability.
For what each trace limitation means, see CLI output and trace limitations.