use crate::extension::ExtensionRegistry;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ServerCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<ToolCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resources: Option<ResourceCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompts: Option<PromptCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tasks: Option<TaskCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logging: Option<LoggingCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completions: Option<CompletionCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub experimental: Option<serde_json::Value>,
}
impl ServerCapabilities {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_tools(mut self) -> Self {
self.tools = Some(ToolCapability::default());
self
}
#[must_use]
pub const fn with_tools_and_changes(mut self) -> Self {
self.tools = Some(ToolCapability {
list_changed: Some(true),
});
self
}
#[must_use]
pub fn with_resources(mut self) -> Self {
self.resources = Some(ResourceCapability::default());
self
}
#[must_use]
pub const fn with_resources_and_subscriptions(mut self) -> Self {
self.resources = Some(ResourceCapability {
subscribe: Some(true),
list_changed: Some(true),
});
self
}
#[must_use]
pub fn with_prompts(mut self) -> Self {
self.prompts = Some(PromptCapability::default());
self
}
#[must_use]
pub fn with_tasks(mut self) -> Self {
self.tasks = Some(TaskCapability::default());
self
}
#[must_use]
pub const fn with_logging(mut self) -> Self {
self.logging = Some(LoggingCapability {});
self
}
#[must_use]
pub const fn with_completions(mut self) -> Self {
self.completions = Some(CompletionCapability {});
self
}
#[must_use]
pub const fn has_tools(&self) -> bool {
self.tools.is_some()
}
#[must_use]
pub const fn has_resources(&self) -> bool {
self.resources.is_some()
}
#[must_use]
pub const fn has_prompts(&self) -> bool {
self.prompts.is_some()
}
#[must_use]
pub const fn has_tasks(&self) -> bool {
self.tasks.is_some()
}
#[must_use]
pub const fn has_completions(&self) -> bool {
self.completions.is_some()
}
#[must_use]
pub fn has_resource_subscribe(&self) -> bool {
self.resources
.as_ref()
.and_then(|r| r.subscribe)
.unwrap_or(false)
}
#[must_use]
pub fn with_extensions(mut self, registry: ExtensionRegistry) -> Self {
if !registry.is_empty() {
self.experimental = Some(registry.to_experimental());
}
self
}
#[must_use]
pub fn has_extension(&self, name: &str) -> bool {
self.experimental
.as_ref()
.and_then(ExtensionRegistry::from_experimental)
.is_some_and(|registry| registry.has(name))
}
#[must_use]
pub fn extensions(&self) -> Option<ExtensionRegistry> {
self.experimental
.as_ref()
.and_then(ExtensionRegistry::from_experimental)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ClientCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub roots: Option<RootsCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sampling: Option<SamplingCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub elicitation: Option<ElicitationCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub experimental: Option<serde_json::Value>,
}
impl ClientCapabilities {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_roots(mut self) -> Self {
self.roots = Some(RootsCapability::default());
self
}
#[must_use]
pub const fn with_roots_and_changes(mut self) -> Self {
self.roots = Some(RootsCapability {
list_changed: Some(true),
});
self
}
#[must_use]
pub const fn with_sampling(mut self) -> Self {
self.sampling = Some(SamplingCapability {});
self
}
#[must_use]
pub const fn with_elicitation(mut self) -> Self {
self.elicitation = Some(ElicitationCapability {});
self
}
#[must_use]
pub const fn has_roots(&self) -> bool {
self.roots.is_some()
}
#[must_use]
pub const fn has_sampling(&self) -> bool {
self.sampling.is_some()
}
#[must_use]
pub const fn has_elicitation(&self) -> bool {
self.elicitation.is_some()
}
#[must_use]
pub fn with_extensions(mut self, registry: ExtensionRegistry) -> Self {
if !registry.is_empty() {
self.experimental = Some(registry.to_experimental());
}
self
}
#[must_use]
pub fn has_extension(&self, name: &str) -> bool {
self.experimental
.as_ref()
.and_then(ExtensionRegistry::from_experimental)
.is_some_and(|registry| registry.has(name))
}
#[must_use]
pub fn extensions(&self) -> Option<ExtensionRegistry> {
self.experimental
.as_ref()
.and_then(ExtensionRegistry::from_experimental)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ToolCapability {
#[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ResourceCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub subscribe: Option<bool>,
#[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PromptCapability {
#[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TaskCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub cancellable: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LoggingCapability {}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CompletionCapability {}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RootsCapability {
#[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SamplingCapability {}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ElicitationCapability {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {
pub name: String,
pub version: String,
#[serde(rename = "protocolVersion", skip_serializing_if = "Option::is_none")]
pub protocol_version: Option<String>,
}
impl ServerInfo {
#[must_use]
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
Self {
name: name.into(),
version: version.into(),
protocol_version: Some(PROTOCOL_VERSION.to_string()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientInfo {
pub name: String,
pub version: String,
}
impl ClientInfo {
#[must_use]
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
Self {
name: name.into(),
version: version.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitializeRequest {
#[serde(rename = "protocolVersion")]
pub protocol_version: String,
pub capabilities: ClientCapabilities,
#[serde(rename = "clientInfo")]
pub client_info: ClientInfo,
}
impl InitializeRequest {
#[must_use]
pub fn new(client_info: ClientInfo, capabilities: ClientCapabilities) -> Self {
Self {
protocol_version: PROTOCOL_VERSION.to_string(),
capabilities,
client_info,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitializeResult {
#[serde(rename = "protocolVersion")]
pub protocol_version: String,
pub capabilities: ServerCapabilities,
#[serde(rename = "serverInfo")]
pub server_info: ServerInfo,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
}
impl InitializeResult {
#[must_use]
pub fn new(server_info: ServerInfo, capabilities: ServerCapabilities) -> Self {
Self {
protocol_version: PROTOCOL_VERSION.to_string(),
capabilities,
server_info,
instructions: None,
}
}
#[must_use]
pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
self.instructions = Some(instructions.into());
self
}
}
pub const PROTOCOL_VERSION: &str = "2025-11-25";
pub const SUPPORTED_PROTOCOL_VERSIONS: &[&str] = &[
"2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05", ];
#[must_use]
pub fn is_version_supported(version: &str) -> bool {
SUPPORTED_PROTOCOL_VERSIONS.contains(&version)
}
#[must_use]
pub fn negotiate_version(requested_version: &str) -> &'static str {
if is_version_supported(requested_version) {
SUPPORTED_PROTOCOL_VERSIONS
.iter()
.find(|&&v| v == requested_version)
.copied()
.unwrap_or(PROTOCOL_VERSION)
} else {
PROTOCOL_VERSION
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VersionNegotiationResult {
Accepted(String),
CounterOffer {
requested: String,
offered: String,
},
}
impl VersionNegotiationResult {
#[must_use]
pub fn version(&self) -> &str {
match self {
Self::Accepted(v) => v,
Self::CounterOffer { offered, .. } => offered,
}
}
#[must_use]
pub const fn is_exact_match(&self) -> bool {
matches!(self, Self::Accepted(_))
}
}
#[must_use]
pub fn negotiate_version_detailed(requested_version: &str) -> VersionNegotiationResult {
if is_version_supported(requested_version) {
VersionNegotiationResult::Accepted(requested_version.to_string())
} else {
VersionNegotiationResult::CounterOffer {
requested: requested_version.to_string(),
offered: PROTOCOL_VERSION.to_string(),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct InitializedNotification {}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PingRequest {}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PingResult {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_server_capabilities_builder() -> Result<(), Box<dyn std::error::Error>> {
let caps = ServerCapabilities::new()
.with_tools()
.with_resources_and_subscriptions()
.with_prompts()
.with_tasks();
assert!(caps.has_tools());
assert!(caps.has_resources());
assert!(caps.has_prompts());
assert!(caps.has_tasks());
assert!(
caps.resources
.ok_or("Expected resources")?
.subscribe
.ok_or("Expected subscribe")?
);
Ok(())
}
#[test]
fn test_client_capabilities_builder() -> Result<(), Box<dyn std::error::Error>> {
let caps = ClientCapabilities::new()
.with_roots_and_changes()
.with_sampling()
.with_elicitation();
assert!(caps.has_roots());
assert!(caps.has_sampling());
assert!(caps.has_elicitation());
assert!(
caps.roots
.ok_or("Expected roots")?
.list_changed
.ok_or("Expected list_changed")?
);
Ok(())
}
#[test]
fn test_initialize_request() {
let client = ClientInfo::new("test-client", "1.0.0");
let caps = ClientCapabilities::new().with_sampling();
let request = InitializeRequest::new(client, caps);
assert_eq!(request.protocol_version, PROTOCOL_VERSION);
assert_eq!(request.client_info.name, "test-client");
}
#[test]
fn test_initialize_result() {
let server = ServerInfo::new("test-server", "1.0.0");
let caps = ServerCapabilities::new().with_tools();
let result =
InitializeResult::new(server, caps).instructions("Use this server to do things");
assert_eq!(result.protocol_version, PROTOCOL_VERSION);
assert!(result.instructions.is_some());
}
#[test]
fn test_serialization() -> Result<(), Box<dyn std::error::Error>> {
let caps = ServerCapabilities::new()
.with_tools_and_changes()
.with_resources();
let json = serde_json::to_string(&caps)?;
assert!(json.contains("\"tools\""));
assert!(json.contains("\"listChanged\":true"));
Ok(())
}
}