@zereight/mcp-gitlab 2.3.6 → 2.3.7-next.136
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/README.ko.md +3 -0
- package/README.md +3 -0
- package/README.pt-BR.md +3 -0
- package/README.zh-CN.md +3 -0
- package/build/cli/args.js +1 -0
- package/build/cli-boolean-flags.js +1 -0
- package/build/config.js +6 -0
- package/build/index.js +22 -5
- package/build/oauth-proxy.js +41 -5
- package/build/server/remote-http-stack.js +4 -1
- package/package.json +1 -2
package/README.ko.md
CHANGED
|
@@ -314,6 +314,7 @@ MCP 서버가 직접 로컬 브라우저 callback을 받을 때만 `GITLAB_OAUTH
|
|
|
314
314
|
| `GITLAB_OAUTH_ALLOWED_REDIRECT_URIS` | 선택 | 콜백 프록시 모드가 리다이렉트할 클라이언트 callback URL 목록 (기본값: Claude, ChatGPT, Cursor, 루프백 callback. 추가하려면 `defaults,<url>`) |
|
|
315
315
|
| `GITLAB_OAUTH_SCOPES` | 선택 | 쉼표로 구분된 scope 목록(기본값: `api,read_api,read_user`) |
|
|
316
316
|
| `GITLAB_OAUTH_ALLOWED_GROUPS` | 선택 | 쉼표로 구분된 GitLab 그룹 전체 경로 — 해당 그룹 및 하위 그룹 멤버만 서버를 사용할 수 있음 (기존 `GITLAB_ALLOWED_GROUPS` 대체) |
|
|
317
|
+
| `OAUTH_DISABLE_REFRESH_TOKEN` | 선택 | `true`면 refresh token을 발급하지 않음: 세션 TTL과 무관하게 GitLab access token이 만료될 때마다(2시간) 브라우저에서 다시 인가 |
|
|
317
318
|
|
|
318
319
|
`STREAMABLE_HTTP=true`일 때 서버 측 GitLab 자격 증명(`GITLAB_PERSONAL_ACCESS_TOKEN`, `GITLAB_JOB_TOKEN`, `GITLAB_AUTH_COOKIE_PATH`, 또는 `GITLAB_USE_OAUTH`)은 `REMOTE_AUTHORIZATION=true`, `GITLAB_MCP_OAUTH=true`, 또는 `STREAMABLE_HTTP_AUTH_TOKEN`이 필요합니다.
|
|
319
320
|
|
|
@@ -418,6 +419,7 @@ Authorization: Bearer glpat-xxxxxxxxxxxxxxxxxxxx
|
|
|
418
419
|
- `GITLAB_OAUTH_CALLBACK_PROXY`
|
|
419
420
|
- `GITLAB_OAUTH_ALLOWED_REDIRECT_URIS`
|
|
420
421
|
- `OAUTH_REGISTER_RATE_LIMIT_PER_HOUR`
|
|
422
|
+
- `OAUTH_DISABLE_REFRESH_TOKEN`
|
|
421
423
|
- `OAUTH_STATELESS_MODE`
|
|
422
424
|
- `OAUTH_STATELESS_SECRET`
|
|
423
425
|
|
|
@@ -578,6 +580,7 @@ node build/index.js
|
|
|
578
580
|
| `STREAMABLE_HTTP` | 예 | 반드시 `true`(SSE 미지원) |
|
|
579
581
|
| `GITLAB_OAUTH_SCOPES` | 아니오 | 요청할 GitLab scope 목록(쉼표 구분). 기본값은 `api`이며, 유효 permission mode가 `readonly`일 때 `read_api`입니다. 사전 등록 애플리케이션에 해당 scope가 설정되어 있어야 합니다. |
|
|
580
582
|
| `OAUTH_REGISTER_RATE_LIMIT_PER_HOUR` | 아니오 | Dynamic Client Registration(`POST /register`)의 클라이언트 IP당 rolling 한도. 기본 `20`/시간, 범위 `1`–`1000`. IDE 창 여러 개 등으로 등록이 막히면 올리세요. GitLab API 한도와 무관합니다. |
|
|
583
|
+
| `OAUTH_DISABLE_REFRESH_TOKEN` | 아니오 | `true`면 `/token` 응답에서 `refresh_token`을 빼고 refresh grant를 `invalid_grant`로 거부합니다. GitLab access token이 만료되면 브라우저에서 다시 인가해야 합니다. 기본 `false`. 콜백 프록시 모드에서는 이 서버의 흐름을 따르는 MCP 클라이언트가 GitLab refresh token을 받지 못합니다(passthrough에서는 클라이언트가 GitLab에서 직접 받을 수 있음). 표준 MCP 클라이언트를 위한 재인가 정책이며 보안 경계가 아닙니다. 켜기 전에 발급된 refresh token은 GitLab **User Settings → Applications → Authorized applications → Revoke**로 revoke할 때까지 유효합니다. |
|
|
581
584
|
| `MCP_DANGEROUSLY_ALLOW_INSECURE_ISSUER_URL` | 아니오 | 로컬 HTTP 개발에서만 `true` |
|
|
582
585
|
|
|
583
586
|
**중요 사항:**
|
package/README.md
CHANGED
|
@@ -336,6 +336,7 @@ exchanging credentials with GitLab on behalf of the client.
|
|
|
336
336
|
| `GITLAB_OAUTH_ALLOWED_REDIRECT_URIS` | optional | Client callback URLs that callback proxy mode will redirect to (default: Claude, ChatGPT, Cursor and loopback callbacks; `defaults,<url>` to add one) |
|
|
337
337
|
| `GITLAB_OAUTH_SCOPES` | optional | Comma-separated scopes (default: `api,read_api,read_user`) |
|
|
338
338
|
| `GITLAB_OAUTH_ALLOWED_GROUPS` | optional | Comma-separated group full paths — only members (and subgroup members) may use the server (replaces deprecated `GITLAB_ALLOWED_GROUPS`) |
|
|
339
|
+
| `OAUTH_DISABLE_REFRESH_TOKEN` | optional | Set to `true` to issue no refresh tokens: users re-authorize in the browser each time the GitLab access token expires (2 h), regardless of the session TTL |
|
|
339
340
|
|
|
340
341
|
When `STREAMABLE_HTTP=true`, server-side GitLab credentials (`GITLAB_PERSONAL_ACCESS_TOKEN`, `GITLAB_JOB_TOKEN`, `GITLAB_AUTH_COOKIE_PATH`, or `GITLAB_USE_OAUTH`) require `REMOTE_AUTHORIZATION=true`, `GITLAB_MCP_OAUTH=true`, or `STREAMABLE_HTTP_AUTH_TOKEN`.
|
|
341
342
|
|
|
@@ -454,6 +455,7 @@ Commonly referenced variables:
|
|
|
454
455
|
- `GITLAB_OAUTH_CALLBACK_PROXY`
|
|
455
456
|
- `GITLAB_OAUTH_ALLOWED_REDIRECT_URIS`
|
|
456
457
|
- `OAUTH_REGISTER_RATE_LIMIT_PER_HOUR`
|
|
458
|
+
- `OAUTH_DISABLE_REFRESH_TOKEN`
|
|
457
459
|
- `OAUTH_STATELESS_MODE`
|
|
458
460
|
- `OAUTH_STATELESS_SECRET`
|
|
459
461
|
|
|
@@ -620,6 +622,7 @@ No `headers` field is needed — Claude.ai obtains the token via OAuth automatic
|
|
|
620
622
|
| `STREAMABLE_HTTP` | Yes | Must be `true` (SSE is not supported) |
|
|
621
623
|
| `GITLAB_OAUTH_SCOPES` | No | Comma-separated GitLab scopes to request (e.g. `api,read_user`). Defaults to `api` (or `read_api` when the effective permission mode is `readonly`). The pre-registered application must be configured with at least these scopes. |
|
|
622
624
|
| `OAUTH_REGISTER_RATE_LIMIT_PER_HOUR` | No | Per-IP rolling limit for Dynamic Client Registration (`POST /register`). Default `20`/hour; range `1`–`1000`. Raise when clients (e.g. multiple IDE windows) hit registration throttling. Not a GitLab API limit. |
|
|
625
|
+
| `OAUTH_DISABLE_REFRESH_TOKEN` | No | Set `true` to drop `refresh_token` from `/token` responses and reject the refresh grant with `invalid_grant`, so users re-authorize in the browser when the GitLab access token expires. Default `false`. In callback proxy mode, MCP clients that follow this server's flow never receive a GitLab refresh token (in passthrough the client can get one from GitLab directly). This is a re-authorization policy for standard MCP clients, not a security boundary. Refresh tokens issued before enabling stay valid until revoked in GitLab under **User Settings → Applications → Authorized applications → Revoke**. |
|
|
623
626
|
| `MCP_DANGEROUSLY_ALLOW_INSECURE_ISSUER_URL` | No | Set `true` for local HTTP dev only |
|
|
624
627
|
|
|
625
628
|
**Important Notes:**
|
package/README.pt-BR.md
CHANGED
|
@@ -314,6 +314,7 @@ Use `GITLAB_OAUTH_REDIRECT_URI` somente quando o próprio servidor MCP receber o
|
|
|
314
314
|
| `GITLAB_OAUTH_ALLOWED_REDIRECT_URIS` | opcional | URLs de callback de cliente para as quais o modo de proxy de callback redireciona (padrão: callbacks do Claude, ChatGPT, Cursor e loopback; use `defaults,<url>` para adicionar) |
|
|
315
315
|
| `GITLAB_OAUTH_SCOPES` | opcional | Scopes separados por vírgula (padrão: `api,read_api,read_user`) |
|
|
316
316
|
| `GITLAB_OAUTH_ALLOWED_GROUPS` | opcional | Caminhos completos de grupos GitLab separados por vírgula — apenas membros desses grupos e subgrupos podem usar o servidor (substitui o obsoleto `GITLAB_ALLOWED_GROUPS`) |
|
|
317
|
+
| `OAUTH_DISABLE_REFRESH_TOKEN` | opcional | Defina como `true` para não emitir refresh tokens: o usuário autoriza de novo no navegador sempre que o access token do GitLab expira (2 h), independente do TTL da sessão |
|
|
317
318
|
|
|
318
319
|
Quando `STREAMABLE_HTTP=true`, credenciais GitLab do lado do servidor (`GITLAB_PERSONAL_ACCESS_TOKEN`, `GITLAB_JOB_TOKEN`, `GITLAB_AUTH_COOKIE_PATH` ou `GITLAB_USE_OAUTH`) exigem `REMOTE_AUTHORIZATION=true`, `GITLAB_MCP_OAUTH=true` ou `STREAMABLE_HTTP_AUTH_TOKEN`.
|
|
319
320
|
|
|
@@ -418,6 +419,7 @@ Variáveis usadas com frequência:
|
|
|
418
419
|
- `GITLAB_OAUTH_CALLBACK_PROXY`
|
|
419
420
|
- `GITLAB_OAUTH_ALLOWED_REDIRECT_URIS`
|
|
420
421
|
- `OAUTH_REGISTER_RATE_LIMIT_PER_HOUR`
|
|
422
|
+
- `OAUTH_DISABLE_REFRESH_TOKEN`
|
|
421
423
|
- `OAUTH_STATELESS_MODE`
|
|
422
424
|
- `OAUTH_STATELESS_SECRET`
|
|
423
425
|
|
|
@@ -578,6 +580,7 @@ Não é necessário definir `headers`. O Claude.ai obtém o token via OAuth.
|
|
|
578
580
|
| `STREAMABLE_HTTP` | sim | Deve ser `true` (SSE não é suportado) |
|
|
579
581
|
| `GITLAB_OAUTH_SCOPES` | não | Scopes do GitLab a solicitar, separados por vírgula. O padrão é `api`, ou `read_api` quando o permission mode efetivo é `readonly`. O aplicativo previamente registrado deve ter pelo menos esses scopes configurados. |
|
|
580
582
|
| `OAUTH_REGISTER_RATE_LIMIT_PER_HOUR` | não | Limite rolling de Dynamic Client Registration (`POST /register`) por IP do cliente. Padrão `20`/hora, intervalo `1`–`1000`. Aumente se múltiplas janelas de IDE estiverem atingindo o limite. Não está relacionado aos limites da API do GitLab. |
|
|
583
|
+
| `OAUTH_DISABLE_REFRESH_TOKEN` | não | Defina `true` para remover `refresh_token` das respostas de `/token` e rejeitar o refresh grant com `invalid_grant`; o usuário autoriza de novo no navegador quando o access token do GitLab expira. Padrão `false`. No modo de proxy de callback, clientes MCP que seguem o fluxo deste servidor nunca recebem um refresh token do GitLab (em passthrough o cliente pode obter um direto do GitLab). É uma política de reautorização para clientes MCP padrão, não uma barreira de segurança. Refresh tokens emitidos antes de ativar continuam válidos até serem revogados no GitLab em **User Settings → Applications → Authorized applications → Revoke**. |
|
|
581
584
|
| `MCP_DANGEROUSLY_ALLOW_INSECURE_ISSUER_URL` | não | Apenas para desenvolvimento local via HTTP |
|
|
582
585
|
|
|
583
586
|
**Observações importantes:**
|
package/README.zh-CN.md
CHANGED
|
@@ -313,6 +313,7 @@ OpenCode、MCPJam、Claude.ai 等远程 MCP 客户端可能会在授权时发送
|
|
|
313
313
|
| `GITLAB_OAUTH_ALLOWED_REDIRECT_URIS` | 可选 | 回调代理模式允许重定向的客户端 callback URL 列表(默认:Claude、ChatGPT、Cursor 和环回地址 callback;用 `defaults,<url>` 追加) |
|
|
314
314
|
| `GITLAB_OAUTH_SCOPES` | 可选 | 逗号分隔的 scope(默认:`api,read_api,read_user`) |
|
|
315
315
|
| `GITLAB_OAUTH_ALLOWED_GROUPS` | 可选 | 逗号分隔的 GitLab 群组完整路径 — 仅该群组及其子群组的成员可使用服务器(替代已废弃的 `GITLAB_ALLOWED_GROUPS`) |
|
|
316
|
+
| `OAUTH_DISABLE_REFRESH_TOKEN` | 可选 | 设置为 `true` 时不发放 refresh token:每当 GitLab access token 过期(2 小时)用户都需在浏览器中重新授权,与会话 TTL 无关 |
|
|
316
317
|
|
|
317
318
|
当 `STREAMABLE_HTTP=true` 时,服务端 GitLab 凭据(`GITLAB_PERSONAL_ACCESS_TOKEN`、`GITLAB_JOB_TOKEN`、`GITLAB_AUTH_COOKIE_PATH` 或 `GITLAB_USE_OAUTH`)需要 `REMOTE_AUTHORIZATION=true`、`GITLAB_MCP_OAUTH=true` 或 `STREAMABLE_HTTP_AUTH_TOKEN`。
|
|
318
319
|
|
|
@@ -417,6 +418,7 @@ Authorization: Bearer glpat-xxxxxxxxxxxxxxxxxxxx
|
|
|
417
418
|
- `GITLAB_OAUTH_CALLBACK_PROXY`
|
|
418
419
|
- `GITLAB_OAUTH_ALLOWED_REDIRECT_URIS`
|
|
419
420
|
- `OAUTH_REGISTER_RATE_LIMIT_PER_HOUR`
|
|
421
|
+
- `OAUTH_DISABLE_REFRESH_TOKEN`
|
|
420
422
|
- `OAUTH_STATELESS_MODE`
|
|
421
423
|
- `OAUTH_STATELESS_SECRET`
|
|
422
424
|
|
|
@@ -577,6 +579,7 @@ node build/index.js
|
|
|
577
579
|
| `STREAMABLE_HTTP` | 是 | 必须为 `true`(不支持 SSE) |
|
|
578
580
|
| `GITLAB_OAUTH_SCOPES` | 否 | 要请求的 GitLab scopes,以逗号分隔。默认值为 `api`;有效 permission mode 为 `readonly` 时为 `read_api`。预注册应用必须配置至少这些 scopes。 |
|
|
579
581
|
| `OAUTH_REGISTER_RATE_LIMIT_PER_HOUR` | 否 | Dynamic Client Registration(`POST /register`)的每客户端 IP rolling 限制。默认 `20`/小时,范围 `1`–`1000`。多个 IDE 窗口等导致注册被限流时可调高。与 GitLab API 限额无关。 |
|
|
582
|
+
| `OAUTH_DISABLE_REFRESH_TOKEN` | 否 | 设置 `true` 时 `/token` 响应不含 `refresh_token`,refresh grant 以 `invalid_grant` 拒绝;GitLab access token 过期后用户需在浏览器中重新授权。默认 `false`。回调代理模式下,遵循本服务器流程的 MCP 客户端拿不到 GitLab refresh token(passthrough 模式下客户端可直接从 GitLab 获取)。这是面向标准 MCP 客户端的重新授权策略,而非安全边界。启用前已发放的 refresh token 在 GitLab **User Settings → Applications → Authorized applications → Revoke** 撤销前仍然有效。 |
|
|
580
583
|
| `MCP_DANGEROUSLY_ALLOW_INSECURE_ISSUER_URL` | 否 | 仅用于本地 HTTP 开发 |
|
|
581
584
|
|
|
582
585
|
**重要说明:**
|
package/build/cli/args.js
CHANGED
|
@@ -17,6 +17,7 @@ export const BOOLEAN_CLI_FLAG_NAMES = new Set([
|
|
|
17
17
|
"allow-unauthenticated-tool-discovery",
|
|
18
18
|
"mcp-trust-proxy",
|
|
19
19
|
"oauth-callback-proxy",
|
|
20
|
+
"oauth-disable-refresh-token",
|
|
20
21
|
"enable-dynamic-api-url",
|
|
21
22
|
"enable-dynamic-project-scope",
|
|
22
23
|
"enable-strict-project-scope",
|
package/build/config.js
CHANGED
|
@@ -111,6 +111,12 @@ export const GITLAB_OAUTH_ALLOWED_GROUPS = (() => {
|
|
|
111
111
|
.filter(Boolean);
|
|
112
112
|
return groups.length > 0 ? groups : undefined;
|
|
113
113
|
})();
|
|
114
|
+
/**
|
|
115
|
+
* MCP OAuth proxy: reject the refresh_token grant (invalid_grant) and omit
|
|
116
|
+
* refresh_token from issued tokens, so clients must redo the browser
|
|
117
|
+
* authorization once the GitLab access token expires.
|
|
118
|
+
*/
|
|
119
|
+
export const OAUTH_DISABLE_REFRESH_TOKEN = getConfig("oauth-disable-refresh-token", "OAUTH_DISABLE_REFRESH_TOKEN") === "true";
|
|
114
120
|
export const ENABLE_DYNAMIC_API_URL = getConfig("enable-dynamic-api-url", "ENABLE_DYNAMIC_API_URL") === "true";
|
|
115
121
|
export const ENABLE_DYNAMIC_PROJECT_SCOPE = getConfig("enable-dynamic-project-scope", "ENABLE_DYNAMIC_PROJECT_SCOPE") === "true";
|
|
116
122
|
export const ENABLE_STRICT_PROJECT_SCOPE = getConfig("enable-strict-project-scope", "ENABLE_STRICT_PROJECT_SCOPE") === "true";
|
package/build/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { cliArgs, getConfig, ENABLE_DYNAMIC_API_URL, ENABLE_DYNAMIC_PROJECT_SCOPE, ENABLE_STRICT_PROJECT_SCOPE, GITLAB_AUTH_COOKIE_PATH, GITLAB_ALLOW_UNAUTHENTICATED_TOOL_DISCOVERY, GITLAB_CA_CERT_PATH, GITLAB_JOB_TOKEN, GITLAB_MCP_OAUTH, GITLAB_MCP_REQUEST_BODY_LIMIT, GITLAB_OAUTH_APP_ID, GITLAB_OAUTH_SCOPES, GITLAB_OAUTH_CALLBACK_PROXY, GITLAB_OAUTH_ALLOWED_REDIRECT_URIS_RAW, GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_POOL_MAX_SIZE, GITLAB_DISABLE_VERSION_CHECK, GITLAB_MASKING_ENABLED, GITLAB_MASKING_CONFIG, GITLAB_MASKING_POLICY_FILE, GITLAB_MASKING_WORKSPACE_DIR, GITLAB_PERMISSION_MODE, GITLAB_TOOLSETS_RAW, GITLAB_TOOLS_RAW, HOST, HTTP_PROXY, HTTPS_PROXY, IS_OLD, MCP_SERVER_URL, NODE_TLS_REJECT_UNAUTHORIZED, NO_PROXY, OAUTH_STATELESS_CLIENT_TTL_SECONDS, OAUTH_STATELESS_MODE, OAUTH_STATELESS_PENDING_TTL_SECONDS, OAUTH_STATELESS_SESSION_TTL_SECONDS, OAUTH_STATELESS_STORED_TTL_SECONDS, PORT, REMOTE_AUTHORIZATION, SESSION_TIMEOUT_SECONDS, SSE, STREAMABLE_HTTP, MCP_TRUST_PROXY, USE_GITLAB_WIKI, USE_MILESTONE, USE_OAUTH, USE_PIPELINE, GITLAB_TOOL_POLICY_APPROVE_RAW, GITLAB_TOOL_POLICY_HIDDEN_RAW, GITLAB_OAUTH_ALLOWED_GROUPS, GITLAB_MCP_COMPACT_RESULTS, GITLAB_MCP_COMPACT_RESULT_CHARS, GITLAB_MCP_COMPACT_TOOL_NAMES, GITLAB_TOOL_PROFILE, GITLAB_DOWNLOAD_DIR, buildDeprecatedEnvInput, } from "./config.js";
|
|
2
|
+
import { cliArgs, getConfig, ENABLE_DYNAMIC_API_URL, ENABLE_DYNAMIC_PROJECT_SCOPE, ENABLE_STRICT_PROJECT_SCOPE, GITLAB_AUTH_COOKIE_PATH, GITLAB_ALLOW_UNAUTHENTICATED_TOOL_DISCOVERY, GITLAB_CA_CERT_PATH, GITLAB_JOB_TOKEN, GITLAB_MCP_OAUTH, GITLAB_MCP_REQUEST_BODY_LIMIT, GITLAB_OAUTH_APP_ID, GITLAB_OAUTH_SCOPES, GITLAB_OAUTH_CALLBACK_PROXY, GITLAB_OAUTH_ALLOWED_REDIRECT_URIS_RAW, GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_POOL_MAX_SIZE, GITLAB_DISABLE_VERSION_CHECK, GITLAB_MASKING_ENABLED, GITLAB_MASKING_CONFIG, GITLAB_MASKING_POLICY_FILE, GITLAB_MASKING_WORKSPACE_DIR, GITLAB_PERMISSION_MODE, GITLAB_TOOLSETS_RAW, GITLAB_TOOLS_RAW, HOST, HTTP_PROXY, HTTPS_PROXY, IS_OLD, MCP_SERVER_URL, NODE_TLS_REJECT_UNAUTHORIZED, NO_PROXY, OAUTH_DISABLE_REFRESH_TOKEN, OAUTH_STATELESS_CLIENT_TTL_SECONDS, OAUTH_STATELESS_MODE, OAUTH_STATELESS_PENDING_TTL_SECONDS, OAUTH_STATELESS_SESSION_TTL_SECONDS, OAUTH_STATELESS_STORED_TTL_SECONDS, PORT, REMOTE_AUTHORIZATION, SESSION_TIMEOUT_SECONDS, SSE, STREAMABLE_HTTP, MCP_TRUST_PROXY, USE_GITLAB_WIKI, USE_MILESTONE, USE_OAUTH, USE_PIPELINE, GITLAB_TOOL_POLICY_APPROVE_RAW, GITLAB_TOOL_POLICY_HIDDEN_RAW, GITLAB_OAUTH_ALLOWED_GROUPS, GITLAB_MCP_COMPACT_RESULTS, GITLAB_MCP_COMPACT_RESULT_CHARS, GITLAB_MCP_COMPACT_TOOL_NAMES, GITLAB_TOOL_PROFILE, GITLAB_DOWNLOAD_DIR, buildDeprecatedEnvInput, } from "./config.js";
|
|
3
3
|
import { getDeprecatedEnvWarnings } from "./deprecated-env.js";
|
|
4
4
|
/** True when the server is running in remote/network mode (SSE or StreamableHTTP transport). */
|
|
5
5
|
const IS_REMOTE = SSE || STREAMABLE_HTTP;
|
|
@@ -11664,7 +11664,7 @@ async function startSSEServer() {
|
|
|
11664
11664
|
* Start server with Streamable HTTP transport
|
|
11665
11665
|
*/
|
|
11666
11666
|
async function startStreamableHTTPServer() {
|
|
11667
|
-
const { express, rateLimit, ipKeyGenerator, StreamableHTTPServerTransport, mcpAuthRouter, requireBearerAuth, } = await loadRemoteHttpStack();
|
|
11667
|
+
const { express, rateLimit, ipKeyGenerator, StreamableHTTPServerTransport, mcpAuthRouter, createOAuthMetadata, metadataHandler, requireBearerAuth, } = await loadRemoteHttpStack();
|
|
11668
11668
|
const app = express();
|
|
11669
11669
|
// Session IDs are untrusted map keys. A null prototype prevents inherited
|
|
11670
11670
|
// names such as "constructor" from masquerading as live transports.
|
|
@@ -12214,8 +12214,11 @@ async function startStreamableHTTPServer() {
|
|
|
12214
12214
|
storedTtlSeconds: OAUTH_STATELESS_STORED_TTL_SECONDS,
|
|
12215
12215
|
}
|
|
12216
12216
|
: null;
|
|
12217
|
-
const oauthProvider = createGitLabOAuthProvider(gitlabBaseUrl, GITLAB_OAUTH_APP_ID, "GitLab MCP Server", GITLAB_PERMISSION_MODE === "readonly", GITLAB_OAUTH_SCOPES, GITLAB_OAUTH_ALLOWED_GROUPS, GITLAB_OAUTH_CALLBACK_PROXY, callbackUrl, statelessOptions, parseAllowedRedirectUris(GITLAB_OAUTH_ALLOWED_REDIRECT_URIS_RAW));
|
|
12217
|
+
const oauthProvider = createGitLabOAuthProvider(gitlabBaseUrl, GITLAB_OAUTH_APP_ID, "GitLab MCP Server", GITLAB_PERMISSION_MODE === "readonly", GITLAB_OAUTH_SCOPES, GITLAB_OAUTH_ALLOWED_GROUPS, GITLAB_OAUTH_CALLBACK_PROXY, callbackUrl, statelessOptions, parseAllowedRedirectUris(GITLAB_OAUTH_ALLOWED_REDIRECT_URIS_RAW), OAUTH_DISABLE_REFRESH_TOKEN);
|
|
12218
12218
|
const scopesSupported = GITLAB_OAUTH_SCOPES ?? ["api", "read_api", "read_user"];
|
|
12219
|
+
const grantTypesSupported = OAUTH_DISABLE_REFRESH_TOKEN
|
|
12220
|
+
? ["authorization_code"]
|
|
12221
|
+
: ["authorization_code", "refresh_token"];
|
|
12219
12222
|
// When server URL has a path (e.g. behind Kong), the SDK's well-known metadata
|
|
12220
12223
|
// advertises root-level endpoints. Override to use path-prefixed endpoints.
|
|
12221
12224
|
const issuerPath = issuerUrl.pathname.replace(/\/$/, "");
|
|
@@ -12230,7 +12233,7 @@ async function startStreamableHTTPServer() {
|
|
|
12230
12233
|
response_types_supported: ["code"],
|
|
12231
12234
|
code_challenge_methods_supported: ["S256"],
|
|
12232
12235
|
token_endpoint_auth_methods_supported: ["client_secret_post", "none"],
|
|
12233
|
-
grant_types_supported:
|
|
12236
|
+
grant_types_supported: grantTypesSupported,
|
|
12234
12237
|
scopes_supported: scopesSupported,
|
|
12235
12238
|
revocation_endpoint_auth_methods_supported: ["client_secret_post"],
|
|
12236
12239
|
};
|
|
@@ -12272,7 +12275,21 @@ async function startStreamableHTTPServer() {
|
|
|
12272
12275
|
});
|
|
12273
12276
|
});
|
|
12274
12277
|
}
|
|
12275
|
-
//
|
|
12278
|
+
// The SDK's root metadata always lists refresh_token; shadow it with the
|
|
12279
|
+
// same document minus that grant (CORS + GET/OPTIONS kept by metadataHandler).
|
|
12280
|
+
if (!issuerPath && OAUTH_DISABLE_REFRESH_TOKEN) {
|
|
12281
|
+
app.use("/.well-known/oauth-authorization-server", metadataHandler({
|
|
12282
|
+
...createOAuthMetadata({
|
|
12283
|
+
provider: oauthProvider,
|
|
12284
|
+
issuerUrl,
|
|
12285
|
+
baseUrl: issuerUrl,
|
|
12286
|
+
scopesSupported,
|
|
12287
|
+
}),
|
|
12288
|
+
grant_types_supported: grantTypesSupported,
|
|
12289
|
+
}));
|
|
12290
|
+
}
|
|
12291
|
+
// Mounts /.well-known/oauth-authorization-server (shadowed above when basePath set
|
|
12292
|
+
// or OAUTH_DISABLE_REFRESH_TOKEN is on),
|
|
12276
12293
|
// /.well-known/oauth-protected-resource, /authorize, /token, /register, /revoke
|
|
12277
12294
|
// Some proxies include the port in X-Forwarded-For (e.g. "1.2.3.4:5678" or
|
|
12278
12295
|
// "[2001:db8::1]:5678"), which makes express-rate-limit throw
|
package/build/oauth-proxy.js
CHANGED
|
@@ -72,6 +72,7 @@ const CLIENT_CACHE_MAX_SIZE = 1000;
|
|
|
72
72
|
const GROUP_MEMBERSHIP_CACHE_MAX_SIZE = 1000;
|
|
73
73
|
const GROUP_MEMBERSHIP_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
74
74
|
const GROUP_ACCESS_DENIED_MESSAGE = "Access denied: user is not a member of an allowed group";
|
|
75
|
+
const REFRESH_TOKEN_DISABLED_MESSAGE = "Refresh tokens are disabled on this server (OAUTH_DISABLE_REFRESH_TOKEN); start a new authorization";
|
|
75
76
|
function singleQueryParam(value) {
|
|
76
77
|
return typeof value === "string" ? value : undefined;
|
|
77
78
|
}
|
|
@@ -155,7 +156,11 @@ class GitLabOAuthServerProvider {
|
|
|
155
156
|
// mode, where GitLab checks redirect_uri against the app registration.
|
|
156
157
|
_redirectPolicy = null;
|
|
157
158
|
_consent = null;
|
|
158
|
-
|
|
159
|
+
// OAUTH_DISABLE_REFRESH_TOKEN: issued tokens carry no refresh_token and the
|
|
160
|
+
// refresh_token grant is rejected, so the client re-runs the browser
|
|
161
|
+
// authorization once the GitLab access token expires.
|
|
162
|
+
_disableRefreshToken;
|
|
163
|
+
constructor(gitlabBaseUrl, gitlabAppId, resourceName, readOnly, customScopes, allowedGroups, callbackProxyEnabled = false, callbackUrl = "", stateless = null, allowedRedirectUris = DEFAULT_ALLOWED_REDIRECT_URIS, disableRefreshToken = false) {
|
|
159
164
|
this._gitlabBaseUrl = gitlabBaseUrl;
|
|
160
165
|
this._gitlabAppId = gitlabAppId;
|
|
161
166
|
this._resourceName = resourceName;
|
|
@@ -169,6 +174,7 @@ class GitLabOAuthServerProvider {
|
|
|
169
174
|
this._callbackProxyEnabled = callbackProxyEnabled;
|
|
170
175
|
this._callbackUrl = callbackUrl;
|
|
171
176
|
this._stateless = stateless;
|
|
177
|
+
this._disableRefreshToken = disableRefreshToken;
|
|
172
178
|
if (callbackProxyEnabled && !callbackUrl) {
|
|
173
179
|
throw new Error("callbackUrl is required when callbackProxyEnabled is true");
|
|
174
180
|
}
|
|
@@ -190,6 +196,10 @@ class GitLabOAuthServerProvider {
|
|
|
190
196
|
`pending TTL: ${stateless.pendingTtlSeconds}s, ` +
|
|
191
197
|
`stored TTL: ${stateless.storedTtlSeconds}s)`);
|
|
192
198
|
}
|
|
199
|
+
if (disableRefreshToken) {
|
|
200
|
+
logger.info("OAUTH_DISABLE_REFRESH_TOKEN enabled — refresh_token grant rejected; " +
|
|
201
|
+
"clients must re-authorize in the browser when the GitLab access token expires");
|
|
202
|
+
}
|
|
193
203
|
}
|
|
194
204
|
// ---- Client store (local DCR) ------------------------------------------
|
|
195
205
|
get clientsStore() {
|
|
@@ -197,6 +207,7 @@ class GitLabOAuthServerProvider {
|
|
|
197
207
|
const resourceName = this._resourceName;
|
|
198
208
|
const stateless = this._stateless;
|
|
199
209
|
const redirectPolicy = this._redirectPolicy;
|
|
210
|
+
const disableRefreshToken = this._disableRefreshToken;
|
|
200
211
|
return {
|
|
201
212
|
getClient: async (clientId) => {
|
|
202
213
|
// Stateless path: a signed client_id carries the registration.
|
|
@@ -231,7 +242,12 @@ class GitLabOAuthServerProvider {
|
|
|
231
242
|
};
|
|
232
243
|
},
|
|
233
244
|
registerClient: async (client) => {
|
|
234
|
-
|
|
245
|
+
let grantTypes = client.grant_types ?? ["authorization_code"];
|
|
246
|
+
if (disableRefreshToken) {
|
|
247
|
+
// OAUTH_DISABLE_REFRESH_TOKEN: match grant_types_supported in the metadata.
|
|
248
|
+
const withoutRefresh = grantTypes.filter((gt) => gt !== "refresh_token");
|
|
249
|
+
grantTypes = withoutRefresh.length > 0 ? withoutRefresh : ["authorization_code"];
|
|
250
|
+
}
|
|
235
251
|
const redirectUris = client.redirect_uris ?? [];
|
|
236
252
|
// Callback proxy mode: GitLab never sees the client's redirect_uri, so
|
|
237
253
|
// this server is the only place it can be checked.
|
|
@@ -514,6 +530,12 @@ class GitLabOAuthServerProvider {
|
|
|
514
530
|
this._usedProxyCodes.commit(reservedProxyCode.hash, reservedProxyCode.id);
|
|
515
531
|
reservedProxyCode = null;
|
|
516
532
|
}
|
|
533
|
+
if (this._disableRefreshToken) {
|
|
534
|
+
// Without a refresh_token the client cannot renew silently and has to
|
|
535
|
+
// restart authorization once the access token expires.
|
|
536
|
+
const { refresh_token: _refreshToken, ...withoutRefresh } = tokens;
|
|
537
|
+
return withoutRefresh;
|
|
538
|
+
}
|
|
517
539
|
return tokens;
|
|
518
540
|
}
|
|
519
541
|
catch (err) {
|
|
@@ -585,6 +607,11 @@ class GitLabOAuthServerProvider {
|
|
|
585
607
|
}
|
|
586
608
|
// ---- Refresh token -----------------------------------------------------
|
|
587
609
|
async exchangeRefreshToken(_client, refreshToken, scopes, resource) {
|
|
610
|
+
if (this._disableRefreshToken) {
|
|
611
|
+
// invalid_grant makes MCP clients drop stored tokens and restart the
|
|
612
|
+
// authorization_code flow; GitLab is not contacted.
|
|
613
|
+
throw new InvalidGrantError(REFRESH_TOKEN_DISABLED_MESSAGE);
|
|
614
|
+
}
|
|
588
615
|
const params = new URLSearchParams({
|
|
589
616
|
grant_type: "refresh_token",
|
|
590
617
|
client_id: this._gitlabAppId,
|
|
@@ -736,7 +763,13 @@ class GitLabOAuthServerProvider {
|
|
|
736
763
|
res.status(502).send("Token exchange with GitLab failed");
|
|
737
764
|
return;
|
|
738
765
|
}
|
|
739
|
-
|
|
766
|
+
let tokens = OAuthTokensSchema.parse(await tokenResponse.json());
|
|
767
|
+
if (this._disableRefreshToken) {
|
|
768
|
+
// Drop the refresh_token before it is stored in the LRU or sealed into
|
|
769
|
+
// the proxy code that travels through the browser.
|
|
770
|
+
const { refresh_token: _refreshToken, ...withoutRefresh } = tokens;
|
|
771
|
+
tokens = withoutRefresh;
|
|
772
|
+
}
|
|
740
773
|
// Generate a proxy auth code for the MCP client. Sealed in stateless
|
|
741
774
|
// mode; random UUID + LRU entry in legacy mode.
|
|
742
775
|
const proxyCode = stateless
|
|
@@ -811,7 +844,10 @@ class GitLabOAuthServerProvider {
|
|
|
811
844
|
* instead of an in-memory cache, enabling multi-pod deploys.
|
|
812
845
|
* @param allowedRedirectUris Callback proxy mode only: client redirect URIs that
|
|
813
846
|
* may register and receive codes (see oauth-consent.ts).
|
|
847
|
+
* @param disableRefreshToken When true, issued tokens omit refresh_token and the
|
|
848
|
+
* refresh_token grant fails with invalid_grant, so clients
|
|
849
|
+
* re-authorize in the browser after access-token expiry.
|
|
814
850
|
*/
|
|
815
|
-
export function createGitLabOAuthProvider(gitlabBaseUrl, gitlabAppId, resourceName = "GitLab MCP Server", readOnly = false, customScopes, allowedGroups, callbackProxyEnabled = false, callbackUrl = "", stateless = null, allowedRedirectUris = DEFAULT_ALLOWED_REDIRECT_URIS) {
|
|
816
|
-
return new GitLabOAuthServerProvider(gitlabBaseUrl, gitlabAppId, resourceName, readOnly, customScopes, allowedGroups, callbackProxyEnabled, callbackUrl, stateless, allowedRedirectUris);
|
|
851
|
+
export function createGitLabOAuthProvider(gitlabBaseUrl, gitlabAppId, resourceName = "GitLab MCP Server", readOnly = false, customScopes, allowedGroups, callbackProxyEnabled = false, callbackUrl = "", stateless = null, allowedRedirectUris = DEFAULT_ALLOWED_REDIRECT_URIS, disableRefreshToken = false) {
|
|
852
|
+
return new GitLabOAuthServerProvider(gitlabBaseUrl, gitlabAppId, resourceName, readOnly, customScopes, allowedGroups, callbackProxyEnabled, callbackUrl, stateless, allowedRedirectUris, disableRefreshToken);
|
|
817
853
|
}
|
|
@@ -18,12 +18,13 @@ export async function loadRemoteHttpStack() {
|
|
|
18
18
|
if (remoteHttpStackCache.current) {
|
|
19
19
|
return remoteHttpStackCache.current;
|
|
20
20
|
}
|
|
21
|
-
const [expressModule, rateLimitModule, sseModule, streamableModule, authRouterModule, bearerModule,] = await Promise.all([
|
|
21
|
+
const [expressModule, rateLimitModule, sseModule, streamableModule, authRouterModule, metadataModule, bearerModule,] = await Promise.all([
|
|
22
22
|
import("express"),
|
|
23
23
|
import("express-rate-limit"),
|
|
24
24
|
import("@modelcontextprotocol/sdk/server/sse.js"),
|
|
25
25
|
import("@modelcontextprotocol/sdk/server/streamableHttp.js"),
|
|
26
26
|
import("@modelcontextprotocol/sdk/server/auth/router.js"),
|
|
27
|
+
import("@modelcontextprotocol/sdk/server/auth/handlers/metadata.js"),
|
|
27
28
|
import("@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"),
|
|
28
29
|
]);
|
|
29
30
|
remoteHttpStackCache.current = {
|
|
@@ -33,6 +34,8 @@ export async function loadRemoteHttpStack() {
|
|
|
33
34
|
SSEServerTransport: sseModule.SSEServerTransport,
|
|
34
35
|
StreamableHTTPServerTransport: streamableModule.StreamableHTTPServerTransport,
|
|
35
36
|
mcpAuthRouter: authRouterModule.mcpAuthRouter,
|
|
37
|
+
createOAuthMetadata: authRouterModule.createOAuthMetadata,
|
|
38
|
+
metadataHandler: metadataModule.metadataHandler,
|
|
36
39
|
requireBearerAuth: bearerModule.requireBearerAuth,
|
|
37
40
|
};
|
|
38
41
|
return remoteHttpStackCache.current;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.7-next.136",
|
|
4
4
|
"mcpName": "io.github.zereight/gitlab-mcp",
|
|
5
5
|
"description": "GitLab MCP server for projects, merge requests, issues, pipelines, wiki, releases, and more",
|
|
6
6
|
"keywords": [
|
|
@@ -49,7 +49,6 @@
|
|
|
49
49
|
"prepare": "npm run build",
|
|
50
50
|
"dev": "npm run build && node build/index.js",
|
|
51
51
|
"watch": "tsc -p tsconfig.build.json --watch",
|
|
52
|
-
"deploy": "npm publish --access public",
|
|
53
52
|
"changelog": "auto-changelog -p",
|
|
54
53
|
"test": "npm run test:all",
|
|
55
54
|
"test:all": "npm run build && npm run test:mock && npm run test:live",
|