use base64::{Engine as _, engine::general_purpose};
use rmcp::{
ErrorData as McpError,
handler::server::wrapper::Parameters,
model::{CallToolResult, Content},
};
use rudof_lib::formats::{DataFormat, InputSpec, ResultDataFormat};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::str::FromStr;
use super::helpers::*;
use crate::service::{errors::*, mcp_service::RudofMcpService};
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct LoadRdfDataFromSourcesRequest {
pub data: Vec<String>,
pub data_format: Option<String>,
pub base: Option<String>,
pub endpoint: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct LoadRdfDataFromSourcesResponse {
pub sources_count: usize,
pub format: String,
pub triple_count: usize,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ExportRdfDataRequest {
pub format: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ExportRdfDataResponse {
pub data: String,
pub format: String,
pub size_bytes: usize,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ExportImageRequest {
pub image_format: String,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ExportImageResponse {
pub image_format: String,
pub mime_type: String,
pub size_bytes: usize,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ExportPlantUmlResponse {
pub plantuml_data: String,
pub size: usize,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct EmptyRequest {}
pub async fn load_rdf_data_from_sources_impl(
service: &RudofMcpService,
params: Parameters<LoadRdfDataFromSourcesRequest>,
) -> Result<CallToolResult, McpError> {
let Parameters(LoadRdfDataFromSourcesRequest {
data,
data_format,
base,
endpoint,
}) = params;
let mut rudof = service.rudof.lock().await;
let data_specs: Vec<InputSpec> = match data
.iter()
.map(|s| InputSpec::parse_from_str(s, true))
.collect::<Result<Vec<_>, _>>()
{
Ok(specs) => specs,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid data specification: {}", e),
"Provide valid file paths, URLs, or raw RDF content",
)
.into_call_tool_result());
},
};
let data_format_str = data_format.as_deref().unwrap_or("turtle");
let parsed_data_format: DataFormat = match DataFormat::from_str(data_format_str) {
Ok(fmt) => fmt,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid data format '{}': {}", data_format_str, e),
format!("Supported formats: {}", RDF_FORMATS),
)
.into_call_tool_result());
},
};
let mut data_loading = rudof
.load_data()
.with_data(&data_specs)
.with_data_format(&parsed_data_format);
if let Some(endpoint) = endpoint.as_deref() {
data_loading = data_loading.with_endpoint(endpoint);
}
if let Some(base) = base.as_deref() {
data_loading = data_loading.with_base(base);
}
data_loading.execute().map_err(|e| {
internal_error(
"RDF load error",
e.to_string(),
Some(json!({"operation":"load_rdf_data_from_sources_impl","phase":"get_data_rudof"})),
)
})?;
let mut ntriples_buffer = Vec::new();
rudof
.serialize_data(&mut ntriples_buffer)
.with_result_data_format(&ResultDataFormat::NTriples)
.execute()
.map_err(|e| {
internal_error(
"RDF count error",
e.to_string(),
Some(json!({"operation":"load_rdf_data_from_sources_impl","phase":"serialize_for_count"})),
)
})?;
let ntriples = String::from_utf8(ntriples_buffer).map_err(|e| {
internal_error(
"Conversion error",
e.to_string(),
Some(json!({"operation":"load_rdf_data_from_sources_impl","phase":"utf8_count_conversion"})),
)
})?;
let triple_count = RudofMcpService::count_triples_in_ntriples(&ntriples);
let sources_count = data_specs.len();
let response = LoadRdfDataFromSourcesResponse {
sources_count,
format: data_format_str.to_string(),
triple_count,
};
let structured = serialize_structured(&response, "load_rdf_data_from_sources_impl")?;
let summary = format!(
"Successfully loaded RDF data from {} source(s) in {} format. Total triples: {}",
sources_count, data_format_str, triple_count
);
let mut result = CallToolResult::success(vec![Content::text(summary)]);
result.structured_content = Some(structured);
drop(rudof);
if let Err(e) = service.persist_state_with(Some(ntriples)).await {
tracing::warn!("Failed to persist state after loading RDF data: {}", e);
}
Ok(result)
}
pub async fn export_rdf_data_impl(
service: &RudofMcpService,
params: Parameters<ExportRdfDataRequest>,
) -> Result<CallToolResult, McpError> {
let Parameters(ExportRdfDataRequest { format }) = params;
let mut rudof = service.rudof.lock().await;
let format_str = format.as_deref().unwrap_or("turtle");
let parsed_format = match ResultDataFormat::from_str(format_str) {
Ok(fmt) => fmt,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid export format '{}': {}", format_str, e),
format!("Supported formats: {}", RDF_FORMATS),
)
.into_call_tool_result());
},
};
let mut v = Vec::new();
rudof
.serialize_data(&mut v)
.with_result_data_format(&parsed_format)
.execute()
.map_err(|e| {
internal_error(
"Serialization error",
e.to_string(),
Some(json!({"operation":"export_rdf_data_impl", "phase":"serialize_data"})),
)
})?;
let size_bytes = v.len();
let serialized = String::from_utf8(v).map_err(|e| {
internal_error(
"Conversion error",
e.to_string(),
Some(json!({"operation":"export_rdf_data_impl", "phase":"utf8_conversion"})),
)
})?;
let response = ExportRdfDataResponse {
data: serialized.clone(),
format: format_str.to_string(),
size_bytes,
};
let structured = serialize_structured(&response, "export_rdf_data_impl")?;
let preview = code_block_preview(format_str, &serialized, DEFAULT_CONTENT_PREVIEW_CHARS);
let summary = format!(
"RDF export completed.\nFormat: {}\nSize: {} bytes",
format_str, size_bytes
);
let mut result = CallToolResult::success(vec![
Content::text(summary),
Content::text(format!("## Data Preview\n\n{}", preview)),
]);
result.structured_content = Some(structured);
Ok(result)
}
pub async fn export_plantuml_impl(
service: &RudofMcpService,
_params: Parameters<EmptyRequest>,
) -> Result<CallToolResult, McpError> {
let mut rudof = service.rudof.lock().await;
let mut v = Vec::new();
rudof
.serialize_data(&mut v)
.with_result_data_format(&ResultDataFormat::PlantUML)
.execute()
.map_err(|e| {
internal_error(
"Serialization error",
e.to_string(),
Some(json!({"operation":"export_plantuml_impl", "phase":"serialize_data"})),
)
})?;
let str = String::from_utf8(v).map_err(|e| {
internal_error(
"Conversion error",
e.to_string(),
Some(json!({"operation":"export_plantuml_impl", "phase":"utf8_conversion"})),
)
})?;
let size = str.len();
let response = ExportPlantUmlResponse {
plantuml_data: str.clone(),
size,
};
let structured = serialize_structured(&response, "export_plantuml_impl")?;
let preview = code_block_preview("plantuml", &str, DEFAULT_CONTENT_PREVIEW_CHARS);
let summary = format!("PlantUML export completed. Size: {} chars", size);
let mut result = CallToolResult::success(vec![
Content::text(summary),
Content::text(format!("## Diagram Preview\n\n{}", preview)),
]);
result.structured_content = Some(structured);
Ok(result)
}
pub async fn export_image_impl(
service: &RudofMcpService,
params: Parameters<ExportImageRequest>,
) -> Result<CallToolResult, McpError> {
let Parameters(ExportImageRequest { image_format }) = params;
let mut rudof = service.rudof.lock().await;
let format = match ResultDataFormat::from_str(&image_format) {
Ok(fmt) => fmt,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid image format '{}': {}", image_format, e),
format!("Supported formats: {}", IMAGE_FORMATS),
)
.into_call_tool_result());
},
};
let mut v = Vec::new();
rudof
.serialize_data(&mut v)
.with_result_data_format(&format)
.execute()
.map_err(|e| {
internal_error(
"Serialization error",
e.to_string(),
Some(json!({"operation":"export_image_impl", "phase":"serialize_data"})),
)
})?;
let size_bytes = v.len();
let base64_data = general_purpose::STANDARD.encode(&v);
let mime_type = match image_format.to_lowercase().as_str() {
"svg" => "image/svg+xml",
"png" => "image/png",
_ => "application/octet-stream",
};
let response = ExportImageResponse {
image_format: image_format.clone(),
mime_type: mime_type.to_string(),
size_bytes,
};
let structured = serialize_structured(&response, "export_image_impl")?;
let summary = format!(
"Image generated successfully ({} format, {} bytes)",
image_format, size_bytes
);
let mut result = CallToolResult::success(vec![Content::text(summary), Content::image(base64_data, mime_type)]);
result.structured_content = Some(structured);
Ok(result)
}