All Articles
CategoryAI
Reading Time
12 min read
Published
2026-08-11
Word Count
3,290words

Grab a coffee — this one is a deep dive!

MCP Deprecates Roots, Sampling, Logging: 12-Month Window

Summary

MCP deprecated Roots, Sampling, and Logging: SEP-2577's rolling 12-month window, the official migration path for each, and an inventory script for your transition plan.

  • MCP's 2026-07-28 spec revision deprecated Roots, Sampling, and Logging under SEP-2577; Dynamic Client Registration was added by a separate decision.
  • The window isn't fixed, it's rolling: every new spec version extends support by at least 12 more months from its own release date; earliest removal is the first revision on or after 2027-07-28.
  • Official migration paths: tool parameters/resource URIs for Roots, direct LLM provider API integration (via MRTR) for Sampling, stderr/OpenTelemetry for Logging.
  • The legacy HTTP+SSE transport's deprecation runs on a separate clock (started 2025-03-26, triggers 3 months after SEP-2596 reaches Final) — don't confuse it with the Roots/Sampling/Logging timeline.
MCP Deprecates Roots, Sampling, Logging: 12-Month Window

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

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.

ts
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 server
13}
14 
15// AFTER — explicit scope via tool parameter
16type 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.

json
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": 100
14 }
15 }
16 },
17 "requestState": "AEAD-protected blob"
18}
19 
20// Client's repeat call — CreateMessageResult and requestState echoed back as-is
21// note: the retry's JSON-RPC id MUST differ from the initial request's
22{
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.

ts
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:

bash
1#!/usr/bin/env bash
2# MCP deprecated primitive inventory
3echo "== 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

Tags

#MCP#Model Context Protocol#SEP-2577#deprecation#OpenTelemetry#API migration#AI integration
Muhittin Çamdalı

Muhittin Çamdalı

Lead Mobile Engineer

Lead Mobile Engineer with 12+ years of experience. Expert in iOS, Android and cross-platform architectures with Swift, SwiftUI, Kotlin and Flutter. I build performant, user-friendly mobile apps.

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.

Share