generate_commit_message
Generate conventional commit messages automatically from staged git changes. Analyzes git diffs and repository status to create properly formatted commits following conventional commit standards. Supports custom commit types and scopes.
Instructions
Prepare a structured analysis and instruction block for generating a Conventional Commit message from staged git changes only.
Behavior: - Validates the repository path and operates on the provided repo or CWD. - Collects staged diff, porcelain status, and a name-status summary. - Incorporates optional user preferences for commit_type and scope. - Returns a single formatted string that includes context plus strict output instructions for an LLM to produce a Conventional Commit.
Args: repo_path: Optional path to the target git repository. If not provided, uses the current working directory. commit_type: Optional commit type (feat, fix, docs, style, refactor, perf, build, ci, test, chore, revert) scope: Optional scope of the change
Returns: A formatted prompt containing git change context and clear output rules for generating a Conventional Commit message
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| repo_path | No | ||
| commit_type | No | ||
| scope | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- The main handler function for the 'generate_commit_message' tool. It is decorated with @mcp.tool() and retrieves staged git diff, status, and file changes, then constructs a formatted prompt for generating a Conventional Commit message.
def generate_commit_message( repo_path: Optional[str] = None, commit_type: Optional[str] = None, scope: Optional[str] = None, ) -> str: """ Prepare a structured analysis and instruction block for generating a Conventional Commit message from staged git changes only. Behavior: - Validates the repository path and operates on the provided repo or CWD. - Collects staged diff, porcelain status, and a name-status summary. - Incorporates optional user preferences for commit_type and scope. - Returns a single formatted string that includes context plus strict output instructions for an LLM to produce a Conventional Commit. Args: repo_path: Optional path to the target git repository. If not provided, uses the current working directory. commit_type: Optional commit type (feat, fix, docs, style, refactor, perf, build, ci, test, chore, revert) scope: Optional scope of the change Returns: A formatted prompt containing git change context and clear output rules for generating a Conventional Commit message """ try: valid_repo_path = _get_valid_repo_path(repo_path) if not valid_repo_path: return f"Path '{repo_path or os.getcwd()}' is not a valid git repository." cwd = valid_repo_path # Get staged changes diff_result = subprocess.run( ["git", "diff", "--cached"], capture_output=True, text=True, check=True, cwd=cwd, ) if not diff_result.stdout.strip(): return "No staged changes found. Please stage your changes with 'git add' first." # Get git status for context status_result = subprocess.run( ["git", "status", "--porcelain"], capture_output=True, text=True, check=True, cwd=cwd, ) # Get list of changed files for better analysis files_result = subprocess.run( ["git", "diff", "--cached", "--name-status"], capture_output=True, text=True, check=True, cwd=cwd, ) diff_preview = diff_result.stdout[:1500] analysis = textwrap.dedent(f""" ## Git Change Analysis for Conventional Commit Message ### Changed Files: {files_result.stdout} ### File Status Summary: {status_result.stdout} ### Diff Preview (first 1500 chars): {diff_preview} ### User Preferences: - Requested commit type: {commit_type or "auto-detect based on changes"} - Requested scope: {scope or "auto-detect based on files changed"} ### Task Write a Conventional Commit message for the STAGED changes only. ### Output format (return ONLY this) First line: type(scope): subject Add blank line before body Body paragraphs, each line <= 72 chars; bullets in body starting with "- " Optional footers (each on its own line), e.g.: BREAKING CHANGE: description ### Example generated commit message feat(core): add new feature - Implement new feature in core module - Update documentation BREAKING CHANGE: this change removes the old API method ### Rules - If commit_type or scope is provided above, USE THEM as-is. - If not provided, infer an appropriate type and a concise scope (or omit scope if unclear). - Subject: use imperative mood, start lowercase, no trailing period, <= 50 chars. - Body: use imperative mood (e.g. Update, Add etc.); explain WHAT and WHY, wrap at 72 chars; omit if subject suffices. - Use domain-specific terms; avoid generic phrases. - Do NOT mention "staged", "diff", or counts of files/lines. - Do NOT include markdown headers, code fences, or extra commentary. - Prefer a broad scope if many files; derive scope from top-level dirs when clear. - If there is a breaking change (e.g., API removal/rename), add a BREAKING CHANGE footer. - Keep the response to ONLY the commit message in the format above. ### Common types feat, fix, docs, style, refactor, perf, build, ci, test, chore, revert """) return analysis.strip() except subprocess.CalledProcessError as e: error_msg = e.stderr or e.stdout or str(e) return f"Git command failed: {error_msg}" except FileNotFoundError: return "Git is not installed or not found in PATH" except OSError as e: return f"OS error occurred: {str(e)}" - src/mcp_git_commit_generator/server.py:35-35 (registration)The tool is registered via the @mcp.tool() decorator on the FastMCP server instance named 'mcp', defined at line 14.
@mcp.tool() - Helper function used by generate_commit_message to resolve and validate that a given path is a valid Git repository (contains a .git directory).
def _get_valid_repo_path(repo_path: Optional[str]) -> Optional[str]: """ Resolve and validate the git repository path. Returns the valid repo path if valid, otherwise None. """ logger = logging.getLogger(__name__) # Resolve user tilde and symlinks to a canonical path resolved = ( os.path.realpath(os.path.expanduser(repo_path)) if repo_path else os.getcwd() ) logger.info("[get_valid_repo_path] Resolved repository path: %s", resolved) if not os.path.isdir(resolved) or not os.path.exists( os.path.join(resolved, ".git") ): return None return resolved