use crate::service::{errors::*, mcp_service::*};
use rmcp::{
ErrorData as McpError,
handler::server::wrapper::Parameters,
model::{CallToolResult, Content},
};
use rudof_lib::{node_info::*, parse_node_selector};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::io::Cursor;
use super::helpers::*;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct NodeInfoRequest {
pub node: String,
pub predicates: Option<Vec<String>>,
pub mode: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct NodeInfoResponse {
pub subject: String,
pub outgoing: Vec<NodePredicateObjects>,
pub incoming: Vec<NodePredicateSubjects>,
pub outgoing_count: usize,
pub incoming_count: usize,
pub total_outgoing_objects: usize,
pub total_incoming_subjects: usize,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct NodePredicateObjects {
pub predicate: String,
pub objects: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct NodePredicateSubjects {
pub predicate: String,
pub subjects: Vec<String>,
}
pub async fn node_info_impl(
service: &RudofMcpService,
Parameters(NodeInfoRequest { node, predicates, mode }): Parameters<NodeInfoRequest>,
) -> Result<CallToolResult, McpError> {
let rudof = service.rudof.lock().await;
let rdf = rudof.get_rdf_data();
let node_selector = match parse_node_selector(&node) {
Ok(sel) => sel,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid node selector '{}': {}", node, e),
"Provide a valid IRI (e.g., 'http://example.org/node'), prefixed name (e.g., 'ex:node'), or blank node (e.g., '_:b0')",
)
.into_call_tool_result());
},
};
let mode_str = mode.as_deref().unwrap_or("both");
let mut options = match NodeInfoOptions::from_mode_str(mode_str) {
Ok(opts) => opts,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid mode '{}': {}", mode_str, e),
format!("Supported modes: {}", NODE_INFO_MODES),
)
.into_call_tool_result());
},
};
options.show_colors = false;
let pred_list: Vec<String> = predicates.unwrap_or_default();
let node_infos = match get_node_info(rdf, node_selector, &pred_list, &options) {
Ok(infos) => infos,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Node not found: {}", e),
"Verify the node exists in the loaded RDF data. Use the correct IRI or prefixed name.",
)
.into_call_tool_result());
},
};
let node_info = &node_infos[0];
let mut output_buffer = Cursor::new(Vec::new());
format_node_info_list(&node_infos, rdf, &mut output_buffer, &options).map_err(|e| {
internal_error(
"Serialization error",
e.to_string(),
Some(json!({"operation":"node_info_impl", "phase":"format_node_info_list"})),
)
})?;
let output_bytes = output_buffer.into_inner();
let output_str = String::from_utf8(output_bytes).map_err(|e| {
internal_error(
"Conversion error",
e.to_string(),
Some(json!({"operation":"node_info_impl", "phase":"utf8_conversion"})),
)
})?;
let outgoing_data: Vec<NodePredicateObjects> = node_info
.outgoing
.iter()
.map(|(predicate_iri, objects_vec)| NodePredicateObjects {
predicate: predicate_iri.to_string(),
objects: objects_vec.iter().map(|term| term.to_string()).collect(),
})
.collect();
let incoming_data: Vec<NodePredicateSubjects> = node_info
.incoming
.iter()
.map(|(predicate_iri, subjects_vec)| NodePredicateSubjects {
predicate: predicate_iri.to_string(),
subjects: subjects_vec.iter().map(|subject| subject.to_string()).collect(),
})
.collect();
let outgoing_count = outgoing_data.len();
let incoming_count = incoming_data.len();
let total_outgoing_objects: usize = outgoing_data.iter().map(|p| p.objects.len()).sum();
let total_incoming_subjects: usize = incoming_data.iter().map(|p| p.subjects.len()).sum();
let response = NodeInfoResponse {
subject: node_info.subject_qualified.clone(),
outgoing: outgoing_data,
incoming: incoming_data,
outgoing_count,
incoming_count,
total_outgoing_objects,
total_incoming_subjects,
};
let structured = serde_json::to_value(&response).map_err(|e| {
internal_error(
"Serialization error",
e.to_string(),
Some(json!({"operation":"node_info_impl", "phase":"serialize_response"})),
)
})?;
let summary = format!(
"# Node Information: {}\n\n\
**Mode:** {}\n\
**Outgoing Predicates:** {}\n\
**Incoming Predicates:** {}\n\
**Total Outgoing Objects:** {}\n\
**Total Incoming Subjects:** {}\n",
node_info.subject_qualified,
mode_str,
outgoing_count,
incoming_count,
total_outgoing_objects,
total_incoming_subjects
);
let detailed_output = format!("## Detailed Node Information\n\n```\n{}\n```", output_str);
let mut result = CallToolResult::success(vec![Content::text(summary), Content::text(detailed_output)]);
result.structured_content = Some(structured);
Ok(result)
}