use serde::Serialize;
use serde_json::Value;
pub const PROTOCOL_VERSION: &str = "2026-07-28";
pub mod method {
pub const INITIALIZE: &str = "initialize";
pub const PING: &str = "ping";
pub const TOOLS_CALL: &str = "tools/call";
pub const TOOLS_LIST: &str = "tools/list";
}
#[derive(Debug, Clone, Serialize)]
pub struct Tool {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "inputSchema", skip_serializing_if = "Option::is_none")]
pub input_schema: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<ToolAnnotations>,
}
#[derive(Debug, Clone, Default, Serialize)]
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 {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn title(mut self, title: &str) -> Self {
self.title = Some(title.to_string());
self
}
#[must_use]
pub fn read_only(mut self, hint: bool) -> Self {
self.read_only_hint = Some(hint);
self
}
#[must_use]
pub fn destructive(mut self, hint: bool) -> Self {
self.destructive_hint = Some(hint);
self
}
#[must_use]
pub fn idempotent(mut self, hint: bool) -> Self {
self.idempotent_hint = Some(hint);
self
}
#[must_use]
pub fn open_world(mut self, hint: bool) -> Self {
self.open_world_hint = Some(hint);
self
}
}
#[derive(Debug, Serialize)]
pub struct ToolsListResult {
pub tools: Vec<Tool>,
}
#[derive(Debug, Serialize)]
pub struct TextContent {
#[serde(rename = "type")]
pub kind: &'static str,
pub text: String,
}
#[derive(Debug, Serialize)]
pub struct CallToolResult {
pub content: Vec<TextContent>,
#[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<Value>,
}
impl From<Result<Value, String>> for CallToolResult {
fn from(result: Result<Value, String>) -> Self {
match result {
Ok(value) => {
let structured = if value.is_object() {
value
} else {
serde_json::json!({ "result": value })
};
Self {
content: vec![TextContent {
kind: "text",
text: structured.to_string(),
}],
is_error: None,
structured_content: Some(structured),
}
}
Err(message) => Self {
content: vec![TextContent {
kind: "text",
text: message,
}],
is_error: Some(true),
structured_content: None,
},
}
}
}
#[derive(Debug, Serialize)]
pub struct ServerInfo {
pub name: String,
pub version: String,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn non_object_results_are_wrapped_into_an_object() {
let result = CallToolResult::from(Ok::<Value, String>(json!(42)));
let v = serde_json::to_value(&result).unwrap();
assert_eq!(v["structuredContent"], json!({ "result": 42 }));
assert_eq!(v["content"][0]["text"], r#"{"result":42}"#);
let result = CallToolResult::from(Ok::<Value, String>(Value::Null));
let v = serde_json::to_value(&result).unwrap();
assert_eq!(v["structuredContent"], json!({ "result": null }));
let result = CallToolResult::from(Ok::<Value, String>(json!([1, 2])));
let v = serde_json::to_value(&result).unwrap();
assert_eq!(v["structuredContent"], json!({ "result": [1, 2] }));
let result = CallToolResult::from(Ok::<Value, String>(json!({ "somme": 12 })));
let v = serde_json::to_value(&result).unwrap();
assert_eq!(v["structuredContent"], json!({ "somme": 12 }));
assert_eq!(v["content"][0]["text"], r#"{"somme":12}"#);
}
}