#![allow(clippy::disallowed_methods)]
use crate::tools::args::{self, try_arg};
use crate::tools::subprocess::run_apr;
use crate::types::{InputSchema, ToolCallResult, ToolDefinition};
pub const NAME: &str = "apr.tensors";
#[must_use]
pub fn tensors_tool_definition() -> ToolDefinition {
let input_schema: InputSchema = serde_json::from_str(crate::schemas::APR_TENSORS_SCHEMA)
.expect(
"FALSIFY-MCP-008: apr.tensors codegen constant must parse as InputSchema; \
regenerate by editing contracts/apr-mcp-tool-schemas-v1.yaml and rebuilding",
);
ToolDefinition {
name: NAME.to_string(),
description: crate::schemas::APR_TENSORS_DESCRIPTION.to_string(),
input_schema,
}
}
pub fn build_argv(args: &serde_json::Value) -> Result<Vec<String>, String> {
let model_path = args::required_str(args, "model_path")?;
let mut argv: Vec<String> = vec![
"tensors".to_string(),
model_path.to_string(),
"--json".to_string(),
];
if args::opt_bool(args, "stats")?.unwrap_or(false) {
argv.push("--stats".to_string());
}
let filter = args::opt_str(args, "filter")?.unwrap_or("");
if !filter.is_empty() {
argv.push("--filter".to_string());
argv.push(filter.to_string());
}
Ok(argv)
}
#[must_use]
pub fn call(args: &serde_json::Value) -> ToolCallResult {
let owned = try_arg!(build_argv(args));
let argv: Vec<&str> = owned.iter().map(String::as_str).collect();
run_apr(&argv)
}
pub fn dispatch(
args: &serde_json::Value,
_cancel: &std::sync::mpsc::Receiver<()>,
_sink: Option<&crate::server::NotificationSink>,
_token: Option<serde_json::Value>,
) -> ToolCallResult {
call(args)
}
crate::register_mcp_tool!(
name: NAME,
definition: tensors_tool_definition,
dispatch: dispatch,
);
#[cfg(test)]
#[allow(clippy::disallowed_methods)]
mod tests {
use super::*;
#[test]
fn definition_has_correct_name_and_required_field() {
let def = tensors_tool_definition();
assert_eq!(def.name, "apr.tensors");
assert_eq!(def.input_schema.schema_type, "object");
assert_eq!(def.input_schema.required, vec!["model_path".to_string()]);
assert!(def.input_schema.properties.contains_key("model_path"));
assert!(def.input_schema.properties.contains_key("stats"));
assert!(def.input_schema.properties.contains_key("filter"));
}
#[test]
fn missing_model_path_returns_error() {
let result = call(&serde_json::json!({}));
assert_eq!(result.is_error, Some(true));
assert!(result.content[0].text.contains("model_path"));
}
#[test]
fn string_stats_still_asks_for_stats() {
let argv = build_argv(&serde_json::json!({ "model_path": "m.gguf", "stats": "true" }))
.expect("\"true\" is a usable boolean");
assert!(argv.contains(&"--stats".to_string()), "{argv:?}");
}
#[test]
fn unusable_stats_is_an_error_not_a_dropped_flag() {
let result = call(&serde_json::json!({ "model_path": "m.gguf", "stats": 1 }));
assert_eq!(result.is_error, Some(true));
assert!(result.content[0].text.contains("stats"));
}
#[test]
fn non_boolean_stats_is_rejected_rather_than_read_as_false() {
let result = call(&serde_json::json!({
"model_path": "/nonexistent/model.gguf",
"stats": "yes",
}));
assert_eq!(result.is_error, Some(true));
let text = &result.content[0].text;
assert!(text.contains("stats"), "must name the argument: {text}");
assert!(
text.contains("boolean"),
"must state the expected type: {text}"
);
assert!(text.contains("yes"), "must quote what was received: {text}");
}
#[test]
fn non_string_filter_is_rejected() {
let result = call(&serde_json::json!({
"model_path": "/nonexistent/model.gguf",
"filter": 7,
}));
assert_eq!(result.is_error, Some(true));
assert!(result.content[0].text.contains("Invalid filter"));
}
}