Token-Efficient GitLab Server Management — An enhanced fork of zereight/gitlab-mcp with progressive disclosure pattern for dramatic token savings.
This fork builds on zereight/gitlab-mcp with a redesigned architecture focused on token efficiency and maintainability. We regularly review upstream commits and port new features and bugfixes while keeping our own structure.
| Area | Upstream | This Fork |
|---|---|---|
| Architecture | Single index.ts (~10K lines) |
Modular src/ with 15 tool modules |
| Tool Discovery | All 140+ tools exposed at once | SDK-native progressive disclosure (2 meta-tools) |
| Configuration | Flat individual exports | Typed ServerConfig interface with loadConfig() |
| Logging | console.log |
Structured MCP protocol logger for agent observability |
| Runtime | Node.js + npm | Bun (faster builds, native TypeScript) |
| Linting | ESLint + Prettier | Strict Biome rules (noExplicitAny, noNonNullAssertion, cognitive complexity) |
| CI/CD | Basic | GitHub Actions (lint, build, test, semantic-release) |
| Pre-commit | None | prek hooks (typos, formatting, build verification) |
| Feature Flags | USE_PIPELINE, USE_MILESTONE, USE_GITLAB_WIKI required |
None — all categories available via progressive disclosure |
- Progressive Disclosure — 2 meta-tools instead of 140+ individual tools (~90% token reduction). Uses the MCP SDK's native
enable()/disable()API so tools are registered but hidden until the LLM activates a category. - Modular Tool Organization — Each GitLab domain (issues, merge requests, pipelines, etc.) lives in its own file under
src/tools/, making it easy to find, test, and extend individual tools without navigating a monolithic file. - Typed Configuration — A
ServerConfiginterface ensures all config values are validated at startup, with IDE autocompletion and compile-time safety. - MCP Protocol Logging — Structured logs sent to LLM clients for agent observability, not just developer console output.
- HTTP Transport Security — DNS rebinding protection, configurable allowed hosts/origins.
- Read-Only Mode & PAT Safety — Automatic PAT scope detection, explicit read-only mode, and actionable 403 error messages. Uses
readOnlyHintannotations on all 146 tools to filter write operations. - Comprehensive Test Suite — 160+ tests covering registry, config, logger, MCP integration, read-only mode, and meta-tools.
- Strict Code Quality — Zero
anytypes, no non-null assertions, enforced cognitive complexity limits. - Modern Tooling — Bun for fast builds, Biome for linting, prek for pre-commit hooks.
- No Feature Flags Needed — Upstream requires
USE_PIPELINE,USE_MILESTONE, andUSE_GITLAB_WIKIenv vars to enable core tools. Progressive disclosure eliminates this — all 15 categories are registered but dormant until activated, so there's zero token cost and zero config overhead. - Automated Releases — Semantic versioning with conventional commits.
We maintain main as a read-only mirror of upstream. New features and bugfixes from upstream are reviewed and ported into our architecture as needed — we don't blindly rebase, since the codebases have structurally diverged. If you're looking for a specific upstream feature, check our releases or open an issue.
Instead of exposing 140+ individual tools, this server exposes 2 meta-tools:
| Meta-Tool | Purpose |
|---|---|
list_categories |
Discover available tool categories and their activation status |
activate_tools |
Enable all tools in one or more categories |
| Approach | Tools Exposed | Approximate Token Cost |
|---|---|---|
| Traditional | 140+ tools | ~20,000+ tokens |
| Progressive Disclosure | 2 meta-tools | ~1,500 tokens |
~90% reduction in tool definition tokens!
1. LLM calls list_categories() → sees "merge-requests" category (20 tools, 0 active)
2. LLM calls activate_tools(categories: ["merge-requests"]) → 20 tools now appear in tool list
3. LLM calls create_merge_request({project_id: "123", title: "Fix bug", source_branch: "fix", target_branch: "main"})
All GitLab operations organized by category:
| Category | Description |
|---|---|
| repositories | Search, create, fork repos. Get files, push files, manage branches |
| merge-requests | Create, update, merge MRs. Discussions, threads, diffs |
| issues | Create, update, delete issues. Links, discussions |
| pipelines | List, create, retry, cancel pipelines. Job output |
| projects | Project details, members, labels |
| commits | List commits, get diffs |
| namespaces | List, get, verify namespaces |
| users | User details, search users, audit/project events, file uploads |
| search | Global, project, and group search across code, issues, MRs, commits |
| wiki | Wiki page management for projects and groups |
| milestones | Create, edit, delete milestones. Burndown events |
| releases | List, create, update, delete releases. Download assets |
| webhooks | List project webhooks and recent events |
| work-items | GraphQL work items: create, update, hierarchy, notes, incidents |
| graphql | Execute arbitrary GraphQL queries |
- Node.js 18+ (for
npx) or Bun 1.0+ (forbunx) - A GitLab Personal Access Token — scope determines what the server can do:
api— Full access (create issues, merge MRs, manage pipelines, etc.)read_api— Read-only (server auto-detects and hides write tools)
- Or
CI_JOB_TOKEN— automatically detected in GitLab CI pipelines
Use an api scope PAT to get all 146 tools across 15 categories:
Claude Code CLI:
# With npx (Node.js)
claude mcp add gitlab \
-e GITLAB_PERSONAL_ACCESS_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx \
-e GITLAB_API_URL=https://gitlab.com \
-- npx efficient-gitlab-mcp-server
# With bunx (Bun)
claude mcp add gitlab \
-e GITLAB_PERSONAL_ACCESS_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx \
-e GITLAB_API_URL=https://gitlab.com \
-- bunx efficient-gitlab-mcp-serverMCP client config (Claude Desktop, IDE extensions, etc.):
{
"mcpServers": {
"gitlab": {
"command": "npx",
"args": ["efficient-gitlab-mcp-server"],
"env": {
"GITLAB_PERSONAL_ACCESS_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx",
"GITLAB_API_URL": "https://gitlab.com"
}
}
}
}Use a read_api scope PAT — the server auto-detects the limited scope and only exposes read tools. No extra config needed:
Claude Code CLI:
# With npx (Node.js)
claude mcp add gitlab \
-e GITLAB_PERSONAL_ACCESS_TOKEN=glpat-your-read-only-token \
-e GITLAB_API_URL=https://gitlab.com \
-- npx efficient-gitlab-mcp-server
# With bunx (Bun)
claude mcp add gitlab \
-e GITLAB_PERSONAL_ACCESS_TOKEN=glpat-your-read-only-token \
-e GITLAB_API_URL=https://gitlab.com \
-- bunx efficient-gitlab-mcp-serverOr force read-only mode explicitly (regardless of token scopes):
# With npx (Node.js)
claude mcp add gitlab \
-e GITLAB_PERSONAL_ACCESS_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx \
-e GITLAB_API_URL=https://gitlab.com \
-e GITLAB_READ_ONLY_MODE=true \
-- npx efficient-gitlab-mcp-server
# With bunx (Bun)
claude mcp add gitlab \
-e GITLAB_PERSONAL_ACCESS_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx \
-e GITLAB_API_URL=https://gitlab.com \
-e GITLAB_READ_ONLY_MODE=true \
-- bunx efficient-gitlab-mcp-serverFor self-hosted GitLab, update GITLAB_API_URL to your instance URL.
git clone https://github.com/detailobsessed/efficient-gitlab-mcp.git
cd efficient-gitlab-mcp
bun install
bun run build
bun startThe server provides three layers of protection for users with limited-scope Personal Access Tokens:
1. Explicit read-only mode — Set GITLAB_READ_ONLY_MODE=true to restrict the server to read-only tools. Write tools won't appear in list_categories counts or be activated by activate_tools. This is controlled by the readOnlyHint annotation on every tool.
2. Automatic PAT scope detection — On startup, the server calls GitLab's GET /personal_access_tokens/self to inspect your token's scopes. If the token lacks the api scope (e.g., only has read_api), read-only mode is automatically enabled. No configuration needed — it just works.
3. Actionable 403 error messages — If a tool call hits a 403 Forbidden error, the error message includes specific guidance about which PAT scopes are needed, so the LLM can inform the user rather than retrying blindly.
# Explicit read-only mode
GITLAB_READ_ONLY_MODE=true
# Or just use a read_api token — auto-detected!
GITLAB_PERSONAL_ACCESS_TOKEN=glpat-your-read-only-token
The server supports MCP protocol logging for agent observability. When connected, LLM clients can receive structured log messages showing what the server is doing:
- Tool execution logs
- GitLab API call details
- Error information with context
This helps agents understand server behavior and debug issues.
When using HTTP transport (STREAMABLE_HTTP=true), the server includes security features:
| Environment Variable | Default | Description |
|---|---|---|
HTTP_ALLOWED_HOSTS |
localhost,127.0.0.1 |
Comma-separated list of allowed Host headers |
HTTP_ALLOWED_ORIGINS |
(any) | Comma-separated list of allowed Origin headers |
HTTP_ENABLE_DNS_REBINDING_PROTECTION |
true |
Enable DNS rebinding attack protection |
Example for production:
HTTP_ALLOWED_HOSTS=api.example.com,localhost \
HTTP_ALLOWED_ORIGINS=https://app.example.com \
STREAMABLE_HTTP=true \
bun start# Run tests
bun test
# Run tests with coverage
bun test --coverage
# Lint and format
bun run check
# Build
bun run build| Variable | Required | Default | Description |
|---|---|---|---|
GITLAB_PERSONAL_ACCESS_TOKEN |
Yes* | - | GitLab personal access token (takes priority over CI_JOB_TOKEN) |
CI_JOB_TOKEN |
No | - | GitLab CI job token (auto-detected in CI pipelines) |
GITLAB_API_URL |
No | https://gitlab.com |
GitLab instance URL |
GITLAB_PROJECT_ID |
No | - | Default project ID when tools omit project_id |
GITLAB_ALLOWED_PROJECT_IDS |
No | - | Restrict tools to these projects (comma-separated). With a single project, acts as default. With multiple, project_id is required per call |
GITLAB_READ_ONLY_MODE |
No | false |
Only expose read-only tools. Auto-detected from PAT scopes if not set |
GITLAB_IS_OLD |
No | false |
For older GitLab instances |
*PAT is recommended. CI_JOB_TOKEN is auto-detected in GitLab CI pipelines when no PAT is set. OAuth support is planned (see DET-44).
| Variable | Required | Default | Description |
|---|---|---|---|
STREAMABLE_HTTP |
No | false |
Enable HTTP transport |
SSE |
No | false |
Enable SSE transport |
PORT |
No | 3002 |
HTTP server port |
HOST |
No | 127.0.0.1 |
HTTP server host |
| Variable | Required | Default | Description |
|---|---|---|---|
LOG_LEVEL |
No | info |
debug, info, warn, error |
LOG_FORMAT |
No | pretty |
json, pretty |
HTTP_ALLOWED_HOSTS |
No | localhost,127.0.0.1 |
Allowed Host headers |
HTTP_ALLOWED_ORIGINS |
No | (any) | Allowed Origin headers |
| Variable | Required | Default | Description |
|---|---|---|---|
REMOTE_AUTHORIZATION |
No | false |
Enable remote auth |
ENABLE_DYNAMIC_API_URL |
No | false |
Allow dynamic GitLab URLs |
SESSION_TIMEOUT_SECONDS |
No | 3600 |
Session timeout |
MAX_SESSIONS |
No | 1000 |
Maximum concurrent sessions |
MAX_REQUESTS_PER_MINUTE |
No | 60 |
Rate limit per session |
*Or use CI_JOB_TOKEN in GitLab CI pipelines. OAuth authentication is planned — see OAuth Setup Guide for the design.
- Never commit tokens — Use
.envfiles (gitignored) - Rotate tokens — Regenerate periodically
- Least privilege — Only grant necessary API scopes
- Audit logs — Monitor API access
This project is a fork of zereight/gitlab-mcp. Thanks to the original author for the comprehensive GitLab API implementation.
- MCP Protocol: modelcontextprotocol.io
- GitLab API: docs.gitlab.com/ee/api
- Bun: bun.sh
MIT License — See LICENSE for details.
Efficient GitLab MCP
AI-Powered GitLab Management with Token Efficiency
Built with Bun and the Model Context Protocol