From 3b68c01d7e8a053b32b3954401a3b25622f80496 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 22 Jun 2026 10:29:17 +0300 Subject: [PATCH 1/6] fix(runners): send job status filter as a GitLab list argument GitLab's runner jobs(statuses:) argument is [CiJobStatus!], but the ListRunnerJobs query declared $statuses as a singular CiJobStatus. GitLab rejects the query with "List dimension mismatch on variable $statuses" whenever a runner with executed jobs exists, so browse_runners list_jobs failed against real data regardless of whether a status filter was passed. Declare the variable as [CiJobStatus!] and wrap the single-status filter in a list before sending. The unit test now asserts the value reaches GitLab as ["FAILED"] rather than the bare enum. Closes #535 --- packages/gitlab-mcp/src/entities/runners/registry.ts | 2 +- packages/gitlab-mcp/src/graphql/runners.ts | 4 ++-- .../gitlab-mcp/tests/unit/entities/runners/registry.test.ts | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/gitlab-mcp/src/entities/runners/registry.ts b/packages/gitlab-mcp/src/entities/runners/registry.ts index 3e65d06a..7047c249 100644 --- a/packages/gitlab-mcp/src/entities/runners/registry.ts +++ b/packages/gitlab-mcp/src/entities/runners/registry.ts @@ -164,7 +164,7 @@ export const runnersToolRegistry: ToolRegistry = new Map = gql` - query ListRunnerJobs($id: CiRunnerID!, $statuses: CiJobStatus, $first: Int, $after: String) { + query ListRunnerJobs($id: CiRunnerID!, $statuses: [CiJobStatus!], $first: Int, $after: String) { runner(id: $id) { id jobs(statuses: $statuses, first: $first, after: $after) { diff --git a/packages/gitlab-mcp/tests/unit/entities/runners/registry.test.ts b/packages/gitlab-mcp/tests/unit/entities/runners/registry.test.ts index 858eac11..a6208af2 100644 --- a/packages/gitlab-mcp/tests/unit/entities/runners/registry.test.ts +++ b/packages/gitlab-mcp/tests/unit/entities/runners/registry.test.ts @@ -115,7 +115,10 @@ describe('runners registry', () => { await browse().handler({ action: 'list_jobs', runner_id: 7, statuses: 'FAILED' }); const [doc, vars] = mockClient.request.mock.calls[0]; expect(doc).toBe(LIST_RUNNER_JOBS); - expect(vars).toMatchObject({ id: RUNNER_GID, statuses: 'FAILED' }); + // GitLab's jobs(statuses:) argument is [CiJobStatus!], so a single status + // filter must be wrapped in a list. Sending the bare enum makes GitLab reject + // the query with "List dimension mismatch on variable $statuses". + expect(vars).toMatchObject({ id: RUNNER_GID, statuses: ['FAILED'] }); }); it('list_jobs throws when the runner is missing', async () => { From 6bd13df530e4fcce3088fedad779c36a5b138241 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 22 Jun 2026 10:29:41 +0300 Subject: [PATCH 2/6] chore(deps): bump graphql to 17 and refresh dev dependencies - graphql 16.14.2 -> 17.0.1 (resolves Dependabot #532 / #533). Node engine already requires >=24, which graphql 17 supports. - graphql-tag 2.12.6 -> 2.12.7 so its peer range covers graphql 17. - @clack/prompts 1.5.1 -> 1.6.0. - @cloudflare/workers-types and @types/node patch refresh (within ^25). Verified with the full unit suite (5206 tests) and full integration suite (443 tests) against a live GitLab instance. --- packages/gitlab-mcp-db/package.json | 2 +- packages/gitlab-mcp/package.json | 10 ++--- yarn.lock | 64 ++++++++++++++--------------- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/packages/gitlab-mcp-db/package.json b/packages/gitlab-mcp-db/package.json index 2d0c100f..beb6bf3b 100644 --- a/packages/gitlab-mcp-db/package.json +++ b/packages/gitlab-mcp-db/package.json @@ -28,7 +28,7 @@ "devDependencies": { "@structured-world/gitlab-mcp": "workspace:^", "@types/jest": "^30.0.0", - "@types/node": "^25.9.3", + "@types/node": "^25.9.4", "jest": "^30.4.2", "ts-jest": "^29.4.11", "typescript": "^6.0.3" diff --git a/packages/gitlab-mcp/package.json b/packages/gitlab-mcp/package.json index 13e02dd2..f7a24510 100644 --- a/packages/gitlab-mcp/package.json +++ b/packages/gitlab-mcp/package.json @@ -558,11 +558,11 @@ "build:mcpb": "./scripts/build-mcpb.sh" }, "dependencies": { - "@clack/prompts": "^1.5.1", + "@clack/prompts": "^1.6.0", "@modelcontextprotocol/sdk": "^1.29.0", "express": "^5.2.1", - "graphql": "^16.14.2", - "graphql-tag": "^2.12.6", + "graphql": "^17.0.0", + "graphql-tag": "^2.12.7", "open": "^11.0.0", "picomatch": "^4.0.4", "pino": "^10.3.1", @@ -574,13 +574,13 @@ "zod": "^4.4.3" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20260616.1", + "@cloudflare/workers-types": "^4.20260621.1", "@eslint/js": "^10.0.1", "@graphql-typed-document-node/core": "^3.2.0", "@structured-world/vue-privacy": "^1.10.0", "@types/express": "^5.0.6", "@types/jest": "^30.0.0", - "@types/node": "^25.9.3", + "@types/node": "^25.9.4", "@types/picomatch": "^4.0.3", "@typescript-eslint/eslint-plugin": "^8.61.1", "@typescript-eslint/parser": "^8.61.1", diff --git a/yarn.lock b/yarn.lock index 0ca53a29..3b438ff5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -450,13 +450,13 @@ __metadata: languageName: node linkType: hard -"@clack/core@npm:1.4.1": - version: 1.4.1 - resolution: "@clack/core@npm:1.4.1" +"@clack/core@npm:1.4.2": + version: 1.4.2 + resolution: "@clack/core@npm:1.4.2" dependencies: fast-wrap-ansi: "npm:^0.2.0" sisteransi: "npm:^1.0.5" - checksum: 10c0/7e7e02f0c423c457aff9acc09f9ffbae3d2841b18f543c614b5b8eaa2c11c9f146bf68b3078d1daa4dea9d3131ccfa514edbe8d29ced18446933567188b2543c + checksum: 10c0/390a0c650dbd4c406029e32fd6a0b704bad3a172cab96e77989a1d376ddeee5f4b0500edb093375b202ae35acfc94fbd2fb11fe3ad9e47724acae4b7ff1d68b6 languageName: node linkType: hard @@ -471,22 +471,22 @@ __metadata: languageName: node linkType: hard -"@clack/prompts@npm:^1.5.1": - version: 1.5.1 - resolution: "@clack/prompts@npm:1.5.1" +"@clack/prompts@npm:^1.6.0": + version: 1.6.0 + resolution: "@clack/prompts@npm:1.6.0" dependencies: - "@clack/core": "npm:1.4.1" + "@clack/core": "npm:1.4.2" fast-string-width: "npm:^3.0.2" fast-wrap-ansi: "npm:^0.2.0" sisteransi: "npm:^1.0.5" - checksum: 10c0/423b1958d28a25703130c2dd6a236413d210446fd36e980dcf106f9165e5c579494214aaca5185269e918488c6d4f93c4de44a7dc5f902add8874486b2624c22 + checksum: 10c0/b288a1085ce75a06a739f4907b38187d9b3121a53e4a86541383df3e2348276fa00903a5d2220d7b2c821a55b81ad11af9cadb3760e3212dac27fe2001555ab1 languageName: node linkType: hard -"@cloudflare/workers-types@npm:^4.20260616.1": - version: 4.20260616.1 - resolution: "@cloudflare/workers-types@npm:4.20260616.1" - checksum: 10c0/ce02ec49826740e35de0c22c45b4fe18e819a890c26c17261f81a1de5f7b7e59824182bff4775ce50b6614d5cd468d1b1393b76956467404ba62f69aaa3305d8 +"@cloudflare/workers-types@npm:^4.20260621.1": + version: 4.20260621.1 + resolution: "@cloudflare/workers-types@npm:4.20260621.1" + checksum: 10c0/5f220aa5feff5b4e20dc549b82f80740c0daa25cb001e8d8a5aac51ab2aba1123096276ec880887c6fd37cc396beb43ebe4ab3c23ae32d31993fa2230c1e4ed7 languageName: node linkType: hard @@ -2136,7 +2136,7 @@ __metadata: "@prisma/client": "npm:^7.8.0" "@structured-world/gitlab-mcp": "workspace:^" "@types/jest": "npm:^30.0.0" - "@types/node": "npm:^25.9.3" + "@types/node": "npm:^25.9.4" jest: "npm:^30.4.2" prisma: "npm:^7.8.0" ts-jest: "npm:^29.4.11" @@ -2148,15 +2148,15 @@ __metadata: version: 0.0.0-use.local resolution: "@structured-world/gitlab-mcp@workspace:packages/gitlab-mcp" dependencies: - "@clack/prompts": "npm:^1.5.1" - "@cloudflare/workers-types": "npm:^4.20260616.1" + "@clack/prompts": "npm:^1.6.0" + "@cloudflare/workers-types": "npm:^4.20260621.1" "@eslint/js": "npm:^10.0.1" "@graphql-typed-document-node/core": "npm:^3.2.0" "@modelcontextprotocol/sdk": "npm:^1.29.0" "@structured-world/vue-privacy": "npm:^1.10.0" "@types/express": "npm:^5.0.6" "@types/jest": "npm:^30.0.0" - "@types/node": "npm:^25.9.3" + "@types/node": "npm:^25.9.4" "@types/picomatch": "npm:^4.0.3" "@typescript-eslint/eslint-plugin": "npm:^8.61.1" "@typescript-eslint/parser": "npm:^8.61.1" @@ -2166,8 +2166,8 @@ __metadata: eslint: "npm:^10.5.0" eslint-plugin-prettier: "npm:^5.5.6" express: "npm:^5.2.1" - graphql: "npm:^16.14.2" - graphql-tag: "npm:^2.12.6" + graphql: "npm:^17.0.0" + graphql-tag: "npm:^2.12.7" jest: "npm:^30.4.2" open: "npm:^11.0.0" picomatch: "npm:^4.0.4" @@ -2460,12 +2460,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^25.9.3": - version: 25.9.3 - resolution: "@types/node@npm:25.9.3" +"@types/node@npm:^25.9.4": + version: 25.9.4 + resolution: "@types/node@npm:25.9.4" dependencies: undici-types: "npm:>=7.24.0 <7.24.7" - checksum: 10c0/72d3aece9d42c2c641bcd3f3cb2dc2828b4bd384dfcbd910c404b8859a68bd69d50c4769ce7defd4ff5e049768e23e615f09407ea2cbbb5f44b90d75a7c6b8ca + checksum: 10c0/4b19670ef5dbefa836a2d4a9ed0b5ac6befbc0844bfdd12833234ff8fa68da1f65e6362d2ef87acf7e7da9d6ff649bf81c454d12e802b7e7fa9246a15c0edcf6 languageName: node linkType: hard @@ -5224,21 +5224,21 @@ __metadata: languageName: node linkType: hard -"graphql-tag@npm:^2.12.6": - version: 2.12.6 - resolution: "graphql-tag@npm:2.12.6" +"graphql-tag@npm:^2.12.7": + version: 2.12.7 + resolution: "graphql-tag@npm:2.12.7" dependencies: tslib: "npm:^2.1.0" peerDependencies: - graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: 10c0/7763a72011bda454ed8ff1a0d82325f43ca6478e4ce4ab8b7910c4c651dd00db553132171c04d80af5d5aebf1ef6a8a9fd53ccfa33b90ddc00aa3d4be6114419 + graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/c5b973b6b0477d3f734a0df7ebea2061790e268774d7fea310fde64820c1686c3b43f3e7262e4224d689917f42ae8e84c67f32772c003c308d38896ed960c5ec languageName: node linkType: hard -"graphql@npm:^16.14.2": - version: 16.14.2 - resolution: "graphql@npm:16.14.2" - checksum: 10c0/a95a96961eaff55cc9fe9d31fae6f33499ac988b972d07ea5085024cb1333f515b902f376e7393a5489aa82200a8aff3eb96580e4d1b69d702ed19b6eb1ce97a +"graphql@npm:^17.0.0": + version: 17.0.1 + resolution: "graphql@npm:17.0.1" + checksum: 10c0/95e4766eefa97bbc37cb4452527d24e12068f170e03c564611a3911f30a77361dfcfbdbfeea00f643c82585c02312c1f41ba6a6263049996d279c49951454980 languageName: node linkType: hard From 95a4a05b5260fc1e36258af86fbd99df9a9640ae Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 22 Jun 2026 10:30:06 +0300 Subject: [PATCH 3/6] ci(release): auto-generate server.json marketing copy and commit all READMEs The MCP Registry listing is driven by server.json's description, which read "GitLab MCP server with 58 tools for projects, MRs, pipelines, and more". That undersells the server next to the README hero line. prepare-release.sh now generates the description from the live tool / operation / entity counts as " CQRS tools exposing GitLab operations across entity types", mirroring the README and staying under the registry's 100-char description limit. prepare-release.sh renders the README to all three shipping locations (core npm page, GitHub repo landing, db npm page), but the release sync job only staged the core README, so the root and db pages drifted a release behind (download badge still on 9.0.0 while core was 9.1.0). Stage all three so the public counts and version stay consistent across npm, GitHub, and the db page. server.json and the root/db READMEs are regenerated here to the current counts. --- .github/workflows/release-please.yml | 6 +++++- README.md | 2 +- packages/gitlab-mcp-db/README.md | 2 +- packages/gitlab-mcp/scripts/prepare-release.sh | 10 +++++++--- packages/gitlab-mcp/server.json | 2 +- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 664f045c..2883cc69 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -414,7 +414,11 @@ jobs: # Only the committed dynamic artifacts. .semantic-release-version is a # release-time-only file and must not be committed. The root # package.json (../../) carries the mirrored release version. - git add package.json README.md server.json ../../package.json + # prepare-release.sh renders the README to all three shipping locations + # (core npm page, GitHub repo landing, db npm page) -- stage every one so + # the public counts/version never drift between them. Missing the root and + # db READMEs here previously left them stuck a release behind the core page. + git add package.json README.md server.json ../../README.md ../../package.json ../gitlab-mcp-db/README.md if git diff --cached --quiet; then echo "No metadata drift; nothing to commit." else diff --git a/README.md b/README.md index b0f55396..c75b3d2b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Advanced GitLab MCP server — 58 CQRS tools exposing 230 GitLab operations across 26 entity types. The tool catalog and parameters are filtered to each instance's GitLab version, tier, and token scopes, so the agent sees only what the connected instance actually supports. -[![Install in Claude Desktop](https://img.shields.io/badge/Claude_Desktop-Install_Extension-F97316?style=for-the-badge)](https://gitlab-mcp.sw.foundation/downloads/gitlab-mcp-9.0.0.mcpb) +[![Install in Claude Desktop](https://img.shields.io/badge/Claude_Desktop-Install_Extension-F97316?style=for-the-badge)](https://gitlab-mcp.sw.foundation/downloads/gitlab-mcp-9.1.0.mcpb) [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_Server-007ACC?style=for-the-badge&logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22gitlab-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40structured-world%2Fgitlab-mcp%22%5D%7D) [![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_MCP_Server-24bfa5?style=for-the-badge&logo=visualstudiocode&logoColor=white)](vscode-insiders:mcp/install?%7B%22name%22%3A%22gitlab-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40structured-world%2Fgitlab-mcp%22%5D%7D) diff --git a/packages/gitlab-mcp-db/README.md b/packages/gitlab-mcp-db/README.md index cc63ec6f..e03f9a8a 100644 --- a/packages/gitlab-mcp-db/README.md +++ b/packages/gitlab-mcp-db/README.md @@ -4,7 +4,7 @@ Advanced GitLab MCP server — 58 CQRS tools exposing 230 GitLab operations across 26 entity types. The tool catalog and parameters are filtered to each instance's GitLab version, tier, and token scopes, so the agent sees only what the connected instance actually supports. -[![Install in Claude Desktop](https://img.shields.io/badge/Claude_Desktop-Install_Extension-F97316?style=for-the-badge)](https://gitlab-mcp.sw.foundation/downloads/gitlab-mcp-9.0.0.mcpb) +[![Install in Claude Desktop](https://img.shields.io/badge/Claude_Desktop-Install_Extension-F97316?style=for-the-badge)](https://gitlab-mcp.sw.foundation/downloads/gitlab-mcp-9.1.0.mcpb) [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_Server-007ACC?style=for-the-badge&logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22gitlab-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40structured-world%2Fgitlab-mcp%22%5D%7D) [![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_MCP_Server-24bfa5?style=for-the-badge&logo=visualstudiocode&logoColor=white)](vscode-insiders:mcp/install?%7B%22name%22%3A%22gitlab-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40structured-world%2Fgitlab-mcp%22%5D%7D) diff --git a/packages/gitlab-mcp/scripts/prepare-release.sh b/packages/gitlab-mcp/scripts/prepare-release.sh index e0ea52ec..9e847e62 100755 --- a/packages/gitlab-mcp/scripts/prepare-release.sh +++ b/packages/gitlab-mcp/scripts/prepare-release.sh @@ -48,11 +48,15 @@ fi echo "prepare-release: v${VERSION}, ${TOOL_COUNT} tools (${READONLY_TOOL_COUNT} read-only, ${ACTION_COUNT} actions), ${ENTITY_COUNT} entities" -# Update server.json: version + description +# Update server.json: version + description. +# The description is the public marketing line shown on the MCP Registry listing, +# so it mirrors the README hero sentence (tools / operations / entity types) instead +# of a generic blurb. The MCP Registry schema caps description at 100 chars; this +# phrasing stays well under that even with 3-digit counts. # Note: set -e ensures script exits on jq failure; leftover server.tmp is benign # (gitignored, cleaned by next successful run, doesn't affect release) -jq --arg v "$VERSION" --arg tc "$TOOL_COUNT" \ - '.version = $v | .packages[0].version = $v | .description = "GitLab MCP server with " + $tc + " tools for projects, MRs, pipelines, and more"' \ +jq --arg v "$VERSION" --arg tc "$TOOL_COUNT" --arg ac "$ACTION_COUNT" --arg ec "$ENTITY_COUNT" \ + '.version = $v | .packages[0].version = $v | .description = $tc + " CQRS tools exposing " + $ac + " GitLab operations across " + $ec + " entity types"' \ server.json > server.tmp && mv server.tmp server.json # Generate README.md from the template for each location it ships to. __REPO_BASE__ diff --git a/packages/gitlab-mcp/server.json b/packages/gitlab-mcp/server.json index c9dc86fa..43e3e976 100644 --- a/packages/gitlab-mcp/server.json +++ b/packages/gitlab-mcp/server.json @@ -3,7 +3,7 @@ "$comment": "Schema version 2025-12-11 is the current MCP Registry standard. Only essential env vars are exposed here; USE_* feature flags are documented at websiteUrl and available via MCPB toggles.", "name": "io.github.structured-world/gitlab-mcp", "title": "Advanced GitLab MCP server", - "description": "GitLab MCP server with 58 tools for projects, MRs, pipelines, and more", + "description": "58 CQRS tools exposing 230 GitLab operations across 26 entity types", "websiteUrl": "https://gitlab-mcp.sw.foundation", "repository": { "url": "https://github.com/structured-world/gitlab-mcp", From 52ef396b186f0ef86d9fecfe77917fc89af7b7ad Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 22 Jun 2026 10:34:16 +0300 Subject: [PATCH 4/6] chore(deps): force form-data >=4.0.6 to close CRLF injection advisory The resolved form-data 4.0.5 is in the vulnerable range (>= 4.0.0, < 4.0.6) for the CRLF-injection-via-multipart-field-names advisory (GHSA, high). Pin it to >=4.0.6 in root resolutions so the dev/CI tree no longer trips the alert. js-yaml's quadratic-merge-key DoS (medium) is left as-is: the only consumer is @istanbuljs/load-nyc-config (dev coverage tooling, parses trusted local config), its sole patch lives in js-yaml 4.x, and forcing that major would break the 3.x safeLoad API the loader relies on. --- package.json | 3 ++- yarn.lock | 23 ++++++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 6b801675..947b6c2d 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "@hono/node-server": "1.19.14", "diff": "4.0.4", "esbuild": ">=0.28.1", - "tmp": ">=0.2.7" + "tmp": ">=0.2.7", + "form-data": ">=4.0.6" }, "scripts": { "build": "nx run-many -t build", diff --git a/yarn.lock b/yarn.lock index 3b438ff5..3f05128a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4953,16 +4953,16 @@ __metadata: languageName: node linkType: hard -"form-data@npm:4.0.5, form-data@npm:^4.0.5": - version: 4.0.5 - resolution: "form-data@npm:4.0.5" +"form-data@npm:>=4.0.6": + version: 4.0.6 + resolution: "form-data@npm:4.0.6" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" es-set-tostringtag: "npm:^2.1.0" - hasown: "npm:^2.0.2" - mime-types: "npm:^2.1.12" - checksum: 10c0/dd6b767ee0bbd6d84039db12a0fa5a2028160ffbfaba1800695713b46ae974a5f6e08b3356c3195137f8530dcd9dfcb5d5ae1eeff53d0db1e5aad863b619ce3b + hasown: "npm:^2.0.4" + mime-types: "npm:^2.1.35" + checksum: 10c0/43947a77bf0ff45c6ceed789778982d47a3f3e720a74b71721174ebf3310a5f1a8be1d6b38a3ee3688e8a18a2c4273073ec0844cd37efda3eaf46d41c9c318ff languageName: node linkType: hard @@ -5292,6 +5292,15 @@ __metadata: languageName: node linkType: hard +"hasown@npm:^2.0.4": + version: 2.0.4 + resolution: "hasown@npm:2.0.4" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/2d8de939e270b70618f8cebb69746620db10617dbb495bc66ddad326955ea24d3ca4af133aff3eb7c1853e0218f867bc2b050ec26fe02e3aea58f880ffc5e506 + languageName: node + linkType: hard + "hast-util-to-html@npm:^9.0.5": version: 9.0.5 resolution: "hast-util-to-html@npm:9.0.5" @@ -6631,7 +6640,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:2.1.35, mime-types@npm:^2.1.12": +"mime-types@npm:2.1.35, mime-types@npm:^2.1.35": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: From 16838571b239a2cf0c89338ed456b0fb04336a81 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 22 Jun 2026 10:40:01 +0300 Subject: [PATCH 5/6] chore(deps): bound form-data resolution to ^4.0.6 --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 947b6c2d..e67954dd 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "diff": "4.0.4", "esbuild": ">=0.28.1", "tmp": ">=0.2.7", - "form-data": ">=4.0.6" + "form-data": "^4.0.6" }, "scripts": { "build": "nx run-many -t build", diff --git a/yarn.lock b/yarn.lock index 3f05128a..386b5bcc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4953,7 +4953,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:>=4.0.6": +"form-data@npm:^4.0.6": version: 4.0.6 resolution: "form-data@npm:4.0.6" dependencies: From 3a3ce88fe55b09269063f932b543be448b068762 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 22 Jun 2026 10:57:34 +0300 Subject: [PATCH 6/6] fix(runners): accept multiple job statuses in list_jobs filter GitLab's jobs(statuses:) argument is [CiJobStatus!], so it natively filters by more than one status. The list_jobs schema now takes an array of statuses instead of a single value, and the handler forwards it straight through. Callers can request e.g. ["FAILED", "CANCELED"] in one query. --- .../src/entities/runners/registry.ts | 2 +- .../src/entities/runners/schema-readonly.ts | 32 ++++++++++--------- .../unit/entities/runners/registry.test.ts | 15 ++++++--- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/packages/gitlab-mcp/src/entities/runners/registry.ts b/packages/gitlab-mcp/src/entities/runners/registry.ts index 7047c249..3e65d06a 100644 --- a/packages/gitlab-mcp/src/entities/runners/registry.ts +++ b/packages/gitlab-mcp/src/entities/runners/registry.ts @@ -164,7 +164,7 @@ export const runnersToolRegistry: ToolRegistry = new Map { it('list_jobs queries the runner jobs connection', async () => { mockClient.request.mockResolvedValueOnce({ runner: { id: RUNNER_GID, jobs: { nodes: [] } } }); - await browse().handler({ action: 'list_jobs', runner_id: 7, statuses: 'FAILED' }); + await browse().handler({ + action: 'list_jobs', + runner_id: 7, + statuses: ['FAILED', 'CANCELED'], + }); const [doc, vars] = mockClient.request.mock.calls[0]; expect(doc).toBe(LIST_RUNNER_JOBS); - // GitLab's jobs(statuses:) argument is [CiJobStatus!], so a single status - // filter must be wrapped in a list. Sending the bare enum makes GitLab reject - // the query with "List dimension mismatch on variable $statuses". - expect(vars).toMatchObject({ id: RUNNER_GID, statuses: ['FAILED'] }); + // GitLab's jobs(statuses:) argument is [CiJobStatus!]; the schema takes a + // list so callers can filter by several statuses at once. Declaring the + // variable as a bare enum made GitLab reject the query with + // "List dimension mismatch on variable $statuses". + expect(vars).toMatchObject({ id: RUNNER_GID, statuses: ['FAILED', 'CANCELED'] }); }); it('list_jobs throws when the runner is missing', async () => {