MCP Client Features: Sampling, Elicitation and Roots Explained

TL;DR
MCP sampling, elicitation and roots explained against the 2026-07-28 spec, with JSON-RPC examples and which of 24 clients support each one.
Last updated: October 18, 2026
MCP has six features. Servers offer three of them (tools, resources and prompts), and clients can offer three back to servers (elicitation, sampling and roots). Under the current spec, 2026-07-28, elicitation is the only active client feature: sampling and roots are deprecated (SEP-2577), with the earliest removal in the first revision released on or after 2027-07-28. The verdict for server authors: build on tools, which all 24 clients in our verified MCP client matrix document; treat elicitation as a progressive enhancement (13 of 24); and do not start new work on sampling (2 of 24) or roots (4 of 24).
The biggest wire change in 2026-07-28 is that servers no longer push requests to clients. Every client feature now travels inside an input_required result that the client answers by retrying its own request, a pattern the spec calls multi round-trip requests (MRTR). This post explains each feature by who starts it, shows the JSON-RPC exchange from the spec, maps it to real client support, and ends with a dual-era TypeScript server you can run. For the full list of 2026-07-28 changes, see our 2026-07-28 breaking changes checklist.
The six features at a glance#
| Feature | Who offers it | Who starts it | Control model | Spec status in 2026-07-28 | Clients documenting support (of 24) |
|---|---|---|---|---|---|
| Tools | Server | Client sends tools/call | Model-controlled | Active | 24 |
| Resources | Server | Client sends resources/read | Application-controlled | Active | 15 |
| Prompts | Server | Client sends prompts/get | User-controlled | Active | 13 |
| Elicitation | Client | Server asks via input_required, user answers | User answers in the client UI | Active | 13 |
| Sampling | Client | Server asks via input_required | Client keeps model access | Deprecated | 2 |
| Roots | Client | Server asks via input_required | Client decides what to expose | Deprecated | 4 |
The control models come from the server features overview: prompts are user-controlled (for example slash commands), resources are application-controlled (context the client attaches, such as file contents), and tools are model-controlled (functions the LLM decides to call). The support counts are clients whose own documentation says yes, read on October 18, 2026. "Not stated" in the matrix is not the same as "no": many clients simply do not document the less common features.
%% title: Who starts each MCP feature
flowchart LR
C[Client] -->|tools/call| T[Tools - model-controlled]
C -->|resources/read| R[Resources - app-controlled]
C -->|prompts/get| P[Prompts - user-controlled]
T -->|input_required| E[Elicitation - active]
T -->|input_required| S[Sampling - deprecated]
T -->|input_required| RT[Roots - deprecated]In 2026-07-28 the client always sends the first request. The client features ride back on an input_required result from tools/call, resources/read or prompts/get (the diagram shows tools, the most common case).
What changed in 2026-07-28: no more server-initiated requests#
The 2026-07-28 revision made the protocol core stateless. The initialize / notifications/initialized handshake is gone. Instead, every request carries io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities in its _meta field (both required), servers must implement server/discover so clients can learn versions and capabilities up front, and every result carries a required resultType of "complete" or "input_required" (changelog). Results from older servers that omit resultType are treated as "complete".
That rewires the client features. The MRTR page is blunt about it: servers MUST send roots/list, sampling/createMessage and elicitation/create using the MRTR pattern, and "the previous pattern of server-initiated requests is no longer supported. This is a breaking change."
The mechanics:
- The client sends
tools/call,resources/readorprompts/get. These are the only three methods allowed to return anInputRequiredResult. - The server answers with
resultType: "input_required", aninputRequestsmap (keyed by names the server picks) and optionally an opaquerequestState. - The client fulfils each embedded request (shows a form, runs a completion, lists roots) and retries the original request with a new JSON-RPC id, adding
inputResponsesand echoingrequestStateunchanged. - The server either finishes (
"complete") or asks again.
Two server-side rules matter in practice. First, a server MUST NOT put a request type into inputRequests that the client did not declare in its capabilities. If a request needs a capability the client lacks, the call fails with MissingRequiredClientCapabilityError, code -32021, with data.requiredCapabilities naming what is missing (basic protocol). Second, requestState comes back from the client, so servers MUST treat it as attacker-controlled and protect its integrity (for example with HMAC or AEAD) when it affects authorization, resource access or business logic.
The session and transport side of this change (no Mcp-Session-Id, no resumable streams) is covered in our stateless MCP migration guide, so this post stays on the features.
Tools: the universal surface#
What it is: a function the server exposes with a name, description and JSON Schema input. Who starts it: the model decides to call a tool, and the client sends tools/call. This is the spec's own example from the tools page:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "New York"
}
}
}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"content": [
{
"type": "text",
"text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
}
],
"isError": false
}
}
The spec notes that its examples omit the required _meta for brevity. A real 2026-07-28 request also carries the protocol version and client capabilities, which is why the captured output later in this post includes them.
Support: 24 of 24 clients in the matrix document tools, from Claude Code, Cursor and VS Code to Zed, LM Studio and Microsoft Copilot Studio. Guidance: tools are the one surface you can count on everywhere, so anything critical belongs behind a tool. The 2026-07-28 spec also asks servers to return tools in a deterministic order and requires ttlMs and cacheScope on list results (SEP-2549), which helps clients cache tool lists. For a longer tool tutorial, see how to build MCP servers and the complete guide to MCP servers.
Resources: context the application attaches#
What it is: data identified by a URI (a file, a database row, an API payload). Who starts it: the host application, or the user through it, decides when to attach a resource. The model does not pull resources on its own. From the resources page:
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/read",
"params": {
"uri": "file:///project/src/main.rs"
}
}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"contents": [
{
"uri": "file:///project/src/main.rs",
"mimeType": "text/x-rust",
"text": "fn main() {\n println!(\"Hello world!\");\n}"
}
],
"ttlMs": 60000,
"cacheScope": "private"
}
}
ttlMs and cacheScope ("public" or "private") are new in 2026-07-28 and required on resources/list, resources/read and resources/templates/list results, alongside the tool and prompt lists.
Support: 15 clients document resources, LangChain MCP adapters explicitly do not, and 8 do not say. Guidance: resources are a good fit for context a user might want to attach, but because the host controls when they are read, do not hide anything the model must reach behind a resource alone. Mirror it through a tool.
Prompts: templates the user picks#
What it is: a named, parameterised message template. Who starts it: the user, usually through a slash command or menu. From the prompts page:
{
"jsonrpc": "2.0",
"id": 2,
"method": "prompts/get",
"params": {
"name": "code_review",
"arguments": {
"code": "def hello():\n print('world')"
}
}
}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"description": "Code review prompt",
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Please review this Python code:\ndef hello():\n print('world')"
}
}
]
}
}
Support: 13 clients document prompts, 2 explicitly do not (Microsoft Copilot Studio and LangChain MCP adapters), and 9 do not say. Zed is a useful reference point: its MCP docs say it currently supports tools and prompts. Guidance: prompts are worth shipping for repeatable workflows (a review template, a release checklist), since users of supporting clients get them as one-click commands. They are not a substitute for tools.
Elicitation: the client feature to build on#
What it is: the server asks the user for input in the middle of a request. Who starts it: the server asks, via input_required; the user answers in the client's UI. The elicitation page defines two modes:
- Form mode (in-band): the server sends a
requestedSchemathat is a flat object of primitive properties - strings (withemail,uri,dateanddate-timeformats), numbers and integers, booleans, and single- or multi-select enums. No nesting. - URL mode (out-of-band, added in 2025-11-25): the client opens a URL and the data does not pass through the client. Servers MUST NOT use form mode to ask for passwords, API keys, access tokens or payment credentials, and must use URL mode for those.
A client declares support on every request:
{
"_meta": {
"io.modelcontextprotocol/clientCapabilities": {
"elicitation": { "form": {}, "url": {} }
}
}
}
For backwards compatibility, an empty "elicitation": {} means form mode only. Responses use a three-action model: accept (with content), decline (the user said no) and cancel (the user dismissed it). Clients must make clear which server is asking and offer clear decline and cancel options, and servers must handle all three outcomes.
Here is the spec's MRTR exchange from the tools page. The server needs a GitHub username before it can answer, so it returns input_required:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "input_required",
"inputRequests": {
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
}
}
}
},
"requestState": "eyJsb2NhdGlvbiI6Ik5ldyBZb3JrIn0..."
}
}
The client shows the form, then retries with a new id, the answer and the echoed state:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "New York"
},
"inputResponses": {
"github_login": {
"action": "accept",
"content": {
"name": "octocat"
}
}
},
"requestState": "eyJsb2NhdGlvbiI6Ik5ldyBZb3JrIn0..."
}
}
One more 2026-07-28 detail: notifications/elicitation/complete and the elicitationId field on URL-mode requests (both added in 2025-11-25) were removed. The client learns the outcome of a URL-mode flow by retrying the original request, and the server correlates retries through requestState.
Support: 13 clients document elicitation: Claude Code, Claude Agent SDK, OpenAI Codex CLI, ChatGPT (apps and connectors), Cursor, VS Code (GitHub Copilot agent mode), GitHub Copilot CLI, Amazon Q Developer / Kiro, goose, opencode, Vercel AI SDK, LangChain MCP adapters and Mastra. Zed explicitly does not, and 10 do not say. The live list is on /mcp-servers/clients.
%% title: Elicitation as a multi round-trip request
sequenceDiagram
participant U as User
participant C as Client
participant S as Server
C->>S: tools/call clear-notes (id 2)
S-->>C: input_required + elicitation/create form
C->>U: Show form - Delete all notes?
U-->>C: accept, confirm true
C->>S: tools/call clear-notes (id 3) + inputResponses
S-->>C: resultType complete - All notes deletedSampling: deprecated, so do not start new work on it#
What it is: the server asks the client's model for a completion, so servers can use AI capabilities "with no server API keys necessary," in the spec's words. Who starts it: the server, via a sampling/createMessage entry in inputRequests. This is the spec's example from the sampling page, with one adaptation: we dropped the "includeContext": "thisServer" line, because the thisServer and allServers values are themselves deprecated (omit the field or use "none"). The model name is the spec's illustration, not a recommendation.
{
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "What is the capital of France?"
}
}
],
"modelPreferences": {
"hints": [
{
"name": "claude-3-sonnet"
}
],
"costPriority": 0.3,
"intelligencePriority": 0.8,
"speedPriority": 0.5
},
"temperature": 0.1,
"systemPrompt": "You are a helpful assistant.",
"maxTokens": 100
}
}
The client's answer goes back inside inputResponses on the retried request:
{
"role": "assistant",
"content": {
"type": "text",
"text": "The capital of France is Paris."
},
"model": "claude-3-sonnet-20240307",
"stopReason": "endTurn"
}
Tool use inside sampling (passing tools and toolChoice) requires the client to declare the sampling.tools capability.
Status: deprecated in 2026-07-28 by SEP-2577. The deprecated features registry gives the migration path as integrating directly with LLM provider APIs. Deprecated features remain fully functional during the window (at least twelve months under the SEP-2596 lifecycle policy), but new implementations SHOULD NOT adopt them.
Support: only VS Code (GitHub Copilot agent mode) and GitHub Copilot CLI document sampling. VS Code's MCP extension guide says it implements the full spec, including sampling, roots and elicitation. Five clients say no: Claude Desktop / claude.ai (Anthropic's connector docs say it implements a subset and does not yet support sampling), Zed, goose (which stopped advertising and handling sampling/createMessage after the deprecation), LangChain MCP adapters and Mastra. The other 17 do not say.
The design goal was sound: the client keeps control of model choice, cost and human review, and a server author does not need to manage keys. In practice adoption never caught up, and a feature only two documented clients support is a poor foundation for anything a user depends on.
Roots: deprecated, so pass paths explicitly#
What it is: the client tells the server which file:// directories matter. The roots page is explicit that roots are "informational guidance rather than an access-control mechanism"; the protocol does not enforce that servers stay inside them. Who starts it: the server, via roots/list in inputRequests:
{
"method": "roots/list"
}
The client's answer, returned inside inputResponses on the retry:
{
"roots": [
{
"uri": "file:///home/user/projects/myproject",
"name": "My Project"
}
]
}
notifications/roots/list_changed was removed in 2026-07-28.
Status: deprecated by SEP-2577. The migration is to pass directories or files via tool parameters, resource URIs or server configuration. Support: 4 clients document roots (Claude Code, Cursor, VS Code with GitHub Copilot, goose), 2 say no (LangChain MCP adapters and Mastra), and 18 do not say.
Transports: stdio vs Streamable HTTP vs deprecated SSE#
The transports page defines two standard transports, and on neither do servers initiate JSON-RPC requests.
- stdio: the client launches the server as a subprocess and exchanges newline-delimited JSON-RPC over stdin and stdout. Nothing but MCP messages may go to stdout; logs go to stderr.
- Streamable HTTP: each message is an HTTP POST to a single MCP endpoint, and the reply is a JSON object or a request-scoped SSE stream. In 2026-07-28,
Mcp-Session-Idand protocol-level sessions are gone (SEP-2567), POSTs carryMCP-Protocol-Versionplus the requiredMcp-MethodandMcp-Nameheaders (SEP-2243), SSE resumability viaLast-Event-IDis removed, the HTTP GET stream andresources/subscribeare replaced bysubscriptions/listen, and servers MUST validate theOriginheader (Streamable HTTP). - HTTP+SSE (from 2024-11-05): deprecated since 2025-03-26 and listed in the deprecated registry with Streamable HTTP as the migration path.
| Transport | Where it runs | Auth model | Status in 2026-07-28 | Clients documenting it (of 24) |
|---|---|---|---|---|
| stdio | Local subprocess | Credentials from the environment | Active | 22 |
| Streamable HTTP | Remote or local HTTP endpoint | OAuth 2.1 (optional) | Active | 22 |
| HTTP+SSE | Remote, two endpoints | Legacy | Deprecated | 16 |
goose, Microsoft Copilot Studio and Mastra explicitly do not support legacy SSE. On the server side, the TypeScript SDK v2 never serves SSE; the frozen v1 SSEServerTransport lives in @modelcontextprotocol/server-legacy/sse for anyone who must keep an old endpoint alive (legacy clients doc).
OAuth for remote servers#
Authorization is OPTIONAL in MCP. HTTP transports SHOULD follow the spec when they protect a server; stdio implementations SHOULD NOT, and take credentials from the environment instead. A protected MCP server acts as an OAuth 2.1 resource server. The flow:
- The client calls the server without a token and gets a 401 with
WWW-Authenticatepointing atresource_metadata(or finds it at the well-known protected-resource URI). - The server MUST implement Protected Resource Metadata (RFC 9728), which lists its
authorization_servers. - The client fetches authorization server metadata and MUST support both RFC 8414 and OpenID Connect Discovery.
- The client authorizes with PKCE (
S256when technically capable) and theresourceparameter from RFC 8707, and validates a presentissper RFC 9207. - The client calls the MCP server with a Bearer token.
For registration, the client registration page sets a priority order: pre-registered client information first, then OAuth Client ID Metadata Documents when the authorization server advertises support, then Dynamic Client Registration as a fallback. DCR (RFC 7591) is deprecated in 2026-07-28 and kept for authorization servers that lack CIMD support.
Support: 19 of 24 clients document OAuth for remote servers. The Claude Agent SDK is marked no because it does not run an interactive OAuth flow; you pass the token in headers yourself. For enterprise identity patterns, see our MCP OAuth enterprise auth deep dive.
%% title: OAuth discovery for a remote MCP server
sequenceDiagram
participant C as Client
participant M as MCP server
participant A as Auth server
C->>M: POST /mcp without token
M-->>C: 401 + WWW-Authenticate resource_metadata
C->>M: GET protected resource metadata
M-->>C: authorization_servers
C->>A: GET AS metadata (RFC 8414 or OIDC)
C->>A: Authorize with PKCE S256 + resource
A-->>C: code + iss
C->>A: Exchange code for access token
C->>M: POST /mcp with Bearer tokenBuild it: a dual-era server with the official TypeScript SDK#
The TypeScript SDK's main branch is v2, split into @modelcontextprotocol/server and @modelcontextprotocol/client, and its README calls it "the stable release line, implementing the 2026-07-28 MCP spec." The older @modelcontextprotocol/sdk package (1.32.1 on npm as of today) still tops out at protocol version 2025-11-25.
npm install @modelcontextprotocol/server@2.3.1 zod@4.6.7This server exposes one of each server feature plus an elicitation-gated tool:
import type { CallToolResult, InputRequiredResult } from '@modelcontextprotocol/server';
import { McpServer, acceptedContent, inputRequired, inputResponse } from '@modelcontextprotocol/server';
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
const notes = new Map<string, string>([['welcome', 'First note']]);
function buildServer() {
const server = new McpServer({ name: 'notes', version: '1.0.0' });
// Tool: model-controlled. Every client in the matrix supports this.
server.registerTool(
'add-note',
{
description: 'Save a note under a short id',
inputSchema: z.object({ id: z.string(), text: z.string() })
},
async ({ id, text }) => {
notes.set(id, text);
return { content: [{ type: 'text', text: `Saved note ${id}` }] };
}
);
// Resource: application-controlled context the host can attach.
server.registerResource(
'note-index',
'notes://index',
{ title: 'Note index', description: 'All note ids', mimeType: 'text/plain' },
async uri => ({
contents: [{ uri: uri.href, text: [...notes.keys()].join('\n') }]
})
);
// Prompt: user-controlled template, often surfaced as a slash command.
server.registerPrompt(
'summarize-notes',
{
title: 'Summarize notes',
description: 'Ask the model to summarize every saved note',
argsSchema: z.object({ style: z.string().describe('For example: bullets') })
},
({ style }) => ({
messages: [
{
role: 'user' as const,
content: { type: 'text' as const, text: `Summarize these notes as ${style}:\n${[...notes.values()].join('\n')}` }
}
]
})
);
// Elicitation via the 2026-07-28 multi round-trip pattern:
// return input_required, the client asks the user and retries the call.
const confirmSchema = z.object({ confirm: z.boolean().meta({ title: 'Delete every note?' }) });
server.registerTool(
'clear-notes',
{ description: 'Delete all notes after the user confirms', inputSchema: z.object({}) },
async (_args, ctx): Promise<CallToolResult | InputRequiredResult> => {
// A decline or cancel must end the call, not re-ask forever.
const view = inputResponse(ctx.mcpReq.inputResponses, 'confirm');
if (view.kind === 'elicit' && view.action !== 'accept') {
return { content: [{ type: 'text', text: 'Nothing deleted' }], isError: true };
}
const answer = acceptedContent(ctx.mcpReq.inputResponses, 'confirm', confirmSchema);
if (answer?.confirm !== true) {
return inputRequired({
inputRequests: {
confirm: inputRequired.elicit({
message: `Delete all ${notes.size} notes?`,
requestedSchema: confirmSchema
})
}
});
}
notes.clear();
return { content: [{ type: 'text', text: 'All notes deleted' }] };
}
);
return server;
}
serveStdio(buildServer);
How the elicitation handler works, per the SDK's input-required guide: the handler runs on every round. On the first entry there is no answer, so it returns inputRequired(...) with an inputRequired.elicit(...) form built from the zod schema. On the retry, acceptedContent validates the untrusted client answer against the same schema. acceptedContent returns undefined for a missing, declined or cancelled answer alike, which is why the handler checks inputResponse(...) first: without that check, a user who clicks Decline would be asked again on every retry. The SDK also provides inputRequired.elicitUrl, inputRequired.createMessage (sampling) and inputRequired.listRoots() builders for the other request types.
Here is real stdio output from running that server under Node 22.22.0, with hand-written JSON-RPC (trimmed for width; the _meta.serverInfo block is removed). server/discover first:
{"result":{"supportedVersions":["2026-07-28"],"capabilities":{"tools":{"listChanged":true},"resources":{"listChanged":true},"prompts":{"listChanged":true}},"resultType":"complete","ttlMs":0,"cacheScope":"private"},"jsonrpc":"2.0","id":1}
tools/call clear-notes with "elicitation":{"form":{}} declared returns the form request:
{"result":{"resultType":"input_required","inputRequests":{"confirm":{"method":"elicitation/create","params":{"message":"Delete all 1 notes?","requestedSchema":{"type":"object","properties":{"confirm":{"type":"boolean","title":"Delete every note?"}},"required":["confirm"],"$schema":"https://json-schema.org/draft/2020-12/schema"},"mode":"form"}}}},"jsonrpc":"2.0","id":2}
The retry with "inputResponses":{"confirm":{"action":"accept","content":{"confirm":true}}} and a new id completes:
{"result":{"content":[{"type":"text","text":"All notes deleted"}],"resultType":"complete"},"jsonrpc":"2.0","id":3}
A retry with {"action":"decline"} instead returns "Nothing deleted" with isError: true. And the same first call from a client whose capabilities omit elicitation never reaches the wire as a form; the SDK rejects it:
{"jsonrpc":"2.0","id":4,"error":{"code":-32021,"message":"Cannot request input 'confirm' (elicitation/create): the request's client capabilities do not declare the required capability","data":{"requiredCapabilities":{"elicitation":{"form":{}}}}}}
One handler, both eras. serveStdio(factory) answers legacy initialize requests by default, and the SDK's legacy shim (on by default) turns the same input_required return into a real pushed elicitation/create request for 2025-era connections, then re-enters the handler with the answer. We confirmed this by sending initialize with protocol version 2025-11-25: the server negotiated 2025-11-25, and calling clear-notes produced a server-pushed elicitation/create with the same message and schema. Set ServerOptions.inputRequired.legacyShim: false to fail loudly instead, or pass legacy: 'reject' to serveStdio to make the server modern-only (legacy clients doc). On the client side, note that the v2 Client still defaults to the legacy handshake; version negotiation mode 'auto' probes with server/discover and falls back, and { pin: '2026-07-28' } never falls back (protocol versions doc).
The v1 equivalent, for servers still on @modelcontextprotocol/sdk
On @modelcontextprotocol/sdk@1.32.1, the same confirmation is written as a push: await server.server.elicitInput({ mode: 'form', message, requestedSchema }) inside the tool handler, with McpServer and StdioServerTransport. That type-checks, and it works with 2025-era clients. But v1 cannot speak 2026-07-28: sent an initialize with protocol version 2026-07-28, our v1 test server answered with 2025-11-25. In v2, the push channels (ctx.mcpReq.elicitInput and ctx.mcpReq.requestSampling) throw on a 2026-07-28 request, so new code should use inputRequired(...). v1.x keeps getting bug fixes and security updates for at least six months after the v2 release on 2026-07-27.
Examples were type-checked with tsc --noEmit (TypeScript 7.0.2, strict, moduleResolution: bundler) against @modelcontextprotocol/server@2.3.1 and @modelcontextprotocol/sdk@1.32.1 with zod@4.6.7, and the v2 server was exercised over stdio with hand-written JSON-RPC. No model or API keys were involved. Once your server works, the MCP server directory is a good place to see how other servers present their tools.
What a server author should rely on#
- Put anything critical behind a tool. It is the only feature all 24 clients document.
- Use elicitation as an enhancement, with a fallback. 13 clients document it, and the SDK rejects an elicitation request with
-32021when the client did not declare the capability. Accept the same value as an optional tool argument so a client without elicitation can still complete the call, and always handle decline and cancel. - Need an LLM call inside your server? Call your provider's API directly. That is the spec's migration path for sampling, and it works in every client.
- Need workspace paths? Take them as tool parameters or server configuration, the migration path for roots.
- Ship stdio for local and Streamable HTTP for remote. Do not build new endpoints on HTTP+SSE.
- If remote and protected, implement Protected Resource Metadata and an OAuth flow that works with PKCE
S256, theresourceparameter and Client ID Metadata Documents. - Return
ttlMsandcacheScopeon list and read results, and keep tool order deterministic.
%% title: What a server author can rely on
flowchart TD
A[What do you need from the client?] --> B{Model takes an action}
B -->|yes| T[Tool - 24 of 24 clients]
A --> C{Ask the user something}
C -->|yes| E[Elicitation - 13 of 24]
E --> F[Fallback - optional tool argument]
A --> D{Need an LLM call}
D -->|yes| S[Call a provider API - sampling deprecated]
A --> G{Need workspace paths}
G -->|yes| R[Tool parameter or config - roots deprecated]FAQ#
Is MCP sampling deprecated?#
Yes. Sampling was deprecated in the 2026-07-28 spec by SEP-2577, together with roots and logging. It still works during the deprecation window, and the earliest removal is the first spec revision released on or after 2027-07-28. The documented migration is to call LLM provider APIs directly from your server.
What replaced server-initiated requests in MCP?#
Multi round-trip requests. The server returns a result with resultType: "input_required" and an inputRequests map, and the client retries the original request with a new JSON-RPC id plus inputResponses (and the echoed requestState). Only tools/call, resources/read and prompts/get may return this result.
Which MCP clients support elicitation?#
As of October 18, 2026, 13 of the 24 clients in our matrix document it, including Claude Code, Cursor, VS Code with GitHub Copilot, OpenAI Codex CLI, ChatGPT, goose and opencode. Zed explicitly does not, and 10 clients do not say. The current list is on /mcp-servers/clients.
Should I still use the HTTP+SSE transport?#
No. HTTP+SSE has been deprecated since 2025-03-26, and Streamable HTTP is the migration path. The TypeScript SDK v2 does not serve SSE at all; the frozen v1 transport lives in @modelcontextprotocol/server-legacy/sse only for compatibility.
Can I keep using @modelcontextprotocol/sdk v1?#
For now. v1.x gets bug fixes and security updates for at least six months after the v2 release on 2026-07-27, but it speaks only up to protocol version 2025-11-25. For 2026-07-28 support, move to @modelcontextprotocol/server, whose legacy shim keeps serving 2025-era clients from the same handlers.
Sources#
- MCP specification 2026-07-28 overview
- MCP 2026-07-28 changelog
- Deprecated features registry
- Multi round-trip requests
- Elicitation
- Sampling
- Roots
- Server features overview
- Tools
- Resources
- Prompts
- server/discover
- Transports
- Streamable HTTP
- Authorization
- Client registration
- MCP TypeScript SDK README
- TypeScript SDK: input-required guide
- TypeScript SDK: legacy clients
- TypeScript SDK: protocol versions
- @modelcontextprotocol/server on npm
- @modelcontextprotocol/sdk on npm
- VS Code MCP extension guide
- Claude connectors: building
- Zed MCP docs
Continue Reading#
Get the next deep dive like this in your inbox
One email a week on MCP and the rest of the AI dev stack. Free.
Read next on AI coding tools
The MCP 2026-07-28 Rewrite: What Breaks and How to Migrate
MCP 2026-07-28 breaking changes: a stateless core, deprecated Roots, Sampling and Logging, and tougher OAuth, plus a migration checklist for server authors.
11 min readMCP Clients Compared: How to Pick a Host for 2026
Claude Code, Claude Desktop, Cursor, VS Code, Zed, and opencode all speak MCP differently. Here is how their transport, auth, and tool-limit support compares.
9 min readHow to Build MCP Servers in TypeScript
Build MCP servers in TypeScript step by step: setup, tools, resources, prompts, testing with Claude Code and the MCP Inspector, and production patterns.
14 min readNew here? Start with
Technical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.





