Skip to content

Commit 9105ba7

Browse files
dsp-antclaude
andauthored
fix: change JSON-RPC request ID type from u32 to i64 (#416)
The JSON-RPC 2.0 specification allows the ID field to be any JSON number, including negative integers and large values. The previous u32 implementation was limited to 0-4,294,967,295 and couldn't handle negative IDs. Changes: - Changed NumberOrString::Number from u32 to i64 to support full JSON number range - Updated deserializer to handle both signed and unsigned integers - Modified AtomicU32Provider to use AtomicU64 internally with i64 conversion - Fixed progress token handling in meta.rs for i64 values - Added comprehensive test for negative and large request IDs This ensures full compliance with the JSON-RPC 2.0 specification. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude <[email protected]>
1 parent ccbd7f0 commit 9105ba7

File tree

3 files changed

+114
-14
lines changed

3 files changed

+114
-14
lines changed

crates/rmcp/src/model.rs

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ impl<'de> Deserialize<'de> for ProtocolVersion {
186186
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
187187
pub enum NumberOrString {
188188
/// A numeric identifier
189-
Number(u32),
189+
Number(i64),
190190
/// A string identifier
191191
String(Arc<str>),
192192
}
@@ -228,10 +228,20 @@ impl<'de> Deserialize<'de> for NumberOrString {
228228
{
229229
let value: Value = Deserialize::deserialize(deserializer)?;
230230
match value {
231-
Value::Number(n) => Ok(NumberOrString::Number(
232-
n.as_u64()
233-
.ok_or(serde::de::Error::custom("Expect an integer"))? as u32,
234-
)),
231+
Value::Number(n) => {
232+
if let Some(i) = n.as_i64() {
233+
Ok(NumberOrString::Number(i))
234+
} else if let Some(u) = n.as_u64() {
235+
// Handle large unsigned numbers that fit in i64
236+
if u <= i64::MAX as u64 {
237+
Ok(NumberOrString::Number(u as i64))
238+
} else {
239+
Err(serde::de::Error::custom("Number too large for i64"))
240+
}
241+
} else {
242+
Err(serde::de::Error::custom("Expected an integer"))
243+
}
244+
}
235245
Value::String(s) => Ok(NumberOrString::String(s.into())),
236246
_ => Err(serde::de::Error::custom("Expect number or string")),
237247
}
@@ -1737,6 +1747,85 @@ mod tests {
17371747
assert_eq!(server_response_json, raw_response_json);
17381748
}
17391749

1750+
#[test]
1751+
fn test_negative_and_large_request_ids() {
1752+
// Test negative ID
1753+
let negative_id_json = json!({
1754+
"jsonrpc": "2.0",
1755+
"id": -1,
1756+
"method": "test",
1757+
"params": {}
1758+
});
1759+
1760+
let message: JsonRpcMessage =
1761+
serde_json::from_value(negative_id_json.clone()).expect("Should parse negative ID");
1762+
1763+
match &message {
1764+
JsonRpcMessage::Request(r) => {
1765+
assert_eq!(r.id, RequestId::Number(-1));
1766+
}
1767+
_ => panic!("Expected Request"),
1768+
}
1769+
1770+
// Test roundtrip serialization
1771+
let serialized = serde_json::to_value(&message).expect("Should serialize");
1772+
assert_eq!(serialized, negative_id_json);
1773+
1774+
// Test large negative ID
1775+
let large_negative_json = json!({
1776+
"jsonrpc": "2.0",
1777+
"id": -9007199254740991i64, // JavaScript's MIN_SAFE_INTEGER
1778+
"method": "test",
1779+
"params": {}
1780+
});
1781+
1782+
let message: JsonRpcMessage = serde_json::from_value(large_negative_json.clone())
1783+
.expect("Should parse large negative ID");
1784+
1785+
match &message {
1786+
JsonRpcMessage::Request(r) => {
1787+
assert_eq!(r.id, RequestId::Number(-9007199254740991i64));
1788+
}
1789+
_ => panic!("Expected Request"),
1790+
}
1791+
1792+
// Test large positive ID (JavaScript's MAX_SAFE_INTEGER)
1793+
let large_positive_json = json!({
1794+
"jsonrpc": "2.0",
1795+
"id": 9007199254740991i64,
1796+
"method": "test",
1797+
"params": {}
1798+
});
1799+
1800+
let message: JsonRpcMessage = serde_json::from_value(large_positive_json.clone())
1801+
.expect("Should parse large positive ID");
1802+
1803+
match &message {
1804+
JsonRpcMessage::Request(r) => {
1805+
assert_eq!(r.id, RequestId::Number(9007199254740991i64));
1806+
}
1807+
_ => panic!("Expected Request"),
1808+
}
1809+
1810+
// Test zero ID
1811+
let zero_id_json = json!({
1812+
"jsonrpc": "2.0",
1813+
"id": 0,
1814+
"method": "test",
1815+
"params": {}
1816+
});
1817+
1818+
let message: JsonRpcMessage =
1819+
serde_json::from_value(zero_id_json.clone()).expect("Should parse zero ID");
1820+
1821+
match &message {
1822+
JsonRpcMessage::Request(r) => {
1823+
assert_eq!(r.id, RequestId::Number(0));
1824+
}
1825+
_ => panic!("Expected Request"),
1826+
}
1827+
}
1828+
17401829
#[test]
17411830
fn test_protocol_version_order() {
17421831
let v1 = ProtocolVersion::V_2024_11_05;

crates/rmcp/src/model/meta.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,19 @@ impl Meta {
116116
pub fn get_progress_token(&self) -> Option<ProgressToken> {
117117
self.0.get(PROGRESS_TOKEN_FIELD).and_then(|v| match v {
118118
Value::String(s) => Some(ProgressToken(NumberOrString::String(s.to_string().into()))),
119-
Value::Number(n) => n
120-
.as_u64()
121-
.map(|n| ProgressToken(NumberOrString::Number(n as u32))),
119+
Value::Number(n) => {
120+
if let Some(i) = n.as_i64() {
121+
Some(ProgressToken(NumberOrString::Number(i)))
122+
} else if let Some(u) = n.as_u64() {
123+
if u <= i64::MAX as u64 {
124+
Some(ProgressToken(NumberOrString::Number(u as i64)))
125+
} else {
126+
None
127+
}
128+
} else {
129+
None
130+
}
131+
}
122132
_ => None,
123133
})
124134
}

crates/rmcp/src/service.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ impl<R: ServiceRole, S: Service<R>> DynService<R> for S {
193193
use std::{
194194
collections::{HashMap, VecDeque},
195195
ops::Deref,
196-
sync::{Arc, atomic::AtomicU32},
196+
sync::{Arc, atomic::AtomicU64},
197197
time::Duration,
198198
};
199199

@@ -212,20 +212,21 @@ pub type AtomicU32ProgressTokenProvider = AtomicU32Provider;
212212

213213
#[derive(Debug, Default)]
214214
pub struct AtomicU32Provider {
215-
id: AtomicU32,
215+
id: AtomicU64,
216216
}
217217

218218
impl RequestIdProvider for AtomicU32Provider {
219219
fn next_request_id(&self) -> RequestId {
220-
RequestId::Number(self.id.fetch_add(1, std::sync::atomic::Ordering::SeqCst))
220+
let id = self.id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
221+
// Safe conversion: we start at 0 and increment by 1, so we won't overflow i64::MAX in practice
222+
RequestId::Number(id as i64)
221223
}
222224
}
223225

224226
impl ProgressTokenProvider for AtomicU32Provider {
225227
fn next_progress_token(&self) -> ProgressToken {
226-
ProgressToken(NumberOrString::Number(
227-
self.id.fetch_add(1, std::sync::atomic::Ordering::SeqCst),
228-
))
228+
let id = self.id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
229+
ProgressToken(NumberOrString::Number(id as i64))
229230
}
230231
}
231232

0 commit comments

Comments
 (0)