Skip to content

v0.6.1

Choose a tag to compare

@RomanEmreis RomanEmreis released this 22 Sep 15:42
· 15 commits to main since this release
ea83b87

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. A 0.0.0.0 or [::] bind becomes the loopback a client actually dials.
  • ServerManifest::with_cargo(neva::cargo_env!()) adds what Cargo knows - description, repository, website, and a cargo package entry. with_cargo_package(cargo, |package| ..) shapes that entry. neva::server_manifest!(app, name) is both calls together.
  • cargo_env! is a macro because CARGO_PKG_* are compile-time values of your crate; a function in this one would read neva's. It works in abuild.rs as well.
  • ServerManifest::to_json validates 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::connect can be retried (#131). McpOptions kept nothing behind after handing out the transport, so a second connect after a spawn failure (#125) reported Transport protocol must be specified -- which was untrue. The options now start the transport while they still own it and hand it over only once start has returned. A connect that got past start is still not retryable; that needs a new Client.
  • An HTTP transport that cannot start reports why. Both Transport::start implementations logged the failure and returned Ok, so a rejected OAuth or TLS configuration reached the caller as a request timeout, and a failed bind as an already-cancelled token. HttpClient::runtime also 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 / run rather than by the first send. TransportProto::None::start returns the configuration error instead of a detached handle.
  • An icon that names no theme leaves theme out instead of writing "theme": null, which is neither light nor dark.

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::connect returns the OAuth or TLS configuration error directly. Code that treated connect as 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 (the legacy-spec profile), with clippy -D warnings, cargo fmt --check and cargo doc clean on both and on every slim feature row CI builds.

Full changelog: 0.6.0...0.6.1