/**
* GitLab MCP Server - Zod Schemas
*
* All schemas follow AgenticLedger patterns:
* - Every tool schema includes accessToken
* - All parameters have .describe() for AI context
* - Consistent naming conventions
*/
import { z } from 'zod';
// =============================================================================
// Base Schemas (reused across tools)
// =============================================================================
export const AccessTokenSchema = z.string()
.describe('GitLab Personal Access Token for authentication. Format: glpat-xxxxxxxxxxxxxxxxxxxx');
export const ProjectIdSchema = z.string()
.describe('GitLab project ID or URL-encoded path (e.g., "12345" or "group%2Fproject")');
export const GroupIdSchema = z.string()
.describe('GitLab group ID or URL-encoded path (e.g., "12345" or "my-group")');
export const PaginationSchema = z.object({
page: z.number().min(1).optional()
.describe('Page number for pagination (starts at 1)'),
per_page: z.number().min(1).max(100).optional()
.describe('Number of items per page (1-100, default 20)')
});
// =============================================================================
// Repository/Project Tools
// =============================================================================
export const SearchProjectsSchema = z.object({
accessToken: AccessTokenSchema,
search: z.string()
.describe('Search query to find projects by name or description'),
page: z.number().min(1).optional()
.describe('Page number for pagination'),
per_page: z.number().min(1).max(100).optional()
.describe('Number of results per page (max 100)')
});
export const GetProjectSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema
});
export const ListGroupProjectsSchema = z.object({
accessToken: AccessTokenSchema,
group_id: GroupIdSchema,
include_subgroups: z.boolean().optional()
.describe('Include projects from subgroups'),
search: z.string().optional()
.describe('Filter projects by name'),
order_by: z.enum(['id', 'name', 'path', 'created_at', 'updated_at', 'last_activity_at']).optional()
.describe('Field to order results by'),
sort: z.enum(['asc', 'desc']).optional()
.describe('Sort direction'),
page: z.number().min(1).optional()
.describe('Page number'),
per_page: z.number().min(1).max(100).optional()
.describe('Results per page')
});
// =============================================================================
// File Operations
// =============================================================================
export const GetFileContentsSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
file_path: z.string()
.describe('Path to the file within the repository (e.g., "docs/README.md")'),
ref: z.string().optional()
.describe('Branch, tag, or commit SHA to read from (default: default branch)')
});
export const GetTreeSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
path: z.string().optional()
.describe('Path inside repository to list (empty for root)'),
ref: z.string().optional()
.describe('Branch, tag, or commit SHA'),
recursive: z.boolean().optional()
.describe('Get tree recursively'),
page: z.number().min(1).optional()
.describe('Page number'),
per_page: z.number().min(1).max(100).optional()
.describe('Results per page')
});
export const CreateOrUpdateFileSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
file_path: z.string()
.describe('Path where the file should be created or updated'),
branch: z.string()
.describe('Target branch for the commit'),
content: z.string()
.describe('File content (will be base64 encoded)'),
commit_message: z.string()
.describe('Commit message describing the change'),
start_branch: z.string().optional()
.describe('Branch to start from if creating new branch'),
author_email: z.string().email().optional()
.describe('Author email for the commit'),
author_name: z.string().optional()
.describe('Author name for the commit')
});
// =============================================================================
// Branch Operations
// =============================================================================
export const ListBranchesSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
search: z.string().optional()
.describe('Filter branches by name'),
page: z.number().min(1).optional()
.describe('Page number'),
per_page: z.number().min(1).max(100).optional()
.describe('Results per page')
});
export const CreateBranchSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
branch: z.string()
.describe('Name of the new branch'),
ref: z.string()
.describe('Source branch, tag, or commit SHA to create from')
});
// =============================================================================
// Commit Operations
// =============================================================================
export const ListCommitsSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
ref_name: z.string().optional()
.describe('Branch or tag name to get commits from'),
since: z.string().optional()
.describe('Only commits after this date (ISO 8601 format)'),
until: z.string().optional()
.describe('Only commits before this date (ISO 8601 format)'),
path: z.string().optional()
.describe('Only commits affecting this file path'),
author: z.string().optional()
.describe('Filter by commit author email'),
with_stats: z.boolean().optional()
.describe('Include commit stats (additions, deletions)'),
page: z.number().min(1).optional()
.describe('Page number'),
per_page: z.number().min(1).max(100).optional()
.describe('Results per page')
});
export const GetCommitSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
sha: z.string()
.describe('Commit SHA hash')
});
export const GetCommitDiffSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
sha: z.string()
.describe('Commit SHA hash'),
page: z.number().min(1).optional()
.describe('Page number'),
per_page: z.number().min(1).max(100).optional()
.describe('Results per page')
});
export const CompareSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
from: z.string()
.describe('Source branch, tag, or commit SHA'),
to: z.string()
.describe('Target branch, tag, or commit SHA'),
straight: z.boolean().optional()
.describe('Use straight comparison (from..to) instead of merge-base')
});
// =============================================================================
// Events/Activity Monitoring
// =============================================================================
export const GetProjectEventsSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
action: z.enum(['created', 'updated', 'closed', 'reopened', 'pushed', 'commented', 'merged', 'joined', 'left', 'destroyed', 'expired']).optional()
.describe('Filter by event action type'),
target_type: z.enum(['issue', 'milestone', 'merge_request', 'note', 'project', 'snippet', 'user']).optional()
.describe('Filter by target type'),
before: z.string().optional()
.describe('Only events before this date (ISO 8601)'),
after: z.string().optional()
.describe('Only events after this date (ISO 8601)'),
sort: z.enum(['asc', 'desc']).optional()
.describe('Sort direction'),
page: z.number().min(1).optional()
.describe('Page number'),
per_page: z.number().min(1).max(100).optional()
.describe('Results per page')
});
// =============================================================================
// Issue Operations
// =============================================================================
export const ListIssuesSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
state: z.enum(['opened', 'closed', 'all']).optional()
.describe('Filter by issue state'),
labels: z.string().optional()
.describe('Comma-separated list of label names'),
milestone: z.string().optional()
.describe('Filter by milestone title'),
search: z.string().optional()
.describe('Search in title and description'),
created_after: z.string().optional()
.describe('Only issues created after this date (ISO 8601)'),
created_before: z.string().optional()
.describe('Only issues created before this date (ISO 8601)'),
updated_after: z.string().optional()
.describe('Only issues updated after this date (ISO 8601)'),
updated_before: z.string().optional()
.describe('Only issues updated before this date (ISO 8601)'),
order_by: z.enum(['created_at', 'updated_at', 'priority', 'due_date', 'relative_position', 'label_priority', 'milestone_due', 'popularity', 'weight']).optional()
.describe('Order issues by field'),
sort: z.enum(['asc', 'desc']).optional()
.describe('Sort direction'),
page: z.number().min(1).optional()
.describe('Page number'),
per_page: z.number().min(1).max(100).optional()
.describe('Results per page')
});
export const GetIssueSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
issue_iid: z.number()
.describe('Issue internal ID (IID) within the project')
});
export const CreateIssueSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
title: z.string()
.describe('Issue title'),
description: z.string().optional()
.describe('Issue description (supports Markdown)'),
labels: z.string().optional()
.describe('Comma-separated list of labels'),
assignee_ids: z.array(z.number()).optional()
.describe('Array of user IDs to assign'),
milestone_id: z.number().optional()
.describe('Milestone ID to associate'),
due_date: z.string().optional()
.describe('Due date (YYYY-MM-DD format)'),
confidential: z.boolean().optional()
.describe('Make issue confidential')
});
export const ListIssueNotesSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
issue_iid: z.number()
.describe('Issue internal ID'),
sort: z.enum(['asc', 'desc']).optional()
.describe('Sort direction'),
order_by: z.enum(['created_at', 'updated_at']).optional()
.describe('Order notes by field'),
page: z.number().min(1).optional()
.describe('Page number'),
per_page: z.number().min(1).max(100).optional()
.describe('Results per page')
});
// =============================================================================
// Merge Request Operations
// =============================================================================
export const ListMergeRequestsSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
state: z.enum(['opened', 'closed', 'merged', 'all']).optional()
.describe('Filter by MR state'),
source_branch: z.string().optional()
.describe('Filter by source branch'),
target_branch: z.string().optional()
.describe('Filter by target branch'),
labels: z.string().optional()
.describe('Comma-separated list of labels'),
search: z.string().optional()
.describe('Search in title and description'),
created_after: z.string().optional()
.describe('Only MRs created after this date (ISO 8601)'),
created_before: z.string().optional()
.describe('Only MRs created before this date (ISO 8601)'),
updated_after: z.string().optional()
.describe('Only MRs updated after this date (ISO 8601)'),
updated_before: z.string().optional()
.describe('Only MRs updated before this date (ISO 8601)'),
order_by: z.enum(['created_at', 'updated_at']).optional()
.describe('Order by field'),
sort: z.enum(['asc', 'desc']).optional()
.describe('Sort direction'),
page: z.number().min(1).optional()
.describe('Page number'),
per_page: z.number().min(1).max(100).optional()
.describe('Results per page')
});
export const GetMergeRequestSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
merge_request_iid: z.number()
.describe('Merge request internal ID (IID)')
});
export const GetMergeRequestChangesSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
merge_request_iid: z.number()
.describe('Merge request internal ID (IID)')
});
export const CreateMergeRequestSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
source_branch: z.string()
.describe('Source branch for the MR'),
target_branch: z.string()
.describe('Target branch for the MR'),
title: z.string()
.describe('Merge request title'),
description: z.string().optional()
.describe('Merge request description (supports Markdown)'),
labels: z.string().optional()
.describe('Comma-separated list of labels'),
assignee_ids: z.array(z.number()).optional()
.describe('Array of user IDs to assign'),
reviewer_ids: z.array(z.number()).optional()
.describe('Array of user IDs for reviewers'),
remove_source_branch: z.boolean().optional()
.describe('Remove source branch after merge'),
squash: z.boolean().optional()
.describe('Squash commits on merge'),
draft: z.boolean().optional()
.describe('Create as draft/WIP MR')
});
// =============================================================================
// Wiki Operations
// =============================================================================
export const ListWikiPagesSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
with_content: z.boolean().optional()
.describe('Include page content in response'),
page: z.number().min(1).optional()
.describe('Page number'),
per_page: z.number().min(1).max(100).optional()
.describe('Results per page')
});
export const GetWikiPageSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
slug: z.string()
.describe('Wiki page slug (URL-encoded title)')
});
export const CreateWikiPageSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
title: z.string()
.describe('Wiki page title'),
content: z.string()
.describe('Wiki page content (supports Markdown)'),
format: z.enum(['markdown', 'rdoc', 'asciidoc', 'org']).optional()
.describe('Content format (default: markdown)')
});
export const UpdateWikiPageSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
slug: z.string()
.describe('Wiki page slug'),
title: z.string().optional()
.describe('New title'),
content: z.string().optional()
.describe('New content'),
format: z.enum(['markdown', 'rdoc', 'asciidoc', 'org']).optional()
.describe('Content format')
});
// =============================================================================
// Member Operations
// =============================================================================
export const ListProjectMembersSchema = z.object({
accessToken: AccessTokenSchema,
project_id: ProjectIdSchema,
query: z.string().optional()
.describe('Search members by name or username'),
page: z.number().min(1).optional()
.describe('Page number'),
per_page: z.number().min(1).max(100).optional()
.describe('Results per page')
});
export const ListGroupMembersSchema = z.object({
accessToken: AccessTokenSchema,
group_id: GroupIdSchema,
query: z.string().optional()
.describe('Search members by name or username'),
page: z.number().min(1).optional()
.describe('Page number'),
per_page: z.number().min(1).max(100).optional()
.describe('Results per page')
});
// =============================================================================
// Search Operations
// =============================================================================
export const SearchSchema = z.object({
accessToken: AccessTokenSchema,
scope: z.enum(['projects', 'issues', 'merge_requests', 'milestones', 'snippet_titles', 'users', 'blobs', 'commits', 'notes', 'wiki_blobs']).optional()
.describe('Search scope (default: all)'),
search: z.string()
.describe('Search query string'),
project_id: z.string().optional()
.describe('Limit search to a specific project'),
group_id: z.string().optional()
.describe('Limit search to a specific group'),
page: z.number().min(1).optional()
.describe('Page number'),
per_page: z.number().min(1).max(100).optional()
.describe('Results per page')
});
// =============================================================================
// Response Types
// =============================================================================
export interface StandardResponse<T = unknown> {
success: boolean;
data?: T;
error?: string;
}
// Type exports for use in handlers
export type SearchProjectsInput = z.infer<typeof SearchProjectsSchema>;
export type GetProjectInput = z.infer<typeof GetProjectSchema>;
export type ListGroupProjectsInput = z.infer<typeof ListGroupProjectsSchema>;
export type GetFileContentsInput = z.infer<typeof GetFileContentsSchema>;
export type GetTreeInput = z.infer<typeof GetTreeSchema>;
export type CreateOrUpdateFileInput = z.infer<typeof CreateOrUpdateFileSchema>;
export type ListBranchesInput = z.infer<typeof ListBranchesSchema>;
export type CreateBranchInput = z.infer<typeof CreateBranchSchema>;
export type ListCommitsInput = z.infer<typeof ListCommitsSchema>;
export type GetCommitInput = z.infer<typeof GetCommitSchema>;
export type GetCommitDiffInput = z.infer<typeof GetCommitDiffSchema>;
export type CompareInput = z.infer<typeof CompareSchema>;
export type GetProjectEventsInput = z.infer<typeof GetProjectEventsSchema>;
export type ListIssuesInput = z.infer<typeof ListIssuesSchema>;
export type GetIssueInput = z.infer<typeof GetIssueSchema>;
export type CreateIssueInput = z.infer<typeof CreateIssueSchema>;
export type ListIssueNotesInput = z.infer<typeof ListIssueNotesSchema>;
export type ListMergeRequestsInput = z.infer<typeof ListMergeRequestsSchema>;
export type GetMergeRequestInput = z.infer<typeof GetMergeRequestSchema>;
export type GetMergeRequestChangesInput = z.infer<typeof GetMergeRequestChangesSchema>;
export type CreateMergeRequestInput = z.infer<typeof CreateMergeRequestSchema>;
export type ListWikiPagesInput = z.infer<typeof ListWikiPagesSchema>;
export type GetWikiPageInput = z.infer<typeof GetWikiPageSchema>;
export type CreateWikiPageInput = z.infer<typeof CreateWikiPageSchema>;
export type UpdateWikiPageInput = z.infer<typeof UpdateWikiPageSchema>;
export type ListProjectMembersInput = z.infer<typeof ListProjectMembersSchema>;
export type ListGroupMembersInput = z.infer<typeof ListGroupMembersSchema>;
export type SearchInput = z.infer<typeof SearchSchema>;