skip to content

Request Metadata and Discovery

MCP has no handshake: each request declares its own protocol version and capabilities in _meta, and server/discover reports what a server offers. Interviewers still ask about the removed handshake.

part ofAPI stylesoverview, primer and where to startread it →
on this pageshow

questions

16

What must every MCP request carry in params._meta now that initialize is gone?

level: juniorimportance: must knowfreq 82%

answer

  1. Two keys, and one may be empty
  2. Namespaced under io.modelcontextprotocol/
  3. Stamped on every request, not once
  4. Missing key is invalid params, not a default
  5. protocolVersion plus clientCapabilities in _meta

basics

~10 s

Every MCP request must put io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities inside params._meta. An empty capabilities object is legal, but the key itself is not optional. clientInfo is optional and SHOULD be sent.

solid answer

~40 s

Since revision 2026-07-28 MCP is stateless, so each request declares for itself what the old `initialize` handshake used to declare once. Inside `params._meta` a client MUST send `io.modelcontextprotocol/protocolVersion` (a dated revision string such as `"2026-07-28"`) and `io.modelcontextprotocol/clientCapabilities` (the capability object; `{}` is a legal declaration meaning "no optional client capabilities"). It SHOULD also send `io.modelcontextprotocol/clientInfo` with a name and version, which is self-reported and therefore untrusted. Optionally it may send `io.modelcontextprotocol/logLevel`. Omitting either required key is an invalid request: the server answers `-32602` invalid params, and over Streamable HTTP that comes back with HTTP 400. Servers echo their own identity the other way, putting `io.modelcontextprotocol/serverInfo` in the result's `_meta`.

code

json · 19 lines
json
{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "search_docs",
    "arguments": { "query": "retry policy" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {
        "elicitation": { "form": {} }
      },
      "io.modelcontextprotocol/clientInfo": {
        "name": "acme-console",
        "version": "3.1.0"
      }
    }
  }
}

go deeper

for a junior

Be able to name the two required _meta keys — protocolVersion and clientCapabilities — and say that they ride on every request, not on a one-time setup call.

for a middle

Explain the required-but-may-be-empty rule for clientCapabilities, why a missing key is -32602 rather than a default, and which keys are only SHOULD-level.

for a senior

Show where you enforce this in a real implementation: one place in the transport that stamps _meta outgoing, one validation point before dispatch, and capability reads scoped to the request being served.

for a principal

Own the tradeoff: repeating metadata on every call costs bandwidth and payload size, and buys stateless routing, free reconnects and no per-connection server state. Be ready to argue when that trade is worth it.

## What `_meta` is `_meta` is a reserved object that may appear inside a JSON-RPC request's `params`. In MCP revision 2026-07-28 it is the carrier for protocol-level metadata that is not part of the method's own arguments: which revision of the protocol this request speaks, what the client is able to do, who the client says it is, and a handful of cross-cutting concerns like progress tokens and tracing. The schema type is `RequestMetaObject`. Keys inside `_meta` are namespaced with a reverse-DNS-style prefix so that vendors and extensions cannot collide with the specification. The protocol's own keys use the `io.modelcontextprotocol/` prefix. ## The two required keys - `io.modelcontextprotocol/protocolVersion` — REQUIRED on every request. A dated revision string, for example `"2026-07-28"`. The server decides per request whether it can serve that revision; there is no handshake in which the two sides agree once and then stop talking about it. - `io.modelcontextprotocol/clientCapabilities` — REQUIRED on every request. The client capability object. An **empty object `{}` is legal and meaningful**: it says "I declare no optional client capabilities". What is not legal is leaving the key out. A server must not paper over the difference by treating an absent key as `{}` — an absent key is a malformed request. That distinction trips people up in interviews. "Required, but may be empty" is a deliberate design choice: it forces every client to state its position explicitly rather than letting silence mean anything. ## The SHOULD-level identity keys - `io.modelcontextprotocol/clientInfo` — optional, and a client SHOULD send it. It reports a client name and version for logging and operator-facing display. - `io.modelcontextprotocol/serverInfo` — the mirror image, which a server SHOULD put in the `_meta` of its *results*. Both are **self-reported and explicitly untrusted**. They are diagnostics, not credentials: no authorization, feature gating or consent decision may hang off them. ## Other keys that may appear - `io.modelcontextprotocol/logLevel` — optional; it sets the logging verbosity for this request. - Reserved names that the specification claims: `progressToken`, `io.modelcontextprotocol/subscriptionId`, and the W3C Trace Context exceptions `traceparent`, `tracestate` and `baggage` — the three tracing keys are the notable exception to the prefixing rule, kept unprefixed so standard tracing tooling works unchanged. ## Why per-request, and not once per connection The specification's own framing is blunt: "MCP is a stateless protocol: every request is self-contained and carries its own protocol version and capabilities." A server MUST NOT rely on prior requests over the same connection to establish context, and "an open connection, such as a STDIO process, is not a conversation or session." Repeating a few hundred bytes of metadata on every call is the price of that property, and it buys a lot: any node in a pool can serve any request, a reconnect costs nothing, and no server-side table maps connections to negotiated state. This is what replaced the `initialize` request and the `notifications/initialized` acknowledgement, both of which revision 2026-07-28 removed. Anything else the handshake used to deliver — the server's `instructions`, its `serverInfo`, the list of revisions it supports — now comes from the discovery call instead. ## What happens when a required field is missing A request whose `_meta` lacks `protocolVersion` or `clientCapabilities` is invalid params: the server responds with JSON-RPC error code `-32602`. On the Streamable HTTP transport that error is delivered with HTTP status 400. There is no defaulting, no grace period, and no "assume the latest revision". A separate error exists for the case where the metadata is well-formed but insufficient: `-32021` `MissingRequiredClientCapabilityError`, used when the server cannot serve the request without a client capability the request did not declare. ## Practical notes for implementers Build the `_meta` block once in your client's transport layer and stamp it onto every outgoing request rather than remembering it at call sites — the single most common bug in hand-rolled 2026-07-28 clients is a code path that forgets it. On the server side, validate `_meta` in one place before dispatch, and read capabilities from the request object you are currently serving rather than from any per-connection field. If you are writing a server that also answers older clients, remember that era is a property of the server, not of a single request: a modern-only server simply rejects handshake-era traffic.

  • What does a server return if a request omits io.modelcontextprotocol/protocolVersion?
    It is invalid params: JSON-RPC error `-32602`, delivered with HTTP 400 over Streamable HTTP. There is no defaulting to the newest revision and no tolerance window — both `protocolVersion` and `clientCapabilities` are required on every single request in revision 2026-07-28, so an absent key is a malformed request rather than an implicit declaration.
  • Is an empty clientCapabilities object legal, and what does it mean?
    Yes. `"io.modelcontextprotocol/clientCapabilities": {}` is a valid, explicit declaration that the client offers no optional client capabilities. The key being present is what matters; its contents may be empty. A server must not treat a *missing* key as equivalent to `{}` — missing is an error, empty is a statement.
  • Besides the protocol version and capabilities, what other names are reserved inside _meta?
    `progressToken`, `io.modelcontextprotocol/subscriptionId`, and the W3C Trace Context keys `traceparent`, `tracestate` and `baggage`. The tracing three are the deliberate exception to the reverse-DNS prefixing convention so that off-the-shelf distributed-tracing tooling interoperates without translation.

saying these in an interview costs you the question

  • Says the client sends capabilities once during initialize
  • Treats a missing clientCapabilities key as an empty object
  • Claims an empty capabilities object is invalid
  • Thinks clientInfo is required or trustworthy
  • Believes protocolVersion defaults to the newest revision

context

open as a page

In MCP, what does server/discover return, and which side is required to implement it?

level: juniorimportance: must knowfreq 68%

basics

~10 s

server/discover returns a DiscoverResult holding supportedVersions, the server's capabilities, optional instructions, and the required cache hints ttlMs and cacheScope, with serverInfo in _meta. Servers MUST implement it; clients MAY call it.

open as a page

In MCP, what does a protocol version like 2026-07-28 mean, and when is it bumped?

level: juniorimportance: must knowfreq 70%

basics

~10 s

MCP protocol versions are dates in YYYY-MM-DD form naming the day of the last backwards-incompatible change to the specification. The current revision is 2026-07-28. Backwards-compatible additions do not bump the version.

open as a page

What do modern, legacy and dual-era mean in MCP, and which combinations interoperate?

level: middleimportance: must knowfreq 58%

basics

~20 s

Modern means the per-request metadata model of MCP 2026-07-28 and later; legacy means the initialize handshake of 2025-11-25 and earlier; dual-era supports both. Era is a property of the server, and only a dual-era server serves clients of both eras.

open as a page

In MCP, what is the -32022 UnsupportedProtocolVersionError and how should a client react?

level: middleimportance: must knowfreq 62%

basics

~20 s

UnsupportedProtocolVersionError, JSON-RPC code -32022, is how an MCP server rejects one request whose declared protocol version it will not serve. Its data carries supported and requested, so the client retries with a listed version rather than disconnecting.

open as a page

Why must an MCP server re-read capabilities from every request rather than caching them?

level: seniorimportance: must knowfreq 52%

basics

~20 s

MCP revision 2026-07-28 makes each request self-contained, so a server MUST NOT infer capabilities from prior requests. A cached capability set can be stale, belong to a different caller, or be unavailable to the node now handling the call.

open as a page

In MCP, what does the error -32021 MissingRequiredClientCapabilityError mean?

level: middleimportance: should knowfreq 45%

basics

~20 s

Error -32021 means the MCP server cannot serve the request without a client capability that the request's _meta never declared. Its data.requiredCapabilities lists exactly which capabilities the client would have to declare for the request to succeed.

open as a page

Why are MCP's clientInfo and serverInfo fields explicitly untrusted?

level: middleimportance: should knowfreq 40%

basics

~20 s

Both are self-reported strings with no verification behind them: any peer can claim any name and version. MCP marks them untrusted so implementations use them for logging and display only, never for authorization or trust decisions.

open as a page

Why must an MCP DiscoverResult carry ttlMs and cacheScope, and what does cacheScope mean?

level: middleimportance: should knowfreq 42%

basics

~20 s

DiscoverResult extends CacheableResult, so both fields are required. ttlMs tells the client how many milliseconds it may reuse the answer; cacheScope is "public" or "private" and says whether the cached copy may be shared across users or must be kept per-authorization.

open as a page

In MCP, where do a server's instructions and serverInfo live now that initialize is gone?

level: middleimportance: should knowfreq 48%

basics

~10 s

Both moved into the server/discover response in MCP revision 2026-07-28: instructions is an optional top-level string on DiscoverResult, and serverInfo sits in that result's _meta under the key io.modelcontextprotocol/serverInfo. Both are self-reported and untrusted.

open as a page

In MCP, what does the Deprecated status guarantee about a feature's removal?

level: middleimportance: should knowfreq 42%

basics

~20 s

A feature marked Deprecated in MCP stays in the specification for at least twelve months, and new implementations SHOULD NOT adopt it. Features deprecated in revision 2026-07-28 cannot be removed before the first revision released on or after 2027-07-28.

open as a page

In MCP 2026-07-28, what does a client give up by never calling server/discover?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Very little structurally: every request is self-contained, so tools/call works as a first message. The client forfeits the server's supportedVersions list, its capability preview, its instructions text and its cache hints — so it learns about mismatches only from a rejected request.

open as a page

How does an MCP client tell a legacy initialize-era server from a modern one?

level: seniorimportance: should knowfreq 50%

basics

~20 s

The client probes. Over stdio it calls server/discover, which every modern server must implement, and reads a method-not-found error as the signal to fall back to the legacy initialize handshake. Over HTTP the JSON-RPC error body of a failed attempt identifies the era.

open as a page

Your MCP server faces a mixed client fleet — do you ship dual-era support, and for how long?

level: principalimportance: should knowfreq 34%

basics

~20 s

Decide from measured client traffic, not from a wish to be safe. Dual-era support means maintaining two lifecycle implementations, so carry it only while real handshake-era clients exist, keep the stateless modern path canonical, and publish a retirement date up front.

open as a page

In MCP, what does the _meta key io.modelcontextprotocol/logLevel control?

level: middleimportance: nice to knowfreq 28%

basics

~10 s

It sets the logging verbosity a server may use while handling that one request. Without the key, a server MUST NOT emit notifications/message at all. It replaced logging/setLevel, which MCP revision 2026-07-28 removed.

open as a page

How would you set ttlMs and cacheScope for server/discover across a fleet of MCP servers?

level: principalimportance: nice to knowfreq 24%

basics

~20 s

Set cacheScope by whether the discovery answer can differ per caller — private whenever capabilities vary by authorization, public only when every caller sees the same profile. Set ttlMs from how fast the profile actually changes, shortening it during rollouts.

open as a page