use crate::extension::ExtensionRegistry;
use crate::types::Icon;
use crate::types::meta::Meta;
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<TasksCapability>,
#[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 {
let tasks = self.tasks.get_or_insert_with(TasksCapability::default);
tasks.list = Some(serde_json::json!({}));
tasks.cancel = Some(serde_json::json!({}));
self
}
#[must_use]
pub fn with_task_tools(mut self) -> Self {
self.tasks
.get_or_insert_with(TasksCapability::default)
.requests
.get_or_insert_with(TaskRequestsCapability::default)
.tools
.get_or_insert_with(ToolsTaskCapability::default)
.call = Some(serde_json::json!({}));
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 const fn has_logging(&self) -> bool {
self.logging.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 tasks: Option<TasksCapability>,
#[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 fn with_sampling(mut self) -> Self {
self.sampling = Some(SamplingCapability::default());
self
}
#[must_use]
pub fn with_sampling_tools(mut self) -> Self {
let sampling = self
.sampling
.get_or_insert_with(SamplingCapability::default);
sampling.tools = Some(serde_json::json!({}));
self
}
#[must_use]
pub fn with_sampling_context(mut self) -> Self {
let sampling = self
.sampling
.get_or_insert_with(SamplingCapability::default);
sampling.context = Some(serde_json::json!({}));
self
}
#[must_use]
pub fn with_elicitation(mut self) -> Self {
self.elicitation = Some(ElicitationCapability {
form: None,
url: None,
});
self
}
#[must_use]
pub fn with_form_elicitation(mut self) -> Self {
self.elicitation
.get_or_insert_with(ElicitationCapability::default)
.form = Some(serde_json::json!({}));
self
}
#[must_use]
pub fn with_url_elicitation(mut self) -> Self {
self.elicitation
.get_or_insert_with(ElicitationCapability::default)
.url = Some(serde_json::json!({}));
self
}
#[must_use]
pub fn with_tasks(mut self) -> Self {
let tasks = self.tasks.get_or_insert_with(TasksCapability::default);
tasks.list = Some(serde_json::json!({}));
tasks.cancel = Some(serde_json::json!({}));
self
}
#[must_use]
pub fn with_task_sampling(mut self) -> Self {
self.tasks
.get_or_insert_with(TasksCapability::default)
.requests
.get_or_insert_with(TaskRequestsCapability::default)
.sampling
.get_or_insert_with(SamplingTaskCapability::default)
.create_message = Some(serde_json::json!({}));
self
}
#[must_use]
pub fn has_task_sampling(&self) -> bool {
self.tasks
.as_ref()
.and_then(|t| t.requests.as_ref())
.and_then(|r| r.sampling.as_ref())
.is_some_and(|s| s.create_message.is_some())
}
#[must_use]
pub fn has_form_elicitation(&self) -> bool {
self.elicitation
.as_ref()
.is_some_and(ElicitationCapability::has_form)
}
#[must_use]
pub fn has_url_elicitation(&self) -> bool {
self.elicitation
.as_ref()
.is_some_and(ElicitationCapability::has_url)
}
#[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_sampling_tools(&self) -> bool {
matches!(&self.sampling, Some(s) if s.tools.is_some())
}
#[must_use]
pub const fn has_sampling_context(&self) -> bool {
matches!(&self.sampling, Some(s) if s.context.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 TasksCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub list: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cancel: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub requests: Option<TaskRequestsCapability>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TaskRequestsCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub sampling: Option<SamplingTaskCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub elicitation: Option<ElicitationTaskCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<ToolsTaskCapability>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SamplingTaskCapability {
#[serde(rename = "createMessage", skip_serializing_if = "Option::is_none")]
pub create_message: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ElicitationTaskCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub create: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ToolsTaskCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub call: Option<serde_json::Value>,
}
#[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 {
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ElicitationCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub form: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<serde_json::Value>,
}
impl ElicitationCapability {
#[must_use]
pub const fn has_form(&self) -> bool {
self.form.is_some() || self.url.is_none()
}
#[must_use]
pub const fn has_url(&self) -> bool {
self.url.is_some()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub version: String,
#[serde(rename = "protocolVersion", skip_serializing_if = "Option::is_none")]
pub protocol_version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "websiteUrl", skip_serializing_if = "Option::is_none")]
pub website_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icons: Option<Vec<Icon>>,
}
impl ServerInfo {
#[must_use]
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
Self {
name: name.into(),
title: None,
version: version.into(),
protocol_version: Some(PROTOCOL_VERSION.to_string()),
description: None,
website_url: None,
icons: None,
}
}
#[must_use]
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
#[must_use]
pub fn website_url(mut self, website_url: impl Into<String>) -> Self {
self.website_url = Some(website_url.into());
self
}
#[must_use]
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
#[must_use]
pub fn icon(mut self, icon: Icon) -> Self {
self.icons.get_or_insert_with(Vec::new).push(icon);
self
}
#[must_use]
pub fn icons(mut self, icons: impl IntoIterator<Item = Icon>) -> Self {
self.icons = Some(icons.into_iter().collect());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientInfo {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "websiteUrl", skip_serializing_if = "Option::is_none")]
pub website_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icons: Option<Vec<Icon>>,
}
impl ClientInfo {
#[must_use]
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
Self {
name: name.into(),
title: None,
version: version.into(),
description: None,
website_url: None,
icons: None,
}
}
#[must_use]
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
#[must_use]
pub fn website_url(mut self, website_url: impl Into<String>) -> Self {
self.website_url = Some(website_url.into());
self
}
#[must_use]
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
#[must_use]
pub fn icon(mut self, icon: Icon) -> Self {
self.icons.get_or_insert_with(Vec::new).push(icon);
self
}
#[must_use]
pub fn icons(mut self, icons: impl IntoIterator<Item = Icon>) -> Self {
self.icons = Some(icons.into_iter().collect());
self
}
}
#[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,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
}
impl InitializeRequest {
#[must_use]
pub fn new(client_info: ClientInfo, capabilities: ClientCapabilities) -> Self {
Self {
protocol_version: PROTOCOL_VERSION.to_string(),
capabilities,
client_info,
meta: None,
}
}
}
#[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>,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
}
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,
meta: 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 {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PingRequest {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PingResult {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
}
#[cfg(test)]
mod tests {
#[test]
fn elicitation_form_url_capability_semantics() {
use super::ClientCapabilities;
let form = ClientCapabilities::default().with_elicitation();
assert!(form.has_elicitation());
assert!(form.has_form_elicitation());
assert!(!form.has_url_elicitation());
assert_eq!(
serde_json::to_value(&form.elicitation).unwrap(),
serde_json::json!({}),
"empty elicitation must still serialize as {{}} for compatibility"
);
let url = ClientCapabilities::default().with_url_elicitation();
assert!(url.has_url_elicitation());
assert!(!url.has_form_elicitation());
let both = ClientCapabilities::default()
.with_form_elicitation()
.with_url_elicitation();
assert!(both.has_form_elicitation());
assert!(both.has_url_elicitation());
}
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(())
}
#[test]
fn implementation_description_and_website_url_round_trip()
-> Result<(), Box<dyn std::error::Error>> {
let s = ServerInfo::new("s", "1.0")
.description("does things")
.website_url("https://example.com");
let json = serde_json::to_value(&s)?;
assert_eq!(json["description"], "does things");
assert_eq!(json["websiteUrl"], "https://example.com");
let c: ClientInfo = serde_json::from_value(serde_json::json!({
"name": "c", "version": "2.0",
"description": "a client", "websiteUrl": "https://client.example"
}))?;
assert_eq!(c.description.as_deref(), Some("a client"));
assert_eq!(c.website_url.as_deref(), Some("https://client.example"));
Ok(())
}
}