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}