* fix: interpret task ttl as milliseconds
* fix: use text/plain for default text mime type
* fix: include resource param in token refresh
* test: align conformance prompt args with runner
* ci: run server conformance suite on PRs
* ci: build client bin and gate pending scenarios
`receive()` reads incoming lines with `read_until`, which is not
cancellation-safe, and it is polled inside the service loop's `select!`.
When another branch (e.g. an outgoing response) becomes ready while a
request line is only partially read, the `receive()` future is dropped
and the next call's `self.line_buf.clear()` discards the partial bytes,
so that incoming request is silently lost and never gets a response.
Under many concurrent large responses this intermittently drops requests.
Keep the partially-read bytes in `line_buf` across calls and clear it
only after a whole line has been consumed, so a cancelled read resumes
the same line instead of dropping it. The buffer is cleared (retaining
capacity) rather than reallocated per message.
Adds a regression test that fires 200 concurrent 64 KiB tool responses
over real stdio pipes and asserts no response id goes missing.
This also covers the child-process client transport, which reuses
`AsyncRwTransport`.
Per RFC 6749 section 6 and OAuth 2.1: when the server does not issue a new
refresh_token on a refresh response, the client MUST keep the existing one.
AuthorizationManager::refresh_token() was persisting the response verbatim,
dropping the previous refresh_token and forcing full re-authorization on
the next refresh.
Match the fix from python-sdk#2270: preserve the existing refresh_token
when the response omits it, replace when the server rotates.
Fixes#921
Signed-off-by: SarthakB11 <sarthak.bhardwaj21b@iiitg.ac.in>
* fix: don't respond to unparseable messages
* docs: spell 'unparsable' to satisfy typos linter
* fix: only ignore unparsable JSON, keep protocol errors visible
Classify the serde error in the receive loop: syntax/EOF errors are
unparsable input with no correlatable id (issue #938) and stay silent,
while data errors (valid JSON that doesn't match the message shape) are
real protocol errors and get an error response instead of being dropped.
Add a test covering the protocol-error path.
* fix: respond with Invalid Request for malformed protocol messages
---------
Co-authored-by: tsouth89 <tsouth89@users.noreply.github.com>
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
* chore: bump Rust toolchain to 1.96
* ci: add cargo-public-api breaking-change check
Adds a release-type-aware `cargo-public-api` diff job that fails on
changed/removed public items unless the PR's commits mark a breaking
(major) release. This catches source-breaking API changes that
cargo-semver-checks cannot yet detect (e.g. function return-type or
field-type changes).
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).
* docs(server): document Err vs Ok(CallToolResult::error) visibility contract
The MCP spec separates two failure modes that surface very differently in
clients:
- Err(ErrorData) is a JSON-RPC protocol error. Most MCP clients render
it opaquely ("Tool result missing due to internal error") - the
caller does not see the message text.
- Ok(CallToolResult::error(content)) is a tool-level error. Clients
render the content; the caller reads the message.
The right shape for "the tool didn't work" is the latter, but Err is
what most handlers reach for because it looks like the natural Rust
return value. This commit adds rustdoc on both ServerHandler::call_tool
and CallToolResult::error pointing handlers at the correct shape, with
a worked example showing protocol errors (-32602 invalid_params) vs
tool errors (empty result, downstream failure).
This is the docs half of the visibility-contract ask. A follow-up may
introduce a typed ToolOutcome sum type to enforce the distinction at
compile time; this PR is the lower-risk version that unblocks the
class immediately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: update crates/rmcp/src/handler/server.rs
* docs: update crates/rmcp/src/model.rs
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
* 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>
* feat(auth): specify OIDC application_type during client registration
SEP-837 [1] requires an MCP client to specify an application_type during
OIDC Dynamic Client Registration. When it is omitted, OIDC servers
default the client to "web", which conflicts with the loopback redirect
URIs that CLI and desktop clients use, so the registration can be
rejected.
I make register_client always send an application_type. It defaults to
"native" to match the loopback redirect this SDK uses, and I added
OAuthClientConfig::with_application_type so web clients can opt in. Tests
cover the serialized request body and the config default. Implements [2].
[1]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/draft/basic/authorization.mdx#L395
[2]: https://github.com/modelcontextprotocol/rust-sdk/issues/880
Signed-off-by: Stefano Amorelli <stefano@amorelli.tech>
* chore(auth): declare application_type in client metadata document
I set application_type to "native" in the hosted client metadata
document so the URL-based client id flow and dynamic registration agree
on the client type that SEP-837 [1] expects.
[1]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/draft/basic/authorization.mdx#L395
Signed-off-by: Stefano Amorelli <stefano@amorelli.tech>
---------
Signed-off-by: Stefano Amorelli <stefano@amorelli.tech>
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.