mcp-gitlab 0.9.2__tar.gz → 0.9.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/.gitignore +3 -0
- mcp_gitlab-0.9.4/AGENTS.md +184 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/CHANGELOG.md +18 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/PKG-INFO +4 -4
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/README.md +2 -2
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/gemini-extension.json +1 -1
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/llms-full.txt +4 -4
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/llms.txt +1 -1
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/pyproject.toml +1 -1
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/server.json +3 -3
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/servers/gitlab.py +29 -28
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/tests/unit/test_tools.py +112 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/uv.lock +12 -6
- mcp_gitlab-0.9.2/AGENTS.md +0 -124
- mcp_gitlab-0.9.2/GEMINI.md +0 -33
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/.env.example +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/.github/dependabot.yml +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/.github/workflows/lint.yml +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/.github/workflows/publish.yml +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/.github/workflows/release.yml +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/.github/workflows/tests.yml +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/LICENSE +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/evaluations/eval.xml +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/__init__.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/__main__.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/client.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/config.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/exceptions.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/py.typed +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/resources/approval-workflow.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/resources/code-review.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/resources/codeowners.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/resources/conventional-commits.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/resources/git-workflow.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/resources/gitlab-ci.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/resources/mr-hygiene.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/resources/prompts/approve-mr.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/resources/prompts/diagnose-pipeline.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/resources/prompts/prepare-release.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/resources/prompts/review-mr.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/resources/prompts/setup-branch-protection.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/resources/prompts/triage-issues.md +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/servers/__init__.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/servers/_helpers.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/servers/prompts.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/servers/resources.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/utils/__init__.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/tests/__init__.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/tests/conftest.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/tests/test_links.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/tests/unit/__init__.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/tests/unit/test_client.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/tests/unit/test_config.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/tests/unit/test_exceptions.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/tests/unit/test_prompts.py +0 -0
- {mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/tests/unit/test_resources.py +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# mcp-gitlab — Agent Context
|
|
2
|
+
|
|
3
|
+
MCP server exposing 83 tools, 7 resources, and 6 prompts over the GitLab REST API v4. Covers the
|
|
4
|
+
full project lifecycle: code, reviews, CI/CD, releases, and issue tracking. Works against
|
|
5
|
+
GitLab.com and self-hosted instances.
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
- **Entry point**: `src/mcp_gitlab/__init__.py` — click CLI, loads `.env` via python-dotenv, runs the FastMCP server
|
|
10
|
+
- **Client**: `src/mcp_gitlab/client.py` — async httpx client with all GitLab API methods
|
|
11
|
+
- **Tools**: `src/mcp_gitlab/servers/gitlab.py` — all 83 FastMCP tool registrations
|
|
12
|
+
- **Resources**: `src/mcp_gitlab/servers/resources.py` — 7 MCP resources (4 `resource://rules/*`, 3 `resource://guides/*`), content in `src/mcp_gitlab/resources/*.md`
|
|
13
|
+
- **Prompts**: `src/mcp_gitlab/servers/prompts.py` — 6 MCP prompts (multi-tool workflows)
|
|
14
|
+
- **Helpers**: `src/mcp_gitlab/servers/_helpers.py` — cached file loader with path-traversal guard, plus GitLab URL parsers. Most tools accept a project ID, a path, *or* a full GitLab URL for `project_id`; MR and pipeline URLs also yield the iid/id
|
|
15
|
+
- **Config**: `src/mcp_gitlab/config.py` — `GitLabConfig` dataclass built from env vars
|
|
16
|
+
- **Exceptions**: `src/mcp_gitlab/exceptions.py` — `GitLabError` base; `GitLabApiError`, `GitLabAuthError`, `GitLabNotFoundError`, `GitLabWriteDisabledError`
|
|
17
|
+
- **Tests**: `tests/` — `unit/test_tools.py` (143 tool-level tests via the FastMCP in-memory client), plus `test_client.py`, `test_config.py`, `test_exceptions.py`, `test_prompts.py`, `test_resources.py`, and `tests/test_links.py`. Shared fixtures (`config`, `client`, `mock_api` via respx) live in `tests/conftest.py`
|
|
18
|
+
|
|
19
|
+
## Development
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
uv sync --all-extras
|
|
23
|
+
uv run pytest --cov
|
|
24
|
+
uv run ruff check .
|
|
25
|
+
uv run ruff format --check . # CI runs --check; formatting drift fails the build
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`pytest` uses `asyncio_mode = "auto"` — async tests need no marker. Ruff line length is 100,
|
|
29
|
+
target py310; lint rules include `S` (bandit), `EM`, `N`, `UP`. `tests/**` waives `S101`,
|
|
30
|
+
`S105`, `S106`.
|
|
31
|
+
|
|
32
|
+
## Patterns
|
|
33
|
+
|
|
34
|
+
- All tools are `async def` returning JSON strings
|
|
35
|
+
- `_ok(data)` for success, `_err(e)` for failure; `_paginated(items)` for list responses
|
|
36
|
+
- Pipeline and job payloads are trimmed by `_slim_pipeline` / `_slim_job`; tools expose `slim=True` by default
|
|
37
|
+
- Write access control: `_check_write(ctx)` raises `GitLabWriteDisabledError` when `GITLAB_READ_ONLY=true`
|
|
38
|
+
- Tags: every tool tagged with `{"gitlab", "<category>", "read"|"write"}`
|
|
39
|
+
- Parameters use `Annotated[type, Field(description=...)]`
|
|
40
|
+
- Client uses httpx with `PRIVATE-TOKEN` header auth
|
|
41
|
+
- Project/group IDs can be numeric or URL-encoded paths
|
|
42
|
+
- `gitlab_list_variables` / `gitlab_list_group_variables` return `***MASKED***` for variables GitLab marks as masked
|
|
43
|
+
|
|
44
|
+
## MCP Compliance Rules
|
|
45
|
+
|
|
46
|
+
### Tool annotations (mandatory)
|
|
47
|
+
Every tool MUST have `annotations={}` with at minimum `readOnlyHint`.
|
|
48
|
+
- Read tools: `annotations={"readOnlyHint": True, "idempotentHint": True}`
|
|
49
|
+
- Non-destructive writes: `annotations={"readOnlyHint": False}`
|
|
50
|
+
- Destructive writes: `annotations={"destructiveHint": True, "readOnlyHint": False}`
|
|
51
|
+
- Idempotent writes (PUT/update): add `idempotentHint: True`
|
|
52
|
+
|
|
53
|
+
### Tool descriptions
|
|
54
|
+
1-2 sentences. Front-load what it does AND what it returns.
|
|
55
|
+
- Bad: "This tool gets a merge request."
|
|
56
|
+
- Good: "Get merge request details. Returns title, state, branches, author, diff_refs."
|
|
57
|
+
|
|
58
|
+
### Error handling
|
|
59
|
+
- Every tool MUST wrap in try/except and return `_err(e)` — never raise.
|
|
60
|
+
- Error text MUST be actionable: what went wrong plus a suggested fix.
|
|
61
|
+
- Never return concatenated JSON strings — always a single valid JSON object.
|
|
62
|
+
- Never expose stack traces, tokens, or internal paths.
|
|
63
|
+
|
|
64
|
+
### Parameter design
|
|
65
|
+
- `Annotated[type, Field(description="...")]` on every parameter.
|
|
66
|
+
- `Literal[...]` for known value sets instead of plain `str`.
|
|
67
|
+
- Every optional parameter has a default.
|
|
68
|
+
- Flatten — no nested dicts unless truly necessary.
|
|
69
|
+
|
|
70
|
+
### Read-only mode
|
|
71
|
+
Every write tool MUST call `_check_write(ctx)` before any mutation.
|
|
72
|
+
|
|
73
|
+
### Naming convention
|
|
74
|
+
- Pattern: `gitlab_{verb}_{resource}` (snake_case)
|
|
75
|
+
- Verbs: create, get, list, search, update, delete, merge, rebase, retry, play, cancel, award,
|
|
76
|
+
remove, share, unshare, compare, add, reply, resolve, approve, unapprove, subscribe, unsubscribe
|
|
77
|
+
|
|
78
|
+
## Tool Categories (83)
|
|
79
|
+
|
|
80
|
+
| Category | Count | Operations |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| Projects | 4 | get, create, delete, update merge settings |
|
|
83
|
+
| Approvals | 10 | project-level approval settings; project and MR approval rules (list, create, update, delete) |
|
|
84
|
+
| Groups | 6 | list, get groups; share/unshare project with group; share/unshare group with group |
|
|
85
|
+
| Branches | 3 | list, create, delete |
|
|
86
|
+
| Commits | 4 | list, get, create, compare refs |
|
|
87
|
+
| Merge Requests | 15 | list, get, create, update, merge, merge sequence, rebase, changes/diffs, approve, unapprove, get approvals, list pipelines, list commits, subscribe, unsubscribe |
|
|
88
|
+
| MR Notes | 6 | list, add, update, delete notes; award/remove emoji |
|
|
89
|
+
| MR Discussions | 4 | list, create, reply, resolve |
|
|
90
|
+
| Pipelines | 5 | list, get, create, retry, cancel |
|
|
91
|
+
| Jobs | 4 | retry, play, cancel, get job log |
|
|
92
|
+
| Tags | 4 | list, get, create, delete |
|
|
93
|
+
| Releases | 5 | list, get, create, update, delete |
|
|
94
|
+
| CI/CD Variables | 8 | project and group variables (list, create, update, delete) |
|
|
95
|
+
| Issues | 5 | list, get, create, update, add comment |
|
|
96
|
+
|
|
97
|
+
Share/unshare tools are counted under Groups, not Projects. There is no `gitlab_list_jobs` —
|
|
98
|
+
get job IDs from `gitlab_get_pipeline(..., include_jobs=True)`.
|
|
99
|
+
|
|
100
|
+
## Common Workflows
|
|
101
|
+
|
|
102
|
+
- **Code review**: `gitlab_list_mrs` → `gitlab_mr_changes` → `gitlab_list_mr_discussions` → `gitlab_add_mr_note` or `gitlab_create_mr_discussion` → `gitlab_resolve_discussion`
|
|
103
|
+
- **Pipeline debugging**: `gitlab_list_pipelines` → `gitlab_get_pipeline` (`include_jobs=True`) → `gitlab_get_job_log` → `gitlab_retry_job`
|
|
104
|
+
- **Release**: `gitlab_list_commits` → `gitlab_compare` → `gitlab_create_tag` → `gitlab_create_release`
|
|
105
|
+
- **Branch protection**: `gitlab_list_project_approval_rules` → `gitlab_create_project_approval_rule` → `gitlab_update_project_merge_settings`
|
|
106
|
+
- **Issue triage**: `gitlab_list_issues` → `gitlab_get_issue` → `gitlab_update_issue` → `gitlab_add_issue_comment`
|
|
107
|
+
|
|
108
|
+
## Prompts
|
|
109
|
+
|
|
110
|
+
Prompt content lives as `.md` files in `src/mcp_gitlab/resources/prompts/`, loaded by
|
|
111
|
+
`servers/prompts.py` via `_load_prompt()` (`string.Template.safe_substitute` for parameters) and
|
|
112
|
+
registered with `@mcp.prompt()`. Each returns `list[Message]`: a user message (workflow template)
|
|
113
|
+
plus an assistant acknowledgment.
|
|
114
|
+
|
|
115
|
+
| Prompt | Purpose | Tags |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `review_mr` | MR review workflow | gitlab, review |
|
|
118
|
+
| `approve_mr` | MR approval workflow | gitlab, review, approvals |
|
|
119
|
+
| `diagnose_pipeline` | CI debug workflow | gitlab, ci |
|
|
120
|
+
| `prepare_release` | Release preparation | gitlab, release |
|
|
121
|
+
| `setup_branch_protection` | Branch protection setup | gitlab, settings |
|
|
122
|
+
| `triage_issues` | Issue triage workflow | gitlab, issues |
|
|
123
|
+
|
|
124
|
+
## Environment Variables
|
|
125
|
+
|
|
126
|
+
| Variable | Required | Default | Notes |
|
|
127
|
+
|---|---|---|---|
|
|
128
|
+
| `GITLAB_URL` | yes | — | Instance base URL; trailing slash stripped, `/api/v4` appended |
|
|
129
|
+
| token (see below) | yes | — | Personal access token, OAuth2 token, or `$CI_JOB_TOKEN` |
|
|
130
|
+
| `GITLAB_READ_ONLY` | no | `false` | `true`/`1`/`yes` disables all writes and deletes, enforced before any API call |
|
|
131
|
+
| `GITLAB_TIMEOUT` | no | `30` | Request timeout in seconds |
|
|
132
|
+
| `GITLAB_SSL_VERIFY` | no | `true` | `false`/`0`/`no` skips verification — self-signed certs only |
|
|
133
|
+
|
|
134
|
+
Token is read from the first of `GITLAB_TOKEN`, `GITLAB_PAT`, `GITLAB_PERSONAL_ACCESS_TOKEN`,
|
|
135
|
+
`GITLAB_API_TOKEN` that is set. Scope `api` for full access, `read_api` for read-only deployments.
|
|
136
|
+
Tokens are never persisted — they are read from the environment at startup.
|
|
137
|
+
|
|
138
|
+
CLI flags override env: `--gitlab-url`, `--gitlab-token`, `--read-only`, plus
|
|
139
|
+
`--transport {stdio,sse,streamable-http}` with `--host` (default `127.0.0.1`) and `--port`
|
|
140
|
+
(default `8000`); host/port apply only to non-stdio transports.
|
|
141
|
+
|
|
142
|
+
## Release Workflow
|
|
143
|
+
|
|
144
|
+
Releases run through GitHub Actions — never bump versions manually.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
gh workflow run release.yml -f bump=minor # 0.9.0 → 0.10.0
|
|
148
|
+
gh workflow run release.yml -f bump=patch # 0.9.0 → 0.9.1
|
|
149
|
+
gh workflow run release.yml -f bump=major # 0.9.0 → 1.0.0
|
|
150
|
+
gh workflow run release.yml -f bump=minor -f dry_run=true # preview changelog, no push
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
1. `release.yml` (workflow_dispatch) — bumps the version in `pyproject.toml`, `llms.txt`,
|
|
154
|
+
`llms-full.txt`, `server.json`, `gemini-extension.json`; regenerates `uv.lock`; prepends a
|
|
155
|
+
CHANGELOG.md entry; creates the release commit and tag via the GitHub API.
|
|
156
|
+
2. `publish.yml` (triggered by a `v*` tag push) — builds the wheel, publishes to PyPI, creates the
|
|
157
|
+
GitHub Release with auto-generated notes, then publishes to the MCP Registry.
|
|
158
|
+
|
|
159
|
+
Rules:
|
|
160
|
+
- Never edit the `pyproject.toml` version directly — the workflow owns it.
|
|
161
|
+
- Never create tags manually — the workflow creates them.
|
|
162
|
+
- Commit messages must follow Conventional Commits (`feat:`, `fix:`, `docs:`, …) for changelog generation.
|
|
163
|
+
- The release commit is authored by `github-actions[bot]` with message `chore(release): X.Y.Z`.
|
|
164
|
+
|
|
165
|
+
## Documentation Freshness (mandatory)
|
|
166
|
+
|
|
167
|
+
When a changeset adds, removes, or modifies tools, resources, or prompts, update ALL of these in
|
|
168
|
+
the same commit:
|
|
169
|
+
|
|
170
|
+
- `README.md` — counts in heading and intro, tool table, full tool reference, usage examples, permissions table
|
|
171
|
+
- `llms.txt` — count in tagline and documentation link
|
|
172
|
+
- `llms-full.txt` — count in tagline, documentation link, full tool reference section
|
|
173
|
+
- `AGENTS.md` — counts in intro, tool category table
|
|
174
|
+
- `GEMINI.md` — count in intro, tool categories, common workflows
|
|
175
|
+
- `server.json` — description field (≤100 chars)
|
|
176
|
+
|
|
177
|
+
Checklist: counts match the actual registered tools/resources/prompts; the category list is
|
|
178
|
+
complete; new entries appear in the right sections with parameters and annotations.
|
|
179
|
+
|
|
180
|
+
## Known Limitations
|
|
181
|
+
|
|
182
|
+
- 83 tools in one server file, well past the 5-15 guideline. Split by category if it is refactored.
|
|
183
|
+
- Errors come back as successful tool results carrying `{"error": ...}` (soft-error pattern);
|
|
184
|
+
callers must inspect the JSON content rather than relying on protocol-level errors.
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.4] - 2026-09-20
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
- fix: correct error masking in merge sequence, tighten docs and messages (7b43094)
|
|
7
|
+
|
|
8
|
+
### Documentation
|
|
9
|
+
- docs: consolidate GEMINI.md into AGENTS.md (bcfb75f)
|
|
10
|
+
|
|
11
|
+
### Chores
|
|
12
|
+
- chore: ignore local 1Password plugin state (8d0b554)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## [0.9.3] - 2026-08-22
|
|
16
|
+
|
|
17
|
+
### Chores
|
|
18
|
+
- chore(deps): bump mcp in the uv group across 1 directory (fcf3ec8)
|
|
19
|
+
|
|
20
|
+
|
|
3
21
|
## [0.9.2] - 2026-07-08
|
|
4
22
|
|
|
5
23
|
### Chores
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: mcp-gitlab
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.4
|
|
4
4
|
Summary: MCP server for GitLab API — projects, MRs, pipelines, CI/CD variables, approvals, and more
|
|
5
5
|
Project-URL: Homepage, https://github.com/vish288/mcp-gitlab
|
|
6
6
|
Project-URL: Repository, https://github.com/vish288/mcp-gitlab
|
|
@@ -156,7 +156,7 @@ These accept any of the following token types:
|
|
|
156
156
|
| **Groups** | 6 | list, get, share/unshare project, share/unshare group |
|
|
157
157
|
| **Branches** | 3 | list, create, delete |
|
|
158
158
|
| **Commits** | 4 | list, get (with diff), create, compare |
|
|
159
|
-
| **Merge Requests** |
|
|
159
|
+
| **Merge Requests** | 15 | list, get, create, update, merge, merge-sequence, rebase, changes, approve, unapprove, get approvals, list pipelines, list commits, subscribe, unsubscribe |
|
|
160
160
|
| **MR Notes** | 6 | list, add, delete, update, award emoji, remove emoji |
|
|
161
161
|
| **MR Discussions** | 4 | list, create (inline + multi-line), reply, resolve |
|
|
162
162
|
| **Pipelines** | 5 | list, get (with jobs), create, retry, cancel |
|
|
@@ -268,7 +268,7 @@ These accept any of the following token types:
|
|
|
268
268
|
| `gitlab_retry_job` | Retry a job |
|
|
269
269
|
| `gitlab_play_job` | Trigger manual job |
|
|
270
270
|
| `gitlab_cancel_job` | Cancel a job |
|
|
271
|
-
| `gitlab_get_job_log` | Get job log output |
|
|
271
|
+
| `gitlab_get_job_log` | Get job log output (last 200 lines by default; `tail_lines=0` for all) |
|
|
272
272
|
|
|
273
273
|
### Tags
|
|
274
274
|
| Tool | Description |
|
|
@@ -120,7 +120,7 @@ These accept any of the following token types:
|
|
|
120
120
|
| **Groups** | 6 | list, get, share/unshare project, share/unshare group |
|
|
121
121
|
| **Branches** | 3 | list, create, delete |
|
|
122
122
|
| **Commits** | 4 | list, get (with diff), create, compare |
|
|
123
|
-
| **Merge Requests** |
|
|
123
|
+
| **Merge Requests** | 15 | list, get, create, update, merge, merge-sequence, rebase, changes, approve, unapprove, get approvals, list pipelines, list commits, subscribe, unsubscribe |
|
|
124
124
|
| **MR Notes** | 6 | list, add, delete, update, award emoji, remove emoji |
|
|
125
125
|
| **MR Discussions** | 4 | list, create (inline + multi-line), reply, resolve |
|
|
126
126
|
| **Pipelines** | 5 | list, get (with jobs), create, retry, cancel |
|
|
@@ -232,7 +232,7 @@ These accept any of the following token types:
|
|
|
232
232
|
| `gitlab_retry_job` | Retry a job |
|
|
233
233
|
| `gitlab_play_job` | Trigger manual job |
|
|
234
234
|
| `gitlab_cancel_job` | Cancel a job |
|
|
235
|
-
| `gitlab_get_job_log` | Get job log output |
|
|
235
|
+
| `gitlab_get_job_log` | Get job log output (last 200 lines by default; `tail_lines=0` for all) |
|
|
236
236
|
|
|
237
237
|
### Tags
|
|
238
238
|
| Tool | Description |
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
MCP server that wraps the GitLab REST API. Works with GitLab.com and self-hosted instances (CE/EE). No GitLab Duo or Premium required. Built with FastMCP, httpx, and Pydantic.
|
|
6
6
|
|
|
7
7
|
- **Install**: `uvx mcp-gitlab`
|
|
8
|
-
- **Version**: 0.9.
|
|
8
|
+
- **Version**: 0.9.4
|
|
9
9
|
- **Python**: >=3.10
|
|
10
10
|
- **License**: MIT
|
|
11
11
|
- **Transport**: stdio (default), SSE, streamable-http
|
|
@@ -133,7 +133,7 @@ uvx mcp-gitlab --gitlab-url https://gitlab.example.com --gitlab-token glpat-xxx
|
|
|
133
133
|
| `gitlab_create_commit` | Create a commit with multiple file actions |
|
|
134
134
|
| `gitlab_compare` | Compare two branches, tags, or commits |
|
|
135
135
|
|
|
136
|
-
### Merge Requests (
|
|
136
|
+
### Merge Requests (15)
|
|
137
137
|
|
|
138
138
|
| Tool | Description |
|
|
139
139
|
|------|-------------|
|
|
@@ -190,7 +190,7 @@ uvx mcp-gitlab --gitlab-url https://gitlab.example.com --gitlab-token glpat-xxx
|
|
|
190
190
|
| `gitlab_retry_job` | Retry a failed job |
|
|
191
191
|
| `gitlab_play_job` | Trigger a manual job |
|
|
192
192
|
| `gitlab_cancel_job` | Cancel a running job |
|
|
193
|
-
| `gitlab_get_job_log` | Get the log (trace) output of a job |
|
|
193
|
+
| `gitlab_get_job_log` | Get the log (trace) output of a job — last 200 lines by default, `tail_lines=0` for the whole log |
|
|
194
194
|
|
|
195
195
|
### Tags (4)
|
|
196
196
|
|
|
@@ -234,7 +234,7 @@ uvx mcp-gitlab --gitlab-url https://gitlab.example.com --gitlab-token glpat-xxx
|
|
|
234
234
|
| `gitlab_update_issue` | Update an existing issue |
|
|
235
235
|
| `gitlab_add_issue_comment` | Add a comment to an issue |
|
|
236
236
|
|
|
237
|
-
## Resources (
|
|
237
|
+
## Resources (7)
|
|
238
238
|
|
|
239
239
|
The server exposes curated workflow guides as MCP resources that clients can read on demand.
|
|
240
240
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
MCP server that wraps the GitLab REST API. Works with GitLab.com and self-hosted instances (CE/EE). No GitLab Duo or Premium required. Built with FastMCP, httpx, and Pydantic.
|
|
6
6
|
|
|
7
7
|
- **Install**: `uvx mcp-gitlab`
|
|
8
|
-
- **Version**: 0.9.
|
|
8
|
+
- **Version**: 0.9.4
|
|
9
9
|
- **Python**: >=3.10
|
|
10
10
|
- **License**: MIT
|
|
11
11
|
- **Transport**: stdio (default), SSE, streamable-http
|
|
@@ -6,19 +6,19 @@
|
|
|
6
6
|
"url": "https://github.com/vish288/mcp-gitlab",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.9.
|
|
9
|
+
"version": "0.9.4",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "pypi",
|
|
13
13
|
"identifier": "mcp-gitlab",
|
|
14
|
-
"version": "0.9.
|
|
14
|
+
"version": "0.9.4",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
18
18
|
"environmentVariables": [
|
|
19
19
|
{
|
|
20
20
|
"name": "GITLAB_TOKEN",
|
|
21
|
-
"description": "GitLab personal access token (also accepts GITLAB_PAT or GITLAB_API_TOKEN)",
|
|
21
|
+
"description": "GitLab personal access token (also accepts GITLAB_PAT, GITLAB_PERSONAL_ACCESS_TOKEN, or GITLAB_API_TOKEN)",
|
|
22
22
|
"isRequired": true,
|
|
23
23
|
"format": "string",
|
|
24
24
|
"isSecret": true
|
|
@@ -726,7 +726,9 @@ async def gitlab_share_group_with_group(
|
|
|
726
726
|
_check_write(ctx)
|
|
727
727
|
level = ACCESS_LEVELS.get(access_level.lower())
|
|
728
728
|
if level is None:
|
|
729
|
-
return _ok(
|
|
729
|
+
return _ok(
|
|
730
|
+
{"error": f"Invalid access level: {access_level}. Use: {', '.join(ACCESS_LEVELS)}"}
|
|
731
|
+
)
|
|
730
732
|
await _get_client(ctx).share_group_with_group(target_group_id, source_group_id, level)
|
|
731
733
|
return _ok({"status": "shared"})
|
|
732
734
|
except Exception as e:
|
|
@@ -1031,10 +1033,7 @@ async def gitlab_get_mr(
|
|
|
1031
1033
|
) -> str:
|
|
1032
1034
|
"""Get merge request details.
|
|
1033
1035
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
1036
|
Returns title, state, source/target branches, author, diff_refs, and merge status.
|
|
1037
|
-
|
|
1038
1037
|
"""
|
|
1039
1038
|
try:
|
|
1040
1039
|
data = await _get_client(ctx).get_merge_request(project_id, mr_iid)
|
|
@@ -1111,7 +1110,7 @@ async def gitlab_update_mr(
|
|
|
1111
1110
|
draft: Annotated[bool | None, Field(description="Set draft status")] = None,
|
|
1112
1111
|
state_event: Annotated[str | None, Field(description="close or reopen")] = None,
|
|
1113
1112
|
) -> str:
|
|
1114
|
-
"""Update an MR's title, description,
|
|
1113
|
+
"""Update an MR's title, description, target branch, labels, squash/draft flags, or state.
|
|
1115
1114
|
|
|
1116
1115
|
Returns the updated MR object.
|
|
1117
1116
|
"""
|
|
@@ -1210,10 +1209,10 @@ async def gitlab_merge_mr_sequence(
|
|
|
1210
1209
|
|
|
1211
1210
|
Returns a per-MR result list with merged/failed status and any error.
|
|
1212
1211
|
"""
|
|
1212
|
+
merged: list[int] = []
|
|
1213
1213
|
try:
|
|
1214
1214
|
_check_write(ctx)
|
|
1215
1215
|
client = _get_client(ctx)
|
|
1216
|
-
merged: list[int] = []
|
|
1217
1216
|
params: dict[str, Any] = {}
|
|
1218
1217
|
if squash is not None:
|
|
1219
1218
|
params["squash"] = squash
|
|
@@ -1238,13 +1237,9 @@ async def gitlab_merge_mr_sequence(
|
|
|
1238
1237
|
|
|
1239
1238
|
return _ok({"status": "all_merged", "merged": merged})
|
|
1240
1239
|
except Exception as e:
|
|
1241
|
-
detail
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
if isinstance(e, GitLabApiError):
|
|
1245
|
-
detail["status_code"] = e.status_code
|
|
1246
|
-
detail["body"] = e.body
|
|
1247
|
-
return json.dumps(detail, indent=2, ensure_ascii=False)
|
|
1240
|
+
detail = json.loads(_err(e))
|
|
1241
|
+
detail["merged_so_far"] = merged
|
|
1242
|
+
return _ok(detail)
|
|
1248
1243
|
|
|
1249
1244
|
|
|
1250
1245
|
@mcp.tool(
|
|
@@ -1455,10 +1450,7 @@ async def gitlab_list_mr_discussions(
|
|
|
1455
1450
|
) -> str:
|
|
1456
1451
|
"""List discussions on a merge request.
|
|
1457
1452
|
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
Returns discussion threads with notes, excluding system notes.
|
|
1461
|
-
|
|
1453
|
+
Returns discussion threads with notes, excluding system-only threads.
|
|
1462
1454
|
"""
|
|
1463
1455
|
try:
|
|
1464
1456
|
data = await _get_client(ctx).list_mr_discussions(project_id, mr_iid)
|
|
@@ -1818,11 +1810,8 @@ async def gitlab_get_pipeline(
|
|
|
1818
1810
|
) -> str:
|
|
1819
1811
|
"""Get pipeline details, optionally with jobs.
|
|
1820
1812
|
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
1813
|
Returns id, status, ref, timing, web_url.
|
|
1824
1814
|
Jobs include id, name, stage, status, timing, failure_reason, web_url.
|
|
1825
|
-
|
|
1826
1815
|
"""
|
|
1827
1816
|
try:
|
|
1828
1817
|
client = _get_client(ctx)
|
|
@@ -1989,9 +1978,15 @@ async def gitlab_get_job_log(
|
|
|
1989
1978
|
str, Field(description="Project ID, path, or full GitLab URL", min_length=1)
|
|
1990
1979
|
],
|
|
1991
1980
|
job_id: Annotated[int, Field(description="Job ID")],
|
|
1992
|
-
tail_lines: Annotated[
|
|
1981
|
+
tail_lines: Annotated[
|
|
1982
|
+
int,
|
|
1983
|
+
Field(description="Lines to return from the end of the log; 0 returns the whole log", ge=0),
|
|
1984
|
+
] = 200,
|
|
1993
1985
|
) -> str:
|
|
1994
|
-
"""Get a job's
|
|
1986
|
+
"""Get a job's log trace, truncated to the last tail_lines lines (default 200).
|
|
1987
|
+
|
|
1988
|
+
Returns {log, total_lines, shown_lines}. Pass tail_lines=0 for the whole log.
|
|
1989
|
+
"""
|
|
1995
1990
|
try:
|
|
1996
1991
|
log_text = await _get_client(ctx).get_job_log(project_id, job_id)
|
|
1997
1992
|
lines = log_text.splitlines()
|
|
@@ -2107,7 +2102,7 @@ async def gitlab_delete_tag(
|
|
|
2107
2102
|
],
|
|
2108
2103
|
tag_name: Annotated[str, Field(description="Tag name to delete", min_length=1)],
|
|
2109
2104
|
) -> str:
|
|
2110
|
-
"""Delete a tag. Returns a {status: deleted,
|
|
2105
|
+
"""Delete a tag. Returns a {status: deleted, tag} confirmation."""
|
|
2111
2106
|
try:
|
|
2112
2107
|
_check_write(ctx)
|
|
2113
2108
|
await _get_client(ctx).delete_tag(project_id, tag_name)
|
|
@@ -2226,7 +2221,7 @@ async def gitlab_update_release(
|
|
|
2226
2221
|
description: Annotated[str | None, Field(description="New release description")] = None,
|
|
2227
2222
|
released_at: Annotated[str | None, Field(description="New release date (ISO 8601)")] = None,
|
|
2228
2223
|
) -> str:
|
|
2229
|
-
"""Update a release's name, description, or
|
|
2224
|
+
"""Update a release's name, description, or release date. Returns the updated release."""
|
|
2230
2225
|
try:
|
|
2231
2226
|
_check_write(ctx)
|
|
2232
2227
|
params: dict[str, Any] = {}
|
|
@@ -2255,7 +2250,7 @@ async def gitlab_delete_release(
|
|
|
2255
2250
|
) -> str:
|
|
2256
2251
|
"""Delete a release (the underlying tag is preserved).
|
|
2257
2252
|
|
|
2258
|
-
Returns
|
|
2253
|
+
Returns a {status: deleted, tag_name} confirmation.
|
|
2259
2254
|
"""
|
|
2260
2255
|
try:
|
|
2261
2256
|
_check_write(ctx)
|
|
@@ -2358,10 +2353,16 @@ async def gitlab_update_variable(
|
|
|
2358
2353
|
protected: Annotated[bool | None, Field(description="Protected branches only")] = None,
|
|
2359
2354
|
masked: Annotated[bool | None, Field(description="Mask in logs")] = None,
|
|
2360
2355
|
raw: Annotated[bool | None, Field(description="Do not expand references")] = None,
|
|
2361
|
-
environment_scope: Annotated[
|
|
2356
|
+
environment_scope: Annotated[
|
|
2357
|
+
str | None,
|
|
2358
|
+
Field(description="Environment scope filter — selects which scoped variable to update"),
|
|
2359
|
+
] = None,
|
|
2362
2360
|
description: Annotated[str | None, Field(description="Variable description")] = None,
|
|
2363
2361
|
) -> str:
|
|
2364
|
-
"""Update a project CI/CD variable. Returns the updated variable object.
|
|
2362
|
+
"""Update a project CI/CD variable. Returns the updated variable object.
|
|
2363
|
+
|
|
2364
|
+
environment_scope selects which scoped variable to update; it does not change the scope.
|
|
2365
|
+
"""
|
|
2365
2366
|
try:
|
|
2366
2367
|
_check_write(ctx)
|
|
2367
2368
|
params: dict[str, Any] = {"value": value}
|
|
@@ -2652,7 +2653,7 @@ async def gitlab_update_issue(
|
|
|
2652
2653
|
state_event: Annotated[str | None, Field(description="close or reopen")] = None,
|
|
2653
2654
|
weight: Annotated[int | None, Field(description="Issue weight")] = None,
|
|
2654
2655
|
) -> str:
|
|
2655
|
-
"""Update an issue's title, description, labels, assignees,
|
|
2656
|
+
"""Update an issue's title, description, labels, assignees, weight, or state event.
|
|
2656
2657
|
|
|
2657
2658
|
Returns the updated issue object.
|
|
2658
2659
|
"""
|
|
@@ -2329,3 +2329,115 @@ class TestOptionalParams:
|
|
|
2329
2329
|
)
|
|
2330
2330
|
parsed = _parse(result)
|
|
2331
2331
|
assert parsed["title"] == "Updated"
|
|
2332
|
+
|
|
2333
|
+
|
|
2334
|
+
# ═══════════════════════════════════════════════════════
|
|
2335
|
+
# Regression tests — defects found by audit
|
|
2336
|
+
# ═══════════════════════════════════════════════════════
|
|
2337
|
+
|
|
2338
|
+
|
|
2339
|
+
class TestDocstringContracts:
|
|
2340
|
+
"""Tools whose docstrings promise a specific return shape must deliver it."""
|
|
2341
|
+
|
|
2342
|
+
async def test_merge_mr_sequence_readonly_blocked(self, readonly_client):
|
|
2343
|
+
"""Read-only mode must return a JSON error, not blow up on an unbound local."""
|
|
2344
|
+
client, router = readonly_client
|
|
2345
|
+
result = await client.call_tool(
|
|
2346
|
+
"gitlab_merge_mr_sequence",
|
|
2347
|
+
{"project_id": "123", "mr_iids": [1, 2]},
|
|
2348
|
+
)
|
|
2349
|
+
parsed = _parse(result)
|
|
2350
|
+
assert "error" in parsed
|
|
2351
|
+
assert "read-only" in parsed["hint"].lower()
|
|
2352
|
+
assert parsed["merged_so_far"] == []
|
|
2353
|
+
|
|
2354
|
+
async def test_merge_mr_sequence_partial_failure_reports_progress(self, tool_client):
|
|
2355
|
+
"""A mid-sequence API failure still reports which MRs already merged, plus a hint."""
|
|
2356
|
+
client, router = tool_client
|
|
2357
|
+
router.get("/projects/123/merge_requests/1").mock(
|
|
2358
|
+
return_value=Response(200, json={"iid": 1, "detailed_merge_status": "mergeable"})
|
|
2359
|
+
)
|
|
2360
|
+
router.put("/projects/123/merge_requests/1/merge").mock(
|
|
2361
|
+
return_value=Response(200, json={"iid": 1, "state": "merged"})
|
|
2362
|
+
)
|
|
2363
|
+
router.get("/projects/123/merge_requests/2").mock(
|
|
2364
|
+
return_value=Response(404, json={"message": "404 Not found"})
|
|
2365
|
+
)
|
|
2366
|
+
result = await client.call_tool(
|
|
2367
|
+
"gitlab_merge_mr_sequence",
|
|
2368
|
+
{"project_id": "123", "mr_iids": [1, 2]},
|
|
2369
|
+
)
|
|
2370
|
+
parsed = _parse(result)
|
|
2371
|
+
assert parsed["merged_so_far"] == [1]
|
|
2372
|
+
assert parsed["status_code"] == 404
|
|
2373
|
+
assert "hint" in parsed
|
|
2374
|
+
|
|
2375
|
+
async def test_delete_tag_returns_tag_key(self, tool_client):
|
|
2376
|
+
"""Docstring promises {status: deleted, tag} — not tag_name."""
|
|
2377
|
+
client, router = tool_client
|
|
2378
|
+
router.delete("/projects/123/repository/tags/v1.0").mock(return_value=Response(204))
|
|
2379
|
+
result = await client.call_tool(
|
|
2380
|
+
"gitlab_delete_tag", {"project_id": "123", "tag_name": "v1.0"}
|
|
2381
|
+
)
|
|
2382
|
+
parsed = _parse(result)
|
|
2383
|
+
assert parsed == {"status": "deleted", "tag": "v1.0"}
|
|
2384
|
+
|
|
2385
|
+
async def test_delete_release_returns_confirmation_not_metadata(self, tool_client):
|
|
2386
|
+
"""Docstring promises {status: deleted, tag_name}, not the release object."""
|
|
2387
|
+
client, router = tool_client
|
|
2388
|
+
router.delete("/projects/123/releases/v1.0").mock(return_value=Response(204))
|
|
2389
|
+
result = await client.call_tool(
|
|
2390
|
+
"gitlab_delete_release", {"project_id": "123", "tag_name": "v1.0"}
|
|
2391
|
+
)
|
|
2392
|
+
parsed = _parse(result)
|
|
2393
|
+
assert parsed == {"status": "deleted", "tag_name": "v1.0"}
|
|
2394
|
+
|
|
2395
|
+
async def test_get_job_log_zero_tail_returns_whole_log(self, tool_client):
|
|
2396
|
+
"""Docstring promises tail_lines=0 returns the whole log."""
|
|
2397
|
+
client, router = tool_client
|
|
2398
|
+
lines = "\n".join(f"line {i}" for i in range(300))
|
|
2399
|
+
router.get("/projects/123/jobs/1/trace").mock(return_value=Response(200, text=lines))
|
|
2400
|
+
result = await client.call_tool(
|
|
2401
|
+
"gitlab_get_job_log",
|
|
2402
|
+
{"project_id": "123", "job_id": 1, "tail_lines": 0},
|
|
2403
|
+
)
|
|
2404
|
+
parsed = _parse(result)
|
|
2405
|
+
assert parsed["shown_lines"] == 300
|
|
2406
|
+
assert parsed["total_lines"] == 300
|
|
2407
|
+
|
|
2408
|
+
async def test_get_job_log_rejects_negative_tail(self, tool_client):
|
|
2409
|
+
"""Negative tail_lines used to silently drop lines off the front; now rejected."""
|
|
2410
|
+
client, router = tool_client
|
|
2411
|
+
with pytest.raises(Exception, match="tail_lines"):
|
|
2412
|
+
await client.call_tool(
|
|
2413
|
+
"gitlab_get_job_log",
|
|
2414
|
+
{"project_id": "123", "job_id": 1, "tail_lines": -5},
|
|
2415
|
+
)
|
|
2416
|
+
|
|
2417
|
+
async def test_share_group_with_group_invalid_level_lists_valid_levels(self, tool_client):
|
|
2418
|
+
"""Error message must list the valid levels, matching its project-share sibling."""
|
|
2419
|
+
client, router = tool_client
|
|
2420
|
+
result = await client.call_tool(
|
|
2421
|
+
"gitlab_share_group_with_group",
|
|
2422
|
+
{"target_group_id": "9", "source_group_id": 1, "access_level": "bogus"},
|
|
2423
|
+
)
|
|
2424
|
+
parsed = _parse(result)
|
|
2425
|
+
assert "bogus" in parsed["error"]
|
|
2426
|
+
assert "maintainer" in parsed["error"]
|
|
2427
|
+
|
|
2428
|
+
async def test_tool_descriptions_have_no_blank_line_runs(self, tool_client):
|
|
2429
|
+
"""Docstrings ship verbatim as MCP tool descriptions — no stray blank-line runs."""
|
|
2430
|
+
client, router = tool_client
|
|
2431
|
+
tools = await client.list_tools()
|
|
2432
|
+
offenders = [t.name for t in tools if t.description and "\n\n\n" in t.description]
|
|
2433
|
+
assert offenders == []
|
|
2434
|
+
|
|
2435
|
+
async def test_update_docstrings_only_name_real_params(self, tool_client):
|
|
2436
|
+
"""Docstrings must not advertise parameters the tool does not accept."""
|
|
2437
|
+
client, router = tool_client
|
|
2438
|
+
tools = {t.name: t for t in await client.list_tools()}
|
|
2439
|
+
for name in ("gitlab_update_mr", "gitlab_update_issue", "gitlab_update_release"):
|
|
2440
|
+
desc = tools[name].description.lower()
|
|
2441
|
+
params = tools[name].inputSchema["properties"]
|
|
2442
|
+
assert "milestone" not in desc or any("milestone" in p for p in params)
|
|
2443
|
+
assert "assignee" not in desc or any("assignee" in p for p in params)
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
version = 1
|
|
2
2
|
revision = 3
|
|
3
3
|
requires-python = ">=3.10"
|
|
4
|
+
resolution-markers = [
|
|
5
|
+
"python_full_version >= '3.14' and sys_platform == 'win32'",
|
|
6
|
+
"python_full_version >= '3.14' and sys_platform != 'win32'",
|
|
7
|
+
"python_full_version < '3.14' and sys_platform == 'win32'",
|
|
8
|
+
"python_full_version < '3.14' and sys_platform != 'win32'",
|
|
9
|
+
]
|
|
4
10
|
|
|
5
11
|
[[package]]
|
|
6
12
|
name = "aiofile"
|
|
@@ -802,7 +808,7 @@ wheels = [
|
|
|
802
808
|
|
|
803
809
|
[[package]]
|
|
804
810
|
name = "mcp"
|
|
805
|
-
version = "1.
|
|
811
|
+
version = "1.28.1"
|
|
806
812
|
source = { registry = "https://pypi.org/simple" }
|
|
807
813
|
dependencies = [
|
|
808
814
|
{ name = "anyio" },
|
|
@@ -820,14 +826,14 @@ dependencies = [
|
|
|
820
826
|
{ name = "typing-inspection" },
|
|
821
827
|
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
|
822
828
|
]
|
|
823
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
829
|
+
sdist = { url = "https://files.pythonhosted.org/packages/6e/77/9450b8f251a13affb6281997d0523c4615f8a8b35d0b21ff30db3a5aac9d/mcp-1.28.1.tar.gz", hash = "sha256:d51e36a5f5644faea4f85ea649bfffa6bc6c26770d42798ad6a3de3d2ba69683", size = 638501, upload-time = "2026-06-26T12:57:29.093Z" }
|
|
824
830
|
wheels = [
|
|
825
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
831
|
+
{ url = "https://files.pythonhosted.org/packages/e2/5e/d118fce19f87a2e7d8101c35c8ae0ec289098a4df0ff244cec23e415aca0/mcp-1.28.1-py3-none-any.whl", hash = "sha256:2726bca5e7193f61c5dde8b12500a6de2d9acf6d1a1c0be9e8c2e706437991df", size = 222620, upload-time = "2026-06-26T12:57:27.218Z" },
|
|
826
832
|
]
|
|
827
833
|
|
|
828
834
|
[[package]]
|
|
829
835
|
name = "mcp-gitlab"
|
|
830
|
-
version = "0.9.
|
|
836
|
+
version = "0.9.4"
|
|
831
837
|
source = { editable = "." }
|
|
832
838
|
dependencies = [
|
|
833
839
|
{ name = "click" },
|
|
@@ -1593,8 +1599,8 @@ name = "secretstorage"
|
|
|
1593
1599
|
version = "3.5.0"
|
|
1594
1600
|
source = { registry = "https://pypi.org/simple" }
|
|
1595
1601
|
dependencies = [
|
|
1596
|
-
{ name = "cryptography" },
|
|
1597
|
-
{ name = "jeepney" },
|
|
1602
|
+
{ name = "cryptography", marker = "sys_platform != 'win32'" },
|
|
1603
|
+
{ name = "jeepney", marker = "sys_platform != 'win32'" },
|
|
1598
1604
|
]
|
|
1599
1605
|
sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" }
|
|
1600
1606
|
wheels = [
|
mcp_gitlab-0.9.2/AGENTS.md
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
# mcp-gitlab — Agent Context
|
|
2
|
-
|
|
3
|
-
MCP server providing 83 tools for the GitLab REST API v4.
|
|
4
|
-
|
|
5
|
-
## Architecture
|
|
6
|
-
|
|
7
|
-
- **Entry point**: `src/mcp_gitlab/__init__.py` — click CLI, loads env, runs FastMCP server
|
|
8
|
-
- **Client**: `src/mcp_gitlab/client.py` — async httpx client with all GitLab API methods
|
|
9
|
-
- **Tools**: `src/mcp_gitlab/servers/gitlab.py` — all FastMCP tool registrations
|
|
10
|
-
- **Resources**: `src/mcp_gitlab/servers/resources.py` — 6 MCP resources (workflow guides)
|
|
11
|
-
- **Prompts**: `src/mcp_gitlab/servers/prompts.py` — 5 MCP prompts (multi-tool workflows)
|
|
12
|
-
- **Config**: `src/mcp_gitlab/config.py` — `GitLabConfig` dataclass from env vars
|
|
13
|
-
- **Exceptions**: `src/mcp_gitlab/exceptions.py` — `GitLabApiError`, `GitLabAuthError`, etc.
|
|
14
|
-
- **Tests**: `tests/unit/test_tools.py` — 120+ tool-level tests via FastMCP test client
|
|
15
|
-
|
|
16
|
-
## Patterns
|
|
17
|
-
|
|
18
|
-
- All tools are `async def` returning JSON strings
|
|
19
|
-
- Error handling: try/except wrapping every tool, returning `{"error": ...}` JSON
|
|
20
|
-
- Write access control: `_check_write(ctx)` raises `GitLabWriteDisabledError` when `GITLAB_READ_ONLY=true`
|
|
21
|
-
- Tags: every tool tagged with `{"gitlab", "<category>", "read"|"write"}`
|
|
22
|
-
- Parameters use `Annotated[type, Field(description=...)]`
|
|
23
|
-
- Client uses httpx with `PRIVATE-TOKEN` header auth
|
|
24
|
-
- Project/group IDs can be numeric or URL-encoded paths
|
|
25
|
-
|
|
26
|
-
## MCP Compliance Rules
|
|
27
|
-
|
|
28
|
-
### Tool Annotations (MANDATORY)
|
|
29
|
-
Every tool MUST have `annotations={}` with at minimum `readOnlyHint`.
|
|
30
|
-
- Read tools: `annotations={"readOnlyHint": True, "idempotentHint": True}`
|
|
31
|
-
- Non-destructive write tools: `annotations={"readOnlyHint": False}`
|
|
32
|
-
- Destructive write tools: `annotations={"destructiveHint": True, "readOnlyHint": False}`
|
|
33
|
-
- Idempotent writes (PUT/update): add `idempotentHint: True`
|
|
34
|
-
|
|
35
|
-
### Tool Descriptions
|
|
36
|
-
- 1-2 sentences. Front-load what it does AND what it returns.
|
|
37
|
-
- Bad: "This tool gets a merge request."
|
|
38
|
-
- Good: "Get merge request details. Returns title, state, branches, author, diff_refs."
|
|
39
|
-
|
|
40
|
-
### Error Handling
|
|
41
|
-
- Every tool MUST wrap in try/except and return `_err(e)` — never raise.
|
|
42
|
-
- Error text MUST be actionable: include what went wrong and suggest a fix.
|
|
43
|
-
- Never return concatenated JSON strings — always a single valid JSON object.
|
|
44
|
-
- Never expose stack traces, tokens, or internal paths.
|
|
45
|
-
|
|
46
|
-
### Parameter Design
|
|
47
|
-
- Use `Annotated[type, Field(description="...")]` on every parameter.
|
|
48
|
-
- Use `Literal[...]` for known value sets instead of plain `str`.
|
|
49
|
-
- Every optional parameter must have a default.
|
|
50
|
-
- Flatten — no nested dicts unless truly necessary.
|
|
51
|
-
|
|
52
|
-
### Read-Only Mode
|
|
53
|
-
- Every write tool MUST call `_check_write(ctx)` before any mutation.
|
|
54
|
-
|
|
55
|
-
### Naming Convention
|
|
56
|
-
- Pattern: `gitlab_{verb}_{resource}` (snake_case)
|
|
57
|
-
- Verbs: create, get, list, search, update, delete, merge, rebase, retry, play, cancel, award, remove, share, unshare, compare, add, reply, resolve, approve, unapprove, subscribe, unsubscribe
|
|
58
|
-
|
|
59
|
-
## Tool Categories
|
|
60
|
-
|
|
61
|
-
Projects (4), Approvals (10), Groups (6), Branches (3), Commits (4), Merge Requests (15), MR Notes (6), MR Discussions (4), Pipelines (5), Jobs (4), Tags (4), Releases (5), CI/CD Variables (8), Issues (5)
|
|
62
|
-
|
|
63
|
-
## Environment Variables
|
|
64
|
-
|
|
65
|
-
- `GITLAB_URL` (required) — GitLab instance base URL
|
|
66
|
-
- `GITLAB_TOKEN` or `GITLAB_PAT` (required) — Personal access token
|
|
67
|
-
- `GITLAB_READ_ONLY` — disable mutations
|
|
68
|
-
- `GITLAB_TIMEOUT` — request timeout seconds
|
|
69
|
-
- `GITLAB_SSL_VERIFY` — SSL verification toggle
|
|
70
|
-
|
|
71
|
-
## Release Workflow
|
|
72
|
-
|
|
73
|
-
Releases are handled via GitHub Actions — never bump versions manually.
|
|
74
|
-
|
|
75
|
-
### How to release
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
# From the repo directory:
|
|
79
|
-
gh workflow run release.yml -f bump=minor # 0.4.0 → 0.5.0
|
|
80
|
-
gh workflow run release.yml -f bump=patch # 0.5.0 → 0.5.1
|
|
81
|
-
gh workflow run release.yml -f bump=major # 0.5.0 → 1.0.0
|
|
82
|
-
|
|
83
|
-
# Dry run (preview changelog, no push):
|
|
84
|
-
gh workflow run release.yml -f bump=minor -f dry_run=true
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### What happens
|
|
88
|
-
|
|
89
|
-
1. `release.yml` (workflow_dispatch) — bumps the version in `pyproject.toml`, `llms.txt`, `llms-full.txt`, `server.json`, `gemini-extension.json`; regenerates `uv.lock`; prepends a CHANGELOG.md entry; creates the release commit + tag via the GitHub API
|
|
90
|
-
2. `publish.yml` (triggered by `v*` tag push) — builds wheel, publishes to PyPI, creates GitHub Release with auto-generated notes, then publishes to the MCP Registry
|
|
91
|
-
|
|
92
|
-
### Rules
|
|
93
|
-
- Never edit `pyproject.toml` version directly — the workflow owns it
|
|
94
|
-
- Never create tags manually — the workflow creates them
|
|
95
|
-
- Commit messages must follow conventional commits (`feat:`, `fix:`, `docs:`, etc.) for changelog generation
|
|
96
|
-
- The release commit is authored by `github-actions[bot]` with message `chore(release): X.Y.Z`
|
|
97
|
-
|
|
98
|
-
## Prompts
|
|
99
|
-
|
|
100
|
-
Prompts follow the resources pattern: prompt content lives as `.md` files in `src/mcp_gitlab/resources/prompts/`, loaded by `servers/prompts.py` via `_load_prompt()`, registered with `@mcp.prompt()`. Each prompt returns `list[Message]` with a user message (workflow template) and an assistant message (acknowledgment).
|
|
101
|
-
|
|
102
|
-
- `review_mr` — MR review workflow (tags: gitlab, review)
|
|
103
|
-
- `diagnose_pipeline` — CI debug workflow (tags: gitlab, ci)
|
|
104
|
-
- `prepare_release` — Release preparation (tags: gitlab, release)
|
|
105
|
-
- `setup_branch_protection` — Branch protection setup (tags: gitlab, settings)
|
|
106
|
-
- `triage_issues` — Issue triage workflow (tags: gitlab, issues)
|
|
107
|
-
|
|
108
|
-
## Documentation Freshness (MANDATORY)
|
|
109
|
-
|
|
110
|
-
When any changeset adds, removes, or modifies tools, resources, or prompts, ALL documentation files MUST be updated in the same commit:
|
|
111
|
-
|
|
112
|
-
- `README.md` — tool count in heading + intro, tool table, full tool reference, usage examples, permissions table
|
|
113
|
-
- `llms.txt` — tool count in tagline and documentation link
|
|
114
|
-
- `llms-full.txt` — tool count in tagline, documentation link, full tool reference section
|
|
115
|
-
- `AGENTS.md` — tool count in intro, tool categories list
|
|
116
|
-
- `GEMINI.md` — tool count in intro, tool categories, common workflows
|
|
117
|
-
- `server.json` — description field (<=100 chars)
|
|
118
|
-
|
|
119
|
-
Checklist: verify tool count matches actual registered tools, verify category list is complete, verify new tools appear in correct sections with parameters and annotations.
|
|
120
|
-
|
|
121
|
-
## Known Limitations / Future Work
|
|
122
|
-
|
|
123
|
-
- 83 tools in one server file (exceeds 5-15 guideline). Consider splitting by category in a future refactor.
|
|
124
|
-
- Errors are returned as successful tool results with `{"error": ...}` (soft-error pattern). Callers must inspect JSON content.
|
mcp_gitlab-0.9.2/GEMINI.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# mcp-gitlab — Gemini CLI Extension Context
|
|
2
|
-
|
|
3
|
-
MCP server providing 83 tools, 7 resources, and 6 prompts for interacting with the GitLab API. Covers the full lifecycle of GitLab projects: code, reviews, CI/CD, releases, and issue tracking.
|
|
4
|
-
|
|
5
|
-
## Tool Categories
|
|
6
|
-
|
|
7
|
-
- **Projects** — get, create, delete, update merge settings, share/unshare with groups
|
|
8
|
-
- **Merge Requests** — list, get, create, update, merge, rebase, view changes and diffs, approve, unapprove, get approvals, list pipelines, list commits, subscribe, unsubscribe
|
|
9
|
-
- **MR Reviews** — list/add/update/delete notes, list/create discussions, reply to and resolve discussions, award/remove emoji
|
|
10
|
-
- **MR Approvals** — project-level and MR-level approval rules (list, create, update, delete)
|
|
11
|
-
- **Pipelines & Jobs** — list/get/create/retry/cancel pipelines, retry/play/cancel jobs, get job logs
|
|
12
|
-
- **Branches** — list, create, delete
|
|
13
|
-
- **Commits** — list, get, create, compare refs
|
|
14
|
-
- **Tags & Releases** — list/get/create/delete tags, list/get/create/update/delete releases
|
|
15
|
-
- **CI/CD Variables** — project and group variables (list, create, update, delete)
|
|
16
|
-
- **Issues** — list, get, create, update, add comments
|
|
17
|
-
- **Groups** — list, get, share/unshare groups
|
|
18
|
-
|
|
19
|
-
## Common Workflows
|
|
20
|
-
|
|
21
|
-
- **Code review**: `list_mrs` -> `mr_changes` -> `list_mr_discussions` -> `add_mr_note` or `create_mr_discussion` -> `resolve_discussion`
|
|
22
|
-
- **Pipeline debugging**: `list_pipelines` -> `get_pipeline` -> `get_job_log` -> `retry_job`
|
|
23
|
-
- **Release process**: `list_commits` -> `compare` -> `create_tag` -> `create_release`
|
|
24
|
-
- **Branch protection**: `list_project_approval_rules` -> `create_project_approval_rule` -> `update_project_merge_settings`
|
|
25
|
-
- **Issue triage**: `list_issues` -> `get_issue` -> `update_issue` -> `add_issue_comment`
|
|
26
|
-
|
|
27
|
-
## Notes
|
|
28
|
-
|
|
29
|
-
- Set `GITLAB_READ_ONLY=true` to restrict all operations to read-only (no writes, no deletes).
|
|
30
|
-
- Token requires appropriate GitLab scopes: `api` for full access, `read_api` for read-only.
|
|
31
|
-
- Default request timeout is 30 seconds; override with `GITLAB_TIMEOUT`.
|
|
32
|
-
- SSL verification is on by default; disable with `GITLAB_SSL_VERIFY=false` for self-signed certs.
|
|
33
|
-
- Works with GitLab.com and self-hosted GitLab instances.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gitlab-0.9.2 → mcp_gitlab-0.9.4}/src/mcp_gitlab/resources/prompts/setup-branch-protection.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|