@zereight/mcp-gitlab 2.1.63 → 2.1.64
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 +1 -1
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/build/index.js +84 -215
- package/build/schemas.js +2 -2
- package/build/scripts/generate-tool-coverage.js +58 -0
- package/build/scripts/generate-tool-docs.js +3 -0
- package/build/scripts/tool-coverage/coverage.js +339 -0
- package/build/scripts/tool-coverage/coverage.test.js +142 -0
- package/build/test/test-blank-filters.js +156 -0
- package/build/test/test-empty-toolsets.js +535 -0
- package/build/test/test-list-issues.js +47 -0
- package/build/test/test-list-merge-requests.js +76 -0
- package/build/test/test-list-milestones.js +145 -0
- package/build/test/utils/tool-args.test.js +57 -1
- package/build/utils/tool-args.js +35 -0
- package/package.json +2 -1
package/README.ko.md
CHANGED
|
@@ -110,7 +110,7 @@ command = lib.getExe inputs.gitlab-mcp.packages.${system}.default;
|
|
|
110
110
|
|
|
111
111
|
예시는 기존 `mcp-gitlab`보다 충돌 가능성이 낮은 `zereight-mcp-gitlab` 별칭을 사용합니다. MCP 클라이언트가 찾지 못하면 `which zereight-mcp-gitlab`의 절대 경로를 사용하세요.
|
|
112
112
|
|
|
113
|
-
전역 설치를 쓰지 않으려면 `npx -y @zereight/mcp-gitlab@2.1.
|
|
113
|
+
전역 설치를 쓰지 않으려면 `npx -y @zereight/mcp-gitlab@2.1.63`처럼 직전 안정 버전(문서가 권장하는 버전)으로 고정하세요. 항상 최신 버전을 원하면 `npx -y @zereight/mcp-gitlab@latest`를 사용하세요. 새 버전이 나오면 서버가 시작 시 stderr로 알려줍니다(`GITLAB_DISABLE_VERSION_CHECK=true`로 비활성화 가능).
|
|
114
114
|
|
|
115
115
|
#### CLI 인자 사용하기(환경 변수 문제가 있는 클라이언트용)
|
|
116
116
|
|
package/README.md
CHANGED
|
@@ -115,7 +115,7 @@ The store path is pinned by your lock file; update it with `nix flake update git
|
|
|
115
115
|
|
|
116
116
|
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`.
|
|
117
117
|
|
|
118
|
-
No global install? Pin `npx` to the previous stable release (the version these docs recommend), for example `npx -y @zereight/mcp-gitlab@2.1.
|
|
118
|
+
No global install? Pin `npx` to the previous stable release (the version these docs recommend), for example `npx -y @zereight/mcp-gitlab@2.1.63`. 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`).
|
|
119
119
|
|
|
120
120
|
#### Using CLI Arguments (for clients with env var issues)
|
|
121
121
|
|
package/README.zh-CN.md
CHANGED
|
@@ -110,7 +110,7 @@ command = lib.getExe inputs.gitlab-mcp.packages.${system}.default;
|
|
|
110
110
|
|
|
111
111
|
示例使用 `zereight-mcp-gitlab`,这是比旧的 `mcp-gitlab` 更不容易冲突的别名。如果 MCP 客户端找不到它,请使用 `which zereight-mcp-gitlab` 输出的绝对路径。
|
|
112
112
|
|
|
113
|
-
如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.1.
|
|
113
|
+
如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.1.63`。如果始终想使用最新版本,请改用 `npx -y @zereight/mcp-gitlab@latest`。有新版本发布时,服务器会在启动时通过 stderr 提示(可用 `GITLAB_DISABLE_VERSION_CHECK=true` 关闭)。
|
|
114
114
|
|
|
115
115
|
#### 使用 CLI 参数(适用于环境变量有问题的客户端)
|
|
116
116
|
|
package/build/index.js
CHANGED
|
@@ -100,7 +100,7 @@ import { checkForNewVersion } from "./utils/version-check.js";
|
|
|
100
100
|
import { assertGitLabVersionAtLeast } from "./utils/gitlab-version-gate.js";
|
|
101
101
|
import { parseGitLabVersionApiResponse, } from "./utils/gitlab-instance-version.js";
|
|
102
102
|
import { buildBulkPublishDraftNotesBody, needsGitLab19_2BulkPublish, } from "./utils/bulk-publish-options.js";
|
|
103
|
-
import { cleanMutuallyExclusiveIdUsernameOptions, LIST_MERGE_REQUESTS_ID_USERNAME_PAIRS, sanitizeToolArguments, } from "./utils/tool-args.js";
|
|
103
|
+
import { appendFilterParam, appendFilterParams, cleanMutuallyExclusiveIdUsernameOptions, dropBlankArrayEntries, isBlankFilterValue, LIST_MERGE_REQUESTS_ID_USERNAME_PAIRS, sanitizeToolArguments, } from "./utils/tool-args.js";
|
|
104
104
|
import { parseSearchReplaceBlocks, applySearchReplace, applyUnifiedDiff, } from "./utils/patch-helper.js";
|
|
105
105
|
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
|
|
106
106
|
import { GitLabClientPool } from "./gitlab-client-pool.js";
|
|
@@ -1506,23 +1506,26 @@ async function listIssues(projectId, options = {}) {
|
|
|
1506
1506
|
}
|
|
1507
1507
|
// Add all query parameters
|
|
1508
1508
|
Object.entries(options).forEach(([key, value]) => {
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
url.searchParams.append(`${key}[]`,
|
|
1520
|
-
}
|
|
1509
|
+
// Drop blank entries inside arrays so ["", "bug"] behaves like the scalar guard below
|
|
1510
|
+
const normalized = dropBlankArrayEntries(value);
|
|
1511
|
+
if (isBlankFilterValue(normalized)) {
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
const keys = ["labels", "assignee_username"];
|
|
1515
|
+
if (keys.includes(key)) {
|
|
1516
|
+
if (Array.isArray(normalized)) {
|
|
1517
|
+
// Handle array of labels
|
|
1518
|
+
normalized.forEach(label => {
|
|
1519
|
+
url.searchParams.append(`${key}[]`, String(label));
|
|
1520
|
+
});
|
|
1521
1521
|
}
|
|
1522
1522
|
else {
|
|
1523
|
-
url.searchParams.append(key
|
|
1523
|
+
url.searchParams.append(`${key}[]`, String(normalized));
|
|
1524
1524
|
}
|
|
1525
1525
|
}
|
|
1526
|
+
else {
|
|
1527
|
+
url.searchParams.append(key, String(normalized));
|
|
1528
|
+
}
|
|
1526
1529
|
});
|
|
1527
1530
|
const response = await fetch(url.toString(), {
|
|
1528
1531
|
...getFetchConfig(),
|
|
@@ -1533,11 +1536,7 @@ async function listIssues(projectId, options = {}) {
|
|
|
1533
1536
|
}
|
|
1534
1537
|
async function listTodos(options = {}) {
|
|
1535
1538
|
const url = new URL(`${getEffectiveApiUrl()}/todos`);
|
|
1536
|
-
|
|
1537
|
-
if (value !== undefined) {
|
|
1538
|
-
url.searchParams.append(key, String(value));
|
|
1539
|
-
}
|
|
1540
|
-
});
|
|
1539
|
+
appendFilterParams(url.searchParams, options);
|
|
1541
1540
|
const response = await fetch(url.toString(), {
|
|
1542
1541
|
...getFetchConfig(),
|
|
1543
1542
|
});
|
|
@@ -1566,20 +1565,22 @@ async function markAllTodosDone() {
|
|
|
1566
1565
|
*/
|
|
1567
1566
|
function appendMergeRequestFilters(url, options) {
|
|
1568
1567
|
Object.entries(options).forEach(([key, value]) => {
|
|
1569
|
-
|
|
1568
|
+
// Drop blank entries inside arrays so ["", "bug"] behaves like the scalar guard below
|
|
1569
|
+
const normalized = dropBlankArrayEntries(value);
|
|
1570
|
+
if (isBlankFilterValue(normalized)) {
|
|
1570
1571
|
return;
|
|
1571
1572
|
}
|
|
1572
|
-
if (key === "labels" && Array.isArray(
|
|
1573
|
-
url.searchParams.append(key,
|
|
1573
|
+
if (key === "labels" && Array.isArray(normalized)) {
|
|
1574
|
+
url.searchParams.append(key, normalized.join(","));
|
|
1574
1575
|
}
|
|
1575
|
-
else if (key === "approved_by_usernames" && Array.isArray(
|
|
1576
|
+
else if (key === "approved_by_usernames" && Array.isArray(normalized)) {
|
|
1576
1577
|
// GitLab expects array-bracket form: approved_by_usernames[]=alice&approved_by_usernames[]=bob
|
|
1577
|
-
for (const v of
|
|
1578
|
+
for (const v of normalized) {
|
|
1578
1579
|
url.searchParams.append(`${key}[]`, String(v));
|
|
1579
1580
|
}
|
|
1580
1581
|
}
|
|
1581
1582
|
else {
|
|
1582
|
-
url.searchParams.append(key, String(
|
|
1583
|
+
url.searchParams.append(key, String(normalized));
|
|
1583
1584
|
}
|
|
1584
1585
|
});
|
|
1585
1586
|
}
|
|
@@ -3800,11 +3801,7 @@ async function getMergeRequestSourceCommitCount(projectId, mergeRequestIid) {
|
|
|
3800
3801
|
async function listMergeRequestPipelines(projectId, mergeRequestIid, options = {}) {
|
|
3801
3802
|
projectId = decodeURIComponent(projectId);
|
|
3802
3803
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/pipelines`);
|
|
3803
|
-
|
|
3804
|
-
if (value !== undefined) {
|
|
3805
|
-
url.searchParams.append(key, value.toString());
|
|
3806
|
-
}
|
|
3807
|
-
});
|
|
3804
|
+
appendFilterParams(url.searchParams, options);
|
|
3808
3805
|
const response = await fetch(url.toString(), {
|
|
3809
3806
|
...getFetchConfig(),
|
|
3810
3807
|
});
|
|
@@ -4734,16 +4731,7 @@ async function getMergeRequestVersion(projectId, mergeRequestIid, versionId, uni
|
|
|
4734
4731
|
async function listProjects(options = {}) {
|
|
4735
4732
|
// Construct the query parameters
|
|
4736
4733
|
const params = new URLSearchParams();
|
|
4737
|
-
|
|
4738
|
-
if (value !== undefined && value !== null) {
|
|
4739
|
-
if (typeof value === "boolean") {
|
|
4740
|
-
params.append(key, value ? "true" : "false");
|
|
4741
|
-
}
|
|
4742
|
-
else {
|
|
4743
|
-
params.append(key, String(value));
|
|
4744
|
-
}
|
|
4745
|
-
}
|
|
4746
|
-
}
|
|
4734
|
+
appendFilterParams(params, options);
|
|
4747
4735
|
// Make the API request
|
|
4748
4736
|
const response = await fetch(`${getEffectiveApiUrl()}/projects?${params.toString()}`, {
|
|
4749
4737
|
...getFetchConfig(),
|
|
@@ -4766,16 +4754,7 @@ async function listLabels(projectId, options = {}) {
|
|
|
4766
4754
|
// Construct the URL with project path
|
|
4767
4755
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/labels`);
|
|
4768
4756
|
// Add query parameters
|
|
4769
|
-
|
|
4770
|
-
if (value !== undefined) {
|
|
4771
|
-
if (typeof value === "boolean") {
|
|
4772
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
4773
|
-
}
|
|
4774
|
-
else {
|
|
4775
|
-
url.searchParams.append(key, String(value));
|
|
4776
|
-
}
|
|
4777
|
-
}
|
|
4778
|
-
});
|
|
4757
|
+
appendFilterParams(url.searchParams, options);
|
|
4779
4758
|
// Make the API request
|
|
4780
4759
|
const response = await fetch(url.toString(), {
|
|
4781
4760
|
...getFetchConfig(),
|
|
@@ -5232,11 +5211,7 @@ async function listPipelines(projectId, options = {}) {
|
|
|
5232
5211
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5233
5212
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines`);
|
|
5234
5213
|
// Add all query parameters
|
|
5235
|
-
|
|
5236
|
-
if (value !== undefined) {
|
|
5237
|
-
url.searchParams.append(key, value.toString());
|
|
5238
|
-
}
|
|
5239
|
-
});
|
|
5214
|
+
appendFilterParams(url.searchParams, options);
|
|
5240
5215
|
const response = await fetch(url.toString(), {
|
|
5241
5216
|
...getFetchConfig(),
|
|
5242
5217
|
});
|
|
@@ -5310,11 +5285,7 @@ async function updatePipelineMetadata(projectId, pipelineId, name) {
|
|
|
5310
5285
|
async function listDeployments(projectId, options = {}) {
|
|
5311
5286
|
projectId = decodeURIComponent(projectId);
|
|
5312
5287
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/deployments`);
|
|
5313
|
-
|
|
5314
|
-
if (value !== undefined) {
|
|
5315
|
-
url.searchParams.append(key, value.toString());
|
|
5316
|
-
}
|
|
5317
|
-
});
|
|
5288
|
+
appendFilterParams(url.searchParams, options);
|
|
5318
5289
|
const response = await fetch(url.toString(), {
|
|
5319
5290
|
...getFetchConfig(),
|
|
5320
5291
|
});
|
|
@@ -5366,11 +5337,7 @@ async function deploymentRequest(projectId, path = "", method = "GET", body, que
|
|
|
5366
5337
|
async function listEnvironments(projectId, options = {}) {
|
|
5367
5338
|
projectId = decodeURIComponent(projectId);
|
|
5368
5339
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/environments`);
|
|
5369
|
-
|
|
5370
|
-
if (value !== undefined) {
|
|
5371
|
-
url.searchParams.append(key, value.toString());
|
|
5372
|
-
}
|
|
5373
|
-
});
|
|
5340
|
+
appendFilterParams(url.searchParams, options);
|
|
5374
5341
|
const response = await fetch(url.toString(), {
|
|
5375
5342
|
...getFetchConfig(),
|
|
5376
5343
|
});
|
|
@@ -5440,16 +5407,7 @@ async function listPipelineJobs(projectId, pipelineId, options = {}) {
|
|
|
5440
5407
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5441
5408
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/jobs`);
|
|
5442
5409
|
// Add all query parameters
|
|
5443
|
-
|
|
5444
|
-
if (value !== undefined) {
|
|
5445
|
-
if (typeof value === "boolean") {
|
|
5446
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
5447
|
-
}
|
|
5448
|
-
else {
|
|
5449
|
-
url.searchParams.append(key, value.toString());
|
|
5450
|
-
}
|
|
5451
|
-
}
|
|
5452
|
-
});
|
|
5410
|
+
appendFilterParams(url.searchParams, options);
|
|
5453
5411
|
const response = await fetch(url.toString(), {
|
|
5454
5412
|
...getFetchConfig(),
|
|
5455
5413
|
});
|
|
@@ -5472,16 +5430,7 @@ async function listPipelineTriggerJobs(projectId, pipelineId, options = {}) {
|
|
|
5472
5430
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5473
5431
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/bridges`);
|
|
5474
5432
|
// Add all query parameters
|
|
5475
|
-
|
|
5476
|
-
if (value !== undefined) {
|
|
5477
|
-
if (typeof value === "boolean") {
|
|
5478
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
5479
|
-
}
|
|
5480
|
-
else {
|
|
5481
|
-
url.searchParams.append(key, value.toString());
|
|
5482
|
-
}
|
|
5483
|
-
}
|
|
5484
|
-
});
|
|
5433
|
+
appendFilterParams(url.searchParams, options);
|
|
5485
5434
|
const response = await fetch(url.toString(), {
|
|
5486
5435
|
...getFetchConfig(),
|
|
5487
5436
|
});
|
|
@@ -5566,16 +5515,7 @@ async function validateCiLint(projectId, options) {
|
|
|
5566
5515
|
async function validateProjectCiLint(projectId, options) {
|
|
5567
5516
|
projectId = decodeURIComponent(projectId);
|
|
5568
5517
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/ci/lint`);
|
|
5569
|
-
|
|
5570
|
-
if (value !== undefined) {
|
|
5571
|
-
if (typeof value === "boolean") {
|
|
5572
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
5573
|
-
}
|
|
5574
|
-
else {
|
|
5575
|
-
url.searchParams.append(key, value.toString());
|
|
5576
|
-
}
|
|
5577
|
-
}
|
|
5578
|
-
});
|
|
5518
|
+
appendFilterParams(url.searchParams, options);
|
|
5579
5519
|
const response = await fetch(url.toString(), {
|
|
5580
5520
|
...getFetchConfig(),
|
|
5581
5521
|
});
|
|
@@ -5594,16 +5534,7 @@ async function validateProjectCiLint(projectId, options) {
|
|
|
5594
5534
|
async function listJobArtifacts(projectId, jobId, options = {}) {
|
|
5595
5535
|
projectId = decodeURIComponent(projectId);
|
|
5596
5536
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/artifacts/tree`);
|
|
5597
|
-
|
|
5598
|
-
if (value !== undefined) {
|
|
5599
|
-
if (typeof value === "boolean") {
|
|
5600
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
5601
|
-
}
|
|
5602
|
-
else {
|
|
5603
|
-
url.searchParams.append(key, value.toString());
|
|
5604
|
-
}
|
|
5605
|
-
}
|
|
5606
|
-
});
|
|
5537
|
+
appendFilterParams(url.searchParams, options);
|
|
5607
5538
|
const response = await fetch(url.toString(), {
|
|
5608
5539
|
...getFetchConfig(),
|
|
5609
5540
|
});
|
|
@@ -5740,11 +5671,7 @@ async function cancelPipeline(projectId, pipelineId) {
|
|
|
5740
5671
|
async function listPipelineSchedules(projectId, options = {}) {
|
|
5741
5672
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5742
5673
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules`);
|
|
5743
|
-
|
|
5744
|
-
if (value !== undefined) {
|
|
5745
|
-
url.searchParams.append(key, value.toString());
|
|
5746
|
-
}
|
|
5747
|
-
});
|
|
5674
|
+
appendFilterParams(url.searchParams, options);
|
|
5748
5675
|
const response = await fetch(url.toString(), {
|
|
5749
5676
|
...getFetchConfig(),
|
|
5750
5677
|
});
|
|
@@ -5780,11 +5707,7 @@ async function getPipelineSchedule(projectId, pipelineScheduleId) {
|
|
|
5780
5707
|
async function listPipelineSchedulePipelines(projectId, pipelineScheduleId, options = {}) {
|
|
5781
5708
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5782
5709
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/pipelines`);
|
|
5783
|
-
|
|
5784
|
-
if (value !== undefined) {
|
|
5785
|
-
url.searchParams.append(key, value.toString());
|
|
5786
|
-
}
|
|
5787
|
-
});
|
|
5710
|
+
appendFilterParams(url.searchParams, options);
|
|
5788
5711
|
const response = await fetch(url.toString(), {
|
|
5789
5712
|
...getFetchConfig(),
|
|
5790
5713
|
});
|
|
@@ -6107,15 +6030,18 @@ async function listProjectMilestones(projectId, options) {
|
|
|
6107
6030
|
projectId = decodeURIComponent(projectId);
|
|
6108
6031
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones`);
|
|
6109
6032
|
Object.entries(options).forEach(([key, value]) => {
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
url.searchParams.append(
|
|
6118
|
-
}
|
|
6033
|
+
// Drop blank entries inside arrays so they behave like the scalar blank guard below
|
|
6034
|
+
const normalized = dropBlankArrayEntries(value);
|
|
6035
|
+
if (isBlankFilterValue(normalized)) {
|
|
6036
|
+
return;
|
|
6037
|
+
}
|
|
6038
|
+
if (key === "iids" && Array.isArray(normalized)) {
|
|
6039
|
+
normalized.forEach(iid => {
|
|
6040
|
+
url.searchParams.append("iids[]", String(iid));
|
|
6041
|
+
});
|
|
6042
|
+
}
|
|
6043
|
+
else {
|
|
6044
|
+
url.searchParams.append(key, String(normalized));
|
|
6119
6045
|
}
|
|
6120
6046
|
});
|
|
6121
6047
|
const response = await fetch(url.toString(), {
|
|
@@ -6202,11 +6128,7 @@ async function deleteProjectMilestone(projectId, milestoneId) {
|
|
|
6202
6128
|
async function getMilestoneIssues(projectId, milestoneId, options = {}) {
|
|
6203
6129
|
projectId = decodeURIComponent(projectId);
|
|
6204
6130
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/issues`);
|
|
6205
|
-
|
|
6206
|
-
if (value !== undefined) {
|
|
6207
|
-
url.searchParams.append(key, String(value));
|
|
6208
|
-
}
|
|
6209
|
-
});
|
|
6131
|
+
appendFilterParams(url.searchParams, options);
|
|
6210
6132
|
const response = await fetch(url.toString(), {
|
|
6211
6133
|
...getFetchConfig(),
|
|
6212
6134
|
});
|
|
@@ -6223,11 +6145,7 @@ async function getMilestoneIssues(projectId, milestoneId, options = {}) {
|
|
|
6223
6145
|
async function getMilestoneMergeRequests(projectId, milestoneId, options = {}) {
|
|
6224
6146
|
projectId = decodeURIComponent(projectId);
|
|
6225
6147
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/merge_requests`);
|
|
6226
|
-
|
|
6227
|
-
if (value !== undefined) {
|
|
6228
|
-
url.searchParams.append(key, String(value));
|
|
6229
|
-
}
|
|
6230
|
-
});
|
|
6148
|
+
appendFilterParams(url.searchParams, options);
|
|
6231
6149
|
const response = await fetch(url.toString(), {
|
|
6232
6150
|
...getFetchConfig(),
|
|
6233
6151
|
});
|
|
@@ -6261,11 +6179,7 @@ async function promoteProjectMilestone(projectId, milestoneId) {
|
|
|
6261
6179
|
async function getMilestoneBurndownEvents(projectId, milestoneId, options = {}) {
|
|
6262
6180
|
projectId = decodeURIComponent(projectId);
|
|
6263
6181
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/burndown_events`);
|
|
6264
|
-
|
|
6265
|
-
if (value !== undefined) {
|
|
6266
|
-
url.searchParams.append(key, String(value));
|
|
6267
|
-
}
|
|
6268
|
-
});
|
|
6182
|
+
appendFilterParams(url.searchParams, options);
|
|
6269
6183
|
const response = await fetch(url.toString(), {
|
|
6270
6184
|
...getFetchConfig(),
|
|
6271
6185
|
});
|
|
@@ -6280,15 +6194,18 @@ async function listGroupMilestones(groupId, options) {
|
|
|
6280
6194
|
groupId = decodeURIComponent(groupId);
|
|
6281
6195
|
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones`);
|
|
6282
6196
|
Object.entries(options).forEach(([key, value]) => {
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
url.searchParams.append(
|
|
6291
|
-
}
|
|
6197
|
+
// Drop blank entries inside arrays so they behave like the scalar blank guard below
|
|
6198
|
+
const normalized = dropBlankArrayEntries(value);
|
|
6199
|
+
if (isBlankFilterValue(normalized)) {
|
|
6200
|
+
return;
|
|
6201
|
+
}
|
|
6202
|
+
if (key === "iids" && Array.isArray(normalized)) {
|
|
6203
|
+
normalized.forEach(iid => {
|
|
6204
|
+
url.searchParams.append("iids[]", String(iid));
|
|
6205
|
+
});
|
|
6206
|
+
}
|
|
6207
|
+
else {
|
|
6208
|
+
url.searchParams.append(key, String(normalized));
|
|
6292
6209
|
}
|
|
6293
6210
|
});
|
|
6294
6211
|
const response = await fetch(url.toString(), {
|
|
@@ -6359,11 +6276,7 @@ async function deleteGroupMilestone(groupId, milestoneId) {
|
|
|
6359
6276
|
async function getGroupMilestoneIssues(groupId, milestoneId, options = {}) {
|
|
6360
6277
|
groupId = decodeURIComponent(groupId);
|
|
6361
6278
|
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/issues`);
|
|
6362
|
-
|
|
6363
|
-
if (value !== undefined) {
|
|
6364
|
-
url.searchParams.append(key, String(value));
|
|
6365
|
-
}
|
|
6366
|
-
});
|
|
6279
|
+
appendFilterParams(url.searchParams, options);
|
|
6367
6280
|
const response = await fetch(url.toString(), {
|
|
6368
6281
|
...getFetchConfig(),
|
|
6369
6282
|
});
|
|
@@ -6377,11 +6290,7 @@ async function getGroupMilestoneIssues(groupId, milestoneId, options = {}) {
|
|
|
6377
6290
|
async function getGroupMilestoneMergeRequests(groupId, milestoneId, options = {}) {
|
|
6378
6291
|
groupId = decodeURIComponent(groupId);
|
|
6379
6292
|
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/merge_requests`);
|
|
6380
|
-
|
|
6381
|
-
if (value !== undefined) {
|
|
6382
|
-
url.searchParams.append(key, String(value));
|
|
6383
|
-
}
|
|
6384
|
-
});
|
|
6293
|
+
appendFilterParams(url.searchParams, options);
|
|
6385
6294
|
const response = await fetch(url.toString(), {
|
|
6386
6295
|
...getFetchConfig(),
|
|
6387
6296
|
});
|
|
@@ -6395,11 +6304,7 @@ async function getGroupMilestoneMergeRequests(groupId, milestoneId, options = {}
|
|
|
6395
6304
|
async function getGroupMilestoneBurndownEvents(groupId, milestoneId, options = {}) {
|
|
6396
6305
|
groupId = decodeURIComponent(groupId);
|
|
6397
6306
|
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/burndown_events`);
|
|
6398
|
-
|
|
6399
|
-
if (value !== undefined) {
|
|
6400
|
-
url.searchParams.append(key, String(value));
|
|
6401
|
-
}
|
|
6402
|
-
});
|
|
6307
|
+
appendFilterParams(url.searchParams, options);
|
|
6403
6308
|
const response = await fetch(url.toString(), {
|
|
6404
6309
|
...getFetchConfig(),
|
|
6405
6310
|
});
|
|
@@ -6598,11 +6503,7 @@ async function getFileBlame(projectId, options) {
|
|
|
6598
6503
|
async function listCommitStatuses(projectId, sha, options = {}) {
|
|
6599
6504
|
projectId = decodeURIComponent(projectId);
|
|
6600
6505
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits/${encodeURIComponent(sha)}/statuses`);
|
|
6601
|
-
|
|
6602
|
-
if (value !== undefined) {
|
|
6603
|
-
url.searchParams.append(key, value.toString());
|
|
6604
|
-
}
|
|
6605
|
-
});
|
|
6506
|
+
appendFilterParams(url.searchParams, options);
|
|
6606
6507
|
const response = await fetch(url.toString(), {
|
|
6607
6508
|
...getFetchConfig(),
|
|
6608
6509
|
});
|
|
@@ -6624,11 +6525,7 @@ async function createCommitStatus(projectId, sha, options) {
|
|
|
6624
6525
|
}
|
|
6625
6526
|
projectId = decodeURIComponent(projectId);
|
|
6626
6527
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/statuses/${encodeURIComponent(sha)}`);
|
|
6627
|
-
|
|
6628
|
-
if (value !== undefined) {
|
|
6629
|
-
url.searchParams.append(key, value.toString());
|
|
6630
|
-
}
|
|
6631
|
-
});
|
|
6528
|
+
appendFilterParams(url.searchParams, options);
|
|
6632
6529
|
const response = await fetch(url.toString(), {
|
|
6633
6530
|
...getFetchConfig(),
|
|
6634
6531
|
method: "POST",
|
|
@@ -6806,19 +6703,13 @@ async function listProjectVariables(projectId, options = {}) {
|
|
|
6806
6703
|
projectId = decodeURIComponent(projectId);
|
|
6807
6704
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/variables`);
|
|
6808
6705
|
Object.entries(options).forEach(([key, value]) => {
|
|
6809
|
-
if (value === undefined)
|
|
6810
|
-
return;
|
|
6811
6706
|
if (key === "filter" && typeof value === "object" && value !== null) {
|
|
6812
|
-
Object.entries(value).forEach(([
|
|
6813
|
-
url.searchParams
|
|
6707
|
+
Object.entries(value).forEach(([filterKey, filterValue]) => {
|
|
6708
|
+
appendFilterParam(url.searchParams, `filter[${filterKey}]`, filterValue);
|
|
6814
6709
|
});
|
|
6710
|
+
return;
|
|
6815
6711
|
}
|
|
6816
|
-
|
|
6817
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
6818
|
-
}
|
|
6819
|
-
else {
|
|
6820
|
-
url.searchParams.append(key, String(value));
|
|
6821
|
-
}
|
|
6712
|
+
appendFilterParam(url.searchParams, key, value);
|
|
6822
6713
|
});
|
|
6823
6714
|
const response = await fetch(url.toString(), getFetchConfig());
|
|
6824
6715
|
await handleGitLabError(response);
|
|
@@ -6872,19 +6763,13 @@ async function listGroupVariables(groupId, options = {}) {
|
|
|
6872
6763
|
const encoded = encodeURIComponent(decodeURIComponent(groupId));
|
|
6873
6764
|
const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables`);
|
|
6874
6765
|
Object.entries(options).forEach(([key, value]) => {
|
|
6875
|
-
if (value === undefined)
|
|
6876
|
-
return;
|
|
6877
6766
|
if (key === "filter" && typeof value === "object" && value !== null) {
|
|
6878
|
-
Object.entries(value).forEach(([
|
|
6879
|
-
url.searchParams
|
|
6767
|
+
Object.entries(value).forEach(([filterKey, filterValue]) => {
|
|
6768
|
+
appendFilterParam(url.searchParams, `filter[${filterKey}]`, filterValue);
|
|
6880
6769
|
});
|
|
6770
|
+
return;
|
|
6881
6771
|
}
|
|
6882
|
-
|
|
6883
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
6884
|
-
}
|
|
6885
|
-
else {
|
|
6886
|
-
url.searchParams.append(key, String(value));
|
|
6887
|
-
}
|
|
6772
|
+
appendFilterParam(url.searchParams, key, value);
|
|
6888
6773
|
});
|
|
6889
6774
|
const response = await fetch(url.toString(), getFetchConfig());
|
|
6890
6775
|
await handleGitLabError(response);
|
|
@@ -7431,11 +7316,7 @@ async function downloadAttachment(projectId, secret, filename, localPath) {
|
|
|
7431
7316
|
async function listEvents(options = {}) {
|
|
7432
7317
|
const url = new URL(`${getEffectiveApiUrl()}/events`);
|
|
7433
7318
|
// Add all query parameters
|
|
7434
|
-
|
|
7435
|
-
if (value !== undefined) {
|
|
7436
|
-
url.searchParams.append(key, value.toString());
|
|
7437
|
-
}
|
|
7438
|
-
});
|
|
7319
|
+
appendFilterParams(url.searchParams, options);
|
|
7439
7320
|
const response = await fetch(url.toString(), { ...getFetchConfig() });
|
|
7440
7321
|
if (!response.ok) {
|
|
7441
7322
|
await handleGitLabError(response);
|
|
@@ -7453,11 +7334,7 @@ async function getProjectEvents(projectId, options = {}) {
|
|
|
7453
7334
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7454
7335
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/events`);
|
|
7455
7336
|
// Add all query parameters
|
|
7456
|
-
|
|
7457
|
-
if (value !== undefined) {
|
|
7458
|
-
url.searchParams.append(key, value.toString());
|
|
7459
|
-
}
|
|
7460
|
-
});
|
|
7337
|
+
appendFilterParams(url.searchParams, options);
|
|
7461
7338
|
const response = await fetch(url.toString(), { ...getFetchConfig() });
|
|
7462
7339
|
if (!response.ok) {
|
|
7463
7340
|
await handleGitLabError(response);
|
|
@@ -7476,11 +7353,7 @@ async function listReleases(projectId, options = {}) {
|
|
|
7476
7353
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7477
7354
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/releases`);
|
|
7478
7355
|
// Add query parameters
|
|
7479
|
-
|
|
7480
|
-
if (value !== undefined) {
|
|
7481
|
-
url.searchParams.append(key, value.toString());
|
|
7482
|
-
}
|
|
7483
|
-
});
|
|
7356
|
+
appendFilterParams(url.searchParams, options);
|
|
7484
7357
|
const response = await fetch(url.toString(), {
|
|
7485
7358
|
...getFetchConfig(),
|
|
7486
7359
|
});
|
|
@@ -7603,11 +7476,7 @@ async function downloadReleaseAsset(projectId, tagName, directAssetPath) {
|
|
|
7603
7476
|
async function listTags(projectId, options = {}) {
|
|
7604
7477
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7605
7478
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/repository/tags`);
|
|
7606
|
-
|
|
7607
|
-
if (value !== undefined) {
|
|
7608
|
-
url.searchParams.append(key, value.toString());
|
|
7609
|
-
}
|
|
7610
|
-
});
|
|
7479
|
+
appendFilterParams(url.searchParams, options);
|
|
7611
7480
|
const response = await fetch(url.toString(), {
|
|
7612
7481
|
...getFetchConfig(),
|
|
7613
7482
|
});
|
|
@@ -8847,8 +8716,8 @@ async function handleToolCall(params) {
|
|
|
8847
8716
|
};
|
|
8848
8717
|
}
|
|
8849
8718
|
case "list_labels": {
|
|
8850
|
-
const
|
|
8851
|
-
const labels = await listLabels(
|
|
8719
|
+
const { project_id, ...options } = ListLabelsSchema.parse(params.arguments);
|
|
8720
|
+
const labels = await listLabels(project_id, options);
|
|
8852
8721
|
return {
|
|
8853
8722
|
content: [{ type: "text", text: JSON.stringify(labels) }],
|
|
8854
8723
|
};
|
package/build/schemas.js
CHANGED
|
@@ -3075,7 +3075,7 @@ export const ListProjectMilestonesSchema = ProjectParamsSchema.extend({
|
|
|
3075
3075
|
iids: z
|
|
3076
3076
|
.array(z.coerce.number())
|
|
3077
3077
|
.optional()
|
|
3078
|
-
.describe("Return only the milestones having the given iid"),
|
|
3078
|
+
.describe("Return only the milestones having the given iid. An empty array is treated as no filter and is left out of the request."),
|
|
3079
3079
|
state: z
|
|
3080
3080
|
.enum(["active", "closed"])
|
|
3081
3081
|
.optional()
|
|
@@ -3137,7 +3137,7 @@ export const ListGroupMilestonesSchema = z
|
|
|
3137
3137
|
iids: z
|
|
3138
3138
|
.array(z.coerce.number())
|
|
3139
3139
|
.optional()
|
|
3140
|
-
.describe("Return only the milestones having the given iid"),
|
|
3140
|
+
.describe("Return only the milestones having the given iid. An empty array is treated as no filter and is left out of the request."),
|
|
3141
3141
|
state: z
|
|
3142
3142
|
.enum(["active", "closed"])
|
|
3143
3143
|
.optional()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Generate docs/reference/tool-coverage.md from tools/registry.ts + test/ MCP calls.
|
|
4
|
+
*
|
|
5
|
+
* Detection rules live in ./tool-coverage/coverage.ts (AST-scoped): a tool is
|
|
6
|
+
* invoked when test source sends that name as a `callTool*` argument, as the
|
|
7
|
+
* `name` of a first-argument params object (`client.callTool({ name })`), or as
|
|
8
|
+
* the `name` of a `params` object inside a `tools/call` payload literal.
|
|
9
|
+
*
|
|
10
|
+
* Name-only mentions (toolset tables, description-quality) do not count.
|
|
11
|
+
* callTool(variable) loops are missed unless the name is also a literal in that call.
|
|
12
|
+
*
|
|
13
|
+
* Run with: npm run docs:tools
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { dirname, join, relative } from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
import { allTools, TOOLSET_DEFINITIONS } from "../tools/registry.js";
|
|
19
|
+
import { assertDetectorHealthy, buildCoverageMarkdown, collectInvocations, countInvoked, groupToolsByToolset, percent, SKIP_FILES, } from "./tool-coverage/coverage.js";
|
|
20
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const REPO_ROOT = join(__dirname, "..");
|
|
22
|
+
const TEST_DIR = join(REPO_ROOT, "test");
|
|
23
|
+
const OUT_PATH = join(REPO_ROOT, "docs", "reference", "tool-coverage.md");
|
|
24
|
+
function walkTestFiles(dir) {
|
|
25
|
+
const out = [];
|
|
26
|
+
for (const name of readdirSync(dir)) {
|
|
27
|
+
const full = join(dir, name);
|
|
28
|
+
const stat = statSync(full);
|
|
29
|
+
if (stat.isDirectory()) {
|
|
30
|
+
out.push(...walkTestFiles(full));
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (/\.(ts|js)$/.test(name) && !SKIP_FILES.has(name)) {
|
|
34
|
+
out.push(full);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
function repoPath(abs) {
|
|
40
|
+
return relative(REPO_ROOT, abs).split("\\").join("/");
|
|
41
|
+
}
|
|
42
|
+
function readScannedFiles() {
|
|
43
|
+
return walkTestFiles(TEST_DIR).map(abs => ({
|
|
44
|
+
path: repoPath(abs),
|
|
45
|
+
text: readFileSync(abs, "utf8"),
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
function main() {
|
|
49
|
+
const toolNames = allTools.map(tool => tool.name);
|
|
50
|
+
const toolNameSet = new Set(toolNames);
|
|
51
|
+
const hits = collectInvocations(readScannedFiles(), toolNameSet);
|
|
52
|
+
const invokedCount = countInvoked(toolNames, hits);
|
|
53
|
+
assertDetectorHealthy(hits, invokedCount);
|
|
54
|
+
const grouped = groupToolsByToolset(toolNames, TOOLSET_DEFINITIONS);
|
|
55
|
+
writeFileSync(OUT_PATH, buildCoverageMarkdown(toolNames, hits, grouped));
|
|
56
|
+
console.log(`generated docs/reference/tool-coverage.md — ${invokedCount}/${toolNames.length} (${percent(invokedCount, toolNames.length)})`);
|
|
57
|
+
}
|
|
58
|
+
main();
|