mod add_remove;
mod build;
mod check;
mod clippy;
mod doc;
mod info;
mod metadata;
mod package;
mod search;
mod test;
mod tree;
mod update;
mod workspace_info;
pub use add_remove::{CargoAddRmcpTool, CargoRemoveRmcpTool};
pub use build::CargoBuildRmcpTool;
pub use check::CargoCheckRmcpTool;
pub use clippy::CargoClippyRmcpTool;
pub use doc::CargoDocRmcpTool;
pub use info::CargoInfoRmcpTool;
pub use metadata::CargoMetadataRmcpTool;
pub use package::CargoPackageRmcpTool;
pub use search::CargoSearchRmcpTool;
pub use test::CargoTestRmcpTool;
pub use tree::CargoTreeRmcpTool;
pub use update::CargoUpdateRmcpTool;
pub use workspace_info::CargoWorkspaceInfoRmcpTool;
use std::process::Command;
use crate::{
Tool, execute_command,
serde_utils::{
deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags,
output_verbosity_to_cli_flags,
},
tools::Registry,
};
use rmcp::ErrorData;
#[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)]
pub struct CargoGenerateLockfileRequest {
#[serde(default, deserialize_with = "deserialize_string")]
manifest_path: Option<String>,
#[serde(default, deserialize_with = "deserialize_string")]
lockfile_path: Option<String>,
#[serde(default)]
ignore_rust_version: Option<bool>,
#[serde(default, deserialize_with = "deserialize_string")]
locking_mode: Option<String>,
#[serde(default, deserialize_with = "deserialize_string")]
output_verbosity: Option<String>,
}
impl CargoGenerateLockfileRequest {
pub fn build_cmd(&self) -> Result<Command, ErrorData> {
let mut cmd = Command::new("cargo");
cmd.arg("generate-lockfile");
if let Some(manifest_path) = &self.manifest_path {
cmd.arg("--manifest-path").arg(manifest_path);
}
if let Some(lockfile_path) = &self.lockfile_path {
cmd.arg("--lockfile-path").arg(lockfile_path);
}
if self.ignore_rust_version.unwrap_or(false) {
cmd.arg("--ignore-rust-version");
}
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 CargoGenerateLockfileRmcpTool;
impl Tool for CargoGenerateLockfileRmcpTool {
const NAME: &'static str = "cargo-generate_lockfile";
const TITLE: &'static str = "Generate Cargo.lock";
const DESCRIPTION: &'static str = "Generates or updates the Cargo.lock file for a Rust project. Usually, run without any additional arguments.";
type RequestArgs = CargoGenerateLockfileRequest;
fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result<crate::Response, ErrorData> {
execute_command(request.build_cmd()?, Self::NAME).map(Into::into)
}
}
#[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)]
pub struct CargoCleanRequest {
#[serde(default, deserialize_with = "deserialize_string")]
toolchain: Option<String>,
#[serde(default, deserialize_with = "deserialize_string_vec")]
package: Option<Vec<String>>,
#[serde(default, deserialize_with = "deserialize_string")]
profile: Option<String>,
#[serde(default)]
doc: Option<bool>,
#[serde(default)]
dry_run: Option<bool>,
#[serde(default)]
release: Option<bool>,
#[serde(default, deserialize_with = "deserialize_string")]
target: Option<String>,
#[serde(default, deserialize_with = "deserialize_string")]
target_dir: Option<String>,
#[serde(default, deserialize_with = "deserialize_string")]
manifest_path: Option<String>,
#[serde(default, deserialize_with = "deserialize_string")]
lockfile_path: Option<String>,
#[serde(default, deserialize_with = "deserialize_string")]
locking_mode: Option<String>,
#[serde(default, deserialize_with = "deserialize_string")]
output_verbosity: Option<String>,
}
impl CargoCleanRequest {
pub fn build_cmd(&self) -> Result<Command, ErrorData> {
let mut cmd = Command::new("cargo");
if let Some(toolchain) = &self.toolchain {
cmd.arg(format!("+{toolchain}"));
}
cmd.arg("clean");
if let Some(packages) = &self.package {
for package in packages {
cmd.arg("--package").arg(package);
}
}
if let Some(profile) = &self.profile {
cmd.arg("--profile").arg(profile);
}
if self.doc.unwrap_or(false) {
cmd.arg("--doc");
}
if self.dry_run.unwrap_or(false) {
cmd.arg("--dry-run");
}
if self.release.unwrap_or(false) {
cmd.arg("--release");
}
if let Some(target) = &self.target {
cmd.arg("--target").arg(target);
}
if let Some(target_dir) = &self.target_dir {
cmd.arg("--target-dir").arg(target_dir);
}
if let Some(manifest_path) = &self.manifest_path {
cmd.arg("--manifest-path").arg(manifest_path);
}
if let Some(lockfile_path) = &self.lockfile_path {
cmd.arg("--lockfile-path").arg(lockfile_path);
}
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 CargoCleanRmcpTool;
impl Tool for CargoCleanRmcpTool {
const NAME: &'static str = "cargo-clean";
const TITLE: &'static str = "Clean Cargo artifacts";
const DESCRIPTION: &'static str = "Cleans the target directory for a Rust project using Cargo. By default, it cleans the entire workspace.";
type RequestArgs = CargoCleanRequest;
fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result<crate::Response, ErrorData> {
execute_command(request.build_cmd()?, Self::NAME).map(Into::into)
}
}
#[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)]
pub struct CargoFmtRequest {
#[serde(default, deserialize_with = "deserialize_string")]
toolchain: Option<String>,
#[serde(default, deserialize_with = "deserialize_string_vec")]
package: Option<Vec<String>>,
#[serde(default)]
all: bool,
#[serde(default)]
check: bool,
#[serde(default, deserialize_with = "deserialize_string")]
manifest_path: Option<String>,
#[serde(default, deserialize_with = "deserialize_string")]
message_format: Option<String>,
#[serde(default, deserialize_with = "deserialize_string")]
output_verbosity: Option<String>,
}
impl CargoFmtRequest {
pub fn build_cmd(&self) -> Result<Command, ErrorData> {
let mut cmd = Command::new("cargo");
if let Some(toolchain) = &self.toolchain {
cmd.arg(format!("+{toolchain}"));
}
cmd.arg("fmt");
if let Some(packages) = &self.package {
for package in packages {
cmd.arg("--package").arg(package);
}
}
if self.all {
cmd.arg("--all");
}
if self.check {
cmd.arg("--check");
}
if let Some(manifest_path) = &self.manifest_path {
cmd.arg("--manifest-path").arg(manifest_path);
}
if let Some(message_format) = &self.message_format {
cmd.arg("--message-format").arg(message_format);
}
let output_flags = output_verbosity_to_cli_flags(self.output_verbosity.as_deref())?;
cmd.args(output_flags);
Ok(cmd)
}
}
pub struct CargoFmtRmcpTool;
impl Tool for CargoFmtRmcpTool {
const NAME: &'static str = "cargo-fmt";
const TITLE: &'static str = "Format Rust code";
const DESCRIPTION: &'static str =
"Formats Rust code using rustfmt. Usually, run without any additional arguments.";
type RequestArgs = CargoFmtRequest;
fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result<crate::Response, ErrorData> {
let output = execute_command(request.build_cmd()?, Self::NAME)?;
let failed = !output.success();
let mut response: crate::Response = output.into();
if failed && request.check {
response.add_recommendation(format!(
"Run #{} with `check: false` to automatically format the code",
Self::NAME
));
}
Ok(response)
}
}
#[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)]
pub struct CargoNewRequest {
#[serde(default, deserialize_with = "deserialize_string")]
toolchain: Option<String>,
pub path: String,
#[serde(default, deserialize_with = "deserialize_string")]
pub name: Option<String>,
#[serde(default)]
pub bin: bool,
#[serde(default)]
pub lib: Option<bool>,
#[serde(default, deserialize_with = "deserialize_string")]
pub edition: Option<String>,
#[serde(default, deserialize_with = "deserialize_string")]
pub vcs: Option<String>,
#[serde(default)]
pub registry: Registry,
#[serde(default, deserialize_with = "deserialize_string")]
pub locking_mode: Option<String>,
#[serde(default, deserialize_with = "deserialize_string")]
pub output_verbosity: Option<String>,
}
impl CargoNewRequest {
pub fn build_cmd(&self) -> Result<Command, ErrorData> {
let mut cmd = Command::new("cargo");
if let Some(toolchain) = &self.toolchain {
cmd.arg(format!("+{toolchain}"));
}
cmd.arg("new");
cmd.arg(&self.path);
if self.bin {
cmd.arg("--bin");
}
if self.lib.unwrap_or(false) {
cmd.arg("--lib");
}
if let Some(name) = &self.name {
cmd.arg("--name").arg(name);
}
if let Some(edition) = &self.edition {
cmd.arg("--edition").arg(edition);
}
if let Some(registry) = self.registry.value() {
cmd.arg("--registry").arg(registry);
}
if let Some(vcs) = &self.vcs {
cmd.arg("--vcs").arg(vcs);
}
let locking_flags = locking_mode_to_cli_flags(self.locking_mode.as_deref(), "unlocked")?;
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 CargoNewRmcpTool;
impl Tool for CargoNewRmcpTool {
const NAME: &'static str = "cargo-new";
const TITLE: &'static str = "Create new Rust project";
const DESCRIPTION: &'static str = "Create a new cargo package at <path>. Creates a new Rust project with the specified name and template.";
type RequestArgs = CargoNewRequest;
fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result<crate::Response, ErrorData> {
execute_command(request.build_cmd()?, Self::NAME).map(Into::into)
}
}
#[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)]
pub struct CargoListRequest {}
impl CargoListRequest {
pub fn build_cmd(&self) -> Result<Command, ErrorData> {
let mut cmd = Command::new("cargo");
cmd.arg("--list");
Ok(cmd)
}
}
pub struct CargoListRmcpTool;
impl Tool for CargoListRmcpTool {
const NAME: &'static str = "cargo-list";
const TITLE: &'static str = "List cargo commands";
const DESCRIPTION: &'static str = "Lists installed cargo commands using 'cargo --list'.";
type RequestArgs = CargoListRequest;
fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result<crate::Response, ErrorData> {
execute_command(request.build_cmd()?, Self::NAME).map(Into::into)
}
}