@zereight/mcp-gitlab 2.1.18 → 2.1.20
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 -0
- package/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/build/config.js +7 -0
- package/build/index.js +108 -20
- package/build/oauth-proxy.js +75 -27
- package/build/schemas.js +83 -4
- package/build/test/mcp-oauth-tests.js +193 -10
- package/build/test/remote-auth-simple-test.js +26 -1
- package/build/test/stateless/callback-proxy.test.js +1 -1
- package/build/test/stateless/client-id.test.js +1 -0
- package/build/test/test-list-issues.js +111 -0
- package/build/test/test-protected-branches.js +155 -0
- package/build/test/test-toolset-filtering.js +1 -1
- package/build/test/utils/tool-args.test.js +48 -1
- package/build/tools/registry.js +36 -1
- package/build/utils/tool-args.js +27 -0
- package/package.json +2 -2
package/README.ko.md
CHANGED
|
@@ -187,6 +187,7 @@ MCP 서버가 직접 로컬 브라우저 callback을 받을 때만 `GITLAB_OAUTH
|
|
|
187
187
|
| `STREAMABLE_HTTP` | 예 | 반드시 `true` |
|
|
188
188
|
| `GITLAB_OAUTH_CALLBACK_PROXY` | 선택 | MCP 서버의 고정 `/callback` URL을 사용하려면 `true` |
|
|
189
189
|
| `GITLAB_OAUTH_SCOPES` | 선택 | 쉼표로 구분된 scope 목록(기본값: `api,read_api,read_user`) |
|
|
190
|
+
| `GITLAB_ALLOWED_GROUPS` | 선택 | 쉼표로 구분된 GitLab 그룹 전체 경로 — 해당 그룹 및 하위 그룹 멤버만 토큰을 발급받을 수 있음 |
|
|
190
191
|
|
|
191
192
|
> **`Unregistered redirect_uri` 문제 해결**
|
|
192
193
|
>
|
package/README.md
CHANGED
|
@@ -208,6 +208,7 @@ exchanging credentials with GitLab on behalf of the client.
|
|
|
208
208
|
| `STREAMABLE_HTTP` | ✅ | Must be `true` |
|
|
209
209
|
| `GITLAB_OAUTH_CALLBACK_PROXY` | optional | Set to `true` to use the MCP server's fixed `/callback` URL |
|
|
210
210
|
| `GITLAB_OAUTH_SCOPES` | optional | Comma-separated scopes (default: `api,read_api,read_user`) |
|
|
211
|
+
| `GITLAB_ALLOWED_GROUPS` | optional | Comma-separated group full paths — only members (and subgroup members) may obtain a token |
|
|
211
212
|
|
|
212
213
|
When `STREAMABLE_HTTP=true`, server-side `GITLAB_PERSONAL_ACCESS_TOKEN` or `GITLAB_JOB_TOKEN` require `REMOTE_AUTHORIZATION=true` or `GITLAB_MCP_OAUTH=true`.
|
|
213
214
|
|
package/README.zh-CN.md
CHANGED
|
@@ -187,6 +187,7 @@ OpenCode、MCPJam、Claude.ai 等远程 MCP 客户端可能会在授权时发送
|
|
|
187
187
|
| `STREAMABLE_HTTP` | 是 | 必须为 `true` |
|
|
188
188
|
| `GITLAB_OAUTH_CALLBACK_PROXY` | 可选 | 设置为 `true` 时使用 MCP 服务器固定的 `/callback` URL |
|
|
189
189
|
| `GITLAB_OAUTH_SCOPES` | 可选 | 逗号分隔的 scope(默认:`api,read_api,read_user`) |
|
|
190
|
+
| `GITLAB_ALLOWED_GROUPS` | 可选 | 逗号分隔的 GitLab 群组完整路径 — 仅该群组及其子群组的成员可获取令牌 |
|
|
190
191
|
|
|
191
192
|
> **排查 `Unregistered redirect_uri`**
|
|
192
193
|
>
|
package/build/config.js
CHANGED
|
@@ -57,6 +57,13 @@ export const GITLAB_OAUTH_SCOPES = GITLAB_OAUTH_SCOPES_RAW
|
|
|
57
57
|
? GITLAB_OAUTH_SCOPES_RAW.split(",").map((s) => s.trim()).filter(Boolean)
|
|
58
58
|
: undefined;
|
|
59
59
|
export const GITLAB_OAUTH_CALLBACK_PROXY = getConfig("oauth-callback-proxy", "GITLAB_OAUTH_CALLBACK_PROXY") === "true";
|
|
60
|
+
export const GITLAB_ALLOWED_GROUPS = (() => {
|
|
61
|
+
const raw = getConfig("allowed-groups", "GITLAB_ALLOWED_GROUPS");
|
|
62
|
+
if (!raw)
|
|
63
|
+
return undefined;
|
|
64
|
+
const groups = raw.split(",").map((g) => g.trim()).filter(Boolean);
|
|
65
|
+
return groups.length > 0 ? groups : undefined;
|
|
66
|
+
})();
|
|
60
67
|
export const ENABLE_DYNAMIC_API_URL = getConfig("enable-dynamic-api-url", "ENABLE_DYNAMIC_API_URL") === "true";
|
|
61
68
|
// ---------------------------------------------------------------------------
|
|
62
69
|
// Stateless mode (multi-pod safe OAuth / session encoding)
|
package/build/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { getConfig, ENABLE_DYNAMIC_API_URL, GITLAB_AUTH_COOKIE_PATH, GITLAB_CA_CERT_PATH, GITLAB_JOB_TOKEN, GITLAB_MCP_OAUTH, GITLAB_OAUTH_APP_ID, GITLAB_OAUTH_SCOPES, GITLAB_OAUTH_CALLBACK_PROXY, GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_POOL_MAX_SIZE, GITLAB_READ_ONLY_MODE, GITLAB_TOOLSETS_RAW, GITLAB_TOOLS_RAW, HOST, HTTP_PROXY, HTTPS_PROXY, IS_OLD, MCP_SERVER_URL, NODE_TLS_REJECT_UNAUTHORIZED, NO_PROXY, OAUTH_STATELESS_CLIENT_TTL_SECONDS, OAUTH_STATELESS_MODE, OAUTH_STATELESS_PENDING_TTL_SECONDS, OAUTH_STATELESS_SESSION_TTL_SECONDS, OAUTH_STATELESS_STORED_TTL_SECONDS, PORT, REMOTE_AUTHORIZATION, SESSION_TIMEOUT_SECONDS, SSE, STREAMABLE_HTTP, USE_GITLAB_WIKI, USE_MILESTONE, USE_OAUTH, USE_PIPELINE, GITLAB_TOOL_POLICY_APPROVE_RAW, GITLAB_TOOL_POLICY_HIDDEN_RAW, } from "./config.js";
|
|
2
|
+
import { getConfig, ENABLE_DYNAMIC_API_URL, GITLAB_AUTH_COOKIE_PATH, GITLAB_CA_CERT_PATH, GITLAB_JOB_TOKEN, GITLAB_MCP_OAUTH, GITLAB_OAUTH_APP_ID, GITLAB_OAUTH_SCOPES, GITLAB_OAUTH_CALLBACK_PROXY, GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_POOL_MAX_SIZE, GITLAB_READ_ONLY_MODE, GITLAB_TOOLSETS_RAW, GITLAB_TOOLS_RAW, HOST, HTTP_PROXY, HTTPS_PROXY, IS_OLD, MCP_SERVER_URL, NODE_TLS_REJECT_UNAUTHORIZED, NO_PROXY, OAUTH_STATELESS_CLIENT_TTL_SECONDS, OAUTH_STATELESS_MODE, OAUTH_STATELESS_PENDING_TTL_SECONDS, OAUTH_STATELESS_SESSION_TTL_SECONDS, OAUTH_STATELESS_STORED_TTL_SECONDS, PORT, REMOTE_AUTHORIZATION, SESSION_TIMEOUT_SECONDS, SSE, STREAMABLE_HTTP, USE_GITLAB_WIKI, USE_MILESTONE, USE_OAUTH, USE_PIPELINE, GITLAB_TOOL_POLICY_APPROVE_RAW, GITLAB_TOOL_POLICY_HIDDEN_RAW, GITLAB_ALLOWED_GROUPS, } from "./config.js";
|
|
3
3
|
/** True when the server is running in remote/network mode (SSE or StreamableHTTP transport). */
|
|
4
4
|
const IS_REMOTE = SSE || STREAMABLE_HTTP;
|
|
5
5
|
/**
|
|
@@ -128,18 +128,18 @@ import { createGitLabOAuthProvider } from "./oauth-proxy.js";
|
|
|
128
128
|
import { mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
129
129
|
import { normalizeGitLabApiUrl } from "./utils/url.js";
|
|
130
130
|
import { estimateMergeCommitCount, filterDiffsByPatterns, summarizeWebhookEvents } from "./utils/helpers.js";
|
|
131
|
-
import { sanitizeToolArguments } from "./utils/tool-args.js";
|
|
131
|
+
import { cleanMutuallyExclusiveIdUsernameOptions, LIST_MERGE_REQUESTS_ID_USERNAME_PAIRS, sanitizeToolArguments, } from "./utils/tool-args.js";
|
|
132
132
|
import { parseSearchReplaceBlocks, applySearchReplace, applyUnifiedDiff, } from "./utils/patch-helper.js";
|
|
133
133
|
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
|
|
134
134
|
import { GitLabClientPool } from "./gitlab-client-pool.js";
|
|
135
135
|
import { allTools, readOnlyTools, destructiveTools, parseEnabledToolsets, parseIndividualTools, buildFeatureFlagOverrides, isToolInEnabledToolset, TOOLSET_DEFINITIONS, ALL_TOOLSET_IDS, } from "./tools/registry.js";
|
|
136
136
|
import { BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, // Added
|
|
137
|
-
CreateMergeRequestNoteSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateMergeRequestNoteEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateRepositorySchema, CreateGroupSchema, CreateWikiPageSchema, CreateGroupWikiPageSchema, DeleteBranchSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteProjectMilestoneSchema, DeleteWikiPageSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, EditProjectMilestoneSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetBranchSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GitLabBlameEntrySchema, GetDraftNoteSchema, GetFileContentsSchema, GetIssueLinkSchema, GetIssueSchema, GetLabelSchema, GetMergeRequestDiffsSchema, GetMergeRequestSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetDeploymentSchema, GetEnvironmentSchema, GetNamespaceSchema, GitLabCiLintResultSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectMilestoneSchema, GetProjectSchema, GetRepositoryTreeSchema, GetUsersSchema, GetUserSchema, GitLabUserFullSchema, WhoAmISchema, GitLabCurrentUserSchema, GetWikiPageSchema, GitLabCommitSchema, GitLabCommitStatusSchema, GitLabCompareResultSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabDiffSchema,
|
|
137
|
+
CreateMergeRequestNoteSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateMergeRequestNoteEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateRepositorySchema, CreateGroupSchema, CreateWikiPageSchema, CreateGroupWikiPageSchema, DeleteBranchSchema, GetProtectedBranchSchema, ListProtectedBranchesSchema, ProtectBranchSchema, UnprotectBranchSchema, UpdateDefaultBranchSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteProjectMilestoneSchema, DeleteWikiPageSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, EditProjectMilestoneSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetBranchSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GitLabBlameEntrySchema, GetDraftNoteSchema, GetFileContentsSchema, GetIssueLinkSchema, GetIssueSchema, GetLabelSchema, GetMergeRequestDiffsSchema, GetMergeRequestSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetDeploymentSchema, GetEnvironmentSchema, GetNamespaceSchema, GitLabCiLintResultSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectMilestoneSchema, GetProjectSchema, GetRepositoryTreeSchema, GetUsersSchema, GetUserSchema, GitLabUserFullSchema, WhoAmISchema, GitLabCurrentUserSchema, GetWikiPageSchema, GitLabCommitSchema, GitLabCommitStatusSchema, GitLabCompareResultSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabDiffSchema,
|
|
138
138
|
// Discussion Schemas
|
|
139
139
|
GitLabDiscussionNoteSchema, // Added
|
|
140
140
|
GitLabDiscussionSchema,
|
|
141
141
|
// Draft Notes Schemas
|
|
142
|
-
GitLabDraftNoteSchema, GitLabForkSchema, GitLabBranchSchema, GitLabGroupSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestPipelineSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, 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
|
|
142
|
+
GitLabDraftNoteSchema, GitLabForkSchema, GitLabBranchSchema, GitLabProtectedBranchSchema, GitLabGroupSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestPipelineSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, 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
|
|
143
143
|
GetMergeRequestFileDiffSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, 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, 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";
|
|
144
144
|
import { randomUUID, createCipheriv, createDecipheriv, randomBytes, createHash } from "node:crypto";
|
|
145
145
|
import { pino } from "pino";
|
|
@@ -7074,7 +7074,8 @@ async function handleToolCall(params) {
|
|
|
7074
7074
|
case "list_issues": {
|
|
7075
7075
|
const args = ListIssuesSchema.parse(params.arguments);
|
|
7076
7076
|
const { project_id, ...options } = args;
|
|
7077
|
-
const
|
|
7077
|
+
const cleanedOptions = cleanMutuallyExclusiveIdUsernameOptions(options);
|
|
7078
|
+
const issues = await listIssues(project_id, cleanedOptions);
|
|
7078
7079
|
return {
|
|
7079
7080
|
content: [{ type: "text", text: JSON.stringify(issues, null, 2) }],
|
|
7080
7081
|
};
|
|
@@ -7743,18 +7744,7 @@ async function handleToolCall(params) {
|
|
|
7743
7744
|
}
|
|
7744
7745
|
case "list_merge_requests": {
|
|
7745
7746
|
const { project_id, ...options } = ListMergeRequestsSchema.parse(params.arguments);
|
|
7746
|
-
|
|
7747
|
-
// When both are provided, prefer _username and remove _id to avoid 400 errors.
|
|
7748
|
-
const cleanedOptions = { ...options };
|
|
7749
|
-
if (cleanedOptions.author_id && cleanedOptions.author_username) {
|
|
7750
|
-
delete cleanedOptions.author_id;
|
|
7751
|
-
}
|
|
7752
|
-
if (cleanedOptions.assignee_id && cleanedOptions.assignee_username) {
|
|
7753
|
-
delete cleanedOptions.assignee_id;
|
|
7754
|
-
}
|
|
7755
|
-
if (cleanedOptions.reviewer_id && cleanedOptions.reviewer_username) {
|
|
7756
|
-
delete cleanedOptions.reviewer_id;
|
|
7757
|
-
}
|
|
7747
|
+
const cleanedOptions = cleanMutuallyExclusiveIdUsernameOptions(options, LIST_MERGE_REQUESTS_ID_USERNAME_PAIRS);
|
|
7758
7748
|
const mergeRequests = await listMergeRequests(project_id, cleanedOptions);
|
|
7759
7749
|
return {
|
|
7760
7750
|
content: [{ type: "text", text: JSON.stringify(mergeRequests, null, 2) }],
|
|
@@ -8315,6 +8305,97 @@ async function handleToolCall(params) {
|
|
|
8315
8305
|
content: [{ type: "text", text: JSON.stringify({ status: "deleted", branch: args.branch_name }, null, 2) }],
|
|
8316
8306
|
};
|
|
8317
8307
|
}
|
|
8308
|
+
case "list_protected_branches": {
|
|
8309
|
+
const args = ListProtectedBranchesSchema.parse(params.arguments);
|
|
8310
|
+
const projectId = decodeURIComponent(args.project_id);
|
|
8311
|
+
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
8312
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/protected_branches`);
|
|
8313
|
+
if (args.search)
|
|
8314
|
+
url.searchParams.append("search", args.search);
|
|
8315
|
+
if (args.page)
|
|
8316
|
+
url.searchParams.append("page", String(args.page));
|
|
8317
|
+
if (args.per_page)
|
|
8318
|
+
url.searchParams.append("per_page", String(args.per_page));
|
|
8319
|
+
const response = await fetch(url.toString(), {
|
|
8320
|
+
...getFetchConfig(),
|
|
8321
|
+
});
|
|
8322
|
+
await handleGitLabError(response);
|
|
8323
|
+
const data = z.array(GitLabProtectedBranchSchema).parse(await response.json());
|
|
8324
|
+
return {
|
|
8325
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
8326
|
+
};
|
|
8327
|
+
}
|
|
8328
|
+
case "get_protected_branch": {
|
|
8329
|
+
const args = GetProtectedBranchSchema.parse(params.arguments);
|
|
8330
|
+
const projectId = decodeURIComponent(args.project_id);
|
|
8331
|
+
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
8332
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/protected_branches/${encodeURIComponent(args.branch_name)}`);
|
|
8333
|
+
const response = await fetch(url.toString(), {
|
|
8334
|
+
...getFetchConfig(),
|
|
8335
|
+
});
|
|
8336
|
+
await handleGitLabError(response);
|
|
8337
|
+
const data = GitLabProtectedBranchSchema.parse(await response.json());
|
|
8338
|
+
return {
|
|
8339
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
8340
|
+
};
|
|
8341
|
+
}
|
|
8342
|
+
case "protect_branch": {
|
|
8343
|
+
const args = ProtectBranchSchema.parse(params.arguments);
|
|
8344
|
+
const projectId = decodeURIComponent(args.project_id);
|
|
8345
|
+
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
8346
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/protected_branches`);
|
|
8347
|
+
const body = { name: args.branch_name };
|
|
8348
|
+
if (args.push_access_level !== undefined)
|
|
8349
|
+
body.push_access_level = args.push_access_level;
|
|
8350
|
+
if (args.merge_access_level !== undefined)
|
|
8351
|
+
body.merge_access_level = args.merge_access_level;
|
|
8352
|
+
if (args.unprotect_access_level !== undefined)
|
|
8353
|
+
body.unprotect_access_level = args.unprotect_access_level;
|
|
8354
|
+
if (args.allow_force_push !== undefined)
|
|
8355
|
+
body.allow_force_push = args.allow_force_push;
|
|
8356
|
+
if (args.code_owner_approval_required !== undefined)
|
|
8357
|
+
body.code_owner_approval_required = args.code_owner_approval_required;
|
|
8358
|
+
const response = await fetch(url.toString(), {
|
|
8359
|
+
...getFetchConfig(),
|
|
8360
|
+
method: "POST",
|
|
8361
|
+
body: JSON.stringify(body),
|
|
8362
|
+
});
|
|
8363
|
+
await handleGitLabError(response);
|
|
8364
|
+
const data = GitLabProtectedBranchSchema.parse(await response.json());
|
|
8365
|
+
return {
|
|
8366
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
8367
|
+
};
|
|
8368
|
+
}
|
|
8369
|
+
case "unprotect_branch": {
|
|
8370
|
+
const args = UnprotectBranchSchema.parse(params.arguments);
|
|
8371
|
+
const projectId = decodeURIComponent(args.project_id);
|
|
8372
|
+
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
8373
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/protected_branches/${encodeURIComponent(args.branch_name)}`);
|
|
8374
|
+
const response = await fetch(url.toString(), {
|
|
8375
|
+
...getFetchConfig(),
|
|
8376
|
+
method: "DELETE",
|
|
8377
|
+
});
|
|
8378
|
+
await handleGitLabError(response);
|
|
8379
|
+
return {
|
|
8380
|
+
content: [{ type: "text", text: JSON.stringify({ status: "unprotected", branch: args.branch_name }, null, 2) }],
|
|
8381
|
+
};
|
|
8382
|
+
}
|
|
8383
|
+
case "update_default_branch": {
|
|
8384
|
+
const args = UpdateDefaultBranchSchema.parse(params.arguments);
|
|
8385
|
+
const projectId = decodeURIComponent(args.project_id);
|
|
8386
|
+
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
8387
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}`);
|
|
8388
|
+
const response = await fetch(url.toString(), {
|
|
8389
|
+
...getFetchConfig(),
|
|
8390
|
+
method: "PUT",
|
|
8391
|
+
body: JSON.stringify({ default_branch: args.default_branch }),
|
|
8392
|
+
});
|
|
8393
|
+
await handleGitLabError(response);
|
|
8394
|
+
const data = await response.json();
|
|
8395
|
+
return {
|
|
8396
|
+
content: [{ type: "text", text: JSON.stringify({ status: "updated", default_branch: args.default_branch, project: data }, null, 2) }],
|
|
8397
|
+
};
|
|
8398
|
+
}
|
|
8318
8399
|
default:
|
|
8319
8400
|
throw new Error(`Unknown tool: ${params.name}`);
|
|
8320
8401
|
}
|
|
@@ -8719,9 +8800,9 @@ async function startStreamableHTTPServer() {
|
|
|
8719
8800
|
return null;
|
|
8720
8801
|
};
|
|
8721
8802
|
/**
|
|
8722
|
-
* Set or reset timeout for session auth
|
|
8803
|
+
* Set or reset timeout for session auth.
|
|
8723
8804
|
* After SESSION_TIMEOUT_SECONDS of inactivity, the auth token is removed
|
|
8724
|
-
*
|
|
8805
|
+
* and the Streamable HTTP transport is closed so the session slot is released.
|
|
8725
8806
|
*/
|
|
8726
8807
|
const setAuthTimeout = (sessionId) => {
|
|
8727
8808
|
// Clear existing timeout if any
|
|
@@ -8733,6 +8814,13 @@ async function startStreamableHTTPServer() {
|
|
|
8733
8814
|
delete authBySession[sessionId];
|
|
8734
8815
|
delete authTimeouts[sessionId];
|
|
8735
8816
|
metrics.expiredSessions++;
|
|
8817
|
+
// Close the transport to free the slot; without this, stale sessions accumulate and exhaust MAX_SESSIONS.
|
|
8818
|
+
const transport = streamableTransports[sessionId];
|
|
8819
|
+
if (transport) {
|
|
8820
|
+
transport.close().catch(err => {
|
|
8821
|
+
logger.error(`Error closing transport for expired session ${sessionId}:`, err);
|
|
8822
|
+
});
|
|
8823
|
+
}
|
|
8736
8824
|
}
|
|
8737
8825
|
}, SESSION_TIMEOUT_SECONDS * 1000);
|
|
8738
8826
|
};
|
|
@@ -8957,7 +9045,7 @@ async function startStreamableHTTPServer() {
|
|
|
8957
9045
|
storedTtlSeconds: OAUTH_STATELESS_STORED_TTL_SECONDS,
|
|
8958
9046
|
}
|
|
8959
9047
|
: null;
|
|
8960
|
-
const oauthProvider = createGitLabOAuthProvider(gitlabBaseUrl, GITLAB_OAUTH_APP_ID, "GitLab MCP Server", GITLAB_READ_ONLY_MODE, GITLAB_OAUTH_SCOPES, GITLAB_OAUTH_CALLBACK_PROXY, callbackUrl, statelessOptions);
|
|
9048
|
+
const oauthProvider = createGitLabOAuthProvider(gitlabBaseUrl, GITLAB_OAUTH_APP_ID, "GitLab MCP Server", GITLAB_READ_ONLY_MODE, GITLAB_OAUTH_SCOPES, GITLAB_ALLOWED_GROUPS, GITLAB_OAUTH_CALLBACK_PROXY, callbackUrl, statelessOptions);
|
|
8961
9049
|
const scopesSupported = GITLAB_OAUTH_SCOPES ?? ["api", "read_api", "read_user"];
|
|
8962
9050
|
// When server URL has a path (e.g. behind Kong), the SDK's well-known metadata
|
|
8963
9051
|
// advertises root-level endpoints. Override to use path-prefixed endpoints.
|
package/build/oauth-proxy.js
CHANGED
|
@@ -127,7 +127,11 @@ class GitLabOAuthServerProvider {
|
|
|
127
127
|
// serialised into opaque OAuth values and the in-memory caches above are
|
|
128
128
|
// bypassed. Enabled independently of callback-proxy mode.
|
|
129
129
|
_stateless;
|
|
130
|
-
|
|
130
|
+
// Group allowlist (optional). When set, tokens are rejected unless the
|
|
131
|
+
// authenticated user is a direct or inherited member of at least one group.
|
|
132
|
+
// Checked once at token issuance (exchangeAuthorizationCode), not per request.
|
|
133
|
+
_allowedGroups;
|
|
134
|
+
constructor(gitlabBaseUrl, gitlabAppId, resourceName, readOnly, customScopes, allowedGroups, callbackProxyEnabled = false, callbackUrl = "", stateless = null) {
|
|
131
135
|
this._gitlabBaseUrl = gitlabBaseUrl;
|
|
132
136
|
this._gitlabAppId = gitlabAppId;
|
|
133
137
|
this._resourceName = resourceName;
|
|
@@ -137,6 +141,7 @@ class GitLabOAuthServerProvider {
|
|
|
137
141
|
: readOnly
|
|
138
142
|
? REQUIRED_GITLAB_SCOPES_RO
|
|
139
143
|
: REQUIRED_GITLAB_SCOPES_RW;
|
|
144
|
+
this._allowedGroups = allowedGroups ?? null;
|
|
140
145
|
this._callbackProxyEnabled = callbackProxyEnabled;
|
|
141
146
|
this._callbackUrl = callbackUrl;
|
|
142
147
|
this._stateless = stateless;
|
|
@@ -313,6 +318,7 @@ class GitLabOAuthServerProvider {
|
|
|
313
318
|
}
|
|
314
319
|
// ---- Token exchange ----------------------------------------------------
|
|
315
320
|
async exchangeAuthorizationCode(client, authorizationCode, codeVerifier, redirectUri, resource) {
|
|
321
|
+
let tokens;
|
|
316
322
|
if (this._callbackProxyEnabled) {
|
|
317
323
|
// --- Callback proxy mode ---
|
|
318
324
|
// The authorizationCode is a proxy code we generated in handleCallback().
|
|
@@ -373,32 +379,74 @@ class GitLabOAuthServerProvider {
|
|
|
373
379
|
throw new ServerError("PKCE verification failed");
|
|
374
380
|
}
|
|
375
381
|
}
|
|
376
|
-
|
|
382
|
+
tokens = entry.tokens;
|
|
377
383
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
384
|
+
else {
|
|
385
|
+
// --- Passthrough mode (original behavior) ---
|
|
386
|
+
const params = new URLSearchParams({
|
|
387
|
+
grant_type: "authorization_code",
|
|
388
|
+
client_id: this._gitlabAppId,
|
|
389
|
+
code: authorizationCode,
|
|
390
|
+
});
|
|
391
|
+
if (codeVerifier)
|
|
392
|
+
params.append("code_verifier", codeVerifier);
|
|
393
|
+
if (redirectUri)
|
|
394
|
+
params.append("redirect_uri", redirectUri);
|
|
395
|
+
if (resource)
|
|
396
|
+
params.append("resource", resource.href);
|
|
397
|
+
const response = await fetch(`${this._gitlabBaseUrl}/oauth/token`, {
|
|
398
|
+
method: "POST",
|
|
399
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
400
|
+
body: params.toString(),
|
|
401
|
+
});
|
|
402
|
+
if (!response.ok) {
|
|
403
|
+
const body = await response.text();
|
|
404
|
+
logger.error(`Token exchange failed (${response.status}): ${body}`);
|
|
405
|
+
throw new ServerError(`Token exchange failed: ${response.status}`);
|
|
406
|
+
}
|
|
407
|
+
const data = await response.json();
|
|
408
|
+
tokens = OAuthTokensSchema.parse(data);
|
|
399
409
|
}
|
|
400
|
-
|
|
401
|
-
|
|
410
|
+
if (this._allowedGroups) {
|
|
411
|
+
const isMember = await this._checkGroupMembership(tokens.access_token);
|
|
412
|
+
if (!isMember) {
|
|
413
|
+
logger.warn({ allowedGroups: this._allowedGroups }, "Token issuance denied: user is not a member of any allowed group");
|
|
414
|
+
throw new ServerError("Access denied: user is not a member of an allowed group");
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return tokens;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Returns true if the token owner belongs to at least one group whose
|
|
421
|
+
* full_path equals or is a sub-path of any configured allowed group.
|
|
422
|
+
*
|
|
423
|
+
* Example: allowedGroups=["my-org"] allows members of "my-org",
|
|
424
|
+
* "my-org/team-a", "my-org/team-a/squad-1", etc.
|
|
425
|
+
*/
|
|
426
|
+
async _checkGroupMembership(token) {
|
|
427
|
+
const allowedPaths = this._allowedGroups.map((g) => g.toLowerCase());
|
|
428
|
+
let page = 1;
|
|
429
|
+
while (true) {
|
|
430
|
+
const res = await fetch(`${this._gitlabBaseUrl}/api/v4/groups?min_access_level=10&per_page=100&page=${page}`, {
|
|
431
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
432
|
+
});
|
|
433
|
+
if (!res.ok)
|
|
434
|
+
break;
|
|
435
|
+
const groups = (await res.json());
|
|
436
|
+
if (groups.length === 0)
|
|
437
|
+
break;
|
|
438
|
+
const matched = groups.some((g) => {
|
|
439
|
+
const fp = g.full_path.toLowerCase();
|
|
440
|
+
return allowedPaths.some((allowed) => fp === allowed || fp.startsWith(`${allowed}/`));
|
|
441
|
+
});
|
|
442
|
+
if (matched)
|
|
443
|
+
return true;
|
|
444
|
+
const totalPages = Number.parseInt(res.headers.get("x-total-pages") ?? "1", 10);
|
|
445
|
+
if (page >= totalPages)
|
|
446
|
+
break;
|
|
447
|
+
page++;
|
|
448
|
+
}
|
|
449
|
+
return false;
|
|
402
450
|
}
|
|
403
451
|
// ---- Refresh token -----------------------------------------------------
|
|
404
452
|
async exchangeRefreshToken(_client, refreshToken, scopes, resource) {
|
|
@@ -598,6 +646,6 @@ class GitLabOAuthServerProvider {
|
|
|
598
646
|
* callback-proxy state is encoded into opaque OAuth values
|
|
599
647
|
* instead of an in-memory cache, enabling multi-pod deploys.
|
|
600
648
|
*/
|
|
601
|
-
export function createGitLabOAuthProvider(gitlabBaseUrl, gitlabAppId, resourceName = "GitLab MCP Server", readOnly = false, customScopes, callbackProxyEnabled = false, callbackUrl = "", stateless = null) {
|
|
602
|
-
return new GitLabOAuthServerProvider(gitlabBaseUrl, gitlabAppId, resourceName, readOnly, customScopes, callbackProxyEnabled, callbackUrl, stateless);
|
|
649
|
+
export function createGitLabOAuthProvider(gitlabBaseUrl, gitlabAppId, resourceName = "GitLab MCP Server", readOnly = false, customScopes, allowedGroups, callbackProxyEnabled = false, callbackUrl = "", stateless = null) {
|
|
650
|
+
return new GitLabOAuthServerProvider(gitlabBaseUrl, gitlabAppId, resourceName, readOnly, customScopes, allowedGroups, callbackProxyEnabled, callbackUrl, stateless);
|
|
603
651
|
}
|
package/build/schemas.js
CHANGED
|
@@ -1515,6 +1515,79 @@ export const ListBranchesSchema = ProjectParamsSchema.extend({
|
|
|
1515
1515
|
export const DeleteBranchSchema = ProjectParamsSchema.extend({
|
|
1516
1516
|
branch_name: z.string().describe("Name of the branch to delete"),
|
|
1517
1517
|
});
|
|
1518
|
+
// Protected Branches related schemas
|
|
1519
|
+
export const ListProtectedBranchesSchema = ProjectParamsSchema.extend({
|
|
1520
|
+
search: z.string().optional().describe("Search term to filter protected branches by name"),
|
|
1521
|
+
}).merge(PaginationOptionsSchema);
|
|
1522
|
+
export const GetProtectedBranchSchema = ProjectParamsSchema.extend({
|
|
1523
|
+
branch_name: z.string().describe("Name of the protected branch"),
|
|
1524
|
+
});
|
|
1525
|
+
// String-aware boolean preprocessing: correctly handles "false" → false
|
|
1526
|
+
const stringBoolean = z.preprocess((val) => {
|
|
1527
|
+
if (typeof val === "string") {
|
|
1528
|
+
const lower = val.toLowerCase();
|
|
1529
|
+
if (lower === "false" || lower === "0")
|
|
1530
|
+
return false;
|
|
1531
|
+
if (lower === "true" || lower === "1")
|
|
1532
|
+
return true;
|
|
1533
|
+
}
|
|
1534
|
+
return val;
|
|
1535
|
+
}, z.boolean().optional());
|
|
1536
|
+
const protectedBranchAccessLevel = z.coerce
|
|
1537
|
+
.number()
|
|
1538
|
+
.int()
|
|
1539
|
+
.refine((level) => [0, 30, 40, 60].includes(level), {
|
|
1540
|
+
message: "Access level must be one of 0 (No access), 30 (Developer), 40 (Maintainer), or 60 (Admin)",
|
|
1541
|
+
});
|
|
1542
|
+
export const ProtectBranchSchema = z.preprocess((input) => {
|
|
1543
|
+
if (typeof input !== "object" || input === null) {
|
|
1544
|
+
return input;
|
|
1545
|
+
}
|
|
1546
|
+
const args = { ...input };
|
|
1547
|
+
if (!args.branch_name && args.name) {
|
|
1548
|
+
args.branch_name = args.name;
|
|
1549
|
+
}
|
|
1550
|
+
return args;
|
|
1551
|
+
}, ProjectParamsSchema.extend({
|
|
1552
|
+
branch_name: z.string().describe("Branch name or wildcard pattern to protect"),
|
|
1553
|
+
name: z
|
|
1554
|
+
.string()
|
|
1555
|
+
.optional()
|
|
1556
|
+
.describe("Deprecated alias for branch_name; prefer branch_name for consistency"),
|
|
1557
|
+
push_access_level: protectedBranchAccessLevel
|
|
1558
|
+
.optional()
|
|
1559
|
+
.describe("Access level for pushing (0=No access, 30=Developer, 40=Maintainer, 60=Admin). GitLab default applies when omitted."),
|
|
1560
|
+
merge_access_level: protectedBranchAccessLevel
|
|
1561
|
+
.optional()
|
|
1562
|
+
.describe("Access level for merging (0=No access, 30=Developer, 40=Maintainer, 60=Admin). GitLab default applies when omitted."),
|
|
1563
|
+
unprotect_access_level: protectedBranchAccessLevel
|
|
1564
|
+
.optional()
|
|
1565
|
+
.describe("Access level for unprotecting (0=No access, 30=Developer, 40=Maintainer, 60=Admin). GitLab default applies when omitted."),
|
|
1566
|
+
allow_force_push: stringBoolean.describe("Allow force push to the protected branch. Default: false"),
|
|
1567
|
+
code_owner_approval_required: stringBoolean.describe("Require code owner approval before merging (PREMIUM). Default: false"),
|
|
1568
|
+
}));
|
|
1569
|
+
export const UnprotectBranchSchema = ProjectParamsSchema.extend({
|
|
1570
|
+
branch_name: z.string().describe("Name of the protected branch to unprotect"),
|
|
1571
|
+
});
|
|
1572
|
+
// Update default branch schema
|
|
1573
|
+
export const UpdateDefaultBranchSchema = ProjectParamsSchema.extend({
|
|
1574
|
+
default_branch: z.string().describe("The new default branch name for the project"),
|
|
1575
|
+
});
|
|
1576
|
+
export const GitLabProtectedBranchAccessLevelSchema = z.object({
|
|
1577
|
+
access_level: z.number().nullable().optional(),
|
|
1578
|
+
access_level_description: z.string().optional(),
|
|
1579
|
+
user_id: z.number().optional(),
|
|
1580
|
+
group_id: z.number().optional(),
|
|
1581
|
+
});
|
|
1582
|
+
export const GitLabProtectedBranchSchema = z.object({
|
|
1583
|
+
id: z.number().optional(),
|
|
1584
|
+
name: z.string(),
|
|
1585
|
+
push_access_levels: z.array(GitLabProtectedBranchAccessLevelSchema).optional(),
|
|
1586
|
+
merge_access_levels: z.array(GitLabProtectedBranchAccessLevelSchema).optional(),
|
|
1587
|
+
unprotect_access_levels: z.array(GitLabProtectedBranchAccessLevelSchema).optional(),
|
|
1588
|
+
allow_force_push: z.boolean().optional(),
|
|
1589
|
+
code_owner_approval_required: z.boolean().optional(),
|
|
1590
|
+
});
|
|
1518
1591
|
export const GitLabBranchSchema = z.object({
|
|
1519
1592
|
name: z.string(),
|
|
1520
1593
|
commit: z.object({
|
|
@@ -1758,13 +1831,19 @@ export const ListIssuesSchema = z
|
|
|
1758
1831
|
assignee_id: z.coerce
|
|
1759
1832
|
.string()
|
|
1760
1833
|
.optional()
|
|
1761
|
-
.describe("Return issues assigned to the given user ID
|
|
1834
|
+
.describe("Return issues assigned to the given user ID (user id, none, or any). Mutually exclusive with assignee_username."),
|
|
1762
1835
|
assignee_username: z
|
|
1763
1836
|
.array(z.string())
|
|
1764
1837
|
.optional()
|
|
1765
|
-
.describe("Return issues assigned to the given username"),
|
|
1766
|
-
author_id: z.coerce
|
|
1767
|
-
|
|
1838
|
+
.describe("Return issues assigned to the given username. Mutually exclusive with assignee_id."),
|
|
1839
|
+
author_id: z.coerce
|
|
1840
|
+
.string()
|
|
1841
|
+
.optional()
|
|
1842
|
+
.describe("Return issues created by the given user ID. Mutually exclusive with author_username."),
|
|
1843
|
+
author_username: z
|
|
1844
|
+
.string()
|
|
1845
|
+
.optional()
|
|
1846
|
+
.describe("Return issues created by the given username. Mutually exclusive with author_id."),
|
|
1768
1847
|
confidential: z.coerce.boolean().optional().describe("Filter confidential or public issues"),
|
|
1769
1848
|
created_after: z.string().optional().describe("Return issues created after the given time"),
|
|
1770
1849
|
created_before: z.string().optional().describe("Return issues created before the given time"),
|