No AI API calls
All language understanding (“ignore CSS files”, “in Portuguese”) happens in Claude Code. The server only executes structured parameters: it reads diffs, filters, renders and writes.
An MCP server for Claude Code that turns your Git changes into a structured Markdown skeleton — ready for Claude to fill in the analysis.
A real documentation.md: diffs in, placeholders for Claude to fill.
02 — How it works
The server does the mechanical part — Git, filters, template, file. Claude Code does the understanding and the writing.
Inside any Git repository, tell Claude Code what to document.
"Document the changes on this branch, ignoring CSS files, in Portuguese."It translates the request into arguments for the generate_documentation tool.
{"exclude": ["*.css"], "language": "pt"}It reads the Git diffs, applies the include/exclude filters, renders a Jinja2 template and writes documentation.md.
{
"success": true,
"output_file": "/path/to/documentation.md",
"files_processed": 12,
"files_ignored": 3
}It edits the file in place: “Changes made”, “Testing process” and the AI Usage fields, using only real session data.
- Changes made: [To be filled by AI]All language understanding (“ignore CSS files”, “in Portuguese”) happens in Claude Code. The server only executes structured parameters: it reads diffs, filters, renders and writes.
cli/ and mcp_server/ are thin adapters that call core.generator.generate_documentation() and format its result. All Git commands live in core/git_diff.py.
Diffs are embedded next to [To be filled by AI] placeholders. Claude Code then edits the output file directly to write the actual analysis.
03 — Modes
Only mode decides which Git command runs. Filtering, rendering and writing are identical for every mode.
working_treedefaultStaged + unstaged changes. When a file has both, the staged diff takes priority.
git diff --cached · git diff
“Document the changes on this branch, ignoring CSS files, in Portuguese.”
{"exclude": ["*.css"], "language": "pt"}mode omitted — defaults to "working_tree".
commitsingle commitEverything a single commit introduced. Without commit_hash it documents HEAD.
git diff-tree -p --root <commit>
“Document what changed in commit a1b2c3d.”
{"mode": "commit", "commit_hash": "a1b2c3d"}“Document the last commit.” → {"mode": "commit"}
branchwhole branchThe current branch against the merge-base of a base branch (three-dot diff).
git diff <base_branch>...HEAD
“Document everything this branch adds compared to origin/develop.”
{"mode": "branch", "base_branch": "origin/develop"}base_branch omitted — defaults to "origin/main".
04 — Install
Two ways to plug it into Claude Code. Online needs nothing but uv; local gives you the source to read and change.
uvx runs Python tools straight from a Git URL. uv can also provide Python 3.10+ if you don't have it.
curl -LsSf https://astral.sh/uv/install.sh | shAt user scope, so it's available in every project. On first launch uvx downloads the package from GitHub and caches it.
claude mcp add --scope user --transport stdio gendoc -- \
uvx --from git+https://github.com/Paulosouzx/mcp-gen-documentation.git gendoc-mcp-serverOr run /mcp inside a Claude Code session.
claude mcp listThen, inside any Git repository, ask Claude Code something like “Document the last commit.” The server writes documentation.md and Claude Code fills it in.
05 — Reference
Calling it with nothing but project_path documents the uncommitted changes.
| Name | Type | Default | Meaning |
|---|---|---|---|
project_path | string | server cwd | Git repository to analyze |
include | string[] | none (= all) | Glob patterns; only matching changed files are kept |
exclude | string[] | none | Glob patterns to drop, applied after include |
output_file | string | documentation.md | Output path, relative to project_path unless absolute |
language | "en" | "pt" | "en" | Skeleton language |
title | string | language default | Document title |
template | string | "default" | Template name, resolved as templates/{template}_{language}.md.j2 |
mode | "working_tree" | "commit" | "branch" | "working_tree" | Diff source |
commit_hash | string | null | null (= HEAD) | Commit to document, used when mode="commit" |
base_branch | string | "origin/main" | Base branch to diff against, used when mode="branch" (base_branch...HEAD) |
{
"success": true,
"output_file": "/path/to/documentation.md",
"files_processed": 12,
"files_ignored": 3
}On failure (success: false) an error field explains what went wrong: not a Git repo, unknown template, unwritable path.
Documentation of changes
Branch: feat/landing-page
Commit: [describe in English]
AI Instructions:
- Fill in only the "Changes made", "Testing process" and "AI Usage" fields below.
- Keep exactly the structure of this document.
- Do not include the diff blocks in the final response.
- For "AI Usage", fill in fields only with real data from the current session, if available. Never invent values — leave a field empty if the information isn't available.
- cli/main.py:
- Changes made: [To be filled by AI]
Diff analysed:
```diff
diff --git a/cli/main.py b/cli/main.py
index 41b5d22..36e3cfe 100644
--- a/cli/main.py
+++ b/cli/main.py
@@ -20,6 +20,14 @@ def build_parser() -> argparse.ArgumentParser:
parser.add_argument("--language", default="en", choices=["en", "pt"], help="Skeleton language")
parser.add_argument("--title", default=None, help="Document title")
parser.add_argument("--template", default="default", help="Template name (see templates/)")
+ parser.add_argument(
+ "--mode",
+ default="working_tree",
+ choices=["working_tree", "commit", "branch"],
+ help="What to diff: uncommitted changes, a single commit, or a branch vs a base branch",
+ )
+ parser.add_argument("--commit", dest="commit_hash", default=None, help="Commit hash for --mode commit (default: HEAD)")
+ parser.add_argument("--base", dest="base_branch", default="origin/main", help="Base branch for --mode branch")
return parser
@@ -34,6 +42,9 @@ def main(argv: list[str] | None = None) -> int:
language=args.language,
title=args.title,
template=args.template,
+ mode=args.mode,
+ commit_hash=args.commit_hash,
+ base_branch=args.base_branch,
)
print(json.dumps(result.to_dict(), indent=2, ensure_ascii=False))
```
Testing process:
- [To be filled by AI]
## AI Usage
- Model:
- Input tokens:
- Output tokens:Real output for one commit of this repo, filtered to cli/*. Every skeleton ends with an empty AI Usage section; the server never fills it in and no cost is calculated.
06 — CLI
gendoc-mcp wraps the same core and prints the result as JSON. With no LLM in the loop, the placeholders and the AI Usage section stay empty.
# Uncommitted changes (default mode)
gendoc-mcp
gendoc-mcp --mode working_tree --exclude "*.css" --language pt
# A specific commit (defaults to HEAD if --commit omitted)
gendoc-mcp --mode commit --commit a1b2c3d
# Current branch vs a base branch (defaults to origin/main)
gendoc-mcp --mode branch --base origin/developuvx --from git+https://github.com/Paulosouzx/mcp-gen-documentation.git gendoc-mcp --mode commit--project-path--include / --exclude--output-file--language--title / --template--mode--commit--base