@zereight/mcp-gitlab 2.1.25 → 2.1.27

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/build/oauth.js CHANGED
@@ -5,6 +5,8 @@ import * as path from "path";
5
5
  import * as http from "http";
6
6
  import * as net from "net";
7
7
  import * as url from "url";
8
+ import { execFile } from "child_process";
9
+ import { promisify } from "util";
8
10
  import open from "open";
9
11
  import pkceChallenge from "pkce-challenge";
10
12
  import { pino } from "pino";
@@ -12,6 +14,7 @@ const logger = pino({
12
14
  name: "gitlab-mcp-oauth",
13
15
  level: process.env.LOG_LEVEL || "info",
14
16
  }, pino.destination(2));
17
+ const execFileAsync = promisify(execFile);
15
18
  function escapeHtml(str) {
16
19
  const map = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
17
20
  return String(str).replace(/[&<>"']/g, c => map[c] || c);
@@ -456,10 +459,64 @@ export class GitLabOAuth {
456
459
  });
457
460
  });
458
461
  }
462
+ /**
463
+ * Get an access token from an external command.
464
+ */
465
+ async getScriptToken() {
466
+ if (!this.config.tokenScript) {
467
+ throw new Error("OAuth token script is not configured");
468
+ }
469
+ const shell = process.platform === "win32" ? process.env.ComSpec || "cmd.exe" : "/bin/sh";
470
+ const args = process.platform === "win32"
471
+ ? ["/d", "/s", "/c", this.config.tokenScript]
472
+ : ["-c", this.config.tokenScript];
473
+ const timeoutSeconds = Number.parseInt(process.env.GITLAB_OAUTH_TOKEN_SCRIPT_TIMEOUT_SECONDS || "30", 10);
474
+ const timeoutMs = (Number.isFinite(timeoutSeconds) && timeoutSeconds > 0 ? timeoutSeconds : 30) * 1000;
475
+ const { stdout } = await execFileAsync(shell, args, {
476
+ timeout: timeoutMs,
477
+ maxBuffer: 1024 * 1024,
478
+ });
479
+ const output = stdout.trim();
480
+ if (!output) {
481
+ throw new Error("OAuth token script produced no output");
482
+ }
483
+ let accessToken = output;
484
+ try {
485
+ const parsed = JSON.parse(output);
486
+ if (typeof parsed === "string") {
487
+ accessToken = parsed;
488
+ }
489
+ else if (parsed && typeof parsed === "object") {
490
+ const value = parsed.access_token ??
491
+ parsed.token;
492
+ if (typeof value !== "string") {
493
+ throw new Error("OAuth token script JSON must include a string access_token or token field");
494
+ }
495
+ accessToken = value;
496
+ }
497
+ }
498
+ catch (error) {
499
+ if (error instanceof Error && error.message.startsWith("OAuth token script JSON")) {
500
+ throw error;
501
+ }
502
+ // Plain-token stdout is the common case.
503
+ }
504
+ if (!accessToken.trim()) {
505
+ throw new Error("OAuth token script returned an empty token");
506
+ }
507
+ return {
508
+ access_token: accessToken.trim(),
509
+ created_at: Date.now(),
510
+ token_type: "Bearer",
511
+ };
512
+ }
459
513
  /**
460
514
  * Get a valid access token, refreshing if necessary
461
515
  */
462
516
  async getAccessToken(force = false) {
517
+ if (this.config.tokenScript) {
518
+ return (await this.getScriptToken()).access_token;
519
+ }
463
520
  let tokenData = this.loadToken();
464
521
  // If no token or expired (or forced), start OAuth flow or refresh
465
522
  if (!tokenData) {
@@ -503,6 +560,9 @@ export class GitLabOAuth {
503
560
  * Check if a valid token exists
504
561
  */
505
562
  hasValidToken() {
563
+ if (this.config.tokenScript) {
564
+ return true;
565
+ }
506
566
  const tokenData = this.loadToken();
507
567
  if (!tokenData) {
508
568
  return false;
@@ -520,16 +580,18 @@ export async function initializeOAuthClient(gitlabUrl = "https://gitlab.com") {
520
580
  const clientSecret = process.env.GITLAB_OAUTH_CLIENT_SECRET;
521
581
  const redirectUri = process.env.GITLAB_OAUTH_REDIRECT_URI || "http://127.0.0.1:8888/callback";
522
582
  const tokenStoragePath = process.env.GITLAB_OAUTH_TOKEN_PATH;
523
- if (!clientId) {
524
- throw new Error("GITLAB_OAUTH_CLIENT_ID environment variable is required for OAuth authentication");
583
+ const tokenScript = process.env.GITLAB_OAUTH_TOKEN_SCRIPT;
584
+ if (!clientId && !tokenScript) {
585
+ throw new Error("GITLAB_OAUTH_CLIENT_ID or GITLAB_OAUTH_TOKEN_SCRIPT environment variable is required for OAuth authentication");
525
586
  }
526
587
  const oauth = new GitLabOAuth({
527
- clientId,
588
+ clientId: clientId || "external-token-script",
528
589
  clientSecret,
529
590
  redirectUri,
530
591
  gitlabUrl,
531
592
  scopes: [process.env.GITLAB_READ_ONLY_MODE === "true" ? "read_api" : "api"],
532
593
  tokenStoragePath,
594
+ tokenScript,
533
595
  });
534
596
  // Single call: triggers browser flow if needed, or reads cached token
535
597
  const accessToken = await oauth.getAccessToken();