use std::sync::Arc;
use std::time::Duration;
use serde_json::Value;
use sqry_mcp::daemon_adapter::WorkspaceContext;
use sqry_mcp::execution::ToolExecution;
use crate::ipc::methods::{HandlerContext, MethodError};
use crate::ipc::protocol::{ResponseEnvelope, ResponseMeta};
use crate::ipc::tool_core;
pub(crate) mod tools;
pub(crate) async fn dispatch_tool(
ctx: &HandlerContext,
method: &str,
params: Value,
) -> Result<Value, MethodError> {
match method {
"semantic_search" => tools::semantic_search::handle(ctx, params).await,
"relation_query" => tools::relation_query::handle(ctx, params).await,
"direct_callers" => tools::direct_callers::handle(ctx, params).await,
"direct_callees" => tools::direct_callees::handle(ctx, params).await,
"find_unused" => tools::find_unused::handle(ctx, params).await,
"find_cycles" => tools::find_cycles::handle(ctx, params).await,
"is_node_in_cycle" => tools::is_node_in_cycle::handle(ctx, params).await,
"trace_path" => tools::trace_path::handle(ctx, params).await,
"subgraph" => tools::subgraph::handle(ctx, params).await,
"export_graph" => tools::export_graph::handle(ctx, params).await,
"complexity_metrics" => tools::complexity_metrics::handle(ctx, params).await,
"semantic_diff" => tools::semantic_diff::handle(ctx, params).await,
"dependency_impact" => tools::dependency_impact::handle(ctx, params).await,
"show_dependencies" => tools::show_dependencies::handle(ctx, params).await,
other => Err(MethodError::MethodNotFound(other.to_owned())),
}
}
pub(crate) fn rpc_error_to_method_error(e: sqry_mcp::error::RpcError) -> MethodError {
if e.code == -32602 {
MethodError::InvalidParamsStructured {
message: e.message,
kind: e.kind,
retryable: e.retryable,
retry_after_ms: e.retry_after_ms,
details: e.details,
}
} else {
MethodError::Internal(anyhow::anyhow!("{} ({})", e.message, e.kind))
}
}
pub(crate) async fn classify_and_build<T, F>(
ctx: &HandlerContext,
tool_name: &'static str,
path: &str,
run: F,
) -> Result<Value, MethodError>
where
F: FnOnce(
&WorkspaceContext,
&sqry_core::query::cancellation::CancellationToken,
) -> anyhow::Result<ToolExecution<T>>
+ Send
+ 'static,
T: serde::Serialize + Send + 'static,
{
let run_as_value = move |wctx: &WorkspaceContext,
cancel: &sqry_core::query::cancellation::CancellationToken|
-> anyhow::Result<Value> {
let exec = run(wctx, cancel)?;
let inner = sqry_mcp::daemon_adapter::tool_response_json(exec)
.map_err(|e| anyhow::anyhow!("response build: {e:?}"))?;
Ok(inner)
};
let tool_timeout = Duration::from_secs(ctx.config.tool_timeout_secs);
let verdict = tool_core::acquire_and_execute(
Arc::clone(&ctx.manager),
Arc::clone(&ctx.workspace_builder),
Arc::clone(&ctx.tool_executor),
tool_timeout,
path,
Some(tool_name),
run_as_value,
)
.await
.map_err(MethodError::Daemon)?;
match verdict {
tool_core::ExecuteVerdict::Fresh { inner, state } => {
let envelope = ResponseEnvelope {
result: inner,
meta: ResponseMeta::fresh_from(state, ctx.daemon_version),
};
serde_json::to_value(&envelope)
.map_err(|e| MethodError::Internal(anyhow::anyhow!("envelope serialise: {e}")))
}
tool_core::ExecuteVerdict::Stale {
mut inner,
stale_warning,
last_good_at,
last_error,
} => {
if let Value::Object(ref mut map) = inner {
map.insert("_stale_warning".into(), Value::String(stale_warning));
}
let envelope = ResponseEnvelope {
result: inner,
meta: ResponseMeta::stale_from(last_good_at, last_error, ctx.daemon_version),
};
serde_json::to_value(&envelope)
.map_err(|e| MethodError::Internal(anyhow::anyhow!("envelope serialise: {e}")))
}
}
}