Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ docker run -i --rm \
81. `list_group_iterations` - List group iterations with filtering options
82. `upload_markdown` - Upload a file to a GitLab project for use in markdown content
83. `download_attachment` - Download an uploaded file from a GitLab project by secret and filename
84. `list_events` - List all events for the currently authenticated user
85. `get_project_events` - List all visible events for a specified project
<!-- TOOLS-END -->

</details>
93 changes: 92 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,11 @@ import {
UpdateMergeRequestNoteSchema,
UpdateMergeRequestSchema,
UpdateWikiPageSchema,
VerifyNamespaceSchema
VerifyNamespaceSchema,
GitLabEventSchema,
ListEventsSchema,
GetProjectEventsSchema,
GitLabEvent
} from "./schemas.js";

import { randomUUID } from "crypto";
Expand Down Expand Up @@ -816,6 +820,16 @@ const allTools = [
description: "Download an uploaded file from a GitLab project by secret and filename",
inputSchema: zodToJsonSchema(DownloadAttachmentSchema),
},
{
name: "list_events",
description: "List all events for the currently authenticated user. Note: before/after parameters accept date format YYYY-MM-DD only",
inputSchema: zodToJsonSchema(ListEventsSchema),
},
{
name: "get_project_events",
description: "List all visible events for a specified project. Note: before/after parameters accept date format YYYY-MM-DD only",
inputSchema: zodToJsonSchema(GetProjectEventsSchema),
},
];

// Define which tools are read-only
Expand Down Expand Up @@ -863,6 +877,8 @@ const readOnlyTools = [
"list_group_iterations",
"get_group_iteration",
"download_attachment",
"list_events",
"get_project_events",
];

// Define which tools are related to wiki and can be toggled by USE_GITLAB_WIKI
Expand Down Expand Up @@ -4089,6 +4105,64 @@ async function downloadAttachment(projectId: string, secret: string, filename: s
return savePath;
}

/**
* List all events for the currently authenticated user
* @param {Object} options - Options for listing events
* @returns {Promise<GitLabEvent[]>} List of events
*/
async function listEvents(options: z.infer<typeof ListEventsSchema> = {}): Promise<GitLabEvent[]> {
const url = new URL(`${GITLAB_API_URL}/events`);

// Add all query parameters
Object.entries(options).forEach(([key, value]) => {
if (value !== undefined) {
url.searchParams.append(key, value.toString());
}
});

const response = await fetch(url.toString(), {
method: "GET",
headers: DEFAULT_HEADERS,
});

if (!response.ok) {
await handleGitLabError(response);
}

const data = await response.json();
return GitLabEventSchema.array().parse(data);
}

/**
* List all visible events for a specified project
* @param {string} projectId - Project ID or URL-encoded path
* @param {Object} options - Options for getting project events
* @returns {Promise<GitLabEvent[]>} List of project events
*/
async function getProjectEvents(projectId: string, options: Omit<z.infer<typeof GetProjectEventsSchema>, "project_id"> = {}): Promise<GitLabEvent[]> {
const effectiveProjectId = getEffectiveProjectId(projectId);
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/events`);

// Add all query parameters
Object.entries(options).forEach(([key, value]) => {
if (value !== undefined) {
url.searchParams.append(key, value.toString());
}
});

const response = await fetch(url.toString(), {
method: "GET",
headers: DEFAULT_HEADERS,
});

if (!response.ok) {
await handleGitLabError(response);
}

const data = await response.json();
return GitLabEventSchema.array().parse(data);
}

server.setRequestHandler(ListToolsRequestSchema, async () => {
// Apply read-only filter first
const tools0 = GITLAB_READ_ONLY_MODE
Expand Down Expand Up @@ -5176,6 +5250,23 @@ server.setRequestHandler(CallToolRequestSchema, async request => {
};
}

case "list_events": {
const args = ListEventsSchema.parse(request.params.arguments);
const events = await listEvents(args);
return {
content: [{ type: "text", text: JSON.stringify(events, null, 2) }],
};
}

case "get_project_events": {
const args = GetProjectEventsSchema.parse(request.params.arguments);
const { project_id, ...options } = args;
const events = await getProjectEvents(project_id, options);
return {
content: [{ type: "text", text: JSON.stringify(events, null, 2) }],
};
}

default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
Expand Down
58 changes: 58 additions & 0 deletions schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1902,6 +1902,58 @@ export const ListGroupIterationsSchema = z
})
.merge(PaginationOptionsSchema);



// Events API schemas
export const GitLabEventAuthorSchema = z.object({
id: z.coerce.string(),
name: z.string(),
username: z.string(),
state: z.string(),
avatar_url: z.string().nullable(),
web_url: z.string(),
});

export const GitLabEventSchema = z.object({
id: z.coerce.string(),
project_id: z.coerce.string(),
action_name: z.string(),
target_id: z.coerce.string().nullable(),
target_iid: z.coerce.string().nullable(),
target_type: z.string().nullable(),
author_id: z.coerce.string(),
target_title: z.string().nullable(),
created_at: z.string(),
author: GitLabEventAuthorSchema,
author_username: z.string(),
imported: flexibleBoolean,
imported_from: z.string(),
}).passthrough(); // Allow additional fields

// List events schema
export const ListEventsSchema = z.object({
action: z.string().optional().describe("If defined, returns events with the specified action type"),
target_type: z.enum(["epic", "issue", "merge_request", "milestone", "note", "project", "snippet", "user"]).optional().describe("If defined, returns events with the specified target type"),
before: z.string().optional().describe("If defined, Returns events created before the specified date (YYYY-MM-DD format). To include events on 2025-08-29, use before=2025-08-30"),
after: z.string().optional().describe("If defined, Returns events created after the specified date (YYYY-MM-DD format). To include events on 2025-08-29, use after=2025-08-28"),
scope: z.string().optional().describe("Include all events across a user's projects"),
sort: z.enum(["asc", "desc"]).optional().describe("Direction to sort the results by creation date. Default: desc"),
page: z.number().optional().describe("Returns the specified results page. Default: 1"),
per_page: z.number().optional().describe("Number of results per page. Default: 20"),
});

// Get project events schema
export const GetProjectEventsSchema = z.object({
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
action: z.string().optional().describe("If defined, returns events with the specified action type"),
target_type: z.enum(["epic", "issue", "merge_request", "milestone", "note", "project", "snippet", "user"]).optional().describe("If defined, returns events with the specified target type"),
before: z.string().optional().describe("If defined, Returns events created before the specified date (YYYY-MM-DD format). To include events on 2025-08-29, use before=2025-08-30"),
after: z.string().optional().describe("If defined, Returns events created after the specified date (YYYY-MM-DD format). To include events on 2025-08-29, use after=2025-08-28"),
sort: z.enum(["asc", "desc"]).optional().describe("Direction to sort the results by creation date. Default: desc"),
page: z.number().optional().describe("Returns the specified results page. Default: 1"),
per_page: z.number().optional().describe("Number of results per page. Default: 20"),
});

// Export types
export type GitLabAuthor = z.infer<typeof GitLabAuthorSchema>;
export type GitLabFork = z.infer<typeof GitLabForkSchema>;
Expand Down Expand Up @@ -1987,3 +2039,9 @@ export type PublishDraftNoteOptions = z.infer<typeof PublishDraftNoteSchema>;
export type BulkPublishDraftNotesOptions = z.infer<typeof BulkPublishDraftNotesSchema>;
export type GitLabMarkdownUpload = z.infer<typeof GitLabMarkdownUploadSchema>;
export type MarkdownUploadOptions = z.infer<typeof MarkdownUploadSchema>;

// Events API type exports
export type GitLabEvent = z.infer<typeof GitLabEventSchema>;
export type GitLabEventAuthor = z.infer<typeof GitLabEventAuthorSchema>;
export type ListEventsOptions = z.infer<typeof ListEventsSchema>;
export type GetProjectEventsOptions = z.infer<typeof GetProjectEventsSchema>;