Skip to main content
Glama
README.md
# ai-git-mcp

An MCP server that reimplements the [`ai-git-fish`](https://github.com/DucTam2411/ai-git-fish)
workflows — `aicommit`, `aibranch`, `aipr` — with Leantime integration, **without fish shell and
without DeepSeek**. Wire it up once and the same workflows work in **Claude Code** and **Codex**.

- [Why no AI inside the server?](#why-no-ai-inside-the-server)
- [Requirements](#requirements)
- [Installation](#installation)
  - [1. Get your Leantime credentials](#1-get-your-leantime-credentials)
  - [2. Add the server to your client](#2-add-the-server-to-your-client)
  - [3. Restart and verify](#3-restart-and-verify)
- [Configuration (env)](#configuration-env)
- [Usage — slash commands](#usage--slash-commands)
- [Tools reference](#tools-reference)
- [Troubleshooting](#troubleshooting)
- [Local development](#local-development)

## Why no AI inside the server?

`ai-git-fish` calls DeepSeek to generate commit messages and PR text. But Claude Code and Codex
**are already the AI**. So this server contains **no LLM**: every tool is deterministic. The agent
reads the diff/ticket through a tool, writes the prose itself, and passes it into the next tool.
No paying for AI twice, no losing context, and the agent understands your code better than DeepSeek.

```
Agent (Claude/Codex)  ──calls tools──>  ai-git-mcp  ──>  git / gh CLI
        │                                    │
        └── writes commit/PR/slug            └──>  Leantime JSON-RPC (/api/jsonrpc)
```

## Requirements

On the machine that runs the agent:

- **Node.js ≥ 18** (`node -v`)
- **git** in `PATH` (`git --version`)
- **GitHub CLI `gh`**, authenticated (`gh auth login`) — needed only for `aipr`
- A **Leantime** instance + API key — needed only for the ticket tools

You do **not** need to clone this repo. `npx` fetches and builds it for you (see below).

## Installation

### 1. Get your Leantime credentials

| Value | Where to find it |
|-|-|
| `LEANTIME_BASE_URL` | Your Leantime URL, e.g. `https://leantime.example.com` |
| `LEANTIME_API_KEY` | Leantime → your profile → **API Keys** → generate one |
| `LEANTIME_USER_ID` | Your numeric user id — see the tip below |

> **Finding your `LEANTIME_USER_ID` the easy way** (replaces ai-git-fish's installer fzf picker):
> set just `LEANTIME_BASE_URL` + `LEANTIME_API_KEY` first, restart, then ask the agent to run
> **`leantime_list_users`**. It prints every user with their `id` — copy yours into the env and
> restart. (Or: open your profile and read the id from the URL, e.g. `.../users/editUser/5` → `5`.)

### 2. Add the server to your client

Pick your client. The `npx -y github:BenBen2109/ai-git-mcp` command downloads this repo, builds
it once, caches it, and runs it — no manual clone or build.

<details open>
<summary><b>Claude Code</b></summary>

**Option A — one command:**

```bash
claude mcp add ai-git \
  -e LEANTIME_BASE_URL=https://leantime.example.com \
  -e LEANTIME_API_KEY=your-key \
  -e LEANTIME_USER_ID=123 \
  -- npx -y github:BenBen2109/ai-git-mcp
```

**Option B — edit `.mcp.json`** (project root) or `~/.claude.json` (global):

```json
{
  "mcpServers": {
    "ai-git": {
      "command": "npx",
      "args": ["-y", "github:BenBen2109/ai-git-mcp"],
      "env": {
        "LEANTIME_BASE_URL": "https://leantime.example.com",
        "LEANTIME_API_KEY": "your-key",
        "LEANTIME_USER_ID": "123"
      }
    }
  }
}
```

</details>

<details open>
<summary><b>Codex</b></summary>

Edit `~/.codex/config.toml`:

```toml
[mcp_servers.ai-git]
command = "npx"
args = ["-y", "github:BenBen2109/ai-git-mcp"]
env = { LEANTIME_BASE_URL = "https://leantime.example.com", LEANTIME_API_KEY = "your-key", LEANTIME_USER_ID = "123" }
```

</details>

> **Pin a version** by appending a tag/branch/commit: `github:BenBen2109/ai-git-mcp#v1.0.0`.
> The first launch is slower (it builds once); later launches use the npx cache.

### 3. Restart and verify

Restart the client (or reload the MCP config). Then check the server is connected:

- **Claude Code:** run `/mcp` — `ai-git` should show **14 tools** and **4 prompts**.
- **Codex:** the `ai-git` tools appear in the tool list at startup.

## Configuration (env)

| Variable | Required | Meaning |
|-|-|-|
| `LEANTIME_BASE_URL` | ticket tools | e.g. `https://leantime.example.com` |
| `LEANTIME_API_KEY` | ticket tools | sent as the `x-api-key` header |
| `LEANTIME_USER_ID` | recommended | numeric id used to filter *your* tickets |
| `LEANTIME_PICK_LIMIT` | optional | max tickets listed (default 30) |
| `SLACK_WEBHOOK_URL` | Slack tools | Incoming Webhook URL; the channel is fixed on the Slack side |
| `PR_PREVIEW_URL_TEMPLATE` | optional | Deploy-preview URL with a `{pr}` placeholder, e.g. `https://frontend-preview-{pr}--anymatemeproject.netlify.app`. Adds a Preview line to the Slack PR message |
| `SLACK_USER_MAP` | optional | JSON map of name/login → Slack member ID, e.g. `{"khanhld":"U012ABC"}`, for @-mentions |
| `SLACK_PR_REVIEWERS` | optional | Who to @-mention on every PR — comma list of names (from the map), raw Slack ids, or `here`/`channel` |

The git-only tools (`git_status`, `git_staged_diff`, `git_commit`, `git_log_for_pr`,
`git_create_branch`, `git_create_or_update_pr`, `git_undo_last_commit`) work **without any Leantime
config**. See [`.env.example`](.env.example).

## Usage — slash commands

Three workflows, exposed as MCP prompts. In **Claude Code** type the slash command; in **Codex**
just say it in plain language (e.g. *"run aicommit"*) — the agent calls the same tools either way.

### `/ai-git:aicommit [hint]` — stage-aware commit

Reads your staged diff, writes a Conventional Commit message, shows it to you, then commits.

```
# stage what you want first
git add -p

# then, in the agent:
/ai-git:aicommit
/ai-git:aicommit drop the debug logging   # optional hint to steer the message
```

→ `git_staged_diff` → *(agent writes message)* → `git_commit`

### `/ai-git:aibranch [type]` — branch from a Leantime ticket

Lists your tickets, you pick one, it creates/switches to `type/<id>-<slug>` (slug auto-derived
from the ticket headline, Vietnamese diacritics handled).

```
/ai-git:aibranch          # type defaults to "feat" → feat/1234-add-login
/ai-git:aibranch fix      # → fix/1234-add-login
```

→ `leantime_list_my_tickets` → *(you pick)* → `git_create_branch`

If `LEANTIME_USER_ID` isn't set (or you want someone else's tickets), the agent calls
`leantime_list_users` first, you pick a person, and it lists that user's tickets via `assignee_id`
— the equivalent of ai-git-fish's fzf user picker.

### `/ai-git:aipr [hint]` — open or update a GitHub PR

Pushes the branch and creates (or updates) its PR with an AI-written title and body. If the branch
is named `type/<id>-<slug>`, the Leantime ticket is auto-linked and embedded.

```
/ai-git:aipr
/ai-git:aipr focus on the migration   # optional hint to steer the PR
```

→ `git_status` + `git_log_for_pr` → *(agent writes title/body)* → `git_create_or_update_pr` → `slack_post_pr`

After the PR is created/updated, it sends a formatted notification to the team Slack channel
(via `SLACK_WEBHOOK_URL`). If Slack isn't configured, that step is skipped with a note.

### `/ai-git:aireview <pr> [spec]` — review a PR and post the review

Fetches the PR + diff, reviews it against the team checklist (Functionality, Security, Error
Handling, Code Quality, Performance, Style), writes the review in **English** following a fixed
format, and posts it as a PR comment. Optionally validates against a Redmine spec URL.

```
/ai-git:aireview https://github.com/org/repo/pull/42
/ai-git:aireview https://github.com/org/repo/pull/42 https://redmine.example.com/issues/1234
```

Prefer the **full PR URL** — it tells `gh` which repo the PR is in. A bare number (`42`) only
resolves when `repo_path` is the PR's repository.

→ `git_pr_view` → *(agent reviews, + Redmine spec via the agent's redmine tools if given)* → `git_pr_comment`

Boundaries: never approves or merges, never changes code without approval.

## Tools reference

All tools return JSON. Each accepts an optional `repo_path` (defaults to the server's working
directory) unless noted.

### Leantime

| Tool | Params | Returns |
|-|-|-|
| `leantime_list_users` | — | `{ count, users: [{ id, name, username, isConfigured }] }` (setup helper to find your `LEANTIME_USER_ID`) |
| `leantime_list_my_tickets` | `limit?` (1–100), `assignee_id?` | `{ count, assigneeId, filtered, tickets: [{ id, headline, statusLabel, projectName, assignee, dueDate, url, description }] }` (description is a ~200-char snippet, like the fzf preview; `filtered:false` means no `LEANTIME_USER_ID` and no `assignee_id` → all tickets) |
| `leantime_get_ticket` | `id` | `{ found, id, headline, description, statusLabel, dueDate, url, markdown }` |

### Git — read-only

| Tool | Params | Returns |
|-|-|-|
| `git_status` | `repo_path?` | `{ branch, ahead, behind, staged[], unstaged[], untracked[] }` |
| `git_staged_diff` | `repo_path?`, `max_bytes?` | `{ diff, files: [{ path, status }], isEmpty, truncated }` |
| `git_log_for_pr` | `base?`, `repo_path?` | `{ base, count, commits: [{ sha, subject, body }] }` |
| `git_pr_view` | `pr`, `max_bytes?`, `repo_path?` | `{ number, title, body, state, author, baseRefName, headRefName, url, additions, deletions, changedFiles, files[], diff, diffTruncated }` |

### Git — write

| Tool | Params | Returns |
|-|-|-|
| `git_commit` | `message`, `repo_path?` | `{ sha, shortStat }` |
| `git_create_branch` | `ticket_id`, `type?`, `slug?`, `repo_path?` | `{ branch, created }` |
| `git_create_or_update_pr` | `title`, `body`, `ticket_id?`, `base?`, `draft?`, `repo_path?` | `{ url, number, action }` |
| `git_pr_comment` | `pr`, `body`, `repo_path?` | `{ url }` — posts a comment (e.g. a review); never approves/merges |
| `git_undo_last_commit` | `repo_path?` | `{ undoneSha, message }` |

### Slack

| Tool | Params | Returns |
|-|-|-|
| `slack_post_pr` | `title`, `url`, `number?`, `author?`, `ticket_id?`, `mention?` | `{ ok }` — posts Author / PR / Preview / Ticket / Feat / Reviewers (@-mentions) to the webhook channel |
| `slack_send_message` | `text`, `mention?` | `{ ok }` — plain Slack mrkdwn message, optional @-mentions |

**@-mentions:** Slack only pings by member id (`<@U…>`), not by display name. Set `SLACK_USER_MAP`
to map names → ids, then list reviewers in `SLACK_PR_REVIEWERS` (or pass `mention` per call).
`here`/`channel` work without any map.

Notes:
- `git_commit` fails with an actionable message if nothing is staged.
- `git_create_branch` auto-derives the slug from the ticket headline when `slug` is omitted.
- `git_create_or_update_pr` pushes `HEAD`, detects an existing PR for the branch (edits it) or
  creates a new one, and appends the ticket markdown block when `ticket_id` is given.
- `git_undo_last_commit` is a soft reset — your changes stay staged, nothing is lost.

## Troubleshooting

| Symptom | Fix |
|-|-|
| `Leantime is not configured` | Set `LEANTIME_BASE_URL` + `LEANTIME_API_KEY` in the server `env`. |
| `Not a git repository` | Run the agent from inside a git repo, or pass `repo_path`. |
| `Nothing staged to commit` | `git add` your changes first. |
| `gh ... failed` on `aipr` | Run `gh auth login`; ensure the repo has a GitHub remote. |
| `npx` is slow on first run | Expected — it builds once, then caches. Pin a tag to stabilise. |
| Slash commands missing in Codex | Codex prompt support varies; just say *"run aicommit"* instead. |

## Local development

```bash
git clone https://github.com/BenBen2109/ai-git-mcp
cd ai-git-mcp
npm install
npm run build      # → dist/index.js
npm test           # vitest
npm run dev        # tsx watch (live reload)
```

Local-path config (instead of `npx`):

```json
{ "mcpServers": { "ai-git": {
  "command": "node",
  "args": ["/absolute/path/to/ai-git-mcp/dist/index.js"],
  "env": { "LEANTIME_BASE_URL": "...", "LEANTIME_API_KEY": "...", "LEANTIME_USER_ID": "..." }
}}}
```

## Credits

Leantime client and ticket formatting (HTML/entity decoding, status labels, PR markdown block)
are ported from [`ai-git-fish`](https://github.com/DucTam2411/ai-git-fish) by DucTam2411.

TDQS

A4.6/5.0

Scored across 9 tools

Disambiguation5/5

All 9 tools have distinct, non-overlapping purposes. Git operations (commit, branch, PR, diff, status, undo, log) are clearly separated from Leantime operations (get ticket, list tickets). No ambiguity in tool selection.

Naming Consistency5/5

All tools use snake_case with a consistent `<domain>_<verb>_<noun>` pattern (git_* and leantime_*). Verbs like create, get, list, log, commit, undo are used uniformly, making the naming predictable and easy to understand.

Tool Count5/5

9 tools is well-scoped for the integrated git+Leantime workflow. Each tool serves an essential step in the aibranch, aicommit, and aipr workflows without unnecessary duplication or bloat.

Completeness5/5

The tool set covers the entire intended workflow: branch creation from tickets, staging diff review, committing, PR log generation, PR creation/update, status checks, undo, and ticket retrieval. No critical gaps for the described use cases.

Maintenance

ActivityInactive
ResponsivenessNo issues