Compare commits

...
Author SHA1 Message Date
dependabot[bot]
dbd2382755
chore(deps): update rstest requirement from 0.26.1 to 0.27.0 (#1276)
Some checks failed
CI / Lint Commit Messages (push) Has been cancelled
CI / Code Formatting (push) Has been cancelled
CI / Lint with Clippy (push) Has been cancelled
CI / SemVer Check (push) Has been cancelled
CI / Public API Check (push) Has been cancelled
CI / spell check with typos (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Run Tests (push) Has been cancelled
CI / Run Tests (no local feature) (push) Has been cancelled
CI / Code Coverage (push) Has been cancelled
CI / Example test (push) Has been cancelled
CI / Security Audit (push) Has been cancelled
CI / Generate Documentation (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
CodeQL / Analyze-1 (push) Has been cancelled
CodeQL / Analyze-2 (push) Has been cancelled
CodeQL / Analyze-3 (push) Has been cancelled
Conformance / server (push) Has been cancelled
Conformance / client (push) Has been cancelled
Release-plz / Release-plz release (push) Has been cancelled
Release-plz / Release-plz PR (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / Release crates (push) Has been cancelled
Updates the requirements on [rstest](https://github.com/la10736/rstest) to permit the latest version.
- [Release notes](https://github.com/la10736/rstest/releases)
- [Changelog](https://github.com/la10736/rstest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/la10736/rstest/compare/v0.26.1...v0.27.0)

---
updated-dependencies:
- dependency-name: rstest
  dependency-version: 0.27.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-18 13:47:35 -04:00
dependabot[bot]
9f7ace357a
chore(deps): bump the codeql-action group with 4 updates (#1277)
Bumps the codeql-action group with 4 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action).


Updates `github/codeql-action/init` from 4.37.9 to 4.38.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](cdf488f595...b96794f015)

Updates `github/codeql-action/autobuild` from 4.37.9 to 4.38.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](cdf488f595...b96794f015)

Updates `github/codeql-action/analyze` from 4.37.9 to 4.38.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](cdf488f595...b96794f015)

Updates `github/codeql-action/upload-sarif` from 4.37.3 to 4.38.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](e4fba868fa...b96794f015)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: codeql-action
- dependency-name: github/codeql-action/autobuild
  dependency-version: 4.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: codeql-action
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: codeql-action
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: codeql-action
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-18 13:47:13 -04:00
dependabot[bot]
011f281ced
chore(deps): bump astral-sh/setup-uv from 10.0.1 to 10.1.0 (#1278)
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 10.0.1 to 10.1.0.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](20cfd1bf94...bec219d24c)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 10.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-18 13:46:56 -04:00
dependabot[bot]
0cfbe7f453
chore(deps): bump taiki-e/install-action from 2.87.6 to 2.87.12 (#1279)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.87.6 to 2.87.12.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](7b8d4719ee...3f74d7c16a)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.87.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-18 13:46:37 -04:00
github-actions[bot]
fd7811fdaa
chore: release v3.4.0 (#1258)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-15 11:37:39 -04:00
Dale Seo
0550e13c1b
fix(rmcp): use ServerConfig in cancellation test (#1273) 2026-09-15 11:18:33 -04:00
Auro
b037c0fa88
fix(rmcp): route peer cancellation by lifecycle (#1262)
* fix(rmcp): route peer cancellation by lifecycle

Cancel inbound legacy requests by their exact request ID. Preserve modern
subscription waiter cleanup using the peer lifecycle and outbound request kind.

Suppress cancelled handler results and errors during EOF draining without
dropping uncancelled responses during graceful shutdown.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* test(rmcp): parameterize reverse cancellation cases

Use rstest to report each startup and request ID combination independently.
Keep the existing per-case timeout and cancellation assertions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(rmcp): use canonical initialize result in cancellation test

Replace the stale ServerInfo reference after merging the upstream alias migration.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Hunter Sadler <aurokin@github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-09-14 16:22:57 -04:00
Dale Seo
d481cb0eec
fix: run first pre-init request in service loop (#1263) 2026-09-14 15:45:05 -04:00
Dale Seo
47fcdf4b57
fix(auth): let url origin decide prm discovery (#1264) 2026-09-14 15:30:52 -04:00
Dale Seo
165ff92df1
feat(model): add ServerConfig and ClientConfig (#1266) 2026-09-14 15:30:41 -04:00
Jack Amadeo
fbfc5cc96e
ci: add OpenSSF Scorecard analysis (#1214) 2026-09-14 12:01:32 -04:00
Teddy Tennant
3075dc9152
fix(model): deprecate ServerInfo and ClientInfo aliases (#1156)
* fix(model): deprecate ServerInfo and ClientInfo aliases

Those names collide with the protocol's serverInfo/clientInfo fields,
which are only Implementation identity. Prefer InitializeResult and
InitializeRequestParams. Handler trait signatures, the tool_handler
macro output, tests, examples and docs now use the canonical types; the
aliases remain as deprecated shims for the rest of 3.x.

Fixes #1082

* fix(model): keep public signatures on the deprecated aliases

The public API check compares rendered signatures, and rustdoc records a
type alias by the name it was written with. Respelling
`ServerHandler::get_info`, `ClientHandler::get_info`,
`DiscoverResult::from_server_info` and `impl ClientHandler for ClientInfo`
onto `InitializeResult`/`InitializeRequestParams` therefore reads as
changed public items even though the aliases resolve to those exact types
and nothing downstream breaks.

Put those four signatures back on the aliases so the published API is
byte-identical, and note why at the alias definitions. The deprecation
still steers new code, and the macro output, tests, examples and docs
keep using the canonical names.

* fix(model): migrate leftover ClientInfo test sites

Two tests landed on main after the alias deprecation and still named
ClientInfo. Clippy -D warnings and the no-local feature test job both
failed on them.
2026-09-12 12:11:30 -04:00
easyinplay
a5d1169e20
fix(auth): ignore non-metadata JSON when probing for protected resource metadata (#1204)
* fix(auth): ignore non-metadata JSON when probing for protected resource metadata

The base URL is probed first when looking for RFC 9728 protected resource
metadata, and any 200 there is taken to mean "this URL is the metadata
document". Every field of ResourceServerMetadata is optional, so an unrelated
JSON object deserializes into an all-None value and validation then fails hard
with "Protected resource metadata missing required resource field". The error
propagates out of resolve_metadata, so the .well-known fallbacks never run.

Servers that answer GET / with a JSON health payload hit this even when they
publish valid metadata at both well-known locations.

Treat a parsed document that carries none of resource, authorization_server or
authorization_servers as a soft failure, the same way this function already
treats a non-200 status and a body that is not JSON. A document carrying any of
those fields still goes through validate_resource_metadata_resource unchanged.

This is the JSON-object half of #810, which made a non-JSON body at the base URL
a soft failure for the same reason.

* fix(auth): stop a 200 from the resource ending metadata discovery

probe_resource_metadata_url treats any 200 as "this url is the metadata
document". That holds for the .well-known candidates it is called with in the
loop, and not for the first call, which is passed the resource itself. RFC 9728
publishes the document at the well-known URI and advertises it through the
resource_metadata parameter of a WWW-Authenticate challenge, so a 200 from the
resource is the resource answering and nothing more.

Because that first probe returned Some(base_url), discovery ended before the
.well-known candidates were tried, and a valid document published there was
never reached. Rejecting the body later could not recover it: by then the
candidates had already been skipped.

Split the first probe into probe_resource_endpoint_for_challenge, which reads
only the 401 branch. The .well-known probe keeps its behaviour.

The check added in the previous commit stays. A .well-known url can also answer
200 with something that is not a metadata document, and every field of
ResourceServerMetadata being optional makes that deserialize into an all-None
value that then fails validation fatally.

resolve_metadata_reaches_the_well_known_document_past_a_non_metadata_base_url
asserts the well-known url is actually requested; without this change it fails
with the base url requested twice and the protected-resource candidate never
probed. resolve_metadata_ignores_a_well_known_url_that_is_not_a_metadata_document
covers the remaining guard; without it the run ends in the original
"Protected resource metadata missing required resource field".

* fix(auth): keep trying the well-known candidates past a non-metadata document

discover_resource_metadata_url returned the first .well-known candidate that
answered 200 and left the loop; the document itself was fetched afterwards,
outside the loop. A candidate answering 200 with something that is not metadata
is only recognised at that point, by which time the remaining candidates have
been skipped and discovery gives up with nothing.

The probe already had the body in hand and threw it away, so the winning
candidate was requested twice. Read the body where the candidate is probed
instead: a candidate that is not the document costs one request and the loop
moves on to the next one, and the candidate that is the document is requested
once.

The second half of the old function, which walks the authorization servers a
document names, is unchanged; it now takes the document as an argument so the
challenge path keeps sharing it.

resolve_metadata_tries_the_next_well_known_candidate_past_a_non_metadata_document
covers the loop; without this change the second candidate is never requested and
the run ends on the issuer of an authorization server it was never meant to
reach.

* fix(auth): report an advertised url that is not the metadata document

A WWW-Authenticate challenge naming a resource_metadata url is the server
saying the document is there. The previous commits made a document carrying
neither resource nor an authorization server reference a soft failure
everywhere, which on that path drops out of resolve_metadata_from_challenge,
continues with authorization server discovery and settles on the legacy
endpoints, silently losing the RFC 8707 resource binding the document was
supposed to carry. Before those commits it surfaced as
"Protected resource metadata missing required resource field".

Where the url came from decides what that document means. A .well-known
candidate is a guess, so its answer only rules out that candidate and the loop
goes on. An advertised url has no better alternative to move on to, so say what
the server got wrong instead of degrading quietly.

Reading a non-200 or a body that is not JSON stays a soft failure on both
paths.

resolve_metadata_from_challenge_reports_an_advertised_url_without_metadata
covers the challenge path; without this change it resolves to
LegacyEndpointFallback.

* fix(auth): let a candidate fail validation without ending discovery

parse_resource_metadata decides what a body that is not the metadata document
means from where the url came from, and it decides it on the presence of
resource, authorization_server and authorization_servers alone. What comes
after is fatal whatever the origin: validate_resource_metadata_resource
rejects a missing resource, a resource that is not a URL, one carrying a
fragment, and one that does not match the base url, and each of those
propagates out of resolve_metadata.

A .well-known candidate handled by a catch-all route reaches it. The
{"error":"not_found","resource":"/.well-known/oauth-protected-resource"} such
a route answers with carries resource, so the presence check lets it through,
and the relative path then fails to parse as a URL. That is the shape this
branch opened on, one field further along: the remaining candidates are
skipped, authorization server discovery is skipped, and the run ends on an
error instead of the legacy endpoints.

Read the body and validate it in the same place, now read_resource_metadata,
so the origin governing the first decision governs the second one as well. A
guess that fails validation rules out that candidate and the loop goes on; an
advertised url still reports what the server got wrong.
authorization_metadata_from_resource_metadata takes a document that has
already been accepted.

resolve_metadata_tries_the_next_well_known_candidate_past_an_unusable_document
covers it; without this change it fails with "Protected resource metadata
resource field is not a valid URL".

* fix(auth): keep probing the candidates when an advertised url answers nothing

An advertised url reading a non-200 or a body that is not JSON is a soft
failure: the server said the document is at that url and nothing is being
served there, which rules out the url and not the document. The .well-known
candidates are derived from the base url rather than from that pointer, so
they are still worth probing, but the pointer was read ahead of the loop and
returned out of discover_resource_metadata either way. A challenge naming
https://host/.well-known/oauth-protected-resource that 404s takes the run
straight to authorization server discovery, while the document sits unread on
https://host/mcp/.well-known/oauth-protected-resource.

Fall through to the loop instead. Carrying on past a candidate's own 401
already let one url be requested twice, because the pointer that challenge
names can be a later candidate of the same run, and it is requested again when
the loop reaches it; not returning on the first pointer adds the same overlap.
Keep the urls this run has requested and skip the ones already read.

resolve_metadata_probes_the_candidates_past_an_advertised_url_that_is_not_served
covers the fall-through; without it the run reaches the authorization server
metadata of a server it was never pointed at.
resolve_metadata_requests_an_advertised_url_that_is_also_a_candidate_once
covers the repeat.

* fix(auth): walk an authorization server named by both fields once

A protected resource metadata document can name its authorization server in
authorization_servers, and servers written against the earlier draft also fill
the singular authorization_server. Filling both with the same value is common,
and the two are concatenated into the candidate list unfiltered, so every
well-known form of that one server's discovery url is requested twice before
the walk gives up on it.

well_known_paths already keeps its candidates distinct. Do the same here,
comparing the trimmed value the loop goes on to use.

resolve_metadata_requests_an_authorization_server_named_twice_once covers it;
without this change the discovery url is requested twice.

---------

Co-authored-by: easyinplay <easyinplay@users.noreply.github.com>
2026-09-12 11:21:03 -04:00
Sebastien Tardif
4fa84a5130
fix: do not treat malformed JSON 200 as Accepted for requests (#1208)
* fix: do not treat malformed JSON 200 as Accepted for requests

Streamable HTTP mapped any JSON body that failed to parse as
ServerJsonRpcMessage to Accepted, which is correct for notification
POSTs. For a request the worker then waits on SSE forever, so
call_tool hangs when the server returns 200 application/json with
an empty or non-JSON-RPC body.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

* fix: include a short body preview in JSON-RPC parse errors

Request POST parse failures now include a 256-byte prefix of the
unusable body so empty, HTML, and `{}` are distinguishable from the
serde diagnostic. HTTP reads bytes() then from_slice, matching Unix.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

---------

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-09-12 10:42:12 -04:00
voidstack
b86995aebd
fix(streamable-http-server): map handler-generated HeaderMismatch to HTTP 400 (#1259)
`jsonrpc_http_status` maps several protocol errors (unsupported protocol
version, missing required client capability, invalid params) to HTTP 400
on the modern per-request Streamable HTTP path, but fell through to the
default HTTP 200 for `ErrorCode::HEADER_MISMATCH`. When a `ServerHandler`
returns `Err(ErrorData::header_mismatch(...))`, RMCP serialized the
correct JSON-RPC -32020 error but sent it with HTTP 200.

The legacy/transport-generated header validation path (SEP-2243
Mcp-Method/Mcp-Name/Mcp-Param-* mismatches) was unaffected, since
`header_mismatch_jsonrpc_response` constructs an HTTP 400 response
directly rather than going through `jsonrpc_http_status`. Only
handler-generated HeaderMismatch errors on the modern path hit the bug.

Add `ErrorCode::HEADER_MISMATCH` to the existing `BAD_REQUEST` match arm,
matching the Streamable HTTP server-validation rules in the 2026-07-28
spec. Added unit tests covering the mapper directly (header-mismatch to
400, method-not-found to 404, an unmapped code defaulting to 200).

Closes #1225

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 20:54:40 -04:00
Luca
e27c5d1c24
fix(http): enforce Origin validation semantics (#1192)
Return HTTP 403 for malformed and non-UTF-8 Origin headers when validation is enabled.
Add enforce_origin_validation() for strict empty-allowlist validation while preserving the
legacy default and non-empty allowlist behavior. Configure the conformance server to opt in
explicitly.

Signed-off-by: lucarlig <luca.carlig@ibm.com>
2026-09-10 21:38:27 -04:00
github-actions[bot]
3e636cab26
chore: release v3.3.0 (#1252)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-10 11:23:32 -04:00
Jack Amadeo
b2c90b1edc
ci: pin commitlint dependencies (#1217) 2026-09-10 10:05:55 -04:00
ump45nose
46db531df9
fix(sse): saturate exponential reconnect backoff to avoid overflow panic (#1231)
* fix(sse): saturate exponential reconnect backoff

ExponentialBackoff::retry computed the reconnect multiplier with
2u32.pow(current_times). With max_times unset, current_times can reach
the bit width, panicking in debug builds and wrapping to a zero delay in
release builds for long-lived SSE clients. Use saturating_pow and
Duration::saturating_mul so the delay stays monotonic and panic-free.

* fix(sse): cap exponential reconnect backoff at a bounded max delay

Saturating the multiplier alone can still yield decades-long sleeps once
current_times reaches the bit width, pinning the stream in
tokio::time::sleep without reconnecting or terminating. Add an optional
max_delay (default 30s) that clamps the computed delay, keeping the
backoff monotonic and panic-free while guaranteeing the client retries.

* fix(sse): default max_delay to None to preserve unbounded backoff

Per maintainer feedback, leave ExponentialBackoff::default() unbounded so
the fix stays a pure overflow bug-fix. The saturating multiplier removes
the debug panic / release wrap from #1198, while max_delay stays opt-in
for callers that want a bounded reconnect delay.
2026-09-09 21:24:29 -04:00
dependabot[bot]
744b9f904c
chore(deps): bump crate-ci/typos (#1249)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1a51d4b5a03bb97576af186c813af67e9137ba7c to d43b6c087ac471e2ea7b8af622ff15f05c0c365b.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](1a51d4b5a0...d43b6c087a)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: d43b6c087ac471e2ea7b8af622ff15f05c0c365b
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-08 20:24:44 -04:00
dependabot[bot]
e8a0d7099a
chore(deps): bump taiki-e/install-action from 2.86.7 to 2.87.6 (#1250)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.86.7 to 2.87.6.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](b6ff580856...7b8d4719ee)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.87.6
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-08 20:24:09 -04:00
Dale Seo
6fa40536de
fix: resolve clippy warnings across workspace (#1195)
* fix: resolve clippy warnings across workspace

* ci: also run clippy on rmcp with all features except local

--all-features enables the local feature, which cfg-gates out
tower.rs and most of the test suite, so the existing clippy step
never actually lints them.
2026-09-08 19:05:29 -04:00
Dale Seo
f9a68bfa5c
feat: add ServerHandler::negotiate_initialize (#1247) 2026-09-08 19:05:04 -04:00
Dale Seo
7d14f3a8a2
feat(macros): reject empty tool_router (#1233)
Closes #1174

Claude-Session: https://claude.ai/code/session_017tyTJfwGpE8fidbZSRi6uU
2026-09-08 19:04:40 -04:00
Dale Seo
04c2f3fac9
fix(auth): unify refresh checks and error handling (#1236)
* fix(auth): unify refresh checks and error handling

Claude-Session: https://claude.ai/code/session_011pHFfoTygeG84mCDXzCcmw

* test(auth): add live authorization-server checks

Claude-Session: https://claude.ai/code/session_011pHFfoTygeG84mCDXzCcmw
2026-09-08 19:04:15 -04:00
Dale Seo
add9cbeab5
ci: bump codeql-action to 4.37.9 and group updates (#1240) 2026-09-08 10:38:30 -04:00
Nick Steele
302319861a
feat(auth): add enterprise refresh-token and ID-JAG exchanges (#1234)
* feat(auth): request ID-JAGs with enterprise refresh tokens

* feat(auth): exchange ID-JAGs for MCP access tokens

Redeem ID-JAGs at the approved resource authorization server and return
its bearer token, lifetime, and effective granted scopes. Preserve scope
narrowing and redacted diagnostics, and reuse the default HTTP adapter.

Support independently configured client authentication at both servers.
Document the exchange profile and test redirects and staged failures.

Partially addresses modelcontextprotocol/rust-sdk#531.

* test(conformance): cover EMA refresh-token exchange
2026-09-04 15:04:08 -04:00
dependabot[bot]
3b5ca4dd3f
chore(deps): bump astral-sh/setup-uv from 7.6.0 to 10.0.1 (#1238)
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.6.0 to 10.0.1.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](37802adc94...20cfd1bf94)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 10.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-02 07:01:50 +09:00
dependabot[bot]
42cf8d9fa3
chore(deps): update process-wrap requirement from 9.0 to 10.0 (#1229)
Updates the requirements on [process-wrap](https://github.com/watchexec/process-wrap) to permit the latest version.
- [Changelog](https://github.com/watchexec/process-wrap/blob/main/CHANGELOG.md)
- [Commits](https://github.com/watchexec/process-wrap/compare/v9.0.0...v10.0.0)

---
updated-dependencies:
- dependency-name: process-wrap
  dependency-version: 10.0.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-01 23:13:12 +09:00
github-actions[bot]
51ccb42993
chore: release v3.2.0 (#1227)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-31 19:08:18 -04:00
Dale Seo
ad9832ec21
fix: keep initialize on legacy protocol versions (#1228)
Claude-Session: https://claude.ai/code/session_01EPcBfiUWJJueJn5cve1sjp
2026-09-01 07:38:57 +09:00
Matthew Zeng
4e74499783
feat(auth): coordinate OAuth refreshes through credential stores (#1232)
* feat(auth): coordinate refreshes through credential stores

Add an optional owned guard covering credential reload, refresh, and save.
Keep credential-store failures distinct from reauthorization, including
reactive refresh after an HTTP 401.

Existing stores retain their default uncoordinated behavior. Cover guard
ordering, concurrent rotation, storage failures, and client identity checks.

* docs(auth): clarify refresh guard coordination
2026-08-31 16:59:37 -04:00
dependabot[bot]
3a2ebbc920
chore(deps): bump taiki-e/install-action from 2.85.13 to 2.86.7 (#1230)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.85.13 to 2.86.7.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](82cd3e7658...b6ff580856)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.86.7
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-30 08:26:41 +09:00
Dale Seo
1afde5b4fc
docs: use auto lifecycle in HTTP example (#1187) 2026-08-29 07:42:20 +09:00
Dale Seo
ec041b7430
ci: reduce dependabot update noise (#1184) 2026-08-29 07:42:11 +09:00
camillelawrence
3ef34e64e7
feat: add request-state key rotation (#1128)
* feat: add request-state key rotation

* docs: streamline request-state codec documentation

* fix: harden request-state fallback verification

* refactor: refine request-state keyring API

* docs: make request-state rotation guidance self-contained

* docs: streamline request-state keyring rustdocs

* test: streamline request-state keyring coverage

* docs: restore request-state key rotation doctest

* chore: remove manual changelog entry
2026-08-28 22:19:23 +09:00
Jack Amadeo
12db02833f
ci: pin release-plz fork revision (#1221) 2026-08-27 12:33:32 -04:00
Jack Amadeo
c8bb1c6f7d
ci: remove coverage job write permission (#1219) 2026-08-27 19:02:06 +09:00
Jack Amadeo
3501f3e646
ci: default workflow tokens to read-only contents (#1218) 2026-08-27 19:01:29 +09:00
Jack Amadeo
103bf239b3
ci: scope release token permissions to jobs (#1220) 2026-08-27 18:52:18 +09:00
Jack Amadeo
78cf9f1cb6
ci: pin GitHub Actions to commit SHAs (#1216) 2026-08-25 14:50:29 -04:00
King Star
02decfb682
fix(transport): fall back after sessionless HTTP discover rejections (#1211)
* fix(transport): fall back after HTTP discover rejection

* fix(transport): limit legacy fallback to sessionless probes

* style: apply nightly rustfmt import ordering
2026-08-25 12:16:49 -04:00
Nick Cooper
697c497724
fix: allow concurrent streamable http requests (#1186)
* fix: allow concurrent streamable http requests

* fix: keep streamable http recovery responsive

Keep cancellation and replies available while old session POSTs finish.
Bound the wait for old POSTs and the replacement initialization handshake.
Do not retry interrupted POSTs because the server may have processed them.

Add regressions for recovery, queued cancellation, control timeouts, and
server replies needed by active requests.

* fix: preserve response and cancellation ordering

* refactor: clarify streamable http control flow

* fix: match stream responses against pending request ids

Match responses against all pending requests before removing a stream
registration. Keep distinct numeric and string ids separate while preserving
the existing fallback for servers that stringify numeric ids.

Add a mixed-id regression and keep a separate registration owner alive in
the abandoned-cancellation test.

* feat: make streamable http control timeouts configurable
2026-08-24 22:43:35 +09:00
github-actions[bot]
4a738b9dd9
chore: release v3.1.4 (#1185)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-20 12:40:17 +09:00
Recoordinate
6f8dcde04f
fix(rmcp): preserve elicitation requestedSchema $schema dialect (#1176)
* fix(rmcp): preserve elicitation requestedSchema $schema dialect

ElicitationSchema round-trips through ElicitationSchemaWire, which had no
field for the top-level `$schema` keyword. A dialect declared on an incoming
requestedSchema (allowed since protocol revision 2025-11-25) was therefore
accepted but silently dropped on re-serialization.

Add a `schema` field to both the public struct and the wire bridge, thread it
through the two `From` conversions, and expose a `with_schema` setter. The
field is `skip_serializing_if = "Option::is_none"`, so schemas without a
dialect serialize unchanged.

Refs #1168

* test(rmcp): regenerate server message schema snapshot for elicitation $schema

The $schema dialect field added to ElicitationSchema changes the
schemars-generated ServerJsonRpcMessage schema. Regenerate the golden
snapshot so test_server_json_rpc_message_schema passes.

* fix(rmcp): generate elicitation $schema as optional string in schemars

Use schemars(with = "String") on ElicitationSchema::$schema so
schemars generates "type": "string" instead of ["string", "null"].
2026-08-19 00:02:45 +09:00
Dale Seo
d5c46b3fe0
fix: harden signing key handling (#1166)
* fix: harden signing key handling

* fix: validate static keys at compile time
2026-08-18 08:56:58 -04:00
Dale Seo
38428f66bc
fix: report pre-init metadata errors (#1160) 2026-08-18 09:34:54 +09:00
Dale Seo
018ab24fac
fix: handle declined MRTR elicitation (#1151) 2026-08-18 09:34:35 +09:00
dependabot[bot]
f9b39f3c13
chore(deps): bump taiki-e/install-action from 2.85.11 to 2.85.13 (#1183)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.85.11 to 2.85.13.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.85.11...v2.85.13)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.85.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-18 09:33:52 +09:00
dependabot[bot]
0f164cd67e
chore(deps): bump github/codeql-action from 4.37.6 to 4.37.7 (#1182)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.37.6 to 4.37.7.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v4.37.6...v4.37.7)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.37.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-18 09:33:32 +09:00
github-actions[bot]
fd14830a19
chore: release v3.1.3 (#1159)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-18 07:43:05 +09:00
Alex Hancock
0282172f33
docs: make ROADMAP status version-agnostic (#1181)
The roadmap pinned a specific patch release as "the current stable
release", so it drifted out of date as soon as the next release shipped.
Reference the releases page instead of a hardcoded version, and drop the
dated status line.

Historical entries (v3.0.0 ship date, the v1.4.0 fix reference) keep
their versions since they record facts that do not change.
2026-08-17 15:01:43 -04:00
stevenlee-oai
248cbca688
fix(auth): ignore query parameters when matching resources (#1177) 2026-08-17 14:05:21 -04:00
dependabot[bot]
f713ebd1a6
chore(deps): bump taiki-e/install-action from 2.85.10 to 2.85.11 (#1169)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.85.10 to 2.85.11.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.85.10...v2.85.11)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.85.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-13 21:33:23 +09:00
stevenlee-oai
a50a73fda2
fix(auth): retain state until issuer validation (#1167) 2026-08-12 19:27:43 +09:00
nightcityblade
d81bfa8854
fix(model): preserve elicitation property order metadata (#1150)
Co-authored-by: nightcityblade <nightcityblade@gmail.com>
2026-08-11 19:56:17 +09:00
dependabot[bot]
3f5a3f6a28
chore(deps): bump taiki-e/install-action from 2.85.8 to 2.85.10 (#1163)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.85.8 to 2.85.10.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.85.8...v2.85.10)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.85.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 19:21:19 +09:00
Dale Seo
a0e103c970
fix: time out auto discovery probe (#1149) 2026-08-11 19:20:13 +09:00
Recoordinate
b5cf34eee7
Fix typo in to_authorized_http_client doc comment (#1158) 2026-08-09 14:23:10 -04:00
ip2a
22be2cc5ad
fix(client): classify discover outcome at source, not at the error type (#1133)
`ClientLifecycleMode::Auto` only fell back from `server/discover` on
`-32601`, so legacy servers that reject the probe with other codes
(`-32600`, `-32602`, implementation-defined errors) failed to connect
even though `initialize` would have succeeded.

The previous attempt (indicates_legacy_server) classified the failure
after the fact by reverse-engineering the error type. This rewrite moves
the classification into `discover_startup` itself, where the full context
(request id, response correlation, transport state) is still available.

`discover_startup` now returns `DiscoverOutcome`: `Modern` on success,
`Legacy(error)` when the probe received a complete, correlated JSON-RPC
error whose code is not a modern-era rejection. Every other failure
becomes `Err`, so `Auto` simply matches the outcome — no methods on
`ClientInitializeError`, no downcast, no transport-specific types
leaking into the generic lifecycle layer.

Additional fixes that fall out naturally:

- Response correlation is now checked in `expect_response` for both
  success and error branches. Previously error responses skipped id
  correlation entirely. A new `UncorrelatedErrorResponse` variant
  surfaces responses that cannot be tied to the request.

- When both discover and the legacy fallback fail, a
  `LegacyFallbackFailed` compound error preserves both phases instead
  of discarding the discover error.

Fixes #1040.
2026-08-09 14:07:50 -04:00
github-actions[bot]
02c62aef2e
chore: release v3.1.2 (#1148)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-07 16:32:03 -04:00
Jeremy Schoemaker
c345078867
fix(auth): map 401/403 challenges on the SSE GET stream (#1152)
`post_message_with_max_sse_event_size` turns a 401 or 403 carrying a
`WWW-Authenticate` header into `StreamableHttpError::AuthRequired` /
`InsufficientScope`. `get_stream_with_max_sse_event_size`, in the same file,
handles only 405 and then falls through to `error_for_status()?`, so the same
challenge becomes an opaque `StreamableHttpError::Client`.

That matters because `AuthClient::get_stream` routes through
`call_reacting_to_challenges`, whose whole purpose is to catch `AuthRequired`,
run `try_refresh_or_reauth()`, and retry once. It never sees that variant from
this path, so an expired token on the standalone SSE stream is never refreshed —
the stream just fails, while the identical expiry on `post_message` recovers
silently.

Copies the two blocks verbatim from `post_message` in the same file.
`unix_socket.rs` already does the same thing in its own `get_stream`.

Adds four tests against an axum mock server: a 401 with a challenge maps to
`AuthRequired`, a 403 maps to `InsufficientScope` with the scope extracted, a
401 *without* a challenge is still not `AuthRequired`, and 405 keeps reporting
`ServerDoesNotSupportSse`. The first two fail before this change with
`Err(Client(reqwest::Error { kind: Status(401, None) }))`; the last two pass
either way and exist to catch an over-broad fix.
2026-08-07 15:39:55 -04:00
dependabot[bot]
f8e63824d3
chore(deps): bump taiki-e/install-action from 2.85.7 to 2.85.8 (#1153)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.85.7 to 2.85.8.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.85.7...v2.85.8)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.85.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 15:31:54 -04:00
dependabot[bot]
8fb3e046a1
chore(deps): bump github/codeql-action from 4.37.4 to 4.37.6 (#1154)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.37.4 to 4.37.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v4.37.4...v4.37.6)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.37.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 15:31:36 -04:00
Jeremy Schoemaker
3c8fb2a7e4
fix(sse): loop instead of recursing when skipping SSE events (#1146)
`SseAutoReconnectStream::poll_next` recursed into itself for every event it
skips — control frames, data-less frames, and frames whose data fails to
deserialize — plus once more on every state transition.

The inner stream returns `Poll::Ready` for each event it can parse out of
already-buffered bytes, so a burst of skipped events has no yield point
between them. Each one adds a stack frame, and `poll_next` is a large frame.
A client connected to a server that emits a run of non-JSON `message` frames
overflows the stack and aborts the process.

Wrap the body in a `loop` and replace the four `self.poll_next(cx)` tail calls
with `continue`. `this` is re-derived from `self.as_mut().project()` at the top
of each iteration, so the borrows end cleanly per iteration; no other logic
changes. The diff is mostly the resulting re-indent — review with `?w=1`.

Adds `skipped_events_do_not_grow_the_stack`, which feeds 50,000 undeserializable
frames through the stream. Before this change it aborts with
`fatal runtime error: stack overflow` (SIGABRT); after, it passes.
2026-08-07 10:34:18 -04:00
砂糖梨子
e150d4f3a6
fix(auth): preserve issuer trailing slash during discovery (#1145) 2026-08-07 10:31:24 -04:00
dependabot[bot]
9a3168af87
chore(deps): bump taiki-e/install-action from 2.85.6 to 2.85.7 (#1139)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.85.6 to 2.85.7.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.85.6...v2.85.7)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.85.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 09:53:43 -04:00
github-actions[bot]
baac607e52
chore: release v3.1.1 (#1115)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-05 10:52:23 -04:00
Dale Seo
f57d585168
chore: upgrade darling and syn (#1138) 2026-08-05 10:20:22 -04:00
Dale Seo
07bcda2973
fix: emit cache hints from handler macros (#1120) 2026-08-05 10:20:08 -04:00
Dale Seo
f8f9607d0b
chore: make async-trait optional (#1119) 2026-08-05 10:19:45 -04:00
Dale Seo
c4def55422
fix: expose MRTR state to tool handlers (#1104)
* fix: expose MRTR state to tool handlers

* docs: document tool call context fields
2026-08-05 09:55:09 -04:00
dependabot[bot]
7e14020cb8
chore(deps): bump taiki-e/install-action from 2.85.5 to 2.85.6 (#1129)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.85.5 to 2.85.6.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.85.5...v2.85.6)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.85.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-04 15:34:47 -04:00
dependabot[bot]
9cbb69d9e9
chore(deps): bump taiki-e/install-action from 2.85.3 to 2.85.5 (#1117)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.85.3 to 2.85.5.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.85.3...v2.85.5)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.85.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-03 14:59:42 -04:00
dependabot[bot]
5b216605f2
chore(deps): bump github/codeql-action from 4.37.3 to 4.37.4 (#1116)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.37.3 to 4.37.4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v4.37.3...v4.37.4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.37.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-03 14:59:28 -04:00
Dale Seo
684ff5a51e
fix: disambiguate input-required results (#1103) 2026-08-03 12:54:59 -04:00
dependabot[bot]
830e088d73
chore(deps): bump taiki-e/install-action from 2.85.2 to 2.85.3 (#1107)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.85.2 to 2.85.3.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.85.2...v2.85.3)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.85.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-31 14:48:08 -04:00
github-actions[bot]
1f9358eddc
chore: release v3.1.0 (#1090)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-31 10:49:59 -04:00
Alex Hancock
1cf6deb419
docs: document the ping utility with examples (#1106)
Add a Ping section to the README covering both directions of the
utility: constructing and sending a PingRequest via send_request, and
the automatic default handler response (with an override example for
custom liveness logic). Links to the MCP ping spec.

Ping was the only non-experimental feature lacking user-facing
documentation and an example.
2026-07-31 10:02:03 -04:00
Filinto Duran
00bcf13800
fix(model): decode metadata-bearing input-required results affecting mrtr (#1097)
* fix(model): decode metadata-bearing input-required results

  The draft permits every Result, including InputRequiredResult, to carry
  _meta and recommends that servers include
  io.modelcontextprotocol/serverInfo on every response. It also requires
  an InputRequiredResult to contain inputRequests or requestState so the
  client knows what to provide or how to resume the request.

  ServerResult uses untagged deserialization and attempts CallToolResult
  before InputRequiredResult. Because CallToolResult accepts _meta and
  defaults missing content to an empty list, it consumed valid
  input_required results carrying metadata. Unknown inputRequests and
  requestState fields were then discarded, leaving clients without the
  information needed to continue.

  Reject resultType input_required when deserializing CallToolResult so
  Serde proceeds to the InputRequiredResult variant. Add regression
  coverage proving that inputRequests, requestState, and serverInfo
  metadata are preserved.

  Spec:
  41d9e938e9/schema/draft/schema.ts (L137-L151)
  41d9e938e9/schema/draft/schema.ts (L198-L224)
  41d9e938e9/schema/draft/schema.ts (L541-L565)

Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com>

* incorporate feedback - close gap in inputrequired not properly checking that at least one of inputRequest or requestState is present

Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com>

---------

Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com>
2026-07-31 09:23:56 -04:00
Dale Seo
65f05e9924
feat: classify authorization-required errors (#1056) 2026-07-31 09:00:23 -04:00
Alex Hancock
3240b6e782
docs: complete Tier 1 feature docs and finalize roadmap (#1101) 2026-07-30 16:31:41 -04:00
dependabot[bot]
def31f0f51
chore(deps): bump github/codeql-action from 4 to 4.37.3 (#1100)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4 to 4.37.3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v4...v4.37.3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.37.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-30 16:00:58 -04:00
dependabot[bot]
570c478962
chore(deps): bump taiki-e/install-action from 2 to 2.85.2 (#1099)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2 to 2.85.2.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2...v2.85.2)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.85.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-30 16:00:37 -04:00
mrcs64
983a1374ff
feat: add strict stateless protocol metadata validation (#1091)
* feat: add strict stateless protocol metadata validation

* fix: clarify strict metadata compatibility boundaries

---------

Co-authored-by: mrcs64 <9069178+mrcs64@users.noreply.github.com>
2026-07-30 16:00:02 -04:00
Dale Seo
58b136f44f
fix: require metadata for modern HTTP requests (#1089) 2026-07-30 15:45:01 -04:00
Dale Seo
d272389fd4
fix: honor supported_protocol_versions when negotiating initialize (#1093) 2026-07-30 15:44:37 -04:00
Alex Hancock
51c8ee60e3
chore(conformance): meeting requirements for tier 1 (#1087)
* chore(conformance): meeting requirements for tier 1

* chore: address PR feedback
2026-07-30 11:36:12 -04:00
camillelawrence
1d0473ba75
feat: SEP-2260 stream-based enforcement of client receive-side request association (#1055)
* refactor(service): express SEP-2260 receive-side association as an enum

Replaces the has_pending_outbound_request bool on
enforce_peer_request_association with PeerRequestAssociation, so a
stream-separating transport can report per-request association (#1033).
Behavior-preserving: the event loop still passes the coarse signal as
Unknown.

* feat(transport): record inbound stream origin on streamable HTTP client (#1033)

The worker attaches an InboundStreamOrigin extension to each inbound
server request: Unassociated for the standalone GET stream,
OutboundRequest(id) for a POST's SSE stream. Mirror of the
OriginatingRequestId marker used by the server side.

* feat(service): enforce SEP-2260 client receive-side check per stream origin (#1033)

The event loop maps InboundStreamOrigin plus the in-flight responder
pool to PeerRequestAssociation: restricted requests arriving on the
standalone GET stream are now rejected with -32602 even while unrelated
outbound requests are in flight.

* test: end-to-end SEP-2260 stream-based enforcement over streamable HTTP (#1033)

* docs: cross-link SEP-2260 stream markers

* test: origin marker survives SSE resumption per SEP-2260 (#1033)

A POST SSE stream resumed via GET + Last-Event-ID (SEP-1699) reconnects
beneath execute_sse_stream, so requests replayed after a resume keep
their OutboundRequest origin. Pins the layering invariant: hoisting
reconnection above the marker attach point would wrongly reject
associated requests with -32602.

* docs: trim SEP-2260 comments to spec rationale

* docs: align SEP-2577 expect reason with sibling suppressions

* test: harden SEP-2260 stream-enforcement e2e (#1033)

Detect handler invocation via a channel asserted empty instead of a
panic in a spawned task (swallowed, cannot fail the test); surface
scripted-server misuse as transport errors rather than panics in the
transport task; bound the tail awaits with 5s timeouts. Correct the
header comment: a 2026-07-28 server minting a session id is not
spec-legal (SEP-2567 removes sessions and the GET endpoint) — the
scripted server is deliberately non-conforming, which is the point of
receive-side enforcement.

* test: adapt SEP-2260 association tests to ServerPeerInfo

Rebase onto 3.0.0: RoleClient::PeerInfo is now ServerPeerInfo (#1065)
and its constructor takes the protocol version directly.

* chore: add 'Copy' macro to new PeerRequestAssociation

Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>

---------

Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
2026-07-29 19:59:47 -04:00
github-actions[bot]
3bb3c8dbce
chore: release v3.0.1 (#1081)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-29 16:53:31 -04:00
thomas
d82c94aa8e
fix(auth): use discovered resource for token refresh (#1084) 2026-07-29 16:28:47 -04:00
mrcs64
f3c786706a
fix: return header mismatch for missing protocol header (#1083)
Co-authored-by: mrcs64 <9069178+mrcs64@users.noreply.github.com>
2026-07-29 16:02:49 -04:00
Dale Seo
596a7e1e0d
fix: negotiate stateless initialize versions (#1080) 2026-07-29 15:47:19 -04:00
mrcs64
cb50ae7890
fix: stamp server info on graceful subscription results (#1078)
* fix: stamp server info on graceful subscription results

* fix: expose subscription server info metadata

---------

Co-authored-by: mrcs64 <9069178+mrcs64@users.noreply.github.com>
2026-07-29 13:34:33 -04:00
github-actions[bot]
4e361b715f
chore: release v3.0.0 (#1077)
* chore: release v3.0.0-beta.6

* chore: release v3.0.0

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Alex Hancock <alexhancock@block.xyz>
2026-07-28 18:44:03 -04:00
Dale Seo
9334c97f0d
fix: recognize 2026 MCP methods (#1076) 2026-07-28 17:34:00 -04:00
github-actions[bot]
29c068d22b
chore: release v3.0.0-beta.5 (#1070)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-28 16:14:38 -04:00
John Howard
9a41811a66
fix!: remove server_info from DiscoverResult (#1065)
Fixes https://github.com/modelcontextprotocol/rust-sdk/issues/1064
Replaces https://github.com/modelcontextprotocol/rust-sdk/pull/1044

This removes the `server_info` field which is not part of the spec:
 https://modelcontextprotocol.io/specification/draft/schema#discoverresult.
2026-07-28 15:55:40 -04:00
Dale Seo
e54c2220af
docs: prepare for stable 3.0 release (#1073)
* docs: prepare for stable 3.0 release

* docs: update stable spec code references
2026-07-28 15:09:56 -04:00
Filip Konečný
b429fc1240
RFC 9728 resource is used instead of base url when possible (#962)
* feat(transport)!: use declared OAuth resource

* fix(transport): validate OAuth resource paths

---------

Co-authored-by: Filip Konečný <filip.konecny@thermofisher.com>
2026-07-28 15:08:25 -04:00
thomas
82a6c48e92
fix: preserve transient OAuth discovery HTTP errors (#1071)
* fix: preserve transient OAuth discovery HTTP errors

* fix: preserve HTTP 425 during OAuth discovery
2026-07-28 15:07:39 -04:00
dependabot[bot]
ce28e6d323
chore(deps): bump actions/stale from 10 to 11 (#1074)
Bumps [actions/stale](https://github.com/actions/stale) from 10 to 11.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v10...v11)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: '11'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 14:39:54 -04:00
Dale Seo
25213a271a
refactor!: remove deprecated v3 APIs (#1066) 2026-07-28 14:01:56 -04:00
Dale Seo
0dea39d101
fix!: preserve OAuth discovery transport errors (#1069)
* fix(auth): preserve OAuth discovery transport errors

* refactor!: expose domain-specific boxed OAuth HTTP errors

Let custom OAuth HTTP clients preserve native error types and source chains
while keeping the type-erased boundary explicit in the public API.

BREAKING CHANGE: OAuthHttpClientError is now a boxed error alias; custom
clients should return native errors with .into() or box them directly.

---------

Co-authored-by: Theodore Ni <3806110+tjni@users.noreply.github.com>
2026-07-28 11:38:39 -04:00
Dale Seo
e5403cf956
fix: gate client handler bounds for local (#1068) 2026-07-28 11:02:32 -04:00
github-actions[bot]
fff05d40fd
chore: release v3.0.0-beta.4 (#1061)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-28 10:32:31 -04:00
dependabot[bot]
506e692074
chore(deps): update base64 requirement from 0.22 to 0.23 (#1059)
Updates the requirements on [base64](https://github.com/marshallpierce/rust-base64) to permit the latest version.
- [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/marshallpierce/rust-base64/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: base64
  dependency-version: 0.23.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 09:12:57 -04:00
dependabot[bot]
1e9e4700f7
chore(deps): update jsonwebtoken requirement from 10 to 11 (#1058)
Updates the requirements on [jsonwebtoken](https://github.com/Keats/jsonwebtoken) to permit the latest version.
- [Changelog](https://github.com/Keats/jsonwebtoken/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Keats/jsonwebtoken/compare/v10.0.0...v11.0.0)

---
updated-dependencies:
- dependency-name: jsonwebtoken
  dependency-version: 11.0.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 08:42:45 -04:00
Alex Hancock
1f63e43479
ci: simplify conformance while running against both spec versions (#1060) 2026-07-27 18:32:07 -04:00
thomas
2047ae42ec
fix: accept namespaced discovery server information (#1044)
* fix: accept namespaced discovery server information (#1039)

* fix: preserve discovery server-info compatibility
2026-07-27 17:20:47 -04:00
github-actions[bot]
9528801f6e
chore: release v3.0.0-beta.3 (#1053)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-27 13:51:16 -04:00
Jack Amadeo
125dbfd5af
fix!: reject missing issuer in authorization server metadata by default (#1054) 2026-07-27 12:25:49 -04:00
Dale Seo
c284607794
refactor: make oauth discovery reactive (#1052) 2026-07-27 10:43:06 -04:00
Jack Amadeo
92c0793247
fix: default to allowing missing issuer (#1051) 2026-07-27 10:35:03 -04:00
github-actions[bot]
14298b72e0
chore: release v3.0.0-beta.2 (#1035)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-24 14:56:40 -04:00
Dale Seo
60a7a75703
feat: expose oauth discovery metadata source (#1041)
* feat: expose oauth discovery metadata source

* feat!: return source from metadata resolution
2026-07-24 14:07:06 -04:00
Dale Seo
6839cfd613
fix!: omit resultType for legacy protocol sessions (#1038) 2026-07-24 10:22:20 -04:00
Jack Amadeo
76988023b2
chore: declare and check MSRV (#1034)
* chore: declare and check MSRV

* fix: clippy fixes for collapsing if-statements

* ci: add explicit contents read permission to MSRV job
2026-07-23 15:59:55 -04:00
github-actions[bot]
e660b805ee
chore: release v3.0.0-beta.1 (#964)
* chore: release v3.0.0

* chore: release v3.0.0-beta.1

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Alex Hancock <alexhancock@block.xyz>
2026-07-23 14:41:54 -04:00
Dale Seo
190ea44218
fix: use issuer for JWT client audience (#1031) 2026-07-23 14:28:47 -04:00
Jack Amadeo
e903341136
chore: refactor OAuth client authorization api (#1009)
* refactor!: refactor OAuth client authorization api

`OAuthState::start_authorization` now accepts a declarative `AuthorizationRequest`
describing the client's available identity material and selects the highest-priority
mechanism the server supports: pre-registered client information → Client ID
Metadata Documents (SEP-991) → Dynamic Client Registration.

This replaces the three mechanism-specific entry points (`start_authorization`,
`start_authorization_with_metadata_url`, `start_authorization_with_preregistered_client`),
where callers previously had to know which mechanism to pick. `AuthorizationSession::new`
is consolidated the same way, and the failure-recovery pattern from #994 (returning the
manager alongside the error so `OAuthState` recovers to `Unauthorized` on transient failures)
is now applied across all registration paths. Includes new `RecordingOAuthHttpClient`-based
tests covering the priority matrix and recovery semantics, plus updated docs, examples, and
conformance client.

* fix: dcr registration uses requested application type

* fix: error for invalid combination
2026-07-23 14:13:11 -04:00
Alex Hancock
5554a41d67
docs: update for 2026-07-28 version (#1032) 2026-07-23 13:55:40 -04:00
camillelawrence
397d416fdc
feat: route SEP-2260 associated server requests to the originating SSE stream (#1029)
* feat: implement SEP-2260 require server requests to associate with client requests

* feat(service): attach originating request id to in-handler outbound requests (SEP-2260)

Reuses the ORIGINATING_REQUEST task-local from #1027; the marker rides
the request's non-serialized Extensions so the streamable HTTP server
can route associated requests to the originating SSE stream.

* feat(transport): route associated server requests to originating SSE stream (SEP-2260)

* test: end-to-end SEP-2260 stream routing over streamable HTTP

* test: register SEP-2260 routing test with required-features; drop dead list_tools

* docs: document SEP-2260 association requirement and stream routing

* docs: fix redundant explicit link on OriginatingRequestId

* docs: note marker is role-agnostic; add reason to deprecated expect

Review follow-ups: the OriginatingRequestId rustdoc now states the marker
is attached for both roles with only the server transport reading it, and
the test's deprecation suppression carries a reason per house style.

* docs: condense inline comments to repo convention

Trims multi-line inline comments added in this branch down to the terse
one-to-two-line style used elsewhere in the codebase, keeping only the
non-obvious rationale (spec references, fallback invariant, version choice).

* docs: condense SEP-2260 rustdoc sections

Tightens the duplicated per-method association section from eleven lines
to five, and trims the marker and SessionManager docs to the same
information in fewer words.

* docs: single-source SEP-2260 caller requirements on OriginatingRequestId

* feat(service): reject unassociated server-to-client requests on the client (SEP-2260)

Implements the client receive-side of SEP-2260: restricted server
requests (sampling/createMessage, roots/list, elicitation/create)
received while the client has no outbound request in flight are
answered with -32602 invalid params instead of being dispatched to the
handler. Gated on negotiated protocol >= 2026-07-28; ping is exempt.

With a request in flight we cannot tell which one the server request
belongs to (SEP-2260 defines no wire field), so this is a deliberate
under-approximation of the spec's SHOULD; exact stream-based
enforcement for streamable HTTP needs receive-side provenance plumbing
and is left as a follow-up.

---------

Co-authored-by: Alex Hancock <alexhancock@block.xyz>
2026-07-23 13:12:20 -04:00
Dale Seo
aad4d4e4c6
feat!: add distributed SSE event store (#1024) 2026-07-23 11:02:27 -04:00
N0zoM1z0
50dd8e2040
fix: reap completed response send tasks (#1026) 2026-07-22 21:01:45 -04:00
Alex Hancock
151970775c
feat: add client-side TTL-honoring response cache (SEP-2549) (#1025)
* feat: add client-side TTL-honoring response cache (SEP-2549)

Add a configurable client response cache in the service layer that honors
SEP-2549 caching hints (ttlMs / cacheScope) for tools/list, prompts/list,
resources/list, resources/templates/list, and resources/read.

Closes #974

Co-Authored-by: John-Francis Nnadi <nnadifrancis23@gmail.com>

* fix: address PR feedback

---------

Co-authored-by: John-Francis Nnadi <nnadifrancis23@gmail.com>
2026-07-22 16:41:48 -04:00
Jack Amadeo
07abfdfd99
feat: Implement SEP-2663 Tasks Extension (#1020)
* feat!: implement SEP-2663 Tasks extension, removing the experimental 2025-11-25 tasks design

Reshape tasks from the experimental SEP-1319/1686 core-protocol feature into
the official io.modelcontextprotocol/tasks extension (SEP-2663):

Model:
- Re-model Task (statusMessage, ttlMs nullable, pollIntervalMs) and add
  DetailedTask with status-discriminated payloads (inputRequests/result/error
  inlined per spec)
- CreateTaskResult now flattens Task with resultType: "task";
  add ResultType::TASK and CallToolResponse::Task
- Add tasks/update (UpdateTaskParams with MRTR InputResponses); tasks/get and
  tasks/cancel reworked; remove tasks/list, tasks/result
- notifications/tasks now carries a full DetailedTask; removed from client
  notifications (SEP-2260)

Capabilities:
- Remove core TasksCapability et al; tasks are declared via the extensions
  map (enable_tasks() builders, supports_tasks() accessors)
- Remove tool-level execution.taskSupport and the _meta.task /
  TaskMetadata / with_task() opt-in: task creation is server-directed and
  gated on the per-request client capability

Runtime:
- Replace OperationProcessor with TaskManager: durable-before-response task
  creation, input_required round-trips via tasks/update, cooperative
  cancellation, TTL expiry
- Client peer helpers get_task/update_task/cancel_task
- Emit Mcp-Name routing header from params.taskId for tasks/* methods
  (SEP-2243/2663)

Macros:
- Remove #[task_handler] and the tool execution() attribute

Tests/examples/docs updated; schema snapshots regenerated.

BREAKING CHANGE: the 2025-11-25 experimental tasks API is removed without a
compatibility shim. Clients that do not declare the tasks extension always
receive synchronous results.

* feat: gate tasks/* methods on the client tasks-extension capability (SEP-2663)

When the server advertises io.modelcontextprotocol/tasks but the client did
not declare it (per-request _meta clientCapabilities, or initialize-time
capabilities in session mode), tasks/get, tasks/update, and tasks/cancel now
return -32021 Missing Required Client Capability with the required extension
in the error data, instead of falling through to the handler's -32601
default. Servers that do not advertise the extension keep returning -32601.

Also add regression tests confirming that unknown taskIds yield -32602 and
that a legacy 2025-11-25 'task' param on tools/call is silently ignored.

* feat: pass SEP-2663 Tasks extension conformance suite

Conformance fixtures (conformance/src/bin/server.rs):
- Add the required fixture tools: greet (sync-only), slow_compute,
  failing_job (task support: required), protocol_error_job, confirm_delete,
  multi_input, and test_tool_with_task (MRTR -> task escalation), all backed
  by TaskManager with server-directed task creation gated on the client's
  tasks-extension capability
- Advertise io.modelcontextprotocol/tasks in server capabilities and wire
  get_task/update_task/cancel_task handlers
- Task-required tools reject with -32021 when the client did not declare the
  extension

SDK wire-shape fixes surfaced by the suite:
- Add resultType: "complete" to GetTaskResult and introduce TaskAckResult
  so tasks/update and tasks/cancel acks carry the SEP-2322 discriminator
  (spec: every non-CreateTaskResult response on the tasks surface is
  resultType complete); dispatch now returns ServerResult::task_ack
- CreateTaskResult gets a strict deserializer requiring resultType: "task"
  so it does not shadow task-shaped results in untagged unions
- Client update_task/cancel_task accept both TaskAckResult and EmptyResult

All 9 runnable Tasks extension server scenarios now pass (35/35 checks;
tasks-status-notifications remains upstream-skipped), so the corresponding
entries are removed from conformance/expected-failures-extensions.yaml.
2025-11-25 server suite (40/40), 2026-07-28 server suite (114/114), draft
client suite, and extensions client suite all pass their baselines.

* fix: address PR 1020 review feedback on DetailedTask schema and cooperative cancel

- DetailedTask's JsonSchema now derives from the actual flattened wire shape
  (DetailedTaskWire: base Task + optional inputRequests/result/error) instead
  of approximating with the base Task schema, so generated schemas for
  GetTaskResult and notifications/tasks document the status-specific payload
  fields. Golden message schemas regenerated.

- TaskManager::cancel_task no longer aborts the underlying future. It records
  the observable cancelled state and acks immediately (spec's eventually
  consistent semantics), but lets the operation keep running so it can observe
  is_cancel_requested() or the error from a woken request_input() and perform
  cleanup; any late result is discarded. New unit tests cover both the
  cooperative-cleanup path and waking parked input requests.

* fix: make tasks/cancel truly cooperative per SEP-2663 (FEEDBACK_2)

- TaskManager::cancel_task no longer forces terminal 'cancelled'. It records
  the cancellation intent, acks immediately, and wakes parked request_input
  awaits, but the operation decides its own terminal state: a post-cancel
  error settles as 'cancelled', while an operation that finishes its work
  settles as 'completed' — per the spec, 'the task may still reach a
  non-cancelled terminal status'.
- Add TaskContext::cancelled(), a watch-based await for use with
  tokio::select! as the cooperative cancellation exit path.
- Correct the unsupported-notification method string from
  'notifications/tasks/status' to 'notifications/tasks' and document that
  task status notifications are not yet routable through subscriptions/listen
  (SubscriptionFilter has no taskIds field; upstream check still skipped).
- Update conformance slow_compute fixture, task_demo example, and tests to
  honor cancellation via ctx.cancelled(); lifecycle conformance still 8/8
  (all 9 scenarios remain green, 35/35 checks).

* fix: sweep and evict expired tasks from TaskManager (FEEDBACK_3)

TTL handling previously only ran from get_task and only flipped overdue
non-terminal tasks to 'failed', never removing entries — an unbounded leak
for long-lived servers, and it made ttl_ms: None = 'unlimited retention'
meaningless since everything was retained forever.

- Rename expire_overdue to sweep_expired and run it from every entry point
  (spawn, get_task, update_task, cancel_task).
- Track terminal_at on each entry; terminal tasks are evicted after being
  retained for one further ttl_ms window past their terminal transition, so
  well-behaved pollers can observe the final state before late tasks/get
  calls return -32602 (spec: servers may delete expired tasks at any time,
  and returning task-not-found for purged tasks is compliant behavior).
- ttl_ms: None entries are never evicted (spec: unlimited retention);
  document the retention model on TaskManager, including that there is no
  background sweeper.
- New tests: retention-window eviction, sweep of abandoned tasks via other
  entry points, unlimited-TTL retention, and error-code assertions
  (-32602) for unknown ids across get/update/cancel.

Reviewed the second feedback item (dedicated task-not-found error code)
against SEP-2663 §Protocol Errors and it is incorrect: the SEP explicitly
specifies -32602 (Invalid params) for invalid or nonexistent taskIds, which
is what unknown_task already returns. Kept -32602; strengthened tests to
assert the code.

* fix: address Copilot review feedback on PR #1020

- TaskManager: drop the JoinHandle as soon as the operation settles instead
  of retaining it for the whole retention window, and only store it at spawn
  time if the task is still non-terminal (avoids keeping a completed handle
  that the completion path could never clear).
- Use RequestContext::client_capabilities() (which applies the initialize-time
  fallback for session peers) instead of raw context.meta.client_capabilities()
  in the task_demo example, the README snippet, and the test server — the
  meta-only form would wrongly treat session-declared tasks clients as
  unsupported.

All 9 Tasks extension conformance scenarios remain green (35/35 checks).

* fix: enforce SEP-2663 task-result gating in tools/call dispatch

A handler could return CallToolResponse::Task without checking whether the
request declared the io.modelcontextprotocol/tasks extension, sending a
CreateTaskResult to a client that cannot parse it. The SDK dispatch now
rejects that case with -32021 Missing Required Client Capability before the
response leaves the server, using RequestContext::client_capabilities()
(per-request _meta with initialize-time fallback).

Adds a regression test with a deliberately misbehaving handler that always
materializes a task; all Tasks extension conformance scenarios remain green
(35/35 checks).

* fix: align TTL boundary comparisons in sweep_expired

Copilot review: ttlMs: 0 never expired immediately because phase 1 used a
strict 'elapsed > ttl_ms' comparison, and phase 2 retained terminal tasks
at exactly the TTL boundary ('elapsed <= ttl_ms'). Treat elapsed >= ttl_ms
as expired in phase 1 and evict when elapsed >= ttl_ms in phase 2, so both
phases agree at the boundary and ttlMs: 0 expires/evicts on the first sweep.

* fix: strict TaskAckResult deserializer and TASK_REQUIRED_TOOLS consistency

- TaskAckResult carried only resultType (+ optional _meta) with a derived
  Deserialize, so inside the untagged ServerResult union it greedily matched
  any result object containing a resultType key, shadowing CustomResult and
  losing data (verified with a probe). Replace with a strict deserializer:
  deny_unknown_fields and require resultType == "complete". Regression
  tests cover both the non-matching shapes and the genuine ack shape.

- Conformance fixtures: confirm_delete and multi_input rejected non-tasks
  clients inline, contradicting the TASK_REQUIRED_TOOLS doc/constant. Move
  them into TASK_REQUIRED_TOOLS (they park on in-task elicitation and have
  no synchronous fallback) so the upfront -32021 gate is the single source
  of truth, and drop the now-unreachable inline checks.

All 9 Tasks extension conformance scenarios remain green (35/35 checks).

* fix: let the operation decide its terminal state via TaskExit

After tasks/cancel, any operation error was coerced to terminal
'cancelled', masking real failures (e.g. an unrelated error landing just
after a late cancel request) and contradicting the documented 'operation
decides its own terminal state' contract.

Change TaskFuture's error type from McpError to a new TaskExit enum:

- TaskExit::Cancelled — an explicit cooperative-cancellation exit;
  settles as terminal 'cancelled'.
- TaskExit::Error(McpError) — a real failure; settles as terminal
  'failed' with the error inlined, even after tasks/cancel was received.

From<McpError> for TaskExit keeps '?' ergonomic in task bodies, and
request_input() now returns TaskExit directly (its wake-on-cancel path
yields TaskExit::Cancelled), so parked operations that propagate it with
'?' settle as 'cancelled' automatically.

Update the conformance fixtures, task_demo example, and tests; add a
regression test asserting a post-cancel unrelated error settles as
'failed' with its error payload preserved.

All 9 Tasks extension conformance scenarios remain green (35/35 checks).

* fix: allow TaskExit enum to be exhaustive

* fix: close spawn/shutdown race, sweep in running_task_count, clarify TTL retention docs

Addresses branch-review findings:

- spawn() raced with shutdown(): if shutdown drained the task map between
  the entry insert and the JoinHandle store, the handle was dropped and the
  operation kept running detached. If the entry is gone at store time, the
  handle is now aborted instead.
- running_task_count() now runs the TTL sweep like every other entry point,
  so it no longer reports overdue tasks as running (matching the documented
  sweep-on-every-entry-point behavior). Regression test added.
- Document that terminal-task retention intentionally extends one ttl_ms
  window past the terminal transition (observation grace period) beyond the
  creation-based lifetime ttlMs advertises on the wire — compliant since
  SEP-2663 allows deleting expired tasks at any time after the TTL.
2026-07-22 14:16:52 -04:00
Alex Hancock
ac9c637d04
fix: conformance tests for tools_call and auth/scope-step-up (#1022) 2026-07-22 14:15:36 -04:00
Dale Seo
8803d39034
fix: accept stringified numeric response IDs (#1021) 2026-07-21 16:01:22 -04:00
dependabot[bot]
99ee024499
chore(deps): bump actions/labeler from 6 to 7 (#1023)
Bumps [actions/labeler](https://github.com/actions/labeler) from 6 to 7.
- [Release notes](https://github.com/actions/labeler/releases)
- [Commits](https://github.com/actions/labeler/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/labeler
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-21 15:22:29 -04:00
Dale Seo
4e36fd9531
fix: re-register after auth server change (#1011) 2026-07-21 12:14:30 -04:00
Dale Seo
a8308dc943
ci: expose extension conformance gaps (#1019) 2026-07-21 10:23:55 -04:00
Dale Seo
9bf459962e
fix: preserve JSON Schema 2020-12 keywords (#1018) 2026-07-21 10:23:35 -04:00
Dale Seo
7044ccfe7b
chore: add workflow to clean up stale PRs and issues (#1007) 2026-07-21 09:53:21 -04:00
Dale Seo
ff1a715b6e
feat: add subscription listen streams (SEP-2575) (#1000)
* feat: add subscription listen streams

* refactor: reuse legacy request helper
2026-07-21 09:53:04 -04:00
Dale Seo
8ee9ee68f1
ci: fix discovery response parsing (#1016) 2026-07-20 20:55:21 -04:00
Jack Amadeo
dd11e5be99
chore: run full conformance CI suite (#1010) 2026-07-20 17:24:30 -04:00
Jack Amadeo
c87b6422f7
fix: .with_stateful_mode -> .with_legacy_session_mode (#1015) 2026-07-20 17:23:22 -04:00
Dale Seo
5b053416b8
fix: preserve negotiated progress responses (#1005) 2026-07-20 16:20:12 -04:00
Alex Hancock
a5bb51c9d2
fix(server): serve draft-version requests statelessly per SEP-2567 (#999)
* fix(server): serve draft-version requests statelessly per SEP-2567

* refactor: address PR feedback

* refactor!: rename stateful_mode to legacy_session_mode

* fix: address PR feedback
2026-07-20 14:51:50 -04:00
Dale Seo
7abbf29e93
fix: pass client header conformance (#1012) 2026-07-20 14:18:53 -04:00
Jack Amadeo
61b40afdca
fix: enumerate conformance client scenarios (#1008)
fixes #1001.

Restores explicit dispatch arms for the ten client scenarios orphaned by #991:

standard auth flow:
- `auth/iss-*`
- `auth/offline-access-*`
- `auth/metadata-issuer-mismatch`
- `auth/authorization-server-migration`

plain connect/list client:
`json-schema-ref-no-deref`

`run_auth_client` now negotiates via `ClientLifecycleMode::Discover` —

`auth/authorization-server-migration` tracked in #879
2026-07-18 09:24:25 -04:00
Alex Hancock
8efc142e51
feat(auth): bind DCR client credentials to issuing authorization server (SEP-2352) (#998)
* feat(auth): bind DCR client credentials to issuing authorization server (SEP-2352)

* fix(auth): address SEP-2352 review feedback on AS credential binding
2026-07-18 06:44:38 -04:00
Jack Amadeo
7d811c49d1
fix(auth): add an SDK path for pre-registered OAuth clients (#994)
... and exercise it in conformance tests
2026-07-17 21:20:30 -04:00
Amey Pawar
bc2c5f3c4a
fix(transport): cancel in-flight request on stateless streamable-HTTP client disconnect (#857) (#967)
A stateless streamable-HTTP request is one-shot (no session, no resumption),
so if the client drops the response before the handler finishes, the request
is terminal and should be cancelled. Previously the handler kept running with
its `RequestContext::ct` never firing, so long-running or destructive tools
could not observe a client disconnect.

Give each stateless request its own cancellation token via
`serve_directly_with_ct` and cancel it when the client disconnects. This covers
both stateless paths: `serve_negotiated_request_directly` (per-request version
negotiation) and the non-negotiated path. In each:

- A disconnect while the handler is still producing its first message cancels
  it. The guard is disarmed once the handler emits anything, so a normal
  response is never cancelled.
- The SSE response stream is wrapped in a guard that cancels the handler if the
  stream is dropped before it ends naturally.
- When a negotiated request replies directly (JSON mode, or a non-OK status),
  the receiver is dropped, so a still-running handler is cancelled rather than
  left emitting into a closed channel. Without this its terminal send fails
  before adding the termination permit and the serve loop parks forever.

Stateful (resumable) mode is intentionally left unchanged: there a disconnect
may be recovered via `Last-Event-ID`, so cancelling on disconnect would break
resumption.

Adds a regression test covering both stateless sub-modes (SSE and JSON).
2026-07-17 09:06:44 -04:00
stevenlee-oai
9df629e631
fix(auth): distinguish rejected refresh tokens from transient failures (#963)
* fix(auth): distinguish rejected refresh tokens

* docs(auth): clarify refresh failure handling
2026-07-16 22:15:19 -04:00
Dale Seo
2e2c79173c
feat: add modern client lifecycle modes (SEP-2575) (#995)
* feat: add modern client lifecycle modes

* refactor: drop modern naming and dead cleanup

* fix: gate request metadata on inline lifecycle only
2026-07-16 17:12:16 -04:00
Jack Amadeo
81745f1375
fix(auth): validate discovered metadata issuer (#996)
* fix(auth): validate discovered metadata issuer

Validate authorization server metadata issuers against the issuer implied by
standard discovery URLs before trusting advertised endpoints.

fixes #983

* test(auth): add required issuer to authorization-server mock metadata

---------

Co-authored-by: Alex Hancock <alexhancock@block.xyz>
2026-07-16 16:55:11 -04:00
dependabot[bot]
77eb607ccb
chore(deps): update hmac requirement from 0.12 to 0.13 (#988)
* chore(deps): update hmac requirement from 0.12 to 0.13

Updates the requirements on [hmac](https://github.com/RustCrypto/MACs) to permit the latest version.
- [Commits](https://github.com/RustCrypto/MACs/compare/hmac-v0.12.0...hmac-v0.13.0)

---
updated-dependencies:
- dependency-name: hmac
  dependency-version: 0.13.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: align hmac and sha2 digest versions

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
2026-07-16 16:51:05 -04:00
Stefano Amorelli
7824bfdada
feat(auth): accumulate client-side scopes during step-up authorization (#888)
SEP-2350 [1] clarifies that scope accumulation is a client-side
responsibility: during re-authorization the client requests the union of
its previously requested scopes and the newly challenged scopes, because
servers report only the scopes needed for the current operation in
403 / insufficient_scope challenges (RFC 6750 §3.1), not the union of
everything granted so far.

select_base_scopes returned a single source, so a 403 challenge replaced
the previously requested scopes instead of widening them, dropping prior
permissions across step-up rounds. I make it union the previously
requested scopes, the WWW-Authenticate challenge, and the protected
resource metadata scopes (RFC 9728), treating each server-reported set as
an operational requirement for the current operation rather than an
exclusive directive; AS metadata and caller defaults only seed the
request when nothing has been requested or challenged yet.

exchange_code_for_token treats an explicit scope list as authoritative,
so a server may still narrow the grant. When the server omits scope it
has granted exactly what the client requested (RFC 6749 §5.1), so the
grant has to fall back to the scopes requested in this round, not the
previously granted set; otherwise a step-up that the server confirms by
omitting scope would silently drop the just-added permission and the
client would loop on the same 403. The widened request was not persisted
anywhere the exchange could read it, so I record it on
StoredAuthorizationState per authorization (defaulting empty for states
stored before this field existed) and resolve the grant from there. This
addresses review feedback [2] that the earlier fallback returned the
previous grant rather than the request. Deduplication preserves
first-seen order for stable, testable output. Tests cover multi-round
accumulation, dedup, resource-metadata unioning, and grant resolution
when the response omits scope. Implements [3].

[1]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/draft/basic/authorization.mdx#L682
[2]: https://github.com/modelcontextprotocol/rust-sdk/pull/888#pullrequestreview-4454854582
[3]: https://github.com/modelcontextprotocol/rust-sdk/issues/877

Signed-off-by: Stefano Amorelli <stefano@amorelli.tech>
2026-07-16 15:47:52 -04:00
Dale Seo
3549e86d28
feat!: add server discovery and negotiation (SEP-2575) (#973)
* feat!: add server discovery and negotiation

* feat!: make protocol unions extensible

* ci: add discovery conformance coverage
2026-07-16 13:47:30 -04:00
King Star
b93220d4a7
feat(conformance): add SEP-2243 header validation tool (#997) 2026-07-16 13:11:45 -04:00
Dale Seo
839922d8fd
feat!: align metadata models with draft schema (#993) 2026-07-15 08:58:46 -04:00
dependabot[bot]
92581bee53
chore(deps): bump actions/setup-node from 6 to 7 (#992)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6 to 7.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 15:20:17 -04:00
Dale Seo
24ba5265e2
fix: bound streamable HTTP memory usage (#970)
* fix: bound streamable HTTP memory usage

* fix: handle SSE comment lines safely

* refactor: narrow SSE limit public API surface
2026-07-14 15:05:07 -04:00
King Star
1543a1afee
fix(streamable-http): preserve progress in JSON mode (#990) 2026-07-14 14:46:42 -04:00
Dale Seo
f3459fa777
test: serialize JavaScript dependency install (#972) 2026-07-14 14:32:56 -04:00
Jack Amadeo
2fe90a245b
chore: update client conformance test scenarios (#991) 2026-07-14 13:47:57 -04:00
dependabot[bot]
98bb2635f4
chore(deps): update tokio-tungstenite requirement from 0.29.0 to 0.30.0 (#989)
Updates the requirements on [tokio-tungstenite](https://github.com/snapview/tokio-tungstenite) to permit the latest version.
- [Changelog](https://github.com/snapview/tokio-tungstenite/blob/master/CHANGELOG.md)
- [Commits](https://github.com/snapview/tokio-tungstenite/compare/v0.29.0...v0.30.0)

---
updated-dependencies:
- dependency-name: tokio-tungstenite
  dependency-version: 0.30.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 15:55:56 -04:00
Dale Seo
93ea09a51b
docs: retarget roadmap to 2026-07-28 spec (#986) 2026-07-13 15:54:55 -04:00
Dale Seo
3662d20ac2
test: enable supported draft SEP coverage (#971)
* test: enable supported draft SEP coverage

* test: add SEP-2322 MRTR conformance scenarios

* refactor: rename run_mrtr_client to run_stateless_client
2026-07-10 17:21:07 -04:00
Michael Neale
d8331d9442
feat!: implement SEP-2549 cache hints (#889)
implements https://github.com/modelcontextprotocol/rust-sdk/issues/875

Co-authored-by: Jack Amadeo <jackamadeo@squareup.com>
2026-07-10 13:54:18 -04:00
Jack Amadeo
2d4c29fb02
fix: specify compatible sse-stream version (#968) 2026-07-10 13:18:55 -04:00
Dale Seo
dd30a70f84
feat!: add MRTR behavior support (SEP-2322) (#929)
* feat!: add MRTR behavior support

* feat: harden SEP-2322 MRTR support

* ci: diff public API only on features common to base and head

cargo public-api builds both revisions with the same feature set, so a
feature introduced (or removed) by a PR broke the build of the other
revision. Restrict the all-features diff to features present in both the
base and head revisions.
2026-07-10 12:02:26 -04:00
Dale Seo
a7194590ff
feat!: type Annotations.lastModified as a string (#956) 2026-07-10 11:47:16 -04:00
Dale Seo
11f525c503
feat!: add SEP-2243 HTTP standard headers (#907)
* feat: add SEP-2243 HTTP standard headers

* feat!: validate Mcp-Param-* headers on server

* fix: emit Mcp-Method on reinit initialized POST

* fix: align header mismatch error code with draft spec

* chore: remove redundant streamable HTTP client comments
2026-07-10 11:16:29 -04:00
Jack Amadeo
60d3e77f52
fix: flag schema derive on schemars feature (#966) 2026-07-09 19:51:37 -04:00
Brandon Bennett
9e3de344f4
feat: relax outputSchema to accept non-object JSON Schema types (SEP-2106) (#895)
* fix: address PR review - schema_for_output no longer validates or returns Result

- Add strip_output() that strips title/description without validating type (Dale #1)
- Change schema_for_output to return Arc<JsonObject> instead of Result (Dale #2)
- Cache only Arc<JsonObject> success values, not Result (Dale #3)
- Remove dead unwrap_or_else panic paths in with_output_schema, ToolBase, and macros
- Tighten test assertions from contains to assert_eq on type field (Dale #4)
- Update test_schema_for_output_rejects_primitive to accept_primitive (SEP-2106)

Co-authored-by: Orca <help@stably.ai>

* test(rmcp): add non-object output schema tests for SEP-2106

Add tests verifying schema_for_output accepts non-object types:
- test_tool_builder_methods: primitive (i32), array (Vec<String>), option
- test_structured_output: tool returning Json<Vec<T>> and Json<i32>
- test_json_schema_detection: Json<Vec<T>>, Result<Json<Vec<T>>,E>, Json<String>
- tool_traits: ToolBase::output_schema with Vec<AddOutput> output type

* test(rmcp): add missing edge case tests from code review

Add tests identified during code review:
- description stripping for primitive types
- composition types (Option<String> with anyOf/oneOf/null)
- cache correctness (Arc::ptr_eq for repeated calls)
- schema_for_input rejecting array types (not just primitives)
- schema_for_output accepting unit type ()

* feat!: mark schema_for_output return-type change as breaking

This introduces SEP-2106: schema_for_output no longer validates or
returns Result. The public signature changed, so bump major.

* fix: address Dale's PR review - direct schema.get assertions, remove ArrayTool

- Replace loose schema_str.contains(...) assertions with direct
  schema.get("type") equality checks in test_tool_builder_methods.rs
  and test_structured_output.rs
- Remove redundant ArrayTool fixture and its round-trip
  serde_json::from_str test from tool_traits.rs since schema is
  already Arc<JsonObject>
- Drop dead schema_str variable in test_structured_output.rs

---------

Co-authored-by: Brandon Bennett <brandonbennett@macbookair.myfiosgateway.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Brandon Bennett <brandonbennett@Pursuits-Air.lan>
2026-07-08 14:19:28 -04:00
Dale Seo
ba00b15097
feat!: relax tool result structuredContent type (SEP-2106) (#933)
Re-applies #919 (reverted by #932): ToolResultContent.structured_content
becomes Option<Value> so non-object structured content is accepted,
matching CallToolResult and SEP-2106.

BREAKING CHANGE: ToolResultContent.structured_content changes from
Option<JsonObject> to Option<Value>.
2026-07-08 14:14:07 -04:00
Dale Seo
f4ff56b81d
feat!: add MRTR model types (SEP-2322) (#915)
* feat!: add SEP-2322 MRTR model types

* feat!: remove URLElicitationRequiredError (SEP-2322)
2026-07-08 14:03:33 -04:00
github-actions[bot]
519577601d
chore: release v2.2.0 (#953)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-08 13:24:21 -04:00
dependabot[bot]
6dd7b858c6
chore(deps): update p256 requirement from 0.13 to 0.14 (#959)
* chore(deps): update p256 requirement from 0.13 to 0.14

Updates the requirements on [p256](https://github.com/RustCrypto/elliptic-curves) to permit the latest version.
- [Commits](https://github.com/RustCrypto/elliptic-curves/compare/primeorder/v0.13.0...p256/v0.14.0)

---
updated-dependencies:
- dependency-name: p256
  dependency-version: 0.14.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(conformance): update p256 key parsing

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
2026-07-08 11:26:57 -04:00
Dale Seo
a03793530d
fix: pass client conformance suite (#960)
* fix(auth): support oauth metadata fallbacks

* ci: run client conformance scenarios

* fix: pass full client conformance suite

* ci: run full client conformance suite

* fix: update SSE stream constructor
2026-07-08 11:25:12 -04:00
Dale Seo
dbda50c0eb
fix: don't respond to cancelled requests (#957)
* fix: don't respond to cancelled requests

* chore: remove redundant comment

* fix: update SSE stream constructor
2026-07-08 11:24:36 -04:00
King Star
45f2f72881
fix: fail orphaned streamable HTTP responses on reinit (#914)
* fix: fail orphaned streamable HTTP responses on reinit

* fix: update crates/rmcp/src/transport/streamable_http_client.rs

---------

Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
2026-07-07 20:06:46 -04:00
Dale Seo
95490facd6
feat: reject auth servers lacking S256 PKCE support (#955) 2026-07-05 09:05:09 -04:00
Dale Seo
bdf0c32e8c
fix: address 2025-11-25 conformance audit findings (#951)
* 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
2026-07-02 22:42:35 -04:00
github-actions[bot]
8e44af499b
chore: release v2.1.0 (#950)
* chore: release v2.1.0

* chore: fix changelog spelling

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
2026-07-02 09:35:24 -04:00
Amey Pawar
4833ec7b63
fix(transport): make AsyncRwTransport::receive cancel-safe (#941) (#947)
`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`.
2026-07-02 09:08:24 -04:00
Sarthak Bhardwaj
5837e225d3
fix(auth): preserve refresh_token when refresh response omits it (#949)
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>
2026-07-02 08:44:59 -04:00
Dale Seo
ee2d81f4d0
feat: add SEP-414 trace context meta accessors (#910) 2026-07-01 16:51:05 -04:00
Dale Seo
496902b9cf
fix: block redirect header leaks (#936) 2026-07-01 16:28:08 -04:00
Tyler
64d22def02
fix: don't respond to unparseable messages (#940)
* 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>
2026-07-01 16:01:58 -04:00
John Howard
288f99653e
feat: add SEP-2575 meta helpers (#942)
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
2026-07-01 14:40:30 -04:00
actsalan
80a74795e9
fix: negotiate protocol version in handler (#930)
* fix: negotiate protocol version in handler (fixes #916)

* fix: use server pinned version as fallback in default initialize handler
2026-06-29 13:29:06 -04:00
github-actions[bot]
67a3085944
chore: release v2.0.0 (#920)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-29 08:24:11 -04:00
Dale Seo
c1a8b29ff2
fix: prevent OAuth resource spoofing (#937) 2026-06-27 19:43:46 -04:00
Dale Seo
eb435c6f51
fix: block oauth metadata ssrf (#935)
* fix: block oauth metadata ssrf

* fix: warn on oauth metadata blocks
2026-06-26 23:37:12 -04:00
Dale Seo
dfa7fd6f93
fix: prevent streamable HTTP session leak (#934) 2026-06-26 21:09:16 -04:00
Dale Seo
e1af378949
chore: consolidate repeated rmcp tests (#931) 2026-06-26 16:15:20 -04:00
Dale Seo
4b9bea7e7d
Revert "feat!: relax tool result structuredContent type (#919)" (#932)
This reverts commit b8a936c4f5.
2026-06-26 16:00:02 -04:00
Dale Seo
b8a936c4f5
feat!: relax tool result structuredContent type (#919) 2026-06-26 14:57:18 -04:00
moroviintaas
415852806d
fix: fill missing fully qualified syntax in prompt_handler macros (#866)
* fix: fill missing fully qualified syntax in prompt_handler macros

* fix: remove unused prompt handler test imports

---------

Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
2026-06-26 12:38:17 -04:00
Dale Seo
d1cabb458f
feat: deprecate roots/sampling/logging types (#923) 2026-06-25 21:11:59 -04:00
Dale Seo
f07ee4ae1a
docs: align README examples with v2 model API (#928) 2026-06-25 20:00:51 -04:00
Dale Seo
f1daa9277e
docs: explain OAuth HTTP client setup (#918) 2026-06-25 19:59:47 -04:00
Dale Seo
77932141e3
feat!: align model types with MCP 2025-11-25 spec (#927)
* feat!: align model types with MCP 2025-11-25 spec

* fix: complete 2025-11-25 model spec conformance
2026-06-24 20:41:13 -04:00
Dale Seo
42a1069833
ci: add cargo-public-api check for breaking API changes (#924)
* 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).
2026-06-24 15:19:04 -04:00
Dale Seo
b79e0d9df3
ci: honor breaking semver markers (#922) 2026-06-23 17:53:57 -04:00
Brice Fernandes
0a95c3b608
fix(rmcp): add Audio variant to PromptMessageContent (#865)
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>
2026-06-23 16:35:40 -04:00
github-actions[bot]
25220361d5
chore: release v1.8.0 (#850)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-23 08:20:21 -04:00
Dale Seo
6d020c9684
fix(auth): preserve configured reqwest client (#917) 2026-06-22 17:34:16 -04:00
jif
de898dd842
Allow custom HTTP clients for OAuth (#908)
* feat: allow custom HTTP clients for OAuth

* fix(auth): preserve configured client for refresh

* fix(auth): harden OAuth HTTP adapter

* refactor(auth): simplify OAuth HTTP plumbing

* fix(auth): stop refresh token redirects by default

* fix(auth): re-export OAuth HTTP client types
2026-06-22 13:24:32 -04:00
King Star
3c5ce2b0d7
fix(auth): align OAuth metadata discovery ordering (#887) 2026-06-22 11:34:18 -04:00
Dale Seo
443677ca31
fix: align progress timeout token (#909) 2026-06-22 10:59:32 -04:00
Abdoul
4fd4986b62
fix(elicitation): preserve enumNames through ElicitationSchema serde round-trip (#905)
* 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).
2026-06-20 16:55:51 -04:00
dependabot[bot]
bf71eb8b09
chore(deps): bump actions/checkout from 6 to 7 (#911)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-19 06:59:25 -04:00
ContextVM-org
5d00e20f2a
Add progress-aware request timeout reset (#858)
* feat: add progress-aware request timeouts

* Update crates/rmcp/src/service.rs

Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>

* refactor(rmcp): move helpers and simplify response waiting

---------

Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
2026-06-17 15:07:37 -04:00
Greg Virgin
4b82e41522
docs(server): document Err vs Ok(CallToolResult::error) visibility contract on ServerHandler::call_tool (#854)
* 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>
2026-06-16 22:13:33 -04:00
Dale Seo
95a8e961e0
feat: standardize resource-not-found error code (SEP-2164) (#899)
* 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>
2026-06-16 21:55:25 -04:00
dependabot[bot]
8f5310b424
chore(deps): update tower-http requirement from 0.6 to 0.7 (#906)
Updates the requirements on [tower-http](https://github.com/tower-rs/tower-http) to permit the latest version.
- [Release notes](https://github.com/tower-rs/tower-http/releases)
- [Commits](https://github.com/tower-rs/tower-http/compare/tower-http-0.6.0...tower-http-0.7.0)

---
updated-dependencies:
- dependency-name: tower-http
  dependency-version: 0.7.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 21:15:14 -04:00
Loocor
266f870e69
docs: refine mcpmate listing copy (#885) 2026-06-11 10:51:57 -04:00
0xWeakSheep
5a78773faa
fix: return tool errors for invalid arguments (#894) 2026-06-11 10:18:02 -04:00
Michael Neale
53c6daadd9
fix(auth): apply offline_access to reauth paths (#897)
* fix(auth): apply offline_access to reauth paths

* Update crates/rmcp/src/transport/auth.rs

Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>

---------

Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
2026-06-11 06:43:49 -04:00
Dawid Nowak
52e731bec6
fix: Small improvement to progress demo (#898)
Signed-off-by: Dawid Nowak <nowakd@gmail.com>
2026-06-10 19:28:33 -04:00
Dale Seo
2536a05992
fix: update peer info on duplicate initialize (#862) 2026-06-10 15:42:09 -04:00
Michael Neale
2d3d1879ad
feat: validate OAuth authorization response issuer (#896)
* feat: validate OAuth authorization response issuer

* fix: tighten issuer validation callbacks
2026-06-10 13:08:31 +10:00
Stefano Amorelli
f1ef2ec86c
feat: specify OIDC application_type during dynamic client registration (SEP-837) (#883)
* 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>
2026-06-04 10:53:18 -04:00
Rohit Ghumare
82b04a31f5
feat: deprecate roots, sampling, and logging (SEP-2577) (#884)
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.
2026-06-04 08:43:39 -04:00
Dale Seo
254f04a764
fix: strip and validate tool outputSchema and inputSchema (#860)
* fix: remove unnecessary fields from tools' outputSchema

* fix: validate input schema root type per MCP spec
2026-06-02 12:01:33 -04:00
Datron
8f558d83be
docs: added jilebi-mcp to the list of built with rmcp (#861) 2026-05-29 11:31:44 -04:00
savanne-kham
6a7f10af51
fix(examples): accept stringified prompt argument values (#859) 2026-05-28 11:32:35 -04:00
Federico Poli
53e4410d99
fix: remove unnecessary fields from tools' inputSchema (#856) 2026-05-28 11:28:57 -04:00
Dale Seo
c330fede90
fix: reject init header/body version mismatch (#853) 2026-05-18 20:21:22 -04:00
Dale Seo
d328751dc9
fix: align protocol version negotiation (#855)
* fix: align protocol version negotiation

* ci: relax semver-checks to allow minor changes
2026-05-18 16:54:08 -04:00
Alex Hancock
cc66e3091e
fix: accept 200 with empty body in response to notifications in addition to 202 (#849) 2026-05-14 14:52:11 -04:00
github-actions[bot]
3529c3675f
chore: release v1.6.1 (#831)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-13 09:36:22 -04:00
Dale Seo
d695046ffa
fix: enable task support on counter long_task example (#838)
* fix: enable task support on counter long_task example

* ci: include example targets when testing example crates
2026-05-13 09:22:58 -04:00
Dale Seo
5ccdfc07be
feat: add task-based stdio examples (#839)
* feat: add task-based stdio examples

* docs: add Tasks section to Chinese README
2026-05-13 09:22:29 -04:00
Yutaka Nishimura
d83b1566d0
fix(rmcp): flatten Resource variant of PromptMessageContent (#843)
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.
2026-05-12 14:46:12 -04:00
Dale Seo
321ab14f67
fix: reply -32700 on stdio parse errors instead of closing (#833)
* fix: reply -32700 on stdio parse errors instead of closing

* fix: make JsonRpcError id optional per MCP spec
2026-05-07 12:27:15 -04:00
Xuntao Chi
0f776ab1d6
chore(rmcp): remove dependency on chrono default features (#829) 2026-05-06 14:53:22 -04:00
Dale Seo
3bf5298972
ci: extend semver check to all features except local (#832) 2026-05-04 20:44:16 -04:00
dependabot[bot]
88df9af9f2
chore(deps): update askama requirement from 0.15 to 0.16 (#830)
Updates the requirements on [askama](https://github.com/askama-rs/askama) to permit the latest version.
- [Release notes](https://github.com/askama-rs/askama/releases)
- [Commits](https://github.com/askama-rs/askama/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: askama
  dependency-version: 0.16.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 20:37:16 -04:00
lutz-grex
2f8d3b7355
Fix/issue 817 idle timeout log level (#824)
* 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
2026-05-04 20:14:06 -04:00
github-actions[bot]
014fb2e6cd
chore: release v1.6.0 (#818)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-01 09:38:25 -04:00
Dale Seo
c1e0eadd5d
fix: add init_timeout for streamable-http sessions (#811) 2026-05-01 09:20:50 -04:00
Dale Seo
ef74147113
fix(http): fall back to :authority for HTTP/2 (#827) 2026-05-01 09:17:06 -04:00
Dale Seo
4cf78736e7
feat(http): log Host/Origin rejections (#826) 2026-05-01 14:42:47 +02:00
Edward Burton
fffe138ec4
docs: add systemprompt-template to Built with rmcp (#820) 2026-04-23 17:22:50 -04:00
Dale Seo
9753d61510
feat(http): add Origin header validation (#823) 2026-04-23 15:41:06 -04:00
lutz-grex
63583b164f
feat(router): support runtime disabling of tools (#809)
* 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
2026-04-22 08:10:29 -04:00
Guy Lichtman
8f696e6788
feat: optional session store (resumabillity support) (#775)
* feat: optional session store

* fix: docs

* fix: pr review comments

* fix: add non_exhaustive

* fix: support for non_exhaustive StreamableHttpServerConfig

* fix: add SessionState::new
2026-04-21 17:06:18 -04:00
Dale Seo
f6893a7d91
ci: add semver check job for pull requests (#819) 2026-04-20 21:00:15 -04:00
lutz-grex
7eb252aee7
fix(docs): use correct Parameters<T> syntax in tool examples (#814)
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
2026-04-20 19:30:52 -04:00
github-actions[bot]
020a38b6ad
chore: release v1.5.0 (#804)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-16 12:47:24 -04:00
jh-block
01a6666429
fix: treat resource metadata JSON parse failure as soft error (#810)
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.
2026-04-16 12:16:19 -04:00
Dale Seo
3e56d52764
fix: include http_request_id in request-wise priming event IDs (#799)
* 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
2026-04-16 12:02:35 -04:00
WeekendsuperHero
6603c1ff15
fix(macros): respect local feature in #[prompt] macro — omit + Send bound (#803)
* refactor(prompt): update return type handling

* fix(prompt): add omit send and test
2026-04-14 09:56:19 -04:00
Dale Seo
c99903a67a
fix(http): drain SSE stream for connection reuse (#790)
* 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
2026-04-13 16:33:30 -04:00
Will Pfleger
ad3997268d
feat(transport): add constructors for non_exhaustive error types (#806)
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
2026-04-13 16:31:26 -04:00
dependabot[bot]
a743f15654
chore(deps): update which requirement from 7 to 8 (#807)
Updates the requirements on [which](https://github.com/harryfei/which-rs) to permit the latest version.
- [Release notes](https://github.com/harryfei/which-rs/releases)
- [Changelog](https://github.com/harryfei/which-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/harryfei/which-rs/compare/7.0.0...8.0.2)

---
updated-dependencies:
- dependency-name: which
  dependency-version: 8.0.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 16:10:42 -04:00
Dale Seo
a64be23152
feat: add 2025-11-25 protocol version support (#802) 2026-04-10 15:21:48 -04:00
github-actions[bot]
4628720f89
chore: release v1.4.0 (#779)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-10 10:40:38 -04:00
Anar Azadaliyev
65d2b29da5
fix(server): remove initialized notification gate to support Streamable HTTP (#788)
* 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>
2026-04-09 19:23:59 -04:00
Dale Seo
a7b570062e
fix: pass GIT_TOKEN to release-plz CLI (#798)
* fix: pass GIT_TOKEN to release-plz CLI

* fix: bump Node.js to 22 in CI for SDK compatibility
2026-04-09 18:59:41 -04:00
Dale Seo
8a8c036ccb
chore: update Rust toolchain to 1.92 (#797) 2026-04-09 14:09:59 -04:00
Alex Hancock
34d0bc6cd2
fix: upgrade rustc in actions (#796) 2026-04-08 21:35:34 -04:00
Eren Atas
45a4cc5316
feat: add Default and constructors to ServerSseMessage (#794)
* feat: add Default and constructors to ServerSseMessage

* fix: add tests, missing feature gates, small test issues
2026-04-08 16:39:16 -04:00
Matthew Zeng
5f432834a1
feat: add meta to elicitation results (#792) 2026-04-08 15:07:04 -04:00
Dale Seo
be321a4abe
feat(macros): auto-generate get_info and default router (#785)
* 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
2026-04-08 15:06:26 -04:00
Dale Seo
5891b45162
refactor: unify IntoCallToolResult Result impls (#787) 2026-04-08 10:39:18 -04:00
Dale Seo
d98248ac22
ci: add --locked to release-plz install (#786) 2026-04-08 10:39:00 -04:00
Dale Seo
929441e443
fix: default session keep_alive to 5 minutes (#780) 2026-04-08 10:36:19 -04:00
Axel
cabf71aa74
feat(transport): add which_command for cross-platform executable resolution (#774)
* 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`.
2026-04-07 06:58:23 -04:00
jokemanfire
8e22aa2de2
fix(http): add host check (#764)
Signed-off-by: jokemanfire <hu.dingyang@zte.com.cn>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-01 19:28:56 -04:00
Adrián Romero
012210baae
fix: example clients_everything_stdio (#770)
* 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>
2026-04-01 19:02:31 -04:00
lif
cf6988ac7c
fix: exclude local feature from docs.rs build (#782)
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>
2026-04-01 17:40:11 -04:00
Dale Seo
52c93e9508
ci: add semver check job and disable release-plz semver (#776) 2026-03-27 14:47:59 -04:00
Will Pfleger
b74f5ca35b
feat(auth): add StoredCredentials::new() constructor (#778)
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
2026-03-27 14:47:41 -04:00
221 changed files with 54873 additions and 11393 deletions

View file

@ -1,50 +0,0 @@
#!/bin/sh
# Verify commit message follows conventional commit format
# https://www.conventionalcommits.org/
#
# Uses npx commitlint if Node.js is available (same as CI),
# otherwise falls back to basic shell regex validation.
commit_msg_file="$1"
commit_msg=$(cat "$commit_msg_file")
# Skip merge commits
if echo "$commit_msg" | grep -qE "^Merge "; then
exit 0
fi
# Try to use commitlint via npx if Node.js is available
if command -v npx >/dev/null 2>&1; then
# Run commitlint with config-conventional rules (same as CI)
echo "$commit_msg" | npx --yes @commitlint/cli@latest --extends @commitlint/config-conventional
exit $?
fi
# Fallback: basic shell regex validation if Node.js is not available
echo "Note: Node.js not found, using basic commit message validation."
echo " Install Node.js for full commitlint validation (same as CI)."
echo ""
# Conventional commit types from @commitlint/config-conventional
types="build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test"
# Pattern: type(optional-scope): description
# The description must start with lowercase and not end with period
pattern="^($types)(\(.+\))?(!)?: .+"
if ! echo "$commit_msg" | head -1 | grep -qE "$pattern"; then
echo "ERROR: Commit message does not follow conventional commit format."
echo ""
echo "Expected format: <type>(<scope>): <description>"
echo ""
echo "Valid types: build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test"
echo ""
echo "Examples:"
echo " feat: add new feature"
echo " fix(parser): resolve parsing issue"
echo " docs: update README"
echo ""
echo "Your commit message:"
echo " $(head -1 "$commit_msg_file")"
exit 1
fi

View file

@ -16,11 +16,15 @@ updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
interval: "weekly"
labels:
# Mark PRs as CI related change.
- T-CI
open-pull-requests-limit: 3
groups:
codeql-action:
patterns:
- "github/codeql-action*"
commit-message:
prefix: "chore"
include: "scope"
include: "scope"

View file

@ -3,6 +3,9 @@ on:
# Runs workflow when activity on a PR in the workflow's repository occurs.
pull_request_target:
permissions:
contents: read
jobs:
auto-label:
permissions:
@ -20,7 +23,7 @@ jobs:
PR_URL: ${{ github.event.pull_request.html_url }}
steps:
- uses: actions/labeler@v6
- uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7
with:
# Auto-include paths starting with dot (e.g. .github)
dot: true

View file

@ -12,34 +12,35 @@ env:
CARGO_TERM_COLOR: always
ARTIFACT_DIR: release-artifacts
permissions:
contents: read
jobs:
commit-lint:
name: Lint Commit Messages
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '20'
node-version: '22'
- name: Install commitlint
run: |
npm install --save-dev @commitlint/cli @commitlint/config-conventional
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
run: npm install --global @commitlint/cli@20.4.3 @commitlint/config-conventional@20.4.3
- name: Lint commit messages
run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose
run: commitlint --extends @commitlint/config-conventional --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose
fmt:
name: Code Formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust fmt
run: rustup toolchain install nightly --component rustfmt
@ -51,41 +52,209 @@ jobs:
name: Lint with Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Run clippy
- name: Run clippy (all features)
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Run clippy (all features except local)
run: |
FEATURES=$(cargo metadata --no-deps --format-version 1 \
| jq -r '[.packages[] | select(.name == "rmcp") | .features | keys[]
| select(startswith("__") | not)
| select(. != "local")] | join(",")')
cargo clippy --package rmcp --all-targets --no-default-features --features "$FEATURES" -- -D warnings
semver:
name: SemVer Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Install Rust
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Install cargo-semver-checks
uses: taiki-e/install-action@3f74d7c16a4242f1c95561e98edc25d36adb4375 # v2.87.12
with:
tool: cargo-semver-checks
- name: Determine semver release type
run: |
if git log --format=%B \
${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} \
| grep -Eq '(^[A-Za-z0-9_-]+(\([^)]*\))?!:|^BREAKING[ -]CHANGE:)'; then
echo "SEMVER_RELEASE_TYPE=major" >> "$GITHUB_ENV"
else
echo "SEMVER_RELEASE_TYPE=minor" >> "$GITHUB_ENV"
fi
- name: Check rmcp (default features)
run: |
cargo semver-checks \
--package rmcp \
--baseline-rev ${{ github.event.pull_request.base.sha }} \
--release-type "$SEMVER_RELEASE_TYPE" \
--only-explicit-features \
--features default
- name: Check rmcp (all features except local)
run: |
FEATURES=$(cargo metadata --no-deps --format-version 1 \
| jq -r '[.packages[] | select(.name == "rmcp") | .features | keys[]
| select(startswith("__") | not)
| select(. != "local")] | join(",")')
cargo semver-checks \
--package rmcp \
--baseline-rev ${{ github.event.pull_request.base.sha }} \
--release-type "$SEMVER_RELEASE_TYPE" \
--only-explicit-features \
--features "$FEATURES"
public-api:
name: Public API Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
# cargo-public-api builds rustdoc JSON, which requires a nightly toolchain
# to be installed (it does not need to be the default; the tool invokes it
# via `cargo +nightly`).
- name: Install Rust
uses: dtolnay/rust-toolchain@7c8d7d138f5c09cef361f8214cf96882cd029cdb # nightly
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Install cargo-public-api
uses: taiki-e/install-action@3f74d7c16a4242f1c95561e98edc25d36adb4375 # v2.87.12
with:
tool: cargo-public-api
# Mirror the SemVer Check job's release-type detection: a breaking-change
# commit marker (`!:` or `BREAKING CHANGE:`) means a major release (any API
# change is allowed); otherwise a minor release (additions allowed, but
# changed/removed public items are denied). This catches breaking changes
# that cargo-semver-checks cannot yet detect, such as a change to a
# function's return type or a field's type.
# See https://github.com/obi1kenobi/cargo-semver-checks/issues/5
- name: Determine release type and deny flags
run: |
if git log --format=%B \
${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} \
| grep -Eq '(^[A-Za-z0-9_-]+(\([^)]*\))?!:|^BREAKING[ -]CHANGE:)'; then
SEMVER_RELEASE_TYPE=major
else
SEMVER_RELEASE_TYPE=minor
fi
case "$SEMVER_RELEASE_TYPE" in
major) DENY="" ;;
patch) DENY="--deny added --deny changed --deny removed" ;;
*) DENY="--deny changed --deny removed" ;;
esac
echo "SEMVER_RELEASE_TYPE=$SEMVER_RELEASE_TYPE" >> "$GITHUB_ENV"
echo "DENY=$DENY" >> "$GITHUB_ENV"
- name: Check rmcp (default features)
run: |
cargo public-api \
--package rmcp \
-ss \
diff \
$DENY \
--force \
${{ github.event.pull_request.base.sha }}..${{ github.sha }}
- name: Check rmcp (all features except local)
run: |
BASE_SHA=${{ github.event.pull_request.base.sha }}
# `cargo public-api diff` builds both revisions with the same feature
# set, so a feature that exists on only one side (e.g. a feature added
# or removed by this PR) would fail to build the other revision. Diff
# only the features present in BOTH the base and the head; features
# unique to one side are necessarily pure additions/removals, which the
# release-type deny flags already govern.
list_features() {
cargo metadata --no-deps --format-version 1 --manifest-path "$1/Cargo.toml" \
| jq -r '.packages[] | select(.name == "rmcp") | .features | keys[]
| select(startswith("__") | not)
| select(. != "local")'
}
list_features "." | sort -u > "$RUNNER_TEMP/head_features"
git worktree add --detach "$RUNNER_TEMP/rmcp-base" "$BASE_SHA"
list_features "$RUNNER_TEMP/rmcp-base" | sort -u > "$RUNNER_TEMP/base_features"
git worktree remove --force "$RUNNER_TEMP/rmcp-base"
FEATURES=$(comm -12 "$RUNNER_TEMP/head_features" "$RUNNER_TEMP/base_features" | paste -sd, -)
cargo public-api \
--package rmcp \
--features "$FEATURES" \
-ss \
diff \
$DENY \
--force \
"$BASE_SHA"..${{ github.sha }}
spelling:
name: spell check with typos
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Spell Check Repo
uses: crate-ci/typos@master
uses: crate-ci/typos@d43b6c087ac471e2ea7b8af622ff15f05c0c365b # master
msrv:
name: Check MSRV
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Read MSRV from Cargo.toml
run: |
MSRV=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[] | select(.name == "rmcp") | .rust_version')
echo "MSRV=$MSRV" >> "$GITHUB_ENV"
- name: Install MSRV Rust
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # master
with:
toolchain: ${{ env.MSRV }}
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Check default workspace members with MSRV
run: cargo +${{ env.MSRV }} check --all-targets --all-features
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# install nodejs
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '20'
node-version: '22'
- name: Install uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
- name: Set up Python
run: uv python install
@ -93,7 +262,7 @@ jobs:
- name: Create venv for python
run: uv venv
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Run tests
run: cargo test --all-features
@ -102,19 +271,19 @@ jobs:
name: Run Tests (no local feature)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# install nodejs
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '20'
node-version: '22'
- name: Install uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
- name: Set up Python
run: uv python install
@ -122,7 +291,7 @@ jobs:
- name: Create venv for python
run: uv venv
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Run tests without local feature
run: |
@ -136,21 +305,21 @@ jobs:
name: Code Coverage
runs-on: ubuntu-latest
permissions:
contents: write
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# install nodejs
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '20'
node-version: '22'
- name: Install uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
- name: Set up Python
run: uv python install
@ -158,7 +327,7 @@ jobs:
- name: Create venv for python
run: uv venv
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Install cargo-llvm-cov
run: cargo install cargo-llvm-cov
@ -173,19 +342,19 @@ jobs:
name: Example test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# install nodejs
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '20'
node-version: '22'
- name: Install uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
- name: Set up Python
run: uv python install
@ -193,7 +362,7 @@ jobs:
- name: Create venv for python
run: uv venv
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Add target WASI preview 2
run: |
@ -220,7 +389,7 @@ jobs:
if [ -f "$dir/Cargo.toml" ]; then
if [[ "$dir" != *"wasi"* ]]; then
echo "Testing $dir"
cargo test --manifest-path "$dir/Cargo.toml" --all-features
cargo test --manifest-path "$dir/Cargo.toml" --all-features --all-targets
fi
fi
done
@ -229,12 +398,12 @@ jobs:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Install cargo-audit
run: cargo install cargo-audit
@ -246,12 +415,12 @@ jobs:
name: Generate Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust
uses: dtolnay/rust-toolchain@nightly
uses: dtolnay/rust-toolchain@7c8d7d138f5c09cef361f8214cf96882cd029cdb # nightly
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Generate documentation
run: |
@ -272,7 +441,7 @@ jobs:
# This happened recently in the attack on `tj-actions/changed-files`, but
# has happened many times before as well.
- uses: actions/checkout@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Update Rust
run: |

View file

@ -8,6 +8,9 @@ on:
schedule:
- cron: '0 0 * * 1' # Weekly on Monday
permissions:
contents: read
jobs:
analyze:
name: Analyze
@ -21,16 +24,16 @@ jobs:
language: [rust, javascript-typescript, python, actions]
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/codeql-config.yml
- name: Autobuild
uses: github/codeql-action/autobuild@v4
uses: github/codeql-action/autobuild@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0

149
.github/workflows/conformance.yml vendored Normal file
View file

@ -0,0 +1,149 @@
name: Conformance
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
concurrency:
group: conformance-${{ github.ref }}
cancel-in-progress: true
env:
CONFORMANCE_VERSION: "0.2.0-alpha.10"
permissions:
contents: read
jobs:
server:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Build conformance binaries
run: cargo build -p mcp-conformance
- name: Test conformance server
run: cargo test -p mcp-conformance --bin conformance-server
- name: Start conformance server
run: |
PORT=8001 ./target/debug/conformance-server &
echo $! > server.pid
for _ in $(seq 1 30); do
if curl -s -o /dev/null http://127.0.0.1:8001/mcp; then
exit 0
fi
sleep 1
done
echo "conformance server did not become ready" >&2
exit 1
- name: Run 2025-11-25 server suite
run: |
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" server \
--url http://127.0.0.1:8001/mcp \
--suite all \
--spec-version 2025-11-25 \
-o conformance-results/2025-11-25
- name: Run 2026-07-28 server suite
run: |
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" server \
--url http://127.0.0.1:8001/mcp \
--suite all \
--spec-version 2026-07-28 \
-o conformance-results/2026-07-28
- name: Run extension server scenarios (informational)
run: |
scenarios=(
tasks-lifecycle
tasks-capability-negotiation
tasks-wire-fields
tasks-request-state-removal
tasks-mrtr-input
tasks-request-headers
tasks-dispatch-and-envelope
tasks-status-notifications
tasks-required-task-error
tasks-mrtr-composition
)
for scenario in "${scenarios[@]}"; do
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" server \
--url http://127.0.0.1:8001/mcp \
--scenario "$scenario" \
--expected-failures conformance/expected-failures-extensions.yaml \
-o conformance-extension-results
done
- name: Stop conformance server
if: always()
run: kill "$(cat server.pid)" 2>/dev/null || true
- name: Upload results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: conformance-server-results
path: |
conformance-results
conformance-extension-results
client:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Build conformance binaries
run: cargo build -p mcp-conformance
- name: Run 2025-11-25 client suite
run: |
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" client \
--command "$(pwd)/target/debug/conformance-client" \
--suite all \
--spec-version 2025-11-25 \
-o conformance-client-results/2025-11-25
- name: Run 2026-07-28 client suite
run: |
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" client \
--command "$(pwd)/target/debug/conformance-client" \
--suite all \
--spec-version 2026-07-28 \
-o conformance-client-results/2026-07-28
- name: Run extension client suite (informational)
run: |
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" client \
--command "$(pwd)/target/debug/conformance-client" \
--suite extensions \
--expected-failures conformance/expected-failures-extensions.yaml \
-o conformance-client-results/extensions
- name: Upload results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: conformance-client-results
path: conformance-client-results

View file

@ -1,8 +1,6 @@
name: Release-plz
permissions:
pull-requests: write
contents: write
permissions: {}
on:
push:
@ -18,18 +16,24 @@ jobs:
if: ${{ github.repository_owner == 'modelcontextprotocol' }}
permissions:
contents: write
pull-requests: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Run release-plz
uses: release-plz/action@v0.5
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
with:
command: release
toolchain: "1.92"
# Using fork until semver_check_features support is released upstream.
# See: https://github.com/release-plz/release-plz/pull/2757
- name: Install release-plz from fork
run: cargo install --locked --git https://github.com/DaleSeo/release-plz --rev c2ea743bacf9bbd56e344df68219e60b0ad35d04 release-plz
- name: Run release-plz release
run: release-plz release
env:
GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
@ -46,15 +50,20 @@ jobs:
cancel-in-progress: false
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Run release-plz
uses: release-plz/action@v0.5
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
with:
command: release-pr
toolchain: "1.92"
# Using fork until semver_check_features support is released upstream.
# See: https://github.com/release-plz/release-plz/pull/2757
- name: Install release-plz from fork
run: cargo install --locked --git https://github.com/DaleSeo/release-plz --rev c2ea743bacf9bbd56e344df68219e60b0ad35d04 release-plz
- name: Run release-plz release-pr
run: release-plz release-pr
env:
GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

48
.github/workflows/scorecard.yml vendored Normal file
View file

@ -0,0 +1,48 @@
# OpenSSF Scorecard supply-chain security analysis.
name: Scorecard supply-chain security
on:
branch_protection_rule:
schedule:
- cron: '5 23 * * 5'
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
security-events: write
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Run analysis
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
# Publishing only succeeds on the default branch. PR and manual runs
# still validate the analysis and produce SARIF without publishing.
publish_results: ${{ github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
- name: Upload SARIF artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: scorecard-results
path: results.sarif
retention-days: 5
- name: Upload to code scanning
uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
with:
sarif_file: results.sarif

38
.github/workflows/stale.yml vendored Normal file
View file

@ -0,0 +1,38 @@
name: Clean Up Stale PRs and Issues
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
permissions:
contents: read
jobs:
stale:
name: Clean Up
runs-on: ubuntu-24.04
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11
with:
days-before-stale: 60
days-before-close: 14
exempt-issue-labels: keep,security,pinned
exempt-pr-labels: keep,security,pinned
exempt-draft-pr: true
stale-issue-message: >
This issue has had no activity for 60 days and is now marked as stale.
It will be closed in 14 days if there is no further activity. Add the
`keep` label to keep it open.
close-issue-message: >
Closing this issue after 14 days of inactivity since it was marked stale.
Feel free to reopen if it is still relevant.
stale-pr-message: >
This pull request has had no activity for 60 days and is now marked as stale.
It will be closed in 14 days if there is no further activity. Add the
`keep` label to keep it open.
close-pr-message: >
Closing this pull request after 14 days of inactivity since it was marked stale.
Feel free to reopen if you plan to continue the work.

View file

@ -39,7 +39,7 @@ jobs:
TRIAGE_MODEL: ${{ vars.TRIAGE_MODEL || 'gpt-4o-mini' }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install jq
run: sudo apt-get install -y jq

2
.gitignore vendored
View file

@ -27,3 +27,5 @@ __pycache__/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
node_modules/
.DS_Store

View file

@ -4,12 +4,13 @@ default-members = ["crates/rmcp", "crates/rmcp-macros"]
resolver = "2"
[workspace.dependencies]
rmcp = { version = "1.3.0", path = "./crates/rmcp" }
rmcp-macros = { version = "1.3.0", path = "./crates/rmcp-macros" }
rmcp = { version = "3.4.0", path = "./crates/rmcp" }
rmcp-macros = { version = "3.4.0", path = "./crates/rmcp-macros" }
[workspace.package]
edition = "2024"
version = "1.3.0"
rust-version = "1.88"
version = "3.4.0"
authors = ["4t145 <u4t145@163.com>"]
license = "Apache-2.0"
repository = "https://github.com/modelcontextprotocol/rust-sdk/"

65
DEPENDENCY_POLICY.md Normal file
View file

@ -0,0 +1,65 @@
# Dependency Policy
This document describes how the Rust MCP SDK (`rmcp`, `rmcp-macros`) selects,
updates, and maintains its dependencies. It applies to all crates in the workspace.
## Goals
- Keep dependencies current and secure without churn that destabilizes the public API.
- Minimize the dependency footprint we impose on downstream users.
- Make dependency updates reviewable and traceable.
## Choosing dependencies
Before adding a new dependency we consider:
- **Necessity** — whether the functionality is worth an added dependency, or is small
enough to implement directly.
- **Maintenance & trust** — active maintenance, a healthy release history, and a
permissive, compatible license (the workspace is `Apache-2.0`).
- **Footprint** — transitive dependency count and compile-time cost.
- **Optionality** — anything not needed by every user should sit behind a Cargo
feature so downstream builds stay lean. The SDK is heavily feature-gated for this
reason (transports, `auth`, `schemars`, `reqwest`, etc.).
Version requirements are specified with caret (`^`, the Cargo default) constraints so
compatible upgrades are picked up automatically.
## Automated updates
Dependency updates are automated with [Dependabot](https://docs.github.com/en/code-security/dependabot),
configured in [`.github/dependabot.yml`](.github/dependabot.yml):
| Ecosystem | Cadence | PR label |
| -------------- | ------- | ----------------- |
| Cargo crates | Weekly | `T-dependencies` |
| GitHub Actions | Daily | `T-CI` |
Dependabot opens a limited number of PRs at a time (currently 3 per ecosystem) to
keep the review queue manageable. Update PRs run the full CI suite (build, format,
clippy, tests, and conformance) and must be green before merge.
## Review and merge
- Patch and minor updates that pass CI are reviewed and merged promptly by maintainers.
- Major (breaking) updates are evaluated individually; they may require code changes
and are scheduled deliberately rather than merged automatically.
- Any dependency change that affects the **public API** (for example a type re-exported
from a dependency) is treated as a potential breaking change and follows
[`VERSIONING.md`](VERSIONING.md).
## Security updates
- Security fixes take priority over routine updates and are expedited.
- Vulnerabilities in the SDK itself are handled per [`SECURITY.md`](SECURITY.md) via
GitHub's private Security Advisory process.
- Advisories affecting our dependencies (e.g. via the [RustSec Advisory Database](https://rustsec.org/))
are addressed as soon as a fixed version is available; if a released version of the
SDK is impacted, a patched release is published and, where warranted, the affected
versions are yanked from crates.io.
## Minimum Supported Rust Version
Dependency upgrades must not raise the SDK's declared MSRV (`rust-version` in
`Cargo.toml`) without an explicit, documented MSRV bump — which is itself treated as a
breaking change per [`VERSIONING.md`](VERSIONING.md).

1348
README.md

File diff suppressed because it is too large Load diff

View file

@ -1,71 +1,112 @@
# RMCP Roadmap
This roadmap tracks the path to SEP-1730 Tier 1 for the Rust MCP SDK.
This roadmap tracks the path to [SEP-1730](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1730) Tier 1 for the Rust MCP SDK.
Server conformance: 87.5% (28/32) · Client conformance: 80.0% (16/20)
**Status: all SEP-1730 Tier 1 requirements are met.** Conformance is
100% across every date-versioned suite, a stable ≥1.0.0 release has shipped
([Releases](https://github.com/modelcontextprotocol/rust-sdk/releases)),
issue triage and critical-bug (P0) resolution are within the Tier 1 SLAs, the
governance documents (`VERSIONING.md`, `DEPENDENCY_POLICY.md`, this `ROADMAP.md`) are
published, and all 48 non-experimental features are documented with examples. This
document now serves as the ongoing tracker for spec conformance and SDK health.
| SEP-1730 Tier 1 requirement | Status | Evidence |
| --------------------------------------------------- | ------ | -------- |
| Server conformance 100% (date-versioned) | ✅ | 30/30 — see below |
| Client conformance 100% (date-versioned) | ✅ | 39/39 scored — see below |
| Issue triage ≥90% within 2 business days | ✅ | 95.2% (20/21) |
| All P0 bugs resolved within 7 days | ✅ | 0 open; last P0 (#741) resolved in 3 days |
| Stable release ≥1.0.0 (no pre-release suffix) | ✅ | [latest release](https://github.com/modelcontextprotocol/rust-sdk/releases/latest) (see tooling note below) |
| Clear versioning + breaking-change policy | ✅ | [`VERSIONING.md`](VERSIONING.md) |
| All non-experimental features documented w/ examples| ✅ | 48/48 in [`README.md`](README.md) |
| Published dependency update policy | ✅ | [`DEPENDENCY_POLICY.md`](DEPENDENCY_POLICY.md) + [`.github/dependabot.yml`](.github/dependabot.yml) |
| Published roadmap tracking spec components | ✅ | this document |
| Suite (date-versioned) | Server | Client |
| ---------------------- | ------------- | ------------- |
| 2025-11-25 | 100% (30/30) | 100% |
| 2026-07-28 | 100% (30/30) | 100% |
Only date-versioned scenarios count toward SDK tiering. `draft` (2026-07-28 draft)
and `extension` scenarios are informational and reported separately below.
> **Tooling note — `stable_release`:** the SEP-1730 `tier-check` CLI may report
> `stable_release` as failing because it does not parse the workspace tag prefix
> `rmcp-v` (as in `rmcp-vX.Y.Z`). The published releases are genuine stable,
> non-pre-release versions ≥1.0.0
> ([Releases](https://github.com/modelcontextprotocol/rust-sdk/releases)); the
> flag is a tooling artifact, not an unmet requirement.
---
## Tier 2 → Tier 1
## Conformance
### Conformance
Per-scenario status for the current spec is tracked in the epic issue
[#977 — Tracking: 2026-07-28 spec conformance](https://github.com/modelcontextprotocol/rust-sdk/issues/977),
under the [`2026-07-28 spec` milestone](https://github.com/modelcontextprotocol/rust-sdk/milestone/3).
#### Server (87.5% → 100%)
CI (`.github/workflows/conformance.yml`) runs the full `2025-11-25` and `2026-07-28`
server and client suites on every push and PR. Both suites are fully green.
- [ ] Fix `prompts-get-with-args` — prompt argument handling returns incorrect result (arg1/arg2 not substituted)
- [ ] Fix `prompts-get-embedded-resource` — embedded resource content in prompt responses (invalid content union)
- [ ] Fix `elicitation-sep1330-enums` — enum inference handling per SEP-1330 (missing enumNames for legacy titled enum)
- [ ] Fix `dns-rebinding-protection` — validate `Host` / `Origin` headers on Streamable HTTP transport (accepts invalid headers with 200)
### Informational (not scored for tiering)
#### Client (80.0% → 100%)
Extension-tagged scenarios are excluded by `--spec-version` filters, so CI runs them
in separate steps against `conformance/expected-failures-extensions.yaml`:
- [ ] Fix `auth/metadata-var3` — AS metadata discovery variant 3 (no authorization support detected)
- [ ] Fix `auth/scope-from-www-authenticate` — use scope parameter from WWW-Authenticate header on 403 insufficient_scope
- [ ] Fix `auth/scope-step-up` — handle 403 `insufficient_scope` and re-authorize with upgraded scopes
- [ ] Fix `auth/2025-03-26-oauth-endpoint-fallback` — legacy OAuth endpoint fallback for pre-2025-06-18 servers (no authorization support detected)
| Scenario | Tag | Status |
| --------------------------------------- | ---------- | ------ |
| `auth/client-credentials-basic` | extension | ✅ Pass |
| `auth/client-credentials-jwt` | extension | ✅ Pass |
| `auth/enterprise-managed-authorization` | extension | ❌ Expected failure (not implemented by the conformance client) |
| `auth/wif-jwt-bearer` | 2026-07-28 draft | ❌ Expected failure (WIF / SEP-1933, draft) |
| `tasks-*` (SEP-2663) | extension | ❌ 9 expected failures · ⏭️ 1 upstream-skipped |
### Governance & Policy
### Spec features without conformance scenarios
- [ ] Create `VERSIONING.md` — document semver scheme, what constitutes a breaking change, and how breaking changes are communicated
Conformance does not cover the entire spec surface. Remaining feature work tracked via
the milestone:
### Documentation (26/48 → 48/48 features with prose + examples)
#### Undocumented features (14)
- [ ] Tools — image results
- [ ] Tools — audio results
- [ ] Tools — embedded resources
- [ ] Prompts — embedded resources
- [ ] Prompts — image content
- [ ] Elicitation — URL mode
- [ ] Elicitation — default values
- [ ] Elicitation — complete notification
- [ ] Ping
- [ ] SSE transport — legacy (client)
- [ ] SSE transport — legacy (server)
- [ ] Pagination
- [ ] Protocol version negotiation
- [ ] JSON Schema 2020-12 support *(upgrade from partial)*
#### Partially documented features (7)
- [ ] Tools — error handling *(add dedicated prose + example)*
- [ ] Resources — reading binary *(add dedicated example)*
- [ ] Elicitation — form mode *(add prose docs, not just example README)*
- [ ] Elicitation — schema validation *(add prose docs)*
- [ ] Elicitation — enum values *(add prose docs)*
- [ ] Capability negotiation *(add dedicated prose explaining the builder API)*
- [ ] Protocol version negotiation *(document version negotiation behavior)*
- SEP-2567 sessionless MCP via explicit state handles (#870)
- SEP-2260 server requests must associate with a client request (#873)
- SEP-2549 follow-up: client-side TTL-honoring cache (#974)
---
## Informational (not scored for tiering)
## Completed
These draft/extension scenarios are tracked but do not count toward tier advancement:
### Tier 1 requirements
| Scenario | Tag | Status |
|---|---|---|
| `auth/resource-mismatch` | draft | ❌ Failed |
| `auth/client-credentials-jwt` | extension | ❌ Failed — JWT `aud` claim verification error |
| `auth/client-credentials-basic` | extension | ✅ Passed |
| `auth/cross-app-access-complete-flow` | extension | ❌ Failed — sends `authorization_code` grant instead of `jwt-bearer` |
- [x] **v3.0.0 stable released** (2026-07-28) — MRTR, SEP-2549 cache hints, SEP-2243
standard headers, SEP-2575 stateless MCP, and SEP-2106 relaxations
- [x] 2025-11-25 server conformance 100% (30/30)
- [x] 2025-11-25 client conformance 100%
- [x] 2026-07-28 server conformance 100% (30/30 dated)
- [x] 2026-07-28 client conformance 100% (dated)
- [x] Issue triage ≥90% within 2 business days (95.2%, 20/21) with the full SEP-1730
label taxonomy (bug, enhancement, question, needs confirmation, needs repro,
ready for work, good first issue, help wanted, P0–P3)
- [x] All P0 bugs resolved within 7 days (0 open; #741 resolved in 3 days). #815 was
reclassified from `P0` to `T-security` — it was a CVE/advisory-coordination task
for an already-shipped fix (PR #764, released in v1.4.0), not a critical-bug fix
- [x] `VERSIONING.md` — semver scheme, breaking-change definition, and communication policy
- [x] `DEPENDENCY_POLICY.md` — published dependency update policy (with `.github/dependabot.yml`)
- [x] `SECURITY.md` and Dependabot configuration
- [x] All 48 non-experimental features documented with examples in the README
(closed the last 6 gaps on 2026-07-30: audio results, resource-template reading,
resource-argument completion, ping, and the legacy HTTP+SSE non-goal writeup)
### Spec implementation
- [x] SEP-2322 MRTR (server scenarios + `sep-2322-client-request-state`)
- [x] SEP-2575 Make MCP Stateless (`server-stateless`)
- [x] SEP-2164 resource not found
- [x] SEP-2549 cache hints (`caching`)
- [x] SEP-2243 HTTP standardization (`http-header-validation`, standard headers)
- [x] DNS rebinding protection
---
## Nice-to-have (scorecard hygiene, not required for Tier 1)
- [ ] Add a top-level `CHANGELOG.md` (release notes are currently managed by release-plz).
- [ ] Add a top-level `CONTRIBUTING.md` (contributor docs currently live at `docs/CONTRIBUTE.MD`).

66
VERSIONING.md Normal file
View file

@ -0,0 +1,66 @@
# Versioning Policy
`rmcp` and `rmcp-macros` follow [Semantic Versioning 2.0.0](https://semver.org/),
the standard versioning scheme for the Rust/Cargo ecosystem. Given a version
`MAJOR.MINOR.PATCH`:
- **MAJOR** — incremented for breaking changes to the public API.
- **MINOR** — incremented for new, backwards-compatible functionality.
- **PATCH** — incremented for backwards-compatible bug fixes.
The `rmcp` and `rmcp-macros` crates are versioned together and released with the
same version number.
## What counts as a breaking change
We treat a change as breaking if it would cause code that compiled against the
previous release to fail to compile, or to change behavior in a way callers could
not reasonably anticipate. This follows the Cargo SemVer guidelines
([The Cargo Book — SemVer Compatibility](https://doc.rust-lang.org/cargo/reference/semver.html)).
Examples include:
- Removing or renaming a public item (function, type, trait, module, variant, field).
- Changing a public function signature, trait method, or trait bound.
- Adding a required method to a public trait, or a field to a struct that callers
construct directly. (Public structs and enums are marked `#[non_exhaustive]` where
practical so that additive changes remain non-breaking.)
- Raising the Minimum Supported Rust Version (MSRV) — see below.
Additive changes — new functions, new types, new enum variants on
`#[non_exhaustive]` enums, new optional Cargo features — are **not** breaking and
ship in MINOR releases.
### Cargo features
The public API is feature-gated. SemVer compatibility is evaluated against the
crate's **default features** and all features except `local`.
## Pre-1.0 crates in the workspace
The core crates (`rmcp`, `rmcp-macros`) are `>= 1.0.0` and follow the rules above.
Any workspace crate still in `0.x` follows Cargo's pre-1.0 convention, where the
`MINOR` version acts as the breaking-change signal (`0.MINOR.PATCH`).
## Minimum Supported Rust Version (MSRV)
The MSRV is declared via `rust-version` in `Cargo.toml`. Raising the MSRV is
considered a breaking change and is only done in a MINOR or MAJOR release with a
note in the release notes.
## How breaking changes are communicated
- Release automation is handled by [release-plz](https://release-plz.dev/), which
derives version bumps from [Conventional Commits](https://www.conventionalcommits.org/)
(`feat!:` / `fix!:` and `BREAKING CHANGE:` footers trigger a MAJOR bump) and runs
`cargo-semver-checks` to catch unintended API breaks.
- Every release is published as a tagged [GitHub Release](https://github.com/modelcontextprotocol/rust-sdk/releases)
with generated notes.
- Significant migrations (for example the 2.x → 3.x upgrade) are accompanied by a
migration guide linked from the release notes and the README.
## Yanking
A published version that is later found to be broken or to contain a security issue
may be [yanked](https://doc.rust-lang.org/cargo/commands/cargo-yank.html) from
crates.io. Yanking prevents new dependents from selecting the version but does not
delete it; a fixed PATCH release is published alongside.

View file

@ -3,5 +3,6 @@
"redirect_uris": ["http://127.0.0.1:8080/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
"token_endpoint_auth_method": "none",
"application_type": "native"
}

View file

@ -1,3 +1,2 @@
msrv = "1.85"
too-many-arguments-threshold = 10
check-private-items = false

View file

@ -18,6 +18,9 @@ rmcp = { path = "../crates/rmcp", features = [
"client",
"elicitation",
"auth",
"auth-client-credentials-jwt",
"auth-enterprise-managed",
"request-state",
"transport-streamable-http-server",
"transport-streamable-http-client-reqwest",
] }
@ -29,8 +32,7 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
axum = { version = "0.8", features = ["macros"] }
anyhow = "1"
oauth2 = { version = "5.0", default-features = false }
reqwest = { version = "0.13", features = ["json"] }
urlencoding = "2"
url = "2"
p256 = { version = "0.13", features = ["ecdsa"] }
base64 = "0.22"

View file

@ -0,0 +1,26 @@
# Known failures for informational extension scenarios in
# @modelcontextprotocol/conformance CONFORMANCE_VERSION.
#
# Extensions are not selected by a `--spec-version` run and do not count toward
# SDK tiering. CI runs them separately so a green versioned-spec suite does not
# hide extension gaps.
#
# This is a strict baseline:
# - an unlisted failure fails the build
# - a listed scenario that starts passing fails the build as a stale entry
#
# When bumping CONFORMANCE_VERSION, review the available extension and
# pending scenarios and update this file deliberately.
# The SEP-2663 Tasks Extension server scenarios (tracked in #868) all pass and
# were removed from this baseline. `tasks-status-notifications` remains
# upstream-skipped pending the subscriptions/listen rewrite; CI will fail if it
# becomes active but does not pass.
server: []
client:
# Informational OAuth extension scenarios.
- auth/enterprise-managed-authorization
- auth/dpop
- auth/dpop-nonce
- auth/wif-jwt-bearer

Some files were not shown because too many files have changed in this diff Show more