use std::process::Command;
use crate::{
Tool, execute_command,
serde_utils::{
PackageWithVersion, deserialize_string, locking_mode_to_cli_flags,
output_verbosity_to_cli_flags,
},
tools::Registry,
};
use rmcp::ErrorData;
#[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)]
pub struct CargoInfoRequest {
#[serde(flatten)]
pub package_spec: PackageWithVersion,
#[serde(default, deserialize_with = "deserialize_string")]
pub index: Option<String>,
#[serde(default)]
pub registry: Registry,
#[serde(default, deserialize_with = "deserialize_string")]
pub output_verbosity: Option<String>,
#[serde(default, deserialize_with = "deserialize_string")]
pub config: Option<String>,
#[serde(default, deserialize_with = "deserialize_string")]
pub locking_mode: Option<String>,
}
impl CargoInfoRequest {
pub fn build_cmd(&self) -> Result<Command, ErrorData> {
let mut cmd = Command::new("cargo");
cmd.arg("info");
cmd.arg(self.package_spec.to_spec());
if let Some(index) = &self.index {
cmd.arg("--index").arg(index);
}
if let Some(registry) = self.registry.value() {
cmd.arg("--registry").arg(registry);
}
if let Some(config) = &self.config {
cmd.arg("--config").arg(config);
}
let locking_flags = locking_mode_to_cli_flags(self.locking_mode.as_deref(), "locked")?;
cmd.args(locking_flags);
let output_flags = output_verbosity_to_cli_flags(self.output_verbosity.as_deref())?;
cmd.args(output_flags);
Ok(cmd)
}
}
pub struct CargoInfoRmcpTool;
impl Tool for CargoInfoRmcpTool {
const NAME: &'static str = "cargo-info";
const TITLE: &'static str = "cargo info";
const DESCRIPTION: &'static str = "Display information about a package. Information includes package description, list of available features, etc. Equivalent to 'cargo info <SPEC>'.";
type RequestArgs = CargoInfoRequest;
fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result<crate::Response, ErrorData> {
let cmd = request.build_cmd()?;
execute_command(cmd, Self::NAME).map(Into::into)
}
}
#[cfg(test)]
mod tests {
use crate::tool::DynTool;
use super::*;
#[test]
fn test_cargo_info_schema() {
const EXPECTED_SCHEMA: &str = r##"{
"description": "Display information about a package. Information includes package description, list of available features, etc. Equivalent to 'cargo info <SPEC>'.",
"properties": {
"config": {
"default": null,
"description": "Override a configuration value",
"type": "string"
},
"index": {
"default": null,
"description": "Registry index URL to search packages in",
"type": "string"
},
"locking_mode": {
"default": null,
"description": "Locking mode for dependency resolution.\n\nValid options:\n- \"locked\" (default): Assert that `Cargo.lock` will remain unchanged\n- \"unlocked\": Allow `Cargo.lock` to be updated\n- \"offline\": Run without accessing the network\n- \"frozen\": Equivalent to specifying both --locked and --offline",
"type": "string"
},
"output_verbosity": {
"default": null,
"description": "Output verbosity level.\n\nValid options:\n- \"quiet\" (default): Show only the essential command output\n- \"normal\": Show standard output (no additional flags)\n- \"verbose\": Show detailed output including build information",
"type": "string"
},
"package": {
"description": "The package name",
"type": "string"
},
"registry": {
"default": null,
"description": "Registry to search packages in",
"type": "string"
},
"version": {
"default": null,
"description": "Optional version specification",
"type": "string"
}
},
"required": [
"package"
],
"title": "CargoInfoRequest",
"type": "object"
}"##;
let schema = serde_json::Value::from(CargoInfoRmcpTool {}.json_schema());
println!(
"CargoInfoTool schema: {}",
serde_json::to_string_pretty(&schema).unwrap()
);
let expected_schema: serde_json::Value = serde_json::from_str(EXPECTED_SCHEMA).unwrap();
assert_eq!(
schema, expected_schema,
"CargoInfoTool schema should match expected structure"
);
}
}