use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub const LATEST_PROTOCOL_VERSION: &str = "2025-06-18";
pub const JSONRPC_VERSION: &str = "2.0";
pub const PROTOCOL_VERSION: &str = LATEST_PROTOCOL_VERSION;
pub type ProgressToken = serde_json::Value;
pub type Cursor = String;
pub type RequestId = serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum JsonRpcId {
String(String),
Number(i64),
Null,
}
impl From<i64> for JsonRpcId {
fn from(value: i64) -> Self {
JsonRpcId::Number(value)
}
}
impl From<String> for JsonRpcId {
fn from(value: String) -> Self {
JsonRpcId::String(value)
}
}
impl From<&str> for JsonRpcId {
fn from(value: &str) -> Self {
JsonRpcId::String(value.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BaseMetadata {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Implementation {
pub name: String,
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}
impl Implementation {
pub fn new<S: Into<String>>(name: S, version: S) -> Self {
Self {
name: name.into(),
version: version.into(),
title: None,
}
}
pub fn with_title<S: Into<String>>(name: S, version: S, title: S) -> Self {
Self {
name: name.into(),
version: version.into(),
title: Some(title.into()),
}
}
}
pub type ServerInfo = Implementation;
pub type ClientInfo = Implementation;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct ServerCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub prompts: Option<PromptsCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resources: Option<ResourcesCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<ToolsCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sampling: Option<SamplingCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logging: Option<LoggingCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completions: Option<CompletionsCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub experimental: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct ClientCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub sampling: Option<SamplingCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub roots: Option<RootsCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub elicitation: Option<ElicitationCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub experimental: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct PromptsCapability {
#[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct ResourcesCapability {
#[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, Serialize, Deserialize, PartialEq, Default)]
pub struct ToolsCapability {
#[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct SamplingCapability {
#[serde(flatten)]
pub additional_properties: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct LoggingCapability {
#[serde(flatten)]
pub additional_properties: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct CompletionsCapability {
#[serde(flatten)]
pub additional_properties: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct RootsCapability {
#[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct ElicitationCapability {
#[serde(flatten)]
pub additional_properties: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct Annotations {
#[serde(skip_serializing_if = "Option::is_none")]
pub audience: Option<Vec<Role>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<f64>,
#[serde(rename = "lastModified", skip_serializing_if = "Option::is_none")]
pub last_modified: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub danger: Option<DangerLevel>,
#[serde(skip_serializing_if = "Option::is_none")]
pub destructive: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub read_only: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TextContent {
#[serde(rename = "type")]
pub content_type: String, pub text: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ImageContent {
#[serde(rename = "type")]
pub content_type: String, pub data: String,
#[serde(rename = "mimeType")]
pub mime_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AudioContent {
#[serde(rename = "type")]
pub content_type: String, pub data: String,
#[serde(rename = "mimeType")]
pub mime_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ResourceLink {
#[serde(rename = "type")]
pub content_type: String, pub uri: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EmbeddedResource {
#[serde(rename = "type")]
pub content_type: String, pub resource: ResourceContents,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type")]
pub enum ContentBlock {
#[serde(rename = "text")]
Text {
text: String,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<HashMap<String, serde_json::Value>>,
},
#[serde(rename = "image")]
Image {
data: String,
#[serde(rename = "mimeType")]
mime_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<HashMap<String, serde_json::Value>>,
},
#[serde(rename = "audio")]
Audio {
data: String,
#[serde(rename = "mimeType")]
mime_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<HashMap<String, serde_json::Value>>,
},
#[serde(rename = "resource_link")]
ResourceLink {
uri: String,
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<HashMap<String, serde_json::Value>>,
},
#[serde(rename = "resource")]
Resource {
resource: ResourceContents,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<HashMap<String, serde_json::Value>>,
},
}
pub type Content = ContentBlock;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct ToolAnnotations {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "readOnlyHint", skip_serializing_if = "Option::is_none")]
pub read_only_hint: Option<bool>,
#[serde(rename = "destructiveHint", skip_serializing_if = "Option::is_none")]
pub destructive_hint: Option<bool>,
#[serde(rename = "idempotentHint", skip_serializing_if = "Option::is_none")]
pub idempotent_hint: Option<bool>,
#[serde(rename = "openWorldHint", skip_serializing_if = "Option::is_none")]
pub open_world_hint: Option<bool>,
}
impl ToolAnnotations {
pub fn new() -> Self {
Self::default()
}
pub fn with_title<S: Into<String>>(mut self, title: S) -> Self {
self.title = Some(title.into());
self
}
pub fn read_only(mut self) -> Self {
self.read_only_hint = Some(true);
self
}
pub fn destructive(mut self) -> Self {
self.destructive_hint = Some(true);
self
}
pub fn idempotent(mut self) -> Self {
self.idempotent_hint = Some(true);
self
}
pub fn open_world(mut self) -> Self {
self.open_world_hint = Some(true);
self
}
pub fn closed_world(mut self) -> Self {
self.open_world_hint = Some(false);
self
}
}
impl From<&crate::core::tool_metadata::ToolBehaviorHints> for ToolAnnotations {
fn from(hints: &crate::core::tool_metadata::ToolBehaviorHints) -> Self {
Self {
title: None, read_only_hint: hints.read_only,
destructive_hint: hints.destructive,
idempotent_hint: hints.idempotent,
open_world_hint: if hints.requires_auth.unwrap_or(false)
|| hints.resource_intensive.unwrap_or(false)
{
Some(true)
} else {
None
},
}
}
}
impl From<&crate::core::tool_metadata::EnhancedToolMetadata> for ToolAnnotations {
fn from(metadata: &crate::core::tool_metadata::EnhancedToolMetadata) -> Self {
ToolAnnotations::from(&metadata.behavior_hints)
}
}
impl ToolAnnotations {
pub fn from_enhanced_metadata(
metadata: &crate::core::tool_metadata::EnhancedToolMetadata,
title_override: Option<String>,
) -> Self {
let mut annotations = Self::from(metadata);
if let Some(title) = title_override {
annotations.title = Some(title);
}
annotations
}
pub fn from_behavior_hints(hints: &crate::core::tool_metadata::ToolBehaviorHints) -> Self {
Self::from(hints)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Tool {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "inputSchema")]
pub input_schema: ToolInputSchema,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<ToolAnnotations>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolInputSchema {
#[serde(rename = "type")]
pub schema_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<HashMap<String, serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<Vec<String>>,
#[serde(flatten)]
pub additional_properties: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CallToolResult {
pub content: Vec<ContentBlock>,
#[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>,
#[serde(rename = "structuredContent", skip_serializing_if = "Option::is_none")]
pub structured_content: Option<serde_json::Value>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
pub type ToolInfo = Tool;
pub type ToolResult = CallToolResult;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Resource {
pub uri: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ResourceTemplate {
#[serde(rename = "uriTemplate")]
pub uri_template: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum ResourceContents {
Text {
uri: String,
#[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
text: String,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<HashMap<String, serde_json::Value>>,
},
Blob {
uri: String,
#[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
blob: String,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<HashMap<String, serde_json::Value>>,
},
}
impl ResourceContents {
pub fn uri(&self) -> &str {
match self {
ResourceContents::Text { uri, .. } => uri,
ResourceContents::Blob { uri, .. } => uri,
}
}
}
pub type ResourceInfo = Resource;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Prompt {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<Vec<PromptArgument>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PromptArgument {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Role {
User,
Assistant,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PromptMessage {
pub role: Role,
pub content: ContentBlock,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GetPromptResult {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub messages: Vec<PromptMessage>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
pub type PromptInfo = Prompt;
pub type PromptResult = GetPromptResult;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SamplingMessage {
pub role: Role,
pub content: SamplingContent,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type")]
pub enum SamplingContent {
#[serde(rename = "text")]
Text {
text: String,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<HashMap<String, serde_json::Value>>,
},
#[serde(rename = "image")]
Image {
data: String,
#[serde(rename = "mimeType")]
mime_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<HashMap<String, serde_json::Value>>,
},
#[serde(rename = "audio")]
Audio {
data: String,
#[serde(rename = "mimeType")]
mime_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<HashMap<String, serde_json::Value>>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ModelHint {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct ModelPreferences {
#[serde(rename = "costPriority", skip_serializing_if = "Option::is_none")]
pub cost_priority: Option<f64>,
#[serde(rename = "speedPriority", skip_serializing_if = "Option::is_none")]
pub speed_priority: Option<f64>,
#[serde(
rename = "intelligencePriority",
skip_serializing_if = "Option::is_none"
)]
pub intelligence_priority: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hints: Option<Vec<ModelHint>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CreateMessageResult {
pub role: Role,
pub content: SamplingContent,
pub model: String,
#[serde(rename = "stopReason", skip_serializing_if = "Option::is_none")]
pub stop_reason: Option<StopReason>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum StopReason {
EndTurn,
StopSequence,
MaxTokens,
#[serde(untagged)]
Other(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type")]
pub enum PrimitiveSchemaDefinition {
#[serde(rename = "string")]
String {
#[serde(skip_serializing_if = "Option::is_none")]
title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
min_length: Option<u32>,
#[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
max_length: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
format: Option<String>,
#[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
enum_values: Option<Vec<String>>,
#[serde(rename = "enumNames", skip_serializing_if = "Option::is_none")]
enum_names: Option<Vec<String>>,
},
#[serde(rename = "number")]
Number {
#[serde(skip_serializing_if = "Option::is_none")]
title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
minimum: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
maximum: Option<i32>,
},
#[serde(rename = "integer")]
Integer {
#[serde(skip_serializing_if = "Option::is_none")]
title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
minimum: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
maximum: Option<i32>,
},
#[serde(rename = "boolean")]
Boolean {
#[serde(skip_serializing_if = "Option::is_none")]
title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
default: Option<bool>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ElicitationSchema {
#[serde(rename = "type")]
pub schema_type: String,
pub properties: HashMap<String, PrimitiveSchemaDefinition>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ElicitationAction {
Accept,
Decline,
Cancel,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum LoggingLevel {
Debug,
Info,
Notice,
Warning,
Error,
Critical,
Alert,
Emergency,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct JsonRpcRequest {
pub jsonrpc: String,
pub id: RequestId,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct JsonRpcResponse {
pub jsonrpc: String,
pub id: RequestId,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct JsonRpcError {
pub jsonrpc: String,
pub id: RequestId,
pub error: ErrorObject,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ErrorObject {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct JsonRpcNotification {
pub jsonrpc: String,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum JsonRpcMessage {
Request(JsonRpcRequest),
Response(JsonRpcResponse),
Error(JsonRpcError),
Notification(JsonRpcNotification),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Request {
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<RequestParams>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RequestParams {
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<RequestMeta>,
#[serde(flatten)]
pub params: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RequestMeta {
#[serde(rename = "progressToken", skip_serializing_if = "Option::is_none")]
pub progress_token: Option<ProgressToken>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Notification {
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<NotificationParams>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct NotificationParams {
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
#[serde(flatten)]
pub params: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PaginatedRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<Cursor>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PaginatedResult {
#[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<Cursor>,
}
impl ContentBlock {
pub fn text<S: Into<String>>(text: S) -> Self {
Self::Text {
text: text.into(),
annotations: None,
meta: None,
}
}
pub fn image<S: Into<String>>(data: S, mime_type: S) -> Self {
Self::Image {
data: data.into(),
mime_type: mime_type.into(),
annotations: None,
meta: None,
}
}
pub fn audio<S: Into<String>>(data: S, mime_type: S) -> Self {
Self::Audio {
data: data.into(),
mime_type: mime_type.into(),
annotations: None,
meta: None,
}
}
pub fn resource_link<S: Into<String>>(uri: S, name: S) -> Self {
Self::ResourceLink {
uri: uri.into(),
name: name.into(),
description: None,
mime_type: None,
size: None,
title: None,
annotations: None,
meta: None,
}
}
pub fn embedded_resource(resource: ResourceContents) -> Self {
Self::Resource {
resource,
annotations: None,
meta: None,
}
}
pub fn resource<S: Into<String>>(uri: S) -> Self {
let uri_str = uri.into();
Self::resource_link(uri_str.clone(), uri_str)
}
}
impl SamplingContent {
pub fn text<S: Into<String>>(text: S) -> Self {
Self::Text {
text: text.into(),
annotations: None,
meta: None,
}
}
pub fn image<S: Into<String>>(data: S, mime_type: S) -> Self {
Self::Image {
data: data.into(),
mime_type: mime_type.into(),
annotations: None,
meta: None,
}
}
pub fn audio<S: Into<String>>(data: S, mime_type: S) -> Self {
Self::Audio {
data: data.into(),
mime_type: mime_type.into(),
annotations: None,
meta: None,
}
}
}
impl Annotations {
pub fn new() -> Self {
Self {
audience: None,
priority: None,
last_modified: None,
danger: None,
destructive: None,
read_only: None,
}
}
pub fn with_priority(mut self, priority: f64) -> Self {
self.priority = Some(priority.clamp(0.0, 1.0));
self
}
pub fn for_audience(mut self, audience: Vec<Role>) -> Self {
self.audience = Some(audience);
self
}
pub fn with_last_modified<S: Into<String>>(mut self, timestamp: S) -> Self {
self.last_modified = Some(timestamp.into());
self
}
pub fn for_audience_legacy(self, _audience: Vec<AnnotationAudience>) -> Self {
self
}
pub fn with_danger_level(self, _level: DangerLevel) -> Self {
self
}
pub fn danger(&self) -> Option<DangerLevel> {
None
}
pub fn audience(&self) -> Option<Vec<AnnotationAudience>> {
None
}
pub fn read_only(mut self) -> Self {
self.read_only = Some(true);
self
}
pub fn destructive(mut self, level: DangerLevel) -> Self {
self.destructive = Some(true);
self.danger = Some(level);
self
}
}
impl Tool {
pub fn new<S: Into<String>>(name: S, description: S) -> Self {
Self {
name: name.into(),
description: Some(description.into()),
input_schema: ToolInputSchema {
schema_type: "object".to_string(),
properties: None,
required: None,
additional_properties: HashMap::new(),
},
annotations: None,
title: None,
meta: None,
}
}
pub fn with_title<S: Into<String>>(mut self, title: S) -> Self {
self.title = Some(title.into());
self
}
pub fn with_annotations(mut self, annotations: ToolAnnotations) -> Self {
self.annotations = Some(annotations);
self
}
}
impl Resource {
pub fn new<S: Into<String>>(uri: S, name: S) -> Self {
Self {
uri: uri.into(),
name: name.into(),
description: None,
mime_type: None,
annotations: None,
size: None,
title: None,
meta: None,
}
}
pub fn from_legacy<S: Into<String>>(uri: S, name: Option<S>) -> Self {
Self {
uri: uri.into(),
name: name
.map(|n| n.into())
.unwrap_or_else(|| "Unnamed Resource".to_string()),
description: None,
mime_type: None,
annotations: None,
size: None,
title: None,
meta: None,
}
}
pub fn with_title<S: Into<String>>(mut self, title: S) -> Self {
self.title = Some(title.into());
self
}
pub fn with_description<S: Into<String>>(mut self, description: S) -> Self {
self.description = Some(description.into());
self
}
}
impl ResourceTemplate {
pub fn new<S: Into<String>>(uri_template: S, name: S) -> Self {
Self {
uri_template: uri_template.into(),
name: name.into(),
description: None,
mime_type: None,
annotations: None,
title: None,
meta: None,
}
}
pub fn from_legacy<S: Into<String>>(uri_template: S, name: Option<S>) -> Self {
Self {
uri_template: uri_template.into(),
name: name
.map(|n| n.into())
.unwrap_or_else(|| "Unnamed Template".to_string()),
description: None,
mime_type: None,
annotations: None,
title: None,
meta: None,
}
}
pub fn with_title<S: Into<String>>(mut self, title: S) -> Self {
self.title = Some(title.into());
self
}
}
impl Prompt {
pub fn new<S: Into<String>>(name: S) -> Self {
Self {
name: name.into(),
description: None,
arguments: None,
title: None,
meta: None,
}
}
pub fn with_title<S: Into<String>>(mut self, title: S) -> Self {
self.title = Some(title.into());
self
}
pub fn with_description<S: Into<String>>(mut self, description: S) -> Self {
self.description = Some(description.into());
self
}
}
impl PromptArgument {
pub fn new<S: Into<String>>(name: S) -> Self {
Self {
name: name.into(),
description: None,
required: None,
title: None,
}
}
pub fn with_title<S: Into<String>>(mut self, title: S) -> Self {
self.title = Some(title.into());
self
}
pub fn required(mut self, required: bool) -> Self {
self.required = Some(required);
self
}
}
impl JsonRpcRequest {
pub fn new<T: Serialize>(
id: RequestId,
method: String,
params: Option<T>,
) -> std::result::Result<Self, serde_json::Error> {
let params = match params {
Some(p) => Some(serde_json::to_value(p)?),
None => None,
};
Ok(Self {
jsonrpc: JSONRPC_VERSION.to_string(),
id,
method,
params,
})
}
}
impl JsonRpcResponse {
pub fn success<T: Serialize>(
id: RequestId,
result: T,
) -> std::result::Result<Self, serde_json::Error> {
Ok(Self {
jsonrpc: JSONRPC_VERSION.to_string(),
id,
result: Some(serde_json::to_value(result)?),
})
}
}
impl JsonRpcError {
pub fn error(
id: RequestId,
code: i32,
message: String,
data: Option<serde_json::Value>,
) -> Self {
Self {
jsonrpc: JSONRPC_VERSION.to_string(),
id,
error: ErrorObject {
code,
message,
data,
},
}
}
}
impl JsonRpcNotification {
pub fn new<T: Serialize>(
method: String,
params: Option<T>,
) -> std::result::Result<Self, serde_json::Error> {
let params = match params {
Some(p) => Some(serde_json::to_value(p)?),
None => None,
};
Ok(Self {
jsonrpc: JSONRPC_VERSION.to_string(),
method,
params,
})
}
}
impl SamplingMessage {
pub fn user_text<S: Into<String>>(text: S) -> Self {
Self {
role: Role::User,
content: SamplingContent::text(text),
}
}
pub fn assistant_text<S: Into<String>>(text: S) -> Self {
Self {
role: Role::Assistant,
content: SamplingContent::text(text),
}
}
pub fn user_image<S: Into<String>>(data: S, mime_type: S) -> Self {
Self {
role: Role::User,
content: SamplingContent::image(data, mime_type),
}
}
pub fn user_audio<S: Into<String>>(data: S, mime_type: S) -> Self {
Self {
role: Role::User,
content: SamplingContent::audio(data, mime_type),
}
}
}
pub mod error_codes {
pub const PARSE_ERROR: i32 = -32700;
pub const INVALID_REQUEST: i32 = -32600;
pub const METHOD_NOT_FOUND: i32 = -32601;
pub const INVALID_PARAMS: i32 = -32602;
pub const INTERNAL_ERROR: i32 = -32603;
pub const TOOL_NOT_FOUND: i32 = -32000;
pub const RESOURCE_NOT_FOUND: i32 = -32001;
pub const PROMPT_NOT_FOUND: i32 = -32002;
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_protocol_version() {
assert_eq!(LATEST_PROTOCOL_VERSION, "2025-06-18");
assert_eq!(JSONRPC_VERSION, "2.0");
}
#[test]
fn test_content_block_types() {
let text = ContentBlock::text("Hello, world!");
let json = serde_json::to_value(&text).unwrap();
assert_eq!(json["type"], "text");
assert_eq!(json["text"], "Hello, world!");
let audio = ContentBlock::audio("base64data", "audio/wav");
let json = serde_json::to_value(&audio).unwrap();
assert_eq!(json["type"], "audio");
assert_eq!(json["data"], "base64data");
assert_eq!(json["mimeType"], "audio/wav");
let resource_link = ContentBlock::resource_link("file:///test.txt", "test file");
let json = serde_json::to_value(&resource_link).unwrap();
assert_eq!(json["type"], "resource_link");
assert_eq!(json["uri"], "file:///test.txt");
assert_eq!(json["name"], "test file");
}
#[test]
fn test_annotations() {
let annotations = Annotations::new()
.with_priority(0.8)
.for_audience(vec![Role::User, Role::Assistant])
.with_last_modified("2025-01-12T15:00:58Z");
assert_eq!(annotations.priority, Some(0.8));
assert_eq!(
annotations.audience,
Some(vec![Role::User, Role::Assistant])
);
assert_eq!(
annotations.last_modified,
Some("2025-01-12T15:00:58Z".to_string())
);
}
#[test]
fn test_tool_with_title() {
let tool = Tool::new("file_reader", "Read files safely")
.with_title("File Reader Tool")
.with_annotations(ToolAnnotations::new().with_title("File Reader"));
assert_eq!(tool.name, "file_reader");
assert_eq!(tool.title, Some("File Reader Tool".to_string()));
assert!(tool.annotations.is_some());
assert_eq!(
tool.annotations.unwrap().title,
Some("File Reader".to_string())
);
}
#[test]
fn test_server_capabilities_2025_06_18() {
let caps = ServerCapabilities {
tools: Some(ToolsCapability {
list_changed: Some(true),
}),
completions: Some(CompletionsCapability::default()),
logging: Some(LoggingCapability::default()),
experimental: Some(HashMap::new()),
..Default::default()
};
let json = serde_json::to_value(&caps).unwrap();
assert!(json["tools"]["listChanged"].as_bool().unwrap());
assert!(json["completions"].is_object());
assert!(json["logging"].is_object());
assert!(json["experimental"].is_object());
}
#[test]
fn test_client_capabilities_with_elicitation() {
let caps = ClientCapabilities {
elicitation: Some(ElicitationCapability::default()),
roots: Some(RootsCapability {
list_changed: Some(true),
}),
..Default::default()
};
let json = serde_json::to_value(&caps).unwrap();
assert!(json["elicitation"].is_object());
assert!(json["roots"]["listChanged"].as_bool().unwrap());
}
#[test]
fn test_implementation_with_title() {
let impl_info = Implementation::with_title("my-server", "1.0.0", "My Awesome Server");
assert_eq!(impl_info.name, "my-server");
assert_eq!(impl_info.version, "1.0.0");
assert_eq!(impl_info.title, Some("My Awesome Server".to_string()));
}
#[test]
fn test_model_preferences_enhanced() {
let prefs = ModelPreferences {
cost_priority: Some(0.3),
speed_priority: Some(0.7),
intelligence_priority: Some(0.9),
hints: Some(vec![ModelHint {
name: Some("claude".to_string()),
}]),
};
let json = serde_json::to_value(&prefs).unwrap();
assert_eq!(json["costPriority"], 0.3);
assert_eq!(json["speedPriority"], 0.7);
assert_eq!(json["intelligencePriority"], 0.9);
assert!(json["hints"].is_array());
}
#[test]
fn test_call_tool_result_with_structured_content() {
let result = CallToolResult {
content: vec![ContentBlock::text("Operation completed")],
is_error: Some(false),
structured_content: Some(json!({"status": "success", "count": 42})),
meta: None,
};
let json = serde_json::to_value(&result).unwrap();
assert!(json["content"].is_array());
assert_eq!(json["isError"], false);
assert_eq!(json["structuredContent"]["status"], "success");
assert_eq!(json["structuredContent"]["count"], 42);
}
#[test]
fn test_sampling_content_types() {
let text = SamplingContent::text("Hello");
let image = SamplingContent::image("data", "image/png");
let audio = SamplingContent::audio("data", "audio/wav");
let text_json = serde_json::to_value(&text).unwrap();
let image_json = serde_json::to_value(&image).unwrap();
let audio_json = serde_json::to_value(&audio).unwrap();
assert_eq!(text_json["type"], "text");
assert_eq!(image_json["type"], "image");
assert_eq!(audio_json["type"], "audio");
}
}
pub type JsonRpcBatchRequest = Vec<JsonRpcRequest>;
pub type JsonRpcBatchResponse = Vec<JsonRpcResponse>;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum JsonRpcRequestOrNotification {
Request(JsonRpcRequest),
Notification(JsonRpcNotification),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum JsonRpcResponseOrError {
Response(JsonRpcResponse),
Error(JsonRpcError),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AnnotationAudience {
User,
Developer,
System,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum DangerLevel {
Safe,
Low,
Medium,
High,
}