use anyhow;
use kodegen_mcp_schema::{McpError, Tool, ToolExecutionContext, ToolResponse};
use kodegen_mcp_schema::github::{SearchRepositoriesArgs, SearchRepositoriesPrompts, GITHUB_SEARCH_REPOSITORIES};
use octocrab::Octocrab;
pub struct SearchRepositoriesTool;
impl Tool for SearchRepositoriesTool {
type Args = SearchRepositoriesArgs;
type Prompts = SearchRepositoriesPrompts;
fn name() -> &'static str {
GITHUB_SEARCH_REPOSITORIES
}
fn description() -> &'static str {
"Search GitHub repositories using GitHub's repository search syntax"
}
fn read_only() -> bool {
true
}
fn destructive() -> bool {
false
}
fn idempotent() -> bool {
true
}
fn open_world() -> bool {
true
}
async fn execute(&self, args: Self::Args, _ctx: ToolExecutionContext) -> Result<ToolResponse<<Self::Args as kodegen_mcp_schema::ToolArgs>::Output>, McpError> {
let token = std::env::var("GITHUB_TOKEN").map_err(|_| {
McpError::Other(anyhow::anyhow!("GITHUB_TOKEN environment variable not set"))
})?;
let octocrab = Octocrab::builder()
.personal_token(token)
.build()
.map_err(|e| McpError::Other(anyhow::anyhow!("Failed to create GitHub client: {e}")))?;
let mut request = octocrab.search().repositories(&args.query);
if let Some(sort_val) = &args.sort {
request = request.sort(sort_val);
}
if let Some(order_val) = &args.order {
request = request.order(order_val);
}
if let Some(p) = args.page {
request = request.page(p);
}
if let Some(pp) = args.per_page {
request = request.per_page(pp);
}
let page = request
.send()
.await
.map_err(|e| McpError::Other(anyhow::anyhow!("GitHub API error: {e}")))?;
let total_count = page.total_count.unwrap_or(0);
let items: Vec<kodegen_mcp_schema::github::GitHubRepoSearchResult> = page.items
.iter()
.map(|repo| kodegen_mcp_schema::github::GitHubRepoSearchResult {
full_name: repo.full_name.clone().unwrap_or_default(),
name: repo.name.clone(),
owner: repo.owner.as_ref().map(|o| o.login.clone()).unwrap_or_default(),
description: repo.description.clone(),
html_url: repo.html_url.as_ref().map(|u| u.to_string()).unwrap_or_default(),
language: repo.language.as_ref().and_then(|v| v.as_str()).map(|s| s.to_string()),
stars: repo.stargazers_count.unwrap_or(0),
forks: repo.forks_count.unwrap_or(0),
watchers: repo.watchers_count.unwrap_or(0),
open_issues: repo.open_issues_count.unwrap_or(0),
created_at: repo.created_at.map(|dt| dt.to_rfc3339()).unwrap_or_default(),
updated_at: repo.updated_at.map(|dt| dt.to_rfc3339()).unwrap_or_default(),
pushed_at: repo.pushed_at.map(|dt| dt.to_rfc3339()),
topics: repo.topics.clone().unwrap_or_default(),
archived: repo.archived.unwrap_or(false),
fork: repo.fork.unwrap_or(false),
})
.collect();
let results_text = if items.is_empty() {
" No repositories found".to_string()
} else {
items.iter()
.map(|r| {
let desc = r.description.as_deref().unwrap_or("No description");
let lang = r.language.as_deref().unwrap_or("Unknown");
format!(" • {} - ⭐ {} - {} - {}", r.full_name, r.stars, lang, desc)
})
.collect::<Vec<_>>()
.join("\n")
};
let display = format!(
"🔍 GitHub Repository Search\n\n\
Query: {}\n\
Total Results: {}\n\
Results Returned: {}\n\n\
{}",
args.query, total_count, items.len(), results_text
);
let output = kodegen_mcp_schema::github::GitHubSearchReposOutput {
success: true,
query: args.query,
total_count: total_count as u32,
items,
};
Ok(ToolResponse::new(display, output))
}
}