Skip to main content
Glama
ConnorBoetig-dev

Unrestricted Development MCP Server

git.ts31.5 kB
import { z } from 'zod'; import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); /** * Git Tools * Provides comprehensive git operations for autonomous development * Enables full version control workflow without human intervention */ // ========== HELPER TYPE ========== type ToolResponse = { content: Array<{ type: "text"; text: string }>; isError?: boolean; }; // Helper function to execute git commands async function executeGitCommand(command: string, cwd?: string): Promise<ToolResponse> { try { const { stdout, stderr } = await execAsync(command, { cwd: cwd || process.cwd(), shell: '/bin/bash', maxBuffer: 10 * 1024 * 1024 // 10MB buffer }); return { content: [ { type: "text" as const, text: JSON.stringify({ success: true, command: command, stdout: stdout.trim(), stderr: stderr.trim(), cwd: cwd || process.cwd() }, null, 2) } ] }; } catch (error: any) { return { content: [ { type: "text" as const, text: JSON.stringify({ success: false, command: command, stdout: error.stdout?.trim() || '', stderr: error.stderr?.trim() || error.message, exitCode: error.code || 1, cwd: cwd || process.cwd() }, null, 2) } ], isError: true }; } } // ========== TOOL SCHEMAS ========== export const gitStatusSchema = z.object({ cwd: z.string().optional().describe('Repository directory (defaults to current directory)'), short: z.boolean().optional().default(false).describe('Show short format status') }); export const gitAddSchema = z.object({ files: z.union([z.string(), z.array(z.string())]).describe('File(s) to stage. Use "." for all files'), cwd: z.string().optional().describe('Repository directory') }); export const gitCommitSchema = z.object({ message: z.string().describe('Commit message'), cwd: z.string().optional().describe('Repository directory'), amend: z.boolean().optional().default(false).describe('Amend previous commit'), noVerify: z.boolean().optional().default(false).describe('Skip pre-commit hooks') }); export const gitDiffSchema = z.object({ cwd: z.string().optional().describe('Repository directory'), cached: z.boolean().optional().default(false).describe('Show staged changes'), files: z.string().optional().describe('Specific file(s) to diff'), commit: z.string().optional().describe('Compare against specific commit/branch') }); export const gitLogSchema = z.object({ cwd: z.string().optional().describe('Repository directory'), limit: z.number().optional().default(10).describe('Number of commits to show'), oneline: z.boolean().optional().default(false).describe('Show one line per commit'), graph: z.boolean().optional().default(false).describe('Show commit graph'), all: z.boolean().optional().default(false).describe('Show all branches') }); export const gitBranchSchema = z.object({ cwd: z.string().optional().describe('Repository directory'), action: z.enum(['list', 'create', 'delete', 'rename']).optional().default('list').describe('Branch action'), name: z.string().optional().describe('Branch name (required for create/delete/rename)'), newName: z.string().optional().describe('New branch name (required for rename)'), force: z.boolean().optional().default(false).describe('Force delete or creation'), remote: z.boolean().optional().default(false).describe('List remote branches') }); export const gitCheckoutSchema = z.object({ target: z.string().describe('Branch name, commit hash, or file path to checkout'), cwd: z.string().optional().describe('Repository directory'), createBranch: z.boolean().optional().default(false).describe('Create new branch'), force: z.boolean().optional().default(false).describe('Force checkout, discarding local changes') }); export const gitPullSchema = z.object({ cwd: z.string().optional().describe('Repository directory'), remote: z.string().optional().default('origin').describe('Remote name'), branch: z.string().optional().describe('Branch to pull (defaults to current branch)'), rebase: z.boolean().optional().default(false).describe('Rebase instead of merge') }); export const gitPushSchema = z.object({ cwd: z.string().optional().describe('Repository directory'), remote: z.string().optional().default('origin').describe('Remote name'), branch: z.string().optional().describe('Branch to push (defaults to current branch)'), force: z.boolean().optional().default(false).describe('Force push (use with caution)'), setUpstream: z.boolean().optional().default(false).describe('Set upstream tracking') }); export const gitCloneSchema = z.object({ url: z.string().describe('Repository URL to clone'), destination: z.string().optional().describe('Destination directory (defaults to repo name)'), cwd: z.string().optional().describe('Directory to clone into'), branch: z.string().optional().describe('Specific branch to clone'), depth: z.number().optional().describe('Create shallow clone with depth') }); export const gitInitSchema = z.object({ cwd: z.string().optional().describe('Directory to initialize as git repository'), bare: z.boolean().optional().default(false).describe('Create bare repository') }); export const gitMergeSchema = z.object({ branch: z.string().describe('Branch to merge into current branch'), cwd: z.string().optional().describe('Repository directory'), noFf: z.boolean().optional().default(false).describe('Create merge commit even if fast-forward is possible'), abort: z.boolean().optional().default(false).describe('Abort current merge') }); export const gitRebaseSchema = z.object({ branch: z.string().optional().describe('Branch to rebase onto'), cwd: z.string().optional().describe('Repository directory'), interactive: z.boolean().optional().default(false).describe('Interactive rebase'), abort: z.boolean().optional().default(false).describe('Abort current rebase'), continue: z.boolean().optional().default(false).describe('Continue rebase after resolving conflicts') }); export const gitFetchSchema = z.object({ cwd: z.string().optional().describe('Repository directory'), remote: z.string().optional().default('origin').describe('Remote name'), prune: z.boolean().optional().default(false).describe('Remove deleted remote branches') }); export const gitRemoteSchema = z.object({ cwd: z.string().optional().describe('Repository directory'), action: z.enum(['list', 'add', 'remove', 'show']).optional().default('list').describe('Remote action'), name: z.string().optional().describe('Remote name (required for add/remove/show)'), url: z.string().optional().describe('Remote URL (required for add)') }); export const gitStashSchema = z.object({ cwd: z.string().optional().describe('Repository directory'), action: z.enum(['push', 'pop', 'list', 'apply', 'drop', 'clear']).optional().default('push').describe('Stash action'), message: z.string().optional().describe('Stash message (for push)'), index: z.number().optional().describe('Stash index (for pop/apply/drop)') }); export const gitResetSchema = z.object({ cwd: z.string().optional().describe('Repository directory'), mode: z.enum(['soft', 'mixed', 'hard']).optional().default('mixed').describe('Reset mode'), commit: z.string().optional().default('HEAD').describe('Commit to reset to'), files: z.string().optional().describe('Specific file(s) to reset') }); export const gitRevertSchema = z.object({ commit: z.string().describe('Commit hash to revert'), cwd: z.string().optional().describe('Repository directory'), noCommit: z.boolean().optional().default(false).describe('Apply changes without committing') }); export const gitTagSchema = z.object({ cwd: z.string().optional().describe('Repository directory'), action: z.enum(['list', 'create', 'delete']).optional().default('list').describe('Tag action'), name: z.string().optional().describe('Tag name (required for create/delete)'), message: z.string().optional().describe('Tag message (for annotated tags)'), commit: z.string().optional().describe('Commit to tag (defaults to HEAD)') }); export const gitShowSchema = z.object({ commit: z.string().optional().default('HEAD').describe('Commit hash or reference to show'), cwd: z.string().optional().describe('Repository directory') }); export const gitConfigSchema = z.object({ cwd: z.string().optional().describe('Repository directory'), action: z.enum(['get', 'set', 'list']).optional().default('list').describe('Config action'), key: z.string().optional().describe('Config key (required for get/set)'), value: z.string().optional().describe('Config value (required for set)'), global: z.boolean().optional().default(false).describe('Use global config instead of repository config') }); // ========== TOOL IMPLEMENTATIONS ========== export async function gitStatus(args: z.infer<typeof gitStatusSchema>): Promise<ToolResponse> { const shortFlag = args.short ? '--short' : ''; return executeGitCommand(`git status ${shortFlag}`, args.cwd); } export async function gitAdd(args: z.infer<typeof gitAddSchema>): Promise<ToolResponse> { const files = Array.isArray(args.files) ? args.files.join(' ') : args.files; return executeGitCommand(`git add ${files}`, args.cwd); } export async function gitCommit(args: z.infer<typeof gitCommitSchema>): Promise<ToolResponse> { const amendFlag = args.amend ? '--amend' : ''; const noVerifyFlag = args.noVerify ? '--no-verify' : ''; // Escape message for shell const escapedMessage = args.message.replace(/'/g, "'\\''"); return executeGitCommand(`git commit ${amendFlag} ${noVerifyFlag} -m '${escapedMessage}'`, args.cwd); } export async function gitDiff(args: z.infer<typeof gitDiffSchema>): Promise<ToolResponse> { const cachedFlag = args.cached ? '--cached' : ''; const files = args.files || ''; const commit = args.commit || ''; return executeGitCommand(`git diff ${cachedFlag} ${commit} ${files}`.trim(), args.cwd); } export async function gitLog(args: z.infer<typeof gitLogSchema>): Promise<ToolResponse> { const onelineFlag = args.oneline ? '--oneline' : '--pretty=format:"%H | %an | %ar | %s"'; const graphFlag = args.graph ? '--graph' : ''; const allFlag = args.all ? '--all' : ''; const limit = `-${args.limit}`; return executeGitCommand(`git log ${onelineFlag} ${graphFlag} ${allFlag} ${limit}`.trim(), args.cwd); } export async function gitBranch(args: z.infer<typeof gitBranchSchema>): Promise<ToolResponse> { switch (args.action) { case 'list': const remoteFlag = args.remote ? '-r' : '-a'; return executeGitCommand(`git branch ${remoteFlag}`, args.cwd); case 'create': if (!args.name) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Branch name required for create action' }, null, 2) }], isError: true }; } return executeGitCommand(`git branch ${args.name}`, args.cwd); case 'delete': if (!args.name) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Branch name required for delete action' }, null, 2) }], isError: true }; } const deleteFlag = args.force ? '-D' : '-d'; return executeGitCommand(`git branch ${deleteFlag} ${args.name}`, args.cwd); case 'rename': if (!args.name || !args.newName) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Both name and newName required for rename action' }, null, 2) }], isError: true }; } return executeGitCommand(`git branch -m ${args.name} ${args.newName}`, args.cwd); default: return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Invalid branch action' }, null, 2) }], isError: true }; } } export async function gitCheckout(args: z.infer<typeof gitCheckoutSchema>): Promise<ToolResponse> { const createFlag = args.createBranch ? '-b' : ''; const forceFlag = args.force ? '-f' : ''; return executeGitCommand(`git checkout ${createFlag} ${forceFlag} ${args.target}`.trim(), args.cwd); } export async function gitPull(args: z.infer<typeof gitPullSchema>): Promise<ToolResponse> { const rebaseFlag = args.rebase ? '--rebase' : ''; const branch = args.branch || ''; return executeGitCommand(`git pull ${rebaseFlag} ${args.remote} ${branch}`.trim(), args.cwd); } export async function gitPush(args: z.infer<typeof gitPushSchema>): Promise<ToolResponse> { const forceFlag = args.force ? '--force' : ''; const upstreamFlag = args.setUpstream ? '-u' : ''; const branch = args.branch || ''; return executeGitCommand(`git push ${upstreamFlag} ${forceFlag} ${args.remote} ${branch}`.trim(), args.cwd); } export async function gitClone(args: z.infer<typeof gitCloneSchema>): Promise<ToolResponse> { const branchFlag = args.branch ? `-b ${args.branch}` : ''; const depthFlag = args.depth ? `--depth ${args.depth}` : ''; const destination = args.destination || ''; return executeGitCommand(`git clone ${branchFlag} ${depthFlag} ${args.url} ${destination}`.trim(), args.cwd); } export async function gitInit(args: z.infer<typeof gitInitSchema>): Promise<ToolResponse> { const bareFlag = args.bare ? '--bare' : ''; return executeGitCommand(`git init ${bareFlag}`.trim(), args.cwd); } export async function gitMerge(args: z.infer<typeof gitMergeSchema>): Promise<ToolResponse> { if (args.abort) { return executeGitCommand('git merge --abort', args.cwd); } const noFfFlag = args.noFf ? '--no-ff' : ''; return executeGitCommand(`git merge ${noFfFlag} ${args.branch}`.trim(), args.cwd); } export async function gitRebase(args: z.infer<typeof gitRebaseSchema>): Promise<ToolResponse> { if (args.abort) { return executeGitCommand('git rebase --abort', args.cwd); } if (args.continue) { return executeGitCommand('git rebase --continue', args.cwd); } if (args.interactive) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Interactive rebase not supported in non-interactive environment' }, null, 2) }], isError: true }; } if (!args.branch) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Branch required for rebase' }, null, 2) }], isError: true }; } return executeGitCommand(`git rebase ${args.branch}`, args.cwd); } export async function gitFetch(args: z.infer<typeof gitFetchSchema>): Promise<ToolResponse> { const pruneFlag = args.prune ? '--prune' : ''; return executeGitCommand(`git fetch ${pruneFlag} ${args.remote}`.trim(), args.cwd); } export async function gitRemote(args: z.infer<typeof gitRemoteSchema>): Promise<ToolResponse> { switch (args.action) { case 'list': return executeGitCommand('git remote -v', args.cwd); case 'add': if (!args.name || !args.url) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Name and URL required for add action' }, null, 2) }], isError: true }; } return executeGitCommand(`git remote add ${args.name} ${args.url}`, args.cwd); case 'remove': if (!args.name) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Name required for remove action' }, null, 2) }], isError: true }; } return executeGitCommand(`git remote remove ${args.name}`, args.cwd); case 'show': if (!args.name) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Name required for show action' }, null, 2) }], isError: true }; } return executeGitCommand(`git remote show ${args.name}`, args.cwd); default: return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Invalid remote action' }, null, 2) }], isError: true }; } } export async function gitStash(args: z.infer<typeof gitStashSchema>): Promise<ToolResponse> { switch (args.action) { case 'push': const message = args.message ? `-m "${args.message}"` : ''; return executeGitCommand(`git stash push ${message}`.trim(), args.cwd); case 'pop': const popIndex = args.index !== undefined ? `stash@{${args.index}}` : ''; return executeGitCommand(`git stash pop ${popIndex}`.trim(), args.cwd); case 'list': return executeGitCommand('git stash list', args.cwd); case 'apply': const applyIndex = args.index !== undefined ? `stash@{${args.index}}` : ''; return executeGitCommand(`git stash apply ${applyIndex}`.trim(), args.cwd); case 'drop': const dropIndex = args.index !== undefined ? `stash@{${args.index}}` : ''; return executeGitCommand(`git stash drop ${dropIndex}`.trim(), args.cwd); case 'clear': return executeGitCommand('git stash clear', args.cwd); default: return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Invalid stash action' }, null, 2) }], isError: true }; } } export async function gitReset(args: z.infer<typeof gitResetSchema>): Promise<ToolResponse> { if (args.files) { return executeGitCommand(`git reset ${args.commit} -- ${args.files}`, args.cwd); } const modeFlag = `--${args.mode}`; return executeGitCommand(`git reset ${modeFlag} ${args.commit}`, args.cwd); } export async function gitRevert(args: z.infer<typeof gitRevertSchema>): Promise<ToolResponse> { const noCommitFlag = args.noCommit ? '--no-commit' : ''; return executeGitCommand(`git revert ${noCommitFlag} ${args.commit}`.trim(), args.cwd); } export async function gitTag(args: z.infer<typeof gitTagSchema>): Promise<ToolResponse> { switch (args.action) { case 'list': return executeGitCommand('git tag -l', args.cwd); case 'create': if (!args.name) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Tag name required for create action' }, null, 2) }], isError: true }; } const messageFlag = args.message ? `-a -m "${args.message}"` : ''; const commit = args.commit || ''; return executeGitCommand(`git tag ${messageFlag} ${args.name} ${commit}`.trim(), args.cwd); case 'delete': if (!args.name) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Tag name required for delete action' }, null, 2) }], isError: true }; } return executeGitCommand(`git tag -d ${args.name}`, args.cwd); default: return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Invalid tag action' }, null, 2) }], isError: true }; } } export async function gitShow(args: z.infer<typeof gitShowSchema>): Promise<ToolResponse> { return executeGitCommand(`git show ${args.commit}`, args.cwd); } export async function gitConfig(args: z.infer<typeof gitConfigSchema>): Promise<ToolResponse> { const globalFlag = args.global ? '--global' : ''; switch (args.action) { case 'list': return executeGitCommand(`git config ${globalFlag} --list`.trim(), args.cwd); case 'get': if (!args.key) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Key required for get action' }, null, 2) }], isError: true }; } return executeGitCommand(`git config ${globalFlag} ${args.key}`.trim(), args.cwd); case 'set': if (!args.key || !args.value) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Key and value required for set action' }, null, 2) }], isError: true }; } return executeGitCommand(`git config ${globalFlag} ${args.key} "${args.value}"`.trim(), args.cwd); default: return { content: [{ type: "text", text: JSON.stringify({ success: false, error: 'Invalid config action' }, null, 2) }], isError: true }; } } // ========== TOOL DEFINITIONS FOR MCP ========== export const gitTools = [ { name: 'git_status', description: 'Get repository status showing staged, unstaged, and untracked files', inputSchema: { type: 'object', properties: { cwd: { type: 'string', description: 'Repository directory (defaults to current directory)' }, short: { type: 'boolean', default: false, description: 'Show short format status' } } } }, { name: 'git_add', description: 'Stage files for commit. Use "." to stage all changes', inputSchema: { type: 'object', properties: { files: { oneOf: [ { type: 'string' }, { type: 'array', items: { type: 'string' } } ], description: 'File(s) to stage. Use "." for all files' }, cwd: { type: 'string', description: 'Repository directory' } }, required: ['files'] } }, { name: 'git_commit', description: 'Create a commit with staged changes', inputSchema: { type: 'object', properties: { message: { type: 'string', description: 'Commit message' }, cwd: { type: 'string', description: 'Repository directory' }, amend: { type: 'boolean', default: false, description: 'Amend previous commit' }, noVerify: { type: 'boolean', default: false, description: 'Skip pre-commit hooks' } }, required: ['message'] } }, { name: 'git_diff', description: 'Show changes between commits, working tree, and staging area', inputSchema: { type: 'object', properties: { cwd: { type: 'string', description: 'Repository directory' }, cached: { type: 'boolean', default: false, description: 'Show staged changes' }, files: { type: 'string', description: 'Specific file(s) to diff' }, commit: { type: 'string', description: 'Compare against specific commit/branch' } } } }, { name: 'git_log', description: 'Show commit history with details', inputSchema: { type: 'object', properties: { cwd: { type: 'string', description: 'Repository directory' }, limit: { type: 'number', default: 10, description: 'Number of commits to show' }, oneline: { type: 'boolean', default: false, description: 'Show one line per commit' }, graph: { type: 'boolean', default: false, description: 'Show commit graph' }, all: { type: 'boolean', default: false, description: 'Show all branches' } } } }, { name: 'git_branch', description: 'List, create, delete, or rename branches', inputSchema: { type: 'object', properties: { cwd: { type: 'string', description: 'Repository directory' }, action: { type: 'string', enum: ['list', 'create', 'delete', 'rename'], default: 'list', description: 'Branch action' }, name: { type: 'string', description: 'Branch name (required for create/delete/rename)' }, newName: { type: 'string', description: 'New branch name (required for rename)' }, force: { type: 'boolean', default: false, description: 'Force delete or creation' }, remote: { type: 'boolean', default: false, description: 'List remote branches' } } } }, { name: 'git_checkout', description: 'Switch branches or restore files', inputSchema: { type: 'object', properties: { target: { type: 'string', description: 'Branch name, commit hash, or file path to checkout' }, cwd: { type: 'string', description: 'Repository directory' }, createBranch: { type: 'boolean', default: false, description: 'Create new branch' }, force: { type: 'boolean', default: false, description: 'Force checkout, discarding local changes' } }, required: ['target'] } }, { name: 'git_pull', description: 'Fetch and integrate changes from remote repository', inputSchema: { type: 'object', properties: { cwd: { type: 'string', description: 'Repository directory' }, remote: { type: 'string', default: 'origin', description: 'Remote name' }, branch: { type: 'string', description: 'Branch to pull (defaults to current branch)' }, rebase: { type: 'boolean', default: false, description: 'Rebase instead of merge' } } } }, { name: 'git_push', description: 'Push commits to remote repository', inputSchema: { type: 'object', properties: { cwd: { type: 'string', description: 'Repository directory' }, remote: { type: 'string', default: 'origin', description: 'Remote name' }, branch: { type: 'string', description: 'Branch to push (defaults to current branch)' }, force: { type: 'boolean', default: false, description: 'Force push (use with caution)' }, setUpstream: { type: 'boolean', default: false, description: 'Set upstream tracking' } } } }, { name: 'git_clone', description: 'Clone a repository into a new directory', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'Repository URL to clone' }, destination: { type: 'string', description: 'Destination directory (defaults to repo name)' }, cwd: { type: 'string', description: 'Directory to clone into' }, branch: { type: 'string', description: 'Specific branch to clone' }, depth: { type: 'number', description: 'Create shallow clone with depth' } }, required: ['url'] } }, { name: 'git_init', description: 'Initialize a new git repository', inputSchema: { type: 'object', properties: { cwd: { type: 'string', description: 'Directory to initialize as git repository' }, bare: { type: 'boolean', default: false, description: 'Create bare repository' } } } }, { name: 'git_merge', description: 'Merge a branch into current branch', inputSchema: { type: 'object', properties: { branch: { type: 'string', description: 'Branch to merge into current branch' }, cwd: { type: 'string', description: 'Repository directory' }, noFf: { type: 'boolean', default: false, description: 'Create merge commit even if fast-forward is possible' }, abort: { type: 'boolean', default: false, description: 'Abort current merge' } }, required: ['branch'] } }, { name: 'git_rebase', description: 'Reapply commits on top of another branch', inputSchema: { type: 'object', properties: { branch: { type: 'string', description: 'Branch to rebase onto' }, cwd: { type: 'string', description: 'Repository directory' }, interactive: { type: 'boolean', default: false, description: 'Interactive rebase (not supported)' }, abort: { type: 'boolean', default: false, description: 'Abort current rebase' }, continue: { type: 'boolean', default: false, description: 'Continue rebase after resolving conflicts' } } } }, { name: 'git_fetch', description: 'Download objects and refs from remote repository', inputSchema: { type: 'object', properties: { cwd: { type: 'string', description: 'Repository directory' }, remote: { type: 'string', default: 'origin', description: 'Remote name' }, prune: { type: 'boolean', default: false, description: 'Remove deleted remote branches' } } } }, { name: 'git_remote', description: 'Manage remote repositories', inputSchema: { type: 'object', properties: { cwd: { type: 'string', description: 'Repository directory' }, action: { type: 'string', enum: ['list', 'add', 'remove', 'show'], default: 'list', description: 'Remote action' }, name: { type: 'string', description: 'Remote name (required for add/remove/show)' }, url: { type: 'string', description: 'Remote URL (required for add)' } } } }, { name: 'git_stash', description: 'Stash changes in working directory', inputSchema: { type: 'object', properties: { cwd: { type: 'string', description: 'Repository directory' }, action: { type: 'string', enum: ['push', 'pop', 'list', 'apply', 'drop', 'clear'], default: 'push', description: 'Stash action' }, message: { type: 'string', description: 'Stash message (for push)' }, index: { type: 'number', description: 'Stash index (for pop/apply/drop)' } } } }, { name: 'git_reset', description: 'Reset current HEAD to specified state', inputSchema: { type: 'object', properties: { cwd: { type: 'string', description: 'Repository directory' }, mode: { type: 'string', enum: ['soft', 'mixed', 'hard'], default: 'mixed', description: 'Reset mode' }, commit: { type: 'string', default: 'HEAD', description: 'Commit to reset to' }, files: { type: 'string', description: 'Specific file(s) to reset' } } } }, { name: 'git_revert', description: 'Revert a commit by creating a new commit', inputSchema: { type: 'object', properties: { commit: { type: 'string', description: 'Commit hash to revert' }, cwd: { type: 'string', description: 'Repository directory' }, noCommit: { type: 'boolean', default: false, description: 'Apply changes without committing' } }, required: ['commit'] } }, { name: 'git_tag', description: 'Create, list, or delete tags', inputSchema: { type: 'object', properties: { cwd: { type: 'string', description: 'Repository directory' }, action: { type: 'string', enum: ['list', 'create', 'delete'], default: 'list', description: 'Tag action' }, name: { type: 'string', description: 'Tag name (required for create/delete)' }, message: { type: 'string', description: 'Tag message (for annotated tags)' }, commit: { type: 'string', description: 'Commit to tag (defaults to HEAD)' } } } }, { name: 'git_show', description: 'Show details of a commit', inputSchema: { type: 'object', properties: { commit: { type: 'string', default: 'HEAD', description: 'Commit hash or reference to show' }, cwd: { type: 'string', description: 'Repository directory' } } } }, { name: 'git_config', description: 'Get or set git configuration', inputSchema: { type: 'object', properties: { cwd: { type: 'string', description: 'Repository directory' }, action: { type: 'string', enum: ['get', 'set', 'list'], default: 'list', description: 'Config action' }, key: { type: 'string', description: 'Config key (required for get/set)' }, value: { type: 'string', description: 'Config value (required for set)' }, global: { type: 'boolean', default: false, description: 'Use global config instead of repository config' } } } } ];

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ConnorBoetig-dev/mcp2'

If you have feedback or need assistance with the MCP directory API, please join our Discord server