use crate::service::mcp_service::RudofMcpService;
use rmcp::{
ErrorData as McpError, handler::server::router::tool::ToolRouter, handler::server::wrapper::Parameters,
model::CallToolResult, tool, tool_router,
};
use schemars::JsonSchema;
use std::sync::{Arc, OnceLock};
use crate::service::tools::data_tools_impl::*;
use crate::service::tools::node_tools_impl::*;
use crate::service::tools::query_tools_impl::*;
use crate::service::tools::shacl_validate_tools_impl::*;
use crate::service::tools::shex_tools_impl::*;
use crate::service::tools::shex_validate_tools_impl::*;
#[tool_router]
impl RudofMcpService {
#[tool(
name = "load_rdf_data_from_sources",
description = "Load RDF data from remote sources (URLs, files, raw text) or SPARQL endpoint into the server's datastore",
annotations(
title = "Load RDF Data from Sources",
read_only_hint = false,
destructive_hint = false,
idempotent_hint = false,
open_world_hint = true,
)
)]
pub async fn load_rdf_data_from_sources(
&self,
params: Parameters<LoadRdfDataFromSourcesRequest>,
) -> Result<CallToolResult, McpError> {
load_rdf_data_from_sources_impl(self, params).await
}
#[tool(
name = "export_rdf_data",
description = "Serialize and return the RDF stored on the server in the requested format",
annotations(
title = "Export RDF Data",
read_only_hint = true,
destructive_hint = false,
idempotent_hint = true,
open_world_hint = false,
)
)]
pub async fn export_rdf_data(&self, params: Parameters<ExportRdfDataRequest>) -> Result<CallToolResult, McpError> {
export_rdf_data_impl(self, params).await
}
#[tool(
name = "export_plantuml",
description = "Generate a PlantUML diagram of the RDF stored on the server",
annotations(
title = "Export PlantUML Diagram",
read_only_hint = true,
destructive_hint = false,
idempotent_hint = true,
open_world_hint = false,
)
)]
pub async fn export_plantuml(&self, params: Parameters<EmptyRequest>) -> Result<CallToolResult, McpError> {
export_plantuml_impl(self, params).await
}
#[tool(
name = "export_image",
description = "Generate an image (SVG or PNG) visualization of the RDF stored on the server",
annotations(
title = "Export RDF Image Visualization",
read_only_hint = true,
destructive_hint = false,
idempotent_hint = true,
open_world_hint = false,
)
)]
pub async fn export_image(&self, params: Parameters<ExportImageRequest>) -> Result<CallToolResult, McpError> {
export_image_impl(self, params).await
}
#[tool(
name = "node_info",
description = "Show information about a node (outgoing/incoming arcs) from the RDF stored on the server",
annotations(
title = "Inspect RDF Node",
read_only_hint = true,
destructive_hint = false,
idempotent_hint = true,
open_world_hint = false,
)
)]
pub async fn node_info(&self, params: Parameters<NodeInfoRequest>) -> Result<CallToolResult, McpError> {
node_info_impl(self, params).await
}
#[tool(
name = "execute_sparql_query",
description = "Execute a SPARQL query (SELECT, CONSTRUCT, ASK, DESCRIBE) against the RDF stored on the server. You can provide either a direct SPARQL query or a natural language description that will be converted to SPARQL using an LLM.",
annotations(
title = "Execute SPARQL Query",
read_only_hint = true,
destructive_hint = false,
idempotent_hint = true,
open_world_hint = false,
)
)]
pub async fn execute_sparql_query(
&self,
params: Parameters<ExecuteSparqlQueryRequest>,
) -> Result<CallToolResult, McpError> {
execute_sparql_query_impl(self, params).await
}
#[tool(
name = "validate_shex",
description = "Validate the RDF data stored on the server against a ShEx schema",
annotations(
title = "Validate RDF with ShEx",
read_only_hint = true,
destructive_hint = false,
idempotent_hint = true,
open_world_hint = false,
)
)]
pub async fn validate_shex(&self, params: Parameters<ValidateShexRequest>) -> Result<CallToolResult, McpError> {
validate_shex_impl(self, params).await
}
#[tool(
name = "check_shex",
description = "Check if a ShEx schema is well-formed",
annotations(
title = "Check ShEx Schema Well-Formedness",
read_only_hint = true,
destructive_hint = false,
idempotent_hint = true,
open_world_hint = false,
)
)]
pub async fn check_shex(&self, params: Parameters<CheckShexRequest>) -> Result<CallToolResult, McpError> {
check_shex_impl(self, params).await
}
#[tool(
name = "show_shex",
description = "Parse a ShEx schema and display it with optional compilation, statistics, and dependency analysis",
annotations(
title = "Parse and Display ShEx Schema",
read_only_hint = true,
destructive_hint = false,
idempotent_hint = true,
open_world_hint = false,
)
)]
pub async fn show_shex(&self, params: Parameters<ShowShexRequest>) -> Result<CallToolResult, McpError> {
show_shex_impl(self, params).await
}
#[tool(
name = "validate_shacl",
description = "Validate the RDF data stored on the server against a SHACL schema",
annotations(
title = "Validate RDF with SHACL",
read_only_hint = true,
destructive_hint = false,
idempotent_hint = true,
open_world_hint = false,
)
)]
pub async fn validate_shacl(&self, params: Parameters<ValidateShaclRequest>) -> Result<CallToolResult, McpError> {
validate_shacl_impl(self, params).await
}
}
pub fn tool_router_public() -> ToolRouter<RudofMcpService> {
RudofMcpService::tool_router()
}
fn output_schema_for<T: JsonSchema + 'static>(tool_name: &str) -> Arc<rmcp::model::JsonObject> {
rmcp::handler::server::tool::schema_for_output::<T>().unwrap_or_else(|e| {
tracing::error!(
tool_name = %tool_name,
error = %e,
"Invalid tool output schema; falling back to empty schema"
);
Arc::new(rmcp::model::JsonObject::default())
})
}
fn apply_tool_metadata(
tool: &mut rmcp::model::Tool,
task_support: rmcp::model::TaskSupport,
output_schema: Arc<rmcp::model::JsonObject>,
) {
tool.execution = Some(rmcp::model::ToolExecution::from_raw(Some(task_support)));
tool.output_schema = Some(output_schema);
}
fn build_annotated_tools() -> Vec<rmcp::model::Tool> {
let mut tools = tool_router_public().list_all();
for tool in tools.iter_mut() {
let (output_schema, task_support) = match tool.name.as_ref() {
"load_rdf_data_from_sources" => (
output_schema_for::<LoadRdfDataFromSourcesResponse>("load_rdf_data_from_sources"),
rmcp::model::TaskSupport::Forbidden,
),
"export_rdf_data" => (
output_schema_for::<ExportRdfDataResponse>("export_rdf_data"),
rmcp::model::TaskSupport::Forbidden,
),
"export_plantuml" => (
output_schema_for::<ExportPlantUmlResponse>("export_plantuml"),
rmcp::model::TaskSupport::Forbidden,
),
"export_image" => (
output_schema_for::<ExportImageResponse>("export_image"),
rmcp::model::TaskSupport::Forbidden,
),
"node_info" => (
output_schema_for::<NodeInfoResponse>("node_info"),
rmcp::model::TaskSupport::Forbidden,
),
"execute_sparql_query" => (
output_schema_for::<QueryExecutionResponse>("execute_sparql_query"),
rmcp::model::TaskSupport::Forbidden,
),
"show_shex" => (
output_schema_for::<ShowShexResponse>("show_shex"),
rmcp::model::TaskSupport::Forbidden,
),
"check_shex" => (
output_schema_for::<CheckShexResponse>("check_shex"),
rmcp::model::TaskSupport::Forbidden,
),
"validate_shex" => (
output_schema_for::<ValidateShexResponse>("validate_shex"),
rmcp::model::TaskSupport::Forbidden,
),
"validate_shacl" => (
output_schema_for::<ValidateShaclResponse>("validate_shacl"),
rmcp::model::TaskSupport::Forbidden,
),
_ => {
tracing::warn!(tool_name = %tool.name, "Tool missing output schema");
continue;
},
};
apply_tool_metadata(tool, task_support, output_schema);
}
tools
}
pub fn annotated_tools() -> &'static [rmcp::model::Tool] {
static TOOLS: OnceLock<Vec<rmcp::model::Tool>> = OnceLock::new();
TOOLS.get_or_init(build_annotated_tools)
}