use serde::{Deserialize, Serialize};
use turbomcp_protocol::types::{
Implementation, Prompt, Resource, ResourceTemplate, ServerCapabilities, Tool,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerSpec {
pub server_info: Implementation,
pub protocol_version: String,
pub capabilities: ServerCapabilities,
pub tools: Vec<Tool>,
pub resources: Vec<Resource>,
#[serde(default)]
pub resource_templates: Vec<ResourceTemplate>,
pub prompts: Vec<Prompt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
}
impl ServerSpec {
#[must_use]
pub fn has_capability(&self, capability: &str) -> bool {
match capability {
"logging" => self.capabilities.logging.is_some(),
"completions" => self.capabilities.completions.is_some(),
"prompts" => self.capabilities.prompts.is_some(),
"resources" => self.capabilities.resources.is_some(),
"tools" => self.capabilities.tools.is_some(),
_ => false,
}
}
#[must_use]
pub fn supports_list_changed(&self, capability: &str) -> bool {
match capability {
"prompts" => self
.capabilities
.prompts
.as_ref()
.and_then(|c| c.list_changed)
.unwrap_or(false),
"resources" => self
.capabilities
.resources
.as_ref()
.and_then(|c| c.list_changed)
.unwrap_or(false),
"tools" => self
.capabilities
.tools
.as_ref()
.and_then(|c| c.list_changed)
.unwrap_or(false),
_ => false,
}
}
#[must_use]
pub fn supports_resource_subscriptions(&self) -> bool {
self.capabilities
.resources
.as_ref()
.and_then(|c| c.subscribe)
.unwrap_or(false)
}
#[must_use]
pub fn summary(&self) -> String {
format!(
"{} v{}: {} tools, {} resources, {} prompts",
self.server_info.name,
self.server_info.version,
self.tools.len(),
self.resources.len(),
self.prompts.len()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use turbomcp_protocol::types::ToolsCapabilities;
fn empty_spec(capabilities: ServerCapabilities) -> ServerSpec {
ServerSpec {
server_info: Implementation::new("test-server", "1.0.0"),
protocol_version: "2025-11-25".to_string(),
capabilities,
tools: vec![],
resources: vec![],
resource_templates: vec![],
prompts: vec![],
instructions: None,
}
}
#[test]
fn test_server_spec_serialization() {
let spec = empty_spec(ServerCapabilities::default());
let json = serde_json::to_string_pretty(&spec).unwrap();
assert!(json.contains("test-server"));
assert!(json.contains("2025-11-25"));
}
#[test]
fn test_capability_checks() {
let spec = empty_spec(ServerCapabilities {
tools: Some(ToolsCapabilities {
list_changed: Some(true),
}),
..Default::default()
});
assert!(spec.has_capability("tools"));
assert!(!spec.has_capability("prompts"));
assert!(spec.supports_list_changed("tools"));
assert!(!spec.supports_list_changed("prompts"));
}
}