Repository navigation
v0.6.1
neva 0.6.1 adds a way to publish a server to the MCP Registry without writing server.json by hand, and fixes three cases where a transport that failed to start said nothing about it.
Compatible with 0.6.0: nothing was removed or changed in the public API. Two failures that used to surface late, or not at all, now surface at connect/run -- see Behaviour changes before upgrading a deployment.
Added
server.json for the MCP Registry (#135)
New registry feature, included in server-full. It adds no dependencies: the manifest is serde_json, and the URL checks use the http crate the client already links.
use neva::prelude::*;
let app = App::new().with_options(|opt| opt.with_stdio().with_version("0.3.0"));
// The app's version and transport, plus the crate's description, repository
// and website, in one call.
let manifest = neva::server_manifest!(app, "io.github.you/weather");
let json = manifest.to_json()?;App::server_manifest(name)seeds a manifest from what the app knows: the version it reports and the transport it is configured with, URL included for an HTTP server. A0.0.0.0or[::]bind becomes the loopback a client actually dials.ServerManifest::with_cargo(neva::cargo_env!())adds what Cargo knows - description, repository, website, and acargopackage entry.with_cargo_package(cargo, |package| ..)shapes that entry.neva::server_manifest!(app, name)is both calls together.cargo_env!is a macro becauseCARGO_PKG_*are compile-time values of your crate; a function in this one would read neva's. It works in abuild.rsas well.ServerManifest::to_jsonvalidates first and returns the pretty-printed document.
name is never derived. The registry's name is a reverse-DNS identifier inside a namespace you have proved you own; the MCP server name with_name sets is a different string under different rules, and substituting one for the other produces manifests that fail namespace verification.
validate answers the schema the manifest is written against: the reverse-DNS name, the 100-character description (crates.io has no such ceiling, so a derived one is the case that bites), a blank title, version ranges, latest as a package version, the fields typed format: uri, an icon source's HTTPS and its 255 characters, fileSha256, a {template} nothing declares, nothing to install and nothing to call, a remote over stdio, and a package derived from an app with no transport.
It is deliberately not a reimplementation of a registry's validator. Rules a registry adds of its own - which hosts it fetches archives from, which base URLs it takes, what it makes of a loopback address - are its to apply and to change, and it names the one you broke when it refuses. Ok here means the document is the shape the schema describes.
New types: ServerManifest, Package, RegistryType, Transport, Remote, Repository, Input, KeyValueInput, Argument, InputFormat, CargoEnv, and the pinned registry::SCHEMA_URL (2025-12-11, overridable with with_schema_url).
examples/registry runs the path end to end, from --emit-manifest to mcp-publisher publish, including the crates.io ownership marker -- which for Cargo must be visible text, since crates.io strips HTML comments when it renders a README.
Fixed
- A failed
Client::connectcan be retried (#131).McpOptionskept nothing behind after handing out the transport, so a secondconnectafter a spawn failure (#125) reportedTransport protocol must be specified-- which was untrue. The options now start the transport while they still own it and hand it over only oncestarthas returned. Aconnectthat got paststartis still not retryable; that needs a newClient. - An HTTP transport that cannot start reports why. Both
Transport::startimplementations logged the failure and returnedOk, so a rejected OAuth or TLS configuration reached the caller as a request timeout, and a failed bind as an already-cancelled token.HttpClient::runtimealso took its state apart one piece at a time, leaving a half-emptied transport behind a refusal; it now decides every refusal while only borrowing, and takes the writer last. - A client or server with no transport is told so by
connect/runrather than by the first send.TransportProto::None::startreturns the configuration error instead of a detached handle. - An icon that names no theme leaves
themeout instead of writing"theme": null, which is neitherlightnordark.
Behaviour changes
Both follow from the fixes above. Neither changes a signature; both change when you hear about a failure.
- A server whose transport cannot start now reports the cause and returns from
App::run, instead of entering a receive loop that broke on its first read. A misconfigured bind address or TLS certificate that previously looked like a hung server is now an error at startup. Client::connectreturns the OAuth or TLS configuration error directly. Code that treatedconnectas infallible-in-practice and waited for the first request to fail will now see the error one step earlier, with the real cause attached.
Dependencies
volga,volga-di,volga-oauth-core,volga-oauth-client: 0.10 -> 0.11.1
(http-server,server-tls,server-oauth,di).sse-stream: 0.2.6 -> 0.3.0 (http-client).
If your crate depends on volga directly alongside neva, move it to 0.11 in the same step.
Compatibility
- MSRV 1.90, edition 2024, unchanged.
- No public item was removed or changed; everything new is behind
registry. - Both protocol profiles are covered: 1569 tests on
server-full client-full(MCP 2026-07-28) and 1150 under--all-features(thelegacy-specprofile), with clippy-D warnings,cargo fmt --checkandcargo docclean on both and on every slim feature row CI builds.
Full changelog: 0.6.0...0.6.1