Skip to main content
Glama

Git Repo Browser MCP

server.js32 kB
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { handleGitDirectoryStructure, handleGitReadFiles, handleGitBranchDiff, handleGitCommitHistory, handleGitCommitsDetails, handleGitLocalChanges, handleGitSearchCode, handleGitCommit, handleGitTrack, handleGitCheckoutBranch, handleGitDeleteBranch, handleGitMergeBranch, handleGitPush, handleGitPull, handleGitStash, handleGitCreateTag, handleGitRebase, handleGitConfig, handleGitReset, handleGitArchive, handleGitAttributes, handleGitBlame, handleGitClean, handleGitHooks, handleGitLFS, handleGitLFSFetch, handleGitRevert, } from "./handlers/index.js"; /** * Main server class for the Git Repository Browser MCP server */ export class GitRepoBrowserServer { /** * Initialize the server */ constructor() { this.server = new Server( { name: "mcp-git-repo-browser", version: "0.1.0", }, { capabilities: { tools: {}, }, } ); this.setupToolHandlers(); // Error handling this.server.onerror = (error) => console.error("[MCP Error]", error); process.on("SIGINT", async () => { await this.server.close(); process.exit(0); }); } /** * Get all registered handler names * @returns {string[]} Array of handler names */ getHandlerNames() { return Object.keys(this.handlersMap || {}); } /** * Check if a handler exists * @param {string} name - Handler name to check * @returns {boolean} True if handler exists */ hasHandler(name) { return Boolean(this.handlersMap && this.handlersMap[name]); } /** * Set up tool handlers for the server */ setupToolHandlers() { // Store tools list for dynamic updates this.toolsList = [ // Basic Repository Operations { name: "git_directory_structure", description: "Clone a Git repository and return its directory structure in a tree format.", inputSchema: { type: "object", properties: { repo_url: { type: "string", description: "The URL of the Git repository", }, }, required: ["repo_url"], }, }, { name: "git_read_files", description: "Read the contents of specified files in a given git repository.", inputSchema: { type: "object", properties: { repo_url: { type: "string", description: "The URL of the Git repository", }, file_paths: { type: "array", items: { type: "string" }, description: "List of file paths to read (relative to repository root)", }, }, required: ["repo_url", "file_paths"], }, }, // Branch Operations { name: "git_branch_diff", description: "Compare two branches and show files changed between them.", inputSchema: { type: "object", properties: { repo_url: { type: "string", description: "The URL of the Git repository", }, source_branch: { type: "string", description: "The source branch name", }, target_branch: { type: "string", description: "The target branch name", }, show_patch: { type: "boolean", description: "Whether to include the actual diff patches", default: false, }, }, required: ["repo_url", "source_branch", "target_branch"], }, }, { name: "git_checkout_branch", description: "Create and/or checkout a branch.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, branch_name: { type: "string", description: "The name of the branch to checkout", }, start_point: { type: "string", description: "Starting point for the branch (optional)", }, create: { type: "boolean", description: "Whether to create a new branch", default: false, }, }, required: ["repo_path", "branch_name"], }, }, { name: "git_delete_branch", description: "Delete a branch from the repository.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, branch_name: { type: "string", description: "The name of the branch to delete", }, force: { type: "boolean", description: "Whether to force deletion", default: false, }, }, required: ["repo_path", "branch_name"], }, }, { name: "git_merge_branch", description: "Merge a source branch into the current or target branch.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, source_branch: { type: "string", description: "Branch to merge from", }, target_branch: { type: "string", description: "Branch to merge into (optional, uses current branch if not provided)", }, no_fast_forward: { type: "boolean", description: "Whether to create a merge commit even if fast-forward is possible", default: false, }, }, required: ["repo_path", "source_branch"], }, }, // Commit Operations { name: "git_commit_history", description: "Get commit history for a branch with optional filtering.", inputSchema: { type: "object", properties: { repo_url: { type: "string", description: "The URL of the Git repository", }, branch: { type: "string", description: "The branch to get history from", default: "main", }, max_count: { type: "integer", description: "Maximum number of commits to retrieve", default: 10, }, author: { type: "string", description: "Filter by author (optional)", }, since: { type: "string", description: 'Get commits after this date (e.g., "1 week ago", "2023-01-01")', }, until: { type: "string", description: 'Get commits before this date (e.g., "yesterday", "2023-12-31")', }, grep: { type: "string", description: "Filter commits by message content (optional)", }, }, required: ["repo_url"], }, }, { name: "git_commits_details", description: "Get detailed information about commits including full messages and diffs.", inputSchema: { type: "object", properties: { repo_url: { type: "string", description: "The URL of the Git repository", }, branch: { type: "string", description: "The branch to get commits from", default: "main", }, max_count: { type: "integer", description: "Maximum number of commits to retrieve", default: 10, }, include_diff: { type: "boolean", description: "Whether to include the commit diffs", default: false, }, since: { type: "string", description: 'Get commits after this date (e.g., "1 week ago", "2023-01-01")', }, until: { type: "string", description: 'Get commits before this date (e.g., "yesterday", "2023-12-31")', }, author: { type: "string", description: "Filter by author (optional)", }, grep: { type: "string", description: "Filter commits by message content (optional)", }, }, required: ["repo_url"], }, }, { name: "git_commit", description: "Create a commit with the specified message.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, message: { type: "string", description: "The commit message", }, }, required: ["repo_path", "message"], }, }, { name: "git_track", description: "Track (stage) specific files or all files.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, files: { type: "array", items: { type: "string" }, description: 'Array of file paths to track/stage (use ["."] for all files)', default: ["."], }, }, required: ["repo_path"], }, }, { name: "git_local_changes", description: "Get uncommitted changes in the working directory.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, }, required: ["repo_path"], }, }, { name: "git_search_code", description: "Search for patterns in repository code.", inputSchema: { type: "object", properties: { repo_url: { type: "string", description: "The URL of the Git repository", }, pattern: { type: "string", description: "Search pattern (regex or string)", }, file_patterns: { type: "array", items: { type: "string" }, description: 'Optional file patterns to filter (e.g., "*.js")', }, case_sensitive: { type: "boolean", description: "Whether the search is case sensitive", default: false, }, context_lines: { type: "integer", description: "Number of context lines to include", default: 2, }, }, required: ["repo_url", "pattern"], }, }, // Remote Operations { name: "git_push", description: "Push changes to a remote repository.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, remote: { type: "string", description: "Remote name", default: "origin", }, branch: { type: "string", description: "Branch to push (default: current branch)", }, force: { type: "boolean", description: "Whether to force push", default: false, }, }, required: ["repo_path"], }, }, { name: "git_pull", description: "Pull changes from a remote repository.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, remote: { type: "string", description: "Remote name", default: "origin", }, branch: { type: "string", description: "Branch to pull (default: current branch)", }, rebase: { type: "boolean", description: "Whether to rebase instead of merge", default: false, }, }, required: ["repo_path"], }, }, // Stash Operations { name: "git_stash", description: "Create or apply a stash.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, action: { type: "string", description: "Stash action (save, pop, apply, list, drop)", default: "save", enum: ["save", "pop", "apply", "list", "drop"], }, message: { type: "string", description: "Stash message (for save action)", default: "", }, index: { type: "integer", description: "Stash index (for pop, apply, drop actions)", default: 0, }, }, required: ["repo_path"], }, }, // Tag Operations { name: "git_create_tag", description: "Create a tag.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, tag_name: { type: "string", description: "Name of the tag", }, message: { type: "string", description: "Tag message (for annotated tags)", default: "", }, annotated: { type: "boolean", description: "Whether to create an annotated tag", default: true, }, }, required: ["repo_path", "tag_name"], }, }, // Advanced Operations { name: "git_rebase", description: "Rebase the current branch onto another branch or commit.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, onto: { type: "string", description: "Branch or commit to rebase onto", }, interactive: { type: "boolean", description: "Whether to perform an interactive rebase", default: false, }, }, required: ["repo_path", "onto"], }, }, // Configuration { name: "git_config", description: "Configure git settings for the repository.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, scope: { type: "string", description: "Configuration scope (local, global, system)", default: "local", enum: ["local", "global", "system"], }, key: { type: "string", description: "Configuration key", }, value: { type: "string", description: "Configuration value", }, }, required: ["repo_path", "key", "value"], }, }, // Repo Management { name: "git_reset", description: "Reset repository to specified commit or state.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, mode: { type: "string", description: "Reset mode (soft, mixed, hard)", default: "mixed", enum: ["soft", "mixed", "hard"], }, to: { type: "string", description: "Commit or reference to reset to", default: "HEAD", }, }, required: ["repo_path"], }, }, // Archive Operations { name: "git_archive", description: "Create a git archive (zip or tar).", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, output_path: { type: "string", description: "Output path for the archive", }, format: { type: "string", description: "Archive format (zip or tar)", default: "zip", enum: ["zip", "tar"], }, prefix: { type: "string", description: "Prefix for files in the archive", }, treeish: { type: "string", description: "Tree-ish to archive (default: HEAD)", default: "HEAD", }, }, required: ["repo_path", "output_path"], }, }, // Attributes Operations { name: "git_attributes", description: "Manage git attributes for files.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, action: { type: "string", description: "Action (get, set, list)", default: "list", enum: ["get", "set", "list"], }, pattern: { type: "string", description: "File pattern", }, attribute: { type: "string", description: "Attribute to set", }, }, required: ["repo_path", "action"], }, }, // Blame Operations { name: "git_blame", description: "Get blame information for a file.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, file_path: { type: "string", description: "Path to the file", }, rev: { type: "string", description: "Revision to blame (default: HEAD)", default: "HEAD", }, }, required: ["repo_path", "file_path"], }, }, // Clean Operations { name: "git_clean", description: "Perform git clean operations.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, directories: { type: "boolean", description: "Whether to remove directories as well", default: false, }, force: { type: "boolean", description: "Whether to force clean", default: false, }, dry_run: { type: "boolean", description: "Whether to perform a dry run", default: true, }, }, required: ["repo_path"], }, }, // Hooks Operations { name: "git_hooks", description: "Manage git hooks in the repository.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, action: { type: "string", description: "Hook action (list, get, create, enable, disable)", default: "list", enum: ["list", "get", "create", "enable", "disable"], }, hook_name: { type: "string", description: "Name of the hook (e.g., 'pre-commit', 'post-merge')", }, script: { type: "string", description: "Script content for the hook (for create action)", }, }, required: ["repo_path", "action"], }, }, // LFS Operations { name: "git_lfs", description: "Manage Git LFS (Large File Storage).", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, action: { type: "string", description: "LFS action (install, track, untrack, list)", default: "list", enum: ["install", "track", "untrack", "list"], }, patterns: { type: "array", description: "File patterns for track/untrack", items: { type: "string" }, }, }, required: ["repo_path", "action"], }, }, // LFS Fetch Operations { name: "git_lfs_fetch", description: "Fetch LFS objects from the remote repository.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, dry_run: { type: "boolean", description: "Whether to perform a dry run", default: false, }, pointers: { type: "boolean", description: "Whether to convert pointers to objects", default: false, }, }, required: ["repo_path"], }, }, // Revert Operations { name: "git_revert", description: "Revert the current branch to a commit or state.", inputSchema: { type: "object", properties: { repo_path: { type: "string", description: "The path to the local Git repository", }, commit: { type: "string", description: "Commit hash or reference to revert", }, no_commit: { type: "boolean", description: "Whether to stage changes without committing", default: false, }, }, required: ["repo_path"], }, }, ]; // Set up dynamic tool listing handler this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: this.toolsList, })); // Handler categories for organization and improved discoverability this.handlerCategories = { read: [ "git_directory_structure", "git_read_files", "git_branch_diff", "git_commit_history", "git_commits_details", "git_local_changes", "git_search_code", ], write: ["git_commit", "git_track", "git_reset"], branch: [ "git_checkout_branch", "git_delete_branch", "git_merge_branch", "git_branch_diff", ], remote: ["git_push", "git_pull"], stash: ["git_stash"], config: ["git_config"], tag: ["git_create_tag"], advanced: ["git_rebase"], }; // Create handler aliases for improved usability this.handlerAliases = { git_ls: "git_directory_structure", git_show: "git_read_files", git_diff: "git_branch_diff", git_log: "git_commit_history", git_status: "git_local_changes", git_grep: "git_search_code", git_add: "git_track", git_checkout: "git_checkout_branch", git_fetch: "git_pull", }; // Initialize statistics tracking this.handlerStats = new Map(); // Create a handlers mapping for O(1) lookup time this.handlersMap = { // Primary handlers git_directory_structure: handleGitDirectoryStructure, git_read_files: handleGitReadFiles, git_branch_diff: handleGitBranchDiff, git_commit_history: handleGitCommitHistory, git_commits_details: handleGitCommitsDetails, git_local_changes: handleGitLocalChanges, git_search_code: handleGitSearchCode, git_commit: handleGitCommit, git_track: handleGitTrack, git_checkout_branch: handleGitCheckoutBranch, git_delete_branch: handleGitDeleteBranch, git_merge_branch: handleGitMergeBranch, git_push: handleGitPush, git_pull: handleGitPull, git_stash: handleGitStash, git_create_tag: handleGitCreateTag, git_rebase: handleGitRebase, git_config: handleGitConfig, git_reset: handleGitReset, git_archive: handleGitArchive, git_attributes: handleGitAttributes, git_blame: handleGitBlame, git_clean: handleGitClean, git_hooks: handleGitHooks, git_lfs: handleGitLFS, git_lfs_fetch: handleGitLFSFetch, git_revert: handleGitRevert, }; // Register aliases for O(1) lookup Object.entries(this.handlerAliases).forEach(([alias, target]) => { if (this.handlersMap[target]) { this.handlersMap[alias] = this.handlersMap[target]; } }); // Log registered handlers console.error( `[INFO] Registered ${ Object.keys(this.handlersMap).length } Git tool handlers` ); // Add method to get handlers by category this.getHandlersByCategory = (category) => { return this.handlerCategories[category] || []; }; // Add method to execute multiple Git operations in sequence this.executeSequence = async (operations) => { const results = []; for (const op of operations) { const { name, arguments: args } = op; const handler = this.handlersMap[name]; if (!handler) { throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } results.push(await handler(args)); } return results; }; // Add method to check if a repository is valid this.validateRepository = async (repoPath) => { try { // Implementation would verify if the path is a valid git repository return true; } catch (error) { return false; } }; this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; const startTime = Date.now(); // Handle batch operations as a special case if (name === "git_batch") { if (!Array.isArray(args.operations)) { throw new McpError( ErrorCode.InvalidParams, "Operations must be an array" ); } return await this.executeSequence(args.operations); } try { // Resolve handler via direct match or alias const handler = this.handlersMap[name]; if (handler) { // Track usage statistics const stats = this.handlerStats.get(name) || { count: 0, totalTime: 0, }; stats.count++; this.handlerStats.set(name, stats); console.error(`[INFO] Executing Git tool: ${name}`); const result = await handler(args); const executionTime = Date.now() - startTime; stats.totalTime += executionTime; console.error(`[INFO] Completed ${name} in ${executionTime}ms`); return result; } // Suggest similar commands if not found const similarCommands = Object.keys(this.handlersMap) .filter((cmd) => cmd.includes(name.replace(/^git_/, ""))) .slice(0, 3); const suggestion = similarCommands.length > 0 ? `. Did you mean: ${similarCommands.join(", ")}?` : ""; throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${name}${suggestion}` ); } catch (error) { // Enhanced error handling if (error instanceof McpError) { throw error; } console.error(`[ERROR] Failed to execute ${name}: ${error.message}`); throw new McpError( ErrorCode.InternalError, `Failed to execute ${name}: ${error.message}` ); } }); /** * Register a new handler at runtime * @param {string} name - The name of the handler * @param {Function} handler - The handler function * @param {Object} [toolInfo] - Optional tool information for ListToolsRequestSchema * @returns {boolean} True if registration was successful */ this.registerHandler = (name, handler, toolInfo) => { if (typeof handler !== "function") { throw new Error(`Handler for ${name} must be a function`); } // Add to handlers map this.handlersMap[name] = handler; // Update tools list if toolInfo is provided if (toolInfo && typeof toolInfo === "object") { // Get current tools const currentTools = this.toolsList || []; // Add new tool info if not already present const exists = currentTools.some((tool) => tool.name === name); if (!exists) { this.toolsList = [...currentTools, { name, ...toolInfo }]; } } console.error(`[INFO] Dynamically registered new handler: ${name}`); return true; }; /** * Remove a handler * @param {string} name - The name of the handler to remove * @returns {boolean} True if removal was successful */ this.unregisterHandler = (name) => { if (!this.handlersMap[name]) { return false; } delete this.handlersMap[name]; console.error(`[INFO] Unregistered handler: ${name}`); return true; }; } /** * Start the server */ async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error("Git Repo Browser MCP server running on stdio"); } }

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/bsreeram08/git-commands-mcp'

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