use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type")]
pub enum McpContent {
#[serde(rename = "text")]
Text { text: String },
#[serde(rename = "image")]
Image {
data: String,
#[serde(skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
},
#[serde(rename = "resource")]
Resource {
uri: String,
#[serde(skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
text: Option<String>,
},
}
impl McpContent {
pub fn text(text: impl Into<String>) -> Self {
Self::Text { text: text.into() }
}
pub fn image(data: impl Into<String>, mime_type: Option<String>) -> Self {
Self::Image {
data: data.into(),
mime_type
}
}
pub fn resource(uri: impl Into<String>, mime_type: Option<String>, text: Option<String>) -> Self {
Self::Resource {
uri: uri.into(),
mime_type,
text
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct McpResponse {
pub content: Vec<McpContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
#[serde(default)]
pub is_error: bool,
}
impl McpResponse {
pub fn new(content: Vec<McpContent>) -> Self {
Self {
content,
data: None,
is_error: false,
}
}
pub fn text(text: impl Into<String>) -> Self {
Self::new(vec![McpContent::text(text)])
}
pub fn with_text_and_data(text: impl Into<String>, data: Value) -> Self {
Self {
content: vec![McpContent::text(text)],
data: Some(data),
is_error: false,
}
}
pub fn error(message: impl Into<String>) -> Self {
Self {
content: vec![McpContent::text(message)],
data: None,
is_error: true,
}
}
pub fn with_data(mut self, data: Value) -> Self {
self.data = Some(data);
self
}
pub fn with_content(mut self, content: McpContent) -> Self {
self.content.push(content);
self
}
pub fn as_error(mut self) -> Self {
self.is_error = true;
self
}
}
pub trait ToMcpResponse {
fn to_mcp_response(self) -> McpResponse;
}
impl ToMcpResponse for String {
fn to_mcp_response(self) -> McpResponse {
McpResponse::text(self)
}
}
impl ToMcpResponse for &str {
fn to_mcp_response(self) -> McpResponse {
McpResponse::text(self)
}
}
impl ToMcpResponse for Value {
fn to_mcp_response(self) -> McpResponse {
let text = if let Some(message) = self.get("message").and_then(|v| v.as_str()) {
message.to_string()
} else if let Some(status) = self.get("status").and_then(|v| v.as_str()) {
status.to_string()
} else if let Some(results) = self.get("results").and_then(|v| v.as_array()) {
format!("Operation completed with {} results", results.len())
} else {
"Operation completed".to_string()
};
McpResponse::with_text_and_data(text, self)
}
}
impl ToMcpResponse for McpResponse {
fn to_mcp_response(self) -> McpResponse {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_mcp_content_text() {
let content = McpContent::text("Hello, world!");
let json = serde_json::to_value(&content).unwrap();
assert_eq!(json["type"], "text");
assert_eq!(json["text"], "Hello, world!");
}
#[test]
fn test_mcp_content_image() {
let content = McpContent::image("base64data", Some("image/png".to_string()));
let json = serde_json::to_value(&content).unwrap();
assert_eq!(json["type"], "image");
assert_eq!(json["data"], "base64data");
assert_eq!(json["mime_type"], "image/png");
}
#[test]
fn test_mcp_response_text() {
let response = McpResponse::text("Success");
let json = serde_json::to_value(&response).unwrap();
assert_eq!(json["content"][0]["type"], "text");
assert_eq!(json["content"][0]["text"], "Success");
assert_eq!(json["is_error"], false);
assert!(json["data"].is_null());
}
#[test]
fn test_mcp_response_with_data() {
let data = json!({"results": [1, 2, 3], "total": 3});
let response = McpResponse::with_text_and_data("Found 3 results", data.clone());
let json = serde_json::to_value(&response).unwrap();
assert_eq!(json["content"][0]["text"], "Found 3 results");
assert_eq!(json["data"], data);
}
#[test]
fn test_to_mcp_response_string() {
let response = "Hello".to_mcp_response();
assert_eq!(response.content.len(), 1);
assert!(matches!(response.content[0], McpContent::Text { ref text } if text == "Hello"));
}
#[test]
fn test_to_mcp_response_json_with_results() {
let data = json!({"results": [1, 2], "status": "ok"});
let response = data.to_mcp_response();
let content_json = serde_json::to_value(&response.content[0]).unwrap();
let text = content_json["text"].as_str().unwrap();
assert!(text.contains("2 results") || text.contains("ok"));
assert!(response.data.is_some());
}
}