use serde::{Deserialize, Serialize};
use crate::protocol::{
ClientCapabilities, Implementation, ProgressToken, Root, ServerCapabilities,
};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StatelessRequestMeta {
#[serde(skip_serializing_if = "Option::is_none")]
pub progress_token: Option<ProgressToken>,
#[serde(
rename = "modelcontextprotocol.io/mcpProtocolVersion",
skip_serializing_if = "Option::is_none"
)]
pub protocol_version: Option<String>,
#[serde(
rename = "modelcontextprotocol.io/sessionId",
skip_serializing_if = "Option::is_none"
)]
pub session_id: Option<String>,
#[serde(
rename = "modelcontextprotocol.io/clientCapabilities",
skip_serializing_if = "Option::is_none"
)]
pub client_capabilities: Option<ClientCapabilities>,
#[serde(
rename = "modelcontextprotocol.io/roots",
skip_serializing_if = "Option::is_none"
)]
pub roots: Option<Vec<Root>>,
#[serde(
rename = "modelcontextprotocol.io/logLevel",
skip_serializing_if = "Option::is_none"
)]
pub log_level: Option<LogLevel>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LogLevel {
Debug,
Info,
Notice,
Warning,
Error,
Critical,
Alert,
Emergency,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DiscoverParams {}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DiscoverResult {
pub supported_versions: Vec<String>,
pub capabilities: ServerCapabilities,
pub server_info: Implementation,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
}
pub mod error_codes {
pub const UNSUPPORTED_VERSION: i32 = -32000;
pub const INVALID_SESSION: i32 = -32001;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UnsupportedVersionData {
pub supported_versions: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct StatelessConfig {
pub require_protocol_version: bool,
pub optional_sessions: bool,
pub enable_discover: bool,
}
impl Default for StatelessConfig {
fn default() -> Self {
Self::new()
}
}
impl StatelessConfig {
pub fn new() -> Self {
Self {
require_protocol_version: true,
optional_sessions: true,
enable_discover: true,
}
}
pub fn backward_compatible() -> Self {
Self {
require_protocol_version: false,
optional_sessions: true,
enable_discover: true,
}
}
}
pub fn validate_protocol_version(
version: &str,
) -> std::result::Result<(), crate::error::JsonRpcError> {
use crate::protocol::SUPPORTED_PROTOCOL_VERSIONS;
if SUPPORTED_PROTOCOL_VERSIONS.contains(&version) {
Ok(())
} else {
let data = UnsupportedVersionData {
supported_versions: SUPPORTED_PROTOCOL_VERSIONS
.iter()
.map(|v| v.to_string())
.collect(),
};
Err(crate::error::JsonRpcError {
code: error_codes::UNSUPPORTED_VERSION,
message: format!("Unsupported protocol version: {}", version),
data: Some(serde_json::to_value(data).unwrap()),
})
}
}
impl StatelessRequestMeta {
pub fn from_params(params: &serde_json::Value) -> Option<Self> {
params
.get("_meta")
.and_then(|meta| serde_json::from_value(meta.clone()).ok())
}
pub fn has_client_capabilities(&self) -> bool {
self.client_capabilities.is_some()
}
pub fn has_roots(&self) -> bool {
self.roots.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stateless_meta_serialization() {
let meta = StatelessRequestMeta {
progress_token: None,
protocol_version: Some("2025-11-25".to_string()),
session_id: Some("abc123".to_string()),
client_capabilities: None,
roots: None,
log_level: Some(LogLevel::Info),
};
let json = serde_json::to_string(&meta).unwrap();
assert!(json.contains("modelcontextprotocol.io/mcpProtocolVersion"));
assert!(json.contains("modelcontextprotocol.io/sessionId"));
assert!(json.contains("modelcontextprotocol.io/logLevel"));
}
#[test]
fn test_stateless_meta_deserialization() {
let json = r#"{
"modelcontextprotocol.io/mcpProtocolVersion": "2025-11-25",
"modelcontextprotocol.io/sessionId": "test-session",
"modelcontextprotocol.io/logLevel": "debug"
}"#;
let meta: StatelessRequestMeta = serde_json::from_str(json).unwrap();
assert_eq!(meta.protocol_version, Some("2025-11-25".to_string()));
assert_eq!(meta.session_id, Some("test-session".to_string()));
assert_eq!(meta.log_level, Some(LogLevel::Debug));
}
#[test]
fn test_discover_result_serialization() {
let result = DiscoverResult {
supported_versions: vec!["2025-11-25".to_string(), "2025-03-26".to_string()],
capabilities: ServerCapabilities::default(),
server_info: Implementation {
name: "test-server".to_string(),
version: "1.0.0".to_string(),
title: None,
description: None,
icons: None,
website_url: None,
meta: None,
},
instructions: Some("Test instructions".to_string()),
};
let json = serde_json::to_value(&result).unwrap();
assert!(json["supportedVersions"].is_array());
assert_eq!(json["serverInfo"]["name"], "test-server");
}
#[test]
fn test_unsupported_version_data() {
let data = UnsupportedVersionData {
supported_versions: vec!["2025-11-25".to_string()],
};
let json = serde_json::to_value(&data).unwrap();
assert_eq!(json["supportedVersions"][0], "2025-11-25");
}
#[test]
fn test_config_defaults() {
let config = StatelessConfig::new();
assert!(config.require_protocol_version);
assert!(config.optional_sessions);
assert!(config.enable_discover);
}
#[test]
fn test_config_backward_compatible() {
let config = StatelessConfig::backward_compatible();
assert!(!config.require_protocol_version);
assert!(config.optional_sessions);
}
#[test]
fn test_from_params() {
let params = serde_json::json!({
"name": "test-tool",
"_meta": {
"modelcontextprotocol.io/mcpProtocolVersion": "2025-11-25",
"modelcontextprotocol.io/sessionId": "session-123",
"modelcontextprotocol.io/logLevel": "debug"
}
});
let meta = StatelessRequestMeta::from_params(¶ms).unwrap();
assert_eq!(meta.protocol_version, Some("2025-11-25".to_string()));
assert_eq!(meta.session_id, Some("session-123".to_string()));
assert_eq!(meta.log_level, Some(LogLevel::Debug));
}
#[test]
fn test_from_params_no_meta() {
let params = serde_json::json!({
"name": "test-tool"
});
let meta = StatelessRequestMeta::from_params(¶ms);
assert!(meta.is_none());
}
#[test]
fn test_has_client_capabilities() {
let meta = StatelessRequestMeta {
progress_token: None,
protocol_version: None,
session_id: None,
client_capabilities: Some(ClientCapabilities::default()),
roots: None,
log_level: None,
};
assert!(meta.has_client_capabilities());
let meta_without = StatelessRequestMeta::default();
assert!(!meta_without.has_client_capabilities());
}
}