Commit graph

579 commits

Author SHA1 Message Date
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) rmcp-macros-v2.2.0 rmcp-v2.2.0
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) rmcp-macros-v2.1.0 rmcp-v2.1.0
* 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