Release notes
neva 0.4.0 — MCP 2026-07-28 Release Candidate
This release adds opt-in support for the MCP 2026-07-28 Release Candidate spec, behind the compile-time proto-2026-07-28-rc feature flag. The legacy spec remains the default and is unchanged for users who don't opt in.
RC status. Wire format and APIs gated by
proto-2026-07-28-rcare not covered by semver and may change before the final spec ships. Once it graduates, the flag will invert: the RC path becomes the default and the current default moves under alegacy-specflag. Plan for that swap as a deliberate breaking change — the spec itself is breaking.
What the RC changes
The 2026-07-28 spec re-architects MCP around stateless, horizontally scalable HTTP transports, removes a few features that pushed too much policy into the protocol (sampling, roots, server logging), and introduces an extensions mechanism for everything that doesn't belong in core. neva 0.4.0 covers all of this except authorization tightening and MCP Apps,
which are scheduled for 0.5.
Stateless HTTP and discovery
The initialize/initialized handshake is replaced by a single server/discover request, and the HTTP transport carries no
Mcp-Session-Id. Every POST carries a required MCP-Protocol-Version header — the client adds it automatically; the server rejects missing or unsupported values. Any request can land on any server instance.
Server-initiated notifications are inert on this transport — clients poll instead. The new ttlMs / cacheScope cache hints on list results give
the server a way to suggest how long each list is valid.
Elicitation via MRTR
Multi Round-Trip Requests replace the legacy push-channel elicit. A handler calls ctx.elicit(key, params).await?; on a miss the framework
returns an InputRequiredResult with an AEAD-sealed requestState, and the client re-issues with inputResponses until the handler completes. The state blob is the only thing that persists across rounds, so a retry can land on any instance.
Because the handler re-runs each round, neva exposes three effect helpers on Context for safe side effects:
ctx.once("charge_card", async { billing.charge(&order).await }).await?;
let quote: Quote = ctx.memo("quote", async { pricing.fetch(&order).await }).await?;
ctx.on_commit(async move { mailer.send_receipt(&order).await });onceruns an effect at most once (marker lives inrequestState).memocaches a computed value (also inrequestState, encrypted).on_commitdefers an effect to the final result.
All three are at-most-once within a single requestState chain — pair non-idempotent effects with a downstream idempotency key.
For multi-instance deployments you need both a shared signing secret (App::with_request_state_secret) and a shared idempotency store (App::with_request_state_store — RequestStateStore trait, default in-memory). The secret keeps requestState portable across instances; the store closes the lost-response retry window (handler runs, response is lost in transit, client retries the same state — without the store the handler runs twice and on_commit double-fires). neva warns at startup if you forget the secret.
Task-augmented elicit
A task-augmented tool runs on a different execution substrate from MRTR — it genuinely suspends, instead of re-running. The two never mix:
let answer = if ctx.is_task() {
ctx.task().elicit(params).await? // suspend the background task
} else {
ctx.elicit("name", params).await? // MRTR re-run
};once/memo/on_commit are MRTR helpers; in a Required task they
error (clear misuse), in an Optional task they degrade to inline.
JSON Schema 2020-12 tools
Tool.input_schema/output_schema now use a per-flag ToolInputSchema alias — ToolSchema under legacy, the new
schema_2020::InputSchema (serde_json::Value-backed) under RC. The #[tool] macro emits full 2020-12 documents: primitive args become inline primitives, structured Json<T> args derive a rich inlined schema when T: JsonSchema, with a graceful {"type":"object"} fallback. Explicit input_schema = "…" literals are validated at compile time on every feature configuration. schemars is re-exported by neva, so user crates don't need a direct dependency.
Extensions, with Tasks as the first consumer
Anything not in core now lives behind the new Extension trait:
let app = App::new()
.with_extension(TasksExtension::new(ServerTasksCapability::default()));Extensions advertise a capability under capabilities.extensions[<reverse-DNS-id>] in server/discover and register their own handlers. Tasks is the built-in example (io.modelcontextprotocol/tasks). The legacy with_tasks API is unchanged — it's now a thin wrapper that registers the extension under the hood.
What changed for legacy users
Nothing on the wire. Two minor additions:
ErrorCode::RESOURCE_NOT_FOUNDis a new constant that emits the right wire code per active spec. Prefer it overErrorCode::ResourceNotFound(now#[deprecated]); the variant stays for back-compat.Dc<T>extractors now work as handler arguments for tools and prompts,
not just resources.
Migration & deployment notes for the RC
If you're trying the RC flag:
- Set
App::with_request_state_secret(<shared secret>)for any HTTP deployment with more than one instance — the default ephemeral
per-process key works for single-instance development only. Cross-instance retries will fail to decrypt without a shared secret. - Set
App::with_request_state_store(<shared store>)for the same reason. ImplementRequestStateStoreover Redis (or similar) for
production. - Rewrite
ctx.elicit(params).awaitcalls asctx.elicit(key, params).await?with a stable string key. Wrap any side effects between/around elicit points inctx.once/ctx.memo/ctx.on_commit— handlers re-run on each round, so anything not idempotent will fire repeatedly. roots/list,sampling/createMessage, andlogging/setLevelare removed under RC. The corresponding APIs are#[cfg]-gated out — the
spec's intended replacements are host-side:roots→ out-of-band,sampling→ host-provided tool,logging→ host's own telemetry.- Server-initiated notifications are inert on the stateless transport. If your server relies on
listChanged/subscribepush, keep the
legacy spec until the spec settles on a stateless replacement (ttlMs/cacheScopecover mostlistChangedcases via polling).
Deferred to 0.5
- Authorization tightening (RFC 9207
issvalidation, DCRapplication_type, refresh-token / scope-accumulation rules) — its
own epic. - MCP Apps (server-rendered UI in sandboxed iframe) — still experimental in the RC; will land once it stabilizes.
What's Changed
- New MCP Spec 2026-07-28 support by @RomanEmreis in #79
Full Changelog: 0.3.4...0.4.0