UNPKG

@cyanheads/git-mcp-server

Version:

An MCP (Model Context Protocol) server enabling LLMs and AI agents to interact with Git repositories. Provides tools for comprehensive Git operations including clone, commit, branch, diff, log, status, push, pull, merge, rebase, worktree, tag management,

97 lines 3.95 kB
/** * @fileoverview Defines the core logic, schemas, and types for the git_push tool. * @module src/mcp-server/tools/gitPush/logic */ import { execFile } from "child_process"; import { promisify } from "util"; import { z } from "zod"; import { logger, sanitization, } from "../../../utils/index.js"; import { McpError, BaseErrorCode } from "../../../types-global/errors.js"; const execFileAsync = promisify(execFile); // 1. DEFINE the Zod input schema. export const GitPushBaseSchema = z.object({ path: z.string().default(".").describe("Path to the Git repository."), remote: z .string() .optional() .describe("The remote repository to push to (e.g., 'origin')."), branch: z.string().optional().describe("The local branch to push."), remoteBranch: z.string().optional().describe("The remote branch to push to."), force: z .boolean() .default(false) .describe("Force the push (use with caution)."), forceWithLease: z .boolean() .default(false) .describe("Force the push only if the remote ref is as expected."), setUpstream: z .boolean() .default(false) .describe("Set the upstream tracking configuration."), tags: z.boolean().default(false).describe("Push all tags."), delete: z.boolean().default(false).describe("Delete the remote branch."), }); export const GitPushInputSchema = GitPushBaseSchema.refine((data) => !(data.delete && !data.branch), { message: "Cannot use --delete without specifying a branch to delete.", path: ["delete", "branch"], }).refine((data) => !(data.force && data.forceWithLease), { message: "Cannot use --force and --force-with-lease together.", path: ["force", "forceWithLease"], }); // 2. DEFINE the Zod response schema. export const GitPushOutputSchema = z.object({ success: z.boolean().describe("Indicates if the command was successful."), message: z.string().describe("A summary message of the result."), rejected: z.boolean().optional().describe("True if the push was rejected."), deleted: z .boolean() .optional() .describe("True if a remote branch was deleted."), }); /** * 4. IMPLEMENT the core logic function. * @throws {McpError} If the logic encounters an unrecoverable issue. */ export async function pushGitChanges(params, context) { const operation = "pushGitChanges"; logger.debug(`Executing ${operation}`, { ...context, params }); const workingDir = context.getWorkingDirectory(); if (params.path === "." && !workingDir) { throw new McpError(BaseErrorCode.VALIDATION_ERROR, "No session working directory set. Please specify a 'path' or use 'git_set_working_dir' first."); } const targetPath = sanitization.sanitizePath(params.path === "." ? workingDir : params.path, { allowAbsolute: true }).sanitizedPath; const args = ["-C", targetPath, "push"]; if (params.force) args.push("--force"); else if (params.forceWithLease) args.push("--force-with-lease"); if (params.setUpstream) args.push("--set-upstream"); if (params.tags) args.push("--tags"); if (params.delete) args.push("--delete"); args.push(params.remote || "origin"); if (params.branch) { if (params.remoteBranch && !params.delete) { args.push(`${params.branch}:${params.remoteBranch}`); } else { args.push(params.branch); } } logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation, }); const { stdout, stderr } = await execFileAsync("git", args); const message = stderr.trim() || stdout.trim() || "Push command executed successfully."; return { success: true, message, rejected: message.includes("[rejected]"), deleted: message.includes("[deleted]"), }; } //# sourceMappingURL=logic.js.map