use anyhow;
use kodegen_mcp_schema::github::{SearchUsersArgs, SearchUsersPrompts, GITHUB_SEARCH_USERS};
use kodegen_mcp_schema::{McpError, Tool, ToolExecutionContext, ToolResponse};
use crate::GitHubClient;
pub struct SearchUsersTool;
impl Tool for SearchUsersTool {
type Args = SearchUsersArgs;
type Prompts = SearchUsersPrompts;
fn name() -> &'static str {
GITHUB_SEARCH_USERS
}
fn description() -> &'static str {
"Search GitHub users using GitHub's user 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 client = GitHubClient::builder()
.personal_token(token)
.build()
.map_err(|e| McpError::Other(anyhow::anyhow!("Failed to create GitHub client: {e}")))?;
let sort_enum = if let Some(s) = args.sort.as_ref() {
match s.as_str() {
"followers" => Some(crate::github::UserSearchSort::Followers),
"repositories" => Some(crate::github::UserSearchSort::Repositories),
"joined" => Some(crate::github::UserSearchSort::Joined),
_ => {
return Err(McpError::InvalidArguments(
"sort must be followers, repositories, or joined".into(),
));
}
}
} else {
None
};
let order_enum = if let Some(o) = args.order.as_ref() {
match o.as_str() {
"asc" => Some(crate::github::SearchOrder::Asc),
"desc" => Some(crate::github::SearchOrder::Desc),
_ => {
return Err(McpError::InvalidArguments(
"order must be asc or desc".into(),
));
}
}
} else {
None
};
let task_result = client
.search_users(args.query.clone(), sort_enum, order_enum, args.page, args.per_page)
.await;
let api_result =
task_result.map_err(|e| McpError::Other(anyhow::anyhow!("Task channel error: {e}")))?;
let page =
api_result.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::GitHubUserSearchResult> = page.items
.iter()
.map(|user| kodegen_mcp_schema::github::GitHubUserSearchResult {
login: user.login.clone(),
id: user.id.0,
avatar_url: user.avatar_url.to_string(),
html_url: user.html_url.to_string(),
user_type: user.r#type.clone(),
name: None, bio: None, location: None, followers: None, })
.collect();
let results_text = if items.is_empty() {
" No users found".to_string()
} else {
items.iter()
.map(|u| {
let name = u.name.as_deref().unwrap_or(&u.login);
let location = u.location.as_deref().unwrap_or("Unknown location");
let followers = u.followers.map(|f| format!("{} followers", f)).unwrap_or_else(|| "Unknown followers".to_string());
format!(" • {} (@{}) - {} - {}", name, u.login, location, followers)
})
.collect::<Vec<_>>()
.join("\n")
};
let display = format!(
"👥 GitHub User 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::GitHubSearchUsersOutput {
success: true,
query: args.query,
total_count: total_count as u32,
items,
};
Ok(ToolResponse::new(display, output))
}
}