gitlab-mcp 0.1.5 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +7 -0
- package/.editorconfig +9 -0
- package/.env.example +75 -0
- package/.github/workflows/nodejs.yml +31 -0
- package/.github/workflows/npm-publish.yml +31 -0
- package/.husky/pre-commit +1 -0
- package/.nvmrc +1 -0
- package/.prettierrc.json +6 -0
- package/Dockerfile +20 -0
- package/README.md +416 -251
- package/docker-compose.yml +10 -0
- package/docs/architecture.md +310 -0
- package/docs/authentication.md +299 -0
- package/docs/configuration.md +149 -0
- package/docs/deployment.md +336 -0
- package/docs/tools.md +294 -0
- package/eslint.config.js +23 -0
- package/package.json +78 -32
- package/scripts/get-oauth-token.example.sh +15 -0
- package/src/config/env.ts +171 -0
- package/src/http.ts +620 -0
- package/src/index.ts +77 -0
- package/src/lib/auth-context.ts +19 -0
- package/src/lib/gitlab-client.ts +1810 -0
- package/src/lib/logger.ts +17 -0
- package/src/lib/network.ts +45 -0
- package/src/lib/oauth.ts +287 -0
- package/src/lib/output.ts +51 -0
- package/src/lib/policy.ts +78 -0
- package/src/lib/request-runtime.ts +376 -0
- package/src/lib/sanitize.ts +25 -0
- package/src/lib/session-capacity.ts +14 -0
- package/src/server/build-server.ts +17 -0
- package/src/tools/gitlab.ts +3135 -0
- package/src/tools/health.ts +27 -0
- package/src/tools/mr-code-context.ts +473 -0
- package/src/types/context.ts +13 -0
- package/tests/auth-context.test.ts +102 -0
- package/tests/gitlab-client.test.ts +672 -0
- package/tests/graphql-guard.test.ts +121 -0
- package/tests/integration/agent-loop.integration.test.ts +558 -0
- package/tests/integration/server.integration.test.ts +543 -0
- package/tests/mr-code-context.test.ts +600 -0
- package/tests/oauth.test.ts +43 -0
- package/tests/output.test.ts +186 -0
- package/tests/policy.test.ts +324 -0
- package/tests/request-runtime.test.ts +252 -0
- package/tests/sanitize.test.ts +123 -0
- package/tests/session-capacity.test.ts +49 -0
- package/tests/upload-reference.test.ts +88 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +12 -0
- package/LICENSE +0 -21
- package/build/index.js +0 -1642
- package/build/schemas.js +0 -684
- package/build/test-note.js +0 -54
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
This document describes the internal design of gitlab-mcp, its module relationships, and key design decisions.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
gitlab-mcp is structured as a layered MCP server with clear separation between transport, tool registration, API client, and cross-cutting concerns (policy, auth, output formatting).
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌──────────────────────────────────────────────────────┐
|
|
11
|
+
│ Transport Layer │
|
|
12
|
+
│ index.ts (stdio) │ http.ts (HTTP/SSE) │
|
|
13
|
+
└─────────┬──────────┴──────────┬──────────────────────┘
|
|
14
|
+
│ │
|
|
15
|
+
│ ┌────────────────▼────────────────────┐
|
|
16
|
+
│ │ Session Management (HTTP only) │
|
|
17
|
+
│ │ - Serial request queuing │
|
|
18
|
+
│ │ - Rate limiting per session │
|
|
19
|
+
│ │ - TTL-based garbage collection │
|
|
20
|
+
│ │ - AsyncLocalStorage auth context │
|
|
21
|
+
│ └────────────────┬────────────────────┘
|
|
22
|
+
│ │
|
|
23
|
+
┌─────────▼─────────────────────▼──────────────────────┐
|
|
24
|
+
│ MCP Server Factory │
|
|
25
|
+
│ build-server.ts │
|
|
26
|
+
│ ┌──────────────────────────────────────────────┐ │
|
|
27
|
+
│ │ registerHealthTool() │ │
|
|
28
|
+
│ │ registerGitLabTools() ──▶ Policy filtering │ │
|
|
29
|
+
│ └──────────────────────────────────────────────┘ │
|
|
30
|
+
└───────────────────────┬──────────────────────────────┘
|
|
31
|
+
│
|
|
32
|
+
┌───────────────────────▼──────────────────────────────┐
|
|
33
|
+
│ AppContext │
|
|
34
|
+
│ ┌──────────┐ ┌───────────┐ ┌──────────────────┐ │
|
|
35
|
+
│ │ env │ │ logger │ │ gitlab (Client) │ │
|
|
36
|
+
│ │ (AppEnv) │ │ (Pino) │ │ │ │
|
|
37
|
+
│ └──────────┘ └───────────┘ └──────────────────┘ │
|
|
38
|
+
│ ┌──────────────────┐ ┌─────────────────────────┐ │
|
|
39
|
+
│ │ policy (Engine) │ │ formatter (Output) │ │
|
|
40
|
+
│ └──────────────────┘ └─────────────────────────┘ │
|
|
41
|
+
└──────────────────────────────────────────────────────┘
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Module Dependency Graph
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
config/env.ts
|
|
48
|
+
▲
|
|
49
|
+
│ (imported by all entry points)
|
|
50
|
+
│
|
|
51
|
+
├── index.ts (stdio)
|
|
52
|
+
│ └── lib/gitlab-client.ts
|
|
53
|
+
│ └── lib/policy.ts
|
|
54
|
+
│ └── lib/output.ts
|
|
55
|
+
│ └── lib/network.ts
|
|
56
|
+
│ └── lib/request-runtime.ts
|
|
57
|
+
│ └── lib/oauth.ts
|
|
58
|
+
│ └── server/build-server.ts
|
|
59
|
+
│ └── tools/gitlab.ts
|
|
60
|
+
│ │ └── tools/mr-code-context.ts
|
|
61
|
+
│ └── tools/health.ts
|
|
62
|
+
│
|
|
63
|
+
└── http.ts (HTTP)
|
|
64
|
+
└── (same dependencies as index.ts)
|
|
65
|
+
└── lib/auth-context.ts (AsyncLocalStorage)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Key Modules
|
|
69
|
+
|
|
70
|
+
### `config/env.ts` — Configuration
|
|
71
|
+
|
|
72
|
+
- Parses and validates all environment variables using Zod schemas
|
|
73
|
+
- Enforces cross-field constraints (e.g. OAuth requires client ID, dynamic API URL requires remote auth)
|
|
74
|
+
- Normalizes API URLs to `/api/v4` suffix
|
|
75
|
+
- Exports a typed `env` singleton and `AppEnv` type
|
|
76
|
+
- Fails fast at startup with descriptive error messages
|
|
77
|
+
|
|
78
|
+
### `server/build-server.ts` — Server Factory
|
|
79
|
+
|
|
80
|
+
- Creates an `McpServer` instance with configured name and version
|
|
81
|
+
- Registers the health check tool
|
|
82
|
+
- Registers all GitLab tools after policy filtering
|
|
83
|
+
- Pure factory function — no side effects
|
|
84
|
+
|
|
85
|
+
### `tools/gitlab.ts` — Tool Definitions
|
|
86
|
+
|
|
87
|
+
- Defines 80+ tools as a `GitLabToolDefinition[]` array
|
|
88
|
+
- Each definition specifies: `name`, `title`, `description`, `mutating`, optional `requiresFeature`, `inputSchema` (Zod), and `handler`
|
|
89
|
+
- Tools are filtered by the policy engine at registration time
|
|
90
|
+
- Tool execution wraps results through the output formatter
|
|
91
|
+
- Error handling converts `GitLabApiError` to structured MCP error responses
|
|
92
|
+
|
|
93
|
+
**Tool execution flow:**
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
Raw args ──▶ stripNullsDeep ──▶ handler(args, context) ──▶ formatter.format() ──▶ MCP response
|
|
97
|
+
│
|
|
98
|
+
└── assertAuthReady() (check token exists)
|
|
99
|
+
└── resolveProjectId() (apply project allowlist)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `tools/mr-code-context.ts` — MR Code Context
|
|
103
|
+
|
|
104
|
+
A specialized tool for AI-assisted code review:
|
|
105
|
+
|
|
106
|
+
1. Fetches MR diff files
|
|
107
|
+
2. Filters by glob patterns, extensions, or languages
|
|
108
|
+
3. Sorts by changed lines, path, or file size
|
|
109
|
+
4. Retrieves file content within a character budget
|
|
110
|
+
5. Supports three content modes:
|
|
111
|
+
- **patch** — Raw unified diff
|
|
112
|
+
- **surrounding** — Changed lines with N lines of context from the full file
|
|
113
|
+
- **fullfile** — Complete file content
|
|
114
|
+
6. Supports `list_only` mode for two-stage retrieval (list first, then fetch selectively)
|
|
115
|
+
|
|
116
|
+
### `lib/gitlab-client.ts` — GitLab API Client
|
|
117
|
+
|
|
118
|
+
- Wraps the GitLab REST API v4 with typed methods
|
|
119
|
+
- Supports multi-instance URL rotation (round-robin)
|
|
120
|
+
- Pre-request hook system (`beforeRequest`) for token/header injection
|
|
121
|
+
- Per-session auth via `AsyncLocalStorage` (checked before each request)
|
|
122
|
+
- Configurable timeout with `AbortSignal`
|
|
123
|
+
- Error wrapping via `GitLabApiError` with status code and details
|
|
124
|
+
|
|
125
|
+
**Request lifecycle:**
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
Method call ──▶ Build URL ──▶ Set headers ──▶ beforeRequest hook
|
|
129
|
+
│ │
|
|
130
|
+
│ ┌───────────────────┘
|
|
131
|
+
│ ▼
|
|
132
|
+
│ Apply session auth (if available)
|
|
133
|
+
│ Apply token header
|
|
134
|
+
│ Apply compatibility headers
|
|
135
|
+
│ │
|
|
136
|
+
└──────────────────────────▼
|
|
137
|
+
fetch() with timeout
|
|
138
|
+
│
|
|
139
|
+
Parse response / throw GitLabApiError
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `lib/policy.ts` — Tool Policy Engine
|
|
143
|
+
|
|
144
|
+
Controls which tools are available:
|
|
145
|
+
|
|
146
|
+
1. **Read-only mode** — Blocks all tools marked `mutating: true`
|
|
147
|
+
2. **Feature toggles** — Blocks tools requiring disabled features (wiki, milestone, pipeline, release)
|
|
148
|
+
3. **Allowlist** — If set, only listed tools are available. Tool names are normalized (accepts `get_project` or `gitlab_get_project`)
|
|
149
|
+
4. **Deny regex** — Blocks tools matching a regex pattern
|
|
150
|
+
|
|
151
|
+
Policy is applied in two places:
|
|
152
|
+
|
|
153
|
+
- **Registration time** — `filterTools()` removes tools from the MCP server entirely
|
|
154
|
+
- **Execution time** — `assertCanExecute()` double-checks (defense in depth)
|
|
155
|
+
|
|
156
|
+
### `lib/auth-context.ts` — Session Auth Context
|
|
157
|
+
|
|
158
|
+
Uses Node.js `AsyncLocalStorage` to provide per-request authentication context:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
interface SessionAuth {
|
|
162
|
+
sessionId?: string;
|
|
163
|
+
token?: string;
|
|
164
|
+
apiUrl?: string;
|
|
165
|
+
header?: "authorization" | "private-token";
|
|
166
|
+
updatedAt: number;
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
- In HTTP mode, each request runs within `runWithSessionAuth()` which sets the context
|
|
171
|
+
- The GitLab client reads `getSessionAuth()` to get per-request credentials
|
|
172
|
+
- In stdio mode, session auth is not used (static PAT is the primary method)
|
|
173
|
+
|
|
174
|
+
### `lib/request-runtime.ts` — Request Preprocessing
|
|
175
|
+
|
|
176
|
+
Orchestrates authentication and request modifications:
|
|
177
|
+
|
|
178
|
+
1. **Cookie management** — Loads Netscape cookie files, auto-reloads on changes, creates `fetch-cookie` wrapper
|
|
179
|
+
2. **Session warmup** — Sends a warmup request to establish cookie sessions
|
|
180
|
+
3. **Token resolution** — When no request/session/PAT token is present, tries OAuth, then token script, then token file
|
|
181
|
+
4. **Compatibility headers** — Applies User-Agent, Accept-Language for Cloudflare bypass
|
|
182
|
+
5. **Token caching** — Caches resolved tokens with configurable TTL
|
|
183
|
+
|
|
184
|
+
### `lib/oauth.ts` — OAuth PKCE Manager
|
|
185
|
+
|
|
186
|
+
Implements the full OAuth 2.0 PKCE flow:
|
|
187
|
+
|
|
188
|
+
1. Check stored token → use if not expired
|
|
189
|
+
2. Try refresh token → persist new token
|
|
190
|
+
3. Fall back to interactive flow:
|
|
191
|
+
- Generate PKCE challenge
|
|
192
|
+
- Build authorization URL
|
|
193
|
+
- Start local HTTP callback server
|
|
194
|
+
- Open browser (optional)
|
|
195
|
+
- Wait for callback (3 minute timeout)
|
|
196
|
+
- Exchange code for token
|
|
197
|
+
- Persist token (chmod 600)
|
|
198
|
+
|
|
199
|
+
### `lib/network.ts` — Network Runtime
|
|
200
|
+
|
|
201
|
+
Configures global fetch behavior using `undici`:
|
|
202
|
+
|
|
203
|
+
- Sets up proxy agent (`ProxyAgent`) if `HTTP_PROXY`/`HTTPS_PROXY` is set
|
|
204
|
+
- Loads custom CA certificates from `GITLAB_CA_CERT_PATH`
|
|
205
|
+
- Controls TLS verification via `rejectUnauthorized`
|
|
206
|
+
- Applied globally via `setGlobalDispatcher()`
|
|
207
|
+
|
|
208
|
+
### `lib/output.ts` — Response Formatting
|
|
209
|
+
|
|
210
|
+
- Serializes tool output to JSON (pretty), compact JSON, or YAML
|
|
211
|
+
- Enforces `GITLAB_MAX_RESPONSE_BYTES` limit
|
|
212
|
+
- Truncates oversized responses with a `[truncated N bytes]` marker
|
|
213
|
+
- Returns metadata: `truncated` flag and `bytes` count
|
|
214
|
+
|
|
215
|
+
### `lib/sanitize.ts` — Null Stripping
|
|
216
|
+
|
|
217
|
+
`stripNullsDeep()` recursively removes `null` values from objects and arrays before passing them to the GitLab API. This prevents sending `null` in JSON payloads where `undefined` (omission) is the correct behavior.
|
|
218
|
+
|
|
219
|
+
## HTTP Server Session Management
|
|
220
|
+
|
|
221
|
+
The HTTP server (`http.ts`) implements a sophisticated session management system:
|
|
222
|
+
|
|
223
|
+
### Streamable HTTP Sessions
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
Client POST /mcp (no session-id)
|
|
227
|
+
└── Create new session
|
|
228
|
+
├── Create McpServer instance
|
|
229
|
+
├── Create StreamableHTTPServerTransport
|
|
230
|
+
├── Add to pending sessions
|
|
231
|
+
├── Connect server to transport
|
|
232
|
+
├── On session init → move to active sessions
|
|
233
|
+
└── Return Mcp-Session-Id header
|
|
234
|
+
|
|
235
|
+
Client POST /mcp (with Mcp-Session-Id)
|
|
236
|
+
└── Look up existing session
|
|
237
|
+
├── Refresh auth from request headers
|
|
238
|
+
├── Check rate limit
|
|
239
|
+
├── Enqueue request (serial per session)
|
|
240
|
+
└── Process within session auth context
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Session Lifecycle
|
|
244
|
+
|
|
245
|
+
1. **Creation** — New session created on first POST without session ID
|
|
246
|
+
2. **Active** — Session receives requests, each queued serially
|
|
247
|
+
3. **Idle timeout** — Garbage collected after `SESSION_TIMEOUT_SECONDS` of inactivity
|
|
248
|
+
4. **Rate limited** — Returns 429 after `MAX_REQUESTS_PER_MINUTE` per session
|
|
249
|
+
5. **Capacity** — Returns 503 when `MAX_SESSIONS` is reached
|
|
250
|
+
6. **Shutdown** — All sessions closed gracefully on SIGINT/SIGTERM
|
|
251
|
+
|
|
252
|
+
### SSE Sessions (Legacy)
|
|
253
|
+
|
|
254
|
+
When `SSE=true`:
|
|
255
|
+
|
|
256
|
+
- Clients connect via `GET /sse` to establish an SSE stream
|
|
257
|
+
- Messages are sent via `POST /messages?sessionId=...`
|
|
258
|
+
- Sessions are cleaned up on client disconnect or idle timeout
|
|
259
|
+
|
|
260
|
+
## Design Patterns
|
|
261
|
+
|
|
262
|
+
### Dependency Injection via AppContext
|
|
263
|
+
|
|
264
|
+
All shared services are bundled into an `AppContext` interface:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
interface AppContext {
|
|
268
|
+
env: AppEnv;
|
|
269
|
+
logger: Logger;
|
|
270
|
+
gitlab: GitLabClient;
|
|
271
|
+
policy: ToolPolicyEngine;
|
|
272
|
+
formatter: OutputFormatter;
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
This is created once at startup and passed to tool registration functions. Tools access all services through this context.
|
|
277
|
+
|
|
278
|
+
### Null Preprocessing in Zod Schemas
|
|
279
|
+
|
|
280
|
+
Many MCP clients send `null` for optional parameters. The tool schemas use `z.preprocess()` to convert `null` to `undefined`:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
const optionalString = z.preprocess(
|
|
284
|
+
(value) => (value === null ? undefined : value),
|
|
285
|
+
z.string().optional()
|
|
286
|
+
);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Backward Compatibility Aliases
|
|
290
|
+
|
|
291
|
+
Several tools have backward-compatible aliases to support existing integrations:
|
|
292
|
+
|
|
293
|
+
- `gitlab_mr_discussions` → alias of `gitlab_list_merge_request_discussions`
|
|
294
|
+
- `gitlab_get_merge_request_notes` → alias of `gitlab_list_merge_request_notes`
|
|
295
|
+
- `gitlab_edit_milestone` → alias of `gitlab_update_milestone`
|
|
296
|
+
- `gitlab_execute_graphql` → backward-compatible executor honoring read-only policy
|
|
297
|
+
|
|
298
|
+
### Structured Content in Responses
|
|
299
|
+
|
|
300
|
+
Tool responses include both text content (for display) and structured content (for programmatic access):
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
return {
|
|
304
|
+
content: [{ type: "text", text: formatted.text }],
|
|
305
|
+
structuredContent: {
|
|
306
|
+
result: toStructuredContent(result),
|
|
307
|
+
meta: { truncated: formatted.truncated, bytes: formatted.bytes }
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
```
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# Authentication Guide
|
|
2
|
+
|
|
3
|
+
gitlab-mcp supports multiple authentication methods. Token behavior depends on whether remote authorization is enabled.
|
|
4
|
+
|
|
5
|
+
## Token Resolution
|
|
6
|
+
|
|
7
|
+
### Default Mode (`REMOTE_AUTHORIZATION=false`)
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Static PAT (GITLAB_PERSONAL_ACCESS_TOKEN)
|
|
11
|
+
└─> OAuth 2.0 PKCE
|
|
12
|
+
└─> External token script
|
|
13
|
+
└─> Token file
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Remote Authorization Mode (`REMOTE_AUTHORIZATION=true`, HTTP)
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
Per-request auth only (required)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
In remote authorization mode, each request must include `Authorization: Bearer <token>` or
|
|
23
|
+
`Private-Token: <token>`. If `ENABLE_DYNAMIC_API_URL=true`, each request must also include
|
|
24
|
+
`X-GitLab-API-URL`.
|
|
25
|
+
|
|
26
|
+
Cookie-based auth (`GITLAB_AUTH_COOKIE_PATH`) is applied independently through a cookie jar and is not part of the token chain.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 1. Personal Access Token (PAT)
|
|
31
|
+
|
|
32
|
+
The simplest method. Create a token at **GitLab > Settings > Access Tokens** with the `api` scope.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
GITLAB_PERSONAL_ACCESS_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This token is the default request token in stdio and HTTP modes when `REMOTE_AUTHORIZATION=false`.
|
|
39
|
+
When `REMOTE_AUTHORIZATION=true`, request headers are required and PAT fallback is disabled.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 2. OAuth 2.0 PKCE
|
|
44
|
+
|
|
45
|
+
Browser-based OAuth flow for interactive use. The server launches a local callback server and opens the browser for authorization.
|
|
46
|
+
|
|
47
|
+
### Setup
|
|
48
|
+
|
|
49
|
+
1. Register an OAuth application in GitLab (**Settings > Applications** or admin area):
|
|
50
|
+
- **Redirect URI:** `http://127.0.0.1:8765/callback`
|
|
51
|
+
- **Scopes:** `api`
|
|
52
|
+
- **Confidential:** No (for PKCE public clients)
|
|
53
|
+
- Note the **Application ID**
|
|
54
|
+
|
|
55
|
+
2. Configure environment variables:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
GITLAB_USE_OAUTH=true
|
|
59
|
+
GITLAB_OAUTH_CLIENT_ID=your-application-id
|
|
60
|
+
GITLAB_OAUTH_REDIRECT_URI=http://127.0.0.1:8765/callback
|
|
61
|
+
GITLAB_OAUTH_SCOPES=api
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Optional Settings
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# For confidential applications
|
|
68
|
+
GITLAB_OAUTH_CLIENT_SECRET=your-client-secret
|
|
69
|
+
|
|
70
|
+
# Custom GitLab URL (derived from GITLAB_API_URL if not set)
|
|
71
|
+
GITLAB_OAUTH_GITLAB_URL=https://gitlab.example.com
|
|
72
|
+
|
|
73
|
+
# Token storage location (default: ~/.gitlab-mcp-oauth-token.json)
|
|
74
|
+
GITLAB_OAUTH_TOKEN_PATH=~/.gitlab-mcp-oauth-token.json
|
|
75
|
+
|
|
76
|
+
# Disable auto-opening the browser
|
|
77
|
+
GITLAB_OAUTH_AUTO_OPEN_BROWSER=false
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### How It Works
|
|
81
|
+
|
|
82
|
+
1. On first request, the server checks for a stored token at `GITLAB_OAUTH_TOKEN_PATH`
|
|
83
|
+
2. If no valid token exists, it generates a PKCE challenge and opens the browser
|
|
84
|
+
3. The user authorizes the application in GitLab
|
|
85
|
+
4. GitLab redirects back to the local callback server with an authorization code
|
|
86
|
+
5. The server exchanges the code for an access token (with PKCE verifier)
|
|
87
|
+
6. The token is persisted to disk (chmod 600) for future sessions
|
|
88
|
+
7. On subsequent requests, the stored token is reused until it expires
|
|
89
|
+
8. Expired tokens are automatically refreshed using the refresh token
|
|
90
|
+
|
|
91
|
+
### Notes
|
|
92
|
+
|
|
93
|
+
- The callback server listens for up to 3 minutes before timing out
|
|
94
|
+
- Token files are stored with `0600` permissions
|
|
95
|
+
- If refresh fails, the server falls back to interactive authorization
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 3. External Token Script
|
|
100
|
+
|
|
101
|
+
Execute a shell command to obtain a token dynamically. Useful for integration with secret managers, vault systems, or custom token providers.
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
GITLAB_TOKEN_SCRIPT=/path/to/get-token.sh
|
|
105
|
+
GITLAB_TOKEN_SCRIPT_TIMEOUT_MS=10000 # 500ms–120s (default: 10s)
|
|
106
|
+
GITLAB_TOKEN_CACHE_SECONDS=300 # 0–86400s (default: 5min)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Script Output Format
|
|
110
|
+
|
|
111
|
+
The script must output one of:
|
|
112
|
+
|
|
113
|
+
1. **Raw token string** (plain text on stdout):
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
glpat-xxxxxxxxxxxxxxxxxxxx
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
2. **JSON object** with any of these keys:
|
|
120
|
+
```json
|
|
121
|
+
{ "access_token": "glpat-xxxxxxxxxxxxxxxxxxxx" }
|
|
122
|
+
```
|
|
123
|
+
```json
|
|
124
|
+
{ "token": "glpat-xxxxxxxxxxxxxxxxxxxx" }
|
|
125
|
+
```
|
|
126
|
+
```json
|
|
127
|
+
{ "private_token": "glpat-xxxxxxxxxxxxxxxxxxxx" }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Example Script
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
#!/usr/bin/env bash
|
|
134
|
+
set -euo pipefail
|
|
135
|
+
|
|
136
|
+
# Example: read from environment or secret manager
|
|
137
|
+
if [[ -n "${GITLAB_OAUTH_ACCESS_TOKEN:-}" ]]; then
|
|
138
|
+
printf '{"access_token":"%s"}\n' "${GITLAB_OAUTH_ACCESS_TOKEN}"
|
|
139
|
+
exit 0
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
echo "Token not available" >&2
|
|
143
|
+
exit 1
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The resolved token is cached for `GITLAB_TOKEN_CACHE_SECONDS` to avoid repeated script executions.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 4. Token File
|
|
151
|
+
|
|
152
|
+
Read a token from a file on disk. The file should contain a raw token string or JSON (same format as the token script output).
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
GITLAB_TOKEN_FILE=~/.gitlab-token
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Security
|
|
159
|
+
|
|
160
|
+
By default, the server enforces strict file permissions — the token file must be readable only by the owner (`chmod 600`). If the file has group or other permissions, the server rejects it.
|
|
161
|
+
|
|
162
|
+
To override this check:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
GITLAB_ALLOW_INSECURE_TOKEN_FILE=true
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The token is cached for `GITLAB_TOKEN_CACHE_SECONDS` (default: 300s).
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 5. Cookie-Based Auth
|
|
173
|
+
|
|
174
|
+
Use browser cookies from a Netscape-format cookie file. This is useful when working with GitLab instances that use SSO or other browser-based authentication.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
GITLAB_AUTH_COOKIE_PATH=~/.gitlab-cookies.txt
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### How It Works
|
|
181
|
+
|
|
182
|
+
1. The server reads cookies from the file in Netscape cookie format
|
|
183
|
+
2. A cookie jar is created and attached to all API requests via `fetch-cookie`
|
|
184
|
+
3. Before the first API call to each GitLab instance, a warmup request is sent to establish the session
|
|
185
|
+
4. If the cookie file changes on disk, it is automatically reloaded
|
|
186
|
+
|
|
187
|
+
### Warmup Path
|
|
188
|
+
|
|
189
|
+
The warmup request hits a lightweight endpoint to establish the session:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
GITLAB_COOKIE_WARMUP_PATH=/user # default
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Cookie File Format
|
|
196
|
+
|
|
197
|
+
Standard Netscape cookie format (tab-separated):
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
# Netscape HTTP Cookie File
|
|
201
|
+
.gitlab.example.com TRUE / TRUE 0 _gitlab_session abc123...
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Lines starting with `#HttpOnly_` are parsed as HttpOnly cookies.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 6. Remote Authorization (HTTP Mode)
|
|
209
|
+
|
|
210
|
+
In HTTP transport mode, this enables strict per-request credentials. This is the recommended approach for shared/multi-user deployments.
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
REMOTE_AUTHORIZATION=true
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`REMOTE_AUTHORIZATION=true` enforces per-request credentials. If a request does not include a token header, the request is rejected.
|
|
217
|
+
|
|
218
|
+
### Client Headers
|
|
219
|
+
|
|
220
|
+
The server accepts tokens via:
|
|
221
|
+
|
|
222
|
+
- **`Authorization: Bearer <token>`** — Standard bearer token
|
|
223
|
+
- **`Private-Token: <token>`** — GitLab private token header
|
|
224
|
+
|
|
225
|
+
### Dynamic API URL
|
|
226
|
+
|
|
227
|
+
When serving multiple GitLab instances, enable dynamic API URL per request:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
REMOTE_AUTHORIZATION=true
|
|
231
|
+
ENABLE_DYNAMIC_API_URL=true
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Clients can then send:
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
X-GitLab-API-URL: https://other-gitlab.example.com/api/v4
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Authentication Flow (HTTP Mode)
|
|
241
|
+
|
|
242
|
+
1. Client sends a request with auth headers
|
|
243
|
+
2. The server extracts the token and required API URL (when dynamic API URLs are enabled) from headers
|
|
244
|
+
3. Auth context is stored in `AsyncLocalStorage` for the duration of the request
|
|
245
|
+
4. All GitLab API calls within that request use the per-session credentials
|
|
246
|
+
5. Requests missing required headers are rejected before tool execution
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Cloudflare Bypass
|
|
251
|
+
|
|
252
|
+
If your GitLab instance is behind Cloudflare, enable browser-like headers:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
GITLAB_CLOUDFLARE_BYPASS=true
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
This adds:
|
|
259
|
+
|
|
260
|
+
- A Chrome-like `User-Agent` header
|
|
261
|
+
- `Accept-Language: en-US,en;q=0.9`
|
|
262
|
+
- `Cache-Control: no-cache`
|
|
263
|
+
- `Pragma: no-cache`
|
|
264
|
+
|
|
265
|
+
You can also set a custom User-Agent:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
GITLAB_USER_AGENT="MyApp/1.0"
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## TLS & Proxy Configuration
|
|
274
|
+
|
|
275
|
+
### Custom CA Certificate
|
|
276
|
+
|
|
277
|
+
For self-signed or internal CA certificates:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
GITLAB_CA_CERT_PATH=/path/to/ca-bundle.pem
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### HTTP Proxy
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
HTTP_PROXY=http://proxy.example.com:8080
|
|
287
|
+
HTTPS_PROXY=http://proxy.example.com:8080
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Insecure TLS (Not Recommended)
|
|
291
|
+
|
|
292
|
+
To disable TLS certificate verification, you must explicitly acknowledge the risk:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
NODE_TLS_REJECT_UNAUTHORIZED=0
|
|
296
|
+
GITLAB_ALLOW_INSECURE_TLS=true
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Both settings are required — setting only `NODE_TLS_REJECT_UNAUTHORIZED=0` without the acknowledgment flag will cause a startup error.
|