@zereight/mcp-gitlab 2.1.50 → 2.1.52
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.ko.md +34 -10
- package/README.md +34 -10
- package/README.zh-CN.md +35 -10
- package/build/auth-cli.js +95 -0
- package/build/cli-command.js +33 -0
- package/build/index.js +40 -20
- package/build/oauth-device-flow.js +158 -0
- package/build/oauth.js +20 -0
- package/build/schemas.js +53 -10
- package/build/scripts/generate-tool-docs.js +1 -1
- package/build/test/create-commit-actions.test.js +102 -0
- package/build/test/gitlab-artifact-entry-schema.test.js +44 -0
- package/build/test/oauth-device-flow-tests.js +440 -0
- package/build/test/schema-tests.js +117 -3
- package/build/test/test-job-artifacts.js +35 -1
- package/build/test/test-permission-mode.js +28 -0
- package/build/tools/tool-descriptions.js +2 -2
- package/build/utils/gitlab-commit-actions.js +42 -0
- package/package.json +4 -3
package/README.ko.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# GitLab MCP Server
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@zereight/mcp-gitlab)
|
|
4
|
+
[](vscode:mcp/install?%7B%22name%22%3A%22zereight.gitlab-mcp%22%2C%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40zereight%2Fmcp-gitlab%40latest%22%5D%2C%22env%22%3A%7B%22GITLAB_PERSONAL_ACCESS_TOKEN%22%3A%22%24%7Binput%3Agitlab-token%7D%22%2C%22GITLAB_API_URL%22%3A%22https%3A%2F%2Fgitlab.com%2Fapi%2Fv4%22%2C%22GITLAB_PERMISSION_MODE%22%3A%22full%22%7D%7D)
|
|
5
|
+
|
|
3
6
|
[English](./README.md) | [한국어](./README.ko.md) | [简体中文](./README.zh-CN.md)
|
|
4
7
|
|
|
5
8
|
📖 **[문서 →](https://zereight.github.io/gitlab-mcp/)** 설정 가이드, 환경 변수, 전체 도구 레퍼런스는 호스팅된 문서 사이트에서 확인할 수 있습니다.
|
|
@@ -8,19 +11,35 @@
|
|
|
8
11
|
|
|
9
12
|
## @zereight/mcp-gitlab
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
**에이전트 워크플로우에 최적화된 GitLab MCP** — stdio, SSE, Streamable HTTP를 통해 프로젝트, 머지 리퀘스트, 이슈, 파이프라인, 위키, 릴리스, 마일스톤 등을 관리할 수 있습니다.
|
|
15
|
+
|
|
16
|
+
동일한 서버가 [`@zereight/gitlab-mcp`](https://www.npmjs.com/package/@zereight/gitlab-mcp) 이름으로도 배포됩니다 (npm 검색용 별칭).
|
|
12
17
|
|
|
13
18
|
PAT, OAuth, 읽기 전용 모드, 동적 API URL, 원격 인증을 지원하며 VS Code, Claude, Cursor, Copilot 및 기타 MCP 클라이언트에서 사용할 수 있습니다.
|
|
14
19
|
|
|
15
20
|
### 왜 이 GitLab MCP를 사용하나요?
|
|
16
21
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
+
- **217개 도구 + `discover_tools`** — 작은 toolset으로 시작하고, 런타임에 카테고리 활성화
|
|
23
|
+
- **MR 2단계 리뷰** — `list_merge_request_changed_files` → 배치 `get_merge_request_file_diff`
|
|
24
|
+
- **Agent Skill 내장** — `skills/gitlab-mcp/` 워크플로우 가이드
|
|
25
|
+
- **유연한 인증** — Personal Access Token, 로컬 OAuth2 브라우저 플로우, MCP OAuth 프록시, 요청별 원격 인증
|
|
26
|
+
- **여러 전송 방식** — 로컬 클라이언트용 stdio, 레거시 클라이언트용 SSE, 최신 원격 배포용 Streamable HTTP
|
|
27
|
+
- **클라이언트 친화적 설정** — Claude Code, Codex, Antigravity, OpenCode, Copilot, Cline, Roo Code, Cursor, Kilo Code, Amp Code 예시 제공
|
|
28
|
+
- **셀프 호스팅 대응** — 커스텀 GitLab 인스턴스, 프록시 설정, 동적 API URL 라우팅 지원
|
|
29
|
+
|
|
30
|
+
### 비교 요약
|
|
22
31
|
|
|
23
|
-
|
|
32
|
+
| | @zereight/mcp-gitlab | GitLab MCP A (커뮤니티 CQRS형) |
|
|
33
|
+
|---|----------------------|--------------------------------|
|
|
34
|
+
| **적합한 경우** | AI 에이전트 워크플로우 | 엔터프라이즈 멀티 인스턴스 / 그룹형 도구 |
|
|
35
|
+
| **도구 모델** | ~217개 세분화 도구 + `discover_tools` | ~50–60개 `browse_*` / `manage_*` 그룹 도구 |
|
|
36
|
+
| **MR 리뷰** | 2단계 배치 diff | 서버마다 다름 |
|
|
37
|
+
| **Node.js** | >=18 | 보통 >=24 |
|
|
38
|
+
| **라이선스** | MIT | 서버마다 다름 |
|
|
39
|
+
|
|
40
|
+
[전체 비교 →](./docs/comparison/community-gitlab-mcp-a.md)
|
|
41
|
+
|
|
42
|
+
빠른 시작: 아래에서 Personal Access Token 또는 OAuth2 설정 중 하나를 선택하고 `@zereight/mcp-gitlab`(또는 `@zereight/gitlab-mcp`)을 설치한 뒤 MCP 클라이언트 설정에서 `zereight-mcp-gitlab`을 사용하세요.
|
|
24
43
|
|
|
25
44
|
### 클라이언트 설정 가이드
|
|
26
45
|
|
|
@@ -62,6 +81,7 @@ PAT, OAuth, 읽기 전용 모드, 동적 API URL, 원격 인증을 지원하며
|
|
|
62
81
|
- **Cursor**: [Cursor 설정 가이드](./docs/clients/cursor.md)
|
|
63
82
|
- **Factory AI Droid / OpenClaw / OpenCode 스타일 클라이언트**: [JSON 기반 MCP 클라이언트 설정 가이드](./docs/clients/json-clients.md)
|
|
64
83
|
- **OAuth 브라우저 플로우 상세**: [OAuth2 인증 설정 가이드](./docs/auth/oauth-setup.md)
|
|
84
|
+
- **localhost callback 없이 OAuth** (SSO, 원격 셸, 백그라운드 클라이언트): `zereight-mcp-gitlab auth`를 먼저 실행하세요 (GitLab 17.9+ device flow; 17.2–17.8은 `oauth2_device_grant_flow` 필요). 그다음 서버는 `GITLAB_USE_OAUTH=true`로 시작합니다. [독립 device-flow 커맨드](./docs/auth/oauth-setup.md#standalone-device-flow-auth-command)를 참고하세요.
|
|
65
85
|
|
|
66
86
|
가장 단순한 로컬 설정은 Personal Access Token으로 시작하세요. 브라우저 기반 로컬 인증은 OAuth2를 사용하세요. 원격 또는 멀티 유저 배포는 아래 MCP OAuth 및 원격 인증 섹션을 참고하세요.
|
|
67
87
|
|
|
@@ -72,15 +92,17 @@ brew tap zereight/gitlab-mcp https://github.com/zereight/gitlab-mcp
|
|
|
72
92
|
brew install zereight/gitlab-mcp/zereight-mcp-gitlab
|
|
73
93
|
```
|
|
74
94
|
|
|
75
|
-
npm으로 설치할 수도
|
|
95
|
+
npm으로 설치할 수도 있습니다 (두 패키지명 모두 동일한 서버입니다):
|
|
76
96
|
|
|
77
97
|
```shell
|
|
78
98
|
npm install -g @zereight/mcp-gitlab
|
|
99
|
+
# 또는
|
|
100
|
+
npm install -g @zereight/gitlab-mcp
|
|
79
101
|
```
|
|
80
102
|
|
|
81
103
|
예시는 기존 `mcp-gitlab`보다 충돌 가능성이 낮은 `zereight-mcp-gitlab` 별칭을 사용합니다. MCP 클라이언트가 찾지 못하면 `which zereight-mcp-gitlab`의 절대 경로를 사용하세요.
|
|
82
104
|
|
|
83
|
-
전역 설치를 쓰지 않으려면 `npx -y @zereight/mcp-gitlab@2.1.
|
|
105
|
+
전역 설치를 쓰지 않으려면 `npx -y @zereight/mcp-gitlab@2.1.51`처럼 직전 안정 버전(문서가 권장하는 버전)으로 고정하세요. 항상 최신 버전을 원하면 `npx -y @zereight/mcp-gitlab@latest`를 사용하세요. 새 버전이 나오면 서버가 시작 시 stderr로 알려줍니다(`GITLAB_DISABLE_VERSION_CHECK=true`로 비활성화 가능).
|
|
84
106
|
|
|
85
107
|
#### CLI 인자 사용하기(환경 변수 문제가 있는 클라이언트용)
|
|
86
108
|
|
|
@@ -111,8 +133,10 @@ npm install -g @zereight/mcp-gitlab
|
|
|
111
133
|
|
|
112
134
|
CLI 인자는 환경 변수보다 우선합니다.
|
|
113
135
|
|
|
136
|
+
`zereight-mcp-gitlab auth`는 MCP 서버 플래그가 아니라 서브커맨드입니다. GitLab device flow를 실행한 뒤 종료합니다. [CLI 인자](./docs/getting-started/cli-arguments.md#auth)를 참고하세요.
|
|
137
|
+
|
|
114
138
|
> **세밀한 도구 필터링:** `GITLAB_PERMISSION_MODE=modify`로 생성/수정은 허용하고 모든 삭제 도구를
|
|
115
|
-
>
|
|
139
|
+
> 차단하거나(`execute_graphql` 삭제 mutation과 `push_files`의 `delete`/`move` 포함), `GITLAB_PERMISSION_MODE=readonly`로 읽기 전용으로 운영할 수 있습니다. 또한
|
|
116
140
|
> `GITLAB_TOOLSETS=<group,…>`로 도구 그룹을 활성화하고, `GITLAB_TOOLS=<tool,…>`로 개별 도구만
|
|
117
141
|
> 허용하며(예: 읽기 도구 + 특정 쓰기 도구 몇 개), `GITLAB_DENIED_TOOLS_REGEX`로 패턴 차단할 수
|
|
118
142
|
> 있습니다. 레거시 `USE_GITLAB_WIKI` / `USE_MILESTONE` / `USE_PIPELINE` 플래그는 하위 호환용으로만
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# GitLab MCP Server
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@zereight/mcp-gitlab)
|
|
4
|
+
[](vscode:mcp/install?%7B%22name%22%3A%22zereight.gitlab-mcp%22%2C%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40zereight%2Fmcp-gitlab%40latest%22%5D%2C%22env%22%3A%7B%22GITLAB_PERSONAL_ACCESS_TOKEN%22%3A%22%24%7Binput%3Agitlab-token%7D%22%2C%22GITLAB_API_URL%22%3A%22https%3A%2F%2Fgitlab.com%2Fapi%2Fv4%22%2C%22GITLAB_PERMISSION_MODE%22%3A%22full%22%7D%7D)
|
|
3
5
|
[](https://mcptoplist.com/server/io.github.zereight%2Fgitlab-mcp) [](https://mcpindex.ai/server/io-github-zereight-gitlab-mcp)
|
|
4
6
|
|
|
5
7
|
[English](./README.md) | [한국어](./README.ko.md) | [简体中文](./README.zh-CN.md)
|
|
@@ -10,19 +12,35 @@
|
|
|
10
12
|
|
|
11
13
|
## @zereight/mcp-gitlab
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
**Agent-workflow-optimized GitLab MCP** — manage projects, merge requests, issues, pipelines, wiki, releases, tags, milestones, and more through stdio, SSE, and Streamable HTTP.
|
|
16
|
+
|
|
17
|
+
Also published as [`@zereight/gitlab-mcp`](https://www.npmjs.com/package/@zereight/gitlab-mcp) (search-friendly alias for the same server).
|
|
14
18
|
|
|
15
19
|
Supports PAT, OAuth, read-only mode, dynamic API URLs, and remote authorization for VS Code, Claude, Cursor, Copilot, and other MCP clients.
|
|
16
20
|
|
|
17
21
|
### Why use this GitLab MCP?
|
|
18
22
|
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
23
|
+
- **217 tools + `discover_tools`** — start with a small toolset; activate more at runtime without CQRS-style grouping
|
|
24
|
+
- **MR 2-step review** — `list_merge_request_changed_files` → batched `get_merge_request_file_diff`
|
|
25
|
+
- **Agent Skill built in** — workflow guidance in `skills/gitlab-mcp/`
|
|
26
|
+
- **Flexible auth** — Personal Access Token, local OAuth2 browser flow, MCP OAuth proxy, and per-request remote authorization
|
|
27
|
+
- **Multiple transports** — stdio for local clients, SSE for legacy clients, and Streamable HTTP for modern remote deployments
|
|
28
|
+
- **Client-friendly setup** — examples for Claude Code, Codex, Antigravity, OpenCode, Copilot, Cline, Roo Code, Cursor, Kilo Code, and Amp Code
|
|
29
|
+
- **Self-hosted ready** — works with custom GitLab instances, proxy settings, and dynamic API URL routing
|
|
30
|
+
|
|
31
|
+
### How we compare
|
|
32
|
+
|
|
33
|
+
| | @zereight/mcp-gitlab | GitLab MCP A (community CQRS-style) |
|
|
34
|
+
|---|----------------------|-------------------------------------|
|
|
35
|
+
| **Best for** | AI agent workflows | Enterprise multi-instance / grouped tools |
|
|
36
|
+
| **Tool model** | ~217 granular tools + `discover_tools` | ~50–60 grouped `browse_*` / `manage_*` tools |
|
|
37
|
+
| **MR review** | 2-step batched diff | Varies |
|
|
38
|
+
| **Node.js** | >=18 | Often >=24 |
|
|
39
|
+
| **License** | MIT | Varies |
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
[Full comparison →](./docs/comparison/community-gitlab-mcp-a.md)
|
|
42
|
+
|
|
43
|
+
Quick start: choose either Personal Access Token or OAuth2 setup below, install `@zereight/mcp-gitlab` (or `@zereight/gitlab-mcp`), and use `zereight-mcp-gitlab` in your MCP client configuration.
|
|
26
44
|
|
|
27
45
|
### Client Setup Guides
|
|
28
46
|
|
|
@@ -64,6 +82,7 @@ The server supports four authentication methods:
|
|
|
64
82
|
- **Cursor**: see [Cursor Setup Guide](./docs/clients/cursor.md)
|
|
65
83
|
- **Factory AI Droid / OpenClaw / OpenCode style clients**: see [JSON-Based MCP Clients Setup Guide](./docs/clients/json-clients.md)
|
|
66
84
|
- **OAuth browser flow details**: see [OAuth2 Authentication Setup Guide](./docs/auth/oauth-setup.md)
|
|
85
|
+
- **OAuth without a localhost callback** (SSO, remote shell, background clients): run `zereight-mcp-gitlab auth` (GitLab 17.9+ device flow; 17.2–17.8 need `oauth2_device_grant_flow`), then start the server with `GITLAB_USE_OAUTH=true`. See [standalone device-flow command](./docs/auth/oauth-setup.md#standalone-device-flow-auth-command).
|
|
67
86
|
|
|
68
87
|
For the simplest local setup, start with a Personal Access Token. For browser-based local auth, use OAuth2. For remote or multi-user deployments, continue to the MCP OAuth and Remote Authorization sections later in this README.
|
|
69
88
|
|
|
@@ -74,15 +93,17 @@ brew tap zereight/gitlab-mcp https://github.com/zereight/gitlab-mcp
|
|
|
74
93
|
brew install zereight/gitlab-mcp/zereight-mcp-gitlab
|
|
75
94
|
```
|
|
76
95
|
|
|
77
|
-
Or with npm:
|
|
96
|
+
Or with npm (either package name installs the same server):
|
|
78
97
|
|
|
79
98
|
```shell
|
|
80
99
|
npm install -g @zereight/mcp-gitlab
|
|
100
|
+
# or
|
|
101
|
+
npm install -g @zereight/gitlab-mcp
|
|
81
102
|
```
|
|
82
103
|
|
|
83
104
|
The examples use `zereight-mcp-gitlab`, a less collision-prone alias for the legacy `mcp-gitlab` binary. If your MCP client cannot find it, use the absolute path from `which zereight-mcp-gitlab`.
|
|
84
105
|
|
|
85
|
-
No global install? Pin `npx` to the previous stable release (the version these docs recommend), for example `npx -y @zereight/mcp-gitlab@2.1.
|
|
106
|
+
No global install? Pin `npx` to the previous stable release (the version these docs recommend), for example `npx -y @zereight/mcp-gitlab@2.1.51`. If you always want the newest release, use `npx -y @zereight/mcp-gitlab@latest` instead. The server prints a notice to stderr on startup when a newer version is available (disable with `GITLAB_DISABLE_VERSION_CHECK=true`).
|
|
86
107
|
|
|
87
108
|
#### Using CLI Arguments (for clients with env var issues)
|
|
88
109
|
|
|
@@ -113,8 +134,11 @@ Some MCP clients (like GitHub Copilot CLI) have issues with environment variable
|
|
|
113
134
|
|
|
114
135
|
CLI arguments take precedence over environment variables.
|
|
115
136
|
|
|
137
|
+
`zereight-mcp-gitlab auth` is a subcommand (not an MCP server flag). It runs GitLab device flow and exits. See [CLI Arguments](./docs/getting-started/cli-arguments.md#auth).
|
|
138
|
+
|
|
116
139
|
> **Fine-grained tool filtering:** use `GITLAB_PERMISSION_MODE=modify` to allow create/update while
|
|
117
|
-
> blocking every delete tool (including delete mutations through `execute_graphql`
|
|
140
|
+
> blocking every delete tool (including delete mutations through `execute_graphql` and
|
|
141
|
+
> `push_files` `delete`/`move` actions), or
|
|
118
142
|
> `GITLAB_PERMISSION_MODE=readonly` for read-only access. You can also
|
|
119
143
|
> enable toolset groups with `GITLAB_TOOLSETS=<group,…>`, allow-list individual tools with
|
|
120
144
|
> `GITLAB_TOOLS=<tool,…>` (e.g. read-only groups plus a few specific write tools), and
|
package/README.zh-CN.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# GitLab MCP Server
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@zereight/mcp-gitlab)
|
|
4
|
+
[](vscode:mcp/install?%7B%22name%22%3A%22zereight.gitlab-mcp%22%2C%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40zereight%2Fmcp-gitlab%40latest%22%5D%2C%22env%22%3A%7B%22GITLAB_PERSONAL_ACCESS_TOKEN%22%3A%22%24%7Binput%3Agitlab-token%7D%22%2C%22GITLAB_API_URL%22%3A%22https%3A%2F%2Fgitlab.com%2Fapi%2Fv4%22%2C%22GITLAB_PERMISSION_MODE%22%3A%22full%22%7D%7D)
|
|
5
|
+
|
|
3
6
|
[English](./README.md) | [한국어](./README.ko.md) | [简体中文](./README.zh-CN.md)
|
|
4
7
|
|
|
5
8
|
📖 **[文档 →](https://zereight.github.io/gitlab-mcp/)** 设置指南、环境变量和完整工具参考请查看托管文档站点。
|
|
@@ -8,19 +11,35 @@
|
|
|
8
11
|
|
|
9
12
|
## @zereight/mcp-gitlab
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
**面向 AI 代理工作流优化的 GitLab MCP** — 可通过 stdio、SSE 和 Streamable HTTP 管理项目、合并请求、议题、流水线、Wiki、发布、里程碑等。
|
|
15
|
+
|
|
16
|
+
同一服务器也以 [`@zereight/gitlab-mcp`](https://www.npmjs.com/package/@zereight/gitlab-mcp) 名称发布(便于 npm 搜索的别名)。
|
|
12
17
|
|
|
13
18
|
支持 PAT、OAuth、只读模式、动态 API URL 和远程授权,可用于 VS Code、Claude、Cursor、Copilot 以及其他 MCP 客户端。
|
|
14
19
|
|
|
15
20
|
### 为什么使用这个 GitLab MCP?
|
|
16
21
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
+
- **217 个工具 + `discover_tools`** — 从小型 toolset 开始,运行时按需激活类别
|
|
23
|
+
- **MR 两步审查** — `list_merge_request_changed_files` → 批量 `get_merge_request_file_diff`
|
|
24
|
+
- **内置 Agent Skill** — `skills/gitlab-mcp/` 工作流指南
|
|
25
|
+
- **认证灵活** — Personal Access Token、本地 OAuth2 浏览器流程、MCP OAuth 代理、按请求远程授权
|
|
26
|
+
- **多种传输方式** — 本地客户端使用 stdio,旧客户端使用 SSE,现代远程部署使用 Streamable HTTP
|
|
27
|
+
- **客户端设置友好** — 提供 Claude Code、Codex、Antigravity、OpenCode、Copilot、Cline、Roo Code、Cursor、Kilo Code 和 Amp Code 示例
|
|
28
|
+
- **适合自托管** — 支持自定义 GitLab 实例、代理设置和动态 API URL 路由
|
|
29
|
+
|
|
30
|
+
### 对比摘要
|
|
22
31
|
|
|
23
|
-
|
|
32
|
+
| | @zereight/mcp-gitlab | GitLab MCP A(社区 CQRS 型) |
|
|
33
|
+
|---|----------------------|------------------------------|
|
|
34
|
+
| **更适合** | AI 代理工作流 | 企业多实例 / 分组工具 |
|
|
35
|
+
| **工具模型** | ~217 个细粒度工具 + `discover_tools` | ~50–60 个 `browse_*` / `manage_*` 分组工具 |
|
|
36
|
+
| **MR 审查** | 两步批量 diff | 因服务器而异 |
|
|
37
|
+
| **Node.js** | >=18 | 通常 >=24 |
|
|
38
|
+
| **许可证** | MIT | 因服务器而异 |
|
|
39
|
+
|
|
40
|
+
[完整对比 →](./docs/comparison/community-gitlab-mcp-a.md)
|
|
41
|
+
|
|
42
|
+
快速开始:在下面选择 Personal Access Token 或 OAuth2 设置,安装 `@zereight/mcp-gitlab`(或 `@zereight/gitlab-mcp`),并在 MCP 客户端配置中使用 `zereight-mcp-gitlab`。
|
|
24
43
|
|
|
25
44
|
### 客户端设置指南
|
|
26
45
|
|
|
@@ -62,6 +81,7 @@
|
|
|
62
81
|
- **Cursor**:[Cursor 设置指南](./docs/clients/cursor.md)
|
|
63
82
|
- **Factory AI Droid / OpenClaw / OpenCode 风格客户端**:[基于 JSON 的 MCP 客户端设置指南](./docs/clients/json-clients.md)
|
|
64
83
|
- **OAuth 浏览器流程详情**:[OAuth2 认证设置指南](./docs/auth/oauth-setup.md)
|
|
84
|
+
- **无需 localhost callback 的 OAuth**(SSO、远程 shell、后台客户端):先运行 `zereight-mcp-gitlab auth`(GitLab 17.9+ device flow;17.2–17.8 需 `oauth2_device_grant_flow`),再以 `GITLAB_USE_OAUTH=true` 启动服务器。参见[独立 device-flow 命令](./docs/auth/oauth-setup.md#standalone-device-flow-auth-command)。
|
|
65
85
|
|
|
66
86
|
最简单的本地设置可以从 Personal Access Token 开始。基于浏览器的本地认证使用 OAuth2。远程或多用户部署请继续查看下面的 MCP OAuth 和远程授权部分。
|
|
67
87
|
|
|
@@ -72,15 +92,17 @@ brew tap zereight/gitlab-mcp https://github.com/zereight/gitlab-mcp
|
|
|
72
92
|
brew install zereight/gitlab-mcp/zereight-mcp-gitlab
|
|
73
93
|
```
|
|
74
94
|
|
|
75
|
-
也可以使用 npm
|
|
95
|
+
也可以使用 npm 安装(两个包名安装的是同一服务器):
|
|
76
96
|
|
|
77
97
|
```shell
|
|
78
98
|
npm install -g @zereight/mcp-gitlab
|
|
99
|
+
# 或
|
|
100
|
+
npm install -g @zereight/gitlab-mcp
|
|
79
101
|
```
|
|
80
102
|
|
|
81
103
|
示例使用 `zereight-mcp-gitlab`,这是比旧的 `mcp-gitlab` 更不容易冲突的别名。如果 MCP 客户端找不到它,请使用 `which zereight-mcp-gitlab` 输出的绝对路径。
|
|
82
104
|
|
|
83
|
-
如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.1.
|
|
105
|
+
如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.1.51`。如果始终想使用最新版本,请改用 `npx -y @zereight/mcp-gitlab@latest`。有新版本发布时,服务器会在启动时通过 stderr 提示(可用 `GITLAB_DISABLE_VERSION_CHECK=true` 关闭)。
|
|
84
106
|
|
|
85
107
|
#### 使用 CLI 参数(适用于环境变量有问题的客户端)
|
|
86
108
|
|
|
@@ -111,7 +133,10 @@ npm install -g @zereight/mcp-gitlab
|
|
|
111
133
|
|
|
112
134
|
CLI 参数优先于环境变量。
|
|
113
135
|
|
|
114
|
-
|
|
136
|
+
`zereight-mcp-gitlab auth` 是子命令,不是 MCP 服务器参数。它运行 GitLab device flow 后退出。参见 [CLI 参数](./docs/getting-started/cli-arguments.md#auth)。
|
|
137
|
+
|
|
138
|
+
> **细粒度工具过滤:**使用 `GITLAB_PERMISSION_MODE=modify` 允许创建/更新并阻止所有删除工具
|
|
139
|
+
> (包括通过 `execute_graphql` 的删除 mutation 以及 `push_files` 的 `delete`/`move`),
|
|
115
140
|
> 或使用 `GITLAB_PERMISSION_MODE=readonly` 只读运行。还可以用
|
|
116
141
|
> `GITLAB_TOOLSETS=<group,…>` 启用工具分组,用 `GITLAB_TOOLS=<tool,…>` 白名单启用单个工具
|
|
117
142
|
> (例如:只读分组 + 少数几个写工具),用 `GITLAB_DENIED_TOOLS_REGEX` 按正则屏蔽工具。
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import * as os from "os";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { GitLabOAuth } from "./oauth.js";
|
|
4
|
+
import { createLogger } from "./utils/logger.js";
|
|
5
|
+
const logger = createLogger("gitlab-mcp-auth");
|
|
6
|
+
const DEFAULT_API_URL = "https://gitlab.com";
|
|
7
|
+
const DEFAULT_REDIRECT_URI = "http://127.0.0.1:8888/callback";
|
|
8
|
+
export const AUTH_CLI_HELP = `Usage: zereight-mcp-gitlab auth [options]
|
|
9
|
+
|
|
10
|
+
Run GitLab OAuth Device Authorization Grant (GitLab 17.9+; 17.2–17.8 need
|
|
11
|
+
oauth2_device_grant_flow) and store a token at the same path used by
|
|
12
|
+
GITLAB_USE_OAUTH (default ~/.gitlab-mcp-token.json).
|
|
13
|
+
|
|
14
|
+
This does not replace the local browser callback flow. After auth succeeds,
|
|
15
|
+
start the MCP server with GITLAB_USE_OAUTH=true and GITLAB_OAUTH_CLIENT_ID.
|
|
16
|
+
If you used --token-path, set GITLAB_OAUTH_TOKEN_PATH to the same path.
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--client-id <id> OAuth application ID (or GITLAB_OAUTH_CLIENT_ID)
|
|
20
|
+
--api-url <url> GitLab API URL (or GITLAB_API_URL). Default: https://gitlab.com
|
|
21
|
+
--token-path <path> Token file path (or GITLAB_OAUTH_TOKEN_PATH)
|
|
22
|
+
-h, --help Show this help
|
|
23
|
+
`;
|
|
24
|
+
function readFlag(argv, name) {
|
|
25
|
+
const equalsPrefix = `--${name}=`;
|
|
26
|
+
for (let i = 0; i < argv.length; i++) {
|
|
27
|
+
const arg = argv[i];
|
|
28
|
+
if (arg.startsWith(equalsPrefix)) {
|
|
29
|
+
const value = arg.slice(equalsPrefix.length);
|
|
30
|
+
return value === "" ? undefined : value;
|
|
31
|
+
}
|
|
32
|
+
if (arg === `--${name}`) {
|
|
33
|
+
const next = argv[i + 1];
|
|
34
|
+
if (next && !next.startsWith("-")) {
|
|
35
|
+
return next;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
function wantsHelp(argv) {
|
|
43
|
+
return argv.includes("--help") || argv.includes("-h");
|
|
44
|
+
}
|
|
45
|
+
function isReadOnlyMode(argv, env) {
|
|
46
|
+
const readOnly = readFlag(argv, "read-only") ?? env.GITLAB_READ_ONLY_MODE;
|
|
47
|
+
if (readOnly === "true") {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return (readFlag(argv, "permission-mode") ?? env.GITLAB_PERMISSION_MODE) === "readonly";
|
|
51
|
+
}
|
|
52
|
+
export function gitlabOriginFromApiUrl(apiUrl) {
|
|
53
|
+
return apiUrl.replace(/\/api\/v4\/?$/, "");
|
|
54
|
+
}
|
|
55
|
+
export async function runAuthCommandAsync(input = {}) {
|
|
56
|
+
const argv = input.argv ?? process.argv;
|
|
57
|
+
const env = input.env ?? process.env;
|
|
58
|
+
const stdout = input.stdout ?? process.stdout;
|
|
59
|
+
if (wantsHelp(argv)) {
|
|
60
|
+
stdout.write(AUTH_CLI_HELP);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const clientId = readFlag(argv, "client-id") ?? env.GITLAB_OAUTH_CLIENT_ID;
|
|
64
|
+
if (!clientId) {
|
|
65
|
+
throw new Error("Missing OAuth client ID. Pass --client-id or set GITLAB_OAUTH_CLIENT_ID.");
|
|
66
|
+
}
|
|
67
|
+
const apiUrl = readFlag(argv, "api-url") ?? env.GITLAB_API_URL ?? DEFAULT_API_URL;
|
|
68
|
+
const tokenPath = readFlag(argv, "token-path") ??
|
|
69
|
+
env.GITLAB_OAUTH_TOKEN_PATH ??
|
|
70
|
+
path.join(os.homedir(), ".gitlab-mcp-token.json");
|
|
71
|
+
const gitlabUrl = gitlabOriginFromApiUrl(apiUrl);
|
|
72
|
+
const scopes = [isReadOnlyMode(argv, env) ? "read_api" : "api"];
|
|
73
|
+
const oauth = new GitLabOAuth({
|
|
74
|
+
clientId,
|
|
75
|
+
clientSecret: env.GITLAB_OAUTH_CLIENT_SECRET,
|
|
76
|
+
redirectUri: env.GITLAB_OAUTH_REDIRECT_URI || DEFAULT_REDIRECT_URI,
|
|
77
|
+
gitlabUrl,
|
|
78
|
+
scopes,
|
|
79
|
+
tokenStoragePath: tokenPath,
|
|
80
|
+
});
|
|
81
|
+
logger.info("Starting GitLab device authorization (no browser will be opened)");
|
|
82
|
+
await oauth.runDeviceFlowAsync({
|
|
83
|
+
fetchImpl: input.fetchImpl,
|
|
84
|
+
sleepAsync: input.sleepAsync,
|
|
85
|
+
onUserCode: info => {
|
|
86
|
+
const visitUrl = info.verificationUriComplete ?? info.verificationUri;
|
|
87
|
+
stdout.write(`Visit: ${visitUrl}\n`);
|
|
88
|
+
stdout.write(`Code: ${info.userCode}\n`);
|
|
89
|
+
stdout.write("Waiting for authorization...\n");
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
stdout.write(`Token saved to ${tokenPath}\n`);
|
|
93
|
+
stdout.write("Start the MCP server with GITLAB_USE_OAUTH=true and the same GITLAB_OAUTH_CLIENT_ID.\n" +
|
|
94
|
+
"If you used --token-path, set GITLAB_OAUTH_TOKEN_PATH to that path as well.\n");
|
|
95
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const SCRIPT_PATH_PATTERN = /\.(cjs|mjs|js|cts|mts|ts)$/;
|
|
2
|
+
const FLAGS_WITHOUT_VALUE = new Set(["--help", "-h"]);
|
|
3
|
+
function isScriptPath(arg) {
|
|
4
|
+
return SCRIPT_PATH_PATTERN.test(arg);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Read the first positional CLI command (not a flag, not a script path).
|
|
8
|
+
* `tsx index.ts auth` and `node build/index.js auth` both resolve to `auth`.
|
|
9
|
+
* Space-separated option values such as `--api-url https://...` are skipped.
|
|
10
|
+
* Flags such as --token stay on the MCP server path.
|
|
11
|
+
*/
|
|
12
|
+
export function getPositionalCliCommand(argv) {
|
|
13
|
+
const args = argv.slice(2);
|
|
14
|
+
for (let i = 0; i < args.length; i++) {
|
|
15
|
+
const arg = args[i];
|
|
16
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (arg.startsWith("-")) {
|
|
20
|
+
if (!FLAGS_WITHOUT_VALUE.has(arg) &&
|
|
21
|
+
i + 1 < args.length &&
|
|
22
|
+
!args[i + 1].startsWith("-")) {
|
|
23
|
+
i += 1;
|
|
24
|
+
}
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (isScriptPath(arg)) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
return arg;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
package/build/index.js
CHANGED
|
@@ -75,6 +75,8 @@ import { CookieJar, parse as parseCookie } from "tough-cookie";
|
|
|
75
75
|
import { URL } from "node:url";
|
|
76
76
|
import { z } from "zod";
|
|
77
77
|
import { initializeOAuthClient } from "./oauth.js";
|
|
78
|
+
import { getPositionalCliCommand } from "./cli-command.js";
|
|
79
|
+
import { runAuthCommandAsync } from "./auth-cli.js";
|
|
78
80
|
import { createGitLabOAuthProvider } from "./oauth-proxy.js";
|
|
79
81
|
import { mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
80
82
|
import rateLimit, { ipKeyGenerator } from "express-rate-limit";
|
|
@@ -91,6 +93,7 @@ import { normalizeGitLabApiUrl } from "./utils/url.js";
|
|
|
91
93
|
import { estimateMergeCommitCount, filterDiffsByPatterns, openSafeOutputWriteStream, readSafeExistingFile, summarizeWebhookEvents, } from "./utils/helpers.js";
|
|
92
94
|
import { graphqlQueryContainsWriteOperation, graphqlQueryContainsDeleteOperation, } from "./utils/graphql-query.js";
|
|
93
95
|
import { resolveNestedWikiUpdateTitle } from "./utils/wiki-title.js";
|
|
96
|
+
import { encodeRepoFilePayloadContent, fileOperationsIncludeDeleteOrMove, toGitLabCommitActions, } from "./utils/gitlab-commit-actions.js";
|
|
94
97
|
import { redactSensitiveGitLabFields } from "./utils/redact-sensitive.js";
|
|
95
98
|
import { checkForNewVersion } from "./utils/version-check.js";
|
|
96
99
|
import { assertGitLabVersionAtLeast } from "./utils/gitlab-version-gate.js";
|
|
@@ -1100,7 +1103,8 @@ if (GITLAB_MCP_OAUTH) {
|
|
|
1100
1103
|
}
|
|
1101
1104
|
logger.info("MCP OAuth enabled: GitLab OAuth proxy active (Private-Token/JOB-TOKEN headers bypass OAuth)");
|
|
1102
1105
|
}
|
|
1103
|
-
if (
|
|
1106
|
+
if (getPositionalCliCommand(process.argv) !== "auth" &&
|
|
1107
|
+
!REMOTE_AUTHORIZATION &&
|
|
1104
1108
|
!GITLAB_MCP_OAUTH &&
|
|
1105
1109
|
!USE_OAUTH &&
|
|
1106
1110
|
!GITLAB_PERSONAL_ACCESS_TOKEN &&
|
|
@@ -3291,12 +3295,6 @@ async function updateMergeRequestNote(projectId, mergeRequestIid, noteId, body)
|
|
|
3291
3295
|
const data = await response.json();
|
|
3292
3296
|
return GitLabDiscussionNoteSchema.parse(data);
|
|
3293
3297
|
}
|
|
3294
|
-
function encodeRepoFilePayloadContent(content) {
|
|
3295
|
-
if (GITLAB_REPO_FILE_ENCODING === "base64") {
|
|
3296
|
-
return Buffer.from(content).toString("base64");
|
|
3297
|
-
}
|
|
3298
|
-
return content;
|
|
3299
|
-
}
|
|
3300
3298
|
/**
|
|
3301
3299
|
* Create or update a file in a GitLab project
|
|
3302
3300
|
* 파일 생성 또는 업데이트
|
|
@@ -3309,15 +3307,18 @@ function encodeRepoFilePayloadContent(content) {
|
|
|
3309
3307
|
* @param {string} [previousPath] - The previous path of the file in case of rename
|
|
3310
3308
|
* @returns {Promise<GitLabCreateUpdateFileResponse>} The file update response
|
|
3311
3309
|
*/
|
|
3312
|
-
async function createOrUpdateFile(projectId, filePath, content, commitMessage, branch, previousPath, last_commit_id, commit_id) {
|
|
3310
|
+
async function createOrUpdateFile(projectId, filePath, content, commitMessage, branch, previousPath, last_commit_id, commit_id, encoding) {
|
|
3313
3311
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3314
3312
|
const encodedPath = encodeURIComponent(filePath);
|
|
3315
3313
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/files/${encodedPath}`);
|
|
3314
|
+
const resolvedEncoding = encoding ?? GITLAB_REPO_FILE_ENCODING;
|
|
3316
3315
|
const body = {
|
|
3317
3316
|
branch,
|
|
3318
|
-
content:
|
|
3317
|
+
content: encoding !== undefined
|
|
3318
|
+
? content
|
|
3319
|
+
: encodeRepoFilePayloadContent(content, GITLAB_REPO_FILE_ENCODING),
|
|
3319
3320
|
commit_message: commitMessage,
|
|
3320
|
-
encoding:
|
|
3321
|
+
encoding: resolvedEncoding,
|
|
3321
3322
|
...(previousPath ? { previous_path: previousPath } : {}),
|
|
3322
3323
|
};
|
|
3323
3324
|
// Check if file exists
|
|
@@ -3387,12 +3388,7 @@ async function createCommit(projectId, message, branch, actions) {
|
|
|
3387
3388
|
body: JSON.stringify({
|
|
3388
3389
|
branch,
|
|
3389
3390
|
commit_message: message,
|
|
3390
|
-
actions: actions
|
|
3391
|
-
action: "create",
|
|
3392
|
-
file_path: action.path,
|
|
3393
|
-
content: encodeRepoFilePayloadContent(action.content),
|
|
3394
|
-
encoding: GITLAB_REPO_FILE_ENCODING,
|
|
3395
|
-
})),
|
|
3391
|
+
actions: toGitLabCommitActions(actions, GITLAB_REPO_FILE_ENCODING),
|
|
3396
3392
|
}),
|
|
3397
3393
|
});
|
|
3398
3394
|
if (response.status === 400) {
|
|
@@ -7280,14 +7276,24 @@ async function handleToolCall(params) {
|
|
|
7280
7276
|
}
|
|
7281
7277
|
case "create_or_update_file": {
|
|
7282
7278
|
const args = CreateOrUpdateFileSchema.parse(params.arguments);
|
|
7283
|
-
const result = await createOrUpdateFile(args.project_id, args.file_path, args.content, args.commit_message, args.branch, args.previous_path, args.last_commit_id, args.commit_id);
|
|
7279
|
+
const result = await createOrUpdateFile(args.project_id, args.file_path, args.content, args.commit_message, args.branch, args.previous_path, args.last_commit_id, args.commit_id, args.encoding);
|
|
7284
7280
|
return {
|
|
7285
7281
|
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7286
7282
|
};
|
|
7287
7283
|
}
|
|
7288
7284
|
case "push_files": {
|
|
7289
7285
|
const args = PushFilesSchema.parse(params.arguments);
|
|
7290
|
-
|
|
7286
|
+
if (GITLAB_PERMISSION_MODE === "modify" &&
|
|
7287
|
+
fileOperationsIncludeDeleteOrMove(args.files)) {
|
|
7288
|
+
throw new Error("push_files does not allow delete or move actions in modify mode");
|
|
7289
|
+
}
|
|
7290
|
+
const result = await createCommit(args.project_id, args.commit_message, args.branch, args.files.map(f => ({
|
|
7291
|
+
path: f.file_path,
|
|
7292
|
+
content: f.content,
|
|
7293
|
+
action: f.action,
|
|
7294
|
+
encoding: f.encoding,
|
|
7295
|
+
previous_path: f.previous_path,
|
|
7296
|
+
})));
|
|
7291
7297
|
return {
|
|
7292
7298
|
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
7293
7299
|
};
|
|
@@ -10795,8 +10801,22 @@ async function runServer() {
|
|
|
10795
10801
|
process.exit(1);
|
|
10796
10802
|
}
|
|
10797
10803
|
}
|
|
10798
|
-
|
|
10799
|
-
|
|
10804
|
+
async function main() {
|
|
10805
|
+
if (getPositionalCliCommand(process.argv) === "auth") {
|
|
10806
|
+
try {
|
|
10807
|
+
await runAuthCommandAsync();
|
|
10808
|
+
process.exit(0);
|
|
10809
|
+
}
|
|
10810
|
+
catch (error) {
|
|
10811
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10812
|
+
process.stderr.write(`${message}\n`);
|
|
10813
|
+
logger.error({ err: error }, "auth command failed");
|
|
10814
|
+
process.exit(1);
|
|
10815
|
+
}
|
|
10816
|
+
}
|
|
10817
|
+
await runServer();
|
|
10818
|
+
}
|
|
10819
|
+
main().catch(error => {
|
|
10800
10820
|
logger.fatal({ err: error }, "Fatal error in main()");
|
|
10801
10821
|
process.exit(1);
|
|
10802
10822
|
});
|