Field note progress0%
← All field notes

MCP observability / 9 min

MCP Went Stateless: Measuring Sessions, Users and Latency After the 2026-07-28 Spec

The 2026-07-28 MCP spec removed initialize and protocol sessions. How to identify AI clients, group sessions, count users and time retried calls.

On this page

After the 2026-07-28 MCP specification, there is no initialize handshake and no protocol session: each request carries its own protocol version, client capabilities and, ideally, client info in _meta, so analytics must identify clients per request, group sessions with its own rule, and link retried requests into one logical call. Flowlines groups stateless MCP traffic into sessions, shows the users and AI clients behind every tool call, and labels sessions it infers.

The revision made MCP servers easier to scale behind load balancers. It also removed the two things many dashboards leaned on: the clientInfo sent once at startup and the Mcp-Session-Id header. This guide covers what replaces them for measuring clients, sessions, users and latency. Facts are checked against the 2026-07-28 changelog ↗ and specification pages.

What changed for analytics in the 2026-07-28 MCP spec?

Client identity moved from a one-time handshake to every request, protocol sessions disappeared, and one logical call can now span several JSON-RPC requests; Flowlines handles all three, but your own instrumentation needs to know about them too.

TopicBefore (2025-11-25)After (2026-07-28)Analytics impact
Handshakeinitialize with clientInfo and capabilitiesRemoved; server/discover returns server detailsNo single moment that says "a client connected"
Client infoOnce per connection_meta["io.modelcontextprotocol/clientInfo"] on each request, optionalRecord the client on every span
Protocol versionNegotiated once_meta["io.modelcontextprotocol/protocolVersion"] on each requestTrack the revision mix per request
SessionsMcp-Session-Id header on Streamable HTTPRemoved; servers ignore the header and never mint oneSessions are an analytics construct you define
Server-to-client requestsElicitation and sampling sent by the serverA result with resultType: "input_required"; the client retries with a new request IDOne logical call, several requests
Resent requestsResumable SSE streamsResumability removed; a broken stream is resent as a new request with a new IDRetries look like new calls unless you link them
HTTP headersMCP-Protocol-VersionAdds required Mcp-Method and Mcp-NameGateways can count calls per tool without parsing bodies

Other changes, such as ping and logging/setLevel being removed and tasks moving to an extension, matter less for measurement. Error codes changed too; the MCP error codes guide lists them.

How do I identify the AI client without the initialize handshake?

Read the client info from _meta on each request and record its name and version on every tool span; Flowlines shows tool calls by AI client with an explicit unknown bucket for requests that do not say.

A stateless tools/call request carries its context with it:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "search_customers",
    "arguments": { "query": "acme" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {},
      "io.modelcontextprotocol/clientInfo": { "name": "example-client", "version": "1.4.0" }
    }
  }
}

The protocol version and client capabilities are required. Client info is optional, and the specification says clients should send it on every request, so some requests will arrive without it.

  • Record name and version as low-cardinality attributes, normalized to a known list of client families.
  • Keep "unknown" visible. A rising unknown share usually means a new client or an integration that stopped sending the field.
  • Do not cache across requests. Two requests from one user can come from different clients.
  • Never use it for authorization. Client info is self-reported.

MCP user analytics shows how to track usage by AI client and recognize the same user across clients.

How do I group MCP requests into sessions now?

Group requests with a rule you define, usually a permitted user identity plus an inactivity gap, and label the result as inferred; Flowlines groups stateless traffic this way and uses supplied session context when your integration provides it.

Grouping signalStrengthWatch out for
Authenticated user plus an inactivity gap, such as 30 minutesWorks for most HTTP servers with OAuthSplits a long job with a pause; merges two jobs close together
Trace context propagated by the clientLinks calls inside one agent turn when the client sends itNot every client propagates it; one turn is not one job
An application identifier your product controls, such as a project or conversation IDThe most faithful groupingOnly where you have permission and a real identifier
Mcp-Session-Id from older clientsAvailable for clients on earlier revisionsIdentifies a connection, not a person or a job; absent on 2026-07-28

The 2026-07-28 revision also defines conventions for carrying traceparent, tracestate and baggage in _meta, so trace context can cross from client to server. Use it when it is present, and do not depend on it.

Whatever rule you choose, write it down and apply it to every client. MCP session analytics explains how Flowlines separates supplied, observed and inferred sessions, and the glossary entry for MCP session defines the term.

How do I count MCP users without a session ID?

Count users from the identity your authentication establishes, exactly as before: a protocol session never identified a person, it identified a connection. Flowlines counts distinct and returning users from a permitted, stable user identifier.

Identity sourceUse it forLimits
OAuth token subject on a remote serverDistinct and returning usersShared service accounts are not people
Your own account or workspace IDAccount-level adoptionSeveral people can share one account
Local stdio server with no authenticationInstallation-level counts at mostNo user identity unless the user opts in

Retention and activation depend on this identity, not on sessions, so the MCP server KPIs keep working after the revision. What changes is the session count, because it now depends on your grouping rule. Do not compare session counts across the migration without saying so.

How do I time tool calls that span several requests?

Record every request as its own span, then link the rounds of one logical call and report two durations: server time per request and end-to-end time per call. Flowlines shows p50, p95 and p99 latency per tool next to the users and clients affected.

A logical call spans several requests in two cases:

  1. Multi round-trip requests. The server answers with resultType: "input_required", for example to ask the user for a confirmation, and the client sends the request again with the answers. The specification requires a new JSON-RPC ID for each round.
  2. Resent requests. With SSE resumability removed, a client whose stream breaks resends the request with a new ID.
DurationMeasured fromMeasured toUse it for
Server time per requestRequest receivedResponse sentTool performance and p95 objectives
End-to-end time per logical callFirst request of the callThe round that returns resultType: "complete"What the user experienced
Waiting for inputAn input_required resultThe next round arrivingExclude from tool latency; it is human time

Do not link rounds by JSON-RPC ID: request IDs only have to be unique among requests still in flight, and every round uses a new one. If your server keeps state between rounds, key it with an identifier you generate and record that identifier on each span. Otherwise, link by user, tool and a short time window, and treat the result as approximate.

Timeouts also need care. The specification asks implementations to set timeouts but gives no default, and on HTTP, closing the response stream counts as cancellation. Record cancellations as their own outcome, not as errors and not as successes. MCP tool analytics covers per-tool latency, and the MCP monitoring guide covers histograms and alert rules.

How do I avoid counting a resent call twice?

Count attempts and logical calls separately: every request is an attempt, and attempts that a client resent for the same work form one logical call. The specification has no idempotency key, only the idempotentHint annotation, so deduplication is your job.

  • Success rate should use logical calls, so a call that failed once and succeeded on resend counts as one success.
  • Retry rate is attempts divided by logical calls, reported per tool and per client.
  • Side effects matter most. A tool that creates records can run twice when a client resends after a broken stream. Make write tools idempotent on your side, and set idempotentHint honestly.

Repeated calls with identical arguments also show up when a tool's result does not tell the agent what to do next; why agents ignore your MCP tools covers that pattern.

Which new HTTP headers help MCP observability?

Mcp-Method and Mcp-Name let a gateway or load balancer count requests per method and per tool without parsing the JSON body, and the specification names observability tooling as one of their purposes. A mismatch between the headers and the body is rejected with HTTP 400 and error -32020.

HeaderCarriesExample use
MCP-Protocol-VersionThe revision of the requestRevision mix per client at the edge
Mcp-MethodThe JSON-RPC methodSplit tools/call from list and discovery traffic
Mcp-NameThe tool, prompt or resource nameCalls and errors per tool at the gateway

Edge counts are useful for traffic and errors. They do not know the user, the job or the outcome, so read them with server-side spans.

What should I do if my clients use different spec revisions?

Support both paths and record the protocol version on every request, so you can see the revision mix and compare each client with itself. Older clients still send initialize with client info once per connection, and may send Mcp-Session-Id; newer clients send everything per request.

Use this checklist when you migrate a server's analytics:

  • Read client info from _meta per request, and fall back to the initialize values for older clients.
  • Record protocolVersion on every span and chart the share of each revision.
  • Replace any metric keyed on Mcp-Session-Id with your own session rule, labeled as inferred.
  • Keep user counts on authenticated identity, unchanged.
  • Add a round number and result type to tool spans, and link rounds with your own identifier.
  • Report attempts, logical calls and cancellations separately.
  • Annotate the migration date on every chart whose definition changed.

Key takeaway: the stateless revision moved client info to every request and left sessions to you. Identify clients per request, keep users on authenticated identity, define and label your session rule, and link retried requests before you measure latency or success. Flowlines does this grouping for stateless MCP traffic and shows the users, clients and sessions behind every tool. See MCP observability for the product walkthrough; the Free plan includes 100 analyzed MCP sessions per month.

Sources

Frequently asked questions

What changed for MCP analytics in the 2026-07-28 specification?

The initialize handshake and protocol sessions are gone. Each request now carries its protocol version and client capabilities in its metadata, and should carry client info. Servers ignore Mcp-Session-Id. Multi round-trip requests and resent requests use new JSON-RPC IDs, so one logical tool call can span several requests.

How do I know which AI client called my MCP server without initialize?

Read the client info in each request's metadata, under the io.modelcontextprotocol/clientInfo key, and record the name and version on every tool span. The field is optional and self-reported, so keep an explicit unknown bucket and never treat it as authenticated identity.

How do I group MCP requests into sessions after the stateless spec?

Group by a permitted user identity from your authentication plus an inactivity gap, or by trace context or an application-level identifier when you have one. Label the result as inferred. Flowlines groups stateless traffic into sessions and uses supplied session context when it is available.

How do I count MCP users without a session ID?

The same way as before: from the identity your authentication establishes, such as the OAuth token's subject or your own account ID. A protocol session never identified a person; it identified a connection.

How do I measure latency for tool calls that span several requests?

Record every request as its own span with its result type, then link the rounds of one logical call with an identifier your server controls. Report server time per request and end-to-end time per logical call separately, and exclude time spent waiting for the user.

Do I need to change my MCP analytics if some clients are still on older revisions?

Support both. Older clients still send initialize with clientInfo once per connection and may send Mcp-Session-Id. Record the protocol version on every request so you can see the revision mix and compare like with like.

100 MCP sessions a month, free

See what people do with your MCP.