@zereight/mcp-gitlab 2.1.42 → 2.1.44
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 -3
- package/README.md +10 -4
- package/README.zh-CN.md +3 -3
- package/build/downloads/proxy.js +3 -2
- package/build/index.js +297 -50
- package/build/oauth-proxy.js +2 -2
- package/build/oauth.js +2 -5
- package/build/schemas.js +67 -0
- package/build/scripts/check-skill-sync.js +1 -0
- package/build/scripts/generate-tool-docs.js +6 -1
- package/build/test/dynamic-api-url-allowlist.test.js +18 -1
- package/build/test/test-health-check.js +106 -0
- package/build/test/test-toolset-filtering.js +2 -0
- package/build/test/test-vulnerabilities.js +399 -0
- package/build/test/utils/gitlab-instance-version.test.js +32 -0
- package/build/test/utils/mock-gitlab-server.js +8 -0
- package/build/tools/registry.js +35 -2
- package/build/utils/gitlab-instance-version.js +16 -0
- package/build/utils/logger.js +51 -0
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -80,7 +80,7 @@ npm install -g @zereight/mcp-gitlab
|
|
|
80
80
|
|
|
81
81
|
예시는 기존 `mcp-gitlab`보다 충돌 가능성이 낮은 `zereight-mcp-gitlab` 별칭을 사용합니다. MCP 클라이언트가 찾지 못하면 `which zereight-mcp-gitlab`의 절대 경로를 사용하세요.
|
|
82
82
|
|
|
83
|
-
전역 설치를 쓰지 않으려면 `npx -y @zereight/mcp-gitlab@2.1.
|
|
83
|
+
전역 설치를 쓰지 않으려면 `npx -y @zereight/mcp-gitlab@2.1.43`처럼 직전 안정 버전(문서가 권장하는 버전)으로 고정하세요. 항상 최신 버전을 원하면 `npx -y @zereight/mcp-gitlab@latest`를 사용하세요. 새 버전이 나오면 서버가 시작 시 stderr로 알려줍니다(`GITLAB_DISABLE_VERSION_CHECK=true`로 비활성화 가능).
|
|
84
84
|
|
|
85
85
|
#### CLI 인자 사용하기(환경 변수 문제가 있는 클라이언트용)
|
|
86
86
|
|
|
@@ -479,7 +479,7 @@ node build/index.js
|
|
|
479
479
|
AI 에이전트가 skill/instruction 로딩을 지원하는 경우(Claude Code, GitHub Copilot, Cursor 등), [`skills/gitlab-mcp/`](./skills/gitlab-mcp/)에 미리 작성된 skill 파일을 사용할 수 있습니다.
|
|
480
480
|
|
|
481
481
|
- **[SKILL.md](./skills/gitlab-mcp/SKILL.md)** — 도구셋 개요, 핵심 워크플로우, 파라미터 힌트를 담은 핵심 가이드
|
|
482
|
-
- **[reference/](./skills/gitlab-mcp/reference/)** — 코드 리뷰, 머지 리퀘스트, 이슈,
|
|
482
|
+
- **[reference/](./skills/gitlab-mcp/reference/)** — 코드 리뷰, 머지 리퀘스트, 이슈, 파이프라인, 취약점 트리아지용 상세 워크플로우 문서
|
|
483
483
|
|
|
484
484
|
`skills` CLI로 설치:
|
|
485
485
|
|
|
@@ -491,7 +491,7 @@ AI 클라이언트에 skill 디렉터리를 등록하면 전체 ListTools 응답
|
|
|
491
491
|
|
|
492
492
|
## 도구 🛠️
|
|
493
493
|
|
|
494
|
-
전체 도구 목록은 영어 README의 [Tools 섹션](./README.md#tools-%EF%B8%8F)을 참고하세요. 현재 서버는 머지 리퀘스트, 이슈, 파이프라인, 배포, 환경, 아티팩트, 마일스톤, 위키, 저장소, 릴리스, 사용자, 이벤트, work item, 웹훅, 코드 검색, GraphQL 실행 도구를 제공합니다.
|
|
494
|
+
전체 도구 목록은 영어 README의 [Tools 섹션](./README.md#tools-%EF%B8%8F)을 참고하세요. 현재 서버는 머지 리퀘스트, 이슈, 파이프라인, 배포, 환경, 아티팩트, 마일스톤, 위키, 저장소, 릴리스, 사용자, 이벤트, work item, 웹훅, 코드 검색, CI/CD 변수, dependency proxy, 취약점 트리아지, GraphQL 실행 도구를 제공합니다.
|
|
495
495
|
|
|
496
496
|
### Wiki 페이지 제목과 slug
|
|
497
497
|
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# GitLab MCP Server
|
|
2
2
|
|
|
3
|
+
[](https://mcptoplist.com/server/io.github.zereight%2Fgitlab-mcp)
|
|
4
|
+
|
|
3
5
|
[English](./README.md) | [한국어](./README.ko.md) | [简体中文](./README.zh-CN.md)
|
|
4
6
|
|
|
5
7
|
📖 **[Documentation →](https://zereight.github.io/gitlab-mcp/)** Setup guides, environment variables, and the full tool reference live on the hosted docs site.
|
|
@@ -80,7 +82,7 @@ npm install -g @zereight/mcp-gitlab
|
|
|
80
82
|
|
|
81
83
|
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`.
|
|
82
84
|
|
|
83
|
-
No global install? Pin `npx` to the previous stable release (the version these docs recommend), for example `npx -y @zereight/mcp-gitlab@2.1.
|
|
85
|
+
No global install? Pin `npx` to the previous stable release (the version these docs recommend), for example `npx -y @zereight/mcp-gitlab@2.1.43`. 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`).
|
|
84
86
|
|
|
85
87
|
#### Using CLI Arguments (for clients with env var issues)
|
|
86
88
|
|
|
@@ -523,7 +525,7 @@ No `headers` field is needed — Claude.ai obtains the token via OAuth automatic
|
|
|
523
525
|
Pre-built skill files are available in [`skills/gitlab-mcp/`](./skills/gitlab-mcp/) for AI agents that support skill/instruction loading (Claude Code, GitHub Copilot, Cursor, etc.).
|
|
524
526
|
|
|
525
527
|
- **[SKILL.md](./skills/gitlab-mcp/SKILL.md)** — Core guide (~800 tokens) with toolset overview, key workflows, and parameter hints
|
|
526
|
-
- **[reference/](./skills/gitlab-mcp/reference/)** — Detailed workflow docs for code review, merge requests, issues, and
|
|
528
|
+
- **[reference/](./skills/gitlab-mcp/reference/)** — Detailed workflow docs for code review, merge requests, issues, pipelines, and vulnerability triage
|
|
527
529
|
|
|
528
530
|
Install with the `skills` CLI:
|
|
529
531
|
|
|
@@ -698,7 +700,7 @@ Register the skill directory in your AI client to get optimal tool usage guidanc
|
|
|
698
700
|
156. `list_group_iterations` - List group iterations with filtering options
|
|
699
701
|
157. `upload_markdown` - Upload a file to a GitLab project for use in markdown content
|
|
700
702
|
158. `download_attachment` - Download an uploaded file from a GitLab project by secret and filename
|
|
701
|
-
159. `health_check` - Verify server status and authentication
|
|
703
|
+
159. `health_check` - Verify server status and authentication; when authenticated, reports GitLab instance version from `/api/v4/version` (`version`, `revision`, `enterprise`)
|
|
702
704
|
160. `list_events` - List all events for the currently authenticated user
|
|
703
705
|
161. `get_project_events` - List all visible events for a specified project
|
|
704
706
|
162. `list_releases` - List all releases for a project
|
|
@@ -751,7 +753,11 @@ Register the skill directory in your AI client to get optimal tool usage guidanc
|
|
|
751
753
|
209. `update_dependency_proxy_settings` - Update dependency proxy settings for a group (enable/disable, credentials for authenticated Docker Hub pulls)
|
|
752
754
|
210. `list_dependency_proxy_blobs` - List cached dependency proxy blobs for a group with cursor-based pagination
|
|
753
755
|
211. `purge_dependency_proxy_cache` - Schedule purge of all cached dependency proxy blobs for a group
|
|
754
|
-
212. `
|
|
756
|
+
212. `list_project_vulnerabilities` - List vulnerabilities for a project with optional state, severity, and report type filters (GraphQL-backed, cursor pagination)
|
|
757
|
+
213. `get_vulnerability` - Get full details of a specific vulnerability
|
|
758
|
+
214. `dismiss_vulnerability` - Dismiss a vulnerability with a reason (acceptable_risk, false_positive, used_in_tests, mitigating_control, not_applicable) and optional comment
|
|
759
|
+
215. `confirm_vulnerability` - Confirm a vulnerability as a real finding requiring remediation
|
|
760
|
+
216. `discover_tools` - Discover and activate additional tool categories for this session. Available categories: merge_requests, issues, repositories, branches, projects, labels, ci, groups, pipelines, milestones, wiki, releases, tags, users, workitems, webhooks, search, variables, dependency_proxy, vulnerabilities. Already-active categories are listed in the response.
|
|
755
761
|
|
|
756
762
|
<!-- TOOLS-END -->
|
|
757
763
|
|
package/README.zh-CN.md
CHANGED
|
@@ -80,7 +80,7 @@ npm install -g @zereight/mcp-gitlab
|
|
|
80
80
|
|
|
81
81
|
示例使用 `zereight-mcp-gitlab`,这是比旧的 `mcp-gitlab` 更不容易冲突的别名。如果 MCP 客户端找不到它,请使用 `which zereight-mcp-gitlab` 输出的绝对路径。
|
|
82
82
|
|
|
83
|
-
如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.1.
|
|
83
|
+
如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.1.43`。如果始终想使用最新版本,请改用 `npx -y @zereight/mcp-gitlab@latest`。有新版本发布时,服务器会在启动时通过 stderr 提示(可用 `GITLAB_DISABLE_VERSION_CHECK=true` 关闭)。
|
|
84
84
|
|
|
85
85
|
#### 使用 CLI 参数(适用于环境变量有问题的客户端)
|
|
86
86
|
|
|
@@ -479,7 +479,7 @@ node build/index.js
|
|
|
479
479
|
对于支持 skill/instruction 加载的 AI 代理(Claude Code、GitHub Copilot、Cursor 等),[`skills/gitlab-mcp/`](./skills/gitlab-mcp/) 中提供了预构建 skill 文件。
|
|
480
480
|
|
|
481
481
|
- **[SKILL.md](./skills/gitlab-mcp/SKILL.md)** — 核心指南,包含工具集概览、关键工作流和参数提示
|
|
482
|
-
- **[reference/](./skills/gitlab-mcp/reference/)** —
|
|
482
|
+
- **[reference/](./skills/gitlab-mcp/reference/)** — 代码审查、合并请求、议题、流水线和漏洞分类的详细工作流文档
|
|
483
483
|
|
|
484
484
|
使用 `skills` CLI 安装:
|
|
485
485
|
|
|
@@ -491,7 +491,7 @@ npx skills add zereight/gitlab-mcp --skill gitlab-mcp-skill
|
|
|
491
491
|
|
|
492
492
|
## 工具 🛠️
|
|
493
493
|
|
|
494
|
-
完整工具列表请参考英文 README 的 [Tools 部分](./README.md#tools-%EF%B8%8F)。当前服务器提供合并请求、议题、流水线、部署、环境、制品、里程碑、Wiki、仓库、发布、用户、事件、work item、webhook
|
|
494
|
+
完整工具列表请参考英文 README 的 [Tools 部分](./README.md#tools-%EF%B8%8F)。当前服务器提供合并请求、议题、流水线、部署、环境、制品、里程碑、Wiki、仓库、发布、用户、事件、work item、webhook、代码搜索、CI/CD 变量、dependency proxy、漏洞分类和 GraphQL 执行相关工具。
|
|
495
495
|
|
|
496
496
|
### Wiki 页面标题与 slug
|
|
497
497
|
|
package/build/downloads/proxy.js
CHANGED
|
@@ -100,8 +100,9 @@ export function registerDownloadProxy(app, deps) {
|
|
|
100
100
|
try {
|
|
101
101
|
apiUrl = deps.resolveTrustedGitLabApiUrl(requestedApiUrl);
|
|
102
102
|
}
|
|
103
|
-
catch {
|
|
104
|
-
|
|
103
|
+
catch (error) {
|
|
104
|
+
const message = error instanceof Error ? error.message : "Invalid X-GitLab-API-URL";
|
|
105
|
+
res.status(400).json({ error: message });
|
|
105
106
|
return;
|
|
106
107
|
}
|
|
107
108
|
}
|
package/build/index.js
CHANGED
|
@@ -91,6 +91,7 @@ import { resolveNestedWikiUpdateTitle } from "./utils/wiki-title.js";
|
|
|
91
91
|
import { redactSensitiveGitLabFields } from "./utils/redact-sensitive.js";
|
|
92
92
|
import { checkForNewVersion } from "./utils/version-check.js";
|
|
93
93
|
import { assertGitLabVersionAtLeast } from "./utils/gitlab-version-gate.js";
|
|
94
|
+
import { parseGitLabVersionApiResponse, } from "./utils/gitlab-instance-version.js";
|
|
94
95
|
import { buildBulkPublishDraftNotesBody, needsGitLab19_2BulkPublish, } from "./utils/bulk-publish-options.js";
|
|
95
96
|
import { cleanMutuallyExclusiveIdUsernameOptions, LIST_MERGE_REQUESTS_ID_USERNAME_PAIRS, sanitizeToolArguments, } from "./utils/tool-args.js";
|
|
96
97
|
import { parseSearchReplaceBlocks, applySearchReplace, applyUnifiedDiff, } from "./utils/patch-helper.js";
|
|
@@ -103,47 +104,11 @@ CreateMergeRequestNoteSchema, CreateMergeRequestDiscussionNoteSchema, CreateMerg
|
|
|
103
104
|
GitLabDiscussionNoteSchema, // Added
|
|
104
105
|
GitLabDiscussionSchema,
|
|
105
106
|
// Draft Notes Schemas
|
|
106
|
-
GitLabDraftNoteSchema, GitLabForkSchema, GitLabBranchSchema, GitLabProtectedBranchSchema, GitLabGroupSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestPipelineSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabGroupMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabDeploymentSchema, GitLabEnvironmentSchema, GitLabPipelineSchema, GitLabPipelineTriggerJobSchema, GitLabProjectMemberSchema, GitLabProjectSchema, GitLabTodoSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabSearchBlobResultSchema, GitLabSearchResponseSchema, GitLabTreeItemSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabWikiPageSchema, GroupIteration, ListCommitStatusesSchema, ListBranchesSchema, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, GitLabCiVariableSchema, ListProjectVariablesSchema, GetProjectVariableSchema, CreateProjectVariableSchema, UpdateProjectVariableSchema, DeleteProjectVariableSchema, ListGroupVariablesSchema, GetGroupVariableSchema, CreateGroupVariableSchema, UpdateGroupVariableSchema, DeleteGroupVariableSchema, GitLabDependencyProxySchema, GitLabDependencyProxyBlobSchema, GetDependencyProxySettingsSchema, UpdateDependencyProxySettingsSchema, ListDependencyProxyBlobsSchema, PurgeDependencyProxyCacheSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListTodosSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
|
|
107
|
+
GitLabDraftNoteSchema, GitLabForkSchema, GitLabBranchSchema, GitLabProtectedBranchSchema, GitLabGroupSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestPipelineSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabGroupMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabDeploymentSchema, GitLabEnvironmentSchema, GitLabPipelineSchema, GitLabPipelineTriggerJobSchema, GitLabProjectMemberSchema, GitLabProjectSchema, GitLabTodoSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabSearchBlobResultSchema, GitLabSearchResponseSchema, GitLabTreeItemSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabWikiPageSchema, GroupIteration, ListCommitStatusesSchema, ListBranchesSchema, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, GitLabCiVariableSchema, ListProjectVariablesSchema, GetProjectVariableSchema, CreateProjectVariableSchema, UpdateProjectVariableSchema, DeleteProjectVariableSchema, ListGroupVariablesSchema, GetGroupVariableSchema, CreateGroupVariableSchema, UpdateGroupVariableSchema, DeleteGroupVariableSchema, GitLabDependencyProxySchema, GitLabDependencyProxyBlobSchema, GetDependencyProxySettingsSchema, UpdateDependencyProxySettingsSchema, ListDependencyProxyBlobsSchema, PurgeDependencyProxyCacheSchema, ListProjectVulnerabilitiesSchema, GetVulnerabilitySchema, DismissVulnerabilitySchema, ConfirmVulnerabilitySchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListTodosSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
|
|
107
108
|
GetMergeRequestFileDiffSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListCiCatalogResourcesSchema, GetCiCatalogResourceSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListGroupMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, GetGroupWikiPageSchema, ListGroupWikiPagesSchema, UpdateGroupWikiPageSchema, MarkdownUploadSchema, MarkdownUploadRemoteSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, GetJobArtifactFileSchema, GitLabArtifactEntrySchema, ListJobArtifactsSchema, MergeMergeRequestSchema, ApproveMergeRequestSchema, UnapproveMergeRequestSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GitLabMergeRequestApprovalsResponseSchema, GitLabMergeRequestApprovalStateSchema, MyIssuesSchema, MarkAllTodosDoneSchema, MarkTodoDoneSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateProjectSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema, ExecuteGraphQLSchema, GitLabReleaseSchema, ListReleasesSchema, GetReleaseSchema, CreateReleaseSchema, UpdateReleaseSchema, DeleteReleaseSchema, CreateReleaseEvidenceSchema, DownloadReleaseAssetSchema, ListTagsSchema, GetTagSchema, CreateTagSchema, DeleteTagSchema, GetTagSignatureSchema, GitLabTagSchema, GitLabTagSignatureSchema, GetMergeRequestNotesSchema, GetMergeRequestNoteSchema, DeleteMergeRequestDiscussionNoteSchema, ResolveMergeRequestThreadSchema, GetWorkItemSchema, ListWorkItemsSchema, CreateWorkItemSchema, UpdateWorkItemSchema, ConvertWorkItemTypeSchema, ListWorkItemStatusesSchema, ListWorkItemNotesSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, MoveWorkItemSchema, ListCustomFieldDefinitionsSchema, GetTimelineEventsSchema, CreateTimelineEventSchema, ListWebhooksSchema, ListWebhookEventsSchema, GetWebhookEventSchema, HealthCheckSchema, } from "./schemas.js";
|
|
108
109
|
import { randomUUID, timingSafeEqual, } from "node:crypto";
|
|
109
|
-
import {
|
|
110
|
-
const logger =
|
|
111
|
-
level: process.env.LOG_LEVEL || "info",
|
|
112
|
-
// Redact sensitive values that must never appear in logs. Covers both
|
|
113
|
-
// typical auth-context property names and common HTTP header-bag shapes
|
|
114
|
-
// that tool/fetch wrappers may include when logging errors.
|
|
115
|
-
redact: {
|
|
116
|
-
paths: [
|
|
117
|
-
"token",
|
|
118
|
-
"*.token",
|
|
119
|
-
"ctx.token",
|
|
120
|
-
"context.token",
|
|
121
|
-
"authData.token",
|
|
122
|
-
"auth.token",
|
|
123
|
-
"headers.authorization",
|
|
124
|
-
"headers.Authorization",
|
|
125
|
-
'headers["private-token"]',
|
|
126
|
-
'headers["Private-Token"]',
|
|
127
|
-
'headers["job-token"]',
|
|
128
|
-
'headers["JOB-TOKEN"]',
|
|
129
|
-
'headers["mcp-session-id"]',
|
|
130
|
-
'headers["Mcp-Session-Id"]',
|
|
131
|
-
"sessionId",
|
|
132
|
-
"*.sessionId",
|
|
133
|
-
"ctx.sessionId",
|
|
134
|
-
"context.sessionId",
|
|
135
|
-
],
|
|
136
|
-
censor: "[REDACTED]",
|
|
137
|
-
},
|
|
138
|
-
transport: {
|
|
139
|
-
target: "pino-pretty",
|
|
140
|
-
options: {
|
|
141
|
-
colorize: true,
|
|
142
|
-
levelFirst: true,
|
|
143
|
-
destination: 2,
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
});
|
|
110
|
+
import { createLogger } from "./utils/logger.js";
|
|
111
|
+
const logger = createLogger();
|
|
147
112
|
/**
|
|
148
113
|
* Read version from package.json
|
|
149
114
|
*/
|
|
@@ -1146,7 +1111,8 @@ function resolveTrustedGitLabApiUrl(value) {
|
|
|
1146
1111
|
}
|
|
1147
1112
|
const allowedApiUrl = GITLAB_ALLOWED_API_URLS_BY_HOST.get(parsed.host);
|
|
1148
1113
|
if (!allowedApiUrl) {
|
|
1149
|
-
throw new Error(`GitLab API URL host is not allowed: ${parsed.host}`
|
|
1114
|
+
throw new Error(`GitLab API URL host is not allowed: ${parsed.host}. ` +
|
|
1115
|
+
"Add the host to GITLAB_ALLOWED_HOSTS or GITLAB_API_URL.");
|
|
1150
1116
|
}
|
|
1151
1117
|
return allowedApiUrl;
|
|
1152
1118
|
}
|
|
@@ -4426,7 +4392,7 @@ async function publishDraftNote(projectId, mergeRequestIid, draftNoteId) {
|
|
|
4426
4392
|
};
|
|
4427
4393
|
}
|
|
4428
4394
|
}
|
|
4429
|
-
async function
|
|
4395
|
+
async function fetchGitLabVersionMetadata() {
|
|
4430
4396
|
try {
|
|
4431
4397
|
const response = await fetch(`${getEffectiveApiUrl()}/version`, {
|
|
4432
4398
|
...getFetchConfig(),
|
|
@@ -4434,18 +4400,16 @@ async function fetchGitLabInstanceVersion() {
|
|
|
4434
4400
|
if (!response.ok)
|
|
4435
4401
|
return null;
|
|
4436
4402
|
const data = await response.json();
|
|
4437
|
-
|
|
4438
|
-
data !== null &&
|
|
4439
|
-
"version" in data &&
|
|
4440
|
-
typeof data.version === "string") {
|
|
4441
|
-
return data.version;
|
|
4442
|
-
}
|
|
4443
|
-
return null;
|
|
4403
|
+
return parseGitLabVersionApiResponse(data);
|
|
4444
4404
|
}
|
|
4445
4405
|
catch {
|
|
4446
4406
|
return null;
|
|
4447
4407
|
}
|
|
4448
4408
|
}
|
|
4409
|
+
async function fetchGitLabInstanceVersion() {
|
|
4410
|
+
const metadata = await fetchGitLabVersionMetadata();
|
|
4411
|
+
return metadata?.version ?? null;
|
|
4412
|
+
}
|
|
4449
4413
|
/**
|
|
4450
4414
|
* Publish all draft notes for a merge request
|
|
4451
4415
|
* @param {string} projectId - The ID or URL-encoded path of the project
|
|
@@ -6453,6 +6417,262 @@ async function purgeDependencyProxyCache(groupId) {
|
|
|
6453
6417
|
const response = await fetch(url.toString(), { ...getFetchConfig(), method: "DELETE" });
|
|
6454
6418
|
await handleGitLabError(response);
|
|
6455
6419
|
}
|
|
6420
|
+
// --- Vulnerability functions (GraphQL) ---
|
|
6421
|
+
//
|
|
6422
|
+
// The REST Vulnerabilities API is deprecated and silently ignores filter
|
|
6423
|
+
// params on list and reason/comment on dismiss. GitLab recommends the
|
|
6424
|
+
// GraphQL API for vulnerability management, which supports all of these.
|
|
6425
|
+
/** Convert a numeric vulnerability ID (or an existing Vulnerability GID) to a GraphQL global ID. */
|
|
6426
|
+
function toVulnerabilityGid(vulnerabilityId) {
|
|
6427
|
+
if (/^\d+$/.test(vulnerabilityId)) {
|
|
6428
|
+
return `gid://gitlab/Vulnerability/${vulnerabilityId}`;
|
|
6429
|
+
}
|
|
6430
|
+
if (/^gid:\/\/gitlab\/Vulnerability\/\d+$/.test(vulnerabilityId)) {
|
|
6431
|
+
return vulnerabilityId;
|
|
6432
|
+
}
|
|
6433
|
+
throw new Error(`Invalid vulnerability ID "${vulnerabilityId}": expected a numeric ID or a gid://gitlab/Vulnerability/<id> global ID`);
|
|
6434
|
+
}
|
|
6435
|
+
/**
|
|
6436
|
+
* Resolve a project ID or path to the full namespace path required by
|
|
6437
|
+
* GraphQL's `project(fullPath:)` query. Numeric IDs are resolved via a
|
|
6438
|
+
* REST lookup; paths are returned as-is (decoded).
|
|
6439
|
+
*/
|
|
6440
|
+
async function resolveProjectFullPath(projectId) {
|
|
6441
|
+
const decoded = decodeURIComponent(getEffectiveProjectId(decodeURIComponent(projectId)));
|
|
6442
|
+
if (!/^\d+$/.test(decoded)) {
|
|
6443
|
+
return decoded;
|
|
6444
|
+
}
|
|
6445
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(decoded)}`);
|
|
6446
|
+
const response = await fetch(url.toString(), { ...getFetchConfig() });
|
|
6447
|
+
await handleGitLabError(response);
|
|
6448
|
+
const project = (await response.json());
|
|
6449
|
+
if (!project.path_with_namespace) {
|
|
6450
|
+
throw new Error(`Could not resolve full path for project ${decoded}`);
|
|
6451
|
+
}
|
|
6452
|
+
return project.path_with_namespace;
|
|
6453
|
+
}
|
|
6454
|
+
/**
|
|
6455
|
+
* Enforce GITLAB_ALLOWED_PROJECT_IDS for vulnerability tools. Vulnerability
|
|
6456
|
+
* GIDs are globally unique rather than project-scoped, so the boundary must
|
|
6457
|
+
* be checked against the project the vulnerability actually belongs to —
|
|
6458
|
+
* mirroring what getEffectiveProjectId does for project-scoped REST calls.
|
|
6459
|
+
* Allowlist entries may be numeric IDs or full namespace paths.
|
|
6460
|
+
*/
|
|
6461
|
+
function assertVulnerabilityProjectAllowed(vulnerabilityId, project) {
|
|
6462
|
+
if (GITLAB_ALLOWED_PROJECT_IDS.length === 0) {
|
|
6463
|
+
return;
|
|
6464
|
+
}
|
|
6465
|
+
const fullPath = project?.fullPath ?? undefined;
|
|
6466
|
+
const numericId = project?.id?.match(/^gid:\/\/gitlab\/Project\/(\d+)$/)?.[1];
|
|
6467
|
+
const allowed = (fullPath !== undefined && GITLAB_ALLOWED_PROJECT_IDS.includes(fullPath)) ||
|
|
6468
|
+
(numericId !== undefined && GITLAB_ALLOWED_PROJECT_IDS.includes(numericId));
|
|
6469
|
+
if (!allowed) {
|
|
6470
|
+
throw new Error(`Access denied: Vulnerability ${vulnerabilityId} belongs to project ${fullPath ?? numericId ?? "unknown"}, which is not in the allowed project list: ${GITLAB_ALLOWED_PROJECT_IDS.join(", ")}`);
|
|
6471
|
+
}
|
|
6472
|
+
}
|
|
6473
|
+
/**
|
|
6474
|
+
* Pre-flight allowlist check for vulnerability mutations: resolves the
|
|
6475
|
+
* vulnerability's project and verifies it against GITLAB_ALLOWED_PROJECT_IDS
|
|
6476
|
+
* before any write is issued. No-op (no extra request) when the allowlist
|
|
6477
|
+
* is not configured.
|
|
6478
|
+
*/
|
|
6479
|
+
async function ensureVulnerabilityProjectAllowed(vulnerabilityId) {
|
|
6480
|
+
if (GITLAB_ALLOWED_PROJECT_IDS.length === 0) {
|
|
6481
|
+
return;
|
|
6482
|
+
}
|
|
6483
|
+
const data = await executeGraphQL(`query getVulnerabilityProject($id: VulnerabilityID!) {
|
|
6484
|
+
vulnerability(id: $id) {
|
|
6485
|
+
project {
|
|
6486
|
+
id
|
|
6487
|
+
fullPath
|
|
6488
|
+
}
|
|
6489
|
+
}
|
|
6490
|
+
}`, { id: toVulnerabilityGid(vulnerabilityId) });
|
|
6491
|
+
if (!data.vulnerability) {
|
|
6492
|
+
throw new Error(`Vulnerability not found: ${vulnerabilityId}`);
|
|
6493
|
+
}
|
|
6494
|
+
assertVulnerabilityProjectAllowed(vulnerabilityId, data.vulnerability.project);
|
|
6495
|
+
}
|
|
6496
|
+
/** Shared GraphQL selection set for vulnerability objects. */
|
|
6497
|
+
const VULNERABILITY_FIELDS = `
|
|
6498
|
+
id
|
|
6499
|
+
title
|
|
6500
|
+
description
|
|
6501
|
+
state
|
|
6502
|
+
severity
|
|
6503
|
+
reportType
|
|
6504
|
+
detectedAt
|
|
6505
|
+
confirmedAt
|
|
6506
|
+
resolvedAt
|
|
6507
|
+
dismissedAt
|
|
6508
|
+
dismissalReason
|
|
6509
|
+
webUrl
|
|
6510
|
+
scanner {
|
|
6511
|
+
name
|
|
6512
|
+
externalId
|
|
6513
|
+
vendor
|
|
6514
|
+
}
|
|
6515
|
+
identifiers {
|
|
6516
|
+
externalType
|
|
6517
|
+
externalId
|
|
6518
|
+
name
|
|
6519
|
+
url
|
|
6520
|
+
}
|
|
6521
|
+
links {
|
|
6522
|
+
name
|
|
6523
|
+
url
|
|
6524
|
+
}
|
|
6525
|
+
location {
|
|
6526
|
+
... on VulnerabilityLocationSast {
|
|
6527
|
+
file
|
|
6528
|
+
startLine
|
|
6529
|
+
endLine
|
|
6530
|
+
}
|
|
6531
|
+
... on VulnerabilityLocationSecretDetection {
|
|
6532
|
+
file
|
|
6533
|
+
startLine
|
|
6534
|
+
endLine
|
|
6535
|
+
}
|
|
6536
|
+
... on VulnerabilityLocationDependencyScanning {
|
|
6537
|
+
file
|
|
6538
|
+
dependency {
|
|
6539
|
+
package {
|
|
6540
|
+
name
|
|
6541
|
+
}
|
|
6542
|
+
version
|
|
6543
|
+
}
|
|
6544
|
+
}
|
|
6545
|
+
... on VulnerabilityLocationContainerScanning {
|
|
6546
|
+
image
|
|
6547
|
+
operatingSystem
|
|
6548
|
+
dependency {
|
|
6549
|
+
package {
|
|
6550
|
+
name
|
|
6551
|
+
}
|
|
6552
|
+
version
|
|
6553
|
+
}
|
|
6554
|
+
}
|
|
6555
|
+
... on VulnerabilityLocationDast {
|
|
6556
|
+
path
|
|
6557
|
+
hostname
|
|
6558
|
+
}
|
|
6559
|
+
}
|
|
6560
|
+
`;
|
|
6561
|
+
async function listProjectVulnerabilities(projectId, options = {}) {
|
|
6562
|
+
const fullPath = await resolveProjectFullPath(projectId);
|
|
6563
|
+
const variables = {
|
|
6564
|
+
fullPath,
|
|
6565
|
+
first: Math.min(options.first ?? 20, 100),
|
|
6566
|
+
};
|
|
6567
|
+
if (options.state)
|
|
6568
|
+
variables.state = [options.state.toUpperCase()];
|
|
6569
|
+
if (options.severity)
|
|
6570
|
+
variables.severity = [options.severity.toUpperCase()];
|
|
6571
|
+
if (options.report_type)
|
|
6572
|
+
variables.reportType = [options.report_type.toUpperCase()];
|
|
6573
|
+
if (options.after)
|
|
6574
|
+
variables.after = options.after;
|
|
6575
|
+
const data = await executeGraphQL(`query listProjectVulnerabilities(
|
|
6576
|
+
$fullPath: ID!
|
|
6577
|
+
$state: [VulnerabilityState!]
|
|
6578
|
+
$severity: [VulnerabilitySeverity!]
|
|
6579
|
+
$reportType: [VulnerabilityReportType!]
|
|
6580
|
+
$first: Int
|
|
6581
|
+
$after: String
|
|
6582
|
+
) {
|
|
6583
|
+
project(fullPath: $fullPath) {
|
|
6584
|
+
vulnerabilities(
|
|
6585
|
+
state: $state
|
|
6586
|
+
severity: $severity
|
|
6587
|
+
reportType: $reportType
|
|
6588
|
+
first: $first
|
|
6589
|
+
after: $after
|
|
6590
|
+
) {
|
|
6591
|
+
nodes {
|
|
6592
|
+
${VULNERABILITY_FIELDS}
|
|
6593
|
+
}
|
|
6594
|
+
pageInfo {
|
|
6595
|
+
endCursor
|
|
6596
|
+
hasNextPage
|
|
6597
|
+
}
|
|
6598
|
+
}
|
|
6599
|
+
}
|
|
6600
|
+
}`, variables);
|
|
6601
|
+
if (!data.project) {
|
|
6602
|
+
throw new Error(`Project not found or not accessible: ${fullPath}`);
|
|
6603
|
+
}
|
|
6604
|
+
return {
|
|
6605
|
+
vulnerabilities: data.project.vulnerabilities.nodes,
|
|
6606
|
+
pageInfo: data.project.vulnerabilities.pageInfo,
|
|
6607
|
+
};
|
|
6608
|
+
}
|
|
6609
|
+
async function getVulnerability(vulnerabilityId) {
|
|
6610
|
+
const data = await executeGraphQL(`query getVulnerability($id: VulnerabilityID!) {
|
|
6611
|
+
vulnerability(id: $id) {
|
|
6612
|
+
${VULNERABILITY_FIELDS}
|
|
6613
|
+
project {
|
|
6614
|
+
id
|
|
6615
|
+
name
|
|
6616
|
+
fullPath
|
|
6617
|
+
}
|
|
6618
|
+
}
|
|
6619
|
+
}`, { id: toVulnerabilityGid(vulnerabilityId) });
|
|
6620
|
+
if (!data.vulnerability) {
|
|
6621
|
+
throw new Error(`Vulnerability not found: ${vulnerabilityId}`);
|
|
6622
|
+
}
|
|
6623
|
+
assertVulnerabilityProjectAllowed(vulnerabilityId, data.vulnerability.project);
|
|
6624
|
+
return data.vulnerability;
|
|
6625
|
+
}
|
|
6626
|
+
async function dismissVulnerability(vulnerabilityId, reason, comment) {
|
|
6627
|
+
await ensureVulnerabilityProjectAllowed(vulnerabilityId);
|
|
6628
|
+
const input = {
|
|
6629
|
+
id: toVulnerabilityGid(vulnerabilityId),
|
|
6630
|
+
dismissalReason: reason.toUpperCase(),
|
|
6631
|
+
};
|
|
6632
|
+
if (comment)
|
|
6633
|
+
input.comment = comment;
|
|
6634
|
+
const data = await executeGraphQL(`mutation dismissVulnerability($input: VulnerabilityDismissInput!) {
|
|
6635
|
+
vulnerabilityDismiss(input: $input) {
|
|
6636
|
+
vulnerability {
|
|
6637
|
+
id
|
|
6638
|
+
state
|
|
6639
|
+
dismissedAt
|
|
6640
|
+
dismissalReason
|
|
6641
|
+
}
|
|
6642
|
+
errors
|
|
6643
|
+
}
|
|
6644
|
+
}`, { input });
|
|
6645
|
+
if (data.vulnerabilityDismiss.errors?.length) {
|
|
6646
|
+
throw new Error(`Failed to dismiss vulnerability: ${data.vulnerabilityDismiss.errors.join(", ")}`);
|
|
6647
|
+
}
|
|
6648
|
+
if (!data.vulnerabilityDismiss.vulnerability) {
|
|
6649
|
+
throw new Error(`Vulnerability not returned after dismissal (id: ${vulnerabilityId})`);
|
|
6650
|
+
}
|
|
6651
|
+
return data.vulnerabilityDismiss.vulnerability;
|
|
6652
|
+
}
|
|
6653
|
+
async function confirmVulnerability(vulnerabilityId, comment) {
|
|
6654
|
+
await ensureVulnerabilityProjectAllowed(vulnerabilityId);
|
|
6655
|
+
const input = { id: toVulnerabilityGid(vulnerabilityId) };
|
|
6656
|
+
if (comment)
|
|
6657
|
+
input.comment = comment;
|
|
6658
|
+
const data = await executeGraphQL(`mutation confirmVulnerability($input: VulnerabilityConfirmInput!) {
|
|
6659
|
+
vulnerabilityConfirm(input: $input) {
|
|
6660
|
+
vulnerability {
|
|
6661
|
+
id
|
|
6662
|
+
state
|
|
6663
|
+
confirmedAt
|
|
6664
|
+
}
|
|
6665
|
+
errors
|
|
6666
|
+
}
|
|
6667
|
+
}`, { input });
|
|
6668
|
+
if (data.vulnerabilityConfirm.errors?.length) {
|
|
6669
|
+
throw new Error(`Failed to confirm vulnerability: ${data.vulnerabilityConfirm.errors.join(", ")}`);
|
|
6670
|
+
}
|
|
6671
|
+
if (!data.vulnerabilityConfirm.vulnerability) {
|
|
6672
|
+
throw new Error(`Vulnerability not returned after confirmation (id: ${vulnerabilityId})`);
|
|
6673
|
+
}
|
|
6674
|
+
return data.vulnerabilityConfirm.vulnerability;
|
|
6675
|
+
}
|
|
6456
6676
|
/**
|
|
6457
6677
|
* Upload a file to a GitLab project for use in markdown content.
|
|
6458
6678
|
*
|
|
@@ -8869,6 +9089,28 @@ async function handleToolCall(params) {
|
|
|
8869
9089
|
],
|
|
8870
9090
|
};
|
|
8871
9091
|
}
|
|
9092
|
+
// --- Vulnerability tools ---
|
|
9093
|
+
case "list_project_vulnerabilities": {
|
|
9094
|
+
const args = ListProjectVulnerabilitiesSchema.parse(params.arguments);
|
|
9095
|
+
const { project_id, ...options } = args;
|
|
9096
|
+
const result = await listProjectVulnerabilities(project_id, options);
|
|
9097
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
9098
|
+
}
|
|
9099
|
+
case "get_vulnerability": {
|
|
9100
|
+
const args = GetVulnerabilitySchema.parse(params.arguments);
|
|
9101
|
+
const result = await getVulnerability(args.vulnerability_id);
|
|
9102
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
9103
|
+
}
|
|
9104
|
+
case "dismiss_vulnerability": {
|
|
9105
|
+
const args = DismissVulnerabilitySchema.parse(params.arguments);
|
|
9106
|
+
const result = await dismissVulnerability(args.vulnerability_id, args.reason, args.comment);
|
|
9107
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
9108
|
+
}
|
|
9109
|
+
case "confirm_vulnerability": {
|
|
9110
|
+
const args = ConfirmVulnerabilitySchema.parse(params.arguments);
|
|
9111
|
+
const result = await confirmVulnerability(args.vulnerability_id, args.comment);
|
|
9112
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
9113
|
+
}
|
|
8872
9114
|
case "upload_markdown": {
|
|
8873
9115
|
if (IS_REMOTE) {
|
|
8874
9116
|
const args = MarkdownUploadRemoteSchema.parse(params.arguments);
|
|
@@ -9096,6 +9338,9 @@ async function handleToolCall(params) {
|
|
|
9096
9338
|
const jobResponse = await fetch(jobUrl.toString(), getFetchConfig());
|
|
9097
9339
|
authenticated = jobResponse.ok;
|
|
9098
9340
|
}
|
|
9341
|
+
const versionMetadata = authenticated
|
|
9342
|
+
? await fetchGitLabVersionMetadata()
|
|
9343
|
+
: null;
|
|
9099
9344
|
return {
|
|
9100
9345
|
content: [
|
|
9101
9346
|
{
|
|
@@ -9104,6 +9349,7 @@ async function handleToolCall(params) {
|
|
|
9104
9349
|
status: authenticated ? "ok" : "error",
|
|
9105
9350
|
authenticated,
|
|
9106
9351
|
gitlab_url: getEffectiveApiUrl(),
|
|
9352
|
+
...(versionMetadata ?? {}),
|
|
9107
9353
|
}),
|
|
9108
9354
|
},
|
|
9109
9355
|
],
|
|
@@ -9496,8 +9742,9 @@ async function startStreamableHTTPServer() {
|
|
|
9496
9742
|
try {
|
|
9497
9743
|
apiUrl = resolveTrustedGitLabApiUrl(dynamicApiUrl);
|
|
9498
9744
|
}
|
|
9499
|
-
catch {
|
|
9500
|
-
|
|
9745
|
+
catch (error) {
|
|
9746
|
+
const reason = error instanceof Error ? error.message : "Invalid X-GitLab-API-URL";
|
|
9747
|
+
logger.warn(`Invalid X-GitLab-API-URL provided: ${dynamicApiUrl}. ${reason}`);
|
|
9501
9748
|
return null; // Reject if URL is malformed or not allowed
|
|
9502
9749
|
}
|
|
9503
9750
|
}
|
package/build/oauth-proxy.js
CHANGED
|
@@ -47,11 +47,11 @@
|
|
|
47
47
|
import { InvalidTokenError, ServerError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
|
|
48
48
|
import { OAuthTokensSchema } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
49
49
|
import { randomUUID, randomBytes, createHash } from "node:crypto";
|
|
50
|
-
import { pino } from "pino";
|
|
51
50
|
import { looksLikeStatelessClientId, mintClientId, openClientId, } from "./stateless/client-id.js";
|
|
52
51
|
import { looksLikeStatelessState, mintPendingAuthState, openPendingAuthState, } from "./stateless/pending-auth.js";
|
|
53
52
|
import { looksLikeStatelessStoredTokensCode, mintStoredTokensCode, openStoredTokensCode, } from "./stateless/stored-tokens.js";
|
|
54
|
-
|
|
53
|
+
import { createLogger } from "./utils/logger.js";
|
|
54
|
+
const logger = createLogger("gitlab-mcp-oauth-proxy");
|
|
55
55
|
// ---------------------------------------------------------------------------
|
|
56
56
|
// GitLab OAuth Server Provider
|
|
57
57
|
// ---------------------------------------------------------------------------
|
package/build/oauth.js
CHANGED
|
@@ -9,11 +9,8 @@ import { execFile } from "child_process";
|
|
|
9
9
|
import { promisify } from "util";
|
|
10
10
|
import open from "open";
|
|
11
11
|
import pkceChallenge from "pkce-challenge";
|
|
12
|
-
import {
|
|
13
|
-
const logger =
|
|
14
|
-
name: "gitlab-mcp-oauth",
|
|
15
|
-
level: process.env.LOG_LEVEL || "info",
|
|
16
|
-
}, pino.destination(2));
|
|
12
|
+
import { createLogger } from "./utils/logger.js";
|
|
13
|
+
const logger = createLogger("gitlab-mcp-oauth");
|
|
17
14
|
const execFileAsync = promisify(execFile);
|
|
18
15
|
function escapeHtml(str) {
|
|
19
16
|
const map = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
package/build/schemas.js
CHANGED
|
@@ -4023,3 +4023,70 @@ export const ListDependencyProxyBlobsSchema = z.object({
|
|
|
4023
4023
|
export const PurgeDependencyProxyCacheSchema = z.object({
|
|
4024
4024
|
group_id: z.coerce.string().describe("Group ID or URL-encoded path"),
|
|
4025
4025
|
});
|
|
4026
|
+
// --- Vulnerability schemas ---
|
|
4027
|
+
// Schema for listing project vulnerabilities with optional filters (GraphQL-backed)
|
|
4028
|
+
export const ListProjectVulnerabilitiesSchema = z.object({
|
|
4029
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
4030
|
+
state: z
|
|
4031
|
+
.enum(["detected", "confirmed", "resolved", "dismissed"])
|
|
4032
|
+
.optional()
|
|
4033
|
+
.describe("Filter by vulnerability state"),
|
|
4034
|
+
severity: z
|
|
4035
|
+
.enum(["critical", "high", "medium", "low", "info", "unknown"])
|
|
4036
|
+
.optional()
|
|
4037
|
+
.describe("Filter by severity level"),
|
|
4038
|
+
report_type: z
|
|
4039
|
+
.enum([
|
|
4040
|
+
"sast",
|
|
4041
|
+
"dast",
|
|
4042
|
+
"dependency_scanning",
|
|
4043
|
+
"container_scanning",
|
|
4044
|
+
"secret_detection",
|
|
4045
|
+
"coverage_fuzzing",
|
|
4046
|
+
"api_fuzzing",
|
|
4047
|
+
"cluster_image_scanning",
|
|
4048
|
+
"generic",
|
|
4049
|
+
])
|
|
4050
|
+
.optional()
|
|
4051
|
+
.describe("Filter by scan/report type (e.g. secret_detection, sast, dast)"),
|
|
4052
|
+
first: z.coerce
|
|
4053
|
+
.number()
|
|
4054
|
+
.int()
|
|
4055
|
+
.min(1)
|
|
4056
|
+
.max(100)
|
|
4057
|
+
.optional()
|
|
4058
|
+
.describe("Number of vulnerabilities to return (max: 100, default: 20)"),
|
|
4059
|
+
after: z
|
|
4060
|
+
.string()
|
|
4061
|
+
.optional()
|
|
4062
|
+
.describe("Cursor for pagination; use the endCursor from a previous response"),
|
|
4063
|
+
});
|
|
4064
|
+
// Schema for getting a single vulnerability by ID
|
|
4065
|
+
export const GetVulnerabilitySchema = z.object({
|
|
4066
|
+
vulnerability_id: z.coerce
|
|
4067
|
+
.string()
|
|
4068
|
+
.describe("The vulnerability ID (numeric or GraphQL global ID)"),
|
|
4069
|
+
});
|
|
4070
|
+
// Schema for dismissing a vulnerability with required reason and optional comment
|
|
4071
|
+
export const DismissVulnerabilitySchema = z.object({
|
|
4072
|
+
vulnerability_id: z.coerce
|
|
4073
|
+
.string()
|
|
4074
|
+
.describe("The ID of the vulnerability to dismiss (numeric or GraphQL global ID)"),
|
|
4075
|
+
reason: z
|
|
4076
|
+
.enum([
|
|
4077
|
+
"acceptable_risk",
|
|
4078
|
+
"false_positive",
|
|
4079
|
+
"used_in_tests",
|
|
4080
|
+
"mitigating_control",
|
|
4081
|
+
"not_applicable",
|
|
4082
|
+
])
|
|
4083
|
+
.describe("Reason for dismissal"),
|
|
4084
|
+
comment: z.string().optional().describe("Optional comment explaining the dismissal"),
|
|
4085
|
+
});
|
|
4086
|
+
// Schema for confirming a vulnerability with optional comment
|
|
4087
|
+
export const ConfirmVulnerabilitySchema = z.object({
|
|
4088
|
+
vulnerability_id: z.coerce
|
|
4089
|
+
.string()
|
|
4090
|
+
.describe("The ID of the vulnerability to confirm (numeric or GraphQL global ID)"),
|
|
4091
|
+
comment: z.string().optional().describe("Optional comment explaining the confirmation"),
|
|
4092
|
+
});
|
|
@@ -14,6 +14,7 @@ const SKILL_PATH = join(REPO_ROOT, "skills", "gitlab-mcp", "SKILL.md");
|
|
|
14
14
|
const REF_DIR = join(REPO_ROOT, "skills", "gitlab-mcp", "reference");
|
|
15
15
|
// Response field names in reference docs — not MCP tools.
|
|
16
16
|
const REFERENCE_FIELD_ALLOWLIST = new Set([
|
|
17
|
+
"after",
|
|
17
18
|
"allow_multiple",
|
|
18
19
|
"deleted_file",
|
|
19
20
|
"excluded_file_patterns",
|