#
tokens: 18006/50000 23/23 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .dockerignore
├── .github
│   ├── actions
│   │   └── test-python
│   │       └── action.yml
│   ├── copilot-instructions.md
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   └── workflows
│       ├── docker-publish.yml
│       └── pypi-publish.yml
├── .gitignore
├── .markdownlint.jsonc
├── .python-version
├── .vscode
│   ├── launch.json
│   ├── mcp.json
│   ├── settings.json
│   └── tasks.json
├── Dockerfile
├── inspector
│   ├── package-lock.json
│   └── package.json
├── LICENSE
├── pyproject.toml
├── README.md
├── requirements-dev.txt
├── src
│   └── mcp_git_commit_generator
│       ├── __init__.py
│       ├── __main__.py
│       └── server.py
├── tests
│   └── test_server.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
1 | 3.13.5
2 | 
```

--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------

```
 1 | __pycache__/
 2 | *.pyc
 3 | *.pyo
 4 | *.pyd
 5 | *.egg-info/
 6 | build/
 7 | dist/
 8 | .git/
 9 | .vscode/
10 | .env
11 | .venv
12 | *.log
13 | uv.lock.bak
14 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Python-generated files
 2 | __pycache__/
 3 | *.py[oc]
 4 | build/
 5 | dist/
 6 | wheels/
 7 | *.egg-info
 8 | 
 9 | # Virtual environments
10 | .venv
11 | 
12 | # others
13 | node_modules/
14 | .DS_Store
15 | 
```

--------------------------------------------------------------------------------
/.markdownlint.jsonc:
--------------------------------------------------------------------------------

```
 1 | {
 2 |   "MD033": {
 3 |     "allowed_elements": ["br", "details", "summary"]
 4 |   },
 5 |   "MD013": {
 6 |     "line_length": 120,
 7 |     "code_blocks": false,
 8 |     "tables": false
 9 |   }
10 | }
11 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Git Commit Generator
  2 | 
  3 | [![PyPI](https://img.shields.io/pypi/v/mcp-git-commit-generator.svg)](https://pypi.org/project/mcp-git-commit-generator/)
  4 | [![GitHub Release](https://img.shields.io/github/v/release/theoklitosBam7/mcp-git-commit-generator)](https://github.com/theoklitosBam7/mcp-git-commit-generator/releases)
  5 | [![Publish Python 🐍 package to PyPI](https://github.com/theoklitosBam7/mcp-git-commit-generator/actions/workflows/pypi-publish.yml/badge.svg)](https://github.com/theoklitosBam7/mcp-git-commit-generator/actions/workflows/pypi-publish.yml)
  6 | [![Create and Publish Docker 🐳 image](https://github.com/theoklitosBam7/mcp-git-commit-generator/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/theoklitosBam7/mcp-git-commit-generator/actions/workflows/docker-publish.yml)
  7 | [![License](https://img.shields.io/github/license/theoklitosBam7/mcp-git-commit-generator.svg)](https://github.com/theoklitosBam7/mcp-git-commit-generator/blob/main/LICENSE)
  8 | 
  9 | Generate conventional commit messages from your staged git changes using Model Context Protocol (MCP).
 10 | 
 11 | ## ✨ Features
 12 | 
 13 | - **🤖 Automatic commit message generation** based on staged git diffs
 14 | - **📝 Conventional Commits** support with auto-detection of type and scope
 15 | - **🔄 Multiple transport options** - stdio (default) and SSE for different use cases
 16 | - **🔍 Inspector UI** for interactive testing and debugging
 17 | - **🐳 Docker support** with pre-built images for easy deployment
 18 | - **⚡ Cross-platform** - works on macOS, Linux, and Windows
 19 | 
 20 | ## 📦 Requirements
 21 | 
 22 | - **For Docker usage**: [Docker](https://www.docker.com/) (for running the server in a container)
 23 | - **For PyPI/uvx usage**: [Python](https://www.python.org/) >= 3.13.5 and [uv](https://github.com/astral-sh/uv)
 24 |   (recommended) or pip
 25 | - [Git](https://git-scm.com/) (for version control)
 26 | - An MCP-compatible client (VS Code with MCP extension, Claude Desktop, Cursor, Windsurf, etc.)
 27 | 
 28 | ## 🚀 Installation
 29 | 
 30 | You can install and use the MCP Git Commit Generator in multiple ways:
 31 | 
 32 | ### Option 1: Using uvx (Recommended)
 33 | 
 34 | The easiest way to use the package is with `uvx`, which automatically manages the virtual environment:
 35 | 
 36 | ```sh
 37 | uvx mcp-git-commit-generator
 38 | ```
 39 | 
 40 | ### Option 2: Install from PyPI
 41 | 
 42 | ```sh
 43 | pip install mcp-git-commit-generator
 44 | ```
 45 | 
 46 | Or with uv:
 47 | 
 48 | ```sh
 49 | uv pip install mcp-git-commit-generator
 50 | ```
 51 | 
 52 | ### Option 3: Using Docker
 53 | 
 54 | Use the pre-built Docker image from GitHub Container Registry (no installation required):
 55 | 
 56 | ```sh
 57 | docker run -i --rm --mount type=bind,src=${HOME},dst=${HOME} ghcr.io/theoklitosbam7/mcp-git-commit-generator:latest
 58 | ```
 59 | 
 60 | ## 🛠️ Available Tools
 61 | 
 62 | This MCP server provides the following tools to help you generate conventional commit messages:
 63 | 
 64 | ### `generate_commit_message`
 65 | 
 66 | Generates a conventional commit message based on your staged git changes.
 67 | 
 68 | **Parameters:**
 69 | 
 70 | - `repo_path` (string, optional): Path to the git repository. If omitted, uses the current directory.
 71 | - `commit_type` (string, optional): Conventional commit type (e.g., `feat`, `fix`, `docs`, `style`, `refactor`,
 72 |   `perf`, `build`, `ci`, `test`, `chore`, `revert`). If omitted, the type will be auto-detected.
 73 | - `scope` (string, optional): Scope of the change (e.g., file or module name). If omitted, the scope will be
 74 |   auto-detected based on changed files.
 75 | 
 76 | **Usage:**
 77 | 
 78 | 1. Stage your changes: `git add <files>`
 79 | 2. Use the tool through your MCP client to generate a commit message
 80 | 3. The tool will analyze your staged changes and generate an appropriate conventional commit message
 81 | 
 82 | ### `check_git_status`
 83 | 
 84 | Checks the current git repository status, including staged, unstaged, and untracked files.
 85 | 
 86 | **Parameters:**
 87 | 
 88 | - `repo_path` (string, optional): Path to the git repository. If omitted, uses the current directory.
 89 | 
 90 | **Usage:**
 91 | 
 92 | Use this tool to get an overview of your current git repository state before generating commit messages.
 93 | 
 94 | ## 🧩 MCP Client Configuration
 95 | 
 96 | Configure the MCP Git Commit Generator in your favorite MCP client. You have multiple options:
 97 | 
 98 | 1. **Using uvx** (recommended - automatically manages dependencies)
 99 | 2. **Using Docker** (no local Python installation required)
100 | 3. **Using local Python installation** (for development)
101 | 
102 | ### VS Code
103 | 
104 | Add one of the following configurations to your VS Code `mcp.json` file (usually located at `.vscode/mcp.json` in your workspace):
105 | 
106 | #### Using uvx (Recommended)
107 | 
108 | ```jsonc
109 | {
110 |   "servers": {
111 |     "mcp-git-commit-generator": {
112 |       "command": "uvx",
113 |       "args": ["mcp-git-commit-generator"]
114 |     }
115 |   }
116 | }
117 | ```
118 | 
119 | #### Using Docker
120 | 
121 | ```jsonc
122 | {
123 |   "servers": {
124 |     "mcp-git-commit-generator": {
125 |       "command": "docker",
126 |       "args": [
127 |         "run",
128 |         "-i",
129 |         "--rm",
130 |         "--mount",
131 |         "type=bind,src=${userHome},dst=${userHome}",
132 |         "ghcr.io/theoklitosbam7/mcp-git-commit-generator:latest"
133 |       ]
134 |     }
135 |   }
136 | }
137 | ```
138 | 
139 | If you want to put the configuration in your user `settings.json` file, you can do so by adding:
140 | 
141 | ```jsonc
142 | {
143 |   "mcp": {
144 |     "servers": {
145 |       "mcp-git-commit-generator": {
146 |         "command": "uvx",
147 |         "args": ["mcp-git-commit-generator"]
148 |       }
149 |     }
150 |   }
151 | }
152 | ```
153 | 
154 | ### Cursor
155 | 
156 | Add one of the following to your Cursor MCP configuration file (usually located at `~/.cursor/mcp.json`):
157 | 
158 | #### Cursor with uvx (Recommended)
159 | 
160 | ```jsonc
161 | {
162 |   "mcpServers": {
163 |     "mcp-git-commit-generator": {
164 |       "command": "uvx",
165 |       "args": ["mcp-git-commit-generator"]
166 |     }
167 |   }
168 | }
169 | ```
170 | 
171 | #### Cursor with Docker
172 | 
173 | ```jsonc
174 | {
175 |   "mcpServers": {
176 |     "mcp-git-commit-generator": {
177 |       "command": "docker",
178 |       "args": [
179 |         "run",
180 |         "-i",
181 |         "--rm",
182 |         "--mount",
183 |         "type=bind,src=${userHome},dst=${userHome}",
184 |         "ghcr.io/theoklitosbam7/mcp-git-commit-generator:latest"
185 |       ]
186 |     }
187 |   }
188 | }
189 | ```
190 | 
191 | ### Windsurf
192 | 
193 | Configure Windsurf with one of the following MCP server settings (usually located at `~/.codeium/windsurf/mcp_config.json`):
194 | 
195 | #### Windsurf with uvx (Recommended)
196 | 
197 | ```jsonc
198 | {
199 |     "mcpServers": {
200 |       "mcp-git-commit-generator": {
201 |         "command": "uvx",
202 |         "args": ["mcp-git-commit-generator"]
203 |       }
204 |     }
205 | }
206 | ```
207 | 
208 | #### Windsurf with Docker
209 | 
210 | ```jsonc
211 | {
212 |     "mcpServers": {
213 |       "mcp-git-commit-generator": {
214 |         "command": "docker",
215 |         "args": [
216 |           "run",
217 |           "-i",
218 |           "--rm",
219 |           "--mount",
220 |           "type=bind,src=${userHome},dst=${userHome}",
221 |           "ghcr.io/theoklitosbam7/mcp-git-commit-generator:latest"
222 |         ]
223 |       }
224 |     }
225 | }
226 | ```
227 | 
228 | ### Claude Desktop
229 | 
230 | Add one of the following to your Claude Desktop configuration file (usually located at
231 | `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
232 | 
233 | #### Claude Desktop with uvx (Recommended)
234 | 
235 | ```jsonc
236 | {
237 |   "mcpServers": {
238 |     "mcp-git-commit-generator": {
239 |       "command": "uvx",
240 |       "args": ["mcp-git-commit-generator"]
241 |     }
242 |   }
243 | }
244 | ```
245 | 
246 | #### Claude Desktop with Docker
247 | 
248 | ```jsonc
249 | {
250 |   "mcpServers": {
251 |     "mcp-git-commit-generator": {
252 |       "command": "docker",
253 |       "args": [
254 |         "run",
255 |         "-i",
256 |         "--rm",
257 |         "--mount",
258 |         "type=bind,src=${userHome},dst=${userHome}",
259 |         "ghcr.io/theoklitosbam7/mcp-git-commit-generator:latest"
260 |       ]
261 |     }
262 |   }
263 | }
264 | ```
265 | 
266 | > **Note**: The `--mount` option in Docker configurations allows the Docker container to access your home
267 | > directory, enabling it to work with git repositories located anywhere in your file system. When using uvx or pip
268 | > installations, this mounting is not needed as the tool runs directly on your system. Adjust the mount path if your
269 | > repositories are located elsewhere when using Docker.
270 | 
271 | ## 🚀 Quick Start Guide
272 | 
273 | 1. **Install the package** using one of the methods above:
274 |    - **Recommended**: `uvx mcp-git-commit-generator` (or configure in your MCP client)
275 |    - **Alternative**: `pip install mcp-git-commit-generator`
276 |    - **Docker**: Use the configurations above with Docker
277 | 2. **Configure your MCP client** using one of the configurations above
278 | 3. **Stage some changes** in a git repository:
279 | 
280 |    ```sh
281 |    git add <files>
282 |    ```
283 | 
284 | 4. **Use the tools** through your MCP client:
285 |    - Use `check_git_status` to see your current repository state
286 |    - Use `generate_commit_message` to create a conventional commit message
287 | 5. **Commit your changes** with the generated message
288 | 
289 | ---
290 | 
291 | ## 👨‍💻 Developer Guidelines
292 | 
293 | The following sections are intended for developers who want to contribute to or modify the MCP Git Commit Generator.
294 | 
295 | ### Local Development Setup 🛠️
296 | 
297 | If you prefer not to use Docker for development, you can run the server locally:
298 | 
299 | **Requirements:**
300 | 
301 | - [Python](https://www.python.org/) >= 3.13.5
302 | - [MCP CLI](https://pypi.org/project/mcp/) >= 1.10.1
303 | - [uv](https://github.com/astral-sh/uv) (for dependency management, optional but recommended)
304 | - [Node.js](https://nodejs.org/en) (for Inspector UI, optional)
305 | - [Python Debugger Extension](https://marketplace.visualstudio.com/items?itemName=ms-python.debugpy) (for debugging, optional)
306 | 
307 | **Installation:**
308 | 
309 | 1. **Clone the repository:**
310 | 
311 |    ```sh
312 |    git clone https://github.com/theoklitosBam7/mcp-git-commit-generator.git
313 |    cd mcp-git-commit-generator
314 |    ```
315 | 
316 | 2. **Prepare environment:**
317 | 
318 |     There are two approaches to set up the environment for this project. You can choose either one based on your preference.
319 | 
320 |     > Note: Reload VSCode or terminal to ensure the virtual environment python is used after creating the virtual environment.
321 | 
322 |     | Approach | Steps |
323 |     | -------- | ----- |
324 |     | Using `uv` | 1. Create virtual environment: `uv venv` <br>2. Run VSCode Command "***Python: Select Interpreter***" and select the python from created virtual environment <br>3. Install dependencies (include dev dependencies): `uv pip install -r pyproject.toml --group dev` <br>4. Install `mcp-git-commit-generator` using the command: `uv pip install -e .`. |
325 |     | Using `pip` | 1. Create virtual environment: `python -m venv .venv` <br>2. Run VSCode Command "***Python: Select Interpreter***" and select the python from created virtual environment <br>3. Install dependencies: `pip install -e .`. <br>4. Install pip dev dependencies: `pip install -r requirements-dev.txt`. |
326 | 
327 | 3. **(Optional) Install Inspector dependencies:**
328 | 
329 |    ```sh
330 |    cd inspector
331 |    npm install
332 |    ```
333 | 
334 | ### 📦 Publishing to PyPI
335 | 
336 | The project includes an automated PyPI publishing workflow (`.github/workflows/pypi-publish.yml`) that:
337 | 
338 | - **Triggers on**: Tag pushes matching `v*.*.*` pattern, manual workflow dispatch, or pull requests to main
339 | - **Builds**: Python package distributions using the `build` package
340 | - **Publishes**: Automatically publishes to PyPI using trusted publishing (OIDC) when tags are pushed
341 | 
342 | To publish a new version:
343 | 
344 | 1. Update the version in `pyproject.toml`
345 | 2. Create and push a git tag: `git tag vX.Y.Z && git push origin vX.Y.Z`
346 | 3. The workflow will automatically build and publish to PyPI
347 | 
348 | ### 🐳 Building and Running with Docker
349 | 
350 | You can build and run the MCP Git Commit Generator using Docker. The provided Dockerfile uses a multi-stage build
351 | with [`uv`](https://github.com/astral-sh/uv) for dependency management and runs the server as a non-root user for security.
352 | 
353 | #### Build the Docker image
354 | 
355 | ```sh
356 | docker build -t mcp-git-commit-generator .
357 | ```
358 | 
359 | #### Run the server in a container (default: stdio transport)
360 | 
361 | You can run the published image directly from GitHub Container Registry.
362 | 
363 | ```sh
364 | docker run -d \
365 |   --name mcp-git-commit-generator \
366 |   ghcr.io/theoklitosbam7/mcp-git-commit-generator:latest
367 | ```
368 | 
369 | By default, the container runs:
370 | 
371 | ```sh
372 | mcp-git-commit-generator --transport stdio
373 | ```
374 | 
375 | If you want to use SSE transport (for Inspector UI or remote access), override the entrypoint or run manually:
376 | 
377 | ```sh
378 | docker run -d \
379 |   --name mcp-git-commit-generator \
380 |   -p 3001:3001 \
381 |   --entrypoint mcp-git-commit-generator \
382 |   ghcr.io/theoklitosbam7/mcp-git-commit-generator:latest --transport sse --host 0.0.0.0 --port 3001
383 | ```
384 | 
385 | The server will be available at `http://localhost:3001` when using SSE.
386 | 
387 | ### 🖥️ Running the Server Locally
388 | 
389 | **To run locally (without Docker):**
390 | 
391 | 1. Set up your uv or Python environment as described in the Local Development Setup section.
392 | 2. From the project root, run:
393 | 
394 |   <details>
395 |   <summary>mcp-git-commit-generator</summary>
396 | 
397 |    ```sh
398 |    # If you have mcp-git-commit-generator installed in your environment (default: stdio)
399 |    mcp-git-commit-generator
400 |    ```
401 | 
402 |   </details>
403 | 
404 |   <details>
405 |   <summary>mcp-git-commit-generator with SSE transport</summary>
406 | 
407 |    ```sh
408 |    mcp-git-commit-generator --transport sse
409 |    ```
410 | 
411 |   </details>
412 | 
413 |   <details>
414 |   <summary>Using uv</summary>
415 | 
416 |    ```sh
417 |    uv run -m mcp_git_commit_generator --transport sse
418 |    ```
419 | 
420 |   </details>
421 | 
422 |   <details>
423 |   <summary>Using Python directly</summary>
424 | 
425 |    ```sh
426 |    python -m mcp_git_commit_generator --transport sse
427 |    ```
428 | 
429 |   </details>
430 | 
431 |   <br/>
432 | 
433 |   You can specify other options, for example:
434 | 
435 |    ```sh
436 |    python -m mcp_git_commit_generator --transport sse --host 0.0.0.0 --port 3001 -v
437 |    ```
438 | 
439 |    > The server listens on `0.0.0.0:3001` by default when using SSE, or as specified by the options above.
440 | 
441 | **Note:**
442 | 
443 | - If you want to use the CLI entrypoint, ensure the package is installed and your environment is activated.
444 | - Do not use positional arguments (e.g., `python -m mcp_git_commit_generator sse`);
445 | always use options like `--transport sse`.
446 | - Available arguments with their values are:
447 |   - `--transport`: Transport type (e.g., `stdio` (default), `sse`).
448 |   - `--host`: Host to bind the server (default: `0.0.0.0`).
449 |   - `--port`: Port to bind the server (default: `3001`).
450 |   - `-v`, `--verbose`: Verbosity level (e.g., `-v`, `-vv`).
451 | 
452 | ### 🔎 Start the Inspector UI
453 | 
454 | From the `inspector` directory:
455 | 
456 | ```sh
457 | npm run dev:inspector
458 | ```
459 | 
460 | > The Inspector UI will be available at `http://localhost:5173`.
461 | 
462 | ### 🧪 Running Tests
463 | 
464 | The project includes comprehensive unit tests to ensure reliability:
465 | 
466 | ```sh
467 | # Run all tests
468 | pytest
469 | 
470 | # Run tests with verbose output
471 | pytest -v
472 | 
473 | # Run tests with coverage
474 | pytest --cov=src/mcp_git_commit_generator
475 | 
476 | # Run specific test file
477 | pytest tests/test_server.py
478 | ```
479 | 
480 | **Test Coverage:**
481 | 
482 | - ✅ Tool validation with invalid repository paths
483 | - ✅ Staged and unstaged change detection
484 | - ✅ Git status reporting
485 | - ✅ Commit message generation workflows
486 | - ✅ Error handling for git command failures
487 | 
488 | ### 🗂️ Project Structure
489 | 
490 | ```sh
491 | .
492 | ├── .github/                # GitHub workflows and issue templates
493 | ├── .gitignore
494 | ├── .markdownlint.jsonc
495 | ├── .python-version
496 | ├── .vscode/                # VSCode configuration
497 | ├── LICENSE
498 | ├── README.md
499 | ├── pyproject.toml          # Python project configuration
500 | ├── requirements-dev.txt    # Development dependencies
501 | ├── uv.lock                 # Python dependencies lock file
502 | ├── Dockerfile              # Docker build file
503 | ├── build/                  # Build artifacts
504 | ├── src/                    # Python source code
505 | │   └── mcp_git_commit_generator/
506 | │       ├── __init__.py     # Main entry point
507 | │       ├── __main__.py     # CLI entry point
508 | │       └── server.py       # Main server implementation
509 | └── inspector/              # Inspector related files
510 |     ├── package.json        # Node.js dependencies
511 |     └── package-lock.json
512 | ```
513 | 
514 | ### ⚙️ Advanced MCP Server Configuration for Development
515 | 
516 | The `.vscode/mcp.json` file configures how VS Code and related tools connect to your MCP Git Commit Generator server.
517 | This file defines available server transports and their connection details, making it easy to switch between
518 | different modes (stdio is default, SSE is optional) for development and debugging.
519 | 
520 | #### Example Development `mcp.json`
521 | 
522 | ```jsonc
523 | {
524 |   "servers": {
525 |     "mcp-git-commit-generator": {
526 |       "command": "docker",
527 |       "args": [
528 |         "run",
529 |         "-i",
530 |         "--rm",
531 |         "--mount",
532 |         "type=bind,src=${userHome},dst=${userHome}",
533 |         "ghcr.io/theoklitosbam7/mcp-git-commit-generator:latest"
534 |       ]
535 |     },
536 |     "sse-mcp-git-commit-generator": {
537 |       "type": "sse",
538 |       "url": "http://localhost:3001/sse"
539 |     },
540 |     "stdio-mcp-git-commit-generator": {
541 |       "type": "stdio",
542 |       "command": "${command:python.interpreterPath}",
543 |       "args": ["-m", "mcp_git_commit_generator", "--transport", "stdio"]
544 |     },
545 |     "uvx-mcp-git-commit-generator": {
546 |       "command": "uvx",
547 |       "args": ["mcp-git-commit-generator"]
548 |     }
549 |   }
550 | }
551 | ```
552 | 
553 | - **mcp-git-commit-generator**: Runs the server in a Docker container (default: stdio transport), using the published image.
554 | - **sse-mcp-git-commit-generator**: Connects to the MCP server using Server-Sent Events (SSE) at `http://localhost:3001/sse`.
555 | Only useful if you run the server with `--transport sse`.
556 | - **stdio-mcp-git-commit-generator**: Connects using standard input/output (stdio), running the server as a subprocess.
557 | This is the default and recommended for local development and debugging.
558 | - **uvx-mcp-git-commit-generator**: Uses uvx to automatically install and run the package from PyPI.
559 | 
560 | ### 🐞 Debugging the MCP Server
561 | 
562 | > Notes:
563 | >
564 | > - [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is a visual developer tool for testing
565 | and debugging MCP servers.
566 | > - All debugging modes support breakpoints, so you can add breakpoints to the tool implementation code.
567 | > - **You can test tool arguments directly in the Inspector UI**: When using the Inspector, select a tool and provide
568 | arguments in the input fields to simulate real usage and debug argument handling.
569 | 
570 | | Debug Mode | Description | Steps to debug |
571 | | ---------- | ----------- | --------------- |
572 | | MCP Inspector | Debug the MCP server using the MCP Inspector. | 1. Install [Node.js](https://nodejs.org/)<br> 2. Set up Inspector: `cd inspector` && `npm install` <br> 3. Open VS Code Debug panel. Select `Debug in Inspector (Edge)` or `Debug in Inspector (Chrome)`. Press F5 to start debugging.<br> 4. When MCP Inspector launches in the browser, click the `Connect` button to connect this MCP server.<br> 5. Then you can `List Tools`, select a tool, input parameters (see arguments above), and `Run Tool` to debug your server code.<br> |
573 | 
574 | ### ⚙️ Default Ports and Customizations
575 | 
576 | | Debug Mode | Ports | Definitions | Customizations | Note |
577 | | ---------- | ----- | ------------ | -------------- |-------------- |
578 | | MCP Inspector | 3001 (Server, SSE only); 5173 and 3000 (Inspector) | [tasks.json](.vscode/tasks.json) | Edit [launch.json](.vscode/launch.json), [tasks.json](.vscode/tasks.json), [\_\_init\_\_.py](src/__init__.py), [mcp.json](.vscode/mcp.json) to change above ports.| N/A |
579 | 
580 | ## 💬 Feedback
581 | 
582 | If you have any feedback or suggestions, please open an issue on the [MCP Git Commit Generator GitHub repository](https://github.com/theoklitosBam7/mcp-git-commit-generator/issues)
583 | 
584 | ## 📖 Troubleshooting
585 | 
586 | ### Common Issues
587 | 
588 | - **"Path is not a valid git repository"**: Ensure you're in a directory with a `.git` folder
589 | - **"No staged changes found"**: Run `git add <files>` to stage your changes first
590 | - **"Git is not installed"**: Install Git from [git-scm.com](https://git-scm.com/)
591 | - **Docker permission issues**: Ensure Docker can access your home directory
592 | - **MCP connection fails**: Verify your client configuration matches the examples above
593 | 
594 | ### Getting Help
595 | 
596 | - Check the [Issues page](https://github.com/theoklitosBam7/mcp-git-commit-generator/issues) for solutions
597 | - Use the Inspector UI for interactive debugging
598 | - Run `pytest -v` to verify your installation
599 | 
600 | ## 📄 License
601 | 
602 | [MIT](./LICENSE) License © 2025 [Theoklitos Bampouris](https://github.com/theoklitosBam7)
603 | 
```

--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------

```
1 | # requirements-dev.txt only for pip compatibility
2 | debugpy>=1.8.14
3 | pre-commit>=4.2.0
4 | pytest>=8.4.1
5 | 
```

--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------

```json
1 | {
2 |   "python.testing.pytestArgs": ["tests"],
3 |   "python.testing.unittestEnabled": false,
4 |   "python.testing.pytestEnabled": true
5 | }
6 | 
```

--------------------------------------------------------------------------------
/src/mcp_git_commit_generator/__main__.py:
--------------------------------------------------------------------------------

```python
1 | """Generate conventional commit messages from your staged git changes using Model Context Protocol (MCP)."""
2 | 
3 | from mcp_git_commit_generator import main
4 | 
5 | main()
6 | 
```

--------------------------------------------------------------------------------
/inspector/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "inspector-placeholder",
 3 |   "version": "0.0.1",
 4 |   "description": "This is just a placeholder to launch the inspector.",
 5 |   "private": true,
 6 |   "scripts": {
 7 |     "dev:inspector": "CLIENT_PORT=5173 SERVER_PORT=3000 mcp-inspector"
 8 |   },
 9 |   "devDependencies": {
10 |     "@modelcontextprotocol/inspector": "^0.15.0"
11 |   }
12 | }
13 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | name: Feature request
 3 | about: Suggest an idea for this project
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 | 
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 | 
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 | 
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 | 
```

--------------------------------------------------------------------------------
/.vscode/mcp.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "servers": {
 3 |     "mcp-git-commit-generator": {
 4 |       "command": "docker",
 5 |       "args": [
 6 |         "run",
 7 |         "-i",
 8 |         "--rm",
 9 |         "--mount",
10 |         "type=bind,src=${userHome},dst=${userHome}",
11 |         "ghcr.io/theoklitosbam7/mcp-git-commit-generator:latest"
12 |       ]
13 |     },
14 |     "sse-mcp-git-commit-generator": {
15 |       "type": "sse",
16 |       "url": "http://localhost:3001/sse"
17 |     },
18 |     "stdio-mcp-git-commit-generator": {
19 |       "type": "stdio",
20 |       "command": "${command:python.interpreterPath}",
21 |       "args": ["-m", "mcp_git_commit_generator", "--transport", "stdio"]
22 |     },
23 |     "uvx-mcp-git-commit-generator": {
24 |       "command": "uvx",
25 |       "args": ["mcp-git-commit-generator"]
26 |     }
27 |   }
28 | }
29 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | name: Bug report
 3 | about: Create a report to help us improve
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 | 
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 | 
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 | 
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 | 
26 | **Desktop (please complete the following information):**
27 |  - OS: [e.g. iOS]
28 |  - Browser [e.g. chrome, safari]
29 |  - Version [e.g. 22]
30 | 
31 | **Smartphone (please complete the following information):**
32 |  - Device: [e.g. iPhone6]
33 |  - OS: [e.g. iOS8.1]
34 |  - Browser [e.g. stock browser, safari]
35 |  - Version [e.g. 22]
36 | 
37 | **Additional context**
38 | Add any other context about the problem here.
39 | 
```

--------------------------------------------------------------------------------
/.github/actions/test-python/action.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: test-python
 2 | description: "Runs Python tests for the MCP Git Commit Generator project"
 3 | 
 4 | runs:
 5 |   using: "composite"
 6 |   steps:
 7 |     - name: Set up Python for tests
 8 |       uses: actions/setup-python@v5
 9 |       with:
10 |         python-version: "3.13.5"
11 | 
12 |     - name: Cache pip
13 |       uses: actions/cache@v4
14 |       with:
15 |         path: |
16 |           ~/.cache/pip
17 |         key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
18 |         restore-keys: |
19 |           ${{ runner.os }}-pip-
20 | 
21 |     - name: Configure git user for CI
22 |       run: |
23 |         git config --global user.email "[email protected]"
24 |         git config --global user.name "CI Runner"
25 |       shell: bash
26 | 
27 |     - name: Install test dependencies
28 |       run: |
29 |         python -m pip install --upgrade pip
30 |         python -m pip install -e .
31 |         if [ -f requirements-dev.txt ]; then python -m pip install -r requirements-dev.txt; fi
32 |       shell: bash
33 | 
34 |     - name: Run tests
35 |       run: |
36 |         pytest -q
37 |       shell: bash
38 | 
```

--------------------------------------------------------------------------------
/.github/workflows/pypi-publish.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Publish Python 🐍 package to PyPI
 2 | 
 3 | on:
 4 |   workflow_dispatch:
 5 |   push:
 6 |     tags:
 7 |       - "v*.*.*"
 8 |   pull_request:
 9 |     branches:
10 |       - "main"
11 | 
12 | jobs:
13 |   release-build:
14 |     runs-on: ubuntu-latest
15 | 
16 |     steps:
17 |       - uses: actions/checkout@v4
18 | 
19 |       - name: Run Python tests
20 |         uses: ./.github/actions/test-python
21 | 
22 |       - name: Build release distributions
23 |         run: |
24 |           python -m pip install build
25 |           python -m build
26 | 
27 |       - name: Upload distributions
28 |         uses: actions/upload-artifact@v4
29 |         with:
30 |           name: release-dists
31 |           path: dist/
32 | 
33 |   pypi-publish:
34 |     if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
35 |     runs-on: ubuntu-latest
36 |     needs:
37 |       - release-build
38 |     permissions:
39 |       contents: read
40 |       id-token: write
41 | 
42 |     environment:
43 |       name: pypi
44 | 
45 |     steps:
46 |       - name: Retrieve release distributions
47 |         uses: actions/download-artifact@v4
48 |         with:
49 |           name: release-dists
50 |           path: dist/
51 | 
52 |       - name: Publish release distributions to PyPI
53 |         uses: pypa/gh-action-pypi-publish@release/v1
54 |         with:
55 |           packages-dir: dist/
56 | 
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "mcp-git-commit-generator"
 3 | version = "2.1.0"
 4 | description = "Generate conventional commit messages from your staged git changes using Model Context Protocol (MCP)."
 5 | authors = [{ name = "Theoklitos Bampouris", email = "[email protected]" }]
 6 | license = { text = "MIT" }
 7 | maintainers = [
 8 |     { name = "Theoklitos Bampouris", email = "[email protected]" },
 9 | ]
10 | keywords = [
11 |     "mcp",
12 |     "model context protocol",
13 |     "git",
14 |     "commit",
15 |     "conventional commits",
16 |     "conventional commit messages",
17 | ]
18 | readme = "README.md"
19 | requires-python = ">=3.13.5"
20 | classifiers = [
21 |     "Programming Language :: Python :: 3",
22 |     "Operating System :: OS Independent",
23 | ]
24 | dependencies = ["click>=8.2.1", "mcp[cli]>=1.10.1"]
25 | 
26 | [dependency-groups]
27 | dev = ["debugpy>=1.8.14", "pre-commit>=4.2.0", "pytest>=8.4.1"]
28 | 
29 | [project.scripts]
30 | mcp-git-commit-generator = "mcp_git_commit_generator:main"
31 | 
32 | [project.urls]
33 | Homepage = "https://github.com/theoklitosBam7/mcp-git-commit-generator"
34 | Issues = "https://github.com/theoklitosBam7/mcp-git-commit-generator/issues"
35 | 
36 | [build-system]
37 | requires = ["uv_build >= 0.7.19, <0.9.0"]
38 | build-backend = "uv_build"
39 | 
40 | [tool.pytest.ini_options]
41 | pythonpath = ["src"]
42 | testpaths = ["tests"]
43 | 
```

--------------------------------------------------------------------------------
/.github/workflows/docker-publish.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Create and Publish Docker 🐳 image
 2 | 
 3 | on:
 4 |   workflow_dispatch:
 5 |   push:
 6 |     tags:
 7 |       - "v*.*.*"
 8 |   pull_request:
 9 |     branches:
10 |       - "main"
11 | 
12 | env:
13 |   REGISTRY: ghcr.io
14 |   IMAGE_NAME: ${{ github.repository }}
15 | 
16 | jobs:
17 |   build-and-push:
18 |     runs-on: ubuntu-latest
19 | 
20 |     permissions:
21 |       contents: read
22 |       packages: write
23 | 
24 |     steps:
25 |       - name: Checkout repository
26 |         uses: actions/checkout@v4
27 | 
28 |       - name: Run Python tests
29 |         uses: ./.github/actions/test-python
30 | 
31 |       - name: Set up QEMU
32 |         uses: docker/setup-qemu-action@v3
33 | 
34 |       - name: Set up Docker Buildx
35 |         uses: docker/setup-buildx-action@v3
36 | 
37 |       - name: Extract metadata (tags, labels) for Docker
38 |         id: meta
39 |         uses: docker/metadata-action@v5
40 |         with:
41 |           images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
42 | 
43 |       - name: Log in to GitHub Container Registry
44 |         uses: docker/login-action@v3
45 |         with:
46 |           registry: ${{ env.REGISTRY }}
47 |           username: ${{ github.actor }}
48 |           password: ${{ secrets.GITHUB_TOKEN }}
49 | 
50 |       - name: Build and push Docker image
51 |         uses: docker/build-push-action@v6
52 |         with:
53 |           context: .
54 |           push: ${{ github.event_name != 'pull_request' }}
55 |           tags: ${{ steps.meta.outputs.tags }}
56 |           labels: ${{ steps.meta.outputs.labels }}
57 |           platforms: linux/amd64,linux/arm64
58 | 
```

--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "version": "0.2.0",
 3 |   "configurations": [
 4 |     {
 5 |       "name": "Attach to Local MCP",
 6 |       "type": "debugpy",
 7 |       "request": "attach",
 8 |       "connect": {
 9 |         "host": "localhost",
10 |         "port": 5678
11 |       },
12 |       "presentation": {
13 |         "hidden": true
14 |       },
15 |       "internalConsoleOptions": "neverOpen",
16 |       "postDebugTask": "Terminate All Tasks"
17 |     },
18 |     {
19 |       "name": "Launch Inspector (Edge)",
20 |       "type": "msedge",
21 |       "request": "launch",
22 |       "url": "http://localhost:5173?timeout=60000&serverUrl=http://localhost:3001/sse#tools",
23 |       "cascadeTerminateToConfigurations": ["Attach to Local MCP"],
24 |       "presentation": {
25 |         "hidden": true
26 |       },
27 |       "internalConsoleOptions": "neverOpen"
28 |     },
29 |     {
30 |       "name": "Launch Inspector (Chrome)",
31 |       "type": "chrome",
32 |       "request": "launch",
33 |       "url": "http://localhost:5173?timeout=60000&serverUrl=http://localhost:3001/sse#tools",
34 |       "cascadeTerminateToConfigurations": ["Attach to Local MCP"],
35 |       "presentation": {
36 |         "hidden": true
37 |       },
38 |       "internalConsoleOptions": "neverOpen"
39 |     }
40 |   ],
41 |   "compounds": [
42 |     {
43 |       "name": "Debug in Inspector (Edge)",
44 |       "configurations": ["Launch Inspector (Edge)", "Attach to Local MCP"],
45 |       "preLaunchTask": "Start MCP Inspector",
46 |       "stopAll": true
47 |     },
48 |     {
49 |       "name": "Debug in Inspector (Chrome)",
50 |       "configurations": ["Launch Inspector (Chrome)", "Attach to Local MCP"],
51 |       "preLaunchTask": "Start MCP Inspector",
52 |       "stopAll": true
53 |     }
54 |   ]
55 | }
56 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Use a Python image with uv pre-installed
 2 | FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS uv
 3 | 
 4 | LABEL org.opencontainers.image.source="https://github.com/theoklitosBam7/mcp-git-commit-generator"
 5 | LABEL org.opencontainers.image.description="Generate conventional commit messages from your staged git changes using Model Context Protocol (MCP)."
 6 | LABEL org.opencontainers.image.licenses=MIT
 7 | 
 8 | # Install the project into `/app`
 9 | WORKDIR /app
10 | 
11 | # First copy only the required files for dependency installation
12 | COPY pyproject.toml uv.lock ./
13 | 
14 | # Enable bytecode compilation
15 | ENV UV_COMPILE_BYTECODE=1
16 | 
17 | # Copy from the cache instead of linking since it's a mounted volume
18 | ENV UV_LINK_MODE=copy
19 | 
20 | # Install the project's dependencies using the lockfile and settings
21 | RUN --mount=type=cache,target=/root/.cache/uv \
22 |     uv sync --frozen --no-install-project --no-dev --no-editable
23 | 
24 | # Then, add the rest of the project source code and install it
25 | # Installing separately from its dependencies allows optimal layer caching
26 | COPY . /app
27 | RUN --mount=type=cache,target=/root/.cache/uv \
28 |     uv sync --frozen --no-dev --no-editable
29 | 
30 | RUN uv pip install .
31 | 
32 | FROM python:3.13-slim-bookworm
33 | 
34 | LABEL org.opencontainers.image.source="https://github.com/theoklitosBam7/mcp-git-commit-generator"
35 | LABEL org.opencontainers.image.description="Generate conventional commit messages from your staged git changes using Model Context Protocol (MCP)."
36 | LABEL org.opencontainers.image.licenses=MIT
37 | 
38 | WORKDIR /app
39 | 
40 | RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* \
41 |     && adduser --disabled-password --gecos '' --uid 1000 appuser
42 | COPY --from=uv --chown=appuser:appuser /app/.venv /app/.venv
43 | 
44 | ENV PATH="/app/.venv/bin:$PATH"
45 | ENV PYTHONUNBUFFERED=1
46 | 
47 | RUN chown -R appuser:appuser /app
48 | USER appuser
49 | 
50 | ENTRYPOINT ["mcp-git-commit-generator", "--transport", "stdio"]
51 | 
```

--------------------------------------------------------------------------------
/src/mcp_git_commit_generator/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | """Main entry point for the MCP server."""
 2 | 
 3 | import logging
 4 | import os
 5 | import sys
 6 | from typing import Literal, cast
 7 | 
 8 | import click
 9 | 
10 | from .server import mcp
11 | 
12 | 
13 | @click.command()
14 | @click.option(
15 |     "--transport",
16 |     default="stdio",
17 |     type=click.Choice(["stdio", "sse"]),
18 |     help="Transport type (stdio or sse)",
19 | )
20 | @click.option("--host", default="0.0.0.0", help="Host to listen on (for sse)")
21 | @click.option("--port", default=3001, type=int, help="Port to listen on (for sse)")
22 | @click.option(
23 |     "-v",
24 |     "--verbose",
25 |     count=True,
26 |     help="Verbosity level, use -v or -vv",
27 | )
28 | def main(
29 |     transport,
30 |     host,
31 |     port,
32 |     verbose,
33 | ):
34 |     """
35 |     Generate conventional commit messages from your staged git changes using
36 |     Model Context Protocol (MCP).
37 |     """
38 |     logging_level = logging.WARN
39 |     if verbose == 1:
40 |         logging_level = logging.INFO
41 |     elif verbose >= 2:
42 |         logging_level = logging.DEBUG
43 | 
44 |     logging.basicConfig(level=logging_level, stream=sys.stderr)
45 |     logger = logging.getLogger(__name__)
46 |     logger.info(
47 |         "Starting MCP server with transport type: %s, verbosity level: %d",
48 |         transport,
49 |         verbose,
50 |     )
51 | 
52 |     allowed_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
53 |     env_log_level = cast(
54 |         Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
55 |         os.environ.get("LOG_LEVEL", "DEBUG"),
56 |     )
57 |     mcp.settings.log_level = (
58 |         env_log_level if env_log_level in allowed_levels else "DEBUG"
59 |     )
60 |     if transport == "sse":
61 |         mcp.settings.port = port
62 |         mcp.settings.host = host
63 |         mcp.run(transport="sse")
64 |     elif transport == "stdio":
65 |         mcp.run(transport="stdio")
66 |     else:
67 |         logger.error("Invalid transport type. Use 'sse' or 'stdio'.")
68 |         sys.exit(1)
69 | 
70 | 
71 | if __name__ == "__main__":
72 |     main(transport="stdio", host=None, port=None, verbose=False)
73 | 
```

--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "version": "2.0.0",
 3 |   "tasks": [
 4 |     {
 5 |       "label": "Start MCP Server",
 6 |       "type": "shell",
 7 |       "command": "${command:python.interpreterPath}",
 8 |       "args": [
 9 |         "-m",
10 |         "debugpy",
11 |         "--listen",
12 |         "localhost:5678",
13 |         "-m",
14 |         "mcp_git_commit_generator",
15 |         "--transport",
16 |         "sse"
17 |       ],
18 |       "isBackground": true,
19 |       "options": {
20 |         "cwd": "${workspaceFolder}",
21 |         "env": {
22 |           "PORT": "3001"
23 |         }
24 |       },
25 |       "problemMatcher": {
26 |         "pattern": [
27 |           {
28 |             "regexp": "^.*$",
29 |             "file": 0,
30 |             "location": 1,
31 |             "message": 2
32 |           }
33 |         ],
34 |         "background": {
35 |           "activeOnStart": true,
36 |           "beginsPattern": ".*",
37 |           "endsPattern": "Application startup complete|running"
38 |         }
39 |       }
40 |     },
41 |     {
42 |       "label": "Start MCP Inspector",
43 |       "type": "shell",
44 |       "command": "npm run dev:inspector",
45 |       "isBackground": true,
46 |       "options": {
47 |         "cwd": "${workspaceFolder}/inspector",
48 |         "env": {
49 |           "CLIENT_PORT": "5173",
50 |           "SERVER_PORT": "3000"
51 |         }
52 |       },
53 |       "problemMatcher": {
54 |         "pattern": [
55 |           {
56 |             "regexp": "^.*$",
57 |             "file": 0,
58 |             "location": 1,
59 |             "message": 2
60 |           }
61 |         ],
62 |         "background": {
63 |           "activeOnStart": true,
64 |           "beginsPattern": "Starting MCP inspector",
65 |           "endsPattern": "⚙️ Proxy server listening on"
66 |         }
67 |       },
68 |       "dependsOn": ["Start MCP Server"]
69 |     },
70 |     {
71 |       "label": "Terminate All Tasks",
72 |       "command": "echo 'Terminating all tasks...'",
73 |       "type": "shell",
74 |       "problemMatcher": []
75 |     }
76 |   ],
77 |   "inputs": [
78 |     {
79 |       "id": "terminate",
80 |       "type": "command",
81 |       "command": "workbench.action.tasks.terminate",
82 |       "args": "terminateAll"
83 |     }
84 |   ]
85 | }
86 | 
```

--------------------------------------------------------------------------------
/.github/copilot-instructions.md:
--------------------------------------------------------------------------------

```markdown
 1 | # MCP Git Commit Generator: AI Agent Guide
 2 | 
 3 | ## Project Overview
 4 | 
 5 | This MCP server generates conventional commit messages from staged git changes. It exposes two main MCP tools:
 6 | 
 7 | - `generate_commit_message`: Analyzes staged changes and generates a conventional commit message. Commit type and scope can be auto-detected or provided.
 8 | - `check_git_status`: Reports staged, unstaged, and untracked files with clear validation of the git repository path.
 9 | 
10 | ## Architecture & Key Files
11 | 
12 | - **MCP Tool Logic**: All tool logic is in `src/mcp_git_commit_generator/server.py` using the `@mcp.tool()` decorator. Helper functions like `_get_valid_repo_path` and robust error handling with `subprocess.run` are standard.
13 | - **CLI Entrypoint**: `src/mcp_git_commit_generator/__init__.py` enforces strict `--option value` usage for all CLI arguments (never use positional args).
14 | - **Module Launcher**: `src/mcp_git_commit_generator/__main__.py` bootstraps the server for `python -m` and CLI use.
15 | - **Inspector UI**: The `inspector/` directory contains a Node.js-based tool for interactive MCP tool testing (`npm run dev:inspector`).
16 | 
17 | ## Developer Workflow & Commands
18 | 
19 | ### Environment Setup
20 | 
21 | - **Recommended**: `uv venv` → `uv pip install -r pyproject.toml --group dev` → `uv pip install -e .`
22 | - **Alternative**: `python -m venv .venv` → `pip install -e . && pip install -r requirements-dev.txt`
23 | 
24 | ### Running & Debugging
25 | 
26 | - **VS Code Tasks**: Use "Start MCP Server" (SSE, port 5678) and "Start MCP Inspector" for local development. Inspector UI runs at `http://localhost:5173`.
27 | - **Local CLI**: Run with `mcp-git-commit-generator --transport sse` or `python -m mcp_git_commit_generator --transport sse` (always use `--option value`).
28 | - **Docker**: Build with `docker build -t mcp-git-commit-generator .`. Run with:
29 |   - Default: `docker run -i --rm --mount type=bind,src=${HOME},dst=${HOME} mcp-git-commit-generator`
30 |   - SSE: `docker run -d -p 3001:3001 --entrypoint mcp-git-commit-generator ... --transport sse --host 0.0.0.0 --port 3001`
31 | 
32 | ### Inspector UI Workflow
33 | 
34 | 1. Start the server (see above)
35 | 2. From `inspector/`, run `npm run dev:inspector`
36 | 3. Open `http://localhost:5173` and connect to the running MCP server
37 | 4. Use the UI to list, invoke, and debug tools interactively
38 | 
39 | ## Project-Specific Conventions & Patterns
40 | 
41 | - **CLI Usage**: Always use `--option value` for all arguments. Positional arguments are not supported and will error.
42 | - **MCP Tool Definitions**: Define new tools in `server.py` with detailed docstrings and robust error handling. Follow the pattern of `generate_commit_message` and `check_git_status`.
43 | - **Git Operations**: Always validate the repo path with `_get_valid_repo_path`. Use `subprocess.run` for all git commands, with structured error handling.
44 | - **Testing**: Run tests with `pytest`, e.g. `pytest -v` or `pytest --cov=src/mcp_git_commit_generator`.
45 | - **Versioning**: Update `pyproject.toml` for releases. Docker images are published on tag pushes starting with "v".
46 | 
47 | ## Integration Points & Communication
48 | 
49 | - **MCP Clients**: Integrates with VS Code, Cursor, Windsurf, and Claude Desktop. See README for config examples.
50 | - **Transports**: Supports both `stdio` and `sse` (for Inspector UI and remote access). Match your client config to the server mode.
51 | 
52 | ## Example Usage
53 | 
54 | 1. Stage changes: `git add <files>`
55 | 2. Run `check_git_status` to verify the current git state
56 | 3. Generate a commit message with `generate_commit_message`
57 | 4. Commit using the generated message
58 | 
59 | ## Key Patterns for AI Agents
60 | 
61 | - **Focus on Key Files**: Use `server.py` for tool logic, `__init__.py` for CLI conventions
62 | - **Agent Guidance**: When building new features, follow the code and docstring patterns in this guide. Use inline comments like `// ...existing code...` to contextualize changes
63 | - **Integration**: Ensure new code fits the established patterns for git ops, Docker, and Inspector UI
64 | 
65 | ## Feedback & Iteration
66 | 
67 | Please review these instructions and provide feedback on any unclear or incomplete sections so they can be improved.
68 | 
```

--------------------------------------------------------------------------------
/tests/test_server.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Unit tests for the MCP Git Commit Generator server tools.
  3 | """
  4 | 
  5 | import subprocess
  6 | 
  7 | from mcp_git_commit_generator import server
  8 | 
  9 | 
 10 | def test_generate_commit_message_invalid_repo():
 11 |     """Test that an invalid repo path returns an error message."""
 12 |     result = server.generate_commit_message(repo_path="/not/a/repo")
 13 |     assert "not a valid git repository" in result
 14 | 
 15 | 
 16 | def test_check_git_status_invalid_repo():
 17 |     """Test that an invalid repo path returns an error message for git status."""
 18 |     result = server.check_git_status(repo_path="/not/a/repo")
 19 |     assert "not a valid git repository" in result
 20 | 
 21 | 
 22 | def test_generate_commit_message_no_staged_changes(tmp_path):
 23 |     """Test that no staged changes returns the appropriate message."""
 24 |     repo_dir = tmp_path / "repo"
 25 |     repo_dir.mkdir()
 26 |     subprocess.run(["git", "init"], cwd=repo_dir, check=True)
 27 |     result = server.generate_commit_message(repo_path=str(repo_dir))
 28 |     assert "No staged changes found" in result
 29 | 
 30 | 
 31 | def test_check_git_status_clean_repo(tmp_path):
 32 |     """Test that a clean repo returns the correct status message."""
 33 |     repo_dir = tmp_path / "repo"
 34 |     repo_dir.mkdir()
 35 |     subprocess.run(["git", "init"], cwd=repo_dir, check=True)
 36 |     result = server.check_git_status(repo_path=str(repo_dir))
 37 |     assert "No changes to commit" in result
 38 | 
 39 | 
 40 | def test_generate_commit_message_with_staged_change(tmp_path):
 41 |     """Test that a staged file produces a commit message analysis."""
 42 |     repo_dir = tmp_path / "repo"
 43 |     repo_dir.mkdir()
 44 |     subprocess.run(["git", "init"], cwd=repo_dir, check=True)
 45 |     file_path = repo_dir / "foo.txt"
 46 |     file_path.write_text("hello world\n")
 47 |     subprocess.run(["git", "add", "foo.txt"], cwd=repo_dir, check=True)
 48 |     result = server.generate_commit_message(repo_path=str(repo_dir))
 49 |     assert "Git Change Analysis for Conventional Commit Message" in result
 50 |     assert "foo.txt" in result
 51 | 
 52 | 
 53 | def test_check_git_status_with_staged_and_unstaged(tmp_path):
 54 |     """Test that both staged and unstaged changes are reported correctly."""
 55 |     repo_dir = tmp_path / "repo"
 56 |     repo_dir.mkdir()
 57 |     subprocess.run(["git", "init"], cwd=repo_dir, check=True)
 58 |     file_path = repo_dir / "bar.txt"
 59 |     file_path.write_text("first\n")
 60 |     subprocess.run(["git", "add", "bar.txt"], cwd=repo_dir, check=True)
 61 |     file_path.write_text("second\n")
 62 |     result = server.check_git_status(repo_path=str(repo_dir))
 63 |     assert "Staged files" in result
 64 |     assert "Unstaged files" in result
 65 |     assert "bar.txt" in result
 66 | 
 67 | 
 68 | def test_check_git_status_with_untracked_files(tmp_path):
 69 |     """Test that untracked files are reported correctly."""
 70 |     repo_dir = tmp_path / "repo"
 71 |     repo_dir.mkdir()
 72 |     subprocess.run(["git", "init"], cwd=repo_dir, check=True)
 73 |     file_path = repo_dir / "untracked.txt"
 74 |     file_path.write_text("untracked\n")
 75 |     result = server.check_git_status(repo_path=str(repo_dir))
 76 |     assert "Untracked files" in result
 77 |     assert "untracked.txt" in result
 78 | 
 79 | 
 80 | def test_generate_commit_message_multiple_files_staged(tmp_path):
 81 |     """Test commit message analysis with multiple files staged."""
 82 |     repo_dir = tmp_path / "repo"
 83 |     repo_dir.mkdir()
 84 |     subprocess.run(["git", "init"], cwd=repo_dir, check=True)
 85 |     file1 = repo_dir / "a.txt"
 86 |     file2 = repo_dir / "b.txt"
 87 |     file1.write_text("A\n")
 88 |     file2.write_text("B\n")
 89 |     subprocess.run(["git", "add", "a.txt", "b.txt"], cwd=repo_dir, check=True)
 90 |     result = server.generate_commit_message(repo_path=str(repo_dir))
 91 |     assert "a.txt" in result and "b.txt" in result
 92 | 
 93 | 
 94 | def test_check_git_status_with_staged_deletion(tmp_path):
 95 |     """Test that staged file deletions are reported correctly."""
 96 |     repo_dir = tmp_path / "repo"
 97 |     repo_dir.mkdir()
 98 |     subprocess.run(["git", "init"], cwd=repo_dir, check=True)
 99 |     file_path = repo_dir / "delete_me.txt"
100 |     file_path.write_text("bye\n")
101 |     subprocess.run(["git", "add", "delete_me.txt"], cwd=repo_dir, check=True)
102 |     subprocess.run(["git", "commit", "-m", "add file"], cwd=repo_dir, check=True)
103 |     subprocess.run(["git", "rm", "delete_me.txt"], cwd=repo_dir, check=True)
104 |     result = server.check_git_status(repo_path=str(repo_dir))
105 |     print(result)
106 |     assert "delete_me.txt" in result
107 |     assert "Staged files" in result
108 | 
109 | 
110 | def test_generate_commit_message_with_type_and_scope(tmp_path):
111 |     """Test generate_commit_message with explicit commit_type and scope."""
112 |     repo_dir = tmp_path / "repo"
113 |     repo_dir.mkdir()
114 |     subprocess.run(["git", "init"], cwd=repo_dir, check=True)
115 |     file_path = repo_dir / "scoped.txt"
116 |     file_path.write_text("scoped\n")
117 |     subprocess.run(["git", "add", "scoped.txt"], cwd=repo_dir, check=True)
118 |     result = server.generate_commit_message(
119 |         repo_path=str(repo_dir), commit_type="feat", scope="core"
120 |     )
121 |     assert "Requested commit type: feat" in result
122 |     assert "Requested scope: core" in result
123 | 
124 | 
125 | def test_generate_commit_message_breaking_change_prompt(tmp_path):
126 |     """Test that the breaking change footer instructions are present in the prompt."""
127 |     repo_dir = tmp_path / "repo"
128 |     repo_dir.mkdir()
129 |     subprocess.run(["git", "init"], cwd=repo_dir, check=True)
130 |     file_path = repo_dir / "breaking.txt"
131 |     file_path.write_text("breaking change\n")
132 |     subprocess.run(["git", "add", "breaking.txt"], cwd=repo_dir, check=True)
133 |     result = server.generate_commit_message(repo_path=str(repo_dir))
134 |     assert "BREAKING CHANGE" in result
135 | 
```

--------------------------------------------------------------------------------
/src/mcp_git_commit_generator/server.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | FastMCP Server for generating conventional commit messages from git diff
  3 | """
  4 | 
  5 | import logging
  6 | import os
  7 | import subprocess
  8 | import textwrap
  9 | from typing import Optional
 10 | 
 11 | from mcp.server.fastmcp import FastMCP
 12 | 
 13 | # Create the FastMCP server
 14 | mcp = FastMCP("Git Commit Generator")
 15 | 
 16 | 
 17 | def _get_valid_repo_path(repo_path: Optional[str]) -> Optional[str]:
 18 |     """
 19 |     Resolve and validate the git repository path.
 20 |     Returns the valid repo path if valid, otherwise None.
 21 |     """
 22 |     logger = logging.getLogger(__name__)
 23 |     # Resolve user tilde and symlinks to a canonical path
 24 |     resolved = (
 25 |         os.path.realpath(os.path.expanduser(repo_path)) if repo_path else os.getcwd()
 26 |     )
 27 |     logger.info("[get_valid_repo_path] Resolved repository path: %s", resolved)
 28 |     if not os.path.isdir(resolved) or not os.path.exists(
 29 |         os.path.join(resolved, ".git")
 30 |     ):
 31 |         return None
 32 |     return resolved
 33 | 
 34 | 
 35 | @mcp.tool()
 36 | def generate_commit_message(
 37 |     repo_path: Optional[str] = None,
 38 |     commit_type: Optional[str] = None,
 39 |     scope: Optional[str] = None,
 40 | ) -> str:
 41 |     """
 42 |     Prepare a structured analysis and instruction block for generating a
 43 |     Conventional Commit message from staged git changes only.
 44 | 
 45 |     Behavior:
 46 |         - Validates the repository path and operates on the provided repo or CWD.
 47 |         - Collects staged diff, porcelain status, and a name-status summary.
 48 |         - Incorporates optional user preferences for commit_type and scope.
 49 |         - Returns a single formatted string that includes context plus strict
 50 |           output instructions for an LLM to produce a Conventional Commit.
 51 | 
 52 |     Args:
 53 |         repo_path: Optional path to the target git repository. If not provided, uses the current working directory.
 54 |         commit_type: Optional commit type (feat, fix, docs, style, refactor, perf, build, ci, test, chore, revert)
 55 |         scope: Optional scope of the change
 56 | 
 57 |     Returns:
 58 |         A formatted prompt containing git change context and clear output rules
 59 |         for generating a Conventional Commit message
 60 |     """
 61 |     try:
 62 |         valid_repo_path = _get_valid_repo_path(repo_path)
 63 |         if not valid_repo_path:
 64 |             return f"Path '{repo_path or os.getcwd()}' is not a valid git repository."
 65 |         cwd = valid_repo_path
 66 |         # Get staged changes
 67 |         diff_result = subprocess.run(
 68 |             ["git", "diff", "--cached"],
 69 |             capture_output=True,
 70 |             text=True,
 71 |             check=True,
 72 |             cwd=cwd,
 73 |         )
 74 | 
 75 |         if not diff_result.stdout.strip():
 76 |             return "No staged changes found. Please stage your changes with 'git add' first."
 77 | 
 78 |         # Get git status for context
 79 |         status_result = subprocess.run(
 80 |             ["git", "status", "--porcelain"],
 81 |             capture_output=True,
 82 |             text=True,
 83 |             check=True,
 84 |             cwd=cwd,
 85 |         )
 86 | 
 87 |         # Get list of changed files for better analysis
 88 |         files_result = subprocess.run(
 89 |             ["git", "diff", "--cached", "--name-status"],
 90 |             capture_output=True,
 91 |             text=True,
 92 |             check=True,
 93 |             cwd=cwd,
 94 |         )
 95 | 
 96 |         diff_preview = diff_result.stdout[:1500]
 97 |         analysis = textwrap.dedent(f"""
 98 |         ## Git Change Analysis for Conventional Commit Message
 99 | 
100 |         ### Changed Files:
101 |         {files_result.stdout}
102 | 
103 |         ### File Status Summary:
104 |         {status_result.stdout}
105 | 
106 |         ### Diff Preview (first 1500 chars):
107 |         {diff_preview}
108 | 
109 |         ### User Preferences:
110 |             - Requested commit type: {commit_type or "auto-detect based on changes"}
111 |             - Requested scope: {scope or "auto-detect based on files changed"}
112 | 
113 |         ### Task
114 |         Write a Conventional Commit message for the STAGED changes only.
115 | 
116 |         ### Output format (return ONLY this)
117 |         First line: type(scope): subject
118 |         Add blank line before body
119 |         Body paragraphs, each line <= 72 chars; bullets in body starting with "- "
120 |         Optional footers (each on its own line), e.g.:
121 |         BREAKING CHANGE: description
122 | 
123 |         ### Example generated commit message
124 |         feat(core): add new feature
125 | 
126 |         - Implement new feature in core module
127 |         - Update documentation
128 | 
129 |         BREAKING CHANGE: this change removes the old API method
130 | 
131 |         ### Rules
132 |         - If commit_type or scope is provided above, USE THEM as-is.
133 |         - If not provided, infer an appropriate type and a concise scope (or omit scope if unclear).
134 |         - Subject: use imperative mood, start lowercase, no trailing period, <= 50 chars.
135 |         - Body: use imperative mood (e.g. Update, Add etc.); explain WHAT and WHY, wrap at 72 chars; omit if subject suffices.
136 |         - Use domain-specific terms; avoid generic phrases.
137 |         - Do NOT mention "staged", "diff", or counts of files/lines.
138 |         - Do NOT include markdown headers, code fences, or extra commentary.
139 |         - Prefer a broad scope if many files; derive scope from top-level dirs when clear.
140 |         - If there is a breaking change (e.g., API removal/rename), add a BREAKING CHANGE footer.
141 |         - Keep the response to ONLY the commit message in the format above.
142 | 
143 |         ### Common types
144 |         feat, fix, docs, style, refactor, perf, build, ci, test, chore, revert
145 |         """)
146 | 
147 |         return analysis.strip()
148 | 
149 |     except subprocess.CalledProcessError as e:
150 |         error_msg = e.stderr or e.stdout or str(e)
151 |         return f"Git command failed: {error_msg}"
152 |     except FileNotFoundError:
153 |         return "Git is not installed or not found in PATH"
154 |     except OSError as e:
155 |         return f"OS error occurred: {str(e)}"
156 | 
157 | 
158 | def _parse_git_status_line(line):
159 |     """
160 |     Helper to parse a single git status line.
161 |     Returns (staged_file, unstaged_file, untracked_file)
162 |     """
163 |     if len(line) < 3:
164 |         return None, None, None
165 |     staged_status = line[0]
166 |     unstaged_status = line[1]
167 |     filename = line[3:]
168 |     if staged_status == "?" and unstaged_status == "?":
169 |         return None, None, filename
170 |     staged_file = filename if staged_status != " " else None
171 |     unstaged_file = filename if unstaged_status != " " else None
172 |     return staged_file, unstaged_file, None
173 | 
174 | 
175 | def _parse_git_status_lines(status_lines):
176 |     staged_files = []
177 |     unstaged_files = []
178 |     untracked_files = []
179 |     for line in status_lines:
180 |         staged_file, unstaged_file, untracked_file = _parse_git_status_line(line)
181 |         if staged_file:
182 |             staged_files.append(staged_file)
183 |         if unstaged_file:
184 |             unstaged_files.append(unstaged_file)
185 |         if untracked_file:
186 |             untracked_files.append(untracked_file)
187 |     return staged_files, unstaged_files, untracked_files
188 | 
189 | 
190 | @mcp.tool()
191 | def check_git_status(repo_path: Optional[str] = None) -> str:
192 |     """
193 |     Check the current git repository status.
194 | 
195 |     Args:
196 |         repo_path: Optional path to the target git repository. If not provided, uses the current working directory.
197 | 
198 |     Returns:
199 |         Current git status including staged, unstaged, and untracked files
200 |     """
201 |     try:
202 |         valid_repo_path = _get_valid_repo_path(repo_path)
203 |         if not valid_repo_path:
204 |             return f"Path '{repo_path or os.getcwd()}' is not a valid git repository."
205 |         cwd = valid_repo_path
206 |         # Get full git status
207 |         status_result = subprocess.run(
208 |             ["git", "status", "--porcelain"],
209 |             capture_output=True,
210 |             text=True,
211 |             check=True,
212 |             cwd=cwd,
213 |         )
214 | 
215 |         # Get branch info
216 |         branch_result = subprocess.run(
217 |             ["git", "branch", "--show-current"],
218 |             capture_output=True,
219 |             text=True,
220 |             check=True,
221 |             cwd=cwd,
222 |         )
223 | 
224 |         current_branch = branch_result.stdout.strip()
225 | 
226 |         if not status_result.stdout.strip():
227 |             return f"Repository is clean on branch '{current_branch}'. No changes to commit."
228 | 
229 |         # Parse status using helper
230 |         status_lines = [line for line in status_result.stdout.split("\n") if line]
231 |         staged_files, unstaged_files, untracked_files = _parse_git_status_lines(
232 |             status_lines
233 |         )
234 | 
235 |         status_summary = f"Current branch: {current_branch}\n\n"
236 | 
237 |         if staged_files:
238 |             status_summary += "Staged files (ready to commit):\n"
239 |             status_summary += "\n".join(f"  {file}" for file in staged_files)
240 |             status_summary += "\n\n"
241 | 
242 |         if unstaged_files:
243 |             status_summary += "Unstaged files (need to be added):\n"
244 |             status_summary += "\n".join(f"  {file}" for file in unstaged_files)
245 |             status_summary += "\n\n"
246 | 
247 |         if untracked_files:
248 |             status_summary += "Untracked files:\n"
249 |             status_summary += "\n".join(f"  {file}" for file in untracked_files)
250 |             status_summary += "\n\n"
251 | 
252 |         if staged_files:
253 |             status_summary += "✓ Ready to generate commit message!"
254 |         else:
255 |             status_summary += (
256 |                 "ℹ Stage some files with 'git add' to generate commit messages."
257 |             )
258 | 
259 |         return status_summary
260 | 
261 |     except subprocess.CalledProcessError as e:
262 |         error_msg = e.stderr or e.stdout or str(e)
263 |         return f"Git command failed: {error_msg}"
264 |     except FileNotFoundError:
265 |         return "Git is not installed or not found in PATH"
266 |     except OSError as e:
267 |         return f"OS error occurred: {str(e)}"
268 | 
269 | 
270 | if __name__ == "__main__":
271 |     mcp.run()
272 | 
```