aprender_mcp/tools/
bench.rs1#![allow(clippy::disallowed_methods)] use crate::tools::subprocess::run_apr;
8use crate::types::{InputSchema, ToolCallResult, ToolDefinition};
9
10pub const NAME: &str = "apr.bench";
12
13#[must_use]
22pub fn bench_tool_definition() -> ToolDefinition {
23 let input_schema: InputSchema = serde_json::from_str(crate::schemas::APR_BENCH_SCHEMA).expect(
24 "FALSIFY-MCP-008: apr.bench codegen constant must parse as InputSchema; \
25 regenerate by editing contracts/apr-mcp-tool-schemas-v1.yaml and rebuilding",
26 );
27 ToolDefinition {
28 name: NAME.to_string(),
29 description: crate::schemas::APR_BENCH_DESCRIPTION.to_string(),
30 input_schema,
31 }
32}
33
34#[must_use]
36pub fn call(args: &serde_json::Value) -> ToolCallResult {
37 let Some(model_path) = args.get("model_path").and_then(|v| v.as_str()) else {
38 return ToolCallResult::error("Missing required argument: model_path");
39 };
40
41 let mut owned: Vec<String> = vec![
42 "bench".to_string(),
43 model_path.to_string(),
44 "--json".to_string(),
45 ];
46
47 if let Some(n) = args.get("iterations").and_then(serde_json::Value::as_u64) {
48 owned.push("--iterations".to_string());
49 owned.push(n.to_string());
50 }
51 if let Some(n) = args.get("max_tokens").and_then(serde_json::Value::as_u64) {
52 owned.push("--max-tokens".to_string());
53 owned.push(n.to_string());
54 }
55 let prompt = args.get("prompt").and_then(|v| v.as_str()).unwrap_or("");
56 if !prompt.is_empty() {
57 owned.push("--prompt".to_string());
58 owned.push(prompt.to_string());
59 }
60
61 let argv: Vec<&str> = owned.iter().map(String::as_str).collect();
62 run_apr(&argv)
63}
64
65pub fn dispatch(
67 args: &serde_json::Value,
68 _cancel: &std::sync::mpsc::Receiver<()>,
69 _sink: Option<&crate::server::NotificationSink>,
70 _token: Option<serde_json::Value>,
71) -> ToolCallResult {
72 call(args)
73}
74
75crate::register_mcp_tool!(
76 name: NAME,
77 definition: bench_tool_definition,
78 dispatch: dispatch,
79);
80
81#[cfg(test)]
82#[allow(clippy::disallowed_methods)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn definition_has_correct_name_and_required_field() {
88 let def = bench_tool_definition();
89 assert_eq!(def.name, "apr.bench");
90 assert_eq!(def.input_schema.schema_type, "object");
91 assert_eq!(def.input_schema.required, vec!["model_path".to_string()]);
92 for field in ["model_path", "iterations", "max_tokens", "prompt"] {
93 assert!(
94 def.input_schema.properties.contains_key(field),
95 "property {field} present"
96 );
97 }
98 }
99
100 #[test]
101 fn missing_model_path_returns_error() {
102 let result = call(&serde_json::json!({}));
103 assert_eq!(result.is_error, Some(true));
104 assert!(result.content[0].text.contains("model_path"));
105 }
106}