Skip to content
Guides

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:

python
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:

sh
m3 test -- tests/test_trace.py

The 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:

clockMeasured by
recordedM3, when it recorded the event as it happened.
wireThe MCP capture proxy, when the message crossed the transport.
harnessThe agent harness as each update arrived, timed from the start of the turn. Codex, Pi, and Claude Code.
ingestedM3, when it received evidence that was observed earlier. The offset is an upper bound, not the time the call ran.
mixedEvidence 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.