use async_trait::async_trait;
use rust_mcp_sdk::{McpServer, mcp_server::ServerHandler, schema::*};
use std::sync::{Arc, Mutex};
use crate::db::{
graph::{ConnectExt, GraphDb},
vector::{VectorConnectExt, VectorDb},
};
use crate::mcp_tools::{
explore_file::ExploreFileTool, find_callers::FindCallersTool,
list_repo_dependencies::ListRepoDependenciesTool,
search_hybrid_context::SearchHybridContextTool,
};
use crate::pipeline::embed::Embedder;
pub struct KnotMcpHandler {
pub vector_db: Option<Arc<VectorDb>>,
pub graph_db: Option<Arc<GraphDb>>,
pub embedder: Option<Arc<Mutex<Embedder>>>,
pub dry_run: bool,
}
impl KnotMcpHandler {
pub async fn new(
qdrant_url: &str,
qdrant_collection: &str,
neo4j_uri: &str,
neo4j_user: &str,
neo4j_password: &str,
embed_dim: u64,
) -> anyhow::Result<Self> {
let vector_db = VectorDb::connect(qdrant_url, qdrant_collection, embed_dim).await?;
let graph_db = GraphDb::connect(neo4j_uri, neo4j_user, neo4j_password).await?;
let embedder = Embedder::init()?;
Ok(Self {
vector_db: Some(Arc::new(vector_db)),
graph_db: Some(Arc::new(graph_db)),
embedder: Some(Arc::new(Mutex::new(embedder))),
dry_run: false,
})
}
pub fn new_dry_run() -> Self {
Self {
vector_db: None,
graph_db: None,
embedder: None,
dry_run: true,
}
}
}
#[async_trait]
impl ServerHandler for KnotMcpHandler {
async fn handle_list_tools_request(
&self,
_request: Option<PaginatedRequestParams>,
_runtime: Arc<dyn McpServer>,
) -> std::result::Result<ListToolsResult, RpcError> {
Ok(ListToolsResult {
tools: vec![
SearchHybridContextTool::tool(),
FindCallersTool::tool(),
ExploreFileTool::tool(),
ListRepoDependenciesTool::tool(),
],
meta: None,
next_cursor: None,
})
}
async fn handle_call_tool_request(
&self,
params: CallToolRequestParams,
_runtime: Arc<dyn McpServer>,
) -> std::result::Result<CallToolResult, CallToolError> {
if self.dry_run {
return Err(CallToolError::from_message(
"Server is running in dry-run mode. Database connections are not available. \
This mode is used for protocol validation and quality checks only."
.to_string(),
));
}
match params.name.as_str() {
"search_hybrid_context" => SearchHybridContextTool::handle(params, self).await,
"find_callers" => FindCallersTool::handle(params, self).await,
"explore_file" => ExploreFileTool::handle(params, self).await,
"list_repo_dependencies" => ListRepoDependenciesTool::handle(params, self).await,
_ => Err(CallToolError::unknown_tool(params.name)),
}
}
}