gitlab-mcp 1.1.0 → 1.2.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/LICENSE +21 -0
- package/dist/config/env.d.ts +56 -0
- package/dist/config/env.js +163 -0
- package/dist/config/env.js.map +1 -0
- package/dist/http-app.d.ts +45 -0
- package/dist/http-app.js +550 -0
- package/dist/http-app.js.map +1 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.js +65 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/auth-context.d.ts +9 -0
- package/dist/lib/auth-context.js +9 -0
- package/dist/lib/auth-context.js.map +1 -0
- package/dist/lib/gitlab-client.d.ts +331 -0
- package/dist/lib/gitlab-client.js +1025 -0
- package/dist/lib/gitlab-client.js.map +1 -0
- package/dist/lib/logger.d.ts +2 -0
- package/dist/lib/logger.js +13 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/network.d.ts +3 -0
- package/dist/lib/network.js +38 -0
- package/dist/lib/network.js.map +1 -0
- package/dist/lib/oauth.d.ts +29 -0
- package/dist/lib/oauth.js +220 -0
- package/dist/lib/oauth.js.map +1 -0
- package/dist/lib/output.d.ts +14 -0
- package/dist/lib/output.js +38 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/policy.d.ts +25 -0
- package/dist/lib/policy.js +48 -0
- package/dist/lib/policy.js.map +1 -0
- package/dist/lib/request-runtime.d.ts +26 -0
- package/dist/lib/request-runtime.js +323 -0
- package/dist/lib/request-runtime.js.map +1 -0
- package/dist/lib/sanitize.d.ts +1 -0
- package/dist/lib/sanitize.js +21 -0
- package/dist/lib/sanitize.js.map +1 -0
- package/dist/lib/session-capacity.d.ts +8 -0
- package/dist/lib/session-capacity.js +7 -0
- package/dist/lib/session-capacity.js.map +1 -0
- package/dist/server/build-server.d.ts +3 -0
- package/dist/server/build-server.js +13 -0
- package/dist/server/build-server.js.map +1 -0
- package/dist/tools/gitlab.d.ts +9 -0
- package/dist/tools/gitlab.js +2576 -0
- package/dist/tools/gitlab.js.map +1 -0
- package/dist/tools/health.d.ts +2 -0
- package/dist/tools/health.js +21 -0
- package/dist/tools/health.js.map +1 -0
- package/dist/tools/mr-code-context.d.ts +38 -0
- package/dist/tools/mr-code-context.js +330 -0
- package/dist/tools/mr-code-context.js.map +1 -0
- package/{src/types/context.ts → dist/types/context.d.ts} +5 -6
- package/dist/types/context.js +2 -0
- package/dist/types/context.js.map +1 -0
- package/docs/configuration.md +6 -6
- package/docs/mcp-integration-testing-best-practices.md +981 -0
- package/package.json +13 -1
- package/.dockerignore +0 -7
- package/.editorconfig +0 -9
- package/.env.example +0 -75
- package/.github/workflows/nodejs.yml +0 -31
- package/.github/workflows/npm-publish.yml +0 -31
- package/.husky/pre-commit +0 -1
- package/.nvmrc +0 -1
- package/.prettierrc.json +0 -6
- package/Dockerfile +0 -20
- package/docker-compose.yml +0 -10
- package/eslint.config.js +0 -23
- package/scripts/get-oauth-token.example.sh +0 -15
- package/src/config/env.ts +0 -171
- package/src/http.ts +0 -620
- package/src/index.ts +0 -77
- package/src/lib/auth-context.ts +0 -19
- package/src/lib/gitlab-client.ts +0 -1810
- package/src/lib/logger.ts +0 -17
- package/src/lib/network.ts +0 -45
- package/src/lib/oauth.ts +0 -287
- package/src/lib/output.ts +0 -51
- package/src/lib/policy.ts +0 -78
- package/src/lib/request-runtime.ts +0 -376
- package/src/lib/sanitize.ts +0 -25
- package/src/lib/session-capacity.ts +0 -14
- package/src/server/build-server.ts +0 -17
- package/src/tools/gitlab.ts +0 -3135
- package/src/tools/health.ts +0 -27
- package/src/tools/mr-code-context.ts +0 -473
- package/tests/auth-context.test.ts +0 -102
- package/tests/gitlab-client.test.ts +0 -672
- package/tests/graphql-guard.test.ts +0 -121
- package/tests/integration/agent-loop.integration.test.ts +0 -558
- package/tests/integration/server.integration.test.ts +0 -543
- package/tests/mr-code-context.test.ts +0 -600
- package/tests/oauth.test.ts +0 -43
- package/tests/output.test.ts +0 -186
- package/tests/policy.test.ts +0 -324
- package/tests/request-runtime.test.ts +0 -252
- package/tests/sanitize.test.ts +0 -123
- package/tests/session-capacity.test.ts +0 -49
- package/tests/upload-reference.test.ts +0 -88
- package/tsconfig.build.json +0 -11
- package/tsconfig.json +0 -21
- package/vitest.config.ts +0 -12
package/package.json
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitlab-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A MCP server for GitLab",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"gitlab-mcp": "dist/index.js",
|
|
8
|
+
"gitlab-mcp-http": "dist/http.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"README.md",
|
|
14
|
+
"docs"
|
|
15
|
+
],
|
|
6
16
|
"packageManager": "pnpm@10.28.1",
|
|
7
17
|
"engines": {
|
|
8
18
|
"node": ">=20.11.0"
|
|
@@ -17,6 +27,7 @@
|
|
|
17
27
|
},
|
|
18
28
|
"scripts": {
|
|
19
29
|
"build": "tsc -p tsconfig.build.json",
|
|
30
|
+
"prepack": "npm run build",
|
|
20
31
|
"typecheck": "tsc --noEmit",
|
|
21
32
|
"dev": "tsx watch src/index.ts",
|
|
22
33
|
"dev:http": "tsx watch src/http.ts",
|
|
@@ -38,6 +49,7 @@
|
|
|
38
49
|
"dotenv": "^17.3.1",
|
|
39
50
|
"express": "^5.2.1",
|
|
40
51
|
"fetch-cookie": "^3.2.0",
|
|
52
|
+
"graphql": "^16.12.0",
|
|
41
53
|
"open": "^11.0.0",
|
|
42
54
|
"picomatch": "^4.0.3",
|
|
43
55
|
"pino": "^10.3.1",
|
package/.dockerignore
DELETED
package/.editorconfig
DELETED
package/.env.example
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
NODE_ENV=development
|
|
2
|
-
LOG_LEVEL=info
|
|
3
|
-
|
|
4
|
-
MCP_SERVER_NAME=gitlab-mcp
|
|
5
|
-
MCP_SERVER_VERSION=0.1.0
|
|
6
|
-
|
|
7
|
-
# Base API URL(s). Supports comma-separated multi-instance URLs.
|
|
8
|
-
# Each URL will be normalized to /api/v4 automatically.
|
|
9
|
-
GITLAB_API_URL=https://gitlab.com/api/v4
|
|
10
|
-
|
|
11
|
-
# For stdio/local mode. In REMOTE_AUTHORIZATION mode this can stay empty.
|
|
12
|
-
GITLAB_PERSONAL_ACCESS_TOKEN=
|
|
13
|
-
|
|
14
|
-
# Optional built-in OAuth (PKCE)
|
|
15
|
-
GITLAB_USE_OAUTH=false
|
|
16
|
-
GITLAB_OAUTH_CLIENT_ID=
|
|
17
|
-
GITLAB_OAUTH_CLIENT_SECRET=
|
|
18
|
-
GITLAB_OAUTH_GITLAB_URL=
|
|
19
|
-
GITLAB_OAUTH_REDIRECT_URI=http://127.0.0.1:8765/callback
|
|
20
|
-
GITLAB_OAUTH_SCOPES=api
|
|
21
|
-
GITLAB_OAUTH_TOKEN_PATH=~/.gitlab-mcp-oauth-token.json
|
|
22
|
-
GITLAB_OAUTH_AUTO_OPEN_BROWSER=true
|
|
23
|
-
|
|
24
|
-
# Tool/runtime policy
|
|
25
|
-
GITLAB_READ_ONLY_MODE=false
|
|
26
|
-
GITLAB_ALLOWED_PROJECT_IDS=
|
|
27
|
-
GITLAB_ALLOWED_TOOLS=
|
|
28
|
-
GITLAB_DENIED_TOOLS_REGEX=
|
|
29
|
-
GITLAB_ALLOW_GRAPHQL_WITH_PROJECT_SCOPE=false
|
|
30
|
-
|
|
31
|
-
# Output tuning
|
|
32
|
-
GITLAB_RESPONSE_MODE=json
|
|
33
|
-
GITLAB_MAX_RESPONSE_BYTES=200000
|
|
34
|
-
GITLAB_HTTP_TIMEOUT_MS=20000
|
|
35
|
-
GITLAB_ERROR_DETAIL_MODE=
|
|
36
|
-
|
|
37
|
-
# Optional auth/runtime enhancements
|
|
38
|
-
GITLAB_AUTH_COOKIE_PATH=
|
|
39
|
-
GITLAB_COOKIE_WARMUP_PATH=/user
|
|
40
|
-
GITLAB_TOKEN_SCRIPT=
|
|
41
|
-
GITLAB_TOKEN_SCRIPT_TIMEOUT_MS=10000
|
|
42
|
-
GITLAB_TOKEN_CACHE_SECONDS=300
|
|
43
|
-
GITLAB_TOKEN_FILE=
|
|
44
|
-
GITLAB_ALLOW_INSECURE_TOKEN_FILE=false
|
|
45
|
-
|
|
46
|
-
# Cloudflare/proxy compatibility
|
|
47
|
-
GITLAB_CLOUDFLARE_BYPASS=false
|
|
48
|
-
GITLAB_USER_AGENT=
|
|
49
|
-
GITLAB_ACCEPT_LANGUAGE=en-US,en;q=0.9
|
|
50
|
-
|
|
51
|
-
# TLS safety guard (must acknowledge if disabled)
|
|
52
|
-
NODE_TLS_REJECT_UNAUTHORIZED=
|
|
53
|
-
GITLAB_ALLOW_INSECURE_TLS=false
|
|
54
|
-
GITLAB_CA_CERT_PATH=
|
|
55
|
-
HTTP_PROXY=
|
|
56
|
-
HTTPS_PROXY=
|
|
57
|
-
|
|
58
|
-
# Feature toggles
|
|
59
|
-
USE_GITLAB_WIKI=true
|
|
60
|
-
USE_MILESTONE=true
|
|
61
|
-
USE_PIPELINE=true
|
|
62
|
-
USE_RELEASE=true
|
|
63
|
-
|
|
64
|
-
# Remote auth/session controls (for Streamable HTTP)
|
|
65
|
-
REMOTE_AUTHORIZATION=false
|
|
66
|
-
ENABLE_DYNAMIC_API_URL=false
|
|
67
|
-
SESSION_TIMEOUT_SECONDS=3600
|
|
68
|
-
MAX_SESSIONS=1000
|
|
69
|
-
MAX_REQUESTS_PER_MINUTE=300
|
|
70
|
-
|
|
71
|
-
# HTTP server
|
|
72
|
-
HTTP_HOST=127.0.0.1
|
|
73
|
-
HTTP_PORT=3333
|
|
74
|
-
HTTP_JSON_ONLY=false
|
|
75
|
-
SSE=false
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
name: Node CI
|
|
2
|
-
|
|
3
|
-
on: [push]
|
|
4
|
-
|
|
5
|
-
jobs:
|
|
6
|
-
build:
|
|
7
|
-
runs-on: ubuntu-latest
|
|
8
|
-
|
|
9
|
-
strategy:
|
|
10
|
-
matrix:
|
|
11
|
-
node-version: [22.x]
|
|
12
|
-
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v4
|
|
15
|
-
- name: Setup pnpm
|
|
16
|
-
uses: pnpm/action-setup@v4
|
|
17
|
-
with:
|
|
18
|
-
version: 10.28.1
|
|
19
|
-
run_install: false
|
|
20
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
21
|
-
uses: actions/setup-node@v4
|
|
22
|
-
with:
|
|
23
|
-
node-version: ${{ matrix.node-version }}
|
|
24
|
-
cache: "pnpm"
|
|
25
|
-
- name: pnpm install, build, and test
|
|
26
|
-
run: |
|
|
27
|
-
pnpm install --frozen-lockfile
|
|
28
|
-
pnpm build
|
|
29
|
-
pnpm test
|
|
30
|
-
env:
|
|
31
|
-
CI: true
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
name: Node.js Package
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [created]
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
publish-npm:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
permissions:
|
|
11
|
-
contents: read
|
|
12
|
-
id-token: write
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v4
|
|
15
|
-
- name: Setup pnpm
|
|
16
|
-
uses: pnpm/action-setup@v4
|
|
17
|
-
with:
|
|
18
|
-
version: 10.28.1
|
|
19
|
-
run_install: false
|
|
20
|
-
- uses: actions/setup-node@v4
|
|
21
|
-
with:
|
|
22
|
-
node-version: "22.x"
|
|
23
|
-
cache: "pnpm"
|
|
24
|
-
registry-url: "https://registry.npmjs.org"
|
|
25
|
-
- run: pnpm install --frozen-lockfile
|
|
26
|
-
- run: pnpm build
|
|
27
|
-
- run: pnpm test
|
|
28
|
-
- run: npm publish --provenance
|
|
29
|
-
env:
|
|
30
|
-
NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
|
|
31
|
-
CI: true
|
package/.husky/pre-commit
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
npm run lint-staged
|
package/.nvmrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
22
|
package/.prettierrc.json
DELETED
package/Dockerfile
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
FROM node:22-alpine AS deps
|
|
2
|
-
WORKDIR /app
|
|
3
|
-
RUN corepack enable
|
|
4
|
-
COPY package.json pnpm-lock.yaml ./
|
|
5
|
-
RUN pnpm install --frozen-lockfile
|
|
6
|
-
|
|
7
|
-
FROM deps AS build
|
|
8
|
-
COPY . .
|
|
9
|
-
RUN pnpm build
|
|
10
|
-
|
|
11
|
-
FROM node:22-alpine AS runtime
|
|
12
|
-
WORKDIR /app
|
|
13
|
-
ENV NODE_ENV=production
|
|
14
|
-
RUN corepack enable
|
|
15
|
-
COPY package.json pnpm-lock.yaml ./
|
|
16
|
-
RUN pnpm install --prod --frozen-lockfile
|
|
17
|
-
COPY --from=build /app/dist ./dist
|
|
18
|
-
COPY .env.example ./.env.example
|
|
19
|
-
EXPOSE 3333
|
|
20
|
-
CMD ["node", "dist/http.js"]
|
package/docker-compose.yml
DELETED
package/eslint.config.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import js from "@eslint/js";
|
|
2
|
-
import tseslint from "typescript-eslint";
|
|
3
|
-
import prettier from "eslint-config-prettier";
|
|
4
|
-
|
|
5
|
-
export default tseslint.config(
|
|
6
|
-
{
|
|
7
|
-
ignores: ["dist/**", "coverage/**", "node_modules/**", "other/**"]
|
|
8
|
-
},
|
|
9
|
-
js.configs.recommended,
|
|
10
|
-
...tseslint.configs.recommended,
|
|
11
|
-
{
|
|
12
|
-
files: ["**/*.ts"],
|
|
13
|
-
rules: {
|
|
14
|
-
"@typescript-eslint/consistent-type-imports": [
|
|
15
|
-
"error",
|
|
16
|
-
{
|
|
17
|
-
prefer: "type-imports"
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
prettier
|
|
23
|
-
);
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
# Example external token script for GITLAB_TOKEN_SCRIPT.
|
|
5
|
-
# The MCP server accepts either:
|
|
6
|
-
# 1) Raw token on stdout
|
|
7
|
-
# 2) JSON: {"access_token":"..."} or {"token":"..."}
|
|
8
|
-
|
|
9
|
-
if [[ -n "${GITLAB_OAUTH_ACCESS_TOKEN:-}" ]]; then
|
|
10
|
-
printf '{"access_token":"%s"}\n' "${GITLAB_OAUTH_ACCESS_TOKEN}"
|
|
11
|
-
exit 0
|
|
12
|
-
fi
|
|
13
|
-
|
|
14
|
-
echo "GITLAB_OAUTH_ACCESS_TOKEN is not set" >&2
|
|
15
|
-
exit 1
|
package/src/config/env.ts
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import "dotenv/config";
|
|
2
|
-
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
|
|
5
|
-
const logLevelSchema = z.enum(["fatal", "error", "warn", "info", "debug", "trace", "silent"]);
|
|
6
|
-
|
|
7
|
-
const responseModeSchema = z.enum(["json", "compact-json", "yaml"]);
|
|
8
|
-
const errorDetailModeSchema = z.enum(["safe", "full"]);
|
|
9
|
-
|
|
10
|
-
function parseBoolean(value: string | undefined, fallback: boolean): boolean {
|
|
11
|
-
if (value === undefined) {
|
|
12
|
-
return fallback;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return value.toLowerCase() === "true";
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function parseCsv(value: string | undefined): string[] {
|
|
19
|
-
if (!value) {
|
|
20
|
-
return [];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return value
|
|
24
|
-
.split(",")
|
|
25
|
-
.map((item) => item.trim())
|
|
26
|
-
.filter((item) => item.length > 0);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const envSchema = z.object({
|
|
30
|
-
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
|
|
31
|
-
LOG_LEVEL: logLevelSchema.default("info"),
|
|
32
|
-
MCP_SERVER_NAME: z.string().min(1).default("gitlab-mcp"),
|
|
33
|
-
MCP_SERVER_VERSION: z.string().min(1).default("0.1.0"),
|
|
34
|
-
GITLAB_API_URL: z.string().min(1).default("https://gitlab.com/api/v4"),
|
|
35
|
-
GITLAB_PERSONAL_ACCESS_TOKEN: z.string().min(1).optional(),
|
|
36
|
-
GITLAB_USE_OAUTH: z.enum(["true", "false"]).default("false"),
|
|
37
|
-
GITLAB_OAUTH_CLIENT_ID: z.string().optional(),
|
|
38
|
-
GITLAB_OAUTH_CLIENT_SECRET: z.string().optional(),
|
|
39
|
-
GITLAB_OAUTH_GITLAB_URL: z.string().optional(),
|
|
40
|
-
GITLAB_OAUTH_REDIRECT_URI: z.string().url().optional(),
|
|
41
|
-
GITLAB_OAUTH_SCOPES: z.string().default("api"),
|
|
42
|
-
GITLAB_OAUTH_TOKEN_PATH: z.string().optional(),
|
|
43
|
-
GITLAB_OAUTH_AUTO_OPEN_BROWSER: z.enum(["true", "false"]).default("true"),
|
|
44
|
-
GITLAB_READ_ONLY_MODE: z
|
|
45
|
-
.enum(["true", "false"])
|
|
46
|
-
.default("false")
|
|
47
|
-
.transform((value) => value === "true"),
|
|
48
|
-
GITLAB_ALLOWED_PROJECT_IDS: z.string().optional(),
|
|
49
|
-
GITLAB_ALLOWED_TOOLS: z.string().optional(),
|
|
50
|
-
GITLAB_DENIED_TOOLS_REGEX: z.string().optional(),
|
|
51
|
-
GITLAB_ALLOW_GRAPHQL_WITH_PROJECT_SCOPE: z.enum(["true", "false"]).default("false"),
|
|
52
|
-
GITLAB_RESPONSE_MODE: responseModeSchema.default("json"),
|
|
53
|
-
GITLAB_MAX_RESPONSE_BYTES: z.coerce.number().int().min(1024).max(2_000_000).default(200_000),
|
|
54
|
-
GITLAB_HTTP_TIMEOUT_MS: z.coerce.number().int().min(1_000).max(120_000).default(20_000),
|
|
55
|
-
GITLAB_ERROR_DETAIL_MODE: errorDetailModeSchema.optional(),
|
|
56
|
-
GITLAB_AUTH_COOKIE_PATH: z.string().optional(),
|
|
57
|
-
GITLAB_COOKIE_WARMUP_PATH: z.string().default("/user"),
|
|
58
|
-
GITLAB_CLOUDFLARE_BYPASS: z.enum(["true", "false"]).default("false"),
|
|
59
|
-
GITLAB_USER_AGENT: z.string().optional(),
|
|
60
|
-
GITLAB_ACCEPT_LANGUAGE: z.string().optional(),
|
|
61
|
-
GITLAB_TOKEN_SCRIPT: z.string().optional(),
|
|
62
|
-
GITLAB_TOKEN_SCRIPT_TIMEOUT_MS: z.coerce.number().int().min(500).max(120_000).default(10_000),
|
|
63
|
-
GITLAB_TOKEN_CACHE_SECONDS: z.coerce.number().int().min(0).max(86_400).default(300),
|
|
64
|
-
GITLAB_TOKEN_FILE: z.string().optional(),
|
|
65
|
-
GITLAB_ALLOW_INSECURE_TOKEN_FILE: z.enum(["true", "false"]).default("false"),
|
|
66
|
-
GITLAB_ALLOW_INSECURE_TLS: z.enum(["true", "false"]).default("false"),
|
|
67
|
-
NODE_TLS_REJECT_UNAUTHORIZED: z.string().optional(),
|
|
68
|
-
GITLAB_CA_CERT_PATH: z.string().optional(),
|
|
69
|
-
HTTP_PROXY: z.string().optional(),
|
|
70
|
-
HTTPS_PROXY: z.string().optional(),
|
|
71
|
-
USE_GITLAB_WIKI: z.enum(["true", "false"]).default("true"),
|
|
72
|
-
USE_MILESTONE: z.enum(["true", "false"]).default("true"),
|
|
73
|
-
USE_PIPELINE: z.enum(["true", "false"]).default("true"),
|
|
74
|
-
USE_RELEASE: z.enum(["true", "false"]).default("true"),
|
|
75
|
-
REMOTE_AUTHORIZATION: z.enum(["true", "false"]).default("false"),
|
|
76
|
-
ENABLE_DYNAMIC_API_URL: z.enum(["true", "false"]).default("false"),
|
|
77
|
-
SESSION_TIMEOUT_SECONDS: z.coerce.number().int().min(1).max(86_400).default(3_600),
|
|
78
|
-
MAX_SESSIONS: z.coerce.number().int().min(1).max(10_000).default(1_000),
|
|
79
|
-
MAX_REQUESTS_PER_MINUTE: z.coerce.number().int().min(1).max(10_000).default(300),
|
|
80
|
-
HTTP_HOST: z.string().min(1).default("127.0.0.1"),
|
|
81
|
-
HTTP_PORT: z.coerce.number().int().min(1).max(65_535).default(3333),
|
|
82
|
-
HTTP_JSON_ONLY: z.enum(["true", "false"]).default("false"),
|
|
83
|
-
SSE: z.enum(["true", "false"]).default("false")
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const parsed = envSchema.safeParse(process.env);
|
|
87
|
-
|
|
88
|
-
if (!parsed.success) {
|
|
89
|
-
const issues = parsed.error.issues
|
|
90
|
-
.map((issue) => `- ${issue.path.join(".") || "(root)"}: ${issue.message}`)
|
|
91
|
-
.join("\n");
|
|
92
|
-
|
|
93
|
-
throw new Error(`Invalid environment variables:\n${issues}`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const data = parsed.data;
|
|
97
|
-
const rawApiUrls = parseCsv(data.GITLAB_API_URL);
|
|
98
|
-
|
|
99
|
-
if (rawApiUrls.length === 0) {
|
|
100
|
-
throw new Error("GITLAB_API_URL must contain at least one URL");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const normalizedApiUrls = rawApiUrls.map((item) => {
|
|
104
|
-
try {
|
|
105
|
-
return normalizeApiUrl(item);
|
|
106
|
-
} catch {
|
|
107
|
-
throw new Error(`Invalid GITLAB_API_URL entry: '${item}'`);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
if (data.ENABLE_DYNAMIC_API_URL === "true" && data.REMOTE_AUTHORIZATION !== "true") {
|
|
112
|
-
throw new Error("ENABLE_DYNAMIC_API_URL=true requires REMOTE_AUTHORIZATION=true");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (data.GITLAB_USE_OAUTH === "true" && !data.GITLAB_OAUTH_CLIENT_ID) {
|
|
116
|
-
throw new Error("GITLAB_USE_OAUTH=true requires GITLAB_OAUTH_CLIENT_ID");
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (data.SSE === "true" && data.REMOTE_AUTHORIZATION === "true") {
|
|
120
|
-
throw new Error("SSE=true is not compatible with REMOTE_AUTHORIZATION=true");
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (data.NODE_TLS_REJECT_UNAUTHORIZED === "0" && data.GITLAB_ALLOW_INSECURE_TLS !== "true") {
|
|
124
|
-
throw new Error(
|
|
125
|
-
"NODE_TLS_REJECT_UNAUTHORIZED=0 requires GITLAB_ALLOW_INSECURE_TLS=true acknowledgment"
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export const env = {
|
|
130
|
-
...data,
|
|
131
|
-
GITLAB_READ_ONLY_MODE: data.GITLAB_READ_ONLY_MODE,
|
|
132
|
-
GITLAB_ERROR_DETAIL_MODE:
|
|
133
|
-
data.GITLAB_ERROR_DETAIL_MODE ?? (data.NODE_ENV === "production" ? "safe" : "full"),
|
|
134
|
-
GITLAB_USE_OAUTH: parseBoolean(data.GITLAB_USE_OAUTH, false),
|
|
135
|
-
GITLAB_OAUTH_AUTO_OPEN_BROWSER: parseBoolean(data.GITLAB_OAUTH_AUTO_OPEN_BROWSER, true),
|
|
136
|
-
GITLAB_CLOUDFLARE_BYPASS: parseBoolean(data.GITLAB_CLOUDFLARE_BYPASS, false),
|
|
137
|
-
GITLAB_ALLOW_INSECURE_TOKEN_FILE: parseBoolean(data.GITLAB_ALLOW_INSECURE_TOKEN_FILE, false),
|
|
138
|
-
GITLAB_ALLOW_INSECURE_TLS: parseBoolean(data.GITLAB_ALLOW_INSECURE_TLS, false),
|
|
139
|
-
USE_GITLAB_WIKI: parseBoolean(data.USE_GITLAB_WIKI, true),
|
|
140
|
-
USE_MILESTONE: parseBoolean(data.USE_MILESTONE, true),
|
|
141
|
-
USE_PIPELINE: parseBoolean(data.USE_PIPELINE, true),
|
|
142
|
-
USE_RELEASE: parseBoolean(data.USE_RELEASE, true),
|
|
143
|
-
REMOTE_AUTHORIZATION: parseBoolean(data.REMOTE_AUTHORIZATION, false),
|
|
144
|
-
ENABLE_DYNAMIC_API_URL: parseBoolean(data.ENABLE_DYNAMIC_API_URL, false),
|
|
145
|
-
HTTP_JSON_ONLY: parseBoolean(data.HTTP_JSON_ONLY, false),
|
|
146
|
-
SSE: parseBoolean(data.SSE, false),
|
|
147
|
-
GITLAB_ALLOWED_PROJECT_IDS: parseCsv(data.GITLAB_ALLOWED_PROJECT_IDS),
|
|
148
|
-
GITLAB_ALLOWED_TOOLS: parseCsv(data.GITLAB_ALLOWED_TOOLS),
|
|
149
|
-
GITLAB_ALLOW_GRAPHQL_WITH_PROJECT_SCOPE: parseBoolean(
|
|
150
|
-
data.GITLAB_ALLOW_GRAPHQL_WITH_PROJECT_SCOPE,
|
|
151
|
-
false
|
|
152
|
-
),
|
|
153
|
-
GITLAB_API_URLS: normalizedApiUrls,
|
|
154
|
-
GITLAB_API_URL: normalizedApiUrls[0] ?? normalizeApiUrl("https://gitlab.com/api/v4")
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
export type AppEnv = typeof env;
|
|
158
|
-
|
|
159
|
-
function normalizeApiUrl(rawUrl: string): string {
|
|
160
|
-
const url = new URL(rawUrl);
|
|
161
|
-
const pathname = url.pathname.replace(/\/+$/, "");
|
|
162
|
-
|
|
163
|
-
if (pathname.endsWith("/api/v4")) {
|
|
164
|
-
url.pathname = pathname;
|
|
165
|
-
return url.toString();
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
url.pathname = `${pathname}/api/v4`.replace(/\/\//g, "/");
|
|
169
|
-
|
|
170
|
-
return url.toString();
|
|
171
|
-
}
|