use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
pub const LATEST_PROTOCOL_VERSION: &str = "2024-11-05";
pub const DEFAULT_NEGOTIATED_VERSION: &str = "2024-11-05";
pub const JSONRPC_VERSION: &str = "2.0";
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(untagged)]
pub enum RequestId {
String(String),
Int(i64),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(untagged)]
pub enum ProgressToken {
String(String),
Int(i64),
}
pub type Cursor = String;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum Role {
User,
Assistant,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcRequest {
pub jsonrpc: String,
pub id: RequestId,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
#[serde(skip)]
pub transport_metadata: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcNotification {
pub jsonrpc: String,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcResponse {
pub jsonrpc: String,
pub id: RequestId,
pub result: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorData {
pub code: i64,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcError {
pub jsonrpc: String,
pub id: RequestId,
pub error: ErrorData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JsonRpcMessage {
Request(JsonRpcRequest),
Notification(JsonRpcNotification),
Response(JsonRpcResponse),
Error(JsonRpcError),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BaseMetadata {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Icon {
pub src: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sizes: Option<Vec<String>>,
}
fn default_text_type() -> String {
"text".to_string()
}
fn default_image_type() -> String {
"image".to_string()
}
fn default_resource_type() -> String {
"resource".to_string()
}
fn default_audio_type() -> String {
"audio".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TextContent {
#[serde(rename = "type", default = "default_text_type")]
pub type_: String, pub text: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ImageContent {
#[serde(rename = "type", default = "default_image_type")]
pub type_: String, pub data: String, pub mime_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EmbeddedResource {
#[serde(rename = "type", default = "default_resource_type")]
pub type_: String, pub resource: ResourceContents,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AudioContent {
#[serde(rename = "type", default = "default_audio_type")]
pub type_: String, pub data: String, pub mime_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourceLink {
#[serde(rename = "type", default = "default_resource_type")]
pub type_: String, pub resource: Resource,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentBlock {
Text(TextContent),
Image(ImageContent),
EmbeddedResource(EmbeddedResource),
Audio(AudioContent),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
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>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourceContents {
pub uri: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub blob: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Implementation {
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub website_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icons: Option<Vec<Icon>>,
#[serde(flatten)]
pub base_metadata: BaseMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Tool {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub input_schema: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_schema: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icons: Option<Vec<Icon>>,
#[serde(flatten)]
pub base_metadata: BaseMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Resource {
pub uri: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icons: Option<Vec<Icon>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(flatten)]
pub base_metadata: BaseMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Prompt {
#[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 icons: Option<Vec<Icon>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(flatten)]
pub base_metadata: BaseMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourceTemplate {
pub uri_template: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
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>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub experimental: Option<HashMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sampling: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub roots: Option<RootsCapability>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServerCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub experimental: Option<HashMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logging: Option<Value>,
#[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>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RootsCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PromptsCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourcesCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub subscribe: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ToolsCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum LoggingLevel {
Debug,
Info,
Notice,
Warning,
Error,
Critical,
Alert,
Emergency,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LoggingMessageNotification {
pub level: LoggingLevel,
#[serde(skip_serializing_if = "Option::is_none")]
pub logger: Option<String>,
pub data: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum IncludeContext {
None,
ThisServer,
AllServers,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModelHint {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModelPreferences {
#[serde(skip_serializing_if = "Option::is_none")]
pub hints: Option<Vec<ModelHint>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cost_priority: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub speed_priority: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub intelligence_priority: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum SamplingMessageContent {
Text(TextContent),
Image(ImageContent),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SamplingMessage {
pub role: Role,
pub content: SamplingMessageContent,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateMessageRequestParams {
pub messages: Vec<SamplingMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model_preferences: Option<ModelPreferences>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_prompt: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_context: Option<IncludeContext>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f64>,
pub max_tokens: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_sequences: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum StopReason {
EndTurn,
StopSequence,
MaxTokens,
#[serde(untagged)]
Other(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateMessageResult {
pub role: Role,
pub content: SamplingMessageContent,
pub model: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_reason: Option<StopReason>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum CompletionReference {
#[serde(rename = "ref/resource")]
Resource { uri: String },
#[serde(rename = "ref/prompt")]
Prompt { name: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletionArgument {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletionContext {
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompleteRequestParams {
pub reference: CompletionReference,
pub argument: CompletionArgument,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<CompletionContext>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletionValues {
pub values: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub has_more: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompleteResult {
pub completion: CompletionValues,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Root {
pub uri: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListRootsResult {
pub roots: Vec<Root>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InitializeRequestParams {
pub protocol_version: String,
pub capabilities: ClientCapabilities,
pub client_info: Implementation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InitializeResult {
pub protocol_version: String,
pub capabilities: ServerCapabilities,
pub server_info: Implementation,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_logging_level_serialization() {
let level = LoggingLevel::Debug;
let serialized = serde_json::to_string(&level).unwrap();
assert_eq!(serialized, "\"debug\"");
}
#[test]
fn test_initialize_request_serialization() {
let params = InitializeRequestParams {
protocol_version: "2024-11-05".to_string(),
capabilities: ClientCapabilities::default(),
client_info: Implementation {
version: "1.0.0".to_string(),
website_url: None,
icons: None,
base_metadata: BaseMetadata {
name: "test-client".to_string(),
title: None,
},
},
};
let serialized = serde_json::to_value(¶ms).unwrap();
assert_eq!(serialized["protocolVersion"], "2024-11-05");
assert_eq!(serialized["clientInfo"]["name"], "test-client");
}
#[test]
fn test_stop_reason_serialization() {
let reason = StopReason::EndTurn;
assert_eq!(serde_json::to_string(&reason).unwrap(), "\"endTurn\"");
let reason_custom = StopReason::Other("custom_stop".to_string());
assert_eq!(
serde_json::to_string(&reason_custom).unwrap(),
"\"custom_stop\""
);
}
#[test]
fn test_content_block_text_serialize() {
let block = ContentBlock::Text(TextContent {
type_: "text".to_string(),
text: "hello world".to_string(),
annotations: None,
});
let json = serde_json::to_value(&block).unwrap();
assert_eq!(json["type"], "text");
assert_eq!(json["text"], "hello world");
}
#[test]
fn test_content_block_text_deserialize() {
let json = serde_json::json!({"type": "text", "text": "hello world"});
let parsed: ContentBlock = serde_json::from_value(json).unwrap();
match parsed {
ContentBlock::Text(t) => assert_eq!(t.text, "hello world"),
_ => panic!("Expected Text variant"),
}
}
#[test]
fn test_content_block_image_serialize() {
let block = ContentBlock::Image(ImageContent {
type_: "image".to_string(),
data: "aGVsbG8=".to_string(),
mime_type: "image/png".to_string(),
annotations: None,
});
let json = serde_json::to_value(&block).unwrap();
assert_eq!(json["type"], "image");
assert_eq!(json["mimeType"], "image/png");
assert_eq!(json["data"], "aGVsbG8=");
}
#[test]
fn test_content_block_image_deserialize() {
let json =
serde_json::json!({"type": "image", "data": "aGVsbG8=", "mimeType": "image/png"});
let parsed: ContentBlock = serde_json::from_value(json).unwrap();
match parsed {
ContentBlock::Image(i) => {
assert_eq!(i.mime_type, "image/png");
assert_eq!(i.data, "aGVsbG8=");
}
_ => panic!("Expected Image variant"),
}
}
#[test]
fn test_content_block_audio_serialize() {
let block = ContentBlock::Audio(AudioContent {
type_: "audio".to_string(),
data: "YXVkaW8=".to_string(),
mime_type: "audio/mp3".to_string(),
annotations: None,
});
let json = serde_json::to_value(&block).unwrap();
assert_eq!(json["type"], "audio");
assert_eq!(json["mimeType"], "audio/mp3");
}
#[test]
fn test_content_block_audio_deserialize() {
let json =
serde_json::json!({"type": "audio", "data": "YXVkaW8=", "mimeType": "audio/mp3"});
let parsed: ContentBlock = serde_json::from_value(json).unwrap();
match parsed {
ContentBlock::Audio(a) => {
assert_eq!(a.mime_type, "audio/mp3");
assert_eq!(a.data, "YXVkaW8=");
}
_ => panic!("Expected Audio variant"),
}
}
#[test]
fn test_role_serialization() {
assert_eq!(serde_json::to_string(&Role::User).unwrap(), "\"user\"");
assert_eq!(
serde_json::to_string(&Role::Assistant).unwrap(),
"\"assistant\""
);
let parsed: Role = serde_json::from_str("\"user\"").unwrap();
assert_eq!(parsed, Role::User);
}
#[test]
fn test_notification_roundtrip() {
let notif = JsonRpcNotification {
jsonrpc: JSONRPC_VERSION.to_string(),
method: "notifications/tools/list_changed".to_string(),
params: None,
};
let json = serde_json::to_string(¬if).unwrap();
let parsed: JsonRpcNotification = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.method, "notifications/tools/list_changed");
assert!(parsed.params.is_none());
}
#[test]
fn test_response_roundtrip() {
let resp = JsonRpcResponse {
jsonrpc: JSONRPC_VERSION.to_string(),
id: RequestId::Int(42),
result: serde_json::json!({"status": "ok"}),
};
let json = serde_json::to_string(&resp).unwrap();
let parsed: JsonRpcResponse = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.id, RequestId::Int(42));
assert_eq!(parsed.result["status"], "ok");
}
#[test]
fn test_resource_contents_text() {
let rc = ResourceContents {
uri: "file:///test.txt".to_string(),
mime_type: Some("text/plain".to_string()),
text: Some("file content".to_string()),
blob: None,
};
let json = serde_json::to_string(&rc).unwrap();
let parsed: ResourceContents = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.uri, "file:///test.txt");
assert_eq!(parsed.text.unwrap(), "file content");
assert!(parsed.blob.is_none());
}
#[test]
fn test_resource_contents_blob() {
let rc = ResourceContents {
uri: "file:///image.png".to_string(),
mime_type: Some("image/png".to_string()),
text: None,
blob: Some("base64data".to_string()),
};
let json = serde_json::to_string(&rc).unwrap();
let parsed: ResourceContents = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.blob.unwrap(), "base64data");
assert!(parsed.text.is_none());
}
#[test]
fn test_request_id_string_variant() {
let id = RequestId::String("abc-123".to_string());
let json = serde_json::to_string(&id).unwrap();
assert_eq!(json, "\"abc-123\"");
let parsed: RequestId = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, RequestId::String("abc-123".to_string()));
}
#[test]
fn test_server_capabilities_default_empty() {
let caps = ServerCapabilities::default();
let json = serde_json::to_value(&caps).unwrap();
let obj = json.as_object().unwrap();
assert!(obj.is_empty());
}
}