Skip to content

How correlation works

When a report arrives, the package attaches the calls that led up to it. There is no explicit “conversation id” in MCP to do this with, so it matches on two signals: the client session and the user.

capture_recent_calls builds the list, up to n calls (20 by default):

  1. session: calls with the same session key as the current request, newest first.
  2. user_window: if that found fewer than n, calls by the same user_sub within the last window (15 minutes), skipping calls already taken.

The result is sorted oldest first and numbered from 0, and each link records the rule that matched it. Candidates come from the in-memory buffer of recent calls (recent_calls, 2000), which includes calls still waiting in the queue, and, when there is a DatabaseSink, from the database, which adds calls from other workers and from before a restart. That lookup is capped by correlation_timeout (1 second).

The session key of a call is the first of these that exists:

  1. The HTTP mcp-session-id header, sent by clients that open a session over Streamable HTTP.
  2. On stdio, one key per server process, since a stdio server serves exactly one client.
  3. FastMCP’s ctx.session_id.

MCP protocol revision 2026-07-28 made the protocol stateless over HTTP: clients send no mcp-session-id, and each request stands alone. Claude Code and FastMCP 4’s own Client speak it. For those requests FastMCP generates a fresh ctx.session_id per request, so the third fallback above is different for every call, and the session rule finds nothing.

So for HTTP servers the user_window rule does the work, and it needs a user_sub, which only an identity resolver can supply. Without one, an HTTP server with modern clients links no calls: submit_feedback answers linked_calls: 0.

TransportClientLinks by
stdioanysession (per process)
HTTPolder protocol, sends mcp-session-idsession, then user_window
HTTP2026-07-28, no sessionuser_window only; needs an identity resolver
in-process Client(app) (tests)FastMCP 4user_window only

Matching by user within a time window is a heuristic. A user running two unrelated tasks at once gets both tasks’ calls on a report about one of them, and a report filed more than 15 minutes after the problem misses the calls that caused it. n and window are parameters of capture_recent_calls for hosts that call it directly; submit_feedback uses the defaults.

Calls that are excluded or sampled out are never candidates, since they were never kept. Failures of sampled tools always are.

Links are stored in ffb_feedback_call_links as (feedback_ref, call_id, position, rule, linked_at). They are never pruned, and the calls they point to are exempt from retention, so a report keeps its evidence. feedback_context joins them back to the call rows, and fills in from memory any call that has not reached the database yet.