rust_mcp_sdk/utils.rs
1use std::cmp::Ordering;
2
3use crate::error::{McpSdkError, SdkResult};
4
5/// Formats an assertion error message for unsupported capabilities.
6///
7/// Constructs a string describing that a specific entity (e.g., server or client) lacks
8/// support for a required capability, needed for a particular method.
9///
10/// # Arguments
11/// - `entity`: The name of the entity (e.g., "Server" or "Client") that lacks support.
12/// - `capability`: The name of the unsupported capability or tool.
13/// - `method_name`: The name of the method requiring the capability.
14///
15/// # Returns
16/// A formatted string detailing the unsupported capability error.
17///
18/// # Examples
19/// ```ignore
20/// let msg = format_assertion_message("Server", "tools", rust_mcp_schema::ListResourcesRequest::method_name());
21/// assert_eq!(msg, "Server does not support resources (required for resources/list)");
22/// ```
23pub fn format_assertion_message(entity: &str, capability: &str, method_name: &str) -> String {
24 format!(
25 "{} does not support {} (required for {})",
26 entity, capability, method_name
27 )
28}
29
30/// Checks if the client and server protocol versions are compatible by ensuring they are equal.
31///
32/// This function compares the provided client and server protocol versions. If they are equal,
33/// it returns `Ok(())`, indicating compatibility. If they differ (either the client version is
34/// lower or higher than the server version), it returns an error with details about the
35/// incompatible versions.
36///
37/// # Arguments
38///
39/// * `client_protocol_version` - A string slice representing the client's protocol version.
40/// * `server_protocol_version` - A string slice representing the server's protocol version.
41///
42/// # Returns
43///
44/// * `Ok(())` if the versions are equal.
45/// * `Err(McpSdkError::IncompatibleProtocolVersion)` if the versions differ, containing the
46/// client and server versions as strings.
47///
48/// # Examples
49///
50/// ```
51/// use rust_mcp_sdk::mcp_client::ensure_server_protocole_compatibility;
52/// use rust_mcp_sdk::error::McpSdkError;
53///
54/// // Compatible versions
55/// let result = ensure_server_protocole_compatibility("2024_11_05", "2024_11_05");
56/// assert!(result.is_ok());
57///
58/// // Incompatible versions (client < server)
59/// let result = ensure_server_protocole_compatibility("2024_11_05", "2025_03_26");
60/// assert!(matches!(
61/// result,
62/// Err(McpSdkError::IncompatibleProtocolVersion(client, server))
63/// if client == "2024_11_05" && server == "2025_03_26"
64/// ));
65///
66/// // Incompatible versions (client > server)
67/// let result = ensure_server_protocole_compatibility("2025_03_26", "2024_11_05");
68/// assert!(matches!(
69/// result,
70/// Err(McpSdkError::IncompatibleProtocolVersion(client, server))
71/// if client == "2025_03_26" && server == "2024_11_05"
72/// ));
73/// ```
74#[allow(unused)]
75pub fn ensure_server_protocole_compatibility(
76 client_protocol_version: &str,
77 server_protocol_version: &str,
78) -> SdkResult<()> {
79 match client_protocol_version.cmp(server_protocol_version) {
80 Ordering::Less | Ordering::Greater => Err(McpSdkError::IncompatibleProtocolVersion(
81 client_protocol_version.to_string(),
82 server_protocol_version.to_string(),
83 )),
84 Ordering::Equal => Ok(()),
85 }
86}
87
88/// Enforces protocol version compatibility on for MCP Server , allowing the client to use a lower or equal version.
89///
90/// This function compares the client and server protocol versions. If the client version is
91/// higher than the server version, it returns an error indicating incompatibility. If the
92/// versions are equal, it returns `Ok(None)`, indicating no downgrade is needed. If the client
93/// version is lower, it returns `Ok(Some(client_protocol_version))`, suggesting the server
94/// can use the client's version for compatibility.
95///
96/// # Arguments
97///
98/// * `client_protocol_version` - The client's protocol version.
99/// * `server_protocol_version` - The server's protocol version.
100///
101/// # Returns
102///
103/// * `Ok(None)` if the versions are equal, indicating no downgrade is needed.
104/// * `Ok(Some(client_protocol_version))` if the client version is lower, returning the client
105/// version to use for compatibility.
106/// * `Err(McpSdkError::IncompatibleProtocolVersion)` if the client version is higher, containing
107/// the client and server versions as strings.
108///
109/// # Examples
110///
111/// ```
112/// use rust_mcp_sdk::mcp_server::enforce_compatible_protocol_version;
113/// use rust_mcp_sdk::error::McpSdkError;
114///
115/// // Equal versions
116/// let result = enforce_compatible_protocol_version("2024_11_05", "2024_11_05");
117/// assert!(matches!(result, Ok(None)));
118///
119/// // Client version lower (downgrade allowed)
120/// let result = enforce_compatible_protocol_version("2024_11_05", "2025_03_26");
121/// assert!(matches!(result, Ok(Some(ref v)) if v == "2024_11_05"));
122///
123/// // Client version higher (incompatible)
124/// let result = enforce_compatible_protocol_version("2025_03_26", "2024_11_05");
125/// assert!(matches!(
126/// result,
127/// Err(McpSdkError::IncompatibleProtocolVersion(client, server))
128/// if client == "2025_03_26" && server == "2024_11_05"
129/// ));
130/// ```
131#[allow(unused)]
132pub fn enforce_compatible_protocol_version(
133 client_protocol_version: &str,
134 server_protocol_version: &str,
135) -> SdkResult<Option<String>> {
136 match client_protocol_version.cmp(server_protocol_version) {
137 // if client protocol version is higher
138 Ordering::Greater => Err(McpSdkError::IncompatibleProtocolVersion(
139 client_protocol_version.to_string(),
140 server_protocol_version.to_string(),
141 )),
142 Ordering::Equal => Ok(None),
143 Ordering::Less => {
144 // return the same version that was received from the client
145 Ok(Some(client_protocol_version.to_string()))
146 }
147 }
148}
149
150/// Removes query string and hash fragment from a URL, returning the base path.
151///
152/// # Arguments
153/// * `endpoint` - The URL or endpoint to process (e.g., "/messages?foo=bar#section1")
154///
155/// # Returns
156/// A String containing the base path without query parameters or fragment
157/// ```
158#[allow(unused)]
159pub(crate) fn remove_query_and_hash(endpoint: &str) -> String {
160 // Split off fragment (if any) and take the first part
161 let without_fragment = endpoint.split_once('#').map_or(endpoint, |(path, _)| path);
162
163 // Split off query string (if any) and take the first part
164 let without_query = without_fragment
165 .split_once('?')
166 .map_or(without_fragment, |(path, _)| path);
167
168 // Return the base path
169 if without_query.is_empty() {
170 "/".to_string()
171 } else {
172 without_query.to_string()
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 #[test]
180 fn tets_remove_query_and_hash() {
181 assert_eq!(remove_query_and_hash("/messages"), "/messages");
182 assert_eq!(
183 remove_query_and_hash("/messages?foo=bar&baz=qux"),
184 "/messages"
185 );
186 assert_eq!(remove_query_and_hash("/messages#section1"), "/messages");
187 assert_eq!(
188 remove_query_and_hash("/messages?key=value#section2"),
189 "/messages"
190 );
191 assert_eq!(remove_query_and_hash("/"), "/");
192 }
193}