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