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
The README examples used `#[tool(param)]` on function parameters,
which is not a supported syntax and fails to compile. Replace with
the `Parameters<T>` wrapper pattern that the macros actually expect.
Closes#812
In fetch_resource_metadata_from_url, a JSON parse failure on the
response body caused a fatal AuthError::MetadataError, preventing
discover_metadata() from falling through to direct
.well-known/oauth-authorization-server discovery (Strategy B).
MCP servers that return HTTP 200 with non-JSON content (e.g. HTML)
at their base URL caused the OAuth flow to abort entirely, even
when the server had a valid .well-known/oauth-authorization-server
endpoint.
Return Ok(None) on parse failure, consistent with how HTTP errors
are already handled in the same function.
* 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
AuthRequiredError, InsufficientScopeError, and DynamicTransportError
were marked #[non_exhaustive] in #715/#768 but don't have constructors
usable by external crates. Add new() for the error types and
from_parts() for DynamicTransportError (the existing new() requires a
Transport type parameter, making it unusable for test fixtures).
Fixes#805
* 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(transport): add which_command for cross-platform executable resolution
Adds a `which_command()` helper that resolves executable paths via the
`which` crate before constructing a `tokio::process::Command`. This fixes
Windows failures where `.cmd` shim scripts (e.g. `npx.cmd`) are not
found by `Command::new()` without a fully-qualified path.
Closes#456
* refactor(transport): move which_command behind opt-in feature flag
Address review feedback: the `which` dependency is now gated behind a
separate `which-command` feature flag instead of being bundled into
`transport-child-process`. Users on Linux/macOS who don't need
cross-platform executable resolution no longer pull in the extra crate.
Also fixes the doc example import path to use the re-exported
`rmcp::transport::which_command`.
* fix: example clients_everything_stdio
* Apply suggestion from @DaleSeo
Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
---------
Co-authored-by: Alex Hancock <alexhancock@block.xyz>
Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
The `local` feature relaxes Send+Sync bounds, which causes items
gated behind `cfg(not(feature = "local"))` to be excluded when
docs.rs builds with all-features. Replace `all-features = true`
with an explicit feature list that omits `local`.
Signed-off-by: majiayu000 <1835304752@qq.com>
StoredCredentials is #[non_exhaustive] but has no constructor, making
it impossible for external crates implementing CredentialStore to
construct instances without a serde roundtrip workaround. Add a new()
constructor matching the pattern used for other #[non_exhaustive]
types in this crate.
Fixes#777
* 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.