# Capability primitives

Canonical URL: https://vercel-mcp-reference.vercel.app/internals/primitives/
Markdown: https://vercel-mcp-reference.vercel.app/internals/primitives.md
Audience: engineer, architect, security, non-technical. MCP spec version: 2026-07-28. Last reviewed: 2026-08-26. Status: stable.

> **Deprecation notice (SEP-2577).** The **Roots**, **Sampling**, and **Logging** features are deprecated as of the 2026-07-28 revision. They remain fully functional for at least a twelve-month window and are documented below because you will meet them in deployed servers, but new implementations should not adopt them. Suggested migrations: pass directories or files via tool parameters, resource URIs, or server configuration instead of roots; integrate directly with LLM provider APIs instead of sampling (on Vercel, that is the AI SDK or AI Gateway); log to `stderr` (stdio) or use OpenTelemetry instead of MCP logging. See the [deprecated features registry](https://modelcontextprotocol.io/specification/2026-07-28/deprecated).

## Plain-language explanation

**TL;DR:** MCP exposes three core building blocks ([tools](https://vercel-mcp-reference.vercel.app/glossary/#tool), [resources](https://vercel-mcp-reference.vercel.app/glossary/#resource), and [prompts](https://vercel-mcp-reference.vercel.app/glossary/#prompt)) and a set of auxiliary ones ([sampling](https://vercel-mcp-reference.vercel.app/glossary/#sampling), [elicitation](https://vercel-mcp-reference.vercel.app/glossary/#elicitation), progress, [cancellation](https://vercel-mcp-reference.vercel.app/glossary/#cancellation), logging, [roots](https://vercel-mcp-reference.vercel.app/glossary/#root), completions). Each primitive is controlled by a different actor: the model picks tools, the application picks resources, the user picks prompts, and the host mediates everything else. As of 2026-07-28, the server-initiated primitives no longer arrive as server requests: a server that needs model inference or user input returns an **`input_required` result**, and the client retries the original request with the answers attached (the [MRTR pattern](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr)).

> **SDK status.** Wire status: the pinned stack (`mcp-handler` 2.1.1 on `@modelcontextprotocol/server` 2.0.0) serves the 2026-07-28 contract natively over Streamable HTTP and falls back to stateless 2025-11-25 Streamable HTTP for legacy clients; the SDK `Client` defaults to that legacy handshake unless you opt in to modern version negotiation, so the examples' in-memory test suites exercise only the legacy path.

The easiest way to remember the primitives is "who is in charge of pulling the trigger." A tool is an action the AI model can choose to invoke ("create issue," "send email"), so the model is in control, but the host must gate it with user [consent](https://vercel-mcp-reference.vercel.app/glossary/#consent). A resource is a piece of context (a file, a row, a snapshot) that the application or user attaches to the model's context window; the model never reaches for resources on its own. A prompt is a reusable template the user explicitly chooses from a menu, like "Summarize this PR." The remaining primitives are smaller and compose with the core three to handle long-running work, isolation, and user assistance.

The rule of thumb most teams remember: **tools are verbs, resources are nouns, prompts are templates**. Auxiliary primitives are protocol plumbing that makes the core three usable in real applications. On Vercel, all of them travel over the [Streamable HTTP transport](https://vercel-mcp-reference.vercel.app/glossary/#streamable-http-transport), and 2026-07-28 fits them to the platform: nothing a server needs from the client outlives the current request's response stream, because what used to be a server-initiated request is now just a result asking for a retry with more input. [Serverless sessions](https://vercel-mcp-reference.vercel.app/internals/serverless-sessions/) explains why that shape matters here.

| Primitive | Controlled by | Declared via | Typical use |
|---|---|---|---|
| Tools | Model | Server capability, listed by `tools/list` | Side-effectful actions the model decides to take |
| Resources | Application or user | Server capability, listed by `resources/list`, `resources/templates/list` | Read-only context the host attaches to the model |
| Prompts | User | Server capability, listed by `prompts/list` | Saved, parameterized message templates the user picks |
| Sampling (deprecated) | Server (requests via MRTR); client (fulfills on retry) | `sampling` in per-request client capabilities | Server asks the client to run model inference |
| Elicitation | Server (requests via MRTR); client (fulfills on retry) | `elicitation` in per-request client capabilities | Server asks the user for structured input via the client |
| Progress | Server | Inline on existing requests | Long-running tool or read reports incremental status |
| Cancellation | Either side | Stream close (HTTP) or notification (stdio) | Aborting work that is no longer needed |
| Logging (deprecated) | Server | Per-request `logLevel` in `_meta` | Server emits structured logs scoped to a request |
| Roots (deprecated) | Client | `roots` in per-request client capabilities; server asks via MRTR | Client declares which `file://` roots are in bounds |
| Completions | Server | Server capability flag | Argument autocompletion for prompts and resource templates |

## Formal protocol perspective

The MCP specification splits primitives into **server features** (tools, resources, prompts, completions, logging) and **client features** (sampling, roots, elicitation), with progress and cancellation as cross-cutting patterns defined under the base protocol. Each feature is gated by a [capability](https://vercel-mcp-reference.vercel.app/glossary/#capability-negotiation) declaration, and with the `initialize` handshake gone, the declaration is per request: clients list the relevant capabilities in `_meta` (`io.modelcontextprotocol/clientCapabilities`) on **every request**, servers advertise theirs via the mandatory `server/discover` RPC, and a server **MUST NOT** rely on a capability the current request did not declare (violations get `MissingRequiredClientCapabilityError`, `-32021`). Optional protocol extensions, including the [tasks extension](https://vercel-mcp-reference.vercel.app/internals/tasks/) (`io.modelcontextprotocol/tasks`), are negotiated through the `extensions` map both capability objects gained in this revision. Every result also carries a required `resultType`: `"complete"` for final results, `"input_required"` for MRTR interim results, and clients **MUST** treat an absent `resultType` from earlier-protocol servers as `"complete"`.

**Tools** are defined in the spec's *Tools* section. The server **MUST** declare the `tools` capability, and exposes `tools/list` (paginated, with an optional cursor) and `tools/call`. Each tool has a `name`, a `description`, an `inputSchema` (JSON Schema), an optional `outputSchema`, optional [annotations](https://vercel-mcp-reference.vercel.app/glossary/#tool-annotation) such as `readOnlyHint` or `destructiveHint` (which clients **MUST** treat as untrusted unless the server is trusted), and optional `icons` metadata. **JSON Schema 2020-12 remains the default dialect**, and 2026-07-28 loosens the rules (SEP-2106): `inputSchema` and `outputSchema` may use any JSON Schema 2020-12 keywords, `structuredContent` may be any JSON value, network `$ref`s **MUST NOT** be auto-dereferenced, and validators **SHOULD** bound composition-keyword cost. Schema properties may carry an `x-mcp-header` annotation to mirror an argument into an `Mcp-Param-{Name}` HTTP header for edge routing (SEP-2243). Two more 2026-07-28 obligations: servers **SHOULD** return tools in deterministic order (client caching, LLM prompt-cache hit rates), and `tools/list` results **MUST** carry `ttlMs` and `cacheScope` (`"public"`/`"private"`) freshness hints via the `CacheableResult` interface (SEP-2549). Call results carry an unstructured `content` array whose items may be text, image, audio, resource links, or embedded resources, plus optional `structuredContent` and an `isError` flag defaulting to `false`. The error-channel line (SEP-1303) is unchanged and the tooling finally agrees with it: **input-validation and business-logic failures are tool execution errors**, returned as a normal result with `isError: true` so the model can read the feedback and self-correct, while unknown tools and malformed requests are JSON-RPC protocol errors (the spec's example uses `-32602` for an unknown tool). The TypeScript SDK v2 (2.0.0) now matches the spec here: unknown tool names are rejected as protocol errors, no longer returned as `isError` results the way SDK 1.x did. The server may emit `notifications/tools/list_changed`, delivered only on a [`subscriptions/listen`](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/subscriptions) stream the client opened with `toolsListChanged: true`.

**Resources** are defined in the *Resources* section. The server exposes `resources/list` for concrete URIs, `resources/templates/list` for [resource templates](https://vercel-mcp-reference.vercel.app/glossary/#resource-template) (URI patterns with `{placeholders}`), and `resources/read` to fetch contents; all of these results **MUST** carry `ttlMs`/`cacheScope`. Reading a missing resource returns `-32602` (Invalid params); the old `-32002` code is retired in this revision, though clients **SHOULD** still accept it from older servers. The 2025-11-25 `resources/subscribe` and `resources/unsubscribe` RPCs are gone: a client that wants per-URI updates lists the URIs in the `resourceSubscriptions` filter of a `subscriptions/listen` request and receives `notifications/resources/updated` (tagged with `io.modelcontextprotocol/subscriptionId`) on that stream; `notifications/resources/list_changed` signals catalog-level changes the same way. On a serverless deployment, a long-lived listen stream lives inside one function invocation's `maxDuration` budget; that constraint and its workarounds are covered in [serverless sessions](https://vercel-mcp-reference.vercel.app/internals/serverless-sessions/).

**Prompts** are defined in the *Prompts* section. The server exposes `prompts/list` (also a `CacheableResult`) and `prompts/get`. A prompt definition includes a `name`, a `description`, a list of `arguments` (each with a description and a `required` flag), and optional `icons`. `prompts/get` returns a `messages` array the host can hand to the model verbatim or splice into a larger conversation.

**Sampling** (deprecated, SEP-2577) is defined in the *Sampling* section of client features. The server no longer sends `sampling/createMessage` as its own request. When a tool call (or `resources/read` or `prompts/get`) needs a model completion, the server returns an `InputRequiredResult` whose `inputRequests` map contains a `CreateMessageRequest`; the **client** fulfills it, keeping control over model access and a human in the loop: it surfaces the request to the user, may edit or decline it, and retries the original request with the completion in `inputResponses`. Sampling requests may still carry `tools` and a `toolChoice` directive (SEP-1577) when the client declares `sampling.tools`; the client still mediates, so the [trust boundary](https://vercel-mcp-reference.vercel.app/glossary/#trust-boundary) is unchanged. The `includeContext` values `"thisServer"` and `"allServers"` are deprecated (SEP-2596): omit the field or use `"none"`.

**Elicitation** is defined in the *Elicitation* section of client features, in two modes declared as sub-capabilities (`elicitation: { form: {}, url: {} }`; an empty object means form-only for backwards compatibility). Like sampling, it is delivered through MRTR: the server returns an `InputRequiredResult` containing an `elicitation/create` input request, and the client answers on the retry. **Form mode** supplies a `requestedSchema`: a flat object of primitive-typed fields only, where enums may be titled or untitled and single-select or multi-select, and string, number, and enum fields may declare `default` values. **URL mode** hands the user off to a URL for out-of-band flows such as third-party authorization. The 2025-11-25 correlation machinery (`elicitationId`, `notifications/elicitation/complete`, and the `-32042` `URLElicitationRequiredError`) is removed in this revision: the client learns the outcome by retrying the original request, and a server that needs to correlate across retries encodes its own identifier in the opaque `requestState` field. The two modes keep their hard security split: servers **MUST NOT** request secrets such as passwords, API keys, or payment credentials through form mode, and **MUST** use URL mode for those, precisely so sensitive data never transits the client or the model context. The `ElicitResult` in either mode carries an `action` of `accept`, `decline`, or `cancel`.

**Progress** and **cancellation** are defined as base-protocol patterns. Progress is `notifications/progress`, sent on the originating request's response stream and tagged with a `progressToken` from that request's `_meta`. Cancellation is transport-shaped in 2026-07-28: on Streamable HTTP, the client closes the request's SSE response stream and the server **MUST** treat that as cancellation; the `notifications/cancelled` message survives only on stdio, where there is no per-request stream to close. Both are best-effort.

**Logging** (deprecated, SEP-2577) is defined in the *Logging* utility. `logging/setLevel` is removed: verbosity is now opt-in per request via `io.modelcontextprotocol/logLevel` in `_meta`, and servers **MUST NOT** emit `notifications/message` for requests that did not include it. Emitted messages carry `level`, `logger`, and `data`, and flow only on the response stream of the request that opted in. Do not confuse this with Vercel runtime logs: MCP logging flows to the connected host, while `console.log` flows to your Vercel dashboard and log drains. The migration path (OpenTelemetry, whose `traceparent`/`tracestate`/`baggage` keys are now reserved in `_meta`) is the direction of travel; see [Observability](https://vercel-mcp-reference.vercel.app/observability/).

**Roots** (deprecated, SEP-2577) are defined in the *Roots* section of client features. The client declares the `roots` capability per request; a server that wants the list returns an `InputRequiredResult` containing a `roots/list` input request and receives the roots in `inputResponses` on the retry. `notifications/roots/list_changed` is removed. Each root URI **MUST** be a `file://` URI. Roots describe the host's local filesystem, so for a deployed Vercel server they are usually irrelevant context; the deprecation migration (pass directories via tool parameters, resource URIs, or server config) is what most servers did anyway.

**Completions** are defined in the *Completion* utility. The client calls `completion/complete` with a reference to a prompt or a resource template plus the partial argument; the server returns ranked candidates with pagination metadata.

In this repo's examples, all of these register through an exported `configureServer(server)` function using the TypeScript SDK, keeping the protocol surface separate from the Next.js route shell; the pages stay implementation-agnostic and the examples carry the code.

## Request / lifecycle flow

The three core primitives and the actor in charge of each, with the auxiliary primitives shown as supporting flows:

```mermaid
flowchart LR
    Model([Model]) -->|invokes| Tools[Tools]
    App([Application/Host]) -->|attaches| Resources[Resources]
    User([User]) -->|selects| Prompts[Prompts]
    Tools --> Server[(Server)]
    Resources --> Server
    Prompts --> Server
    Server -.->|input_required results carrying sampling, elicitation, roots requests| Host[(Host)]
    Host -.->|retries with inputResponses, completions, stream-close cancellation| Server
```

Solid arrows are the primary trigger path for the three core primitives. Dashed arrows show the auxiliary loop that replaced server-initiated requests: the server hands back an `input_required` result naming what it needs (a model completion, user input, the roots list), and the host retries the original request carrying the answers. Progress and log notifications still ride the SSE response stream of the request that triggered them, which is why they are bounded by that invocation's lifetime on Vercel; standing change notifications ride a `subscriptions/listen` stream instead.

## Key messages / state transitions

- `tools/list` - **client to server**, request. Returns `{ tools: [...], nextCursor?, resultType: "complete", ttlMs, cacheScope }`. Servers **SHOULD** order tools deterministically. Re-issued after `notifications/tools/list_changed` or when `ttlMs` lapses.
- `tools/call` - **client to server**, request. Fields: `name`, `arguments`. Returns `{ resultType: "complete", content: [...], structuredContent?, isError? }`, where `content` is the unstructured array (text, image, audio, resource link, embedded resource), `structuredContent` matches the tool's optional `outputSchema`, and `isError` defaults to `false`. Input-validation failures come back as `isError: true` results (SEP-1303), not protocol errors, so the model can self-correct; unknown tool names are protocol errors. May instead return `resultType: "input_required"` (see MRTR below).
- `resources/list` / `resources/templates/list` - **client to server**, requests. Return concrete URIs and URI templates respectively, with `ttlMs`/`cacheScope`.
- `resources/read` - **client to server**, request. Field: `uri`. Returns `{ contents: [...], resultType, ttlMs, cacheScope }`. Unknown URI: error `-32602`.
- `subscriptions/listen` - **client to server**, request. Params: a `notifications` filter (`toolsListChanged`, `promptsListChanged`, `resourcesListChanged`, `resourceSubscriptions: [uris]`). The response stream opens with `notifications/subscriptions/acknowledged` and then carries only the opted-in types, each tagged with `io.modelcontextprotocol/subscriptionId`. Replaces `resources/subscribe`/`resources/unsubscribe` and the GET stream.
- `notifications/resources/updated` - **server to client**, notification on a listen stream. Fields: `uri`, plus the subscription id in `_meta`.
- `prompts/list` - **client to server**, request. Returns prompt metadata with `ttlMs`/`cacheScope`.
- `prompts/get` - **client to server**, request. Fields: `name`, `arguments`. Returns `{ messages: [...] }`.
- MRTR interim result - **server to client**, response to `tools/call`, `resources/read`, or `prompts/get` only. Shape: `{ resultType: "input_required", inputRequests?, requestState? }`, where `inputRequests` maps server-chosen keys to `ElicitRequest`, `CreateMessageRequest`, or `ListRootsRequest` objects, and `requestState` is an opaque string the client **MUST** echo unmodified. At least one of the two fields is always present.
- MRTR retry - **client to server**: the original request re-sent with a **new** JSON-RPC id, plus `inputResponses` (keyed to match `inputRequests`) and the echoed `requestState`.
- `elicitation/create` (inside `inputRequests`) - form mode: `message` plus `requestedSchema` (flat, primitive-typed fields; enums titled or untitled, single- or multi-select; optional defaults); `mode` defaults to `"form"`. URL mode: `mode: "url"`, `message`, `url`. The `ElicitResult` in `inputResponses` is `{ action, content? }` with `action` one of `accept`, `decline`, `cancel`; URL-mode accepts omit `content`.
- `sampling/createMessage` (inside `inputRequests`) - fields: `messages`, `modelPreferences?`, `systemPrompt?`, `maxTokens`, optional `tools?` plus `toolChoice?`. The `CreateMessageResult` in `inputResponses` carries the completion.
- `roots/list` (inside `inputRequests`) - no fields. The result in `inputResponses` carries the client's `file://` roots.
- `notifications/progress` - **server to client**, notification on the originating request's response stream. Fields: `progressToken`, `progress`, optional `total`.
- Cancellation - **client to server**. Streamable HTTP: close the request's SSE response stream. stdio: `notifications/cancelled` with `requestId`, optional `reason`.
- `notifications/message` - **server to client**, notification on the response stream of a request whose `_meta` included `io.modelcontextprotocol/logLevel`. Fields: `level`, `logger?`, `data`.
- `completion/complete` - **client to server**, request. Fields: `ref` (prompt or template), `argument`. Returns ranked candidates.
- Removed in 2026-07-28 - `logging/setLevel`, `notifications/roots/list_changed`, `resources/subscribe`, `resources/unsubscribe`, `notifications/elicitation/complete`, the `elicitationId` field, and `ping`.

## Common misconceptions

- **Misconception:** The model picks resources the way it picks tools. **Reality:** Resources are application-controlled. The host or the user decides which resources the model sees; the model has no `resources/read` capability of its own. See the spec's [Resources](https://modelcontextprotocol.io/specification/2026-07-28/server/resources) section.
- **Misconception:** A tool with `readOnlyHint: true` is safe to auto-approve. **Reality:** Annotations are non-binding self-descriptions, and the spec says clients **MUST** treat them as untrusted unless the server is trusted. Consent decisions remain a host responsibility. The [query-vs-command](https://vercel-mcp-reference.vercel.app/patterns/query-vs-command/) pattern shows what annotations are actually for.
- **Misconception:** Prompts are system prompts. **Reality:** Prompts are user-invoked templates returned to the host as a message list. They are not injected silently; the user explicitly selects them.
- **Misconception:** Sampling lets a server use its own model. **Reality:** Sampling is the reverse: the server asks the **host** to run inference on its behalf, now by returning an `input_required` result rather than sending a request. The host may decline, and since the feature is deprecated, new servers should call provider APIs directly instead.
- **Misconception:** The server pushes elicitation and sampling requests to the client mid-call. **Reality:** Not since 2025-11-25. Under MRTR the server *returns* instead of *asks*: the tool call ends with `resultType: "input_required"`, and a second, independent tool call carries the answers. Nothing is in flight while the user thinks.
- **Misconception:** Form-mode elicitation is fine for collecting an API key, since the user typed it willingly. **Reality:** The spec forbids it. Servers **MUST NOT** request credentials or payment details through form mode and **MUST** use URL mode, which keeps the secret out of the client, the model context, and every log in between.
- **Misconception:** Cancellation guarantees the work stops. **Reality:** Cancellation is best-effort. The receiver may have already completed the request, and side effects already executed are not rolled back. On Streamable HTTP the cancellation signal is simply closing the response stream.
- **Misconception:** Roots are enforced by MCP. **Reality:** Roots are a declaration the client makes; enforcement is a server-implementation responsibility. Treat roots as guidance, and back it with real isolation (for untrusted work on Vercel, that is [Sandbox](https://vercel-mcp-reference.vercel.app/glossary/#sandbox)) if it matters. The feature is deprecated; prefer explicit tool parameters.
- **Misconception:** Completions are autocomplete for tool arguments. **Reality:** `completion/complete` is scoped to prompts and resource templates only; tool-argument completion remains out of scope in 2026-07-28.

## Debugging notes

- Symptom: `tools/list` returns nothing even though the server clearly registers tools. Likely cause: the tools are registered after the request is served, or the server does not advertise the `tools` capability. Where to look: the `capabilities` object in the `server/discover` result; with the SDK, confirm the tools are registered inside `configureServer` before the handler is built.
- Symptom: `client.callTool()` behavior changed between SDK majors for a tool name that does not exist. Likely cause: nothing is wrong; SDK 2.0.0 rejects unknown tool names with a protocol error (matching the spec), where SDK 1.x returned `isError: true` results. Schema-invalid arguments on a *known* tool still come back as `isError: true` results in both. Where to look: assert a rejection for unknown names and `result.isError === true` for invalid arguments in your vitest suites. See [Testing](https://vercel-mcp-reference.vercel.app/testing/).
- Symptom: a watched resource never fires `notifications/resources/updated`. Likely cause: the URI was not in the `resourceSubscriptions` filter of the `subscriptions/listen` request, or the server's acknowledgment silently omitted a notification type it does not support. Where to look: the `notifications/subscriptions/acknowledged` frame first (it echoes the subset the server agreed to honor), then [serverless sessions](https://vercel-mcp-reference.vercel.app/internals/serverless-sessions/) for how long a listen stream can actually live on a function.
- Symptom: a tool call keeps returning `resultType: "input_required"` in a loop. Likely cause: the retry is missing some of the requested `inputResponses`, so the server keeps re-asking (the spec tells it to re-request rather than error), or the client mutated `requestState` and the server rejected it. Where to look: key-by-key equality between `inputRequests` and the retry's `inputResponses`, and byte equality of `requestState` across the round trip.
- Symptom: `-32021` errors on tool calls that use elicitation or sampling. Likely cause: the client did not declare the corresponding capability in `io.modelcontextprotocol/clientCapabilities` **on that request**; per-request declaration means a capability declared on one call does not carry to the next. Where to look: the `_meta` of the exact failing request.
- Symptom: progress notifications never reach the host from the deployed server. Likely cause: there was no `progressToken` in the originating request's `_meta`, or the notifications were emitted after the tool result was already sent, when the stream is gone. Where to look: the `_meta` of the originating request, and the ordering of emits relative to the returned result.
- Symptom: no log messages arrive even though the server calls the logging API. Likely cause: the request's `_meta` did not include `io.modelcontextprotocol/logLevel`; servers **MUST NOT** emit `notifications/message` without it. Where to look: the request `_meta`, then remember the feature is deprecated and consider OpenTelemetry.
- Useful observability hooks: per-primitive counters (`tools/call` invocations, `resources/read` reads, prompt selections), latency histograms per tool name, a counter of `input_required` results and how many retries resolve them, and a count of `-32021` capability rejections; that last one is a misbehaving-or-compromised-peer detector. See [Observability](https://vercel-mcp-reference.vercel.app/observability/).

## Security implications

Each primitive crosses a different [trust boundary](https://vercel-mcp-reference.vercel.app/glossary/#trust-boundary), and the right control lives at a different layer. The security checklist has the full inventory; the per-primitive shape:

**Tools** are the highest-risk primitive because the model decides when to invoke them. The host must implement [consent](https://vercel-mcp-reference.vercel.app/glossary/#consent): at minimum a confirmation surface for first use and for anything flagged `destructiveHint`, remembering the flags are advisory. The server **MUST** validate all tool inputs and sanitize outputs regardless of what the client checked, because on Vercel your endpoint is reachable by any client, not just well-behaved ones. Parameters mirrored into `Mcp-Param-*` headers via `x-mcp-header` are visible to every intermediary; never annotate sensitive fields. See the [security checklist](https://vercel-mcp-reference.vercel.app/security/checklist/).

**Resources** look innocuous because they are read-only, but they are the most common vector for prompt injection: content fetched from a server becomes part of the model's context. Treat resource contents as untrusted network input, even from your own deployment. A `subscriptions/listen` stream with auto-refresh creates an attacker-driven push channel into the model's context; subscribe deliberately, and respect `cacheScope: "private"` when anything user-specific could otherwise land in a shared cache.

**Prompts** are user-controlled and therefore lower risk, but a server can still smuggle instructions into the returned `messages` array. Render prompt previews before sending to the model, and never auto-execute a prompt without the user's explicit selection.

**Sampling** (deprecated) inverts the usual direction: the server is asking to use the host's model, and the host's token budget. The consent gate now lives at `input_required` handling: inspect the embedded `CreateMessageRequest`, cap `maxTokens`, let the user edit or decline, and only then retry. With tool calling inside sampling, the client also decides which tools the sampled completion may see; expose the minimum.

**Elicitation** is where users hand data to servers, so the mode split is the control: secrets go through URL mode only, and clients **MUST** show the full URL and get explicit consent before opening it, without prefetching. The `requestState` blob that correlates an elicitation across retries passes through the client, so the spec is blunt: servers **MUST** treat it as attacker-controlled input and integrity-protect it (HMAC or AEAD) whenever it influences authorization, resource access, or business logic, binding it to the verified principal with a short TTL. A server running elicitation-driven flows on Vercel **MUST** bind the resulting state to the verified user identity from [authorization](https://vercel-mcp-reference.vercel.app/security/authorization/); [Identity and principals](https://vercel-mcp-reference.vercel.app/security/identity-and-principals/) explains why that distinction is load-bearing on a multi-tenant deployment.

**Roots** (deprecated) are an isolation hint, not an enforcement mechanism; if isolation matters, use real sandboxing. **Logging** (deprecated) can exfiltrate sensitive data twice on Vercel: once through `notifications/message` into the host, and once through runtime logs into your log drains; scrub both paths. **Completions** are low-risk but still let a server influence what the user types; treat candidates as suggestions, not assertions.

## Runnable example

The resources-server example exercises the resource primitive end-to-end, including list, read, and templates:

- Example: `examples/resources-server` (in the repository)

Run it and watch how `resources/list` returns concrete URIs while `resources/templates/list` returns parameterized patterns, and how a `resources/read` of a templated URI resolves. For the input-loop primitives, `examples/sampling-server` (in the repository) and `examples/elicitation-server` (in the repository) drive sampling and elicitation against an in-memory client whose handlers you control; observe that the server receives an `accept`, `decline`, or `cancel` and handles each distinctly. Note that the examples exercise the SDK's current client-callback API rather than raw MRTR frames, and the sampling example carries its own deprecation banner.

## Related

- [MCP internals overview](https://vercel-mcp-reference.vercel.app/internals/overview/) - the stateless per-request model these primitives ride on.
- [Transports](https://vercel-mcp-reference.vercel.app/internals/transports/) - how results, streams, and `subscriptions/listen` are actually delivered over Streamable HTTP.
- [Serverless sessions](https://vercel-mcp-reference.vercel.app/internals/serverless-sessions/) - why long-lived streams and cross-request state need a home outside the function instance.
- [Tasks](https://vercel-mcp-reference.vercel.app/internals/tasks/) - the official extension for work that outlives a request.
- [Query vs command](https://vercel-mcp-reference.vercel.app/patterns/query-vs-command/) - tool annotations put to honest work.
- [Client-side elicitation](https://vercel-mcp-reference.vercel.app/client-side/elicitation/) - the host's obligations for form and URL mode.
- [Sampling request handling](https://vercel-mcp-reference.vercel.app/client-side/sampling-request-handling/) - the host-side consent gate, now at `input_required` handling.

## Bibliography

- Model Context Protocol Specification, *Tools*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/server/tools>
- Model Context Protocol Specification, *Resources*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/server/resources>
- Model Context Protocol Specification, *Prompts*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/server/prompts>
- Model Context Protocol Specification, *Multi Round-Trip Requests*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr>
- Model Context Protocol Specification, *Subscriptions*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/subscriptions>
- Model Context Protocol Specification, *Caching*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/caching>
- Model Context Protocol Specification, *Completion*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/completion>
- Model Context Protocol Specification, *Logging*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/logging>
- Model Context Protocol Specification, *Sampling*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/client/sampling>
- Model Context Protocol Specification, *Roots*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/client/roots>
- Model Context Protocol Specification, *Elicitation*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/client/elicitation>
- Model Context Protocol Specification, *Progress*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/progress>
- Model Context Protocol Specification, *Cancellation*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/cancellation>
- Model Context Protocol Specification, *Deprecated Features*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/deprecated>
- Model Context Protocol, *Security Best Practices* - <https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices>
- JSON-RPC 2.0 Specification - <https://www.jsonrpc.org/specification>
- Model Context Protocol, official site - <https://modelcontextprotocol.io>
