Build1 distinct publisher3 min readPublished
Deleting sessions and the initialize exchange leaves a stateless handler nothing to remember, and the C# SDK says so by keeping request.Server.ClientCapabilities null while the real answer rides in _meta.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The read is one expression deep. A tool handler takes its injected `RequestContext<CallToolRequestParams>` and pulls `request.JsonRpcRequest.Context?.ClientCapabilities`, then asks whether `Extensions` contains the key it cares about [5]. Keys are reverse-DNS strings, and the sample gates on `com.example/report-export` [3]. Keeping that lookup beside the decision it drives is the entire discipline here, because the alternative is a field somewhere holding a value that was true for some other call [9].
The null cases are where the design shows. There are three states, not two [10]. A missing request context means you never received an affirmative declaration. An empty `extensions` dictionary means the client sent a valid capability object that does not include your extension. A present key means it does. Collapsing the first two into "disabled" is defensible. Collapsing either into "whatever the previous handler saw" is the bug the revision creates [9].
The fixture in the dev.to post by ssukhpinder is the part I would steal. It stands up an ASP.NET Core TestServer, so the real Streamable HTTP endpoint gets exercised without opening a port [12]. Two `tools/call` requests go in concurrently, one advertising the extension and one sending an empty map, with a one-shot barrier holding both handlers until both have arrived [13]. The expected output is `enabled-request:request=enabled,server=null` and `disabled-request:request=disabled,server=null` [14]. Two of the two assertion lines end in `server=null`, which is the useful arithmetic: the connection-level property carries no information in either branch, so a handler reading it cannot be right by accident [17]. A property that is deliberately null is odd as API surface and excellent as a smoke alarm.
For this to matter to your service, one thing has to be true: your traffic has to vary. A single well-behaved client that sends the same extension map on every call will make a cached, connection-scoped read look correct indefinitely. The post's own account of the failure mode is overlap, a load test or proxy or compatibility client mixing modern requests with different extension maps inside one process, where code that passed with one caller fails only under concurrency [16]. That is also why a sequential happy-path test proves so little [15].
One boundary worth stating before anyone wires this into a permission check. A declared capability is a self-reported protocol feature, not authentication, authorization, or trusted identity, and a protected report export still needs validated claims and application policy behind it [11]. The declaration tells you what response shape the caller can handle. It tells you nothing about who the caller is.
This is one engineer's reading plus a working sample, not a spec I can quote back at you. The load-bearing claims are checkable, though: the sample runs on the stable `ModelContextProtocol.AspNetCore` 2.2.0 package rather than a preview API [7], and the writeup says the official `JsonRpcMessageContext` reference already names the current request as authoritative [6]. If both hold, the migration is a search for cached capability state, and the sessionless model gives you a real deployment benefit in exchange: separate instances can serve calls from the same client with no sticky routing and no shared session store [2].
Ranked by verification strength, evidence, and original report placement.
The MCP 2026-07-28 release removed protocol-level sessions and the initialize exchange from the modern path.
Every request is self-contained, which lets separate server instances handle calls without sticky routing or a shared MCP session store.
The request carries reserved metadata in _meta, including io.modelcontextprotocol/protocolVersion, io.modelcontextprotocol/clientInfo, and io.modelcontextprotocol/clientCapabilities, whose extensions map in the sample holds the key com.example/report-export.
Inside a stateless HTTP handler, request.Server.ClientCapabilities is intentionally null.
The C# SDK exposes the parsed value through JsonRpcMessageContext.ClientCapabilities, which a tool reaches from its injected RequestContext<CallToolRequestParams> as request.JsonRpcRequest.Context?.ClientCapabilities.
The official JsonRpcMessageContext reference calls the current request authoritative.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 29, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
MCP's empty-string cursor: the null check that hides most of a tool catalog1 distinct publisher
build
MCP 2026-07-28 drops the `result` wrapper, and your unit tests will not notice1 distinct publisher
build
MCP standardizes the tool call, not the authority to cause the effect1 distinct publisher
build
A SKILL.md layer quietly rerouted an agent off the MCP tools it was given1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
One voice, but it shows its work
The post does more than assert. It prints the tool method, names the package version, states the two output lines a reader should see, and lists the commands to get there — a claim written to be falsified. What it never puts in front of you is the 2026-07-28 spec text or the SDK reference page it paraphrases, so the sentence doing the most work in the piece, that the current request is authoritative, is a summary of a document our coverage never quotes.
Shipped surface, unmeasured uptake
Two things are genuinely on the ground: the revision itself and ModelContextProtocol.AspNetCore 2.2.0, which the author is careful to flag as stable rather than preview. Everything past that is absent — no production server described, no download or usage figure, no second .NET team reporting the same null property. The sample's virtue for reproducibility, an in-memory server with no port open and no network calls, is exactly what makes it worthless as evidence that anyone is running this.
Narrow claim, wider framing
The core advice is small and delivered: read the request, not the server, and here is the test that catches you cheating. The framing around it runs ahead of what is shown — 'the protocol removed sessions' arrives as settled fact with no excerpt behind it, and the migration scenario about proxies and load generators mixing extension maps is reasoning about a failure rather than a failure anyone observed. Working the other way, the post twice talks itself down: capabilities are not authorization, and the pattern applies only to the modern stateless path.
Reputation, not revenue
This is a developer writing under a personal handle on a publishing platform, with no sponsor, employer or product stake visible anywhere in the piece. The distortion available to him is the ordinary one for platform tutorials: a crisp 'trap' travels further than a caveat, and that pressure shows in the headline more than in the body. Pushing the other way, linking a validation record and running a vulnerable-package check across transitives is what someone who expects to be checked does.
Sure about the sample, unsure about the world
We can be fairly confident about what this code does and what it prints. Generalising from it is another matter: one author, one publisher, one machine's output, and a protocol revision whose design intent we only hear about second-hand. The barrier is what keeps the number from sinking lower — the author understood that a test which happens to serialize proves nothing, and said so. A spec excerpt or the same behaviour reported from a second SDK would move this substantially.