@zereight/mcp-gitlab 2.0.18 → 2.0.19

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.
Files changed (3) hide show
  1. package/README.md +73 -9
  2. package/build/oauth.js +20 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -28,6 +28,7 @@ The server supports two authentication methods:
28
28
  #### Using OAuth2 Authentication
29
29
 
30
30
  OAuth2 provides a more secure authentication flow using browser-based authentication. When enabled, the server will:
31
+
31
32
  1. Open your browser to GitLab's authorization page
32
33
  2. Wait for you to approve the access
33
34
  3. Store the token securely for future use
@@ -55,6 +56,7 @@ Then configure the MCP server with OAuth:
55
56
  "env": {
56
57
  "GITLAB_USE_OAUTH": "true",
57
58
  "GITLAB_OAUTH_CLIENT_ID": "your_oauth_client_id",
59
+ "GITLAB_OAUTH_CLIENT_SECRET": "your_oauth_client_secret", // Required for Confidential apps only
58
60
  "GITLAB_OAUTH_REDIRECT_URI": "http://127.0.0.1:8888/callback",
59
61
  "GITLAB_API_URL": "your_gitlab_api_url",
60
62
  "GITLAB_PROJECT_ID": "your_project_id", // Optional: default project
@@ -94,13 +96,69 @@ Then configure the MCP server with OAuth:
94
96
 
95
97
  #### vscode .vscode/mcp.json
96
98
 
99
+ **Using OAuth2 (Non-Confidential - Recommended):**
100
+
101
+ ```json
102
+ {
103
+ "servers": {
104
+ "GitLab-MCP": {
105
+ "type": "stdio",
106
+ "command": "npx",
107
+ "args": ["-y", "@zereight/mcp-gitlab"],
108
+ "env": {
109
+ "GITLAB_USE_OAUTH": "true",
110
+ "GITLAB_OAUTH_CLIENT_ID": "your_oauth_client_id",
111
+ "GITLAB_OAUTH_REDIRECT_URI": "http://127.0.0.1:8888/callback",
112
+ "GITLAB_API_URL": "https://gitlab.com/api/v4",
113
+ "GITLAB_READ_ONLY_MODE": "false",
114
+ "USE_GITLAB_WIKI": "false",
115
+ "USE_MILESTONE": "false",
116
+ "USE_PIPELINE": "false"
117
+ }
118
+ }
119
+ }
120
+ }
121
+ ```
122
+
123
+ **Using OAuth2 (Confidential):**
124
+
125
+ ```json
126
+ {
127
+ "inputs": [
128
+ {
129
+ "type": "promptString",
130
+ "id": "gitlab-oauth-secret",
131
+ "description": "GitLab OAuth Client Secret",
132
+ "password": true
133
+ }
134
+ ],
135
+ "servers": {
136
+ "GitLab-MCP": {
137
+ "type": "stdio",
138
+ "command": "npx",
139
+ "args": ["-y", "@zereight/mcp-gitlab"],
140
+ "env": {
141
+ "GITLAB_USE_OAUTH": "true",
142
+ "GITLAB_OAUTH_CLIENT_ID": "your_oauth_client_id",
143
+ "GITLAB_OAUTH_CLIENT_SECRET": "${input:gitlab-oauth-secret}",
144
+ "GITLAB_OAUTH_REDIRECT_URI": "http://127.0.0.1:8888/callback",
145
+ "GITLAB_API_URL": "https://gitlab.com/api/v4",
146
+ "GITLAB_READ_ONLY_MODE": "false"
147
+ }
148
+ }
149
+ }
150
+ }
151
+ ```
152
+
153
+ **Using Personal Access Token:**
154
+
97
155
  ```json
98
156
  {
99
157
  "inputs": [
100
158
  {
101
159
  "type": "promptString",
102
160
  "id": "gitlab-token",
103
- "description": "Gitlab Token to read API",
161
+ "description": "GitLab Personal Access Token",
104
162
  "password": true
105
163
  }
106
164
  ],
@@ -111,9 +169,11 @@ Then configure the MCP server with OAuth:
111
169
  "args": ["-y", "@zereight/mcp-gitlab"],
112
170
  "env": {
113
171
  "GITLAB_PERSONAL_ACCESS_TOKEN": "${input:gitlab-token}",
114
- "GITLAB_API_URL": "your-fancy-gitlab-url",
115
- "GITLAB_READ_ONLY_MODE": "true",
116
- ...
172
+ "GITLAB_API_URL": "https://gitlab.com/api/v4",
173
+ "GITLAB_READ_ONLY_MODE": "false",
174
+ "USE_GITLAB_WIKI": "false",
175
+ "USE_MILESTONE": "false",
176
+ "USE_PIPELINE": "false"
117
177
  }
118
178
  }
119
179
  }
@@ -127,7 +187,7 @@ env_vars = {
127
187
  "GITLAB_PERSONAL_ACCESS_TOKEN": gitlab_access_token,
128
188
  "GITLAB_API_URL": gitlab_api_url,
129
189
  "USE_GITLAB_WIKI": use_gitlab_wiki
130
- # ......the rest of the optional parameters
190
+ # ......the rest of the optional parameters
131
191
  }
132
192
 
133
193
  stdio_gitlab_mcp_client = MCPClient(
@@ -141,10 +201,11 @@ stdio_gitlab_mcp_client = MCPClient(
141
201
  )
142
202
  ```
143
203
 
144
-
145
204
  #### Docker
146
205
 
147
- - stdio mcp.json
206
+ > **Note**: For Docker deployments, **Personal Access Token is recommended**. OAuth requires browser-based authentication and a local callback server, which does not work properly in containerized environments.
207
+
208
+ **Using Personal Access Token (stdio) - Recommended:**
148
209
 
149
210
  ```json
150
211
  {
@@ -171,7 +232,7 @@ stdio_gitlab_mcp_client = MCPClient(
171
232
  ],
172
233
  "env": {
173
234
  "GITLAB_PERSONAL_ACCESS_TOKEN": "your_gitlab_token",
174
- "GITLAB_API_URL": "https://gitlab.com/api/v4", // Optional, for self-hosted GitLab
235
+ "GITLAB_API_URL": "https://gitlab.com/api/v4",
175
236
  "GITLAB_READ_ONLY_MODE": "false",
176
237
  "USE_GITLAB_WIKI": "true",
177
238
  "USE_MILESTONE": "true",
@@ -241,6 +302,7 @@ docker run -i --rm \
241
302
  - `GITLAB_PERSONAL_ACCESS_TOKEN`: Your GitLab personal access token. **Required in standard mode**; not used when `REMOTE_AUTHORIZATION=true` or when using OAuth.
242
303
  - `GITLAB_USE_OAUTH`: Set to `true` to enable OAuth2 authentication instead of personal access token.
243
304
  - `GITLAB_OAUTH_CLIENT_ID`: The Client ID from your GitLab OAuth application. Required when using OAuth.
305
+ - `GITLAB_OAUTH_CLIENT_SECRET`: The Client Secret from your GitLab OAuth application. Required only for Confidential applications.
244
306
  - `GITLAB_OAUTH_REDIRECT_URI`: The OAuth callback URL. Default: `http://127.0.0.1:8888/callback`
245
307
  - `GITLAB_OAUTH_TOKEN_PATH`: Custom path to store the OAuth token. Default: `~/.gitlab-mcp-token.json`
246
308
  - `REMOTE_AUTHORIZATION`: When set to 'true', enables remote per-session authorization via HTTP headers. In this mode:
@@ -290,6 +352,7 @@ When using Streamable HTTP transport, the following endpoints are available:
290
352
  ### Remote Authorization Setup (Multi-User Support)
291
353
 
292
354
  When using `REMOTE_AUTHORIZATION=true`, the MCP server can support multiple users, each with their own GitLab token passed via HTTP headers. This is useful for:
355
+
293
356
  - Shared MCP server instances where each user needs their own GitLab access
294
357
  - IDE integrations that can inject user-specific tokens into MCP requests
295
358
 
@@ -339,9 +402,10 @@ The token is stored per session (identified by `mcp-session-id` header) and reus
339
402
  ```
340
403
 
341
404
  **Important Notes:**
405
+
342
406
  - Remote authorization **only works with Streamable HTTP transport**
343
407
  - Each session is isolated - tokens from one session cannot access another session's data
344
- Tokens are automatically cleaned up when sessions close
408
+ Tokens are automatically cleaned up when sessions close
345
409
  - **Session timeout:** Auth tokens expire after `SESSION_TIMEOUT_SECONDS` (default 1 hour) of inactivity. After timeout, the client must send auth headers again. The transport session remains active.
346
410
  - Each request resets the timeout timer for that session
347
411
  - **Rate limiting:** Each session is limited to `MAX_REQUESTS_PER_MINUTE` requests per minute (default 60)
package/build/oauth.js CHANGED
@@ -16,7 +16,7 @@ const pendingAuthRequests = new Map();
16
16
  * Check if a port is already in use
17
17
  */
18
18
  async function isPortInUse(port) {
19
- return new Promise((resolve) => {
19
+ return new Promise(resolve => {
20
20
  const server = net.createServer();
21
21
  server.once("error", (err) => {
22
22
  if (err.code === "EADDRINUSE") {
@@ -44,9 +44,9 @@ async function requestAuthFromExistingServer(port, requestId) {
44
44
  path: `/auth-request?requestId=${requestId}`,
45
45
  method: "GET",
46
46
  };
47
- const req = http.request(options, (res) => {
47
+ const req = http.request(options, res => {
48
48
  let data = "";
49
- res.on("data", (chunk) => {
49
+ res.on("data", chunk => {
50
50
  data += chunk;
51
51
  });
52
52
  res.on("end", () => {
@@ -64,7 +64,7 @@ async function requestAuthFromExistingServer(port, requestId) {
64
64
  }
65
65
  });
66
66
  });
67
- req.on("error", (error) => {
67
+ req.on("error", error => {
68
68
  reject(new Error(`Failed to connect to existing OAuth server: ${error.message}`));
69
69
  });
70
70
  req.setTimeout(5 * 60 * 1000, () => {
@@ -82,8 +82,7 @@ export class GitLabOAuth {
82
82
  constructor(config) {
83
83
  this.config = config;
84
84
  this.tokenStoragePath =
85
- config.tokenStoragePath ||
86
- path.join(process.env.HOME || "", ".gitlab-mcp-token.json");
85
+ config.tokenStoragePath || path.join(process.env.HOME || "", ".gitlab-mcp-token.json");
87
86
  }
88
87
  /**
89
88
  * Get the authorization URL for OAuth flow
@@ -119,6 +118,10 @@ export class GitLabOAuth {
119
118
  redirect_uri: this.config.redirectUri,
120
119
  code_verifier: this.codeVerifier,
121
120
  });
121
+ // Add client_secret for Confidential applications
122
+ if (this.config.clientSecret) {
123
+ params.append("client_secret", this.config.clientSecret);
124
+ }
122
125
  const response = await fetch(tokenUrl, {
123
126
  method: "POST",
124
127
  headers: {
@@ -130,7 +133,7 @@ export class GitLabOAuth {
130
133
  const errorText = await response.text();
131
134
  throw new Error(`Token exchange failed: ${response.status} ${errorText}`);
132
135
  }
133
- const data = await response.json();
136
+ const data = (await response.json());
134
137
  return {
135
138
  access_token: data.access_token,
136
139
  refresh_token: data.refresh_token,
@@ -150,6 +153,10 @@ export class GitLabOAuth {
150
153
  grant_type: "refresh_token",
151
154
  redirect_uri: this.config.redirectUri,
152
155
  });
156
+ // Add client_secret for Confidential applications
157
+ if (this.config.clientSecret) {
158
+ params.append("client_secret", this.config.clientSecret);
159
+ }
153
160
  const response = await fetch(tokenUrl, {
154
161
  method: "POST",
155
162
  headers: {
@@ -161,7 +168,7 @@ export class GitLabOAuth {
161
168
  const errorText = await response.text();
162
169
  throw new Error(`Token refresh failed: ${response.status} ${errorText}`);
163
170
  }
164
- const data = await response.json();
171
+ const data = (await response.json());
165
172
  return {
166
173
  access_token: data.access_token,
167
174
  refresh_token: data.refresh_token || refreshToken,
@@ -271,7 +278,7 @@ export class GitLabOAuth {
271
278
  const authUrl = await this.getAuthorizationUrl(newState);
272
279
  logger.info("Opening browser for new authentication request...");
273
280
  logger.info(`If browser doesn't open, visit: ${authUrl}`);
274
- open(authUrl).catch((err) => {
281
+ open(authUrl).catch(err => {
275
282
  logger.error("Failed to open browser:", err);
276
283
  logger.info(`Please manually open: ${authUrl}`);
277
284
  });
@@ -427,12 +434,12 @@ export class GitLabOAuth {
427
434
  const authUrl = await this.getAuthorizationUrl(state);
428
435
  logger.info("Opening browser for authentication...");
429
436
  logger.info(`If browser doesn't open, visit: ${authUrl}`);
430
- open(authUrl).catch((err) => {
437
+ open(authUrl).catch(err => {
431
438
  logger.error("Failed to open browser:", err);
432
439
  logger.info(`Please manually open: ${authUrl}`);
433
440
  });
434
441
  });
435
- server.on("error", (error) => {
442
+ server.on("error", error => {
436
443
  logger.error("OAuth server error:", error);
437
444
  const pending = pendingAuthRequests.get(initialRequestId);
438
445
  if (pending) {
@@ -502,6 +509,7 @@ export class GitLabOAuth {
502
509
  */
503
510
  export async function initializeOAuth(gitlabUrl = "https://gitlab.com") {
504
511
  const clientId = process.env.GITLAB_OAUTH_CLIENT_ID;
512
+ const clientSecret = process.env.GITLAB_OAUTH_CLIENT_SECRET;
505
513
  const redirectUri = process.env.GITLAB_OAUTH_REDIRECT_URI || "http://127.0.0.1:8888/callback";
506
514
  const tokenStoragePath = process.env.GITLAB_OAUTH_TOKEN_PATH;
507
515
  if (!clientId) {
@@ -509,6 +517,7 @@ export async function initializeOAuth(gitlabUrl = "https://gitlab.com") {
509
517
  }
510
518
  const oauth = new GitLabOAuth({
511
519
  clientId,
520
+ clientSecret,
512
521
  redirectUri,
513
522
  gitlabUrl,
514
523
  scopes: ["api"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "2.0.18",
3
+ "version": "2.0.19",
4
4
  "description": "MCP server for using the GitLab API",
5
5
  "license": "MIT",
6
6
  "author": "zereight",