diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6ef176b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,136 @@ +# AGENTS.md — AI Coding Agent Instructions + +> This file tells AI coding agents (Claude Code, Cursor, Copilot, etc.) how to use +> `commit-check-mcp` effectively when validating commits in this repository. + +## What commit-check-mcp does for you + +`commit-check-mcp` is an MCP server that exposes structured commit validation tools. As +an AI agent, you can call these tools to validate: + +- **Commit messages** — Conventional Commits format, subject length, imperative mood, etc. +- **Branch names** — Naming conventions (feature/, bugfix/, hotfix/, etc.) +- **Author info** — Name and email format validation +- **Push safety** — Force-push detection for pre-push hooks +- **Repository state** — Combined validation of all the above in one call +- **Validation rules** — Inspect the effective config (defaults + repo config + overrides) + +## Installing in your MCP client + +```json +{ + "mcpServers": { + "commit-check": { + "command": "commit-check-mcp" + } + } +} +``` + +Or with `uvx` (no install needed): + +```json +{ + "mcpServers": { + "commit-check": { + "command": "uvx", + "args": ["commit-check-mcp"] + } + } +} +``` + +## When to call each tool + +### Before creating a commit → `validate_commit_message` + +Always call this **before** writing a commit message. Pass the message you intend to use: + +``` +Tool: validate_commit_message +Args: { "message": "feat(api): add user authentication endpoint" } +``` + +If it returns `"status": "fail"`, read the `checks[].suggest` field and fix the message +before committing. Never commit a message that fails validation. + +### Before creating a branch → `validate_branch_name` + +When you need to create a new branch, validate the name first: + +``` +Tool: validate_branch_name +Args: { "branch": "feature/user-auth" } +``` + +### After switching to a repo → `describe_validation_rules` + +On first entering a repository, inspect what rules are enforced: + +``` +Tool: describe_validation_rules +Args: { "repo_path": "/path/to/repo" } +``` + +This tells you which checks are active so you can comply from the start. + +### Before pushing → `validate_push_safety` + +If the repo has pre-push hooks configured, validate push safety: + +``` +Tool: validate_push_safety +Args: { "repo_path": "/path/to/repo" } +``` + +### Full state check → `validate_repository_state` + +For a comprehensive check of the current repo state: + +``` +Tool: validate_repository_state +Args: { "repo_path": "/path/to/repo" } +``` + +## Understanding responses + +All validation tools return this shape: + +```json +{ + "status": "pass|fail", + "checks": [ + { + "check": "message", + "status": "pass|fail", + "value": "the input that was checked", + "error": "reason for failure (only on fail)", + "suggest": "how to fix it (only on fail)" + } + ] +} +``` + +### When a check fails + +1. Read `error` to understand what went wrong +2. Read `suggest` for the recommended fix +3. Apply the fix and re-validate +4. Only proceed when `status` is `"pass"` + +### Config precedence + +When both `repo_path` and `config` are provided: + +1. commit-check built-in defaults +2. `cchk.toml` / `commit-check.toml` from the repo +3. Explicit `config_path` if given +4. Inline `config` overrides (highest priority) + +## Best practices for AI agents + +1. **Validate early, validate often** — check commit messages before writing them, not after +2. **Don't bypass failures** — if a check fails, fix it; never force-push or skip hooks +3. **Use the suggest field** — it contains the exact fix needed +4. **Check rules first** — call `describe_validation_rules` when entering a new repo +5. **Prefer `validate_repository_state`** for a comprehensive check in one call diff --git a/README.md b/README.md index 2d25f74..40ae15c 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,34 @@ [![Build](https://github.com/commit-check/commit-check-mcp/actions/workflows/main.yml/badge.svg)](https://github.com/commit-check/commit-check-mcp/actions/workflows/main.yml) [![Coverage](https://codecov.io/gh/commit-check/commit-check-mcp/graph/badge.svg)](https://codecov.io/gh/commit-check/commit-check-mcp) [![MCP server](https://img.shields.io/badge/MCP-server-0A7B83)](https://modelcontextprotocol.io/) +[![Smithery](https://img.shields.io/badge/Smithery-MCP%20Server-8B5CF6)](https://smithery.ai) -Model Context Protocol (MCP) server for [commit-check](https://github.com/commit-check/commit-check). +> **AI agent-friendly commit validation via MCP.** Built for AI coding agents (Claude Code, Cursor, Copilot, etc.) — not just human CI pipelines. -`commit-check-mcp` exposes `commit-check` as local MCP tools so an MCP client can validate commit messages, branch names, author info, push safety, and repository state. +![Demo](demo/demo.gif) + +**commit-check-mcp** is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that exposes [commit-check](https://github.com/commit-check/commit-check) validations as structured tools. AI agents call these tools to validate commit messages, branch names, author info, push safety, and repository state — every tool returns pass/fail results with actionable suggestions. + +### Why this vs commitlint? + +| | commitlint | commit-check-mcp | +|---|---|---| +| **Target user** | Humans in CI pipelines | AI coding agents via MCP | +| **Interface** | CLI + git hooks + config files | MCP tools (JSON in/out) | +| **Output** | Terminal text, exit codes | Structured JSON with `.error` + `.suggest` fields | +| **Agent awareness** | None | AGENTS.md instructions, tool descriptions optimized for LLM function calling | +| **Integration** | husky + CI config | Drop-in MCP server config for any MCP client | +| **Repository context** | Reads config from cwd | Explicit `repo_path` + `config_path` params — works across repos | + +## For AI Agents + +If you're an AI coding agent working in this repository, read **[AGENTS.md](AGENTS.md)** for instructions on how to use commit-check-mcp tools effectively. + +Key conventions for agents: +- Always validate commit messages **before** writing them +- Read `.suggest` on failures — it contains the exact fix +- Call `describe_validation_rules` when entering a new repo +- Use `validate_repository_state` for comprehensive checks in one call ## Features @@ -92,6 +116,16 @@ Example using an absolute path: For local development from this repository, that absolute path may point to something like `.venv/bin/commit-check-mcp`. +## Smithery / mcp.so + +commit-check-mcp is available on [Smithery](https://smithery.ai), the MCP server registry: + +```bash +npx @smithery-ai/cli install commit-check-mcp +``` + +Or add it to your MCP client directly using the [Smithery config](smithery.yaml). + ## Run Manually ```bash @@ -205,3 +239,27 @@ Config precedence is: 2. repository config loaded from `repo_path` 3. `config_path` when explicitly provided 4. inline `config` overrides passed to the tool + +## Development + +### Running tests + +```bash +pip install -e .[dev] +pytest -q --cov=src/commit_check_mcp +``` + +### Regenerating the demo GIF + +Requires [vhs](https://github.com/charmbracelet/vhs): + +```bash +brew install vhs +vhs demo/demo.tape --output demo/demo.gif +``` + +Edit `demo/demo-compact.py` to change the demo content. + +## License + +MIT — see [LICENSE](LICENSE). diff --git a/demo/demo-compact.py b/demo/demo-compact.py new file mode 100644 index 0000000..9edfab2 --- /dev/null +++ b/demo/demo-compact.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +"""Compact terminal demo optimized for GIF recording.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src")) + +from commit_check_mcp.server import ( + _validate_message, + _validate_branch, + _validate_author, + _validate_push, + _validate_all, + server_health, +) + +GREEN = "\033[32m" +RED = "\033[31m" +YELLOW = "\033[33m" +CYAN = "\033[36m" +BOLD = "\033[1m" +RESET = "\033[0m" +DIM = "\033[2m" + + +def heading(text: str) -> None: + print(f"\n{BOLD}{CYAN}{text}{RESET}") + + +def show(result: dict) -> None: + s = f"{GREEN}PASS{RESET}" if result["status"] == "pass" else f"{RED}FAIL{RESET}" + print(f" [{s}]", end="") + for c in result["checks"]: + mark = f"{GREEN}✓{RESET}" if c["status"] == "pass" else f"{RED}✗{RESET}" + print(f" {mark}{c['check']}", end="") + if c["status"] == "fail" and c.get("suggest"): + print(f"\n {YELLOW}→ {c['suggest'][:80]}{RESET}", end="") + print() + + +# Header +print(f"{BOLD}commit-check-mcp — AI Agent-Friendly Commit Validation{RESET}") +print(f"{DIM}https://github.com/commit-check/commit-check-mcp{RESET}") + +heading("Server Health") +health = server_health() +print(f" {health['server']} v{health['server_version']} | commit-check v{health['commit_check_version']}") + +heading("1. Commit Message") +show(_validate_message("feat(api): add user auth endpoint")) +show(_validate_message("add user auth")) + +heading("2. Branch Name") +show(_validate_branch("feature/user-auth")) +show(_validate_branch("my_branch")) + +heading("3. Author Info") +show(_validate_author("Xianpeng Shen", "xianpeng.shen@gmail.com")) +show(_validate_author("", "bad-email")) + +heading("4. Push Safety") +show(_validate_push("refs/heads/main abc123 refs/heads/main def456")) + +heading("5. Combined Context") +show(_validate_all( + message="chore(deps): bump requests", + branch="chore/update-deps", + author_name="Xianpeng Shen", + author_email="xianpeng.shen@gmail.com", +)) + +print(f"\n{BOLD}{GREEN}8 MCP tools for AI agents — install: pip install commit-check-mcp{RESET}") diff --git a/demo/demo.gif b/demo/demo.gif new file mode 100644 index 0000000..06d1053 Binary files /dev/null and b/demo/demo.gif differ diff --git a/demo/demo.py b/demo/demo.py new file mode 100644 index 0000000..36ea608 --- /dev/null +++ b/demo/demo.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +"""Terminal demo for commit-check-mcp — exercises all validation tools.""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +# Add local src to path so demo works from repo root +sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src")) + +from commit_check_mcp.server import ( + _validate_message, + _validate_branch, + _validate_author, + _validate_push, + _validate_all, + _merge_config, + server_health, +) +from commit_check import __version__ as cc_version + + +GREEN = "\033[32m" +RED = "\033[31m" +YELLOW = "\033[33m" +CYAN = "\033[36m" +BOLD = "\033[1m" +RESET = "\033[0m" +DIM = "\033[2m" + + +def heading(text: str) -> None: + print(f"\n{BOLD}{CYAN}━━━ {text} ━━━{RESET}\n") + + +def ok(text: str) -> None: + print(f" {GREEN}✓{RESET} {text}") + + +def fail(text: str) -> None: + print(f" {RED}✗{RESET} {text}") + + +def info(text: str) -> None: + print(f" {DIM}{text}{RESET}") + + +def show_result(result: dict) -> None: + status = result["status"] + icon = f"{GREEN}PASS{RESET}" if status == "pass" else f"{RED}FAIL{RESET}" + print(f" Status: [{icon}]") + for check in result["checks"]: + cstatus = check["status"] + mark = f"{GREEN}✓{RESET}" if cstatus == "pass" else f"{RED}✗{RESET}" + line = f" {mark} {check['check']}" + if check.get("value"): + line += f" {DIM}(input: {check['value'][:40]}){RESET}" + print(line) + if cstatus == "fail": + if check.get("error"): + print(f" {RED}error:{RESET} {check['error']}") + if check.get("suggest"): + print(f" {YELLOW}suggest:{RESET} {check['suggest']}") + print() + + +def main() -> None: + print(f"{BOLD}{CYAN}") + print(" ╔══════════════════════════════════════════╗") + print(" ║ commit-check-mcp — Demo Tour ║") + print(" ║ AI Agent-Friendly Commit Validation ║") + print(" ╚══════════════════════════════════════════╝") + print(f"{RESET}") + + # ---- Server Health ---- + heading("1. Server Health") + health = server_health() + print(f" Server: {health['server']} v{health['server_version']}") + print(f" commit-check: v{health['commit_check_version']}") + print(f" MCP SDK: v{health['mcp_sdk_version']}") + ok("Server healthy") + + # ---- Commit Message Validation ---- + heading("2. Commit Message Validation") + + # Good message + info("Validating: 'feat(api): add user authentication endpoint'") + result = _validate_message("feat(api): add user authentication endpoint") + show_result(result) + + # Bad message - missing type + info("Validating: 'add user auth' (missing type)") + result = _validate_message("add user auth") + show_result(result) + + # Bad message - too long subject + info("Validating: 'feat: ' + 73 chars...") + result = _validate_message("feat: " + "x" * 73) + show_result(result) + + # ---- Branch Name Validation ---- + heading("3. Branch Name Validation") + + # Good branch + info("Validating branch: 'feature/user-auth'") + result = _validate_branch("feature/user-auth") + show_result(result) + + # Bad branch + info("Validating branch: 'my_branch' (no prefix)") + result = _validate_branch("my_branch") + show_result(result) + + # ---- Author Validation ---- + heading("4. Author Name/Email Validation") + + info("Validating: name='Xianpeng Shen', email='xianpeng.shen@gmail.com'") + result = _validate_author("Xianpeng Shen", "xianpeng.shen@gmail.com") + show_result(result) + + info("Validating: name='', email='not-an-email'") + result = _validate_author("", "not-an-email") + show_result(result) + + # ---- Push Safety ---- + heading("5. Push Safety Validation") + + info("Simulating normal push (no force-push refs)") + result = _validate_push("refs/heads/main abc123 refs/heads/main def456") + show_result(result) + + # ---- Commit Context (combined) ---- + heading("6. Combined Commit Context Validation") + + info("Validating message + branch + author together") + result = _validate_all( + message="chore(deps): bump requests to 2.32.0", + branch="chore/update-deps", + author_name="Xianpeng Shen", + author_email="xianpeng.shen@gmail.com", + ) + show_result(result) + + # ---- Config Inspection ---- + heading("7. Validation Rules Inspection (describe_validation_rules)") + + merged = _merge_config(None) + commit_rules = [k for k in merged.get("commit", {})] + branch_rules = [k for k in merged.get("branch", {})] + print(f" Active commit rules: {YELLOW}{', '.join(sorted(commit_rules))}{RESET}") + print(f" Active branch rules: {YELLOW}{', '.join(sorted(branch_rules))}{RESET}") + print() + + # ---- Summary ---- + heading("Summary") + print(f" {BOLD}commit-check-mcp{RESET} provides {CYAN}8 MCP tools{RESET} for") + print(f" AI agents to validate commits, branches, authors,") + print(f" push safety, and repository state.") + print() + print(f" {DIM}Install: pip install commit-check-mcp{RESET}") + print(f" {DIM}MCP config: {{ 'command': 'commit-check-mcp' }}{RESET}") + print(f" {DIM}GitHub: https://github.com/commit-check/commit-check-mcp{RESET}") + print() + + +if __name__ == "__main__": + main() diff --git a/demo/demo.tape b/demo/demo.tape new file mode 100644 index 0000000..431cbd4 --- /dev/null +++ b/demo/demo.tape @@ -0,0 +1,7 @@ +Set Width 400 +Set Height 400 +Set FontSize 24 + +Type ".venv/bin/python demo/demo-compact.py" +Enter +Sleep 10 diff --git a/smithery.yaml b/smithery.yaml new file mode 100644 index 0000000..417d500 --- /dev/null +++ b/smithery.yaml @@ -0,0 +1,40 @@ +# Smithery.ai MCP server configuration +# https://smithery.ai + +name: commit-check-mcp +description: | + AI agent-friendly commit validation via MCP. Validates commit messages + (Conventional Commits), branch names, author info, push safety, and + repository state. Designed for AI coding agents — each tool returns + structured pass/fail results with actionable suggestions. + + Unlike commitlint (designed for human CI pipelines), commit-check-mcp + is built for the MCP tool ecosystem: structured JSON output, agent-readable + error messages with fix suggestions, and repository-aware validation + that reads your project's cchk.toml config. + +repository: https://github.com/commit-check/commit-check-mcp +homepage: https://github.com/commit-check/commit-check-mcp#readme +package: + registry: pypi + name: commit-check-mcp + +# Server launch configuration for Smithery +server: + command: commit-check-mcp + transport: stdio + +# MCP server metadata +capabilities: + tools: true + resources: false + prompts: false + +tags: + - git + - commit + - conventional-commits + - validation + - developer-tools + - ci-cd + - ai-agent