@zereight/mcp-gitlab 2.2.5 → 2.2.6

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 CHANGED
@@ -138,7 +138,7 @@ command = lib.getExe inputs.gitlab-mcp.packages.${system}.default;
138
138
 
139
139
  예시는 기존 `mcp-gitlab`보다 충돌 가능성이 낮은 `zereight-mcp-gitlab` 별칭을 사용합니다. MCP 클라이언트가 찾지 못하면 `which zereight-mcp-gitlab`의 절대 경로를 사용하세요.
140
140
 
141
- 전역 설치를 쓰지 않으려면 `npx -y @zereight/mcp-gitlab@2.2.4`처럼 직전 안정 버전(문서가 권장하는 버전)으로 고정하세요. 항상 최신 버전을 원하면 `npx -y @zereight/mcp-gitlab@latest`를 사용하세요. 새 버전이 나오면 서버가 시작 시 stderr로 알려줍니다(`GITLAB_DISABLE_VERSION_CHECK=true`로 비활성화 가능).
141
+ 전역 설치를 쓰지 않으려면 `npx -y @zereight/mcp-gitlab@2.2.5`처럼 직전 안정 버전(문서가 권장하는 버전)으로 고정하세요. 항상 최신 버전을 원하면 `npx -y @zereight/mcp-gitlab@latest`를 사용하세요. 새 버전이 나오면 서버가 시작 시 stderr로 알려줍니다(`GITLAB_DISABLE_VERSION_CHECK=true`로 비활성화 가능).
142
142
 
143
143
  #### CLI 인자 사용하기(환경 변수 문제가 있는 클라이언트용)
144
144
 
package/README.md CHANGED
@@ -143,7 +143,7 @@ The store path is pinned by your lock file; update it with `nix flake update git
143
143
 
144
144
  The examples use `zereight-mcp-gitlab`, a less collision-prone alias for the legacy `mcp-gitlab` binary. If your MCP client cannot find it, use the absolute path from `which zereight-mcp-gitlab`.
145
145
 
146
- No global install? Pin `npx` to the previous stable release (the version these docs recommend), for example `npx -y @zereight/mcp-gitlab@2.2.4`. If you always want the newest release, use `npx -y @zereight/mcp-gitlab@latest` instead. The server prints a notice to stderr on startup when a newer version is available (disable with `GITLAB_DISABLE_VERSION_CHECK=true`).
146
+ No global install? Pin `npx` to the previous stable release (the version these docs recommend), for example `npx -y @zereight/mcp-gitlab@2.2.5`. If you always want the newest release, use `npx -y @zereight/mcp-gitlab@latest` instead. The server prints a notice to stderr on startup when a newer version is available (disable with `GITLAB_DISABLE_VERSION_CHECK=true`).
147
147
 
148
148
  #### Using CLI Arguments (for clients with env var issues)
149
149
 
package/README.pt-BR.md CHANGED
@@ -138,7 +138,7 @@ O caminho no store é fixado pelo seu lock file; atualize-o com `nix flake updat
138
138
 
139
139
  Os exemplos usam `zereight-mcp-gitlab`, um alias com menor chance de conflito que o binário legado `mcp-gitlab`. Se o seu cliente MCP não o encontrar, use o caminho absoluto retornado por `which zereight-mcp-gitlab`.
140
140
 
141
- Não quer instalar globalmente? Fixe o `npx` na versão estável anterior (a versão recomendada por esta documentação), por exemplo `npx -y @zereight/mcp-gitlab@2.2.4`. Se quiser sempre a versão mais recente, use `npx -y @zereight/mcp-gitlab@latest`. O servidor exibe um aviso no stderr durante a inicialização quando existe uma versão mais nova disponível (desative com `GITLAB_DISABLE_VERSION_CHECK=true`).
141
+ Não quer instalar globalmente? Fixe o `npx` na versão estável anterior (a versão recomendada por esta documentação), por exemplo `npx -y @zereight/mcp-gitlab@2.2.5`. Se quiser sempre a versão mais recente, use `npx -y @zereight/mcp-gitlab@latest`. O servidor exibe um aviso no stderr durante a inicialização quando existe uma versão mais nova disponível (desative com `GITLAB_DISABLE_VERSION_CHECK=true`).
142
142
 
143
143
  #### Usando argumentos de CLI (para clientes com problemas com variáveis de ambiente)
144
144
 
package/README.zh-CN.md CHANGED
@@ -138,7 +138,7 @@ command = lib.getExe inputs.gitlab-mcp.packages.${system}.default;
138
138
 
139
139
  示例使用 `zereight-mcp-gitlab`,这是比旧的 `mcp-gitlab` 更不容易冲突的别名。如果 MCP 客户端找不到它,请使用 `which zereight-mcp-gitlab` 输出的绝对路径。
140
140
 
141
- 如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.2.4`。如果始终想使用最新版本,请改用 `npx -y @zereight/mcp-gitlab@latest`。有新版本发布时,服务器会在启动时通过 stderr 提示(可用 `GITLAB_DISABLE_VERSION_CHECK=true` 关闭)。
141
+ 如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.2.5`。如果始终想使用最新版本,请改用 `npx -y @zereight/mcp-gitlab@latest`。有新版本发布时,服务器会在启动时通过 stderr 提示(可用 `GITLAB_DISABLE_VERSION_CHECK=true` 关闭)。
142
142
 
143
143
  #### 使用 CLI 参数(适用于环境变量有问题的客户端)
144
144
 
package/build/index.js CHANGED
@@ -1186,6 +1186,27 @@ function getEffectiveAllowedProjectIds() {
1186
1186
  }
1187
1187
  return GITLAB_ALLOWED_PROJECT_IDS;
1188
1188
  }
1189
+ /**
1190
+ * Keep a dynamic project scope bound to its session
1191
+ * A request that omits X-GitLab-Allowed-Project-Ids keeps the session scope, and a
1192
+ * request naming a different scope is refused (null). An unscoped session may be
1193
+ * narrowed. Re-initialize to change the scope.
1194
+ */
1195
+ function bindSessionProjectScope(requested, sessionScope) {
1196
+ if (!sessionScope || sessionScope.length === 0) {
1197
+ return requested;
1198
+ }
1199
+ if (requested === undefined) {
1200
+ return sessionScope;
1201
+ }
1202
+ const want = new Set(requested);
1203
+ const have = new Set(sessionScope);
1204
+ return want.size === have.size && [...want].every(id => have.has(id)) ? sessionScope : null;
1205
+ }
1206
+ const SESSION_PROJECT_SCOPE_MISMATCH = {
1207
+ error: "Project scope mismatch",
1208
+ message: "X-GitLab-Allowed-Project-Ids does not match the project scope of this session. Re-initialize to change it.",
1209
+ };
1189
1210
  /**
1190
1211
  * Reject tools that cannot be limited to allowed projects when
1191
1212
  * ENABLE_STRICT_PROJECT_SCOPE is set and an allowlist is in effect
@@ -11694,6 +11715,9 @@ async function startStreamableHTTPServer() {
11694
11715
  const incomingSid = readMcpSessionIdHeader(req);
11695
11716
  const isInit = isInitializationRequestBody(req.body);
11696
11717
  const headerAuth = parseAuthHeaders(req);
11718
+ const opened = incomingSid && looksLikeStatelessSessionId(incomingSid)
11719
+ ? openSessionId(material, incomingSid, sessionTtlSeconds)
11720
+ : null;
11697
11721
  // In GITLAB_MCP_OAUTH mode, req.auth may be populated by requireBearerAuth.
11698
11722
  // Use it when headerAuth is null (no Private-Token / JOB-TOKEN / Authorization).
11699
11723
  let effective = null;
@@ -11701,7 +11725,6 @@ async function startStreamableHTTPServer() {
11701
11725
  if (headerAuth) {
11702
11726
  let needsUpstreamValidation = isInit || !incomingSid;
11703
11727
  if (!needsUpstreamValidation && incomingSid && looksLikeStatelessSessionId(incomingSid)) {
11704
- const opened = openSessionId(material, incomingSid, sessionTtlSeconds);
11705
11728
  needsUpstreamValidation =
11706
11729
  !opened ||
11707
11730
  opened.h !== headerAuth.header ||
@@ -11748,7 +11771,6 @@ async function startStreamableHTTPServer() {
11748
11771
  let sidPresentedButInvalid = false;
11749
11772
  if (!effective && incomingSid) {
11750
11773
  if (looksLikeStatelessSessionId(incomingSid)) {
11751
- const opened = openSessionId(material, incomingSid, sessionTtlSeconds);
11752
11774
  if (opened) {
11753
11775
  effective = {
11754
11776
  header: opened.h,
@@ -11791,6 +11813,18 @@ async function startStreamableHTTPServer() {
11791
11813
  });
11792
11814
  return;
11793
11815
  }
11816
+ // The project scope sealed into the sid belongs to the session: live headers
11817
+ // may refresh the token but cannot drop or replace the scope.
11818
+ if (ENABLE_DYNAMIC_PROJECT_SCOPE && freshAuthPresent && !isInit && opened) {
11819
+ const allowedProjectIds = bindSessionProjectScope(effective.allowedProjectIds, opened.p);
11820
+ if (allowedProjectIds === null) {
11821
+ metrics.authFailures++;
11822
+ metrics.statelessAuthFailures++;
11823
+ res.status(401).json(SESSION_PROJECT_SCOPE_MISMATCH);
11824
+ return;
11825
+ }
11826
+ effective.allowedProjectIds = allowedProjectIds;
11827
+ }
11794
11828
  // Always mint a fresh sid so the embedded iat advances on every request.
11795
11829
  // This makes OAUTH_STATELESS_SESSION_TTL_SECONDS behave as an inactivity
11796
11830
  // timeout rather than an absolute-age cap — matching the legacy
@@ -12180,7 +12214,13 @@ async function startStreamableHTTPServer() {
12180
12214
  }
12181
12215
  }
12182
12216
  else if (sessionId && authData) {
12183
- const result = await storeValidatedSessionAuth(sessionId, authData, publicBaseUrl, authBySession[sessionId], { skipIfUnchanged: true });
12217
+ const allowedProjectIds = bindSessionProjectScope(authData.allowedProjectIds, authBySession[sessionId].allowedProjectIds);
12218
+ if (allowedProjectIds === null) {
12219
+ metrics.authFailures++;
12220
+ res.status(401).json(SESSION_PROJECT_SCOPE_MISMATCH);
12221
+ return;
12222
+ }
12223
+ const result = await storeValidatedSessionAuth(sessionId, { ...authData, allowedProjectIds }, publicBaseUrl, authBySession[sessionId], { skipIfUnchanged: true });
12184
12224
  if (result === "invalid") {
12185
12225
  metrics.authFailures++;
12186
12226
  res.status(401).json({
@@ -12193,6 +12233,10 @@ async function startStreamableHTTPServer() {
12193
12233
  if (result === "stored") {
12194
12234
  logger.debug(`Session ${sessionId}: updated ${authData.header} header`);
12195
12235
  }
12236
+ else {
12237
+ // Same token: keep the stored auth but let an unscoped session narrow.
12238
+ authBySession[sessionId].allowedProjectIds = allowedProjectIds;
12239
+ }
12196
12240
  }
12197
12241
  else if (sessionId && authBySession[sessionId]) {
12198
12242
  // Existing session with stored auth: update last used time and reset timeout
@@ -12218,7 +12262,13 @@ async function startStreamableHTTPServer() {
12218
12262
  setAuthTimeout(sessionId);
12219
12263
  }
12220
12264
  else {
12221
- authBySession[sessionId] = withPublicBaseUrl(headerAuthData, publicBaseUrl, authBySession[sessionId]);
12265
+ const allowedProjectIds = bindSessionProjectScope(headerAuthData.allowedProjectIds, authBySession[sessionId].allowedProjectIds);
12266
+ if (allowedProjectIds === null) {
12267
+ metrics.authFailures++;
12268
+ res.status(401).json(SESSION_PROJECT_SCOPE_MISMATCH);
12269
+ return;
12270
+ }
12271
+ authBySession[sessionId] = withPublicBaseUrl({ ...headerAuthData, allowedProjectIds }, publicBaseUrl, authBySession[sessionId]);
12222
12272
  setAuthTimeout(sessionId);
12223
12273
  }
12224
12274
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "2.2.5",
3
+ "version": "2.2.6",
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": [