use crate::mcp::tools::agent_context_tools::{
FindSimilarTool, GetFunctionTool, IndexManager, IndexStatsTool, McpTool as InnerMcpTool,
QueryCodeTool,
};
use async_trait::async_trait;
use pmcp::{Error as PmcpError, RequestHandlerExtra, Result as PmcpResult, ToolHandler};
use serde_json::Value;
use std::sync::Arc;
const CLIENT_FAULT_PREFIXES: &[&str] = &[
"Missing required parameter",
"Invalid function_id format",
"Function not found",
];
fn adapt_tool_error(message: String) -> PmcpError {
if CLIENT_FAULT_PREFIXES
.iter()
.any(|p| message.starts_with(*p))
{
return PmcpError::validation(message);
}
PmcpError::internal(message)
}
macro_rules! impl_pmat_handler {
($wrapper:ident, $inner:ident, $tool_name:expr) => {
pub struct $wrapper {
inner: $inner,
}
impl $wrapper {
pub fn new(mgr: Arc<IndexManager>) -> Self {
Self {
inner: $inner::new(mgr),
}
}
}
#[async_trait]
impl ToolHandler for $wrapper {
async fn handle(&self, args: Value, _extra: RequestHandlerExtra) -> PmcpResult<Value> {
self.inner.execute(args).await.map_err(adapt_tool_error)
}
fn metadata(&self) -> Option<pmcp::types::ToolInfo> {
Some(crate::mcp_pmcp::tool_schemas_generated::tool_info_for(
$tool_name,
))
}
}
};
}
impl_pmat_handler!(PmatQueryCodeHandler, QueryCodeTool, "pmat_query_code");
impl_pmat_handler!(PmatGetFunctionHandler, GetFunctionTool, "pmat_get_function");
impl_pmat_handler!(PmatFindSimilarHandler, FindSimilarTool, "pmat_find_similar");
impl_pmat_handler!(PmatIndexStatsHandler, IndexStatsTool, "pmat_index_stats");
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn argument_faults_become_validation_errors() {
for message in [
"Missing required parameter: function_id",
"Invalid function_id format. Expected 'file_path::function_name', got: x",
"Function not found: src/a.rs::gone",
] {
assert!(
matches!(
adapt_tool_error(message.to_string()),
PmcpError::Validation(_)
),
"{message} is the caller's mistake, not ours"
);
}
}
#[test]
fn our_own_failures_stay_internal() {
for message in [
"Failed to build index: permission denied",
"IO error reading the index",
] {
assert!(
matches!(
adapt_tool_error(message.to_string()),
PmcpError::Internal(_)
),
"{message} must not be reported as a bad argument"
);
}
}
}