@jswk1/gitlab-mcp 1.0.0 → 1.1.0
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.
- package/README.md +138 -155
- package/dist/attachment-cache.d.ts +27 -0
- package/dist/attachment-cache.js +72 -0
- package/dist/attachment-cache.js.map +1 -0
- package/dist/attachments.d.ts +1 -0
- package/dist/attachments.js +28 -0
- package/dist/attachments.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -1
- package/dist/gitlab.d.ts +11 -2
- package/dist/gitlab.js +55 -5
- package/dist/gitlab.js.map +1 -1
- package/dist/server.js +75 -26
- package/dist/server.js.map +1 -1
- package/dist/workflows.d.ts +20 -4
- package/dist/workflows.js +61 -11
- package/dist/workflows.js.map +1 -1
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -1,97 +1,74 @@
|
|
|
1
1
|
# GitLab read-only MCP server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Give your AI assistant GitLab issues, merge requests, discussions and attachments from a self-managed instance. The server reads GitLab; your assistant uses its own tools to inspect and edit your local repository.
|
|
4
4
|
|
|
5
|
-
## Setup
|
|
5
|
+
## Setup
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- **GitLab 17.4+** for attachment downloads. Older versions are untested.
|
|
8
|
+
- **A personal access token with `read_api` scope**, belonging to the developer using the assistant. The token owner's GitLab permissions determine access, including confidential issues.
|
|
9
|
+
- **Node.js 20.20+** for npm/local setup, or **Docker** for container setup.
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
npm ci
|
|
11
|
-
npm run build
|
|
12
|
-
```
|
|
11
|
+
Choose one option below and add its configuration to your MCP client's settings. All options use **stdio**: the client starts the server and communicates over stdin/stdout. Run one server process per developer/client; there is no HTTP endpoint.
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
After the package is published, configure your MCP host to run it with `npx` (pin a version instead of `latest` if you want reproducible installs):
|
|
19
|
-
|
|
20
|
-
```json
|
|
21
|
-
{
|
|
22
|
-
"mcpServers": {
|
|
23
|
-
"gitlab": {
|
|
24
|
-
"command": "npx",
|
|
25
|
-
"args": ["--yes", "--package=@jswk1/gitlab-mcp@1.0.0", "gitlab-mcp"],
|
|
26
|
-
"env": {
|
|
27
|
-
"GITLAB_URL": "https://gitlab.example.internal",
|
|
28
|
-
"GITLAB_TOKEN": "YOUR_PERSONAL_READ_API_TOKEN",
|
|
29
|
-
"GITLAB_USER_ID": "123"
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
The first run downloads the package; Node.js 20.20+ must be installed on the host. Keep the token in the host's private configuration, not in a committed file.
|
|
37
|
-
|
|
38
|
-
### Publish to npm
|
|
39
|
-
|
|
40
|
-
Review the files npm will publish and release:
|
|
41
|
-
|
|
42
|
-
```sh
|
|
43
|
-
npm login
|
|
44
|
-
npm pack --dry-run
|
|
45
|
-
npm publish
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
The scoped name avoids collisions with unscoped packages, but `@jswk1` must be an npm scope you control. For the first publish of a scoped package, use `npm publish --access public`; npm requires an authenticated account with publish access to that scope. For later releases, bump the version first, for example with `npm version patch`, then publish again. Add public repository/issue URLs to `package.json` only if those URLs are intended to be public.
|
|
49
|
-
|
|
50
|
-
Configure your MCP host to start this command (replace the path, URL and token locally):
|
|
13
|
+
### Option 1: npm package
|
|
14
|
+
|
|
15
|
+
Configure your MCP client to run the npm package:
|
|
51
16
|
|
|
52
17
|
```json
|
|
53
18
|
{
|
|
54
19
|
"mcpServers": {
|
|
55
20
|
"gitlab": {
|
|
56
|
-
"command": "
|
|
57
|
-
"args": ["/
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["--yes", "--package=@jswk1/gitlab-mcp@1.1.0", "gitlab-mcp"],
|
|
58
23
|
"env": {
|
|
59
24
|
"GITLAB_URL": "https://gitlab.example.internal",
|
|
60
|
-
"GITLAB_TOKEN": "YOUR_PERSONAL_READ_API_TOKEN"
|
|
61
|
-
"GITLAB_USER_ID": "123"
|
|
25
|
+
"GITLAB_TOKEN": "YOUR_PERSONAL_READ_API_TOKEN"
|
|
62
26
|
}
|
|
63
27
|
}
|
|
64
28
|
}
|
|
65
29
|
}
|
|
66
30
|
```
|
|
67
31
|
|
|
68
|
-
|
|
32
|
+
The first run downloads the package. Keep a pinned version for reproducible installs. If the package is unavailable, build locally or use Docker.
|
|
33
|
+
|
|
34
|
+
### Option 2: local build over stdio
|
|
69
35
|
|
|
70
|
-
|
|
36
|
+
From the repository root:
|
|
71
37
|
|
|
72
38
|
```sh
|
|
73
|
-
|
|
39
|
+
npm ci
|
|
40
|
+
npm run build
|
|
74
41
|
```
|
|
75
42
|
|
|
76
|
-
|
|
43
|
+
Point your MCP client at the compiled entry point using an absolute path:
|
|
77
44
|
|
|
78
|
-
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"mcpServers": {
|
|
48
|
+
"gitlab": {
|
|
49
|
+
"command": "node",
|
|
50
|
+
"args": ["/absolute/path/to/gitlab-mcp/dist/index.js"],
|
|
51
|
+
"env": {
|
|
52
|
+
"GITLAB_URL": "https://gitlab.example.internal",
|
|
53
|
+
"GITLAB_TOKEN": "YOUR_PERSONAL_READ_API_TOKEN"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
79
59
|
|
|
80
|
-
|
|
60
|
+
To launch manually, copy `.env.example` to `.env`, fill in your credentials, then run `node --env-file=.env dist/index.js`. The process waits for MCP messages; it is not an interactive terminal application.
|
|
81
61
|
|
|
82
|
-
|
|
83
|
-
npm run docker:build
|
|
84
|
-
```
|
|
62
|
+
### Option 3: Docker
|
|
85
63
|
|
|
86
|
-
|
|
64
|
+
From the repository root:
|
|
87
65
|
|
|
88
66
|
```sh
|
|
89
|
-
|
|
67
|
+
npm run docker:build
|
|
68
|
+
cp .env.example .env
|
|
90
69
|
```
|
|
91
70
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
For an MCP host, use the following configuration, replacing the URL, token and user ID locally:
|
|
71
|
+
Fill in `GITLAB_URL` and `GITLAB_TOKEN` in `.env`, then configure your MCP client:
|
|
95
72
|
|
|
96
73
|
```json
|
|
97
74
|
{
|
|
@@ -100,131 +77,137 @@ For an MCP host, use the following configuration, replacing the URL, token and u
|
|
|
100
77
|
"command": "docker",
|
|
101
78
|
"args": [
|
|
102
79
|
"run", "--rm", "-i",
|
|
103
|
-
"-
|
|
104
|
-
"-e", "GITLAB_TOKEN",
|
|
105
|
-
"-e", "GITLAB_USER_ID",
|
|
80
|
+
"--env-file", "/absolute/path/to/gitlab-mcp/.env",
|
|
106
81
|
"gitlab-readonly-mcp"
|
|
107
|
-
]
|
|
108
|
-
"env": {
|
|
109
|
-
"GITLAB_URL": "https://gitlab.example.internal",
|
|
110
|
-
"GITLAB_TOKEN": "YOUR_PERSONAL_READ_API_TOKEN",
|
|
111
|
-
"GITLAB_USER_ID": "123"
|
|
112
|
-
}
|
|
82
|
+
]
|
|
113
83
|
}
|
|
114
84
|
}
|
|
115
85
|
}
|
|
116
86
|
```
|
|
117
87
|
|
|
118
|
-
Keep `-i
|
|
88
|
+
Keep `-i`; do not add `-t` or `-d`. No ports or repository mounts are needed. The image runs as a non-root user and receives credentials at runtime.
|
|
119
89
|
|
|
120
|
-
|
|
90
|
+
GitLab must be reachable from inside the container. On Docker Desktop, use `host.docker.internal` to reach GitLab on your host. On Linux Docker Engine, also add `--add-host=host.docker.internal:host-gateway` to the Docker arguments when using that hostname.
|
|
121
91
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
```sh
|
|
125
|
-
docker run --rm -i --env-file .env \
|
|
126
|
-
--mount type=bind,source=/home/developer/certs/company-ca.pem,target=/certs/company-ca.pem,readonly \
|
|
127
|
-
-e NODE_EXTRA_CA_CERTS=/certs/company-ca.pem \
|
|
128
|
-
gitlab-readonly-mcp
|
|
129
|
-
```
|
|
92
|
+
After source updates, rebuild the image and restart your client's MCP connection to create a new container.
|
|
130
93
|
|
|
131
|
-
|
|
94
|
+
## Workflows
|
|
132
95
|
|
|
133
|
-
|
|
96
|
+
Ask your assistant to work on an issue or MR in your local repository. It can start with `gitlab_get_workflow`, passing `project`, `iid`, `target` (`issue` or `merge_request`), optional `profile`, optional `goal` to specify scope, and optional `mode` for developer/documentalist issue resolution.
|
|
134
97
|
|
|
135
|
-
|
|
|
136
|
-
| --- | --- |
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `gitlab_list_branches` | Discover issue branch candidates and head commit metadata |
|
|
142
|
-
| `gitlab_get_branch` | Resolve a branch's current head SHA and commit date, including fork branches |
|
|
143
|
-
| `gitlab_compare_refs` | Read branch/commit changes, including branches without an MR |
|
|
144
|
-
| `gitlab_list_issues` | Search issues by text, labels and state |
|
|
145
|
-
| `gitlab_get_issue` | Full issue metadata, description and upload discovery |
|
|
146
|
-
| `gitlab_issue_related_merge_requests` | Merge requests associated with an issue |
|
|
147
|
-
| `gitlab_get_discussions` | Issue/MR discussion threads, replies, review positions and uploads |
|
|
148
|
-
| `gitlab_read_attachment` | Download an upload by project, secret and filename |
|
|
149
|
-
| `gitlab_list_merge_requests` | Search merge requests |
|
|
150
|
-
| `gitlab_get_merge_request` | MR metadata, description, diff SHAs, pipeline summary and uploads |
|
|
151
|
-
| `gitlab_get_merge_request_diffs` | Paginated changed files and unified diffs |
|
|
152
|
-
| `gitlab_list_merge_request_commits` | Commits in a merge request |
|
|
153
|
-
| `gitlab_repository_tree` | Optional API fallback for repository paths unavailable locally |
|
|
154
|
-
| `gitlab_read_file` | Optional API fallback for repository text unavailable locally |
|
|
155
|
-
| `gitlab_prepare_review_comment` | General or validated inline comment draft, without posting |
|
|
98
|
+
| Profile | Issue | Merge request |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| `developer` (default) | Investigate and guide the user through a fix/feature, or implement automatically when selected | Review changed code for bugs, regressions and improvements |
|
|
101
|
+
| `tester` | Inspect the latest related implementation and prepare prioritized manual test cases | Prepare a manual test plan for changed behavior |
|
|
102
|
+
| `documentalist` | Guide documentation implementation, or edit automatically when selected, using merged/current implementation | Update local Markdown docs to match the implementation |
|
|
103
|
+
| `business_analyst` (business analyst/designer) | Report on client requirements and issue status; reconcile external sheets | Summarize business/design impact for client reporting |
|
|
156
104
|
|
|
157
|
-
|
|
105
|
+
Profiles apply per task and share the same tools and permissions. The workflow tool returns guidance; the assistant gathers evidence and performs local work. Tester workflows produce plans, not test execution. Implementation and documentation edits require a client with local repository access and editing tools.
|
|
158
106
|
|
|
159
|
-
|
|
107
|
+
Developer and documentalist issue resolution offers **guided mode (recommended)** and **fully automated mode**. If the user asks to resolve, fix, implement or update an issue without choosing a mode, the assistant asks which they prefer and waits before making changes. A plain implementation request does not select automation. Explicit requests such as “automatically implement this issue” skip the question, as do explicit guided requests or an existing mode choice for the same task. Analysis-only, plan-only and draft-only requests keep their scope without a mode question.
|
|
160
108
|
|
|
161
|
-
|
|
109
|
+
In guided mode, the user edits the code, tests or documentation. The assistant investigates, outlines the approach, then gives one manageable step at a time with relevant files, what to change, why, pitfalls and checks. It waits for the user's changes/results, reviews them and adapts the next step. In automated mode, the assistant performs the implementation and checks. Neither mode authorizes commits, pushes or GitLab writes.
|
|
162
110
|
|
|
163
|
-
|
|
164
|
-
| --- | --- | --- |
|
|
165
|
-
| `developer` | Read the complete issue and implement the requested fix/feature directly in local code | Return a Markdown review of bugs, regressions, code smells and improvements limited to changed code |
|
|
166
|
-
| `tester` | Read the issue and latest related branch changes; return risk-prioritized manual test cases in Markdown | Inspect current changes and requirements; return a prioritized Markdown manual test plan |
|
|
167
|
-
| `documentalist` | Read the issue, comments and merged/current MR changes; edit affected local Markdown documentation | Compare the implementation with current local docs and edit the relevant Markdown sections |
|
|
111
|
+
Clients can pass `mode: "guided"` or `mode: "automated"` to `gitlab_get_workflow`, `implement_issue`, `investigate_issue` or `update_documentation`. Omitting it (or passing `"ask"`) delegates the mode question and interpretation of explicit user intent to the host assistant. Workflow calls do not persist choices; the host carries the user's selection forward for the same task. Mode selection applies to developer/documentalist issue implementation; MR reviews, tester and reporting workflows retain their existing behavior.
|
|
168
112
|
|
|
169
|
-
|
|
113
|
+
For aggregate reporting, select `profile: "business_analyst"`, `target: "report"`, `project` and a `goal`; omit `iid`. This mode does not require a source checkout. The assistant uses host file tools to generate actual CSV files with the requested columns (or returns CSV text if file tools are unavailable). External sheets require a user-provided file or an authorized host connector; this server does not connect to or update sheets itself.
|
|
170
114
|
|
|
171
|
-
|
|
115
|
+
Client attribution must use an explicit label or supplied mapping. Reports follow all issue pages and filter `closed_at` locally with inclusive start and exclusive end dates in the agreed timezone. Current closed issues do not describe historical closure events for reopened issues. Milestone membership and closure dates are separate filters. Reports include source links, exact filters, retrieval time, totals and coverage limits. Client-facing exports omit confidential/internal content unless authorized, and CSV text cells are protected against spreadsheet formulas. Reconciliation preserves stable issue and external row identifiers and reports duplicates, missing rows and conflicts.
|
|
172
116
|
|
|
173
|
-
|
|
174
|
-
- **Developer, review:** “Review MR !456 in `group/project`. Read all comments and compare original versus changed source. Return Markdown findings for bugs, regressions, code smells and easy improvements, limited to changed code.”
|
|
175
|
-
- **Tester:** “Use the tester profile for issue #123 in `group/project`. Read the complete issue and the latest related branch changes. Return the critical manual test flows as Markdown, sorted by priority/risk.”
|
|
176
|
-
- **Documentalist:** “Read issue #123 in `group/project` and prepare the documentation changes. Read the merged implementation, or the MR changes if not merged, then update the affected Markdown files in this repository.”
|
|
117
|
+
Example requests:
|
|
177
118
|
|
|
178
|
-
|
|
119
|
+
- “Guide me through fixing issue #123. I want to make the changes myself.”
|
|
120
|
+
- “Automatically implement issue #123, including the relevant tests.”
|
|
121
|
+
- “Read issue #123 in `group/project`, including comments and attachments, and fix the validation bug in this repository.”
|
|
122
|
+
- “Review MR !456 in `group/project`. Return findings limited to changed code, ordered by severity.”
|
|
123
|
+
- “Use the tester profile for issue #123 in `group/project` and prepare the critical manual test cases for the latest related branch.”
|
|
124
|
+
- “Use the documentalist profile for MR !456 in `group/project` and update the affected Markdown docs.”
|
|
125
|
+
- “Use the business analyst/designer profile: export issues closed for client X in Q3 2026 from `group/project`. Client X uses label `client::X`; use Europe/Warsaw time and include IID, title, milestone, closed date and URL.”
|
|
126
|
+
- “Generate a CSV of closed client X issues in milestone `Release 2.0`, then reconcile it with this client sheet and report missing or conflicting rows.”
|
|
179
127
|
|
|
180
|
-
|
|
181
|
-
| --- | --- |
|
|
182
|
-
| `implement_issue` | `project`, `iid`, optional `goal`; implements locally using developer |
|
|
183
|
-
| `investigate_issue` | `project`, `iid`, optional `profile` and `goal` |
|
|
184
|
-
| `review_merge_request` | `project`, `iid`, optional `profile` and `goal` |
|
|
185
|
-
| `prepare_test_plan` | `project`, `iid`, `target`, optional `goal`; always uses tester |
|
|
186
|
-
| `update_documentation` | `project`, `iid`, `target`, optional `goal`; edits local Markdown using documentalist |
|
|
187
|
-
| `draft_documentation` | `project`, `iid`, `target`; always uses documentalist |
|
|
128
|
+
Clients supporting MCP prompts can use these shortcuts:
|
|
188
129
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
130
|
+
| Prompt | Purpose | Arguments |
|
|
131
|
+
| --- | --- | --- |
|
|
132
|
+
| `report_issues` | Generate reports/CSVs or reconcile sheets as business analyst/designer | `project`, `goal` |
|
|
133
|
+
| `implement_issue` | Resolve an issue as developer in guided or automated mode | `project`, `iid`, optional `goal`, `mode` |
|
|
134
|
+
| `investigate_issue` | Investigate or act on an issue | `project`, `iid`, optional `profile`, `goal`, `mode` |
|
|
135
|
+
| `review_merge_request` | Review an MR using the selected profile | `project`, `iid`, optional `profile`, `goal` |
|
|
136
|
+
| `prepare_test_plan` | Plan manual tests as tester | `project`, `iid`, `target`, optional `goal` |
|
|
137
|
+
| `update_documentation` | Resolve documentation as documentalist; issues offer guided/automated modes | `project`, `iid`, `target`, optional `goal`, `mode` |
|
|
138
|
+
| `draft_documentation` | Return documentation drafts without editing files | `project`, `iid`, `target` |
|
|
194
139
|
|
|
195
|
-
|
|
140
|
+
## Available tools
|
|
196
141
|
|
|
197
|
-
|
|
142
|
+
| Tool | Purpose |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| `gitlab_get_workflow` | Get task guidance for a profile and issue/MR or aggregate report |
|
|
145
|
+
| `gitlab_current_user` | Identify the token owner |
|
|
146
|
+
| `gitlab_list_projects` | Find accessible projects |
|
|
147
|
+
| `gitlab_get_project` | Read project metadata and default branch |
|
|
148
|
+
| `gitlab_list_branches` | Find branches and head commit metadata |
|
|
149
|
+
| `gitlab_get_branch` | Resolve a branch's current head SHA and commit date |
|
|
150
|
+
| `gitlab_compare_refs` | Compare branches or commits, including changes without an MR |
|
|
151
|
+
| `gitlab_list_issues` | Search issues by text, labels, state, milestone title and update lower bound |
|
|
152
|
+
| `gitlab_get_issue` | Read issue metadata, description and discovered uploads |
|
|
153
|
+
| `gitlab_issue_related_merge_requests` | Find MRs associated with an issue |
|
|
154
|
+
| `gitlab_get_discussions` | Read issue/MR threads, replies, review positions and uploads |
|
|
155
|
+
| `gitlab_read_attachment` | Reuse cached analysis or read an upload by project, secret and filename |
|
|
156
|
+
| `gitlab_save_attachment_summary` | Save completed host analysis in the local cache; never writes to GitLab |
|
|
157
|
+
| `gitlab_list_merge_requests` | Search merge requests |
|
|
158
|
+
| `gitlab_get_merge_request` | Read MR metadata, description, diff SHAs and pipeline summary |
|
|
159
|
+
| `gitlab_get_merge_request_diffs` | Read changed files and unified diffs |
|
|
160
|
+
| `gitlab_list_merge_request_commits` | Read MR commits |
|
|
161
|
+
| `gitlab_repository_tree` | Browse repository paths when unavailable locally |
|
|
162
|
+
| `gitlab_read_file` | Read repository text when unavailable locally |
|
|
163
|
+
| `gitlab_prepare_review_comment` | Prepare a general or validated inline draft without posting |
|
|
198
164
|
|
|
199
|
-
|
|
165
|
+
`project` accepts a numeric ID or `group/subgroup/repository` path. Issue/MR `iid` is the project-local number in its web URL. Paginated tools return `items` and `nextPage`; follow pages until `nextPage` is null for complete results. Default page size is 20, maximum 100. Ref comparisons do not paginate diffs; GitLab can omit or truncate diffs, so check `compare_timeout`, `collapsed` and `too_large` where returned.
|
|
200
166
|
|
|
201
|
-
|
|
167
|
+
Issue reporting filters use the [GitLab Issues API](https://docs.gitlab.com/api/issues/). `milestone` is a title; `updated_after` is an update-time lower bound, not a closure-time filter.
|
|
202
168
|
|
|
203
|
-
|
|
169
|
+
## Attachment cache
|
|
204
170
|
|
|
205
|
-
|
|
171
|
+
Attachment downloads and analysis summaries persist locally across server restarts. The assistant first calls `gitlab_read_attachment`. If a summary exists for the requested `analysis_focus` (default `general`), the server returns that summary instead of sending the image/video back for interpretation. Otherwise it reuses cached bytes or downloads the upload once. Concurrent reads of the same upload share one download.
|
|
206
172
|
|
|
207
|
-
|
|
208
|
-
| --- | --- | --- |
|
|
209
|
-
| `GITLAB_URL` | Required | Instance root, optionally including a subpath; no `/api/v4` suffix |
|
|
210
|
-
| `GITLAB_TOKEN` | Required | Current developer's personal access token |
|
|
211
|
-
| `GITLAB_USER_ID` | Optional | Expected numeric user ID |
|
|
212
|
-
| `GITLAB_TIMEOUT_MS` | `30000` | Timeout for each REST request and response body |
|
|
213
|
-
| `GITLAB_MAX_BYTES` | `5242880` | Maximum response bytes per request, enforced while streaming |
|
|
214
|
-
| `NODE_EXTRA_CA_CERTS` | Optional | PEM file with your organization's trusted CA |
|
|
173
|
+
After actually inspecting the attachment, the assistant calls `gitlab_save_attachment_summary` with the returned `sha256`, matching `analysis_focus`, `inspected: true`, factual `summary`, inspection `method`, `coverage` and `limitations`. The server stores the host's analysis; it does not run a vision/video model itself. Summaries are bound to the downloaded content hash. Video frames or transcripts must be marked partial with explicit limitations; an unsupported download alone does not count as inspection. The workflows tell the assistant to save and reuse these summaries during subsequent resolution steps.
|
|
215
174
|
|
|
216
|
-
|
|
175
|
+
- `reuse_summary: false` reopens the cached content for more detailed inspection without downloading again.
|
|
176
|
+
- A different `analysis_focus` keeps a separate summary for a different question (up to 10 scopes per upload).
|
|
177
|
+
- `refresh: true` downloads a fresh copy and invalidates previous summaries, even if the bytes are unchanged.
|
|
178
|
+
- `output: "resource"` returns cached raw bytes for a capable host/parser instead of a summary.
|
|
217
179
|
|
|
218
|
-
|
|
180
|
+
The default directory is `~/.cache/gitlab-mcp/attachments`. Entries expire for reuse after seven days and the cache retains at most 256 uploads across server processes, evicting older files when new entries are saved. Each upload remains subject to `GITLAB_MAX_BYTES`. Cache files contain private attachment content and summaries, use owner-only file permissions, and never store the token. Entries are isolated by GitLab instance, token, project and upload identity; token rotation starts a separate cache. Cached reads require a live authenticated project-access check, so they cannot be used offline. This check validates project access, not whether an individual upload has since been deleted; use refresh when fresh attachment availability matters. Remove the cache directory with the server stopped to clear all stored content. If disk caching is unavailable, downloads still use a bounded in-memory cache; a failed summary persistence request returns an error.
|
|
219
181
|
|
|
220
|
-
|
|
182
|
+
For Docker, persist the cache by adding these arguments before the image name in the existing configuration:
|
|
221
183
|
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
npm test
|
|
225
|
-
npm run build
|
|
184
|
+
```json
|
|
185
|
+
"--mount", "type=volume,src=gitlab-mcp-cache,dst=/home/node/.cache/gitlab-mcp"
|
|
226
186
|
```
|
|
227
187
|
|
|
228
|
-
|
|
188
|
+
Without a volume, Docker's `--rm` removes cached content when the container exits.
|
|
189
|
+
|
|
190
|
+
## Configuration
|
|
229
191
|
|
|
230
|
-
|
|
192
|
+
| Environment variable | Default | Purpose |
|
|
193
|
+
| --- | --- | --- |
|
|
194
|
+
| `GITLAB_URL` | Required | GitLab instance root, optionally with a subpath; omit `/api/v4` |
|
|
195
|
+
| `GITLAB_TOKEN` | Required | Developer's personal access token with `read_api` |
|
|
196
|
+
| `GITLAB_USER_ID` | Optional | Expected numeric user ID; startup fails if the token belongs to another user |
|
|
197
|
+
| `GITLAB_TIMEOUT_MS` | `30000` | Timeout per API request and response body |
|
|
198
|
+
| `GITLAB_CACHE_DIR` | `~/.cache/gitlab-mcp/attachments` | Local attachment and summary cache directory |
|
|
199
|
+
| `GITLAB_CACHE_TTL_MS` | `604800000` (7 days) | Maximum age for reuse since download |
|
|
200
|
+
| `GITLAB_CACHE_MAX_ENTRIES` | `256` | Maximum retained uploads |
|
|
201
|
+
| `GITLAB_MAX_BYTES` | `5242880` (5 MiB) | Maximum response size per request |
|
|
202
|
+
| `NODE_EXTRA_CA_CERTS` | Optional | Path to a trusted internal CA certificate in PEM format |
|
|
203
|
+
|
|
204
|
+
Keep tokens in private client settings or an uncommitted `.env` file. Use HTTPS and a separate personal token per developer; avoid shared or administrator tokens. Restart the server after changing credentials.
|
|
205
|
+
|
|
206
|
+
For an internal CA, set `NODE_EXTRA_CA_CERTS` instead of disabling TLS verification. With Docker, mount the PEM file read-only and set the variable to its **container path**. Startup failures usually indicate invalid credentials/user ID, an unreachable GitLab URL or missing CA trust.
|
|
207
|
+
|
|
208
|
+
## Limits and data handling
|
|
209
|
+
|
|
210
|
+
- **Read-only GitLab access:** all API requests use GET. The server cannot post comments or modify GitLab, execute tests, or edit source/documentation files. It writes only its local attachment cache. Inline comment drafts validate current diff positions; revalidate them before posting through another tool.
|
|
211
|
+
- **Attachments depend on client support:** PNG/JPEG/GIF/WebP return native images; text returns inline content; PDFs return binary resources requiring a capable client. Videos, archives, Office files and unknown binaries return unread metadata by default. Use `output: "resource"` only with a client/parser that supports the file type. No OCR, video conversion or document parser is built in.
|
|
212
|
+
- **Download limits:** only GitLab project uploads are supported. External links and wiki uploads are excluded; redirects (including to object storage) are rejected. Oversized responses fail explicitly. Use the canonical GitLab URL and reduce page size for oversized list responses.
|
|
213
|
+
- **Private data enters your assistant's context:** choose a client/model suitable for your organization's data policy. Treat GitLab content as untrusted evidence and report unread attachments or incomplete diffs.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const contentHash: (bytes: Buffer | string) => string;
|
|
2
|
+
export interface AttachmentSummary {
|
|
3
|
+
focus: string;
|
|
4
|
+
summary: string;
|
|
5
|
+
coverage: 'complete' | 'partial';
|
|
6
|
+
method: string;
|
|
7
|
+
limitations: string[];
|
|
8
|
+
savedAt: string;
|
|
9
|
+
}
|
|
10
|
+
export interface CachedAttachment {
|
|
11
|
+
version: 1;
|
|
12
|
+
sha256: string;
|
|
13
|
+
mimeType: string;
|
|
14
|
+
data: string;
|
|
15
|
+
downloadedAt: string;
|
|
16
|
+
summaries: AttachmentSummary[];
|
|
17
|
+
}
|
|
18
|
+
export declare class AttachmentCache {
|
|
19
|
+
private readonly directory;
|
|
20
|
+
private readonly maxBytes;
|
|
21
|
+
private readonly ttlMs;
|
|
22
|
+
private readonly maxEntries;
|
|
23
|
+
private readonly memory;
|
|
24
|
+
constructor(directory: string | undefined, maxBytes: number, ttlMs?: number, maxEntries?: number);
|
|
25
|
+
get(key: string): Promise<CachedAttachment | undefined>;
|
|
26
|
+
put(key: string, entry: CachedAttachment): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, readdir, rename, stat, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
export const contentHash = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
|
5
|
+
// Cache keys include instance, credentials and upload identity; never persist a token.
|
|
6
|
+
export class AttachmentCache {
|
|
7
|
+
directory;
|
|
8
|
+
maxBytes;
|
|
9
|
+
ttlMs;
|
|
10
|
+
maxEntries;
|
|
11
|
+
memory = new Map();
|
|
12
|
+
constructor(directory, maxBytes, ttlMs = 7 * 24 * 60 * 60 * 1000, maxEntries = 256) {
|
|
13
|
+
this.directory = directory;
|
|
14
|
+
this.maxBytes = maxBytes;
|
|
15
|
+
this.ttlMs = ttlMs;
|
|
16
|
+
this.maxEntries = maxEntries;
|
|
17
|
+
}
|
|
18
|
+
async get(key) {
|
|
19
|
+
let entry = this.memory.get(key);
|
|
20
|
+
if (!entry && this.directory) {
|
|
21
|
+
try {
|
|
22
|
+
const path = join(this.directory, `${key}.json`);
|
|
23
|
+
if ((await stat(path)).size > Math.ceil(this.maxBytes * 4 / 3) + 1024 * 1024)
|
|
24
|
+
return undefined;
|
|
25
|
+
entry = JSON.parse(await readFile(path, 'utf8'));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (!entry)
|
|
32
|
+
return undefined;
|
|
33
|
+
try {
|
|
34
|
+
const bytes = Buffer.from(entry.data, 'base64');
|
|
35
|
+
const age = Date.now() - Date.parse(entry.downloadedAt);
|
|
36
|
+
if (!Array.isArray(entry.summaries))
|
|
37
|
+
return undefined;
|
|
38
|
+
if (!entry.summaries.every(summary => summary && typeof summary.focus === 'string' && typeof summary.summary === 'string' && ['complete', 'partial'].includes(summary.coverage) && typeof summary.method === 'string' && typeof summary.savedAt === 'string' && Array.isArray(summary.limitations) && summary.limitations.every(value => typeof value === 'string')))
|
|
39
|
+
return undefined;
|
|
40
|
+
if (entry.version !== 1 || !Number.isFinite(age) || age < 0 || age >= this.ttlMs || bytes.length > this.maxBytes || contentHash(bytes) !== entry.sha256 || typeof entry.mimeType !== 'string' || !Array.isArray(entry.summaries))
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
return entry;
|
|
47
|
+
}
|
|
48
|
+
async put(key, entry) {
|
|
49
|
+
this.memory.delete(key);
|
|
50
|
+
this.memory.set(key, entry);
|
|
51
|
+
while (this.memory.size > this.maxEntries)
|
|
52
|
+
this.memory.delete(this.memory.keys().next().value);
|
|
53
|
+
if (this.directory) {
|
|
54
|
+
await mkdir(this.directory, { recursive: true, mode: 0o700 });
|
|
55
|
+
const path = join(this.directory, `${key}.json`);
|
|
56
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
57
|
+
try {
|
|
58
|
+
await writeFile(temporary, JSON.stringify(entry), { mode: 0o600, flag: 'wx' });
|
|
59
|
+
await rename(temporary, path);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await unlink(temporary).catch(() => { });
|
|
63
|
+
}
|
|
64
|
+
// Bound on-disk retention, including entries from earlier server processes.
|
|
65
|
+
const files = (await readdir(this.directory)).filter(name => /^[a-f\d]{64}\.json$/.test(name));
|
|
66
|
+
const dated = await Promise.all(files.map(async (name) => ({ name, time: (await stat(join(this.directory, name)).catch(() => ({ mtimeMs: 0 }))).mtimeMs })));
|
|
67
|
+
dated.sort((a, b) => b.time - a.time);
|
|
68
|
+
await Promise.all(dated.slice(this.maxEntries).map(file => unlink(join(this.directory, file.name)).catch(() => { })));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=attachment-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-cache.js","sourceRoot":"","sources":["../src/attachment-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function uploadMimeType(bytes: Buffer, filename: string, contentType: string | null): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { extname } from 'node:path';
|
|
2
|
+
// GitLab/proxies may serve uploads with a generic download Content-Type.
|
|
3
|
+
// Prefer recognizable bytes, then the filename, only for generic headers.
|
|
4
|
+
export function uploadMimeType(bytes, filename, contentType) {
|
|
5
|
+
const declared = contentType?.split(';')[0]?.trim().toLowerCase();
|
|
6
|
+
if (declared && !['application/octet-stream', 'binary/octet-stream', 'application/binary'].includes(declared))
|
|
7
|
+
return declared;
|
|
8
|
+
if (bytes.subarray(0, 8).equals(Buffer.from('89504e470d0a1a0a', 'hex')))
|
|
9
|
+
return 'image/png';
|
|
10
|
+
if (bytes.length >= 3 && bytes.subarray(0, 3).equals(Buffer.from('ffd8ff', 'hex')))
|
|
11
|
+
return 'image/jpeg';
|
|
12
|
+
if (['GIF87a', 'GIF89a'].includes(bytes.toString('ascii', 0, 6)))
|
|
13
|
+
return 'image/gif';
|
|
14
|
+
if (bytes.toString('ascii', 0, 4) === 'RIFF' && bytes.toString('ascii', 8, 12) === 'WEBP')
|
|
15
|
+
return 'image/webp';
|
|
16
|
+
if (bytes.toString('ascii', 0, 5) === '%PDF-')
|
|
17
|
+
return 'application/pdf';
|
|
18
|
+
const types = {
|
|
19
|
+
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
|
|
20
|
+
'.gif': 'image/gif', '.webp': 'image/webp', '.pdf': 'application/pdf',
|
|
21
|
+
'.mp4': 'video/mp4', '.m4v': 'video/mp4', '.mov': 'video/quicktime',
|
|
22
|
+
'.webm': 'video/webm', '.avi': 'video/x-msvideo', '.mkv': 'video/x-matroska',
|
|
23
|
+
'.txt': 'text/plain', '.log': 'text/plain', '.md': 'text/markdown',
|
|
24
|
+
'.csv': 'text/csv', '.json': 'application/json', '.xml': 'application/xml',
|
|
25
|
+
};
|
|
26
|
+
return types[extname(filename).toLowerCase()] ?? 'application/octet-stream';
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=attachments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachments.js","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,yEAAyE;AACzE,0EAA0E;AAC1E,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,QAAgB,EAAE,WAA0B;IACxF,MAAM,QAAQ,GAAG,WAAW,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAClE,IAAI,QAAQ,IAAI,CAAC,CAAC,0BAA0B,EAAE,qBAAqB,EAAE,oBAAoB,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC/H,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;QAAE,OAAO,WAAW,CAAC;IAC5F,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAAE,OAAO,YAAY,CAAC;IACxG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAAE,OAAO,WAAW,CAAC;IACrF,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,MAAM;QAAE,OAAO,YAAY,CAAC;IAC/G,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,iBAAiB,CAAC;IACxE,MAAM,KAAK,GAA2B;QACpC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY;QAChE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,iBAAiB;QACrE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,iBAAiB;QACnE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,kBAAkB;QAC5E,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe;QAClE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,EAAE,iBAAiB;KAC3E,CAAC;IACF,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,0BAA0B,CAAC;AAC9E,CAAC"}
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
1
3
|
function integer(value, fallback, name) {
|
|
2
4
|
const parsed = value === undefined ? fallback : Number(value);
|
|
3
5
|
if (!Number.isSafeInteger(parsed) || parsed <= 0)
|
|
@@ -17,6 +19,9 @@ export function readConfig(env = process.env) {
|
|
|
17
19
|
baseUrl: url.href.replace(/\/$/, ''), token: env.GITLAB_TOKEN.trim(),
|
|
18
20
|
expectedUserId: env.GITLAB_USER_ID === undefined ? undefined : integer(env.GITLAB_USER_ID, 1, 'GITLAB_USER_ID'),
|
|
19
21
|
timeoutMs: integer(env.GITLAB_TIMEOUT_MS, 30000, 'GITLAB_TIMEOUT_MS'),
|
|
22
|
+
cacheDir: env.GITLAB_CACHE_DIR || join(homedir(), '.cache', 'gitlab-mcp', 'attachments'),
|
|
23
|
+
cacheTtlMs: integer(env.GITLAB_CACHE_TTL_MS, 7 * 24 * 60 * 60 * 1000, 'GITLAB_CACHE_TTL_MS'),
|
|
24
|
+
cacheMaxEntries: integer(env.GITLAB_CACHE_MAX_ENTRIES, 256, 'GITLAB_CACHE_MAX_ENTRIES'),
|
|
20
25
|
maxBytes: integer(env.GITLAB_MAX_BYTES, 5 * 1024 * 1024, 'GITLAB_MAX_BYTES'),
|
|
21
26
|
};
|
|
22
27
|
}
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAajC,SAAS,OAAO,CAAC,KAAyB,EAAE,QAAgB,EAAE,IAAY;IACxE,MAAM,MAAM,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9D,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,6BAA6B,CAAC,CAAC;IACxG,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC7D,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9G,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACpC,IAAI,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QAC1G,MAAM,IAAI,KAAK,CAAC,oFAAoF,CAAC,CAAC;IACxG,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC7E,OAAO;QACL,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE;QACpE,cAAc,EAAE,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,EAAE,gBAAgB,CAAC;QAC/G,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,KAAK,EAAE,mBAAmB,CAAC;QACrE,QAAQ,EAAE,GAAG,CAAC,gBAAgB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,CAAC;QACxF,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,qBAAqB,CAAC;QAC5F,eAAe,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,GAAG,EAAE,0BAA0B,CAAC;QACvF,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,kBAAkB,CAAC;KAC7E,CAAC;AACJ,CAAC"}
|