Skip to content

Commit 7a9f292

Browse files
committed
Merge remote-tracking branch 'upstream/main'
# Conflicts: # crates/rmcp/CHANGELOG.md
2 parents 304a662 + 2522036 commit 7a9f292

15 files changed

Lines changed: 933 additions & 178 deletions

.github/workflows/ci.yml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
runs-on: ubuntu-latest
1919
if: github.event_name == 'pull_request'
2020
steps:
21-
- uses: actions/checkout@v6
21+
- uses: actions/checkout@v7
2222
with:
2323
fetch-depth: 0
2424

@@ -39,7 +39,7 @@ jobs:
3939
name: Code Formatting
4040
runs-on: ubuntu-latest
4141
steps:
42-
- uses: actions/checkout@v6
42+
- uses: actions/checkout@v7
4343

4444
- name: Install Rust fmt
4545
run: rustup toolchain install nightly --component rustfmt
@@ -51,7 +51,7 @@ jobs:
5151
name: Lint with Clippy
5252
runs-on: ubuntu-latest
5353
steps:
54-
- uses: actions/checkout@v6
54+
- uses: actions/checkout@v7
5555

5656
- name: Install Rust
5757
uses: dtolnay/rust-toolchain@stable
@@ -66,7 +66,7 @@ jobs:
6666
runs-on: ubuntu-latest
6767
if: github.event_name == 'pull_request'
6868
steps:
69-
- uses: actions/checkout@v6
69+
- uses: actions/checkout@v7
7070
with:
7171
fetch-depth: 0
7272

@@ -106,15 +106,15 @@ jobs:
106106
name: spell check with typos
107107
runs-on: ubuntu-latest
108108
steps:
109-
- uses: actions/checkout@v6
109+
- uses: actions/checkout@v7
110110
- name: Spell Check Repo
111111
uses: crate-ci/typos@master
112112

113113
test:
114114
name: Run Tests
115115
runs-on: ubuntu-latest
116116
steps:
117-
- uses: actions/checkout@v6
117+
- uses: actions/checkout@v7
118118

119119
# install nodejs
120120
- name: Setup Node.js
@@ -143,7 +143,7 @@ jobs:
143143
name: Run Tests (no local feature)
144144
runs-on: ubuntu-latest
145145
steps:
146-
- uses: actions/checkout@v6
146+
- uses: actions/checkout@v7
147147

148148
# install nodejs
149149
- name: Setup Node.js
@@ -179,7 +179,7 @@ jobs:
179179
permissions:
180180
contents: write
181181
steps:
182-
- uses: actions/checkout@v6
182+
- uses: actions/checkout@v7
183183

184184
# install nodejs
185185
- name: Setup Node.js
@@ -214,7 +214,7 @@ jobs:
214214
name: Example test
215215
runs-on: ubuntu-latest
216216
steps:
217-
- uses: actions/checkout@v6
217+
- uses: actions/checkout@v7
218218

219219
# install nodejs
220220
- name: Setup Node.js
@@ -270,7 +270,7 @@ jobs:
270270
name: Security Audit
271271
runs-on: ubuntu-latest
272272
steps:
273-
- uses: actions/checkout@v6
273+
- uses: actions/checkout@v7
274274

275275
- name: Install Rust
276276
uses: dtolnay/rust-toolchain@stable
@@ -287,7 +287,7 @@ jobs:
287287
name: Generate Documentation
288288
runs-on: ubuntu-latest
289289
steps:
290-
- uses: actions/checkout@v6
290+
- uses: actions/checkout@v7
291291

292292
- name: Install Rust
293293
uses: dtolnay/rust-toolchain@nightly
@@ -313,7 +313,7 @@ jobs:
313313
# This happened recently in the attack on `tj-actions/changed-files`, but
314314
# has happened many times before as well.
315315

316-
- uses: actions/checkout@v6
316+
- uses: actions/checkout@v7
317317

318318
- name: Update Rust
319319
run: |

.github/workflows/codeql.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
language: [rust, javascript-typescript, python, actions]
2222
steps:
2323
- name: Checkout repository
24-
uses: actions/checkout@v6
24+
uses: actions/checkout@v7
2525

2626
- name: Initialize CodeQL
2727
uses: github/codeql-action/init@v4

.github/workflows/release-plz.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
contents: write
2121
steps:
2222
- name: Checkout repository
23-
uses: actions/checkout@v6
23+
uses: actions/checkout@v7
2424
with:
2525
fetch-depth: 0
2626
- name: Install Rust toolchain
@@ -51,7 +51,7 @@ jobs:
5151
cancel-in-progress: false
5252
steps:
5353
- name: Checkout repository
54-
uses: actions/checkout@v6
54+
uses: actions/checkout@v7
5555
with:
5656
fetch-depth: 0
5757
- name: Install Rust toolchain

.github/workflows/triage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
TRIAGE_MODEL: ${{ vars.TRIAGE_MODEL || 'gpt-4o-mini' }}
4040

4141
steps:
42-
- uses: actions/checkout@v6
42+
- uses: actions/checkout@v7
4343

4444
- name: Install jq
4545
run: sudo apt-get install -y jq

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ default-members = ["crates/rmcp", "crates/rmcp-macros"]
44
resolver = "2"
55

66
[workspace.dependencies]
7-
rmcp = { version = "1.7.0", path = "./crates/rmcp" }
8-
rmcp-macros = { version = "1.7.0", path = "./crates/rmcp-macros" }
7+
rmcp = { version = "1.8.0", path = "./crates/rmcp" }
8+
rmcp-macros = { version = "1.8.0", path = "./crates/rmcp-macros" }
99

1010
[workspace.package]
1111
edition = "2024"
12-
version = "1.7.0"
12+
version = "1.8.0"
1313
authors = ["4t145 <u4t145@163.com>"]
1414
license = "Apache-2.0"
1515
repository = "https://github.com/modelcontextprotocol/rust-sdk/"

crates/rmcp-macros/CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.8.0](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-macros-v1.7.0...rmcp-macros-v1.8.0) - 2026-06-22
11+
12+
### Added
13+
14+
- deprecate roots, sampling, and logging (SEP-2577) ([#884](https://github.com/modelcontextprotocol/rust-sdk/pull/884))
15+
16+
### Fixed
17+
18+
- strip and validate tool outputSchema and inputSchema ([#860](https://github.com/modelcontextprotocol/rust-sdk/pull/860))
19+
- remove unnecessary fields from tools' inputSchema ([#856](https://github.com/modelcontextprotocol/rust-sdk/pull/856))
20+
21+
### Other
22+
23+
- refine mcpmate listing copy ([#885](https://github.com/modelcontextprotocol/rust-sdk/pull/885))
24+
- added jilebi-mcp to the list of built with rmcp ([#861](https://github.com/modelcontextprotocol/rust-sdk/pull/861))
25+
1026
## [1.7.0](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-macros-v1.6.0...rmcp-macros-v1.7.0) - 2026-05-13
1127

1228
### Added

crates/rmcp/CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- *(server)* Normalise bare boolean subschemas (`true` / `false`) in generated `inputSchema`, `outputSchema`, and `ElicitationSchema` to their object-form equivalents (`{}` / `{"not": {}}`) before serialisation. Triggered by `serde_json::Value` field expansions, `Vec<serde_json::Value>`, `BTreeMap<String, serde_json::Value>`, and `#[serde(deny_unknown_fields)]`. Claude Code's `LocalMcpServerManager` schema walker throws `TypeError: Cannot use 'in' operator to search for 'properties' in <bool>` on bare booleans, silently dropping the entire server's tool list ([anthropics/claude-code#50194](https://github.com/anthropics/claude-code/issues/50194), [#25081](https://github.com/anthropics/claude-code/issues/25081)). The fix is unconditional — boolean subschemas are spec-legal per JSON Schema 2020-12 §4.3.2, but real-world MCP clients can't always handle them, so emit object form universally.
1313

14+
## [1.8.0](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-v1.7.0...rmcp-v1.8.0) - 2026-06-22
15+
16+
### Added
17+
18+
- standardize resource-not-found error code (SEP-2164) ([#899](https://github.com/modelcontextprotocol/rust-sdk/pull/899))
19+
- validate OAuth authorization response issuer ([#896](https://github.com/modelcontextprotocol/rust-sdk/pull/896))
20+
- specify OIDC application_type during dynamic client registration (SEP-837) ([#883](https://github.com/modelcontextprotocol/rust-sdk/pull/883))
21+
- deprecate roots, sampling, and logging (SEP-2577) ([#884](https://github.com/modelcontextprotocol/rust-sdk/pull/884))
22+
23+
### Fixed
24+
25+
- *(auth)* preserve configured reqwest client ([#917](https://github.com/modelcontextprotocol/rust-sdk/pull/917))
26+
- *(auth)* align OAuth metadata discovery ordering ([#887](https://github.com/modelcontextprotocol/rust-sdk/pull/887))
27+
- align progress timeout token ([#909](https://github.com/modelcontextprotocol/rust-sdk/pull/909))
28+
- *(elicitation)* preserve enumNames through ElicitationSchema serde round-trip ([#905](https://github.com/modelcontextprotocol/rust-sdk/pull/905))
29+
- return tool errors for invalid arguments ([#894](https://github.com/modelcontextprotocol/rust-sdk/pull/894))
30+
- *(auth)* apply offline_access to reauth paths ([#897](https://github.com/modelcontextprotocol/rust-sdk/pull/897))
31+
- update peer info on duplicate initialize ([#862](https://github.com/modelcontextprotocol/rust-sdk/pull/862))
32+
- strip and validate tool outputSchema and inputSchema ([#860](https://github.com/modelcontextprotocol/rust-sdk/pull/860))
33+
- remove unnecessary fields from tools' inputSchema ([#856](https://github.com/modelcontextprotocol/rust-sdk/pull/856))
34+
- reject init header/body version mismatch ([#853](https://github.com/modelcontextprotocol/rust-sdk/pull/853))
35+
- align protocol version negotiation ([#855](https://github.com/modelcontextprotocol/rust-sdk/pull/855))
36+
- accept 200 with empty body in response to notifications in addition to 202 ([#849](https://github.com/modelcontextprotocol/rust-sdk/pull/849))
37+
38+
### Other
39+
40+
- Allow custom HTTP clients for OAuth ([#908](https://github.com/modelcontextprotocol/rust-sdk/pull/908))
41+
- Add progress-aware request timeout reset ([#858](https://github.com/modelcontextprotocol/rust-sdk/pull/858))
42+
- *(server)* document Err vs Ok(CallToolResult::error) visibility contract on ServerHandler::call_tool ([#854](https://github.com/modelcontextprotocol/rust-sdk/pull/854))
43+
- refine mcpmate listing copy ([#885](https://github.com/modelcontextprotocol/rust-sdk/pull/885))
44+
- added jilebi-mcp to the list of built with rmcp ([#861](https://github.com/modelcontextprotocol/rust-sdk/pull/861))
45+
1446
## [1.7.0](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-v1.6.0...rmcp-v1.7.0) - 2026-05-13
1547

1648
### Added

crates/rmcp/src/model/elicitation_schema.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,12 +544,13 @@ pub struct LegacyEnumSchema {
544544
pub description: Option<Cow<'static, str>>,
545545
#[serde(rename = "enum")]
546546
pub enum_: Vec<String>,
547+
#[serde(skip_serializing_if = "Option::is_none")]
547548
pub enum_names: Option<Vec<String>>,
548549
}
549550

550551
/// Untitled single-select
551552
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
552-
#[serde(rename_all = "camelCase")]
553+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
553554
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
554555
#[non_exhaustive]
555556
pub struct UntitledSingleSelectEnumSchema {
@@ -1753,6 +1754,45 @@ mod tests {
17531754
Ok(())
17541755
}
17551756

1757+
#[test]
1758+
fn test_legacy_enum_schema_roundtrip_preserves_enum_names() -> anyhow::Result<()> {
1759+
// Regression test for: legacy enum payload with `enumNames` was silently
1760+
// deserialized as `UntitledSingleSelectEnumSchema` (which has no `enumNames`
1761+
// field), causing the array to be dropped on re-serialization.
1762+
let input = serde_json::json!({
1763+
"type": "object",
1764+
"properties": {
1765+
"choice": {
1766+
"type": "string",
1767+
"enum": ["opt1", "opt2", "opt3"],
1768+
"enumNames": ["Option One", "Option Two", "Option Three"]
1769+
}
1770+
}
1771+
});
1772+
let schema: ElicitationSchema = serde_json::from_value(input.clone())?;
1773+
let output = serde_json::to_value(&schema)?;
1774+
assert_eq!(
1775+
output["properties"]["choice"]["enumNames"],
1776+
serde_json::json!(["Option One", "Option Two", "Option Three"]),
1777+
);
1778+
Ok(())
1779+
}
1780+
1781+
#[test]
1782+
fn test_legacy_enum_schema_no_enum_names_omits_field() -> anyhow::Result<()> {
1783+
// `LegacyEnumSchema` with `enum_names: None` must not serialize `"enumNames": null`.
1784+
let schema = EnumSchema::Legacy(LegacyEnumSchema {
1785+
type_: StringTypeConst,
1786+
title: None,
1787+
description: None,
1788+
enum_: vec!["a".to_string(), "b".to_string()],
1789+
enum_names: None,
1790+
});
1791+
let json = serde_json::to_value(&schema)?;
1792+
assert!(!json.as_object().unwrap().contains_key("enumNames"));
1793+
Ok(())
1794+
}
1795+
17561796
#[test]
17571797
fn test_enum_schema_titled_multi_select_serialization() -> anyhow::Result<()> {
17581798
let schema = EnumSchema::builder(vec!["US".to_string(), "UK".to_string()])

crates/rmcp/src/service.rs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,6 @@ pub struct RequestHandle<R: ServiceRole> {
348348
pub peer: Peer<R>,
349349
pub id: RequestId,
350350
pub progress_token: ProgressToken,
351-
progress_timeout_watchers: ProgressTimeoutWatchers,
352351
progress_reset_rx: Option<mpsc::Receiver<()>>,
353352
}
354353

@@ -416,8 +415,10 @@ impl<R: ServiceRole> RequestHandle<R> {
416415
max_total_timeout: Option<Duration>,
417416
reset_timeout_on_progress: bool,
418417
) -> Result<R::PeerResp, ServiceError> {
419-
let mut idle_sleep = timeout.map(tokio::time::sleep).map(Box::pin);
420-
let mut max_total_sleep = max_total_timeout.map(tokio::time::sleep).map(Box::pin);
418+
let mut idle_sleep =
419+
timeout.map(|timeout| (timeout, Box::pin(tokio::time::sleep(timeout))));
420+
let mut max_total_sleep =
421+
max_total_timeout.map(|timeout| (timeout, Box::pin(tokio::time::sleep(timeout))));
421422

422423
loop {
423424
tokio::select! {
@@ -427,32 +428,34 @@ impl<R: ServiceRole> RequestHandle<R> {
427428
return response.map_err(|_e| ServiceError::TransportClosed)?;
428429
}
429430
_ = async {
430-
if let Some(sleep) = idle_sleep.as_mut() {
431+
if let Some((_, sleep)) = idle_sleep.as_mut() {
431432
sleep.as_mut().await;
432433
}
433434
}, if idle_sleep.is_some() => {
434-
let timeout = timeout.expect("idle timeout exists when idle sleep exists");
435-
self.send_timeout_cancel_notification(Self::REQUEST_TIMEOUT_REASON).await;
436-
return Err(ServiceError::Timeout { timeout });
435+
if let Some((timeout, _)) = idle_sleep.as_ref() {
436+
self.send_timeout_cancel_notification(Self::REQUEST_TIMEOUT_REASON).await;
437+
return Err(ServiceError::Timeout { timeout: *timeout });
438+
}
437439
}
438440
_ = async {
439-
if let Some(sleep) = max_total_sleep.as_mut() {
441+
if let Some((_, sleep)) = max_total_sleep.as_mut() {
440442
sleep.as_mut().await;
441443
}
442444
}, if max_total_sleep.is_some() => {
443-
let timeout = max_total_timeout.expect("max total timeout exists when max total sleep exists");
444-
self.send_timeout_cancel_notification(Self::REQUEST_MAX_TOTAL_TIMEOUT_REASON).await;
445-
return Err(ServiceError::Timeout { timeout });
445+
if let Some((timeout, _)) = max_total_sleep.as_ref() {
446+
self.send_timeout_cancel_notification(Self::REQUEST_MAX_TOTAL_TIMEOUT_REASON).await;
447+
return Err(ServiceError::Timeout { timeout: *timeout });
448+
}
446449
}
447450
progress = async {
448451
match self.progress_reset_rx.as_mut() {
449452
Some(rx) => rx.recv().await,
450453
None => None,
451454
}
452-
}, if reset_timeout_on_progress && timeout.is_some() && self.progress_reset_rx.is_some() => {
455+
}, if reset_timeout_on_progress && idle_sleep.is_some() && self.progress_reset_rx.is_some() => {
453456
if progress.is_some() {
454-
if let (Some(timeout), Some(sleep)) = (timeout, idle_sleep.as_mut()) {
455-
sleep.as_mut().reset(tokio::time::Instant::now() + timeout);
457+
if let Some((timeout, sleep)) = idle_sleep.as_mut() {
458+
sleep.as_mut().reset(tokio::time::Instant::now() + *timeout);
456459
}
457460
}
458461
}
@@ -463,7 +466,7 @@ impl<R: ServiceRole> RequestHandle<R> {
463466
/// Cancel this request
464467
pub async fn cancel(self, reason: Option<String>) -> Result<(), ServiceError> {
465468
Self::cleanup_progress_timeout_watcher(
466-
&self.progress_timeout_watchers,
469+
&self.peer.progress_timeout_watchers,
467470
&self.progress_token,
468471
self.progress_reset_rx.is_some(),
469472
)
@@ -617,12 +620,12 @@ impl<R: ServiceRole> Peer<R> {
617620
) -> Result<RequestHandle<R>, ServiceError> {
618621
let id = self.request_id_provider.next_request_id();
619622
let progress_token = self.progress_token_provider.next_progress_token();
620-
request
621-
.get_meta_mut()
622-
.set_progress_token(progress_token.clone());
623623
if let Some(meta) = options.meta.clone() {
624624
request.get_meta_mut().extend(meta);
625625
}
626+
request
627+
.get_meta_mut()
628+
.set_progress_token(progress_token.clone());
626629
let (responder, receiver) = tokio::sync::oneshot::channel();
627630
let progress_reset_rx = if options.reset_timeout_on_progress && options.timeout.is_some() {
628631
let (sender, receiver) = mpsc::channel(1);
@@ -658,7 +661,6 @@ impl<R: ServiceRole> Peer<R> {
658661
progress_token,
659662
options,
660663
peer: self.clone(),
661-
progress_timeout_watchers: self.progress_timeout_watchers.clone(),
662664
progress_reset_rx,
663665
})
664666
}

crates/rmcp/src/transport.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,9 @@ pub use auth::JwtSigningAlgorithm;
101101
pub use auth::{
102102
AuthClient, AuthError, AuthorizationManager, AuthorizationSession, AuthorizedHttpClient,
103103
ClientCredentialsConfig, CredentialStore, EXTENSION_OAUTH_CLIENT_CREDENTIALS,
104-
InMemoryCredentialStore, InMemoryStateStore, ScopeUpgradeConfig, StateStore,
105-
StoredAuthorizationState, StoredCredentials, WWWAuthenticateParams,
104+
InMemoryCredentialStore, InMemoryStateStore, OAuthHttpClient, OAuthHttpClientError,
105+
OAuthHttpClientFuture, OAuthHttpRedirectPolicy, OAuthHttpRequest, ScopeUpgradeConfig,
106+
StateStore, StoredAuthorizationState, StoredCredentials, WWWAuthenticateParams,
106107
};
107108

108109
// #[cfg(feature = "transport-ws")]

0 commit comments

Comments
 (0)