Skip to main content

Legacy spec

legacy-spec is the opt-in Cargo feature that restores the pre-2026-07-28 protocol generation — MCP 2024-11-05 … 2025-11-25.

[dependencies]
neva = { version = "0.5", features = ["server-full", "legacy-spec"] }

It is a generation switch, not an addition: enabling it compiles the MCP 2026-07-28 surface out. The two generations never coexist in one build.

--all-features selects the legacy profile

Cargo features are additive, so --all-features turns legacy-spec on and therefore exercises the legacy profile. The default profile needs an explicit feature list — e.g. --features "server-full client-full" or --features full. This is also why docs.rs publishes neva with features = ["full"] rather than all features.

Migrating to 0.5.0

You were on 0.4.x with…Do this
features = ["proto-2026-07-28-rc"]Drop the flag. It no longer exists — what it gated is now the default.
The old default (no protocol flag)Add legacy-spec to keep the old wire, or migrate to MCP 2026-07-28.

Beyond the flag, the code changes worth checking:

  • Tasksopt.with_tasks() takes no closure; list_tasks() is gone (poll tasks/get instead); Task::ttl serializes as ttlMs and is now Option<usize>. See Tasks.
  • Results — every success result now carries resultType. If you parse raw responses, read it via Response::result_type().
  • HTTP engine adaptersSseResponse is renamed to StreamResponse (its Status variant to Complete), and handlers::dispatch_post returns StreamResponse<…> instead of a plain response. See Custom HTTP Stack. A deprecated SseResponse alias remains for one release.
  • Removed callsping, complete_elicitation, on_elicitation_completed, with_logging / set_log_level.
  • Resource subscriptionsresources/subscribe / resources/unsubscribe are folded into the subscriptions/listen filter. Replace client.subscribe_to_resource(uri) with client.listen(SubscriptionFilter::new().with_resource(uri)), and drop ctx.subscribe_to_resource(..) from server handlers — the client owns the subscription now. See Subscriptions.
  • Sampling & roots — still available, but as MRTR input-request kinds and #[deprecated]. The #[sampling] attribute macro belongs to the legacy push model and is not available in the default build; wire the handler with map_sampling.

What legacy-spec restores

AreaLegacy behavior
Handshakeinitialize / initialized, with serverInfo in InitializeResult
TransportSession-bound Streamable HTTP: Mcp-Session-Id, session DELETE, standalone SSE GET stream with Last-Event-ID replay
Stream resumptionA dropped POST response stream is resumed once, with a GET carrying Last-Event-ID after the pause the server asked for — only when the server named an id to resume from. Each stream keeps its own cursor and its own reconnection delay, taken from that stream's SSE retry: field rather than a fixed three seconds
Version selectionwith_mcp_version(...) on the server
Server→client requestsCapability-driven push for sampling/createMessage, roots/list, elicitation/create — no MRTR
MacrosThe #[sampling] attribute macro
Logginglogging/setLevel plus with_logging(handle) and a global notifications/message emission path
ToolsThe legacy ToolSchema (not JSON Schema 2020-12)
TasksThe 2025-11-25 surface: tasks/list, tasks/result, the cancel/list/requests capability sub-tree, `with_tasks(
Notificationsping, notifications/roots/list_changed, notifications/elicitation/complete
SubscriptionsThe resources/subscribe / resources/unsubscribe RPC pair, Context::subscribe_to_resource / unsubscribe_from_resource, and resource::commands::{SUBSCRIBE, UNSUBSCRIBE} — server-side subscription state instead of a subscriptions/listen stream
RequestsNo mandatory _meta keys, no routing-header validation, no resultType

Everything else — DI, middleware, content types, JWT auth, TLS, custom HTTP engines, batch requests — is shared between the two generations and behaves the same either way.

Talking to a legacy peer without legacy-spec

You usually don't need the flag on the client. neva's default-build client is dual-mode: it opens with server/discover and, if the peer clearly does not speak MCP 2026-07-28, falls back to the initialize handshake and speaks legacy to that peer for the rest of the connection. See Discovery replaces the handshake.

The server side has no such fallback — it is compile-time pure. A server that must serve legacy clients needs the legacy-spec build.

Examples

The legacy variants of the roots and sampling examples live under a legacy/ sub-directory, each its own Cargo workspace (Cargo unifies features across members built together, so a shared workspace would flip the generation for every crate in it):