use serde::{Deserialize, Serialize};
use crate::jsonrpc::RequestId;
use crate::types::{
ClientCapabilities, ClientInfo, Content, Prompt, PromptMessage, Resource, ResourceContent,
ResourceTemplate, ServerCapabilities, ServerInfo, Tool,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ProgressMarker {
String(String),
Number(i64),
}
impl From<String> for ProgressMarker {
fn from(s: String) -> Self {
ProgressMarker::String(s)
}
}
impl From<&str> for ProgressMarker {
fn from(s: &str) -> Self {
ProgressMarker::String(s.to_owned())
}
}
impl From<i64> for ProgressMarker {
fn from(n: i64) -> Self {
ProgressMarker::Number(n)
}
}
impl std::fmt::Display for ProgressMarker {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProgressMarker::String(s) => write!(f, "{s}"),
ProgressMarker::Number(n) => write!(f, "{n}"),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RequestMeta {
#[serde(rename = "progressTo\x6ben", skip_serializing_if = "Option::is_none")]
pub progress_marker: Option<ProgressMarker>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitializeParams {
#[serde(rename = "protocolVersion")]
pub protocol_version: String,
pub capabilities: ClientCapabilities,
#[serde(rename = "clientInfo")]
pub client_info: ClientInfo,
}
#[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>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListToolsParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(
rename = "includeTags",
default,
skip_serializing_if = "Option::is_none"
)]
pub include_tags: Option<Vec<String>>,
#[serde(
rename = "excludeTags",
default,
skip_serializing_if = "Option::is_none"
)]
pub exclude_tags: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListToolsResult {
pub tools: Vec<Tool>,
#[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallToolParams {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arguments: Option<serde_json::Value>,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<RequestMeta>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallToolResult {
pub content: Vec<Content>,
#[serde(
rename = "isError",
default,
skip_serializing_if = "std::ops::Not::not"
)]
pub is_error: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListResourcesParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(
rename = "includeTags",
default,
skip_serializing_if = "Option::is_none"
)]
pub include_tags: Option<Vec<String>>,
#[serde(
rename = "excludeTags",
default,
skip_serializing_if = "Option::is_none"
)]
pub exclude_tags: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListResourcesResult {
pub resources: Vec<Resource>,
#[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListResourceTemplatesParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(
rename = "includeTags",
default,
skip_serializing_if = "Option::is_none"
)]
pub include_tags: Option<Vec<String>>,
#[serde(
rename = "excludeTags",
default,
skip_serializing_if = "Option::is_none"
)]
pub exclude_tags: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListResourceTemplatesResult {
#[serde(rename = "resourceTemplates")]
pub resource_templates: Vec<ResourceTemplate>,
#[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReadResourceParams {
pub uri: String,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<RequestMeta>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReadResourceResult {
pub contents: Vec<ResourceContent>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscribeResourceParams {
pub uri: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnsubscribeResourceParams {
pub uri: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListPromptsParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(
rename = "includeTags",
default,
skip_serializing_if = "Option::is_none"
)]
pub include_tags: Option<Vec<String>>,
#[serde(
rename = "excludeTags",
default,
skip_serializing_if = "Option::is_none"
)]
pub exclude_tags: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListPromptsResult {
pub prompts: Vec<Prompt>,
#[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetPromptParams {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arguments: Option<std::collections::HashMap<String, String>>,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<RequestMeta>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetPromptResult {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub messages: Vec<PromptMessage>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LogLevel {
Debug,
Info,
Warning,
Error,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SetLogLevelParams {
pub level: LogLevel,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CancelledParams {
#[serde(rename = "requestId")]
pub request_id: RequestId,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(rename = "awaitCleanup", skip_serializing_if = "Option::is_none")]
pub await_cleanup: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgressParams {
#[serde(rename = "progressTo\x6ben")]
pub progress_marker: ProgressMarker,
pub progress: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
impl ProgressParams {
#[must_use]
pub fn new(marker: impl Into<ProgressMarker>, progress: f64) -> Self {
Self {
progress_marker: marker.into(),
progress,
total: None,
message: None,
}
}
#[must_use]
pub fn with_total(marker: impl Into<ProgressMarker>, progress: f64, total: f64) -> Self {
Self {
progress_marker: marker.into(),
progress,
total: Some(total),
message: None,
}
}
#[must_use]
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into());
self
}
#[must_use]
pub fn fraction(&self) -> Option<f64> {
self.total
.map(|t| if t > 0.0 { self.progress / t } else { 0.0 })
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceUpdatedNotificationParams {
pub uri: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogMessageParams {
pub level: LogLevel,
#[serde(skip_serializing_if = "Option::is_none")]
pub logger: Option<String>,
pub data: serde_json::Value,
}
use crate::types::{TaskId, TaskInfo, TaskResult, TaskStatus};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListTasksParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<TaskStatus>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListTasksResult {
pub tasks: Vec<TaskInfo>,
#[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetTaskParams {
pub id: TaskId,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetTaskResult {
pub task: TaskInfo,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<TaskResult>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CancelTaskParams {
pub id: TaskId,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CancelTaskResult {
pub cancelled: bool,
pub task: TaskInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubmitTaskParams {
#[serde(rename = "taskType")]
pub task_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubmitTaskResult {
pub task: TaskInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskStatusNotificationParams {
pub id: TaskId,
pub status: TaskStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub progress: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<TaskResult>,
}
use crate::types::{ModelPreferences, SamplingContent, SamplingMessage, StopReason};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateMessageParams {
pub messages: Vec<SamplingMessage>,
#[serde(rename = "maxTo\x6bens")]
pub max_tokens: u32,
#[serde(rename = "systemPrompt", skip_serializing_if = "Option::is_none")]
pub system_prompt: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f64>,
#[serde(
rename = "stopSequences",
default,
skip_serializing_if = "Vec::is_empty"
)]
pub stop_sequences: Vec<String>,
#[serde(rename = "modelPreferences", skip_serializing_if = "Option::is_none")]
pub model_preferences: Option<ModelPreferences>,
#[serde(rename = "includeContext", skip_serializing_if = "Option::is_none")]
pub include_context: Option<IncludeContext>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<RequestMeta>,
}
impl CreateMessageParams {
#[must_use]
pub fn new(messages: Vec<SamplingMessage>, max_tokens: u32) -> Self {
Self {
messages,
max_tokens,
system_prompt: None,
temperature: None,
stop_sequences: Vec::new(),
model_preferences: None,
include_context: None,
meta: None,
}
}
#[must_use]
pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.system_prompt = Some(prompt.into());
self
}
#[must_use]
pub fn with_temperature(mut self, temp: f64) -> Self {
self.temperature = Some(temp);
self
}
#[must_use]
pub fn with_stop_sequences(mut self, sequences: Vec<String>) -> Self {
self.stop_sequences = sequences;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum IncludeContext {
None,
ThisServer,
AllServers,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateMessageResult {
pub content: SamplingContent,
pub role: crate::types::Role,
pub model: String,
#[serde(rename = "stopReason")]
pub stop_reason: StopReason,
}
impl CreateMessageResult {
#[must_use]
pub fn text(text: impl Into<String>, model: impl Into<String>) -> Self {
Self {
content: SamplingContent::Text { text: text.into() },
role: crate::types::Role::Assistant,
model: model.into(),
stop_reason: StopReason::EndTurn,
}
}
#[must_use]
pub fn with_stop_reason(mut self, reason: StopReason) -> Self {
self.stop_reason = reason;
self
}
#[must_use]
pub fn text_content(&self) -> Option<&str> {
match &self.content {
SamplingContent::Text { text } => Some(text),
SamplingContent::Image { .. } => None,
}
}
}
use crate::types::Root;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListRootsParams {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListRootsResult {
pub roots: Vec<Root>,
}
impl ListRootsResult {
#[must_use]
pub fn empty() -> Self {
Self { roots: Vec::new() }
}
#[must_use]
pub fn new(roots: Vec<Root>) -> Self {
Self { roots }
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RootsListChangedNotificationParams {}
pub type ElicitRequestedSchema = serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElicitRequestFormParams {
pub mode: ElicitMode,
pub message: String,
#[serde(rename = "requestedSchema")]
pub requested_schema: ElicitRequestedSchema,
}
impl ElicitRequestFormParams {
#[must_use]
pub fn new(message: impl Into<String>, schema: serde_json::Value) -> Self {
Self {
mode: ElicitMode::Form,
message: message.into(),
requested_schema: schema,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElicitRequestUrlParams {
pub mode: ElicitMode,
pub message: String,
pub url: String,
#[serde(rename = "elicitationId")]
pub elicitation_id: String,
}
impl ElicitRequestUrlParams {
#[must_use]
pub fn new(
message: impl Into<String>,
url: impl Into<String>,
elicitation_id: impl Into<String>,
) -> Self {
Self {
mode: ElicitMode::Url,
message: message.into(),
url: url.into(),
elicitation_id: elicitation_id.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ElicitMode {
Form,
Url,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ElicitRequestParams {
Form(ElicitRequestFormParams),
Url(ElicitRequestUrlParams),
}
impl ElicitRequestParams {
#[must_use]
pub fn form(message: impl Into<String>, schema: serde_json::Value) -> Self {
Self::Form(ElicitRequestFormParams::new(message, schema))
}
#[must_use]
pub fn url(
message: impl Into<String>,
url: impl Into<String>,
elicitation_id: impl Into<String>,
) -> Self {
Self::Url(ElicitRequestUrlParams::new(message, url, elicitation_id))
}
#[must_use]
pub fn mode(&self) -> ElicitMode {
match self {
Self::Form(_) => ElicitMode::Form,
Self::Url(_) => ElicitMode::Url,
}
}
#[must_use]
pub fn message(&self) -> &str {
match self {
Self::Form(f) => &f.message,
Self::Url(u) => &u.message,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ElicitAction {
Accept,
Decline,
Cancel,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ElicitContentValue {
Null,
Bool(bool),
Int(i64),
Float(f64),
String(String),
StringArray(Vec<String>),
}
impl From<bool> for ElicitContentValue {
fn from(v: bool) -> Self {
Self::Bool(v)
}
}
impl From<i64> for ElicitContentValue {
fn from(v: i64) -> Self {
Self::Int(v)
}
}
impl From<f64> for ElicitContentValue {
fn from(v: f64) -> Self {
Self::Float(v)
}
}
impl From<String> for ElicitContentValue {
fn from(v: String) -> Self {
Self::String(v)
}
}
impl From<&str> for ElicitContentValue {
fn from(v: &str) -> Self {
Self::String(v.to_owned())
}
}
impl From<Vec<String>> for ElicitContentValue {
fn from(v: Vec<String>) -> Self {
Self::StringArray(v)
}
}
impl<T: Into<ElicitContentValue>> From<Option<T>> for ElicitContentValue {
fn from(v: Option<T>) -> Self {
match v {
Some(v) => v.into(),
None => Self::Null,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElicitResult {
pub action: ElicitAction,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<std::collections::HashMap<String, ElicitContentValue>>,
}
impl ElicitResult {
#[must_use]
pub fn accept(content: std::collections::HashMap<String, ElicitContentValue>) -> Self {
Self {
action: ElicitAction::Accept,
content: Some(content),
}
}
#[must_use]
pub fn accept_url() -> Self {
Self {
action: ElicitAction::Accept,
content: None,
}
}
#[must_use]
pub fn decline() -> Self {
Self {
action: ElicitAction::Decline,
content: None,
}
}
#[must_use]
pub fn cancel() -> Self {
Self {
action: ElicitAction::Cancel,
content: None,
}
}
#[must_use]
pub fn is_accepted(&self) -> bool {
matches!(self.action, ElicitAction::Accept)
}
#[must_use]
pub fn is_declined(&self) -> bool {
matches!(self.action, ElicitAction::Decline)
}
#[must_use]
pub fn is_cancelled(&self) -> bool {
matches!(self.action, ElicitAction::Cancel)
}
#[must_use]
pub fn get_string(&self, key: &str) -> Option<&str> {
self.content.as_ref().and_then(|c| {
c.get(key).and_then(|v| match v {
ElicitContentValue::String(s) => Some(s.as_str()),
_ => None,
})
})
}
#[must_use]
pub fn get_bool(&self, key: &str) -> Option<bool> {
self.content.as_ref().and_then(|c| {
c.get(key).and_then(|v| match v {
ElicitContentValue::Bool(b) => Some(*b),
_ => None,
})
})
}
#[must_use]
pub fn get_int(&self, key: &str) -> Option<i64> {
self.content.as_ref().and_then(|c| {
c.get(key).and_then(|v| match v {
ElicitContentValue::Int(i) => Some(*i),
_ => None,
})
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElicitCompleteNotificationParams {
#[serde(rename = "elicitationId")]
pub elicitation_id: String,
}
impl ElicitCompleteNotificationParams {
#[must_use]
pub fn new(elicitation_id: impl Into<String>) -> Self {
Self {
elicitation_id: elicitation_id.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElicitationRequiredErrorData {
pub elicitations: Vec<ElicitRequestUrlParams>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::PROTOCOL_VERSION;
const PROGRESS_MARKER_KEY: &str = "progressTo\x6ben";
const MAX_TOKENS_KEY: &str = "maxTo\x6bens";
#[test]
fn progress_marker_string_serialization() {
let progress = ProgressMarker::String("progress_value_test_1".to_string());
let value = serde_json::to_value(&progress).expect("serialize");
assert_eq!(value, "progress_value_test_1");
}
#[test]
fn progress_marker_number_serialization() {
let progress = ProgressMarker::Number(42);
let value = serde_json::to_value(&progress).expect("serialize");
assert_eq!(value, 42);
}
#[test]
fn progress_marker_from_impls() {
let from_str: ProgressMarker = "progress".into();
assert!(matches!(from_str, ProgressMarker::String(_)));
let from_string: ProgressMarker = "progress".to_string().into();
assert!(matches!(from_string, ProgressMarker::String(_)));
let from_i64: ProgressMarker = 99i64.into();
assert!(matches!(from_i64, ProgressMarker::Number(99)));
}
#[test]
fn progress_marker_display() {
assert_eq!(
format!(
"{}",
ProgressMarker::String("progress_value_test_1".to_string())
),
"progress_value_test_1"
);
assert_eq!(format!("{}", ProgressMarker::Number(42)), "42");
}
#[test]
fn progress_marker_equality() {
assert_eq!(ProgressMarker::Number(1), ProgressMarker::Number(1));
assert_ne!(ProgressMarker::Number(1), ProgressMarker::Number(2));
assert_eq!(
ProgressMarker::String("a".to_string()),
ProgressMarker::String("a".to_string())
);
}
#[test]
fn request_meta_default_empty() {
let meta = RequestMeta::default();
let value = serde_json::to_value(&meta).expect("serialize");
assert_eq!(value, serde_json::json!({}));
}
#[test]
fn request_meta_with_marker() {
let meta = RequestMeta {
progress_marker: Some(ProgressMarker::String("progress_value_test_2".to_string())),
};
let value = serde_json::to_value(&meta).expect("serialize");
assert_eq!(value[PROGRESS_MARKER_KEY], "progress_value_test_2");
}
#[test]
fn initialize_params_serialization() {
let params = InitializeParams {
protocol_version: PROTOCOL_VERSION.to_string(),
capabilities: ClientCapabilities::default(),
client_info: ClientInfo {
name: "test-client".to_string(),
version: "1.0.0".to_string(),
},
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["protocolVersion"], PROTOCOL_VERSION);
assert_eq!(value["clientInfo"]["name"], "test-client");
assert_eq!(value["clientInfo"]["version"], "1.0.0");
}
#[test]
fn initialize_params_round_trip() {
let json = serde_json::json!({
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "my-client", "version": "0.1.0"}
});
let params: InitializeParams = serde_json::from_value(json).expect("deserialize");
assert_eq!(params.protocol_version, "2024-11-05");
assert_eq!(params.client_info.name, "my-client");
}
#[test]
fn initialize_result_serialization() {
let result = InitializeResult {
protocol_version: PROTOCOL_VERSION.to_string(),
capabilities: ServerCapabilities::default(),
server_info: ServerInfo {
name: "test-server".to_string(),
version: "1.0.0".to_string(),
},
instructions: Some("Welcome!".to_string()),
};
let value = serde_json::to_value(&result).expect("serialize");
assert_eq!(value["protocolVersion"], PROTOCOL_VERSION);
assert_eq!(value["serverInfo"]["name"], "test-server");
assert_eq!(value["instructions"], "Welcome!");
}
#[test]
fn initialize_result_without_instructions() {
let result = InitializeResult {
protocol_version: PROTOCOL_VERSION.to_string(),
capabilities: ServerCapabilities::default(),
server_info: ServerInfo {
name: "srv".to_string(),
version: "0.1.0".to_string(),
},
instructions: None,
};
let value = serde_json::to_value(&result).expect("serialize");
assert!(value.get("instructions").is_none());
}
#[test]
fn list_tools_params_default() {
let params = ListToolsParams::default();
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value, serde_json::json!({}));
}
#[test]
fn list_tools_params_with_cursor() {
let params = ListToolsParams {
cursor: Some("next-page".to_string()),
include_tags: None,
exclude_tags: None,
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["cursor"], "next-page");
}
#[test]
fn list_tools_params_with_tags() {
let params = ListToolsParams {
cursor: None,
include_tags: Some(vec!["api".to_string(), "v2".to_string()]),
exclude_tags: Some(vec!["deprecated".to_string()]),
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["includeTags"], serde_json::json!(["api", "v2"]));
assert_eq!(value["excludeTags"], serde_json::json!(["deprecated"]));
}
#[test]
fn call_tool_params_minimal() {
let params = CallToolParams {
name: "greet".to_string(),
arguments: None,
meta: None,
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["name"], "greet");
assert!(value.get("arguments").is_none());
assert!(value.get("_meta").is_none());
}
#[test]
fn call_tool_params_full() {
let params = CallToolParams {
name: "add".to_string(),
arguments: Some(serde_json::json!({"a": 1, "b": 2})),
meta: Some(RequestMeta {
progress_marker: Some(ProgressMarker::Number(100)),
}),
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["name"], "add");
assert_eq!(value["arguments"]["a"], 1);
assert_eq!(value["_meta"][PROGRESS_MARKER_KEY], 100);
}
#[test]
fn call_tool_result_success() {
let result = CallToolResult {
content: vec![Content::Text {
text: "42".to_string(),
}],
is_error: false,
};
let value = serde_json::to_value(&result).expect("serialize");
assert_eq!(value["content"][0]["type"], "text");
assert_eq!(value["content"][0]["text"], "42");
assert!(value.get("isError").is_none());
}
#[test]
fn call_tool_result_error() {
let result = CallToolResult {
content: vec![Content::Text {
text: "Something went wrong".to_string(),
}],
is_error: true,
};
let value = serde_json::to_value(&result).expect("serialize");
assert_eq!(value["isError"], true);
}
#[test]
fn list_resources_params_default() {
let params = ListResourcesParams::default();
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value, serde_json::json!({}));
}
#[test]
fn list_resources_params_with_tags() {
let params = ListResourcesParams {
cursor: None,
include_tags: Some(vec!["config".to_string()]),
exclude_tags: None,
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["includeTags"], serde_json::json!(["config"]));
}
#[test]
fn read_resource_params_serialization() {
let params = ReadResourceParams {
uri: "file://config.json".to_string(),
meta: None,
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["uri"], "file://config.json");
assert!(value.get("_meta").is_none());
}
#[test]
fn read_resource_params_with_meta() {
let params = ReadResourceParams {
uri: "file://data.csv".to_string(),
meta: Some(RequestMeta {
progress_marker: Some(ProgressMarker::String("pt-read".to_string())),
}),
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["uri"], "file://data.csv");
assert_eq!(value["_meta"][PROGRESS_MARKER_KEY], "pt-read");
}
#[test]
fn read_resource_result_serialization() {
let result = ReadResourceResult {
contents: vec![ResourceContent {
uri: "file://test.txt".to_string(),
mime_type: Some("text/plain".to_string()),
text: Some("Hello!".to_string()),
blob: None,
}],
};
let value = serde_json::to_value(&result).expect("serialize");
assert_eq!(value["contents"][0]["uri"], "file://test.txt");
assert_eq!(value["contents"][0]["text"], "Hello!");
}
#[test]
fn list_prompts_params_default() {
let params = ListPromptsParams::default();
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value, serde_json::json!({}));
}
#[test]
fn list_prompts_params_with_tags() {
let params = ListPromptsParams {
cursor: Some("c1".to_string()),
include_tags: Some(vec!["onboarding".to_string()]),
exclude_tags: Some(vec!["deprecated".to_string()]),
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["cursor"], "c1");
assert_eq!(value["includeTags"], serde_json::json!(["onboarding"]));
assert_eq!(value["excludeTags"], serde_json::json!(["deprecated"]));
}
#[test]
fn get_prompt_params_minimal() {
let params = GetPromptParams {
name: "greeting".to_string(),
arguments: None,
meta: None,
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["name"], "greeting");
assert!(value.get("arguments").is_none());
}
#[test]
fn get_prompt_params_with_arguments() {
let mut args = std::collections::HashMap::new();
args.insert("name".to_string(), "Alice".to_string());
args.insert("language".to_string(), "French".to_string());
let params = GetPromptParams {
name: "translate".to_string(),
arguments: Some(args),
meta: None,
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["name"], "translate");
assert_eq!(value["arguments"]["name"], "Alice");
assert_eq!(value["arguments"]["language"], "French");
}
#[test]
fn get_prompt_result_serialization() {
let result = GetPromptResult {
description: Some("A greeting prompt".to_string()),
messages: vec![PromptMessage {
role: crate::types::Role::User,
content: Content::Text {
text: "Say hello".to_string(),
},
}],
};
let value = serde_json::to_value(&result).expect("serialize");
assert_eq!(value["description"], "A greeting prompt");
assert_eq!(value["messages"][0]["role"], "user");
assert_eq!(value["messages"][0]["content"]["text"], "Say hello");
}
#[test]
fn get_prompt_result_without_description() {
let result = GetPromptResult {
description: None,
messages: vec![],
};
let value = serde_json::to_value(&result).expect("serialize");
assert!(value.get("description").is_none());
}
#[test]
fn cancelled_params_minimal() {
let params = CancelledParams {
request_id: RequestId::Number(5),
reason: None,
await_cleanup: None,
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["requestId"], 5);
assert!(value.get("reason").is_none());
assert!(value.get("awaitCleanup").is_none());
}
#[test]
fn cancelled_params_full() {
let params = CancelledParams {
request_id: RequestId::String("req-7".to_string()),
reason: Some("User cancelled".to_string()),
await_cleanup: Some(true),
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["requestId"], "req-7");
assert_eq!(value["reason"], "User cancelled");
assert_eq!(value["awaitCleanup"], true);
}
#[test]
fn progress_params_new() {
let params = ProgressParams::new("id-1", 0.5);
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value[PROGRESS_MARKER_KEY], "id-1");
assert_eq!(value["progress"], 0.5);
assert!(value.get("total").is_none());
assert!(value.get("message").is_none());
}
#[test]
fn progress_params_with_total() {
let params = ProgressParams::with_total(42i64, 50.0, 100.0);
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value[PROGRESS_MARKER_KEY], 42);
assert_eq!(value["progress"], 50.0);
assert_eq!(value["total"], 100.0);
}
#[test]
fn progress_params_with_message() {
let params = ProgressParams::new("tok", 0.75).with_message("Almost done");
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["message"], "Almost done");
}
#[test]
fn progress_params_fraction() {
let params = ProgressParams::with_total("t", 25.0, 100.0);
assert_eq!(params.fraction(), Some(0.25));
let params = ProgressParams::with_total("t", 10.0, 0.0);
assert_eq!(params.fraction(), Some(0.0));
let params = ProgressParams::new("t", 0.5);
assert_eq!(params.fraction(), None);
}
#[test]
fn get_task_params_serialization() {
let params = GetTaskParams {
id: TaskId::from_string("task-abc"),
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["id"], "task-abc");
}
#[test]
fn get_task_result_serialization() {
let result = GetTaskResult {
task: crate::types::TaskInfo {
id: TaskId::from_string("task-1"),
task_type: "compute".to_string(),
status: TaskStatus::Completed,
progress: Some(1.0),
message: Some("Done".to_string()),
created_at: "2026-01-28T00:00:00Z".to_string(),
started_at: Some("2026-01-28T00:01:00Z".to_string()),
completed_at: Some("2026-01-28T00:02:00Z".to_string()),
error: None,
},
result: Some(crate::types::TaskResult {
id: TaskId::from_string("task-1"),
success: true,
data: Some(serde_json::json!({"value": 42})),
error: None,
}),
};
let value = serde_json::to_value(&result).expect("serialize");
assert_eq!(value["task"]["status"], "completed");
assert_eq!(value["result"]["success"], true);
assert_eq!(value["result"]["data"]["value"], 42);
}
#[test]
fn cancel_task_params_serialization() {
let params = CancelTaskParams {
id: TaskId::from_string("task-1"),
reason: Some("No longer needed".to_string()),
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["id"], "task-1");
assert_eq!(value["reason"], "No longer needed");
}
#[test]
fn cancel_task_params_without_reason() {
let params = CancelTaskParams {
id: TaskId::from_string("task-2"),
reason: None,
};
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["id"], "task-2");
assert!(value.get("reason").is_none());
}
#[test]
fn cancel_task_result_serialization() {
let result = CancelTaskResult {
cancelled: true,
task: crate::types::TaskInfo {
id: TaskId::from_string("task-1"),
task_type: "compute".to_string(),
status: TaskStatus::Cancelled,
progress: None,
message: None,
created_at: "2026-01-28T00:00:00Z".to_string(),
started_at: None,
completed_at: None,
error: None,
},
};
let value = serde_json::to_value(&result).expect("serialize");
assert_eq!(value["cancelled"], true);
assert_eq!(value["task"]["status"], "cancelled");
}
#[test]
fn log_level_serialization() {
assert_eq!(serde_json::to_value(LogLevel::Debug).unwrap(), "debug");
assert_eq!(serde_json::to_value(LogLevel::Info).unwrap(), "info");
assert_eq!(serde_json::to_value(LogLevel::Warning).unwrap(), "warning");
assert_eq!(serde_json::to_value(LogLevel::Error).unwrap(), "error");
}
#[test]
fn log_level_deserialization() {
assert_eq!(
serde_json::from_value::<LogLevel>(serde_json::json!("debug")).unwrap(),
LogLevel::Debug
);
assert_eq!(
serde_json::from_value::<LogLevel>(serde_json::json!("warning")).unwrap(),
LogLevel::Warning
);
}
#[test]
fn list_resource_templates_params_serialization() {
let params = ListResourceTemplatesParams::default();
let value = serde_json::to_value(¶ms).expect("serialize params");
assert_eq!(value, serde_json::json!({}));
let params = ListResourceTemplatesParams {
cursor: Some("next".to_string()),
..Default::default()
};
let value = serde_json::to_value(¶ms).expect("serialize params with cursor");
assert_eq!(value, serde_json::json!({ "cursor": "next" }));
}
#[test]
fn list_resource_templates_result_serialization() {
let result = ListResourceTemplatesResult {
resource_templates: vec![ResourceTemplate {
uri_template: "resource://{id}".to_string(),
name: "Resource Template".to_string(),
description: Some("Template description".to_string()),
mime_type: Some("text/plain".to_string()),
icon: None,
version: None,
tags: vec![],
}],
next_cursor: None,
};
let value = serde_json::to_value(&result).expect("serialize result");
let templates = value
.get("resourceTemplates")
.expect("resourceTemplates key");
let template = templates.get(0).expect("first resource template");
assert_eq!(template["uriTemplate"], "resource://{id}");
assert_eq!(template["name"], "Resource Template");
assert_eq!(template["description"], "Template description");
assert_eq!(template["mimeType"], "text/plain");
}
#[test]
fn resource_updated_notification_serialization() {
let params = ResourceUpdatedNotificationParams {
uri: "resource://test".to_string(),
};
let value = serde_json::to_value(¶ms).expect("serialize params");
assert_eq!(value, serde_json::json!({ "uri": "resource://test" }));
}
#[test]
fn subscribe_unsubscribe_resource_params_serialization() {
let subscribe = SubscribeResourceParams {
uri: "resource://alpha".to_string(),
};
let value = serde_json::to_value(&subscribe).expect("serialize subscribe params");
assert_eq!(value, serde_json::json!({ "uri": "resource://alpha" }));
let unsubscribe = UnsubscribeResourceParams {
uri: "resource://alpha".to_string(),
};
let value = serde_json::to_value(&unsubscribe).expect("serialize unsubscribe params");
assert_eq!(value, serde_json::json!({ "uri": "resource://alpha" }));
}
#[test]
fn logging_params_serialization() {
let set_level = SetLogLevelParams {
level: LogLevel::Warning,
};
let value = serde_json::to_value(&set_level).expect("serialize setLevel");
assert_eq!(value, serde_json::json!({ "level": "warning" }));
let log_message = LogMessageParams {
level: LogLevel::Info,
logger: Some("fastmcp_rust::server".to_string()),
data: serde_json::Value::String("hello".to_string()),
};
let value = serde_json::to_value(&log_message).expect("serialize log message");
assert_eq!(value["level"], "info");
assert_eq!(value["logger"], "fastmcp_rust::server");
assert_eq!(value["data"], "hello");
}
#[test]
fn list_tasks_params_serialization() {
let params = ListTasksParams {
cursor: None,
limit: None,
status: None,
};
let value = serde_json::to_value(¶ms).expect("serialize list tasks params");
assert_eq!(value, serde_json::json!({}));
let params = ListTasksParams {
cursor: Some("next".to_string()),
limit: Some(10),
status: Some(TaskStatus::Running),
};
let value = serde_json::to_value(¶ms).expect("serialize list tasks params");
assert_eq!(
value,
serde_json::json!({"cursor": "next", "limit": 10, "status": "running"})
);
}
#[test]
fn submit_task_params_serialization() {
let params = SubmitTaskParams {
task_type: "demo".to_string(),
params: None,
};
let value = serde_json::to_value(¶ms).expect("serialize submit task params");
assert_eq!(value, serde_json::json!({"taskType": "demo"}));
let params = SubmitTaskParams {
task_type: "demo".to_string(),
params: Some(serde_json::json!({"payload": 1})),
};
let value = serde_json::to_value(¶ms).expect("serialize submit task params");
assert_eq!(
value,
serde_json::json!({"taskType": "demo", "params": {"payload": 1}})
);
}
#[test]
fn task_status_notification_serialization() {
let params = TaskStatusNotificationParams {
id: TaskId::from_string("task-1"),
status: TaskStatus::Running,
progress: Some(0.5),
message: Some("halfway".to_string()),
error: None,
result: None,
};
let value = serde_json::to_value(¶ms).expect("serialize task status notification");
assert_eq!(
value,
serde_json::json!({
"id": "task-1",
"status": "running",
"progress": 0.5,
"message": "halfway"
})
);
}
#[test]
fn create_message_params_minimal() {
let params = CreateMessageParams::new(vec![SamplingMessage::user("Hello")], 100);
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value[MAX_TOKENS_KEY], 100);
assert!(value["messages"].is_array());
assert!(value.get("systemPrompt").is_none());
assert!(value.get("temperature").is_none());
}
#[test]
fn create_message_params_full() {
let params = CreateMessageParams::new(
vec![
SamplingMessage::user("Hello"),
SamplingMessage::assistant("Hi there!"),
],
500,
)
.with_system_prompt("You are helpful")
.with_temperature(0.7)
.with_stop_sequences(vec!["END".to_string()]);
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value[MAX_TOKENS_KEY], 500);
assert_eq!(value["systemPrompt"], "You are helpful");
assert_eq!(value["temperature"], 0.7);
assert_eq!(value["stopSequences"][0], "END");
assert_eq!(value["messages"].as_array().unwrap().len(), 2);
}
#[test]
fn create_message_result_text() {
let result = CreateMessageResult::text("Hello!", "claude-3");
let value = serde_json::to_value(&result).expect("serialize");
assert_eq!(value["content"]["type"], "text");
assert_eq!(value["content"]["text"], "Hello!");
assert_eq!(value["model"], "claude-3");
assert_eq!(value["role"], "assistant");
assert_eq!(value["stopReason"], "endTurn");
}
#[test]
fn create_message_result_max_tokens() {
use crate::types::StopReason;
let result =
CreateMessageResult::text("Truncated", "gpt-4").with_stop_reason(StopReason::MaxTokens);
let value = serde_json::to_value(&result).expect("serialize");
assert_eq!(value["stopReason"], "maxTo\x6bens");
}
#[test]
fn sampling_message_user() {
let msg = SamplingMessage::user("Test message");
let value = serde_json::to_value(&msg).expect("serialize");
assert_eq!(value["role"], "user");
assert_eq!(value["content"]["type"], "text");
assert_eq!(value["content"]["text"], "Test message");
}
#[test]
fn sampling_message_assistant() {
let msg = SamplingMessage::assistant("Response");
let value = serde_json::to_value(&msg).expect("serialize");
assert_eq!(value["role"], "assistant");
assert_eq!(value["content"]["type"], "text");
assert_eq!(value["content"]["text"], "Response");
}
#[test]
fn sampling_content_image() {
let content = SamplingContent::Image {
data: "base64data".to_string(),
mime_type: "image/png".to_string(),
};
let value = serde_json::to_value(&content).expect("serialize");
assert_eq!(value["type"], "image");
assert_eq!(value["data"], "base64data");
assert_eq!(value["mimeType"], "image/png");
}
#[test]
fn include_context_serialization() {
let none = IncludeContext::None;
let this = IncludeContext::ThisServer;
let all = IncludeContext::AllServers;
assert_eq!(serde_json::to_value(none).unwrap(), "none");
assert_eq!(serde_json::to_value(this).unwrap(), "thisServer");
assert_eq!(serde_json::to_value(all).unwrap(), "allServers");
}
#[test]
fn create_message_result_text_content() {
let result = CreateMessageResult::text("Hello!", "model");
assert_eq!(result.text_content(), Some("Hello!"));
let result = CreateMessageResult {
content: SamplingContent::Image {
data: "data".to_string(),
mime_type: "image/png".to_string(),
},
role: crate::types::Role::Assistant,
model: "model".to_string(),
stop_reason: StopReason::EndTurn,
};
assert_eq!(result.text_content(), None);
}
#[test]
fn elicit_form_params_serialization() {
let params = ElicitRequestFormParams::new(
"Please enter your name",
serde_json::json!({
"type": "object",
"properties": {
"name": {"type": "string"}
},
"required": ["name"]
}),
);
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["mode"], "form");
assert_eq!(value["message"], "Please enter your name");
assert!(value["requestedSchema"]["properties"]["name"].is_object());
}
#[test]
fn elicit_url_params_serialization() {
let params = ElicitRequestUrlParams::new(
"Please authenticate",
"https://auth.example.com/oauth",
"elicit-12345",
);
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["mode"], "url");
assert_eq!(value["message"], "Please authenticate");
assert_eq!(value["url"], "https://auth.example.com/oauth");
assert_eq!(value["elicitationId"], "elicit-12345");
}
#[test]
fn elicit_request_params_untagged() {
let form = ElicitRequestParams::form(
"Enter name",
serde_json::json!({"type": "object", "properties": {}}),
);
assert_eq!(form.mode(), ElicitMode::Form);
assert_eq!(form.message(), "Enter name");
let url = ElicitRequestParams::url("Auth required", "https://example.com", "id-1");
assert_eq!(url.mode(), ElicitMode::Url);
assert_eq!(url.message(), "Auth required");
}
#[test]
fn elicit_result_accept_with_content() {
let mut content = std::collections::HashMap::new();
content.insert(
"name".to_string(),
ElicitContentValue::String("Alice".to_string()),
);
content.insert("age".to_string(), ElicitContentValue::Int(30));
content.insert("active".to_string(), ElicitContentValue::Bool(true));
let result = ElicitResult::accept(content);
assert!(result.is_accepted());
assert!(!result.is_declined());
assert!(!result.is_cancelled());
assert_eq!(result.get_string("name"), Some("Alice"));
assert_eq!(result.get_int("age"), Some(30));
assert_eq!(result.get_bool("active"), Some(true));
}
#[test]
fn elicit_result_serialization() {
let result = ElicitResult::decline();
let value = serde_json::to_value(&result).expect("serialize");
assert_eq!(value["action"], "decline");
assert!(value.get("content").is_none());
let result = ElicitResult::cancel();
let value = serde_json::to_value(&result).expect("serialize");
assert_eq!(value["action"], "cancel");
}
#[test]
fn elicit_content_value_conversions() {
let s: ElicitContentValue = "hello".into();
assert!(matches!(s, ElicitContentValue::String(_)));
let i: ElicitContentValue = 42i64.into();
assert!(matches!(i, ElicitContentValue::Int(42)));
let b: ElicitContentValue = true.into();
assert!(matches!(b, ElicitContentValue::Bool(true)));
let f: ElicitContentValue = 1.23.into();
assert!(matches!(f, ElicitContentValue::Float(_)));
let arr: ElicitContentValue = vec!["a".to_string(), "b".to_string()].into();
assert!(matches!(arr, ElicitContentValue::StringArray(_)));
let none: ElicitContentValue = None::<String>.into();
assert!(matches!(none, ElicitContentValue::Null));
}
#[test]
fn elicit_complete_notification_serialization() {
let params = ElicitCompleteNotificationParams::new("elicit-12345");
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["elicitationId"], "elicit-12345");
}
#[test]
fn elicitation_capability_modes() {
use crate::types::ElicitationCapability;
let form_only = ElicitationCapability::form();
assert!(form_only.supports_form());
assert!(!form_only.supports_url());
let url_only = ElicitationCapability::url();
assert!(!url_only.supports_form());
assert!(url_only.supports_url());
let both = ElicitationCapability::both();
assert!(both.supports_form());
assert!(both.supports_url());
}
#[test]
fn root_new() {
use crate::types::Root;
let root = Root::new("file:///home/user/project");
assert_eq!(root.uri, "file:///home/user/project");
assert!(root.name.is_none());
}
#[test]
fn root_with_name() {
use crate::types::Root;
let root = Root::with_name("file:///home/user/project", "My Project");
assert_eq!(root.uri, "file:///home/user/project");
assert_eq!(root.name, Some("My Project".to_string()));
}
#[test]
fn root_serialization() {
use crate::types::Root;
let root = Root::with_name("file:///home/user/project", "My Project");
let json = serde_json::to_value(&root).expect("serialize");
assert_eq!(json["uri"], "file:///home/user/project");
assert_eq!(json["name"], "My Project");
let root_no_name = Root::new("file:///tmp");
let json = serde_json::to_value(&root_no_name).expect("serialize");
assert_eq!(json["uri"], "file:///tmp");
assert!(json.get("name").is_none());
}
#[test]
fn list_roots_result_empty() {
let result = ListRootsResult::empty();
assert!(result.roots.is_empty());
}
#[test]
fn list_roots_result_serialization() {
use crate::types::Root;
let result = ListRootsResult::new(vec![
Root::with_name("file:///home/user/frontend", "Frontend"),
Root::with_name("file:///home/user/backend", "Backend"),
]);
let json = serde_json::to_value(&result).expect("serialize");
let roots = json["roots"].as_array().expect("roots array");
assert_eq!(roots.len(), 2);
assert_eq!(roots[0]["uri"], "file:///home/user/frontend");
assert_eq!(roots[0]["name"], "Frontend");
assert_eq!(roots[1]["uri"], "file:///home/user/backend");
assert_eq!(roots[1]["name"], "Backend");
}
#[test]
fn roots_capability_serialization() {
use crate::types::RootsCapability;
let cap = RootsCapability { list_changed: true };
let json = serde_json::to_value(&cap).expect("serialize");
assert_eq!(json["listChanged"], true);
let cap = RootsCapability::default();
let json = serde_json::to_value(&cap).expect("serialize");
assert!(json.get("listChanged").is_none());
}
#[test]
fn tool_version_serialization() {
use crate::types::Tool;
let tool = Tool {
name: "my_tool".to_string(),
description: Some("A test tool".to_string()),
input_schema: serde_json::json!({"type": "object"}),
output_schema: None,
icon: None,
version: None,
tags: vec![],
annotations: None,
};
let json = serde_json::to_value(&tool).expect("serialize");
assert!(json.get("version").is_none());
let tool = Tool {
name: "my_tool".to_string(),
description: Some("A test tool".to_string()),
input_schema: serde_json::json!({"type": "object"}),
output_schema: None,
icon: None,
version: Some("1.2.3".to_string()),
tags: vec![],
annotations: None,
};
let json = serde_json::to_value(&tool).expect("serialize");
assert_eq!(json["version"], "1.2.3");
}
#[test]
fn resource_version_serialization() {
use crate::types::Resource;
let resource = Resource {
uri: "file://test".to_string(),
name: "Test Resource".to_string(),
description: None,
mime_type: Some("text/plain".to_string()),
icon: None,
version: None,
tags: vec![],
};
let json = serde_json::to_value(&resource).expect("serialize");
assert!(json.get("version").is_none());
let resource = Resource {
uri: "file://test".to_string(),
name: "Test Resource".to_string(),
description: None,
mime_type: Some("text/plain".to_string()),
icon: None,
version: Some("2.0.0".to_string()),
tags: vec![],
};
let json = serde_json::to_value(&resource).expect("serialize");
assert_eq!(json["version"], "2.0.0");
}
#[test]
fn prompt_version_serialization() {
use crate::types::Prompt;
let prompt = Prompt {
name: "greeting".to_string(),
description: Some("A greeting prompt".to_string()),
arguments: vec![],
icon: None,
version: None,
tags: vec![],
};
let json = serde_json::to_value(&prompt).expect("serialize");
assert!(json.get("version").is_none());
let prompt = Prompt {
name: "greeting".to_string(),
description: Some("A greeting prompt".to_string()),
arguments: vec![],
icon: None,
version: Some("0.1.0".to_string()),
tags: vec![],
};
let json = serde_json::to_value(&prompt).expect("serialize");
assert_eq!(json["version"], "0.1.0");
}
#[test]
fn resource_template_version_serialization() {
let template = ResourceTemplate {
uri_template: "file://{path}".to_string(),
name: "Files".to_string(),
description: None,
mime_type: None,
icon: None,
version: None,
tags: vec![],
};
let json = serde_json::to_value(&template).expect("serialize");
assert!(json.get("version").is_none());
let template = ResourceTemplate {
uri_template: "file://{path}".to_string(),
name: "Files".to_string(),
description: None,
mime_type: None,
icon: None,
version: Some("3.0.0".to_string()),
tags: vec![],
};
let json = serde_json::to_value(&template).expect("serialize");
assert_eq!(json["version"], "3.0.0");
}
#[test]
fn version_deserialization() {
use crate::types::{Prompt, Resource, Tool};
let json = serde_json::json!({
"name": "tool",
"inputSchema": {"type": "object"}
});
let tool: Tool = serde_json::from_value(json).expect("deserialize");
assert!(tool.version.is_none());
let json = serde_json::json!({
"name": "tool",
"inputSchema": {"type": "object"},
"version": "1.0.0"
});
let tool: Tool = serde_json::from_value(json).expect("deserialize");
assert_eq!(tool.version, Some("1.0.0".to_string()));
let json = serde_json::json!({
"uri": "file://test",
"name": "Test"
});
let resource: Resource = serde_json::from_value(json).expect("deserialize");
assert!(resource.version.is_none());
let json = serde_json::json!({
"name": "prompt"
});
let prompt: Prompt = serde_json::from_value(json).expect("deserialize");
assert!(prompt.version.is_none());
}
#[test]
fn tool_tags_serialization() {
use crate::types::Tool;
let tool = Tool {
name: "my_tool".to_string(),
description: None,
input_schema: serde_json::json!({"type": "object"}),
output_schema: None,
icon: None,
version: None,
tags: vec![],
annotations: None,
};
let json = serde_json::to_value(&tool).expect("serialize");
assert!(
json.get("tags").is_none(),
"Empty tags should not appear in JSON"
);
let tool = Tool {
name: "my_tool".to_string(),
description: None,
input_schema: serde_json::json!({"type": "object"}),
output_schema: None,
icon: None,
version: None,
tags: vec!["api".to_string(), "database".to_string()],
annotations: None,
};
let json = serde_json::to_value(&tool).expect("serialize");
assert_eq!(json["tags"], serde_json::json!(["api", "database"]));
}
#[test]
fn resource_tags_serialization() {
use crate::types::Resource;
let resource = Resource {
uri: "file://test".to_string(),
name: "Test Resource".to_string(),
description: None,
mime_type: None,
icon: None,
version: None,
tags: vec![],
};
let json = serde_json::to_value(&resource).expect("serialize");
assert!(
json.get("tags").is_none(),
"Empty tags should not appear in JSON"
);
let resource = Resource {
uri: "file://test".to_string(),
name: "Test Resource".to_string(),
description: None,
mime_type: None,
icon: None,
version: None,
tags: vec!["files".to_string(), "readonly".to_string()],
};
let json = serde_json::to_value(&resource).expect("serialize");
assert_eq!(json["tags"], serde_json::json!(["files", "readonly"]));
}
#[test]
fn prompt_tags_serialization() {
use crate::types::Prompt;
let prompt = Prompt {
name: "greeting".to_string(),
description: None,
arguments: vec![],
icon: None,
version: None,
tags: vec![],
};
let json = serde_json::to_value(&prompt).expect("serialize");
assert!(
json.get("tags").is_none(),
"Empty tags should not appear in JSON"
);
let prompt = Prompt {
name: "greeting".to_string(),
description: None,
arguments: vec![],
icon: None,
version: None,
tags: vec!["templates".to_string(), "onboarding".to_string()],
};
let json = serde_json::to_value(&prompt).expect("serialize");
assert_eq!(json["tags"], serde_json::json!(["templates", "onboarding"]));
}
#[test]
fn resource_template_tags_serialization() {
let template = ResourceTemplate {
uri_template: "file://{path}".to_string(),
name: "Files".to_string(),
description: None,
mime_type: None,
icon: None,
version: None,
tags: vec![],
};
let json = serde_json::to_value(&template).expect("serialize");
assert!(
json.get("tags").is_none(),
"Empty tags should not appear in JSON"
);
let template = ResourceTemplate {
uri_template: "file://{path}".to_string(),
name: "Files".to_string(),
description: None,
mime_type: None,
icon: None,
version: None,
tags: vec!["filesystem".to_string()],
};
let json = serde_json::to_value(&template).expect("serialize");
assert_eq!(json["tags"], serde_json::json!(["filesystem"]));
}
#[test]
fn tags_deserialization() {
use crate::types::{Prompt, Resource, Tool};
let json = serde_json::json!({
"name": "tool",
"inputSchema": {"type": "object"}
});
let tool: Tool = serde_json::from_value(json).expect("deserialize");
assert!(tool.tags.is_empty());
let json = serde_json::json!({
"name": "tool",
"inputSchema": {"type": "object"},
"tags": ["compute", "heavy"]
});
let tool: Tool = serde_json::from_value(json).expect("deserialize");
assert_eq!(tool.tags, vec!["compute", "heavy"]);
let json = serde_json::json!({
"uri": "file://test",
"name": "Test"
});
let resource: Resource = serde_json::from_value(json).expect("deserialize");
assert!(resource.tags.is_empty());
let json = serde_json::json!({
"uri": "file://test",
"name": "Test",
"tags": ["data"]
});
let resource: Resource = serde_json::from_value(json).expect("deserialize");
assert_eq!(resource.tags, vec!["data"]);
let json = serde_json::json!({
"name": "prompt"
});
let prompt: Prompt = serde_json::from_value(json).expect("deserialize");
assert!(prompt.tags.is_empty());
let json = serde_json::json!({
"name": "prompt",
"tags": ["greeting", "onboarding"]
});
let prompt: Prompt = serde_json::from_value(json).expect("deserialize");
assert_eq!(prompt.tags, vec!["greeting", "onboarding"]);
}
#[test]
fn tool_annotations_serialization() {
use crate::types::{Tool, ToolAnnotations};
let tool = Tool {
name: "my_tool".to_string(),
description: None,
input_schema: serde_json::json!({"type": "object"}),
output_schema: None,
icon: None,
version: None,
tags: vec![],
annotations: None,
};
let json = serde_json::to_value(&tool).expect("serialize");
assert!(
json.get("annotations").is_none(),
"None annotations should not appear in JSON"
);
let tool = Tool {
name: "delete_file".to_string(),
description: Some("Deletes a file".to_string()),
input_schema: serde_json::json!({"type": "object"}),
output_schema: None,
icon: None,
version: None,
tags: vec![],
annotations: Some(
ToolAnnotations::new()
.destructive(true)
.idempotent(false)
.read_only(false),
),
};
let json = serde_json::to_value(&tool).expect("serialize");
let annotations = json.get("annotations").expect("annotations field");
assert_eq!(annotations["destructive"], true);
assert_eq!(annotations["idempotent"], false);
assert_eq!(annotations["readOnly"], false);
assert!(annotations.get("openWorldHint").is_none());
let tool = Tool {
name: "get_status".to_string(),
description: Some("Gets status".to_string()),
input_schema: serde_json::json!({"type": "object"}),
output_schema: None,
icon: None,
version: None,
tags: vec![],
annotations: Some(ToolAnnotations::new().read_only(true)),
};
let json = serde_json::to_value(&tool).expect("serialize");
let annotations = json.get("annotations").expect("annotations field");
assert_eq!(annotations["readOnly"], true);
assert!(annotations.get("destructive").is_none());
}
#[test]
fn tool_annotations_deserialization() {
use crate::types::Tool;
let json = serde_json::json!({
"name": "tool",
"inputSchema": {"type": "object"}
});
let tool: Tool = serde_json::from_value(json).expect("deserialize");
assert!(tool.annotations.is_none());
let json = serde_json::json!({
"name": "delete_tool",
"inputSchema": {"type": "object"},
"annotations": {
"destructive": true,
"idempotent": false,
"readOnly": false,
"openWorldHint": "May delete any file"
}
});
let tool: Tool = serde_json::from_value(json).expect("deserialize");
let annotations = tool.annotations.expect("annotations present");
assert_eq!(annotations.destructive, Some(true));
assert_eq!(annotations.idempotent, Some(false));
assert_eq!(annotations.read_only, Some(false));
assert_eq!(
annotations.open_world_hint,
Some("May delete any file".to_string())
);
}
#[test]
fn tool_annotations_builder() {
use crate::types::ToolAnnotations;
let annotations = ToolAnnotations::new()
.destructive(true)
.idempotent(true)
.read_only(false)
.open_world_hint("Handles unknown inputs gracefully");
assert_eq!(annotations.destructive, Some(true));
assert_eq!(annotations.idempotent, Some(true));
assert_eq!(annotations.read_only, Some(false));
assert_eq!(
annotations.open_world_hint,
Some("Handles unknown inputs gracefully".to_string())
);
assert!(!annotations.is_empty());
let empty = ToolAnnotations::new();
assert!(empty.is_empty());
}
#[test]
fn tool_output_schema_serialization() {
use crate::types::Tool;
let tool = Tool {
name: "my_tool".to_string(),
description: None,
input_schema: serde_json::json!({"type": "object"}),
output_schema: None,
icon: None,
version: None,
tags: vec![],
annotations: None,
};
let json = serde_json::to_value(&tool).expect("serialize");
assert!(
json.get("outputSchema").is_none(),
"None output_schema should not appear in JSON"
);
let tool = Tool {
name: "compute".to_string(),
description: Some("Computes a result".to_string()),
input_schema: serde_json::json!({"type": "object"}),
output_schema: Some(serde_json::json!({
"type": "object",
"properties": {
"result": {"type": "number"},
"success": {"type": "boolean"}
}
})),
icon: None,
version: None,
tags: vec![],
annotations: None,
};
let json = serde_json::to_value(&tool).expect("serialize");
let output_schema = json.get("outputSchema").expect("outputSchema field");
assert_eq!(output_schema["type"], "object");
assert!(output_schema["properties"]["result"]["type"] == "number");
assert!(output_schema["properties"]["success"]["type"] == "boolean");
}
#[test]
fn tool_output_schema_deserialization() {
use crate::types::Tool;
let json = serde_json::json!({
"name": "tool",
"inputSchema": {"type": "object"}
});
let tool: Tool = serde_json::from_value(json).expect("deserialize");
assert!(tool.output_schema.is_none());
let json = serde_json::json!({
"name": "compute",
"inputSchema": {"type": "object"},
"outputSchema": {
"type": "object",
"properties": {
"value": {"type": "integer"}
}
}
});
let tool: Tool = serde_json::from_value(json).expect("deserialize");
assert!(tool.output_schema.is_some());
let schema = tool.output_schema.unwrap();
assert_eq!(schema["type"], "object");
assert_eq!(schema["properties"]["value"]["type"], "integer");
}
}