use std::pin::Pin;
use std::sync::Arc;
use anyhow::Result;
use mcp_methods::server::{Manifest, McpServer, ToolSpec};
use rmcp::handler::server::router::tool::ToolRoute;
use rmcp::handler::server::tool::ToolCallContext;
use rmcp::model::{CallToolResult, Content, Tool};
use rmcp::ErrorData as McpError;
use serde_json::{Map, Value};
use crate::tools::GraphState;
type DynFut<'a, T> = Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>;
pub type CypherRunner =
Arc<dyn Fn(&str, &Map<String, Value>) -> Result<String> + Send + Sync + 'static>;
pub fn make_runner(
state: GraphState,
csv_http: Option<Arc<crate::csv_http::CsvHttpConfig>>,
) -> CypherRunner {
Arc::new(move |template: &str, args: &Map<String, Value>| {
Ok(state.run_cypher_template(template, args, csv_http.as_deref()))
})
}
pub fn register_cypher_tools(
server: &mut McpServer,
manifest: &Manifest,
runner: CypherRunner,
) -> Result<usize> {
let cypher_tools: Vec<_> = manifest
.tools
.iter()
.filter_map(|t| match t {
ToolSpec::Cypher(c) => Some(c),
_ => None,
})
.collect();
if cypher_tools.is_empty() {
return Ok(0);
}
let count = cypher_tools.len();
let router = server.tool_router_mut();
for spec in cypher_tools {
let schema = spec
.parameters
.as_ref()
.and_then(|v| v.as_object().cloned())
.unwrap_or_else(|| {
let mut m = Map::new();
m.insert("type".into(), Value::String("object".into()));
m.insert("properties".into(), Value::Object(Map::new()));
m
});
let attr = Tool::new_with_raw(
spec.name.clone(),
spec.description
.as_deref()
.map(|s| std::borrow::Cow::Owned(s.to_string())),
Arc::new(schema),
);
let template = spec.cypher.clone();
let name = spec.name.clone();
let runner = runner.clone();
router.add_route(ToolRoute::new_dyn(
attr,
move |ctx: ToolCallContext<'_, McpServer>| -> DynFut<'_, Result<CallToolResult, McpError>> {
let runner = runner.clone();
let template = template.clone();
let name = name.clone();
let arguments = ctx.arguments.clone();
Box::pin(async move {
let args: Map<String, Value> = arguments.unwrap_or_default();
let body = match runner(&template, &args) {
Ok(text) => text,
Err(e) => format!("cypher tool {name:?} error: {e}"),
};
Ok(CallToolResult::success(vec![Content::text(body)]))
})
},
));
}
Ok(count)
}