gitai/server/tools/
utils.rs

1//! Common utilities for MCP tools
2//!
3//! This module provides shared functionality used across different MCP tool implementations.
4
5use crate::common::DetailLevel;
6use crate::config::Config as PilotConfig;
7use crate::git::GitRepo;
8use rmcp::model::{Annotated, CallToolResult, Content, RawContent, RawTextContent};
9use std::sync::Arc;
10
11/// Common trait for all `GitAI` MCP tools
12///
13/// This trait defines the common interface that all `GitAI` tools must implement.
14#[async_trait::async_trait]
15pub trait PilotTool {
16    /// Execute the tool with the provided repository and configuration
17    async fn execute(
18        &self,
19        git_repo: Arc<GitRepo>,
20        config: PilotConfig,
21    ) -> Result<CallToolResult, anyhow::Error>;
22}
23
24/// Creates a text result response for tool calls
25///
26/// This is a common utility used by all tools to return a text response.
27pub fn create_text_result(text: String) -> CallToolResult {
28    CallToolResult {
29        content: vec![Content::from(Annotated {
30            raw: RawContent::Text(RawTextContent {
31                text,
32                meta: Option::default(),
33            }),
34            annotations: None,
35        })],
36        is_error: None,
37        meta: Option::default(),
38        structured_content: None,
39    }
40}
41
42/// Parses a detail level string into the corresponding enum value
43///
44/// This provides consistent handling of detail level across all tools.
45pub fn parse_detail_level(detail_level: &str) -> DetailLevel {
46    if detail_level.trim().is_empty() {
47        return DetailLevel::Standard;
48    }
49
50    match detail_level.trim().to_lowercase().as_str() {
51        "minimal" => DetailLevel::Minimal,
52        "detailed" => DetailLevel::Detailed,
53        _ => DetailLevel::Standard,
54    }
55}
56
57/// Apply custom instructions to config if provided
58pub fn apply_custom_instructions(config: &mut crate::config::Config, custom_instructions: &str) {
59    if !custom_instructions.trim().is_empty() {
60        config.set_temp_instructions(Some(custom_instructions.to_string()));
61    }
62}
63
64/// Validates the repository parameter: must be non-empty, and if local, must exist and be a git repo
65pub fn validate_repository_parameter(repo: &str) -> Result<(), anyhow::Error> {
66    if repo.trim().is_empty() {
67        return Err(anyhow::anyhow!(
68            "The `repository` parameter is required and must be a valid local path or remote URL."
69        ));
70    }
71    if !(repo.starts_with("http://") || repo.starts_with("https://") || repo.starts_with("git@")) {
72        let path = std::path::Path::new(repo);
73        if !path.exists() {
74            return Err(anyhow::anyhow!(format!(
75                "The specified repository path does not exist: {repo}"
76            )));
77        }
78        if !path.join(".git").exists() {
79            return Err(anyhow::anyhow!(format!(
80                "The specified path is not a git repository: {repo}"
81            )));
82        }
83    }
84    Ok(())
85}
86
87/// Resolves a Git repository from a `repo_path` parameter
88///
89/// If `repo_path` is provided, creates a new `GitRepo` for that path/URL.
90/// Assumes the parameter has already been validated.
91pub fn resolve_git_repo(
92    repo_path: Option<&str>,
93    _default_git_repo: Arc<GitRepo>,
94) -> Result<Arc<GitRepo>, anyhow::Error> {
95    match repo_path {
96        Some(path) if !path.trim().is_empty() => {
97            if path.starts_with("http://")
98                || path.starts_with("https://")
99                || path.starts_with("git@")
100            {
101                // Handle remote repository URL
102                Ok(Arc::new(GitRepo::new_from_url(Some(path.to_string()))?))
103            } else {
104                // Handle local repository path
105                let path = std::path::Path::new(path);
106                Ok(Arc::new(GitRepo::new(path)?))
107            }
108        }
109        _ => Err(anyhow::anyhow!(
110            "The `repository` parameter is required and must be a valid local path or remote URL."
111        )),
112    }
113}