#![allow(missing_docs)]
use serde::{Deserialize, Serialize};
pub const API_VERSION: &str = "v1-beta";
pub const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const MCP_PROTOCOL_VERSION: &str = "2024-11-05";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ApiStability {
Experimental,
Beta,
Stable,
Deprecated,
}
#[derive(Debug, Clone)]
pub struct ApiEndpoint {
pub method: &'static str,
pub stability: ApiStability,
pub since_version: &'static str,
pub deprecated_in: Option<&'static str>,
pub removed_in: Option<&'static str>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionInfo {
pub api_version: String,
pub server_version: String,
pub protocol_version: String,
pub stability: String,
}
impl Default for VersionInfo {
fn default() -> Self {
Self {
api_version: API_VERSION.to_string(),
server_version: SERVER_VERSION.to_string(),
protocol_version: MCP_PROTOCOL_VERSION.to_string(),
stability: "beta".to_string(),
}
}
}
pub struct ApiRegistry;
impl ApiRegistry {
pub fn endpoints() -> Vec<ApiEndpoint> {
vec![
ApiEndpoint {
method: "initialize",
stability: ApiStability::Stable,
since_version: "0.9.1",
deprecated_in: None,
removed_in: None,
},
ApiEndpoint {
method: "initialized",
stability: ApiStability::Stable,
since_version: "0.9.1",
deprecated_in: None,
removed_in: None,
},
ApiEndpoint {
method: "shutdown",
stability: ApiStability::Stable,
since_version: "0.9.1",
deprecated_in: None,
removed_in: None,
},
ApiEndpoint {
method: "tools/list",
stability: ApiStability::Stable,
since_version: "0.9.1",
deprecated_in: None,
removed_in: None,
},
ApiEndpoint {
method: "tools/call",
stability: ApiStability::Stable,
since_version: "0.9.1",
deprecated_in: None,
removed_in: None,
},
ApiEndpoint {
method: "resources/list",
stability: ApiStability::Stable,
since_version: "0.9.1",
deprecated_in: None,
removed_in: None,
},
ApiEndpoint {
method: "resources/read",
stability: ApiStability::Stable,
since_version: "0.9.1",
deprecated_in: None,
removed_in: None,
},
ApiEndpoint {
method: "prompts/list",
stability: ApiStability::Beta,
since_version: "0.9.1",
deprecated_in: None,
removed_in: None,
},
ApiEndpoint {
method: "security/status",
stability: ApiStability::Experimental,
since_version: "0.9.1",
deprecated_in: None,
removed_in: None,
},
ApiEndpoint {
method: "security/threats",
stability: ApiStability::Experimental,
since_version: "0.9.1",
deprecated_in: None,
removed_in: None,
},
ApiEndpoint {
method: "security/rate_limit_status",
stability: ApiStability::Experimental,
since_version: "0.9.1",
deprecated_in: None,
removed_in: None,
},
ApiEndpoint {
method: "admin/update_config",
stability: ApiStability::Experimental,
since_version: "0.9.1",
deprecated_in: None,
removed_in: None,
},
]
}
pub fn is_stable(method: &str) -> bool {
Self::endpoints()
.iter()
.find(|e| e.method == method)
.is_some_and(|e| e.stability == ApiStability::Stable)
}
pub fn get_stability(method: &str) -> Option<ApiStability> {
Self::endpoints()
.iter()
.find(|e| e.method == method)
.map(|e| e.stability)
}
pub fn experimental_enabled() -> bool {
std::env::var("KINDLYGUARD_EXPERIMENTAL")
.map(|v| v == "1" || v.to_lowercase() == "true")
.unwrap_or(false)
}
}
pub fn add_version_metadata(response: &mut serde_json::Value) {
if let Some(obj) = response.as_object_mut() {
let version_info = VersionInfo::default();
obj.insert(
"_meta".to_string(),
serde_json::json!({
"api_version": version_info.api_version,
"server_version": version_info.server_version,
"timestamp": chrono::Utc::now().to_rfc3339(),
}),
);
}
}
pub struct VersionNegotiator;
impl VersionNegotiator {
pub const SUPPORTED_PROTOCOLS: &'static [&'static str] = &[
"2024-11-05", "2024-10-01", ];
pub fn is_supported(version: &str) -> bool {
Self::SUPPORTED_PROTOCOLS.contains(&version)
}
pub fn negotiate(requested: &str) -> Option<&'static str> {
Self::SUPPORTED_PROTOCOLS
.iter()
.find(|&&v| v == requested)
.copied()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_api_registry() {
assert!(ApiRegistry::is_stable("initialize"));
assert!(ApiRegistry::is_stable("tools/list"));
assert!(!ApiRegistry::is_stable("security/status"));
assert_eq!(
ApiRegistry::get_stability("security/status"),
Some(ApiStability::Experimental)
);
}
#[test]
fn test_version_negotiation() {
assert!(VersionNegotiator::is_supported("2024-11-05"));
assert!(!VersionNegotiator::is_supported("2023-01-01"));
assert_eq!(
VersionNegotiator::negotiate("2024-11-05"),
Some("2024-11-05")
);
}
#[test]
fn test_version_metadata() {
let mut response = serde_json::json!({
"result": "test"
});
add_version_metadata(&mut response);
assert!(response.get("_meta").is_some());
let meta = response.get("_meta").unwrap();
assert!(meta.get("api_version").is_some());
assert!(meta.get("server_version").is_some());
}
}