Expose typed accessors for the per-request protocol version, client info,
client capabilities, and log level entries carried in _meta without making
those fields required for older peers.
Fixes#869
The spec's prompt-message ContentBlock union is
`text | image | audio | resource_link | resource`, but
PromptMessageContent omitted `Audio`. Because the enum is
`#[serde(tag = "type")]` with no catch-all, a spec-conformant
`{"type":"audio",...}` content block failed to deserialize with
"unknown variant `audio`", breaking prompts/get for any server that
returns audio prompt content (the audio analogue of #842 / #843).
The supporting AudioContent type already existed, and Audio was
already a variant of the general RawContent enum (tool results,
sampling) -- only PromptMessageContent lacked it.
Add the flattened Audio variant (mirroring Image), a
PromptMessage::new_audio constructor (mirroring new_image), and
serialization + deserialization regression tests.
Fixes#864.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(elicitation): preserve enumNames through ElicitationSchema serde round-trip
UntitledSingleSelectEnumSchema lacked deny_unknown_fields, so a legacy
enum payload containing enumNames was silently matched by that variant
(ignoring the field) rather than falling through to LegacyEnumSchema.
The enumNames array was lost on re-serialization.
Add deny_unknown_fields to UntitledSingleSelectEnumSchema so that any
unknown field (including enumNames) causes serde to try the next
untagged variant, reaching LegacyEnumSchema correctly.
Also add skip_serializing_if = "Option::is_none" to
LegacyEnumSchema::enum_names so that an untitled legacy enum without
enumNames does not serialize "enumNames": null.
Fixes#903
* test(elicitation): regenerate server schema snapshot for deny_unknown_fields
deny_unknown_fields on UntitledSingleSelectEnumSchema makes schemars
emit additionalProperties: false for that definition. Regenerate the
golden schema fixtures to match (UPDATE_SCHEMA=1 cargo test -p rmcp
--test test_message_schema --all-features).
* feat: implement SEP-2164 resource not found errors
* test: update protocol version utility expectations
* feat: gate not-found code at server boundary
---------
Co-authored-by: Michael Neale <michael.neale@gmail.com>
SEP-2577 deprecates the Roots, Sampling, and Logging features. The
deprecation is advisory: the features stay fully functional and there is
no wire-level change. Mark the corresponding Rust APIs as deprecated so
downstream users get compiler warnings and migration guidance.
- Forward attributes through the service `method!` macros and deprecate
`Peer::create_message`, `Peer::list_roots`, `Peer::set_level`, and
`Peer::notify_logging_message`.
- Forward per-field attributes through the capability `builder!` macro and
deprecate the generated `enable_roots`, `enable_sampling`, and
`enable_logging` builders, plus the hand-written
`enable_roots_list_changed`, `enable_sampling_tools`, and
`enable_sampling_context`.
- Document the deprecation on the capability types and fields, and in the
README feature sections.
- Allow `deprecated` at the crate's own call sites so the build stays
warning-clean, and refresh the message schema snapshots.
The Resource variant of PromptMessageContent was missing #[serde(flatten)],
causing the embedded resource content block to serialize as a double-nested
shape `{ "type": "resource", "resource": { "resource": {...} } }` instead of
the spec-compliant flat shape `{ "type": "resource", "resource": {uri, mimeType, text} }`.
This caused Zod-based MCP clients (e.g. Claude Code) to reject prompts/get
responses containing embedded resource messages with InvalidUnion errors.
The Image and ResourceLink variants already use #[serde(flatten)] correctly;
only Resource was missing it.
Fix: add #[serde(flatten)] so EmbeddedResource (=Annotated<RawEmbeddedResource>)
fields _meta / annotations / resource are flattened to the content-block level,
matching the MCP spec for prompts embedded resources.
Regression test: test_prompt_message_resource_serialization_is_flat verifies
content.resource.uri is reachable and content.resource.resource is absent.
Schema snapshots regenerated via UPDATE_SCHEMA=1.
* fix(transport): downgrade idle timeout log from error to debug
Idle keep-alive timeout is normal zombie-session cleanup, not a transport failure.
Route it through a dedicated WorkerQuitReason::IdleTimeout variant.
Log it at debug level instead of treating it as a fatal error.
Remove the unused LocalSessionWorkerError::KeepAliveTimeout variant.
Closes#817
* fix(session): tolerate dead worker in close_session
Swallow SessionServiceTerminated in close_session when the worker has already exited.
This prevents a spurious ERROR log during the post-exit cleanup path in
spawn_session_worker.
* fix(transport): address PR review feedback
- deprecate KeepAliveTimeout
- harden tests
* feat(router): support runtime disabling of tools
Add methods to disable/enable tools at runtime.
Disabled tools are hidden from listing, lookup,
and execution, including in composed routers.
Closes#477
* fix(router): simplify disable tool api
* feat(router): auto-send tools/list_changed on disable/enable
* refactor(router): simplify disable_route and notifier call
* fix: include http_request_id in request-wise priming event IDs
* refactor: use Option::into_iter and usize::from for priming
* fix: retain event cache for completed request-wise channels
* fix: track completed_at for cache eviction and resume
* fix: log resume failures at warn level
* test: add completed_cache_ttl eviction test
* fix: return empty stream on failed resume
* test: add resume after completion test
* fix(http): reduce latency on subsequent StreamableHttp calls
* refactor: rely on stream drain for connection reuse
* refactor: clean up comments and naming
* fix: restore pool_max_idle_per_host(0) for Linux
* fix(server): remove initialized notification gate to support Streamable HTTP
The server's init handshake loop fatally rejected any request arriving
before the `notifications/initialized` message. This breaks Streamable
HTTP clients where each JSON-RPC message is a separate POST with no
ordering guarantee — `tools/list` can easily arrive before `initialized`.
Remove the ~40-line wait loop and enter `serve_inner` immediately after
sending `InitializeResult`. The `initialized` notification is now
handled as a regular notification by the main service loop, matching the
TypeScript SDK behavior (validated in typescript-sdk#578).
Also remove the now-unreachable `ExpectedInitializedNotification` error
variant from `ServerInitializeError`.
Closes#783
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(server): keep ExpectedInitializedNotification as deprecated
Retain the variant for semver compatibility — removing it would be a
breaking change caught by cargo-semver-checks. Mark it deprecated with
a note that it is never constructed and will be removed in a future
major release.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Anar Azadaliyev <anar.azadaliye@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(macros): auto-generate get_info and default router
* docs: simplify examples and docs with new defaults
* feat(macros): add tool_router(server_handler) to elide separate #[tool_handler] impl
* docs: add Tools section to README and simplify calculator examples with server_handler
* feat: add theme field to Icon
* fix: update IconThem crates/rmcp/src/model.rs (non_exhaustive)
Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
* fix: update IconThem crates/rmcp/src/model.rs (eq, hash)
Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
* fix: update docs with full descriptions of theme from mcp spec
---------
Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
* feat(transport): add Unix domain socket client for streamable HTTP
MCP hosts in Kubernetes environments with Envoy sidecars need to route
HTTP through Unix domain sockets because DNS-based URIs only resolve
via the proxy. Adds UnixSocketHttpClient implementing StreamableHttpClient
using hyper over tokio::net::UnixStream, gated behind the
transport-streamable-http-client-unix-socket feature.
Also extracts RESERVED_HEADERS, extract_scope_from_header, and
validate_custom_header into common/http_header.rs to share header
validation logic between the reqwest and unix socket implementations.
* fix(transport): address review feedback for unix socket transport
- Document one-connection-per-request behavior on UnixSocketHttpClient
- Reject empty socket paths and bare '@' in constructor with assert
- Add explicit dep:http to unix-socket feature for self-documenting deps
- Document MCP-Protocol-Version exception on RESERVED_HEADERS constant
- Fix test catch-all to echo request id instead of hardcoding 1
- Remove leftover sleep(100ms) in test_unix_socket_custom_headers
- Add blank line before macro comment in Cargo.toml
* fix(transport): fix CI failures for unix socket transport
- Use std::io::Error::other() instead of Error::new(ErrorKind::Other)
to satisfy clippy::io_other_error on newer nightly
- Use #[tokio::test(flavor = "current_thread")] for unix socket tests
since axum's serve(UnixListener) requires spawn_local
- Gate validate_custom_header behind client-side-sse feature since it
references http::HeaderName which isn't available with default features
* fix(transport): fix CI failures for unix socket transport
axum::serve(UnixListener) uses spawn_local on Linux, which panics
outside a LocalSet. Replace with manual hyper HTTP/1.1 server that
accepts connections directly from the UnixListener, avoiding the
spawn_local requirement entirely.
* fix(transport): skip unix socket tests when local feature is enabled
The local feature causes ().serve(transport) to use spawn_local, which
requires a LocalSet. Gate the integration tests with not(feature = "local")
to match every other integration test in the repo.
* feat: add configuration for transparent session re-init
* fix: in ci revert running tests without local until all tests pass
* fix: pr comments
* fix: documentation
* fix(rmcp): surface JSON-RPC error bodies on HTTP 4xx responses
When a server returns a 4xx status with Content-Type: application/json,
attempt to deserialize the body as a ServerJsonRpcMessage before falling
back to UnexpectedServerResponse. This allows JSON-RPC error payloads
carried on HTTP error responses to be surfaced as McpError instead of
being lost in a transport-level error string.
Fixes#724
* fix(rmcp): surface JSON-RPC error bodies on HTTP 4xx responses
When a server returns a 4xx status with Content-Type: application/json,
attempt to deserialize the body as a ServerJsonRpcMessage before falling
back to UnexpectedServerResponse. This allows JSON-RPC error payloads
carried on HTTP error responses to be surfaced as McpError instead of
being lost in a transport-level error string.
Fixes#724
* fix(rmcp): only accept JsonRpcMessage::Error on non-success responses