Model Context Protocol's 2026-07-28 spec revision moved three cornerstone features to the deprecated list at once: Roots, Sampling, and Logging. This post walks through the MCP deprecated-features migration step by step — what's removed and why, how much time you have, and how to migrate your server or client without breaking it. This decision, formalized by SEP-2577, is the first time the protocol has enforced a real deprecation policy — and the pattern you learn here applies to every deprecation that follows.
💡 Pro Tip: Roots, Sampling, and Logging still work right now — nothing broke at the wire level. Don't panic and rush the migration; the real priority is inventorying where your codebase uses these three primitives and clarifying the migration path.
Table of Contents
- What Got Deprecated and Why
- Where the deprecation criteria come from
- Why all four at once
- The 12-Month Window: Timeline and Risk
- Why the actual removal date can shift
- Passing File Scope Without Roots
- Alternatives to Server-Initiated LLM Calls Without Sampling
- Why this change counts as an improvement
- A Telemetry Pattern to Replace Logging
- Migrating Off the Legacy HTTP+SSE Transport
- Inventory: A Script to Find What's Affected
- Prioritizing your inventory
- FAQ
- Why was the Sampling feature in MCP deprecated?
- What should I use instead of Roots?
- How long does the MCP deprecation window last?
- How do I update my MCP server that uses Logging?
- Is the HTTP+SSE transport's deprecation on the same timeline as Roots/Sampling/Logging?
- What happens if I keep using a deprecated feature?
- I'm writing a new MCP server — should I avoid Roots/Sampling/Logging entirely?
- Conclusion
- Sources
What Got Deprecated and Why
The spec revision dated 2026-07-28 moved Roots, Sampling, and Logging to Deprecated status via SEP-2577. The official announcement states it plainly: "Roots, Sampling, and Logging are deprecated (SEP-2577). They still work, and they'll keep working for at least twelve months. New implementations shouldn't adopt them." In other words, none of your existing integrations broke that day; the real message is that new projects shouldn't build on these three primitives anymore.
In the same revision cycle, Dynamic Client Registration (DCR) was also added to the Deprecated list through a separate decision track (PR #2858). So this transition is actually a four-part package: Roots, Sampling, Logging, and DCR — each with its own migration path, but all subject to the same official deprecation policy.
Where the deprecation criteria come from
Marking a feature Deprecated isn't arbitrary; the official feature-lifecycle policy lists four criteria: being superseded by another feature, carrying a security/privacy/interoperability risk that can't be fixed in place, "ecosystem telemetry or SDK maintainer consensus indicates negligible adoption relative to its maintenance cost," or other grounds the Core Maintainers deem appropriate. For this trio, the closest criterion is the third: SEP-2577's Motivation section states, "Features that see low adoption, overlap with existing alternatives, or impose disproportionate implementation burden relative to their value are candidates for removal."
Feature | Status (as of 2026-07-28) | Short rationale |
|---|---|---|
Roots | Deprecated (SEP-2577) | Low adoption, ambiguous semantics, more explicit alternatives exist |
Sampling | Deprecated (SEP-2577) | Complex implementation, low adoption, direct API path exists |
Logging | Deprecated (SEP-2577) | Overlaps with mature infrastructure (stderr, OTel), low relative value |
Dynamic Client Registration | Deprecated (PR #2858) | Replaced by Client ID Metadata Documents |
Why all four at once
It's not a coincidence that four features were deprecated in the same revision. SEP-2577 gives Roots and Sampling the same two shared reasons: low client adoption per the feature support matrix, and more explicit alternatives outside the protocol — for Roots, a tool parameter, resource URI, server configuration, or environment variable; for Sampling, direct LLM provider APIs. Sampling gets an extra "complex to implement" point: a correct implementation needs human approval, model-selection logic, security review, and tool-loop support (since SEP-1577). Logging's rationale differs: its in-protocol RPCs overlapped with already-mature external standards like OpenTelemetry, so the spec leans on the existing ecosystem standard instead of keeping this surface in core. DCR's deprecation came from a separate security decision (the move to Client ID Metadata Documents) and is technically independent of the other three — it just shares the same timeline.
The 12-Month Window: Timeline and Risk
Two different numbers shouldn't get conflated here. The blog post says "at least twelve months"; SEP-2577's own text explains the mechanism in more detail: "They will continue to be fully functional in all specification versions released within one year of that version's release... This provides implementations with an extended migration window before the features are fully removed." So this is a rolling window: every new spec version extends support by another year from its own release date — it's not a single, fixed cutoff date. One caveat worth noting: SEP-2577 describes this mechanism "assuming the one-year-per-version support policy proposed in a separate SEP," meaning the per-version one-year support policy is tied to a separate SEP and isn't considered finalized.
The official deprecated-registry page also gives Roots/Sampling/Logging/DCR a concrete lower bound: the table's "Earliest removal" column lists "First revision released on or after 2027-07-28" for all four rows. Reading this as "it's definitely gone by July 2027" would be wrong — the registry only says the first revision after that date _becomes eligible_ for removal; the actual removal decision still belongs to the Core Maintainers and could happen later.
The same 2026-07-28 revision includes one more parallel deprecation: the legacy HTTP+SSE transport. But it uses a different counter — the official record's Deprecated date isn't 2026-07-28, it's 2025-03-26; the early-removal trigger in the same table's column reads "Three months after SEP-2596 reaches Final." The blog post describes this as a "year-long offramp," but the trigger given in the registry is three months — the two official sources use different frames, so don't mistake these for the same clock.
Feature group | Deprecated date | Earliest removal trigger | Clock type |
|---|---|---|---|
Roots / Sampling / Logging / DCR | 2026-07-28 | First revision after 2027-07-28 | Rolling, at least 12 months |
HTTP+SSE transport | 2025-03-26 | 3 months after SEP-2596 Final | Fixed, tied to SEP status |
The risk is this: teams might rush their migration on the assumption that "it's definitely gone in 12 months." The real situation is softer — but that's not a reason to delay the migration; it's just a reason to size the timeline pressure correctly.
Why the actual removal date can shift
The official registry deliberately leaves a gap between "eligible for removal" and "removed": even once eligible, the actual removal is "a Core Maintainer decision taken during release preparation," made while preparing the next spec revision — and it can shift based on real-world adoption, open implementations, and community feedback. The takeaway: the "earliest removal date" is a floor, not an alarm. Don't defer your migration plan to that date, but don't rush either just because "it'll break automatically" — both readings are wrong.
Passing File Scope Without Roots
Roots' official migration path is clear: carry directory/file context through tool parameters, resource URIs, or server configuration instead of a separate protocol-level "roots" list. This also aligns with the general principle of a stateless core — instead of holding server state in the transport, carry it as an explicit handle in the tool argument, visible to the model.
1// BEFORE — implicit directory context via Roots (roots/list requested by the SERVER)2type ListRootsResult = { roots: Array<{ uri: string; name?: string }> };3 4// Server sends the roots/list request to the client inside InputRequiredResult:5const inputRequired = {6 resultType: "input_required" as const,7 inputRequests: { workspace: { method: "roots/list" } },8};9 10// Client returns ListRootsResult back under inputResponses:11function pickScope(responses: Record<string, ListRootsResult>): string {12 return responses.workspace.roots[0].uri; // informational; doesn't bind the server13}14 15// AFTER — explicit scope via tool parameter16type ToolCall = { name: string; arguments: Record<string, string> };17const explicitCall: ToolCall = {18 name: "search_files",19 arguments: {20 workspacePath: "/Users/dev/projects/portfolio",21 pattern: "*.ts",22 },23};24 25console.log(26 inputRequired.resultType,27 pickScope,28 explicitCall.arguments.workspacePath,29);Writing scope explicitly into the tool argument lets the model and the server both know unambiguously which directory they're working with — a clear contract replaces Roots' non-binding ambiguity.
Alternatives to Server-Initiated LLM Calls Without Sampling
Sampling's official migration recommendation is short: integrate directly with LLM provider APIs. But understanding the technical background makes the migration easier. Previously, the server had to call the model through an explicit open stream to say "generate me a completion" (sampling/createMessage, roots/list, elicitation/create). This server-initiated pattern is now replaced by MRTR (SEP-2322): instead of opening a stream, the server returns resultType: "input_required", and the client repeats the same call with the required answers in inputResponses.
1// Server's response — an "input required" signal instead of an open stream.2// inputRequests is not an ARRAY; it's a MAP keyed by identifiers the server assigns.3{4 "resultType": "input_required",5 "inputRequests": {6 "capital_of_france": {7 "method": "sampling/createMessage",8 "params": {9 "messages": [10 { "role": "user", "content": { "type": "text", "text": "What is the capital of France?" } }11 ],12 "systemPrompt": "You are a helpful assistant.",13 "maxTokens": 10014 }15 }16 },17 "requestState": "AEAD-protected blob"18}19 20// Client's repeat call — CreateMessageResult and requestState echoed back as-is21// note: the retry's JSON-RPC id MUST differ from the initial request's22{23 "jsonrpc": "2.0",24 "id": 2,25 "method": "tools/call",26 "params": {27 "name": "summarize_repo",28 "inputResponses": {29 "capital_of_france": {30 "role": "assistant",31 "content": { "type": "text", "text": "The capital of France is Paris." },32 "model": "claude-3-sonnet-20240307",33 "stopReason": "endTurn"34 }35 },36 "requestState": "AEAD-protected blob"37 }38}In practice, this means the server now talks directly to its own LLM client (an OpenAI, Anthropic, or other provider SDK) — model selection, parameters, and streaming stay entirely under your control, with no in-protocol intermediary.
Why this change counts as an improvement
The old server-initiated Sampling pattern's biggest practical problem was that the server had to keep the connection open while waiting for a response — a serious constraint in load balancer, proxy, and serverless environments. MRTR's input_required / inputResponses loop reduces every step to an ordinary request-response pair; the server never holds a stream open, and the client can freely close and reopen the connection while completing the same work. This isn't a loss for Sampling — it's the same functionality moved into a stateless world.
A Telemetry Pattern to Replace Logging
Logging's official migration path has two parts: write to stderr for servers using stdio transport, and move to OpenTelemetry for broader observability needs. The spec also removed the logging/setLevel RPC entirely; log level is now set per-request via the io.modelcontextprotocol/logLevel key inside the _meta field.
1// Per-request log level on every call (instead of logging/setLevel)2declare const client: { request(req: unknown): Promise<unknown> };3 4async function callWithLogLevel() {5 await client.request({6 method: "tools/call",7 params: {8 name: "run_migration",9 _meta: {10 "io.modelcontextprotocol/logLevel": "debug",11 traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",12 },13 },14 });15}The same revision also documented OpenTelemetry trace-context propagation rules (traceparent, tracestate, baggage — SEP-414) inside _meta. This is part of the telemetry infrastructure filling the gap Logging left behind: instead of individual log lines, you can now carry distributed trace context across calls.
Migrating Off the Legacy HTTP+SSE Transport
In the same 2026-07-28 cycle, there's one more deprecation independent of the core primitives: the legacy HTTP+SSE transport, deprecated since 2025-03-26, was formally reclassified as Deprecated under the feature-lifecycle policy in this revision, with Streamable HTTP as the recommended target. This migration has its own timeline and technical details — rather than repeat it here, see MCP 2026-07-28: The Stateless Migration and Moving Without Breaking Your Server, which covers the stateless transition and handshake side in depth.
Inventory: A Script to Find What's Affected
Before starting the migration, you need to find where these four items are used in your codebase. The following simple grep sweep catches the most common call signatures and extension usage points:
1#!/usr/bin/env bash2# MCP deprecated primitive inventory3echo "== Roots =="4grep -rn "roots/list\|roots/list_changed" --include="*.ts" --include="*.py" .5 6echo "== Sampling =="7grep -rn "sampling/createMessage\|elicitation/create" --include="*.ts" --include="*.py" .8 9echo "== Logging (legacy) =="10grep -rn "logging/setLevel" --include="*.ts" --include="*.py" .11 12echo "== Dynamic Client Registration =="13grep -rn "register_client\|dynamic_client_registration" --include="*.ts" --include="*.py" .14 15echo "== Tasks extension usage =="16grep -rn "io.modelcontextprotocol/tasks" --include="*.ts" --include="*.py" .The last line was added deliberately: in the same 2026-07-28 revision, the experimental Tasks feature was also pulled out of core into an official extension called io.modelcontextprotocol/tasks. Extension usage, as the changelog puts it, is declared via the extensions field added to the ClientCapabilities and ServerCapabilities types — on the client side this capability block travels in _meta.io.modelcontextprotocol/clientCapabilities on every request, and on the server side it's carried in the server/discover response — your inventory script should scan for this field too, because Tasks is now opt-in.
Prioritizing your inventory
The script's output usually gives you four separate lists; don't migrate them all at once. Migrate Logging first — it's lowest-risk, since moving to stderr and OpenTelemetry doesn't change your application logic, only the output destination. Handle Roots next — moving to tool parameters is usually a single signature change. Sampling takes the most effort, since it may need an LLM provider integration built from scratch, so leave it third. Save DCR for last: the official registry points it to Client ID Metadata Documents but notes, "It remains available for backwards compatibility with authorization servers that do not support Client ID Metadata Documents" — so if yours doesn't support CIMD, the existing flow keeps working.
GOLDEN TIP
The most valuable insight in this article
This tip holds the article's most important takeaway.
Easter Egg
You found a hidden gem!
There's a hidden detail in this section. Want to uncover it?
Reader Reward
Before kicking off your migration plan, we put together a short checklist based on official sources that you can review with your team. Each item corresponds to a migration path covered in this post.
FAQ
Why was the Sampling feature in MCP deprecated?
SEP-2577 lists three reasons: it's complex to implement (needing human approval, model-selection logic, security review, and tool-loop support since SEP-1577), client adoption is low according to the feature support matrix, and a direct alternative exists outside the protocol. The official migration recommendation is for servers to connect directly to LLM provider APIs, so that model selection, parameters, and streaming stay entirely under the server's control.
What should I use instead of Roots?
You should now carry directory or file context explicitly through tool parameters, resource URIs, or server configuration instead of a separate protocol-level Roots list. This gives the model and server a clear contract instead of leaving scope ambiguous.
How long does the MCP deprecation window last?
For Roots, Sampling, Logging, and Dynamic Client Registration, the window is a rolling structure: every new spec version extends support by at least one more year from its own release date. The official registry says the earliest removal for these four features could happen in the first revision released on or after 2027-07-28 — but that's not a firm removal date, just an eligibility threshold.
How do I update my MCP server that uses Logging?
logging/setLevel was removed in this revision: the changelog's Major changes list states, "Remove ping, logging/setLevel, and notifications/roots/list_changed." Level is now set per-request via io.modelcontextprotocol/logLevel inside _meta, and notifications/message is only emitted for requests carrying this field. Long term, the official recommendation is stderr for stdio and OpenTelemetry for broader observability.
Is the HTTP+SSE transport's deprecation on the same timeline as Roots/Sampling/Logging?
No. HTTP+SSE's official Deprecated date in the registry is 2025-03-26, and its earliest removal trigger is three months after SEP-2596 reaches Final status — a completely separate clock from the Roots/Sampling/Logging/DCR rolling window that starts 2026-07-28 and runs at least 12 months.
What happens if I keep using a deprecated feature?
SEP-2577 says explicitly, "they still work" — nothing breaks the moment something is deprecated. But new implementations are recommended not to adopt these primitives, and the final removal decision belongs to the Core Maintainers — so taking inventory now and clarifying your migration path is safer than a rushed migration later.
I'm writing a new MCP server — should I avoid Roots/Sampling/Logging entirely?
The official announcement recommends exactly that: "New implementations shouldn't adopt them." So if you're building a server or client from scratch, preferring scope via tool parameters, direct integration with LLM provider APIs, and OpenTelemetry-based observability from day one is the shortest path to avoiding migration debt later.
Conclusion
The deprecation of Roots, Sampling, Logging, and DCR is the first time MCP has enforced an official deprecation policy — and that's good news, because the timeline and migration path are no longer ambiguous; they're documented and predictable. To scan your codebase for these four items, you can check MCP (Model Context Protocol): The AI Integration Standard for core concepts, Authorizing an AI Coding Agent: Prompt Injection Risk for the security side, and Agentic AI: Tool Use, Planner Loops, and Production Agent Architecture for the tool-use patterns replacing Sampling in agentic flows. If you want an overview of Claude Code's MCP integration, Claude Code MCP: The AI Plugin Ecosystem via Model Context Protocol is a good starting point; to clarify which tool to pick and when, see Skill, Subagent, Hook, MCP in Claude Code: Which One, When.
The last step is always the same: take inventory first, verify the migration path against the official source, then migrate — not out of panic, but trusting the timeline.
Sources
- MCP Roadmap Blog — 2026-07-28 announcement — the official announcement of the Roots/Sampling/Logging deprecation and the source of the "at least twelve months" phrase
- MCP Specification Changelog 2026-07-28 — technical breakdown of
logging/setLevelremoval, per-request log level via_meta, and OTel trace context changes - MCP Deprecated Features Registry — the migration path, deprecated date, and earliest removal trigger for each feature
- MCP Feature Lifecycle Policy — deprecation criteria and window calculation rules
- SEP-2577: Deprecate Roots, Sampling, and Logging — the verbatim text of the rolling-window mechanism, Final status
- MCP Tasks Extension Overview — Tasks' move from core to extension and the capability-declaration pattern
Tags
iOS Development News
Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.
We respect your privacy. You can unsubscribe at any time.

