use anyhow;
use kodegen_mcp_schema::github::{
PushFilesArgs,
PushFilesPrompts,
GitHubPushFilesOutput,
GITHUB_PUSH_FILES
};
use kodegen_mcp_schema::{Tool, ToolExecutionContext, ToolResponse, McpError};
#[derive(Clone)]
pub struct PushFilesTool;
impl Tool for PushFilesTool {
type Args = PushFilesArgs;
type Prompts = PushFilesPrompts;
fn name() -> &'static str {
GITHUB_PUSH_FILES
}
fn description() -> &'static str {
"Push multiple files to a GitHub repository in a single commit. All files \
are added atomically (creates tree, commit, and updates ref). File content \
must be base64-encoded. Requires GITHUB_TOKEN environment variable."
}
fn read_only() -> bool {
false }
fn destructive() -> bool {
false }
fn idempotent() -> bool {
false }
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 = crate::GitHubClient::builder()
.personal_token(token)
.build()
.map_err(|e| McpError::Other(anyhow::anyhow!("Failed to create GitHub client: {}", e)))?;
let file_count = args.files.len();
let file_paths: Vec<String> = args.files.keys().cloned().collect();
let task_result = client.push_files(
args.owner.clone(),
args.repo.clone(),
args.branch.clone(),
args.files,
args.message.clone(),
).await;
let api_result = task_result
.map_err(|e| McpError::Other(anyhow::anyhow!("Task channel error: {}", e)))?;
let commit = api_result
.map_err(|e| McpError::Other(anyhow::anyhow!("GitHub API error: {}", e)))?;
let file_list_preview = file_paths
.iter()
.take(5)
.map(|p| format!(" 📄 {}", p))
.collect::<Vec<_>>()
.join("\n");
let more_indicator = if file_paths.len() > 5 {
format!("\n ... and {} more files", file_paths.len() - 5)
} else {
String::new()
};
let commit_sha = commit.sha.as_deref().unwrap_or("N/A");
let summary = format!(
"📦 Pushed {} file(s) to {}\n\n\
Repository: {}/{}\n\
Branch: {}\n\
Commit: \"{}\"\n\
Commit SHA: {}\n\n\
Files:\n{}{}",
file_count,
args.branch,
args.owner,
args.repo,
args.branch,
args.message,
commit_sha,
file_list_preview,
more_indicator
);
let output = GitHubPushFilesOutput {
success: true,
owner: args.owner,
repo: args.repo,
branch: args.branch,
message: args.message,
file_count,
file_paths,
commit_sha: commit.sha.unwrap_or_default(),
commit_url: commit.html_url.unwrap_or_default(),
};
Ok(ToolResponse::new(summary, output))
}
}