@zereight/mcp-gitlab 2.0.32 → 2.0.33
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/build/index.js +165 -6
- package/build/schemas.js +66 -0
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -50,7 +50,7 @@ GitLabDiscussionNoteSchema, // Added
|
|
|
50
50
|
GitLabDiscussionSchema,
|
|
51
51
|
// Draft Notes Schemas
|
|
52
52
|
GitLabDraftNoteSchema, GitLabForkSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabDeploymentSchema, GitLabEnvironmentSchema, GitLabPipelineSchema, GitLabPipelineTriggerJobSchema, GitLabProjectMemberSchema, GitLabProjectSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabSearchResponseSchema, GitLabTreeItemSchema, GitLabTreeSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabWikiPageSchema, GroupIteration, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
|
|
53
|
-
ListMergeRequestDiscussionsSchema, ListMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, MarkdownUploadSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, GetJobArtifactFileSchema, GitLabArtifactEntrySchema, ListJobArtifactsSchema, MergeMergeRequestSchema, ApproveMergeRequestSchema, UnapproveMergeRequestSchema, GetMergeRequestApprovalStateSchema, GitLabMergeRequestApprovalsResponseSchema, GitLabMergeRequestApprovalStateSchema, MyIssuesSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema, ExecuteGraphQLSchema, GitLabReleaseSchema, ListReleasesSchema, GetReleaseSchema, CreateReleaseSchema, UpdateReleaseSchema, DeleteReleaseSchema, CreateReleaseEvidenceSchema, DownloadReleaseAssetSchema, GetMergeRequestNotesSchema, GetMergeRequestNoteSchema, DeleteMergeRequestDiscussionNoteSchema, ResolveMergeRequestThreadSchema, } from "./schemas.js";
|
|
53
|
+
ListMergeRequestDiscussionsSchema, ListMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, MarkdownUploadSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, GetJobArtifactFileSchema, GitLabArtifactEntrySchema, ListJobArtifactsSchema, MergeMergeRequestSchema, ApproveMergeRequestSchema, UnapproveMergeRequestSchema, GetMergeRequestApprovalStateSchema, GitLabMergeRequestApprovalsResponseSchema, GitLabMergeRequestApprovalStateSchema, MyIssuesSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema, ExecuteGraphQLSchema, GitLabReleaseSchema, ListReleasesSchema, GetReleaseSchema, CreateReleaseSchema, UpdateReleaseSchema, DeleteReleaseSchema, CreateReleaseEvidenceSchema, DownloadReleaseAssetSchema, GetMergeRequestNotesSchema, GetMergeRequestNoteSchema, DeleteMergeRequestDiscussionNoteSchema, ResolveMergeRequestThreadSchema, ListWebhooksSchema, ListWebhookEventsSchema, GetWebhookEventSchema, } from "./schemas.js";
|
|
54
54
|
import { randomUUID } from "node:crypto";
|
|
55
55
|
import { pino } from "pino";
|
|
56
56
|
const logger = pino({
|
|
@@ -226,9 +226,10 @@ function validateConfiguration() {
|
|
|
226
226
|
const remoteAuth = getConfig("remote-auth", "REMOTE_AUTHORIZATION") === "true";
|
|
227
227
|
const useOAuth = getConfig("use-oauth", "GITLAB_USE_OAUTH") === "true";
|
|
228
228
|
const hasToken = !!getConfig("token", "GITLAB_PERSONAL_ACCESS_TOKEN");
|
|
229
|
+
const hasJobToken = !!getConfig("job-token", "GITLAB_JOB_TOKEN");
|
|
229
230
|
const hasCookie = !!getConfig("cookie-path", "GITLAB_AUTH_COOKIE_PATH");
|
|
230
|
-
if (!remoteAuth && !useOAuth && !hasToken && !hasCookie) {
|
|
231
|
-
errors.push("Either --token, --cookie-path, --use-oauth=true, or --remote-auth=true must be set (or use environment variables)");
|
|
231
|
+
if (!remoteAuth && !useOAuth && !hasToken && !hasJobToken && !hasCookie) {
|
|
232
|
+
errors.push("Either --token, --job-token, --cookie-path, --use-oauth=true, or --remote-auth=true must be set (or use environment variables)");
|
|
232
233
|
}
|
|
233
234
|
const enableDynamicApiUrl = getConfig("enable-dynamic-api-url", "ENABLE_DYNAMIC_API_URL") === "true";
|
|
234
235
|
if (enableDynamicApiUrl && !remoteAuth) {
|
|
@@ -242,6 +243,7 @@ function validateConfiguration() {
|
|
|
242
243
|
logger.info("Configuration validation passed");
|
|
243
244
|
}
|
|
244
245
|
const GITLAB_PERSONAL_ACCESS_TOKEN = getConfig("token", "GITLAB_PERSONAL_ACCESS_TOKEN");
|
|
246
|
+
const GITLAB_JOB_TOKEN = getConfig("job-token", "GITLAB_JOB_TOKEN");
|
|
245
247
|
let OAUTH_ACCESS_TOKEN = null;
|
|
246
248
|
let oauthClient = null;
|
|
247
249
|
/**
|
|
@@ -492,6 +494,10 @@ function buildAuthHeaders() {
|
|
|
492
494
|
}
|
|
493
495
|
return {}; // No auth headers if no session context
|
|
494
496
|
}
|
|
497
|
+
// CI job tokens use a dedicated header (not Bearer/Private-Token)
|
|
498
|
+
if (GITLAB_JOB_TOKEN) {
|
|
499
|
+
return { "JOB-TOKEN": String(GITLAB_JOB_TOKEN) };
|
|
500
|
+
}
|
|
495
501
|
// Standard mode: prioritize OAuth token, then fall back to environment token
|
|
496
502
|
const token = OAUTH_ACCESS_TOKEN || GITLAB_PERSONAL_ACCESS_TOKEN;
|
|
497
503
|
if (IS_OLD && token) {
|
|
@@ -1152,6 +1158,21 @@ const allTools = [
|
|
|
1152
1158
|
description: "Download a release asset file by direct asset path",
|
|
1153
1159
|
inputSchema: toJSONSchema(DownloadReleaseAssetSchema),
|
|
1154
1160
|
},
|
|
1161
|
+
{
|
|
1162
|
+
name: "list_webhooks",
|
|
1163
|
+
description: "List all configured webhooks for a GitLab project or group. Provide either project_id or group_id.",
|
|
1164
|
+
inputSchema: toJSONSchema(ListWebhooksSchema),
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
name: "list_webhook_events",
|
|
1168
|
+
description: "List recent webhook events (past 7 days) for a project or group webhook. Use summary mode for overview, then get_webhook_event for full details.",
|
|
1169
|
+
inputSchema: toJSONSchema(ListWebhookEventsSchema),
|
|
1170
|
+
},
|
|
1171
|
+
{
|
|
1172
|
+
name: "get_webhook_event",
|
|
1173
|
+
description: "Get full details of a specific webhook event by ID, including request/response payloads. Searches up to 500 most recent events.",
|
|
1174
|
+
inputSchema: toJSONSchema(GetWebhookEventSchema),
|
|
1175
|
+
},
|
|
1155
1176
|
];
|
|
1156
1177
|
// Define which tools are read-only
|
|
1157
1178
|
const readOnlyTools = new Set([
|
|
@@ -1214,6 +1235,9 @@ const readOnlyTools = new Set([
|
|
|
1214
1235
|
"get_release",
|
|
1215
1236
|
"download_release_asset",
|
|
1216
1237
|
"get_merge_request_approval_state",
|
|
1238
|
+
"list_webhooks",
|
|
1239
|
+
"list_webhook_events",
|
|
1240
|
+
"get_webhook_event",
|
|
1217
1241
|
]);
|
|
1218
1242
|
// Define which tools are related to wiki and can be toggled by USE_GITLAB_WIKI
|
|
1219
1243
|
const wikiToolNames = new Set([
|
|
@@ -1439,6 +1463,15 @@ const TOOLSET_DEFINITIONS = [
|
|
|
1439
1463
|
"download_attachment",
|
|
1440
1464
|
]),
|
|
1441
1465
|
},
|
|
1466
|
+
{
|
|
1467
|
+
id: "webhooks",
|
|
1468
|
+
isDefault: false,
|
|
1469
|
+
tools: new Set([
|
|
1470
|
+
"list_webhooks",
|
|
1471
|
+
"list_webhook_events",
|
|
1472
|
+
"get_webhook_event",
|
|
1473
|
+
]),
|
|
1474
|
+
},
|
|
1442
1475
|
];
|
|
1443
1476
|
// Derived lookup: tool name → toolset ID
|
|
1444
1477
|
const TOOLSET_BY_TOOL_NAME = new Map();
|
|
@@ -2926,7 +2959,7 @@ async function approveMergeRequest(projectId, mergeRequestIid, sha, approvalPass
|
|
|
2926
2959
|
body: JSON.stringify(body),
|
|
2927
2960
|
});
|
|
2928
2961
|
await handleGitLabError(response);
|
|
2929
|
-
return
|
|
2962
|
+
return parseApprovalsResponse(await response.json());
|
|
2930
2963
|
}
|
|
2931
2964
|
/**
|
|
2932
2965
|
* Unapprove a previously approved merge request
|
|
@@ -2944,7 +2977,7 @@ async function unapproveMergeRequest(projectId, mergeRequestIid) {
|
|
|
2944
2977
|
body: JSON.stringify({}),
|
|
2945
2978
|
});
|
|
2946
2979
|
await handleGitLabError(response);
|
|
2947
|
-
return
|
|
2980
|
+
return parseApprovalsResponse(await response.json());
|
|
2948
2981
|
}
|
|
2949
2982
|
/**
|
|
2950
2983
|
* Get the approval state of a merge request
|
|
@@ -2981,7 +3014,16 @@ async function getMergeRequestApprovalsFallback(projectId, mergeRequestIid) {
|
|
|
2981
3014
|
method: "GET",
|
|
2982
3015
|
});
|
|
2983
3016
|
await handleGitLabError(approvalsResponse);
|
|
2984
|
-
|
|
3017
|
+
return parseApprovalsResponse(await approvalsResponse.json());
|
|
3018
|
+
}
|
|
3019
|
+
/**
|
|
3020
|
+
* Parse the response from POST /approve and POST /unapprove endpoints.
|
|
3021
|
+
* These endpoints return the approvals format (approved_by contains nested
|
|
3022
|
+
* { user: {...} } objects), which must be converted to the flat format
|
|
3023
|
+
* used by GitLabMergeRequestApprovalStateSchema.
|
|
3024
|
+
*/
|
|
3025
|
+
function parseApprovalsResponse(responseJson) {
|
|
3026
|
+
const parsedApprovals = GitLabMergeRequestApprovalsResponseSchema.parse(responseJson);
|
|
2985
3027
|
const approvedByUsers = getUniqueApprovalUsers((parsedApprovals.approved_by || []).map(approvedByEntry => approvedByEntry.user));
|
|
2986
3028
|
const approvedByUsernames = approvedByUsers.map(user => user.username);
|
|
2987
3029
|
return GitLabMergeRequestApprovalStateSchema.parse({
|
|
@@ -3624,6 +3666,89 @@ async function listGroupProjects(options) {
|
|
|
3624
3666
|
const projects = await response.json();
|
|
3625
3667
|
return GitLabProjectSchema.array().parse(projects);
|
|
3626
3668
|
}
|
|
3669
|
+
// Webhook API helper functions
|
|
3670
|
+
/**
|
|
3671
|
+
* Build the base URL for webhooks (project or group)
|
|
3672
|
+
*/
|
|
3673
|
+
function buildWebhookBaseUrl(projectId, groupId) {
|
|
3674
|
+
if (projectId) {
|
|
3675
|
+
projectId = decodeURIComponent(projectId);
|
|
3676
|
+
return `${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/hooks`;
|
|
3677
|
+
}
|
|
3678
|
+
const decodedGroupId = decodeURIComponent(groupId);
|
|
3679
|
+
return `${getEffectiveApiUrl()}/groups/${encodeURIComponent(decodedGroupId)}/hooks`;
|
|
3680
|
+
}
|
|
3681
|
+
/**
|
|
3682
|
+
* List webhooks for a project or group
|
|
3683
|
+
*/
|
|
3684
|
+
async function listWebhooks(options) {
|
|
3685
|
+
const url = new URL(buildWebhookBaseUrl(options.project_id, options.group_id));
|
|
3686
|
+
if (options.page)
|
|
3687
|
+
url.searchParams.append("page", options.page.toString());
|
|
3688
|
+
if (options.per_page)
|
|
3689
|
+
url.searchParams.append("per_page", options.per_page.toString());
|
|
3690
|
+
const response = await fetch(url.toString(), { ...getFetchConfig() });
|
|
3691
|
+
await handleGitLabError(response);
|
|
3692
|
+
return (await response.json());
|
|
3693
|
+
}
|
|
3694
|
+
/**
|
|
3695
|
+
* Summarize webhook events by stripping heavy payload fields
|
|
3696
|
+
*/
|
|
3697
|
+
function summarizeWebhookEvents(events) {
|
|
3698
|
+
return events.map(event => ({
|
|
3699
|
+
id: event.id,
|
|
3700
|
+
url: event.url,
|
|
3701
|
+
trigger: event.trigger,
|
|
3702
|
+
response_status: event.response_status,
|
|
3703
|
+
execution_duration: event.execution_duration,
|
|
3704
|
+
}));
|
|
3705
|
+
}
|
|
3706
|
+
/**
|
|
3707
|
+
* Fetch a single page of webhook events
|
|
3708
|
+
*/
|
|
3709
|
+
async function fetchWebhookEventsPage(baseUrl, page, perPage, status) {
|
|
3710
|
+
const url = new URL(baseUrl);
|
|
3711
|
+
url.searchParams.set("page", page.toString());
|
|
3712
|
+
url.searchParams.set("per_page", perPage.toString());
|
|
3713
|
+
if (status !== undefined)
|
|
3714
|
+
url.searchParams.append("status", String(status));
|
|
3715
|
+
const response = await fetch(url.toString(), { ...getFetchConfig() });
|
|
3716
|
+
await handleGitLabError(response);
|
|
3717
|
+
return (await response.json());
|
|
3718
|
+
}
|
|
3719
|
+
/**
|
|
3720
|
+
* List webhook events for a project or group webhook
|
|
3721
|
+
*/
|
|
3722
|
+
async function listWebhookEvents(options) {
|
|
3723
|
+
const eventsUrl = `${buildWebhookBaseUrl(options.project_id, options.group_id)}/${options.hook_id}/events`;
|
|
3724
|
+
const events = await fetchWebhookEventsPage(eventsUrl, options.page ?? 1, options.per_page ?? 20, options.status);
|
|
3725
|
+
return options.summary ? summarizeWebhookEvents(events) : events;
|
|
3726
|
+
}
|
|
3727
|
+
/**
|
|
3728
|
+
* Get a specific webhook event by ID (searches up to 500 recent events)
|
|
3729
|
+
*/
|
|
3730
|
+
async function getWebhookEvent(options) {
|
|
3731
|
+
const eventsUrl = `${buildWebhookBaseUrl(options.project_id, options.group_id)}/${options.hook_id}/events`;
|
|
3732
|
+
// GitLab enforces max per_page=20 for webhook events
|
|
3733
|
+
const perPage = 20;
|
|
3734
|
+
if (options.page) {
|
|
3735
|
+
// Direct page lookup — single API call
|
|
3736
|
+
const events = await fetchWebhookEventsPage(eventsUrl, options.page, perPage);
|
|
3737
|
+
const match = events.find(e => e.id === options.event_id);
|
|
3738
|
+
return match ?? null;
|
|
3739
|
+
}
|
|
3740
|
+
// Auto-paginate up to 500 events
|
|
3741
|
+
const maxPages = 25;
|
|
3742
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
3743
|
+
const events = await fetchWebhookEventsPage(eventsUrl, page, perPage);
|
|
3744
|
+
const match = events.find(e => e.id === options.event_id);
|
|
3745
|
+
if (match)
|
|
3746
|
+
return match;
|
|
3747
|
+
if (events.length < perPage)
|
|
3748
|
+
break;
|
|
3749
|
+
}
|
|
3750
|
+
return null;
|
|
3751
|
+
}
|
|
3627
3752
|
// Wiki API helper functions
|
|
3628
3753
|
/**
|
|
3629
3754
|
* List wiki pages in a project
|
|
@@ -6102,6 +6227,40 @@ async function handleToolCall(params) {
|
|
|
6102
6227
|
content: [{ type: "text", text: assetContent }],
|
|
6103
6228
|
};
|
|
6104
6229
|
}
|
|
6230
|
+
case "list_webhooks": {
|
|
6231
|
+
const args = ListWebhooksSchema.parse(params.arguments);
|
|
6232
|
+
const webhooks = await listWebhooks(args);
|
|
6233
|
+
return {
|
|
6234
|
+
content: [{ type: "text", text: JSON.stringify(webhooks, null, 2) }],
|
|
6235
|
+
};
|
|
6236
|
+
}
|
|
6237
|
+
case "list_webhook_events": {
|
|
6238
|
+
const args = ListWebhookEventsSchema.parse(params.arguments);
|
|
6239
|
+
const events = await listWebhookEvents(args);
|
|
6240
|
+
return {
|
|
6241
|
+
content: [{ type: "text", text: JSON.stringify(events, null, 2) }],
|
|
6242
|
+
};
|
|
6243
|
+
}
|
|
6244
|
+
case "get_webhook_event": {
|
|
6245
|
+
const args = GetWebhookEventSchema.parse(params.arguments);
|
|
6246
|
+
const event = await getWebhookEvent(args);
|
|
6247
|
+
if (!event) {
|
|
6248
|
+
const searchScope = args.page
|
|
6249
|
+
? `on page ${args.page}`
|
|
6250
|
+
: "in the 500 most recent events";
|
|
6251
|
+
return {
|
|
6252
|
+
content: [
|
|
6253
|
+
{
|
|
6254
|
+
type: "text",
|
|
6255
|
+
text: JSON.stringify({ error: `Webhook event ${args.event_id} not found ${searchScope}` }, null, 2),
|
|
6256
|
+
},
|
|
6257
|
+
],
|
|
6258
|
+
};
|
|
6259
|
+
}
|
|
6260
|
+
return {
|
|
6261
|
+
content: [{ type: "text", text: JSON.stringify(event, null, 2) }],
|
|
6262
|
+
};
|
|
6263
|
+
}
|
|
6105
6264
|
default:
|
|
6106
6265
|
throw new Error(`Unknown tool: ${params.name}`);
|
|
6107
6266
|
}
|
package/build/schemas.js
CHANGED
|
@@ -2449,3 +2449,69 @@ export const DownloadReleaseAssetSchema = z.object({
|
|
|
2449
2449
|
.string()
|
|
2450
2450
|
.describe("Path to the release asset file as specified when creating or updating its link"),
|
|
2451
2451
|
});
|
|
2452
|
+
// --- Webhook schemas ---
|
|
2453
|
+
export const ListWebhooksSchema = z
|
|
2454
|
+
.object({
|
|
2455
|
+
project_id: z.coerce
|
|
2456
|
+
.string()
|
|
2457
|
+
.optional()
|
|
2458
|
+
.describe("Project ID or URL-encoded path. Provide either project_id or group_id, not both."),
|
|
2459
|
+
group_id: z.coerce
|
|
2460
|
+
.string()
|
|
2461
|
+
.optional()
|
|
2462
|
+
.describe("Group ID or URL-encoded path. Provide either project_id or group_id, not both."),
|
|
2463
|
+
})
|
|
2464
|
+
.merge(PaginationOptionsSchema)
|
|
2465
|
+
.refine(data => (data.project_id || data.group_id) && !(data.project_id && data.group_id), {
|
|
2466
|
+
message: "Provide exactly one of project_id or group_id",
|
|
2467
|
+
});
|
|
2468
|
+
export const ListWebhookEventsSchema = z
|
|
2469
|
+
.object({
|
|
2470
|
+
project_id: z.coerce
|
|
2471
|
+
.string()
|
|
2472
|
+
.optional()
|
|
2473
|
+
.describe("Project ID or URL-encoded path. Provide either project_id or group_id, not both."),
|
|
2474
|
+
group_id: z.coerce
|
|
2475
|
+
.string()
|
|
2476
|
+
.optional()
|
|
2477
|
+
.describe("Group ID or URL-encoded path. Provide either project_id or group_id, not both."),
|
|
2478
|
+
hook_id: z.coerce.number().describe("ID of the webhook"),
|
|
2479
|
+
status: z
|
|
2480
|
+
.union([z.number(), z.string()])
|
|
2481
|
+
.optional()
|
|
2482
|
+
.describe("Filter by response status code (e.g. 200, 500) or category: successful, client_failure, server_failure"),
|
|
2483
|
+
summary: z
|
|
2484
|
+
.boolean()
|
|
2485
|
+
.optional()
|
|
2486
|
+
.describe("If true, return only summary fields (id, url, trigger, response_status, execution_duration) without full request/response payloads. Recommended for overview queries to avoid huge responses."),
|
|
2487
|
+
per_page: z
|
|
2488
|
+
.number()
|
|
2489
|
+
.max(20)
|
|
2490
|
+
.optional()
|
|
2491
|
+
.default(20)
|
|
2492
|
+
.describe("Number of events per page"),
|
|
2493
|
+
page: z.number().optional().describe("Page number for pagination"),
|
|
2494
|
+
})
|
|
2495
|
+
.refine(data => (data.project_id || data.group_id) && !(data.project_id && data.group_id), {
|
|
2496
|
+
message: "Provide exactly one of project_id or group_id",
|
|
2497
|
+
});
|
|
2498
|
+
export const GetWebhookEventSchema = z
|
|
2499
|
+
.object({
|
|
2500
|
+
project_id: z.coerce
|
|
2501
|
+
.string()
|
|
2502
|
+
.optional()
|
|
2503
|
+
.describe("Project ID or URL-encoded path. Provide either project_id or group_id, not both."),
|
|
2504
|
+
group_id: z.coerce
|
|
2505
|
+
.string()
|
|
2506
|
+
.optional()
|
|
2507
|
+
.describe("Group ID or URL-encoded path. Provide either project_id or group_id, not both."),
|
|
2508
|
+
hook_id: z.coerce.number().describe("ID of the webhook"),
|
|
2509
|
+
event_id: z.coerce.number().describe("ID of the webhook event to retrieve"),
|
|
2510
|
+
page: z
|
|
2511
|
+
.number()
|
|
2512
|
+
.optional()
|
|
2513
|
+
.describe("If known, the page where the event is located (from list_webhook_events). Skips auto-pagination and fetches only this page."),
|
|
2514
|
+
})
|
|
2515
|
+
.refine(data => (data.project_id || data.group_id) && !(data.project_id && data.group_id), {
|
|
2516
|
+
message: "Provide exactly one of project_id or group_id",
|
|
2517
|
+
});
|