Skip to content

Commit acdd499

Browse files
committed
chore: request_id_gen
1 parent 5208284 commit acdd499

File tree

1 file changed

+99
-0
lines changed

1 file changed

+99
-0
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use std::sync::atomic::AtomicI64;
2+
3+
use crate::schema::{schema_utils::McpMessage, RequestId};
4+
use async_trait::async_trait;
5+
6+
/// A trait for generating and managing request IDs in a thread-safe manner.
7+
///
8+
/// Implementors provide functionality to generate unique request IDs, retrieve the last
9+
/// generated ID, and reset the ID counter.
10+
#[async_trait]
11+
pub trait RequestIdGen: Send + Sync {
12+
fn next_request_id(&self) -> RequestId;
13+
fn last_request_id(&self) -> Option<RequestId>;
14+
fn reset_to(&self, id: u64);
15+
16+
/// Determines the request ID for an outgoing MCP message.
17+
///
18+
/// For requests, generates a new ID using the internal counter. For responses or errors,
19+
/// uses the provided `request_id`. Notifications receive no ID.
20+
///
21+
/// # Arguments
22+
/// * `message` - The MCP message to evaluate.
23+
/// * `request_id` - An optional existing request ID (required for responses/errors).
24+
///
25+
/// # Returns
26+
/// An `Option<RequestId>`: `Some` for requests or responses/errors, `None` for notifications.
27+
fn request_id_for_message(
28+
&self,
29+
message: &dyn McpMessage,
30+
request_id: Option<RequestId>,
31+
) -> Option<RequestId> {
32+
// we need to produce next request_id for requests
33+
if message.is_request() {
34+
// request_id should be None for requests
35+
assert!(request_id.is_none());
36+
Some(self.next_request_id())
37+
} else if !message.is_notification() {
38+
// `request_id` must not be `None` for errors, notifications and responses
39+
assert!(request_id.is_some());
40+
request_id
41+
} else {
42+
None
43+
}
44+
}
45+
}
46+
47+
pub struct RequestIdGenNumeric {
48+
message_id_counter: AtomicI64,
49+
last_message_id: AtomicI64,
50+
}
51+
52+
impl RequestIdGenNumeric {
53+
pub fn new(initial_id: Option<u64>) -> Self {
54+
Self {
55+
message_id_counter: AtomicI64::new(initial_id.unwrap_or(0) as i64),
56+
last_message_id: AtomicI64::new(-1),
57+
}
58+
}
59+
}
60+
61+
impl RequestIdGen for RequestIdGenNumeric {
62+
/// Generates the next unique request ID as an integer.
63+
///
64+
/// Increments the internal counter atomically and updates the last generated ID.
65+
/// Uses `Relaxed` ordering for performance, as the counter only needs to ensure unique IDs.
66+
fn next_request_id(&self) -> RequestId {
67+
let id = self
68+
.message_id_counter
69+
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
70+
// Store the new ID as the last generated ID
71+
self.last_message_id
72+
.store(id, std::sync::atomic::Ordering::Relaxed);
73+
RequestId::Integer(id)
74+
}
75+
76+
/// Returns the last generated request ID, if any.
77+
///
78+
/// Returns `None` if no ID has been generated (indicated by a sentinel value of -1).
79+
/// Uses `Relaxed` ordering since the read operation doesn’t require synchronization
80+
/// with other memory operations beyond atomicity.
81+
fn last_request_id(&self) -> Option<RequestId> {
82+
let last_id = self
83+
.last_message_id
84+
.load(std::sync::atomic::Ordering::Relaxed);
85+
if last_id == -1 {
86+
None
87+
} else {
88+
Some(RequestId::Integer(last_id))
89+
}
90+
}
91+
92+
/// Resets the internal counter to the specified ID.
93+
///
94+
/// The provided `id` (u64) is converted to i64 and stored atomically.
95+
fn reset_to(&self, id: u64) {
96+
self.message_id_counter
97+
.store(id as i64, std::sync::atomic::Ordering::Relaxed);
98+
}
99+
}

0 commit comments

Comments
 (0)