@zereight/mcp-gitlab 2.3.8-next.143 → 2.3.8-next.145

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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # GitLab MCP Server
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@zereight/mcp-gitlab.svg)](https://www.npmjs.com/package/@zereight/mcp-gitlab)
4
- [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_GitLab_MCP-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect/mcp/install?name=zereight.gitlab-mcp&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22gitlab-token%22%2C%22description%22%3A%22GitLab%20Personal%20Access%20Token%22%2C%22password%22%3Atrue%7D%5D&config=%7B%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
- [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=zereight.gitlab-mcp&config=eyJ0eXBlIjoic3RkaW8iLCJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB6ZXJlaWdodC9tY3AtZ2l0bGFiQGxhdGVzdCJdLCJlbnYiOnsiR0lUTEFCX1BFUlNPTkFMX0FDQ0VTU19UT0tFTiI6IiR7ZW52OkdJVExBQl9QRVJTT05BTF9BQ0NFU1NfVE9LRU59IiwiR0lUTEFCX0FQSV9VUkwiOiJodHRwczovL2dpdGxhYi5jb20vYXBpL3Y0IiwiR0lUTEFCX1BFUk1JU1NJT05fTU9ERSI6ImZ1bGwifX0%3D)
4
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_GitLab_MCP-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect/mcp/install?name=zereight.gitlab-mcp&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22gitlab-token%22%2C%22description%22%3A%22GitLab%20Personal%20Access%20Token%22%2C%22password%22%3Atrue%7D%5D&config=%7B%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%7D%7D)
5
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=zereight.gitlab-mcp&config=eyJ0eXBlIjoic3RkaW8iLCJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB6ZXJlaWdodC9tY3AtZ2l0bGFiQGxhdGVzdCJdLCJlbnYiOnsiR0lUTEFCX1BFUlNPTkFMX0FDQ0VTU19UT0tFTiI6IiR7ZW52OkdJVExBQl9QRVJTT05BTF9BQ0NFU1NfVE9LRU59IiwiR0lUTEFCX0FQSV9VUkwiOiJodHRwczovL2dpdGxhYi5jb20vYXBpL3Y0In19)
6
6
 
7
7
  [English](./README.md) | [한국어](./README.ko.md) | [简体中文](./README.zh-CN.md) | [Português (Brasil)](./README.pt-BR.md)
8
8
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  `@zereight/mcp-gitlab`의 권장 로컬 PAT 설치입니다. 명령 인자는 [Claude Code](./docs/clients/claude-code.md)와 [Codex](./docs/clients/codex.md) 가이드와 같습니다.
12
12
 
13
- `zereight-mcp-gitlab` 바이너리를 먼저 설치하세요. `npm install -g @zereight/mcp-gitlab`, 또는 Homebrew `brew tap zereight/gitlab-mcp https://github.com/zereight/gitlab-mcp` 다음 `brew install zereight/gitlab-mcp/zereight-mcp-gitlab`.
13
+ `zereight-mcp-gitlab` 바이너리를 먼저 설치하세요. `npm install -g @zereight/mcp-gitlab`, 또는 Homebrew `brew tap zereight/gitlab-mcp https://github.com/zereight/gitlab-mcp` 다음 `brew install zereight/gitlab-mcp/zereight-mcp-gitlab`. 공식 MCP Registry 항목: [`io.github.zereight/gitlab-mcp`](https://registry.modelcontextprotocol.io/?q=io.github.zereight%2Fgitlab-mcp). 한 줄 설치: `npx -y @zereight/mcp-gitlab@latest`.
14
14
 
15
15
  토큰을 화면에 표시하지 않고 읽은 다음 export 하세요. 셸 기록에는 변수 이름만 남습니다.
16
16
 
@@ -55,7 +55,17 @@ PAT, OAuth, 읽기 전용 모드, 동적 API URL, 원격 인증을 지원하며
55
55
  | **Node.js** | >=18.17 | 보통 >=24 | stdio는 `mcp-remote`, Node.js 20+ |
56
56
  | **라이선스** | MIT | 서버마다 다름 | GitLab의 일부 |
57
57
 
58
- [GitLab MCP A 전체 비교 →](./docs/comparison/community-gitlab-mcp-a.md)
58
+ ### tools/list 크기 (2026-10-23 실측)
59
+
60
+ 클라이언트는 세션 시작 때 `tools/list` 전체를 모델에 보냅니다. 토큰 추정은 UTF-8 바이트 / 4입니다.
61
+
62
+ | 프로필 | 도구 수 | JSON 바이트 | 추정 토큰 |
63
+ | ------ | ------: | ----------: | --------: |
64
+ | 기본값 (`core` + `discover_tools`) | 39 | 48,069 | 12,018 |
65
+ | `GITLAB_TOOL_PROFILE=slim` | 38 | 47,199 | 11,800 |
66
+ | `GITLAB_TOOLSETS=all` | 275 | 274,896 | 68,724 |
67
+
68
+ 설명이나 스키마를 바꾼 뒤에는 `npm run test:mock` (`test/tools-list-budget.test.ts`)으로 다시 측정하세요.
59
69
 
60
70
  GitLab은 [공식 MCP 서버](https://docs.gitlab.com/user/model_context_protocol/mcp_server/)도 제공합니다.
61
71
 
@@ -192,7 +202,7 @@ CLI 인자는 환경 변수보다 우선합니다.
192
202
 
193
203
  `zereight-mcp-gitlab auth`는 MCP 서버 플래그가 아니라 서브커맨드입니다. GitLab device flow를 실행한 뒤 종료합니다. [CLI 인자](./docs/getting-started/cli-arguments.md#auth)를 참고하세요.
194
204
 
195
- 같은 바이너리는 `gh` 스타일 GitLab CLI이기도 합니다 (`tool <name>` 또는 `mr list` 같은 curated 명령). **MCP 등록은 필요 없습니다** — PAT를 설정하거나, `auth`를 실행한 뒤 `GITLAB_USE_OAUTH=true`를 설정하면 됩니다: [MCP 없이 CLI 사용하기](./docs/getting-started/cli-without-mcp.md). 전체 페이로드는 터미널에서 이 CLI로 읽습니다. MCP 응답은 대화에 남습니다. `GITLAB_MCP_COMPACT_RESULTS=true`(또는 `--compact-results`)를 켜면 큰 도구 응답이 미리보기와 CLI 명령으로 바뀝니다. Human CLI 자체는 compact 되지 않습니다. permission mode, toolsets, denied-tools regex, tool-policy는 MCP와 같습니다. destructive 도구와 `GITLAB_TOOL_POLICY_APPROVE` 도구는 `--yes`가 필요합니다. [Human CLI](./docs/getting-started/cli-arguments.md#human-cli)를 참고하세요.
205
+ 같은 바이너리는 `gh` 스타일 GitLab CLI이기도 합니다 (`tool <name>` 또는 `mr list` 같은 curated 명령). **MCP 등록은 필요 없습니다** — PAT를 설정하거나, `auth`를 실행한 뒤 `GITLAB_USE_OAUTH=true`를 설정하면 됩니다: [MCP 없이 CLI 사용하기](./docs/getting-started/cli-without-mcp.md). `setup --cursor` / `setup --claude`는 클라이언트 설정을 출력하고, `doctor`는 토큰·API URL·toolset·버전을 점검합니다. 전체 페이로드는 터미널에서 이 CLI로 읽습니다. MCP 응답은 대화에 남습니다. `GITLAB_MCP_COMPACT_RESULTS=true`(또는 `--compact-results`)를 켜면 큰 도구 응답이 미리보기와 CLI 명령으로 바뀝니다. Human CLI 자체는 compact 되지 않습니다. permission mode, toolsets, denied-tools regex, tool-policy는 MCP와 같습니다. destructive 도구와 `GITLAB_TOOL_POLICY_APPROVE` 도구는 `--yes`가 필요합니다. [Human CLI](./docs/getting-started/cli-arguments.md#human-cli)를 참고하세요.
196
206
 
197
207
  > **세밀한 도구 필터링:** `GITLAB_PERMISSION_MODE=modify`로 생성/수정은 허용하고 모든 삭제 도구와
198
208
  > 파괴적인 중단(teardown) 도구(`cancel_pipeline`, `cancel_pipeline_job`, `stop_environment`,
package/README.md CHANGED
@@ -4,8 +4,8 @@
4
4
  [![npm downloads](https://badgen.net/npm/dm/@zereight/mcp-gitlab)](https://www.npmjs.com/package/@zereight/mcp-gitlab)
5
5
  [![npm](https://img.shields.io/npm/v/@zereight/mcp-gitlab.svg)](https://www.npmjs.com/package/@zereight/mcp-gitlab)
6
6
  [![GitHub License](https://badgen.net/github/license/zereight/gitlab-mcp)](https://github.com/zereight/gitlab-mcp/blob/main/LICENSE)
7
- [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_GitLab_MCP-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect/mcp/install?name=zereight.gitlab-mcp&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22gitlab-token%22%2C%22description%22%3A%22GitLab%20Personal%20Access%20Token%22%2C%22password%22%3Atrue%7D%5D&config=%7B%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)
8
- [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=zereight.gitlab-mcp&config=eyJ0eXBlIjoic3RkaW8iLCJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB6ZXJlaWdodC9tY3AtZ2l0bGFiQGxhdGVzdCJdLCJlbnYiOnsiR0lUTEFCX1BFUlNPTkFMX0FDQ0VTU19UT0tFTiI6IiR7ZW52OkdJVExBQl9QRVJTT05BTF9BQ0NFU1NfVE9LRU59IiwiR0lUTEFCX0FQSV9VUkwiOiJodHRwczovL2dpdGxhYi5jb20vYXBpL3Y0IiwiR0lUTEFCX1BFUk1JU1NJT05fTU9ERSI6ImZ1bGwifX0%3D)
7
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_GitLab_MCP-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect/mcp/install?name=zereight.gitlab-mcp&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22gitlab-token%22%2C%22description%22%3A%22GitLab%20Personal%20Access%20Token%22%2C%22password%22%3Atrue%7D%5D&config=%7B%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%7D%7D)
8
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=zereight.gitlab-mcp&config=eyJ0eXBlIjoic3RkaW8iLCJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB6ZXJlaWdodC9tY3AtZ2l0bGFiQGxhdGVzdCJdLCJlbnYiOnsiR0lUTEFCX1BFUlNPTkFMX0FDQ0VTU19UT0tFTiI6IiR7ZW52OkdJVExBQl9QRVJTT05BTF9BQ0NFU1NfVE9LRU59IiwiR0lUTEFCX0FQSV9VUkwiOiJodHRwczovL2dpdGxhYi5jb20vYXBpL3Y0In19)
9
9
  [![Ask DeepWiki](https://img.shields.io/badge/DeepWiki-Ask-blue)](https://deepwiki.com/zereight/gitlab-mcp)
10
10
  [![MCP Toplist](https://mcptoplist.com/badge/io.github.zereight%2Fgitlab-mcp.svg)](https://mcptoplist.com/server/io.github.zereight%2Fgitlab-mcp) [![mcpindex](https://mcpindex.ai/api/v1/badge/io-github-zereight-gitlab-mcp)](https://mcpindex.ai/server/io-github-zereight-gitlab-mcp)
11
11
 
@@ -15,7 +15,7 @@
15
15
 
16
16
  Install `@zereight/mcp-gitlab` with the recommended local personal-access-token setup. These commands use the same arguments as the [Claude Code](./docs/clients/claude-code.md) and [Codex](./docs/clients/codex.md) guides.
17
17
 
18
- Install the `zereight-mcp-gitlab` binary first with `npm install -g @zereight/mcp-gitlab`, or Homebrew `brew tap zereight/gitlab-mcp https://github.com/zereight/gitlab-mcp` and `brew install zereight/gitlab-mcp/zereight-mcp-gitlab`.
18
+ Install the `zereight-mcp-gitlab` binary first with `npm install -g @zereight/mcp-gitlab`, or Homebrew `brew tap zereight/gitlab-mcp https://github.com/zereight/gitlab-mcp` and `brew install zereight/gitlab-mcp/zereight-mcp-gitlab`. Official MCP Registry listing: [`io.github.zereight/gitlab-mcp`](https://registry.modelcontextprotocol.io/?q=io.github.zereight%2Fgitlab-mcp). One-liner: `npx -y @zereight/mcp-gitlab@latest`.
19
19
 
20
20
  Read the token without echo, then export it. The shell history keeps the variable name.
21
21
 
@@ -60,7 +60,17 @@ Supports PAT, OAuth, read-only mode, dynamic API URLs, and remote authorization
60
60
  | **Node.js** | >=18.17 | Often >=24 | Node.js 20+ for stdio via `mcp-remote` |
61
61
  | **License** | MIT | Varies | Part of GitLab |
62
62
 
63
- [Full comparison with GitLab MCP A →](./docs/comparison/community-gitlab-mcp-a.md)
63
+ ### tools/list size (measured 2026-10-23)
64
+
65
+ A client sends the whole `tools/list` result to the model at session start. Token estimate is UTF-8 bytes / 4.
66
+
67
+ | Profile | Tools | JSON bytes | Est. tokens |
68
+ | ------- | ----: | ---------: | ----------: |
69
+ | Default (`core` + `discover_tools`) | 39 | 48,069 | 12,018 |
70
+ | `GITLAB_TOOL_PROFILE=slim` | 38 | 47,199 | 11,800 |
71
+ | `GITLAB_TOOLSETS=all` | 275 | 274,896 | 68,724 |
72
+
73
+ Re-measure with `npm run test:mock` (`test/tools-list-budget.test.ts`) after description or schema changes.
64
74
 
65
75
  GitLab also ships an [official MCP server](https://docs.gitlab.com/user/model_context_protocol/mcp_server/).
66
76
 
@@ -197,7 +207,7 @@ CLI arguments take precedence over environment variables.
197
207
 
198
208
  `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).
199
209
 
200
- The same binary is also a `gh`-style GitLab CLI (`tool <name>` or curated forms such as `mr list`). **No MCP registration is needed** — set a PAT, or run `auth` and set `GITLAB_USE_OAUTH=true`: see [CLI without MCP](./docs/getting-started/cli-without-mcp.md). Use it for the full payload in a terminal. MCP replies stay in the chat unless you set `GITLAB_MCP_COMPACT_RESULTS=true` (or `--compact-results`), which replaces oversized tool replies with a preview and a CLI command. Human CLI is never compacted. Same permission mode, toolsets, denied-tools regex, and tool-policy filters. Destructive tools and `GITLAB_TOOL_POLICY_APPROVE` tools need `--yes`. See [Human CLI](./docs/getting-started/cli-arguments.md#human-cli).
210
+ The same binary is also a `gh`-style GitLab CLI (`tool <name>` or curated forms such as `mr list`). **No MCP registration is needed** — set a PAT, or run `auth` and set `GITLAB_USE_OAUTH=true`: see [CLI without MCP](./docs/getting-started/cli-without-mcp.md). `setup --cursor` / `setup --claude` prints client config; `doctor` checks token, API URL, toolsets, and version. Use it for the full payload in a terminal. MCP replies stay in the chat unless you set `GITLAB_MCP_COMPACT_RESULTS=true` (or `--compact-results`), which replaces oversized tool replies with a preview and a CLI command. Human CLI is never compacted. Same permission mode, toolsets, denied-tools regex, and tool-policy filters. Destructive tools and `GITLAB_TOOL_POLICY_APPROVE` tools need `--yes`. See [Human CLI](./docs/getting-started/cli-arguments.md#human-cli).
201
211
 
202
212
  > **Fine-grained tool filtering:** use `GITLAB_PERMISSION_MODE=modify` to allow create/update while
203
213
  > blocking every delete tool and the destructive teardown tools (`cancel_pipeline`,
package/README.pt-BR.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # GitLab MCP Server
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@zereight/mcp-gitlab.svg)](https://www.npmjs.com/package/@zereight/mcp-gitlab)
4
- [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_GitLab_MCP-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect/mcp/install?name=zereight.gitlab-mcp&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22gitlab-token%22%2C%22description%22%3A%22GitLab%20Personal%20Access%20Token%22%2C%22password%22%3Atrue%7D%5D&config=%7B%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
- [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=zereight.gitlab-mcp&config=eyJ0eXBlIjoic3RkaW8iLCJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB6ZXJlaWdodC9tY3AtZ2l0bGFiQGxhdGVzdCJdLCJlbnYiOnsiR0lUTEFCX1BFUlNPTkFMX0FDQ0VTU19UT0tFTiI6IiR7ZW52OkdJVExBQl9QRVJTT05BTF9BQ0NFU1NfVE9LRU59IiwiR0lUTEFCX0FQSV9VUkwiOiJodHRwczovL2dpdGxhYi5jb20vYXBpL3Y0IiwiR0lUTEFCX1BFUk1JU1NJT05fTU9ERSI6ImZ1bGwifX0%3D)
4
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_GitLab_MCP-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect/mcp/install?name=zereight.gitlab-mcp&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22gitlab-token%22%2C%22description%22%3A%22GitLab%20Personal%20Access%20Token%22%2C%22password%22%3Atrue%7D%5D&config=%7B%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%7D%7D)
5
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=zereight.gitlab-mcp&config=eyJ0eXBlIjoic3RkaW8iLCJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB6ZXJlaWdodC9tY3AtZ2l0bGFiQGxhdGVzdCJdLCJlbnYiOnsiR0lUTEFCX1BFUlNPTkFMX0FDQ0VTU19UT0tFTiI6IiR7ZW52OkdJVExBQl9QRVJTT05BTF9BQ0NFU1NfVE9LRU59IiwiR0lUTEFCX0FQSV9VUkwiOiJodHRwczovL2dpdGxhYi5jb20vYXBpL3Y0In19)
6
6
 
7
7
  [English](./README.md) | [한국어](./README.ko.md) | [简体中文](./README.zh-CN.md) | [Português (Brasil)](./README.pt-BR.md)
8
8
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  Instalação local recomendada com PAT para `@zereight/mcp-gitlab`. Os argumentos são os mesmos dos guias de [Claude Code](./docs/clients/claude-code.md) e [Codex](./docs/clients/codex.md).
12
12
 
13
- Instale o binário `zereight-mcp-gitlab` antes. `npm install -g @zereight/mcp-gitlab`, ou Homebrew `brew tap zereight/gitlab-mcp https://github.com/zereight/gitlab-mcp` e `brew install zereight/gitlab-mcp/zereight-mcp-gitlab`.
13
+ Instale o binário `zereight-mcp-gitlab` antes. `npm install -g @zereight/mcp-gitlab`, ou Homebrew `brew tap zereight/gitlab-mcp https://github.com/zereight/gitlab-mcp` e `brew install zereight/gitlab-mcp/zereight-mcp-gitlab`. Listagem oficial no MCP Registry: [`io.github.zereight/gitlab-mcp`](https://registry.modelcontextprotocol.io/?q=io.github.zereight%2Fgitlab-mcp). Instalação em uma linha: `npx -y @zereight/mcp-gitlab@latest`.
14
14
 
15
15
  Leia o token sem eco e exporte. O histórico do shell fica só com o nome da variável.
16
16
 
@@ -55,7 +55,17 @@ Suporta PAT, OAuth, modo somente leitura, URLs de API dinâmicas e autorização
55
55
  | **Node.js** | >=18.17 | Frequentemente >=24 | Node.js 20+ para stdio via `mcp-remote` |
56
56
  | **Licença** | MIT | Varia | Parte do GitLab |
57
57
 
58
- [Comparação completa com o GitLab MCP A →](./docs/comparison/community-gitlab-mcp-a.md)
58
+ ### Tamanho do tools/list (medido em 2026-10-23)
59
+
60
+ O cliente envia o `tools/list` inteiro ao modelo no início da sessão. A estimativa de tokens é bytes UTF-8 / 4.
61
+
62
+ | Perfil | Ferramentas | Bytes JSON | Tokens estimados |
63
+ | ------ | ----------: | ---------: | ---------------: |
64
+ | Padrão (`core` + `discover_tools`) | 39 | 48,069 | 12,018 |
65
+ | `GITLAB_TOOL_PROFILE=slim` | 38 | 47,199 | 11,800 |
66
+ | `GITLAB_TOOLSETS=all` | 275 | 274,896 | 68,724 |
67
+
68
+ Após alterar descrições ou schemas, meça de novo com `npm run test:mock` (`test/tools-list-budget.test.ts`).
59
69
 
60
70
  O GitLab também inclui um [servidor MCP oficial](https://docs.gitlab.com/user/model_context_protocol/mcp_server/).
61
71
 
@@ -192,7 +202,7 @@ Os argumentos de CLI têm precedência sobre as variáveis de ambiente.
192
202
 
193
203
  `zereight-mcp-gitlab auth` é um subcomando, não uma flag do servidor MCP. Ele executa o device flow do GitLab e encerra. Veja [Argumentos de CLI](./docs/getting-started/cli-arguments.md#auth).
194
204
 
195
- O mesmo binário também é uma CLI do GitLab no estilo `gh` (`tool <name>` ou formas curadas como `mr list`). **Não é necessário registrar o MCP** — defina um PAT, ou execute `auth` e defina `GITLAB_USE_OAUTH=true`: veja [CLI sem MCP](./docs/getting-started/cli-without-mcp.md). Use-o para ver a carga completa no terminal. As respostas do MCP permanecem no chat, a menos que você defina `GITLAB_MCP_COMPACT_RESULTS=true` (ou `--compact-results`), o que substitui respostas grandes de ferramentas por uma prévia e um comando CLI. A CLI humana nunca é compactada. Os mesmos filtros de permission mode, toolsets, denied-tools regex e tool-policy se aplicam. Ferramentas destrutivas e ferramentas com `GITLAB_TOOL_POLICY_APPROVE` exigem `--yes`. Consulte [CLI Humana](./docs/getting-started/cli-arguments.md#human-cli).
205
+ O mesmo binário também é uma CLI do GitLab no estilo `gh` (`tool <name>` ou formas curadas como `mr list`). **Não é necessário registrar o MCP** — defina um PAT, ou execute `auth` e defina `GITLAB_USE_OAUTH=true`: veja [CLI sem MCP](./docs/getting-started/cli-without-mcp.md). `setup --cursor` / `setup --claude` imprime a configuração do cliente; `doctor` verifica token, URL da API, toolsets e versão. Use-o para ver a carga completa no terminal. As respostas do MCP permanecem no chat, a menos que você defina `GITLAB_MCP_COMPACT_RESULTS=true` (ou `--compact-results`), o que substitui respostas grandes de ferramentas por uma prévia e um comando CLI. A CLI humana nunca é compactada. Os mesmos filtros de permission mode, toolsets, denied-tools regex e tool-policy se aplicam. Ferramentas destrutivas e ferramentas com `GITLAB_TOOL_POLICY_APPROVE` exigem `--yes`. Consulte [CLI Humana](./docs/getting-started/cli-arguments.md#human-cli).
196
206
 
197
207
  > **Filtragem granular de ferramentas:** use `GITLAB_PERMISSION_MODE=modify` para permitir criação/atualização enquanto
198
208
  > bloqueia todas as ferramentas de exclusão e as ferramentas destrutivas de teardown (`cancel_pipeline`,
package/README.zh-CN.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # GitLab MCP Server
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@zereight/mcp-gitlab.svg)](https://www.npmjs.com/package/@zereight/mcp-gitlab)
4
- [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_GitLab_MCP-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect/mcp/install?name=zereight.gitlab-mcp&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22gitlab-token%22%2C%22description%22%3A%22GitLab%20Personal%20Access%20Token%22%2C%22password%22%3Atrue%7D%5D&config=%7B%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
- [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=zereight.gitlab-mcp&config=eyJ0eXBlIjoic3RkaW8iLCJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB6ZXJlaWdodC9tY3AtZ2l0bGFiQGxhdGVzdCJdLCJlbnYiOnsiR0lUTEFCX1BFUlNPTkFMX0FDQ0VTU19UT0tFTiI6IiR7ZW52OkdJVExBQl9QRVJTT05BTF9BQ0NFU1NfVE9LRU59IiwiR0lUTEFCX0FQSV9VUkwiOiJodHRwczovL2dpdGxhYi5jb20vYXBpL3Y0IiwiR0lUTEFCX1BFUk1JU1NJT05fTU9ERSI6ImZ1bGwifX0%3D)
4
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_GitLab_MCP-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect/mcp/install?name=zereight.gitlab-mcp&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22gitlab-token%22%2C%22description%22%3A%22GitLab%20Personal%20Access%20Token%22%2C%22password%22%3Atrue%7D%5D&config=%7B%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%7D%7D)
5
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=zereight.gitlab-mcp&config=eyJ0eXBlIjoic3RkaW8iLCJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB6ZXJlaWdodC9tY3AtZ2l0bGFiQGxhdGVzdCJdLCJlbnYiOnsiR0lUTEFCX1BFUlNPTkFMX0FDQ0VTU19UT0tFTiI6IiR7ZW52OkdJVExBQl9QRVJTT05BTF9BQ0NFU1NfVE9LRU59IiwiR0lUTEFCX0FQSV9VUkwiOiJodHRwczovL2dpdGxhYi5jb20vYXBpL3Y0In19)
6
6
 
7
7
  [English](./README.md) | [한국어](./README.ko.md) | [简体中文](./README.zh-CN.md) | [Português (Brasil)](./README.pt-BR.md)
8
8
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  下面是 `@zereight/mcp-gitlab` 的推荐本地 PAT 安装。命令参数与 [Claude Code](./docs/clients/claude-code.md) 和 [Codex](./docs/clients/codex.md) 指南相同。
12
12
 
13
- 先安装 `zereight-mcp-gitlab` 二进制。`npm install -g @zereight/mcp-gitlab`,或 Homebrew `brew tap zereight/gitlab-mcp https://github.com/zereight/gitlab-mcp` 然后 `brew install zereight/gitlab-mcp/zereight-mcp-gitlab`。
13
+ 先安装 `zereight-mcp-gitlab` 二进制。`npm install -g @zereight/mcp-gitlab`,或 Homebrew `brew tap zereight/gitlab-mcp https://github.com/zereight/gitlab-mcp` 然后 `brew install zereight/gitlab-mcp/zereight-mcp-gitlab`。官方 MCP Registry 条目:[`io.github.zereight/gitlab-mcp`](https://registry.modelcontextprotocol.io/?q=io.github.zereight%2Fgitlab-mcp)。一键安装:`npx -y @zereight/mcp-gitlab@latest`。
14
14
 
15
15
  先不回显地读取令牌,再 export。shell 历史里只保留变量名。
16
16
 
@@ -55,7 +55,17 @@ read -rs GITLAB_PERSONAL_ACCESS_TOKEN && export GITLAB_PERSONAL_ACCESS_TOKEN
55
55
  | **Node.js** | >=18.17 | 通常 >=24 | stdio 经 `mcp-remote`,需要 Node.js 20+ |
56
56
  | **许可证** | MIT | 因服务器而异 | GitLab 的一部分 |
57
57
 
58
- [与 GitLab MCP A 的完整对比 →](./docs/comparison/community-gitlab-mcp-a.md)
58
+ ### tools/list 体积(2026-10-23 实测)
59
+
60
+ 客户端会在会话开始时把整份 `tools/list` 发给模型。Token 估算为 UTF-8 字节 / 4。
61
+
62
+ | 配置 | 工具数 | JSON 字节 | 估算 token |
63
+ | ---- | -----: | --------: | ---------: |
64
+ | 默认(`core` + `discover_tools`) | 39 | 48,069 | 12,018 |
65
+ | `GITLAB_TOOL_PROFILE=slim` | 38 | 47,199 | 11,800 |
66
+ | `GITLAB_TOOLSETS=all` | 275 | 274,896 | 68,724 |
67
+
68
+ 更改描述或 schema 后,用 `npm run test:mock`(`test/tools-list-budget.test.ts`)重新测量。
59
69
 
60
70
  GitLab 还提供[官方 MCP 服务器](https://docs.gitlab.com/user/model_context_protocol/mcp_server/)。
61
71
 
@@ -192,7 +202,7 @@ CLI 参数优先于环境变量。
192
202
 
193
203
  `zereight-mcp-gitlab auth` 是子命令,不是 MCP 服务器参数。它运行 GitLab device flow 后退出。参见 [CLI 参数](./docs/getting-started/cli-arguments.md#auth)。
194
204
 
195
- 同一二进制也是 `gh` 风格的 GitLab CLI(`tool <name>`,或 `mr list` 这类 curated 命令)。**无需注册 MCP**——设置 PAT,或运行 `auth` 后设置 `GITLAB_USE_OAUTH=true`:参见 [无需 MCP 使用 CLI](./docs/getting-started/cli-without-mcp.md)。完整结果在终端用 CLI 查看。MCP 回复仍进入对话。设置 `GITLAB_MCP_COMPACT_RESULTS=true`(或 `--compact-results`)后,过大的工具回复会变成预览加上 CLI 命令。Human CLI 本身不会被 compact。permission mode、toolsets、denied-tools regex 和 tool-policy 与 MCP 相同。破坏性工具以及 `GITLAB_TOOL_POLICY_APPROVE` 工具需要 `--yes`。参见 [Human CLI](./docs/getting-started/cli-arguments.md#human-cli)。
205
+ 同一二进制也是 `gh` 风格的 GitLab CLI(`tool <name>`,或 `mr list` 这类 curated 命令)。**无需注册 MCP**——设置 PAT,或运行 `auth` 后设置 `GITLAB_USE_OAUTH=true`:参见 [无需 MCP 使用 CLI](./docs/getting-started/cli-without-mcp.md)。`setup --cursor` / `setup --claude` 会打印客户端配置;`doctor` 检查 token、API URL、toolset 和版本。完整结果在终端用 CLI 查看。MCP 回复仍进入对话。设置 `GITLAB_MCP_COMPACT_RESULTS=true`(或 `--compact-results`)后,过大的工具回复会变成预览加上 CLI 命令。Human CLI 本身不会被 compact。permission mode、toolsets、denied-tools regex 和 tool-policy 与 MCP 相同。破坏性工具以及 `GITLAB_TOOL_POLICY_APPROVE` 工具需要 `--yes`。参见 [Human CLI](./docs/getting-started/cli-arguments.md#human-cli)。
196
206
 
197
207
  > **细粒度工具过滤:**使用 `GITLAB_PERMISSION_MODE=modify` 允许创建/更新,同时阻止所有删除工具以及
198
208
  > 破坏性的拆除(teardown)工具(`cancel_pipeline`、`cancel_pipeline_job`、`stop_environment`、
package/build/cli/args.js CHANGED
@@ -7,7 +7,17 @@ export class CliUsageError extends Error {
7
7
  }
8
8
  }
9
9
  const SCRIPT_PATH_PATTERN = /\.(cjs|mjs|js|cts|mts|ts)$/;
10
- const FLAGS_WITHOUT_VALUE = new Set(["help", "yes", ...BOOLEAN_CLI_FLAG_NAMES]);
10
+ const FLAGS_WITHOUT_VALUE = new Set([
11
+ "help",
12
+ "yes",
13
+ "cursor",
14
+ "claude",
15
+ "write",
16
+ "skill",
17
+ "probe",
18
+ "oauth",
19
+ ...BOOLEAN_CLI_FLAG_NAMES,
20
+ ]);
11
21
  const GLOBAL_FLAG_NAMES = new Set([
12
22
  "help",
13
23
  "yes",
@@ -199,7 +199,11 @@ export function canonicalGroup(name) {
199
199
  return GROUP_ALIASES[name];
200
200
  }
201
201
  export function isKnownCliCommand(name) {
202
- return name === "auth" || name === "tool" || canonicalGroup(name) !== undefined;
202
+ return (name === "auth" ||
203
+ name === "tool" ||
204
+ name === "setup" ||
205
+ name === "doctor" ||
206
+ canonicalGroup(name) !== undefined);
203
207
  }
204
208
  export function findCuratedCommand(group, action) {
205
209
  const canonical = canonicalGroup(group);
@@ -0,0 +1,104 @@
1
+ import { SERVER_VERSION } from "../server/version.js";
2
+ export function doctorHelpText() {
3
+ return `zereight-mcp-gitlab doctor
4
+
5
+ Check token presence, API URL, permission mode, toolsets, and package version.
6
+ Does not print secret values.
7
+
8
+ Usage:
9
+ zereight-mcp-gitlab doctor [--probe]
10
+ zereight-mcp-gitlab doctor --help
11
+
12
+ Flags:
13
+ --probe GET {apiUrl}/user with the configured credentials (network)
14
+ `;
15
+ }
16
+ export function packageVersion() {
17
+ return SERVER_VERSION;
18
+ }
19
+ export function runDoctor(input) {
20
+ const checks = [
21
+ credentialCheck(input.credentials),
22
+ apiUrlCheck(input.apiUrl),
23
+ permissionModeCheck(input.permissionMode),
24
+ toolsetCheck(input.toolsetsRaw),
25
+ versionCheck(input.version),
26
+ ];
27
+ return formatDoctorReport(checks);
28
+ }
29
+ export async function runDoctorWithOptionalProbe(input) {
30
+ const report = runDoctor(input);
31
+ if (input.probe === undefined) {
32
+ return report;
33
+ }
34
+ const probe = await input.probe(input.apiUrl);
35
+ return formatDoctorReport([
36
+ ...report.checks,
37
+ { name: "gitlab_user", ok: probe.ok, detail: probe.detail },
38
+ ]);
39
+ }
40
+ export function formatDoctorReport(checks) {
41
+ const ok = checks.every(check => check.ok);
42
+ const lines = checks.map(check => `${check.ok ? "ok" : "fail"} ${check.name}: ${check.detail}`);
43
+ return {
44
+ checks,
45
+ ok,
46
+ text: `${lines.join("\n")}\n`,
47
+ };
48
+ }
49
+ function credentialCheck(credentials) {
50
+ const hasAny = credentials.hasPersonalAccessToken ||
51
+ credentials.hasJobToken ||
52
+ credentials.hasCookie ||
53
+ credentials.useOAuth;
54
+ return {
55
+ name: "credentials",
56
+ ok: hasAny,
57
+ detail: hasAny
58
+ ? "a token, job token, cookie, or OAuth is configured (value hidden)"
59
+ : "set GITLAB_PERSONAL_ACCESS_TOKEN or run `zereight-mcp-gitlab auth`",
60
+ };
61
+ }
62
+ function apiUrlCheck(apiUrl) {
63
+ try {
64
+ const parsed = new URL(apiUrl);
65
+ const hasApi = parsed.pathname.includes("/api/");
66
+ return {
67
+ name: "api_url",
68
+ ok: hasApi,
69
+ detail: hasApi
70
+ ? `${parsed.origin}${parsed.pathname}`
71
+ : `${apiUrl} is missing /api/v4 (use the API URL, not the web root)`,
72
+ };
73
+ }
74
+ catch {
75
+ return {
76
+ name: "api_url",
77
+ ok: false,
78
+ detail: `${apiUrl} is not a valid URL`,
79
+ };
80
+ }
81
+ }
82
+ function permissionModeCheck(mode) {
83
+ const ok = mode === "readonly" || mode === "modify" || mode === "full";
84
+ return {
85
+ name: "permission_mode",
86
+ ok,
87
+ detail: ok ? mode : `${mode} is not readonly, modify, or full`,
88
+ };
89
+ }
90
+ function toolsetCheck(raw) {
91
+ const value = raw === undefined || raw.trim() === "" ? "core (default)" : raw.trim();
92
+ return {
93
+ name: "toolsets",
94
+ ok: true,
95
+ detail: value,
96
+ };
97
+ }
98
+ function versionCheck(version) {
99
+ return {
100
+ name: "version",
101
+ ok: version !== "unknown",
102
+ detail: version,
103
+ };
104
+ }
package/build/cli/help.js CHANGED
@@ -8,6 +8,8 @@ export function globalHelpText() {
8
8
 
9
9
  Usage:
10
10
  ${BINARY} auth [options]
11
+ ${BINARY} setup --cursor|--claude [options]
12
+ ${BINARY} doctor [--probe]
11
13
  ${BINARY} tool <tool-name> [options]
12
14
  ${BINARY} <group> <action> [options]
13
15
  ${BINARY} --help
@@ -5,6 +5,8 @@ import { applyFlagAliases, argvHasHelp, CliUsageError, parseArgv, parseToolArgs,
5
5
  import { liveCliExposure } from "./exposure.js";
6
6
  import { canonicalGroup, findCuratedCommand, isKnownCliCommand, resolveScopedTool, } from "./curated.js";
7
7
  import { curatedHelpText, globalHelpText, groupHelpText, toolHelpText, toolIndexHelpText, } from "./help.js";
8
+ import { doctorHelpText } from "./doctor.js";
9
+ import { readSetupOptions, setupHelpText } from "./setup.js";
8
10
  export function isCliInvocation(argv) {
9
11
  if (argvHasHelp(argv)) {
10
12
  return true;
@@ -38,6 +40,24 @@ function resolveCliUnsafe(argv, permissionMode, exposure) {
38
40
  if (head === "auth") {
39
41
  return { kind: "auth" };
40
42
  }
43
+ if (head === "setup") {
44
+ if (action !== undefined) {
45
+ return { kind: "usage", message: `Unexpected extra arguments: ${parsed.positionals.slice(1).join(" ")}` };
46
+ }
47
+ if (parsed.help) {
48
+ return { kind: "help", text: setupHelpText() };
49
+ }
50
+ return { kind: "setup", options: readSetupOptions(parsed.toolFlags) };
51
+ }
52
+ if (head === "doctor") {
53
+ if (action !== undefined) {
54
+ return { kind: "usage", message: `Unexpected extra arguments: ${parsed.positionals.slice(1).join(" ")}` };
55
+ }
56
+ if (parsed.help) {
57
+ return { kind: "help", text: doctorHelpText() };
58
+ }
59
+ return { kind: "doctor", probe: parsed.toolFlags.probe === "true" };
60
+ }
41
61
  if (head === "tool") {
42
62
  if (extra !== undefined) {
43
63
  return { kind: "usage", message: `Unexpected extra arguments: ${parsed.positionals.slice(2).join(" ")}` };
@@ -0,0 +1,137 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { CliUsageError } from "./args.js";
4
+ const DEFAULT_API_URL = "https://gitlab.com/api/v4";
5
+ const SKILL_INSTALL = "npx skills add zereight/gitlab-mcp --skill gitlab-mcp-skill";
6
+ export function setupHelpText() {
7
+ return `zereight-mcp-gitlab setup
8
+
9
+ Print (or write) a local MCP client config. Tokens stay in the environment.
10
+
11
+ Usage:
12
+ zereight-mcp-gitlab setup --cursor [--oauth] [--write] [--dir PATH]
13
+ zereight-mcp-gitlab setup --claude [--oauth] [--write]
14
+ zereight-mcp-gitlab setup --help
15
+
16
+ Flags:
17
+ --cursor Cursor mcp.json snippet (default path .cursor/mcp.json)
18
+ --claude Claude Code one-liner (claude mcp add)
19
+ --oauth Use GITLAB_USE_OAUTH instead of a PAT
20
+ --write Write the Cursor file or print the Claude command as the install step
21
+ --skill Also print the Agent Skill install command
22
+ --dir PATH Directory that contains .cursor/mcp.json (default: cwd)
23
+ --api-url GitLab API URL (default ${DEFAULT_API_URL})
24
+
25
+ Dry-run is the default. --write is required to create or merge .cursor/mcp.json.
26
+ `;
27
+ }
28
+ export function readSetupOptions(flags, directory = process.cwd()) {
29
+ const cursor = flags.cursor === "true";
30
+ const claude = flags.claude === "true";
31
+ if (cursor === claude) {
32
+ throw new CliUsageError("setup requires exactly one of --cursor or --claude");
33
+ }
34
+ return {
35
+ client: cursor ? "cursor" : "claude",
36
+ oauth: flags.oauth === "true" || flags.use_oauth === "true",
37
+ write: flags.write === "true",
38
+ skill: flags.skill === "true",
39
+ directory: flags.dir ?? directory,
40
+ apiUrl: flags.api_url ?? DEFAULT_API_URL,
41
+ };
42
+ }
43
+ export function cursorMcpConfig(options) {
44
+ const env = {
45
+ GITLAB_API_URL: options.apiUrl,
46
+ };
47
+ if (options.oauth) {
48
+ env.GITLAB_USE_OAUTH = "true";
49
+ }
50
+ else {
51
+ env.GITLAB_PERSONAL_ACCESS_TOKEN = "${env:GITLAB_PERSONAL_ACCESS_TOKEN}";
52
+ }
53
+ return {
54
+ mcpServers: {
55
+ gitlab: {
56
+ command: "zereight-mcp-gitlab",
57
+ env,
58
+ },
59
+ },
60
+ };
61
+ }
62
+ export function claudeMcpAddCommand(options) {
63
+ const envFlags = options.oauth
64
+ ? `--env GITLAB_USE_OAUTH=true --env GITLAB_API_URL=${options.apiUrl}`
65
+ : `--env GITLAB_PERSONAL_ACCESS_TOKEN="$GITLAB_PERSONAL_ACCESS_TOKEN" --env GITLAB_API_URL=${options.apiUrl}`;
66
+ return `claude mcp add gitlab --transport stdio --scope local ${envFlags} -- zereight-mcp-gitlab`;
67
+ }
68
+ export function runSetup(options) {
69
+ switch (options.client) {
70
+ case "cursor":
71
+ return runCursorSetup(options);
72
+ case "claude":
73
+ return runClaudeSetup(options);
74
+ default: {
75
+ const exhaustive = options.client;
76
+ throw new Error(`Unhandled setup client: ${String(exhaustive)}`);
77
+ }
78
+ }
79
+ }
80
+ function runCursorSetup(options) {
81
+ const snippet = cursorMcpConfig(options);
82
+ const target = join(options.directory, ".cursor", "mcp.json");
83
+ const body = `${JSON.stringify(snippet, null, 2)}\n`;
84
+ const skillLine = options.skill ? `\nSkill: ${SKILL_INSTALL}\n` : "";
85
+ if (!options.write) {
86
+ return {
87
+ text: `Dry-run Cursor config (pass --write to merge ${target}):\n${body}` + skillLine,
88
+ };
89
+ }
90
+ const merged = mergeMcpJson(readJsonFile(target), snippet);
91
+ mkdirSync(dirname(target), { recursive: true });
92
+ writeFileSync(target, `${JSON.stringify(merged, null, 2)}\n`);
93
+ return {
94
+ text: `Wrote ${target}\n${skillLine}`,
95
+ writtenPath: target,
96
+ };
97
+ }
98
+ function runClaudeSetup(options) {
99
+ const command = claudeMcpAddCommand(options);
100
+ const skillLine = options.skill ? `\nSkill: ${SKILL_INSTALL}\n` : "";
101
+ return {
102
+ text: `${command}\n${skillLine}`,
103
+ };
104
+ }
105
+ function readJsonFile(path) {
106
+ if (!existsSync(path)) {
107
+ return {};
108
+ }
109
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
110
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
111
+ throw new CliUsageError(`${path} is not a JSON object`);
112
+ }
113
+ const copy = {};
114
+ for (const [key, value] of Object.entries(parsed)) {
115
+ copy[key] = value;
116
+ }
117
+ return copy;
118
+ }
119
+ function mergeMcpJson(existing, snippet) {
120
+ const existingServers = typeof existing.mcpServers === "object" &&
121
+ existing.mcpServers !== null &&
122
+ !Array.isArray(existing.mcpServers)
123
+ ? existing.mcpServers
124
+ : {};
125
+ const snippetServers = typeof snippet.mcpServers === "object" &&
126
+ snippet.mcpServers !== null &&
127
+ !Array.isArray(snippet.mcpServers)
128
+ ? snippet.mcpServers
129
+ : {};
130
+ return {
131
+ ...existing,
132
+ mcpServers: {
133
+ ...existingServers,
134
+ ...snippetServers,
135
+ },
136
+ };
137
+ }
package/build/index.js CHANGED
@@ -77,6 +77,8 @@ import { createGitLabOAuthClient, ensureOAuthToken } from "./oauth.js";
77
77
  import { getPositionalCliCommand } from "./cli-command.js";
78
78
  import { runAuthCommandAsync } from "./auth-cli.js";
79
79
  import { isCliInvocation, resolveCli } from "./cli/router.js";
80
+ import { runDoctorWithOptionalProbe, packageVersion, } from "./cli/doctor.js";
81
+ import { runSetup } from "./cli/setup.js";
80
82
  import { formatCliError, formatToolOutput } from "./cli/output.js";
81
83
  import { compactMcpToolResult, readOwnRecord } from "./cli/compact-mcp-result.js";
82
84
  import { compileDeniedToolsRegex } from "./tools/denied-regex.js";
@@ -13005,6 +13007,43 @@ async function main() {
13005
13007
  process.stderr.write(`${cli.message}\n`);
13006
13008
  process.exit(2);
13007
13009
  }
13010
+ if (cli.kind === "setup") {
13011
+ try {
13012
+ const result = runSetup({
13013
+ ...cli.options,
13014
+ apiUrl: getConfig("api-url", "GITLAB_API_URL", cli.options.apiUrl),
13015
+ });
13016
+ process.stdout.write(result.text);
13017
+ process.exit(0);
13018
+ }
13019
+ catch (error) {
13020
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
13021
+ process.exit(2);
13022
+ }
13023
+ }
13024
+ if (cli.kind === "doctor") {
13025
+ try {
13026
+ const report = await runDoctorWithOptionalProbe({
13027
+ credentials: {
13028
+ hasPersonalAccessToken: Boolean(GITLAB_PERSONAL_ACCESS_TOKEN),
13029
+ hasJobToken: Boolean(GITLAB_JOB_TOKEN),
13030
+ hasCookie: Boolean(GITLAB_AUTH_COOKIE_PATH),
13031
+ useOAuth: USE_OAUTH,
13032
+ },
13033
+ apiUrl: GITLAB_API_URL,
13034
+ permissionMode: GITLAB_PERMISSION_MODE,
13035
+ toolsetsRaw: GITLAB_TOOLSETS_RAW,
13036
+ version: packageVersion(),
13037
+ probe: cli.probe ? probeGitLabUser : undefined,
13038
+ });
13039
+ process.stdout.write(report.text);
13040
+ process.exit(report.ok ? 0 : 1);
13041
+ }
13042
+ catch (error) {
13043
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
13044
+ process.exit(1);
13045
+ }
13046
+ }
13008
13047
  if (cli.kind === "auth" || getPositionalCliCommand(process.argv) === "auth") {
13009
13048
  try {
13010
13049
  await runAuthCommandAsync();
@@ -13053,6 +13092,32 @@ async function main() {
13053
13092
  function hasCliCredentials() {
13054
13093
  return Boolean(GITLAB_PERSONAL_ACCESS_TOKEN || GITLAB_JOB_TOKEN || GITLAB_AUTH_COOKIE_PATH || USE_OAUTH);
13055
13094
  }
13095
+ async function probeGitLabUser(apiUrl) {
13096
+ const headers = { Accept: "application/json" };
13097
+ if (GITLAB_PERSONAL_ACCESS_TOKEN) {
13098
+ headers["PRIVATE-TOKEN"] = GITLAB_PERSONAL_ACCESS_TOKEN;
13099
+ }
13100
+ else if (GITLAB_JOB_TOKEN) {
13101
+ headers["JOB-TOKEN"] = GITLAB_JOB_TOKEN;
13102
+ }
13103
+ try {
13104
+ const response = await fetch(`${apiUrl.replace(/\/$/, "")}/user`, { headers });
13105
+ if (response.ok) {
13106
+ return { ok: true, status: response.status, detail: `GET /user ${response.status}` };
13107
+ }
13108
+ return {
13109
+ ok: false,
13110
+ status: response.status,
13111
+ detail: `GET /user ${response.status} ${response.statusText}`,
13112
+ };
13113
+ }
13114
+ catch (error) {
13115
+ return {
13116
+ ok: false,
13117
+ detail: error instanceof Error ? error.message : String(error),
13118
+ };
13119
+ }
13120
+ }
13056
13121
  function isRecord(value) {
13057
13122
  return typeof value === "object" && value !== null && !Array.isArray(value);
13058
13123
  }
@@ -2276,15 +2276,9 @@ const discoverTool = allTools.find(t => t.name === "discover_tools");
2276
2276
  if (discoverTool) {
2277
2277
  discoverTool.description = `Discover and activate additional tool categories for this session. Available categories: ${[...DISCOVERABLE_TOOLSET_IDS].join(", ")}. Already-active categories are listed in the response. Explicit-only toolsets are omitted; list them in GITLAB_TOOLSETS.`;
2278
2278
  }
2279
- function acceptsProjectOrGroupId(inputSchema) {
2280
- const properties = inputSchema?.properties;
2281
- return (typeof properties === "object" &&
2282
- properties !== null &&
2283
- ("project_id" in properties || "group_id" in properties));
2284
- }
2285
2279
  const allToolNames = new Set(allTools.map(tool => tool.name));
2286
2280
  for (const tool of allTools) {
2287
- tool.description = getToolDescription(tool.name, tool.description, readOnlyTools.has(tool.name), destructiveTools.has(tool.name), acceptsProjectOrGroupId(tool.inputSchema), allToolNames);
2281
+ tool.description = getToolDescription(tool.name, tool.description, allToolNames);
2288
2282
  }
2289
2283
  export function parseEnabledToolsets(raw) {
2290
2284
  if (!raw || raw.trim() === "") {
@@ -1,70 +1,68 @@
1
1
  const TOOL_GUIDANCE = {
2
- merge_merge_request: "Use this only after checking the merge request approval, conflict, and pipeline state; use `approve_merge_request` to approve rather than merge. The operation changes repository state and may squash commits, schedule auto-merge, or delete the source branch, so it requires merge permission and returns GitLab's merge result or a mergeability error. Pass `sha` from `get_merge_request` (`sha` or `diff_refs.head_sha`); GitLab 19.2+ groups may reject merges without it.",
3
- approve_merge_request: "Use this to record an approval on an existing merge request; it does not merge the request or change its source branch. The operation changes review state, may require re-authentication or approval permission, and returns the updated approval result or a permission/state error.",
4
- unapprove_merge_request: "Use this to remove the current user's approval from an existing merge request; use `merge_merge_request` only when you intend to merge. The operation changes review state and requires approval permission, and GitLab returns the updated result or an error when the request or approval is unavailable.",
5
- rebase_merge_request: "Use this when the source branch is behind its target branch; use `get_merge_request_conflicts` to inspect conflicts and `merge_merge_request` to merge. The operation rewrites the source branch history with a force push and requires permission to push to that branch. With `wait` true (default), it polls until the rebase ends and returns the merge request plus `previous_sha` (its `sha` before the rebase) and `head_changed`. `head_changed: false` means that the head did not move; `merge_error` gives the reason when GitLab sends one. `merge_error` can keep a message from an earlier attempt, so it alone does not prove that this attempt failed. If the rebase still runs after `timeout_seconds`, it returns a timeout error while GitLab continues the rebase. With `wait` false, it returns GitLab's enqueue response (`rebase_in_progress` true) at once. Set `skip_ci` to skip the CI pipeline for the rebased branch.",
6
- get_merge_request_approval_state: "Use this to inspect approval rules and approvers before deciding whether a merge request can be merged; use `approve_merge_request` to change approval state. It is read-only and returns the approval-state response, while missing requests, unsupported GitLab versions, and permission failures are reported as errors.",
7
- get_merge_request_conflicts: "Use this to inspect merge conflicts before attempting `merge_merge_request`; it reports conflicts and does not resolve them. It is read-only, requires access to the project and merge request, and returns GitLab's conflict data or an error when the request cannot be evaluated.",
8
- list_merge_request_pipelines: "Use this to inspect pipelines associated with one merge request; use `list_pipelines` for project-wide pipeline filtering. It is read-only and paginated, requires project access, and returns pipeline records or GitLab errors for invalid identifiers, missing resources, or rate limits.",
9
- create_or_update_file: "Use this for a single repository file when you know whether the target path is new or already exists; use `push_files` for a multi-file commit. Optional `encoding` (`text` or `base64`) defaults to `GITLAB_REPO_FILE_ENCODING` so existing callers stay unchanged. The operation creates or updates remote content in a commit, requires repository write permission, and returns the commit result or a conflict/validation error.",
10
- push_files: "Use this to commit several file changes atomically; use `create_or_update_file` when only one path is involved. Each file defaults to action `create`; optional per-file `action` (create/update/delete/move) and `encoding` (text/base64) are additive. `GITLAB_PERMISSION_MODE=modify` rejects `delete` and `move`. The operation writes repository history on the selected branch, requires repository write permission, and returns the commit result or a validation, conflict, or protected-branch error.",
11
- create_issue: "Use this to open a new issue; use `update_issue` for an existing issue and `create_issue_note` to add discussion without changing issue fields. The operation creates remote project data, requires issue creation permission, and returns the new issue or a validation, permission, or duplicate-related error.",
12
- list_issues: "Use this for issue management: list GitLab issues, optionally scoped with `project_id`. Use `get_issue` when the issue iid is already known and `my_issues` for issues assigned to the current user. It is read-only and paginated, requires issue read permission, and returns issue records or GitLab errors for invalid identifiers, missing resources, or rate limits.",
13
- my_issues: "Use this for issue management: list issues assigned to the authenticated user. Use `list_issues` for project-wide or author-scoped listing and `get_issue` for one issue. Resolving the current user requires classic read_user or fine-grained User:Read. It is read-only and paginated, requires authentication, and returns assigned issue records or permission/rate-limit errors.",
14
- get_issue: "Use this for issue management: inspect one issue's fields; use `list_issues` or `my_issues` to discover issues first. It is read-only, requires issue read permission, and returns the issue or an error when the identifier is invalid, the issue is missing, or access is denied.",
15
- create_merge_request: "Use this to open a new merge request from an existing source branch to a target branch; use `update_merge_request` after it exists. The operation creates remote review state, requires project access, and returns the new merge request or a validation, permission, branch, or duplicate-related error.",
16
- fork_repository: "Use this to create a copy of an existing project in the current user's namespace or a permitted namespace; use `search_repositories` or `get_project` to inspect projects without copying them. The operation creates a new project, requires fork permission, and returns the forked project or a namespace/permission error.",
17
- create_branch: "Use this to create a branch from a branch, tag, or commit; use `get_branch` or `list_branches` to inspect branches and `protect_branch` to configure protection afterward. The operation changes remote repository state, requires branch-creation permission, and returns the new branch or a validation, missing-ref, protected-project, or already-exists error. `project_id` accepts a numeric ID or URL-encoded path, `branch` is the new name, and `ref` selects its starting revision.",
18
- delete_branch: "Use this only after confirming the branch name and intended data loss; use `get_branch` or `list_branches` before deletion and never use it to remove branch protection. The operation permanently removes a remote branch, requires branch-delete permission, and returns the deletion result or a protected-branch, missing-resource, or permission error.",
19
- protect_branch: "Use this to create a protection rule for a branch or wildcard that has none; it does not update an existing rule (GitLab returns 409 Conflict), so use `get_protected_branch` to check first and `update_protected_branch` to change a rule. The operation changes who may push, merge, or unprotect, may enable force-push or code-owner settings, requires maintainer-level permission, and returns the protection rule or a validation/permission/conflict error.",
20
- update_protected_branch: "Use this to change an existing protection rule for a branch or wildcard in place, so the branch is never left unprotected; use `protect_branch` to create a rule and `get_protected_branch` to read the access level record `id`s first. In `allowed_to_push`, `allowed_to_merge`, and `allowed_to_unprotect`, add an entry with one of `access_level`, `user_id`, `group_id`, or `deploy_key_id` (push only); change a record with its `id` and the new value; remove one with `{id, _destroy: true}`. `member_role_id` (custom roles) is not supported; an entry with only that key is rejected. For example, `allowed_to_push: [{id: 12, access_level: 0}]` sets that push record to No access. GitLab documents `access_level`, `user_id`, and `group_id` entries and `code_owner_approval_required` as Premium/Ultimate, and `deploy_key_id` push entries as Free from 18.10; check the returned rule to see what was applied. A request whose `_destroy` entries would leave `allowed_to_unprotect` with no records is refused before the update (a best-effort check; concurrent changes between the read and the update are not detected); keep one or add a replacement in the same request. GitLab may reject the update when the caller is not allowed by the rule's current `allowed_to_unprotect` settings, even as a Maintainer; check current unprotect access with `get_protected_branch` first, especially before replacing an unprotect record. The operation changes repository security controls, requires maintainer-level permission, and returns the updated rule or a validation, missing-rule, or permission error.",
21
- unprotect_branch: "Use this to remove protection from an existing branch; use `update_protected_branch` to change access levels or force push while keeping the rule. The operation changes repository security controls, requires permission to manage protected branches, and returns the result or an error when the branch is missing or policy forbids the change.",
22
- protect_tag: "Use this to protect a tag or wildcard such as `v*` so protected CI/CD variables reach tag pipelines; use `get_protected_tag` or `list_protected_tags` to inspect existing rules first. The operation changes who may create matching tags, requires maintainer-level permission, and returns the protection rule or a validation/permission error.",
23
- unprotect_tag: "Use this to remove protection from an existing tag or wildcard. To change the create access level, use `unprotect_tag` first, then `protect_tag`; `protect_tag` creates a rule and does not update an existing rule. This change is unavailable in `modify` mode because `unprotect_tag` is blocked there. The operation changes repository security controls, requires permission to manage protected tags, and returns the result or an error when the tag is missing or policy forbids the change.",
24
- update_default_branch: "Use this to change which branch GitLab treats as the project's default; use `create_branch` to create a branch rather than changing project defaults. The operation changes project settings and may affect clone, merge request, and CI defaults, requires project-maintainer permission, and returns the updated project or a validation/permission error.",
25
- create_note: "Use this for a top-level comment on an issue or merge request when no typed discussion operation is needed; use `create_merge_request_thread` or `create_issue_note` for threaded replies. The operation creates remote discussion content, requires note permission, and returns the created note or a target/permission/validation error.",
26
- create_merge_request_thread: "Use this to start a review thread on a merge request; use `create_merge_request_note` for an unthreaded note and `create_merge_request_discussion_note` to reply to an existing thread. The operation creates remote review content, requires note permission, and returns the discussion or a position/permission/validation error.",
27
- resolve_merge_request_thread: "Use this to mark an existing merge request review thread resolved; use `update_merge_request_discussion_note` when the note text itself must change. The operation changes review state, requires permission to resolve discussions, and returns the updated discussion or a missing-thread/permission error.",
28
- mr_discussions: "Use this to list complete discussion threads for a merge request; use `get_merge_request_notes` when only flat notes are needed. It is read-only and returns threaded discussion items, while invalid merge request identifiers, missing resources, and permission failures are reported as errors.",
29
- get_merge_request_discussion: "Use this to fetch one known merge request discussion by discussion identifier; use `mr_discussions` for a collection and `get_merge_request_note` for a flat note. It is read-only and returns the discussion item or an error for an invalid identifier, missing discussion, or insufficient permission.",
30
- create_merge_request_discussion_note: "Use this to reply inside an existing merge request discussion; use `create_merge_request_thread` to start a new thread and `create_merge_request_note` for a top-level note. The operation creates remote review content, requires note permission, and returns the new note or a missing-discussion/position/permission error.",
31
- get_merge_request_note: "Use this to fetch one known merge request note by note identifier; use `get_merge_request_notes` for a collection and `mr_discussions` for threaded context. It is read-only and returns the note object or an error for an invalid identifier, missing note, or insufficient permission.",
32
- get_merge_request_notes: "Use this to list flat notes on a merge request; use `mr_discussions` when thread structure and resolution state are required. It is read-only and returns note records, while invalid identifiers, missing resources, and pagination or permission errors are reported by GitLab.",
33
- create_issue_note: "Use this to add a note to an existing issue, optionally as a reply to a discussion; use `update_issue` for issue fields and `create_note` only when the generic endpoint is required. The operation creates remote discussion content, requires note permission, and returns the note or a missing-issue/thread/permission error.",
34
- list_issue_discussions: "Use this to inspect threaded discussions for an issue; use `list_issues` for issue records and `get_issue` for one issue's fields. It is read-only and returns discussion items, while invalid identifiers, missing issues, and permission failures are reported as errors.",
35
- update_issue: "Use this to change fields on an existing issue; use `update_issue_description_patch` for a targeted description edit that avoids sending the full body, and use `create_issue_note` for discussion. The operation mutates issue state, requires issue-edit permission, and returns the updated issue or a validation/permission/conflict error.",
36
- update_issue_description_patch: "Use this for a targeted search/replace or unified-diff change to an issue description; use `dry_run` before applying an uncertain patch and `create_note` when an audit summary is wanted. It changes the issue description when not dry-running, requires issue-edit permission, and returns the patch result or a mismatch/validation/permission error.",
37
- delete_issue: "Use this only after confirming the issue and intended permanent removal; use `update_issue` to close or edit an issue without deleting it. The operation permanently removes issue data, requires delete permission, and returns the deletion result or a missing-resource, permission, or policy error.",
38
- delete_issue_link: "Use this to remove an existing relationship between two issues; use `list_issue_links` or `get_issue_link` to verify the link first. The operation changes issue relationships, requires issue-edit permission, and returns the result or an error when the link is missing or access is denied.",
39
- download_job_artifacts: "Use this to retrieve a pipeline job's artifact archive; remote HTTP mode returns a download URL while local mode saves the archive to a local path. It is read-only on GitLab but creates a local file in stdio mode (rejected in readonly mode), requires job/project access, and returns the download result or an artifact/permission error.",
40
- download_attachment: "Use this to retrieve a previously uploaded project attachment; remote mode returns inline base64 for images or a download URL, while local mode saves the file as a new file under the download directory (GITLAB_DOWNLOAD_DIR, default .gitlab-mcp-downloads). It is read-only with respect to GitLab but creates a local file in stdio mode (readonly mode returns images inline and rejects everything else), never replaces an existing file, requires project access, and returns the file content or an attachment/permission error.",
41
- gitlab_api_request: "Send one GitLab REST call under /api/v4. Readonly mode allows GET only, and modify mode rejects DELETE. Enable it only by listing api in GITLAB_TOOLSETS; all, discover_tools, and GITLAB_TOOLS do not. Prompt injection can drive this tool, so operators who rely on group, project, or tool restrictions must not enable it. With GITLAB_ALLOWED_PROJECT_IDS or a session project scope, only /projects/:id paths for an allowed project are accepted and every other path is rejected; body and query parameters that name another project are not checked.",
42
- execute_graphql: "Use this only when a supported GitLab REST tool does not cover the requested operation; prefer a typed tool when one exists. The query is sent directly to GitLab and can include mutations when permission allows, so callers must treat it as potentially state-changing and handle GraphQL errors in the returned response.",
43
- discover_tools: "Use this when a needed opt-in category is not currently exposed; omit `category` to inspect available categories, then call it with a category to activate that group for the current session. It changes only the session's tool registry, returns the active-tool summary, and does not change GitLab data.",
44
- health_check: "Use this to verify server connectivity and authentication before making GitLab requests; use `whoami` when the authenticated user's identity is the goal. A 403 from GET /user with an insufficient_scope error means the token is accepted but User API scope is missing (read_user or fine-grained User:Read); the check stays ok, sets user_api_warning, and omits version metadata. Rate-limit and authentication-ban 403 responses stay unauthenticated. It does not mutate GitLab state and returns server/authentication status plus GitLab version details when available. Connection failures with no HTTP response return status error, authenticated false, and an error object (kind tls, dns, timeout, or network, plus code and message) instead of a protocol error.",
45
- whoami: "Use this to identify the authenticated GitLab user; use `get_user` or `get_users` when looking up another user. It requires classic read_user or fine-grained User:Read. It is read-only and returns the current user profile, while missing credentials or GitLab permission failures are reported as errors.",
46
- list_project_runners: "Use this when a job is stuck pending with no matching runner and you need the runners a project can use, including ancestor group runners and allowed instance runners. Filter with type, status, paused, and tag_list; the list payload includes id, description, runner_type, status, and paused (among other non-secret GitLab runner fields) but not tags — call `get_runner` for tag_list, run_untagged, locked, and access_level. It is read-only and paginated, requires Maintainer or Auditor access on the project, and never returns a runner registration token (runners_token) or runner authentication token.",
47
- get_runner: "Use this after list_project_runners when you already have a runner ID and need tag_list, run_untagged, locked, and access_level to explain a stuck pending job. Instance runner details are visible to authenticated users; group and project runners follow GitLab's role checks for that runner. When GITLAB_ALLOWED_PROJECT_IDS or a session project scope is active, the runner must appear on an allowed project's runner list before details are returned — pass project_id when more than one project is allowed — projects outside that allowlist are omitted, and groups are omitted because a project allowlist cannot verify them. GITLAB_PROJECT_ID alone is only the default project for that membership check when project_id is omitted. It does not omit other projects or groups. It is read-only and never returns a runner registration token (runners_token) or runner authentication token. token_expires_at is expiry metadata, not the token.",
48
- list_pipeline_schedules: "Use this to survey a project's scheduled pipelines and their `cron`, `next_run_at`, and `active` state, optionally narrowed with `scope` to `active` or `inactive`; use `list_pipelines` for pipelines that already ran and `get_pipeline_schedule` when the schedule ID is known. The list response carries no `variables` — call `get_pipeline_schedule` for those — and includes `inputs` only for Maintainers, Owners, or the schedule owner. It is read-only and paginated, requires project access, and returns schedule records or an error when the project is missing or access is denied.",
49
- get_pipeline_schedule: "Use this to inspect one schedule's cron, owner, last pipeline, variables, and inputs; use `list_pipeline_schedules` to discover schedule IDs and `list_pipeline_schedule_pipelines` for the pipelines it produced. It is read-only, returns `variables` and `inputs` only to Maintainers, Owners, or the schedule owner, and returns the schedule or an error when the schedule is missing or access is denied.",
50
- list_pipeline_schedule_pipelines: "Use this to review the pipelines a schedule has actually triggered, for example to see whether the nightly run succeeded; use `get_pipeline` for one pipeline's full detail and `list_pipelines` for project-wide filtering. Narrow the result with `scope`, `status`, `sort`, and the `created_*` / `updated_*` date bounds. It is read-only and paginated, requires project access, and returns pipeline records or an error when the schedule is missing or access is denied.",
51
- create_pipeline_schedule: "Use this to add a recurring pipeline for a branch or tag; use `create_pipeline` to run a pipeline once now and `update_pipeline_schedule` once the schedule exists. `ref` accepts a short name or a full ref such as `refs/heads/main`, which you should prefer when a branch and tag share a name; `cron_timezone` defaults to `UTC` and `active` defaults to true. It creates remote CI configuration, requires the Developer, Maintainer, or Owner role plus permission to run pipelines on the target ref, and returns the new schedule or a validation, cron, ref, or permission error.",
52
- update_pipeline_schedule: "Use this to change an existing schedule's cron, ref, description, inputs, or `active` flag; call `take_ownership_pipeline_schedule` first when the schedule belongs to another user, because GitLab rejects edits from non-owners. Every field is optional and GitLab reschedules the next run after the update. It changes remote CI configuration, requires the Developer, Maintainer, or Owner role and ownership of the schedule, and returns the updated schedule or a validation, cron, or permission error.",
53
- delete_pipeline_schedule: "Use this only after confirming the schedule with `get_pipeline_schedule`; use `update_pipeline_schedule` with `active: false` to pause a schedule instead of removing it. The operation permanently removes the schedule and its variables, requires the Developer, Maintainer, or Owner role and ownership of the schedule, and returns a confirmation or a missing-resource or permission error.",
54
- play_pipeline_schedule: "Use this to run a schedule's pipeline immediately without waiting for its cron; it does not shift `next_run_at`, so the regular cadence continues unchanged, and it does not modify the schedule itself. Use `create_pipeline` when you want a one-off pipeline that is unrelated to a schedule. It creates a pipeline on GitLab, requires ownership of the schedule and permission to run pipelines on its ref, is rate-limited to once per minute per schedule, and returns GitLab's acknowledgement message or a permission, rate-limit, or missing-schedule error.",
55
- take_ownership_pipeline_schedule: "Use this to transfer a schedule's ownership to the authenticated user, which is the prerequisite for `update_pipeline_schedule` or `delete_pipeline_schedule` on someone else's schedule; it does not otherwise change the cron, ref, or variables. It changes remote ownership, requires the Maintainer or Owner role, and returns the schedule with its new owner or a permission or missing-schedule error.",
56
- get_pipeline_schedule_variable: "Use this to read one variable attached to a pipeline schedule by key; use `get_pipeline_schedule` to list every variable at once and `get_project_variable` for project-level CI/CD variables, which are a separate set. It is read-only, requires the Maintainer or Owner role or ownership of the schedule (GitLab 18.7 or later exposes this endpoint), and returns the variable or an error when the schedule, key, or permission is missing.",
57
- create_pipeline_schedule_variable: "Use this to attach a new variable to a pipeline schedule so its scheduled pipelines receive it; use `update_pipeline_schedule_variable` when the key already exists and `create_project_variable` for a project-wide variable. `variable_type` defaults to `env_var` and `key` is limited to 255 characters matching /[a-zA-Z0-9_]+/. It creates remote CI configuration, requires the Developer, Maintainer, or Owner role and ownership of the schedule, and returns the new variable or a validation, duplicate-key, or permission error.",
58
- update_pipeline_schedule_variable: "Use this to change the value or type of an existing pipeline schedule variable; use `create_pipeline_schedule_variable` when the key does not exist yet. It changes remote CI configuration that scheduled pipelines consume, requires the Developer, Maintainer, or Owner role and ownership of the schedule, and returns the updated variable or a missing-key, validation, or permission error.",
59
- delete_pipeline_schedule_variable: "Use this only after confirming the key with `get_pipeline_schedule` or `get_pipeline_schedule_variable`; use `update_pipeline_schedule_variable` to change a value without removing it. The operation permanently removes the variable from the schedule so later scheduled pipelines no longer receive it, requires the Developer, Maintainer, or Owner role and ownership of the schedule, and returns a confirmation or a missing-key or permission error.",
60
- stop_stale_environments: "Use this to stop eligible stale environments in a project. GitLab excludes protected environments, and this operation stops environments without deleting them. It requires the necessary project permission and returns the result or a validation, permission, or rate-limit error.",
61
- delete_review_app_environments: "Use this to clean up stopped review-app environments after verifying the scope. GitLab schedules deletion one week later rather than deleting environments immediately; `dry_run` defaults to `true`, so actual scheduling requires `dry_run: false`. It requires the necessary project permission and returns the cleanup result or a validation, permission, or rate-limit error.",
62
- orbit_query: "Use this to answer cross-project relationship questions (blast radius, file-to-MR history, dependency fans) in one call instead of chaining list/get tools; use `orbit_get_schema` first when the node/edge types are unknown. It is read-only but each call consumes GitLab credits, requires Premium/Ultimate on an Orbit-enabled scope, and returns the graph result or a query/permission error.",
63
- orbit_get_schema: "Use this to learn the node and edge types available for `orbit_query` before writing a query; use `orbit_get_status` when the concern is index freshness rather than shape. It is read-only and free of credit charges, and returns the current graph schema or a permission error.",
64
- orbit_get_status: "Use this to check whether Orbit indexing has completed before trusting `orbit_query` results; results reflect the last index cycle, not real-time state. It is read-only and free of credit charges, and returns the indexing status or a permission error.",
65
- orbit_list_tools: "Use this to see which MCP tool definitions Orbit itself exposes; use `orbit_query` to run graph queries directly. It is read-only and free of credit charges, and returns the tool definition list or a permission error.",
2
+ merge_merge_request: "Use this only after checking approval, conflicts, and pipeline state; use `approve_merge_request` to approve rather than merge. Pass `sha` from `get_merge_request`; GitLab 19.2+ groups may reject merges without it.",
3
+ approve_merge_request: "Record an approval on an existing merge request. It does not merge or change the source branch.",
4
+ unapprove_merge_request: "Remove the current user's approval. Use `merge_merge_request` only when you intend to merge.",
5
+ rebase_merge_request: "Rebase the source onto the target (force-push). With `wait` true (default) it returns `previous_sha` and `head_changed`; `merge_error` can be stale. Use `get_merge_request_conflicts` for conflicts.",
6
+ get_merge_request_approval_state: "Inspect approval rules and approvers before merging. Use `approve_merge_request` to change approval state.",
7
+ get_merge_request_conflicts: "Inspect merge conflicts before `merge_merge_request`. It reports conflicts and does not resolve them.",
8
+ list_merge_request_pipelines: "List pipelines for one merge request. Use `list_pipelines` for project-wide filtering.",
9
+ create_or_update_file: "Create or update one repository file. Use `push_files` for a multi-file commit. Optional `encoding` (`text` or `base64`) defaults to `GITLAB_REPO_FILE_ENCODING`.",
10
+ push_files: "Commit several file changes atomically. Each file defaults to action `create`; optional per-file `action` (create/update/delete/move) and `encoding` (text/base64) are additive. `GITLAB_PERMISSION_MODE=modify` rejects `delete` and `move`.",
11
+ create_issue: "Open a new issue. Use `update_issue` for an existing issue and `create_issue_note` for discussion.",
12
+ list_issues: "Use this for issue management: list GitLab issues, optionally scoped with `project_id`. Use `get_issue` when the iid is known and `my_issues` for the current user's assignments.",
13
+ my_issues: "Use this for issue management: list issues assigned to the authenticated user. Use `list_issues` for project-wide listing and `get_issue` for one issue. Requires classic read_user or fine-grained User:Read.",
14
+ get_issue: "Use this for issue management: inspect one issue. Use `list_issues` or `my_issues` to discover issues first.",
15
+ create_merge_request: "Open a merge request from an existing source branch to a target. Use `update_merge_request` after it exists.",
16
+ fork_repository: "Fork an existing project into the current user's namespace or a permitted namespace. Use `search_repositories` or `get_project` to inspect without copying.",
17
+ create_branch: "Create a branch from a source branch, tag, or commit. Use `protect_branch` after creation. Returns already-exists when the name is taken.",
18
+ delete_branch: "Permanently delete a remote branch. Confirm the name first with `get_branch` or `list_branches`; this does not remove branch protection.",
19
+ protect_branch: "Create a protection rule; GitLab returns 409 if one exists. Use `update_protected_branch` to change an existing rule.",
20
+ update_protected_branch: "Change an existing protection rule in place (do not unprotect first). Read record `id`s with `get_protected_branch`. In `allowed_to_push`, `allowed_to_merge`, and `allowed_to_unprotect`, add with `access_level`, `user_id`, `group_id`, or `deploy_key_id` (push only); change with `id`; remove with `{id, _destroy: true}`. `member_role_id` is not supported. A request that would leave `allowed_to_unprotect` empty is refused. GitLab may reject the update unless the caller is on the current unprotect list.",
21
+ unprotect_branch: "Remove protection. Use `update_protected_branch` to change access levels without removing the rule.",
22
+ protect_tag: "Protect a tag or wildcard such as `v*` so protected CI/CD variables reach tag pipelines. Inspect first with `get_protected_tag` or `list_protected_tags`.",
23
+ unprotect_tag: "Remove tag protection. To change create access, `unprotect_tag` then `protect_tag`. Blocked in `modify` mode.",
24
+ update_default_branch: "Change the project's default branch. Use `create_branch` to create a branch rather than changing defaults.",
25
+ create_note: "Add a top-level comment on an issue or merge request. Use `create_merge_request_thread` or `create_issue_note` for typed discussion.",
26
+ create_merge_request_thread: "Start a review thread. Use `create_merge_request_note` for an unthreaded note and `create_merge_request_discussion_note` to reply.",
27
+ resolve_merge_request_thread: "Mark an existing review thread resolved. Use `update_merge_request_discussion_note` to change note text.",
28
+ mr_discussions: "List complete discussion threads. Use `get_merge_request_notes` for flat notes.",
29
+ get_merge_request_discussion: "Fetch one discussion by id. Use `mr_discussions` for a collection and `get_merge_request_note` for a flat note.",
30
+ create_merge_request_discussion_note: "Reply inside an existing discussion. Use `create_merge_request_thread` to start a thread and `create_merge_request_note` for a top-level note.",
31
+ get_merge_request_note: "Fetch one note by id. Use `get_merge_request_notes` for a collection and `mr_discussions` for threads.",
32
+ get_merge_request_notes: "List flat notes. Use `mr_discussions` when thread structure and resolution state are required.",
33
+ create_issue_note: "Add a note to an existing issue, optionally as a reply. Use `update_issue` for issue fields.",
34
+ list_issue_discussions: "List threaded discussions for an issue. Use `list_issues` or `get_issue` for issue records.",
35
+ update_issue: "Change fields on an existing issue. Use `update_issue_description_patch` for a targeted description edit and `create_issue_note` for discussion.",
36
+ delete_issue: "Permanently delete an issue. Use `update_issue` to close or edit without deleting.",
37
+ delete_issue_link: "Remove a relationship between two issues. Verify first with `list_issue_links` or `get_issue_link`.",
38
+ download_job_artifacts: "Retrieve a job artifact archive. Remote HTTP returns a download URL; local stdio saves a file (rejected in readonly mode).",
39
+ download_attachment: "Retrieve a project attachment. Remote HTTP returns inline base64 or a URL; local stdio saves under GITLAB_DOWNLOAD_DIR and never overwrites.",
40
+ gitlab_api_request: "Send one GitLab REST call under /api/v4. Readonly allows GET only; modify rejects DELETE. Enable only by listing api in GITLAB_TOOLSETS; all, discover_tools, and GITLAB_TOOLS do not. Prompt injection can drive this tool, so operators who rely on group, project, or tool restrictions must not enable it. With GITLAB_ALLOWED_PROJECT_IDS or a session project scope, only /projects/:id paths for an allowed project are accepted; body and query parameters that name another project are not checked.",
41
+ execute_graphql: "Use only when no typed REST tool covers the operation. The query is sent directly and may include mutations.",
42
+ whoami: "Requires classic read_user or fine-grained User:Read. Use `get_user` or `get_users` to look up another user.",
43
+ list_project_members: "Search project members by name or username. Use `list_group_members` for a group.",
44
+ get_commit_diff: "Return the file diffs for one commit. Use `get_commit` for metadata without the patch.",
45
+ list_project_runners: "List runners a project can use, including ancestor group and allowed instance runners. Payload omits tags — call `get_runner` for tag_list. Never returns runners_token or the runner authentication token.",
46
+ get_runner: "Get tag_list, run_untagged, locked, and access_level for a runner id. With GITLAB_ALLOWED_PROJECT_IDS or a session project scope, the runner must appear on an allowed project's list — pass project_id when more than one project is allowed. Never returns runners_token or the runner authentication token; token_expires_at is expiry metadata, not the token.",
47
+ list_pipeline_schedules: "List scheduled pipelines (`cron`, `next_run_at`, `active`). The list has no `variables` — call `get_pipeline_schedule` for those.",
48
+ get_pipeline_schedule: "Inspect one schedule including variables and inputs (Maintainer/Owner/schedule owner only).",
49
+ list_pipeline_schedule_pipelines: "List pipelines a schedule has triggered. Use `get_pipeline` for one pipeline and `list_pipelines` for project-wide filtering.",
50
+ create_pipeline_schedule: "Add a recurring pipeline. Prefer a full ref such as `refs/heads/main` when a branch and tag share a name. `cron_timezone` defaults to UTC.",
51
+ update_pipeline_schedule: "Change an existing schedule. Call `take_ownership_pipeline_schedule` first when the schedule belongs to another user.",
52
+ delete_pipeline_schedule: "Permanently remove a schedule and its variables. Pause with `update_pipeline_schedule` `active: false` instead when possible.",
53
+ play_pipeline_schedule: "Run a schedule immediately without shifting `next_run_at`. Rate-limited to once per minute per schedule.",
54
+ take_ownership_pipeline_schedule: "Transfer schedule ownership to the current user. Required before updating or deleting someone else's schedule.",
55
+ get_pipeline_schedule_variable: "Read one schedule variable by key. Separate from project-level CI/CD variables.",
56
+ create_pipeline_schedule_variable: "Attach a new variable to a schedule. `variable_type` defaults to `env_var`; `key` is /[a-zA-Z0-9_]+/ up to 255 characters.",
57
+ update_pipeline_schedule_variable: "Change the value or type of an existing schedule variable.",
58
+ delete_pipeline_schedule_variable: "Permanently remove a schedule variable. Later scheduled pipelines no longer receive it.",
59
+ stop_stale_environments: "Stop eligible stale environments. GitLab excludes protected environments; this does not delete them.",
60
+ delete_review_app_environments: "Schedule deletion of stopped review-app environments one week later. `dry_run` defaults to true.",
61
+ orbit_query: "Answer cross-project relationship questions in one call. Premium/Ultimate; each call consumes GitLab credits. Use `orbit_get_schema` first when types are unknown.",
62
+ orbit_get_schema: "List node and edge types for `orbit_query`. Read-only and free of credit charges.",
63
+ orbit_get_status: "Check whether Orbit indexing has completed. Results reflect the last index cycle.",
64
+ orbit_list_tools: "List MCP tool definitions Orbit itself exposes. Use `orbit_query` to run graph queries.",
66
65
  };
67
- const PARAMETER_GUIDANCE = "`project_id`/`group_id` take a numeric ID or URL-encoded path.";
68
66
  function ensureSentence(text) {
69
67
  const trimmed = text.trim();
70
68
  return /[.!?]$/.test(trimmed) ? trimmed : `${trimmed}.`;
@@ -128,30 +126,12 @@ function getLifecycleGuidance(name, toolNames) {
128
126
  }
129
127
  return "";
130
128
  }
131
- function getBehaviorGuidance(readOnly, destructive) {
132
- if (readOnly) {
133
- return "Read-only; missing resources, denied access, and rate limits return errors.";
134
- }
135
- if (destructive) {
136
- return "Changes or removes GitLab data and may be irreversible; needs permission.";
137
- }
138
- return "Changes GitLab data; needs permission and returns validation or conflict errors.";
139
- }
140
- export function getToolDescription(name, baseDescription, readOnly, destructive, acceptsProjectOrGroupId, toolNames) {
129
+ export function getToolDescription(name, baseDescription, toolNames) {
141
130
  const explicitGuidance = TOOL_GUIDANCE[name];
142
131
  if (explicitGuidance) {
143
132
  return `${ensureSentence(baseDescription)} ${explicitGuidance}`;
144
133
  }
145
134
  const normalizedDescription = ensureSentence(baseDescription);
146
- if (normalizedDescription.length >= 180) {
147
- return normalizedDescription;
148
- }
149
- return [
150
- normalizedDescription,
151
- getLifecycleGuidance(name, toolNames),
152
- getBehaviorGuidance(readOnly, destructive),
153
- acceptsProjectOrGroupId ? PARAMETER_GUIDANCE : "",
154
- ]
155
- .filter(Boolean)
156
- .join(" ");
135
+ const lifecycle = getLifecycleGuidance(name, toolNames);
136
+ return lifecycle ? `${normalizedDescription} ${lifecycle}` : normalizedDescription;
157
137
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "2.3.8-next.143",
3
+ "version": "2.3.8-next.145",
4
4
  "mcpName": "io.github.zereight/gitlab-mcp",
5
5
  "description": "GitLab MCP server for projects, merge requests, issues, pipelines, wiki, releases, and more",
6
6
  "keywords": [