#!/usr/bin/env node
/**
* GitLab MCP Server for AgenticLedger Platform
*
* This MCP server provides GitLab integration capabilities for AI agents,
* enabling them to:
* - Monitor documentation changes in GitLab repositories
* - Read file contents and track modifications
* - List commits, branches, and project events
* - Manage issues and merge requests
* - Search across projects
*
* Authentication Pattern: API Key (Personal Access Token)
* Token Format: GitLab PAT (glpat-xxxxxxxxxxxxxxxxxxxx)
*
* Following AgenticLedger MCP Guidelines:
* - All tools include accessToken parameter
* - All responses use { success: boolean, data?: any, error?: string }
* - All schemas use Zod with .describe() for parameters
* - No credentials in logs
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from '@modelcontextprotocol/sdk/types.js';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { z } from 'zod';
import { GitLabClient } from './gitlab-client.js';
import * as schemas from './schemas.js';
// Tool definition interface
interface ToolDefinition {
name: string;
description: string;
schema: z.ZodType<{ accessToken: string; [key: string]: unknown }>;
}
// Tool definitions with Zod schemas
const TOOLS: ToolDefinition[] = [
// ============ Project Operations ============
{
name: 'gitlab_search_projects',
description: 'Search for GitLab projects by name or description. Returns project IDs, names, and metadata.',
schema: schemas.SearchProjectsSchema,
},
{
name: 'gitlab_get_project',
description: 'Get detailed information about a specific GitLab project including visibility, URLs, and last activity.',
schema: schemas.GetProjectSchema,
},
{
name: 'gitlab_list_group_projects',
description: 'List all projects within a GitLab group, optionally including subgroups. Useful for discovering documentation repositories.',
schema: schemas.ListGroupProjectsSchema,
},
// ============ File Operations ============
{
name: 'gitlab_get_file_contents',
description: 'Read the contents of a specific file from a GitLab repository. Essential for monitoring documentation changes.',
schema: schemas.GetFileContentsSchema,
},
{
name: 'gitlab_get_tree',
description: 'List files and directories in a repository path. Use to discover documentation structure.',
schema: schemas.GetTreeSchema,
},
{
name: 'gitlab_create_or_update_file',
description: 'Create or update a file in a GitLab repository with a commit message.',
schema: schemas.CreateOrUpdateFileSchema,
},
// ============ Branch Operations ============
{
name: 'gitlab_list_branches',
description: 'List all branches in a repository. Useful for understanding documentation versioning.',
schema: schemas.ListBranchesSchema,
},
{
name: 'gitlab_create_branch',
description: 'Create a new branch from an existing branch, tag, or commit.',
schema: schemas.CreateBranchSchema,
},
// ============ Commit Operations ============
{
name: 'gitlab_list_commits',
description: 'List recent commits with optional filtering by date, path, or author. Critical for monitoring documentation changes.',
schema: schemas.ListCommitsSchema,
},
{
name: 'gitlab_get_commit',
description: 'Get detailed information about a specific commit including stats and parent commits.',
schema: schemas.GetCommitSchema,
},
{
name: 'gitlab_get_commit_diff',
description: 'Get the file changes (diff) for a specific commit. Shows exactly what changed in documentation.',
schema: schemas.GetCommitDiffSchema,
},
{
name: 'gitlab_compare',
description: 'Compare two branches, tags, or commits to see all differences. Useful for reviewing documentation updates between versions.',
schema: schemas.CompareSchema,
},
// ============ Events/Activity Monitoring ============
{
name: 'gitlab_get_project_events',
description: 'Get recent activity events for a project (pushes, comments, issues, etc.). Primary tool for monitoring documentation changes.',
schema: schemas.GetProjectEventsSchema,
},
// ============ Issue Operations ============
{
name: 'gitlab_list_issues',
description: 'List issues in a project with filtering options. Can track documentation-related issues.',
schema: schemas.ListIssuesSchema,
},
{
name: 'gitlab_get_issue',
description: 'Get detailed information about a specific issue.',
schema: schemas.GetIssueSchema,
},
{
name: 'gitlab_create_issue',
description: 'Create a new issue in a GitLab project.',
schema: schemas.CreateIssueSchema,
},
{
name: 'gitlab_list_issue_notes',
description: 'List all comments/notes on an issue.',
schema: schemas.ListIssueNotesSchema,
},
// ============ Merge Request Operations ============
{
name: 'gitlab_list_merge_requests',
description: 'List merge requests with filtering. Track documentation PRs.',
schema: schemas.ListMergeRequestsSchema,
},
{
name: 'gitlab_get_merge_request',
description: 'Get detailed merge request information including reviewers and approval status.',
schema: schemas.GetMergeRequestSchema,
},
{
name: 'gitlab_get_merge_request_changes',
description: 'Get the file changes in a merge request. See exactly what documentation updates are proposed.',
schema: schemas.GetMergeRequestChangesSchema,
},
{
name: 'gitlab_create_merge_request',
description: 'Create a new merge request for documentation updates.',
schema: schemas.CreateMergeRequestSchema,
},
// ============ Wiki Operations ============
{
name: 'gitlab_list_wiki_pages',
description: 'List all wiki pages in a project. Wikis often contain documentation.',
schema: schemas.ListWikiPagesSchema,
},
{
name: 'gitlab_get_wiki_page',
description: 'Get the content of a specific wiki page.',
schema: schemas.GetWikiPageSchema,
},
{
name: 'gitlab_create_wiki_page',
description: 'Create a new wiki page.',
schema: schemas.CreateWikiPageSchema,
},
{
name: 'gitlab_update_wiki_page',
description: 'Update an existing wiki page.',
schema: schemas.UpdateWikiPageSchema,
},
// ============ Member Operations ============
{
name: 'gitlab_list_project_members',
description: 'List all members of a project with their access levels.',
schema: schemas.ListProjectMembersSchema,
},
{
name: 'gitlab_list_group_members',
description: 'List all members of a group with their access levels.',
schema: schemas.ListGroupMembersSchema,
},
// ============ Search Operations ============
{
name: 'gitlab_search',
description: 'Search across GitLab for projects, issues, merge requests, code, commits, users, and more. Powerful for finding documentation.',
schema: schemas.SearchSchema,
},
];
// Create MCP server
const server = new Server(
{
name: 'gitlab-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Register tool listing handler
server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools: Tool[] = TOOLS.map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: zodToJsonSchema(tool.schema) as Tool['inputSchema'],
}));
return { tools };
});
// Register tool execution handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
// Find the tool
const toolDef = TOOLS.find((t) => t.name === name);
if (!toolDef) {
return {
content: [
{
type: 'text',
text: JSON.stringify({ success: false, error: `Unknown tool: ${name}` }),
},
],
};
}
// Validate arguments
const validated = toolDef.schema.safeParse(args);
if (!validated.success) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: false,
error: `Validation error: ${validated.error.message}`,
}),
},
],
};
}
const validatedArgs = validated.data as { accessToken: string; [key: string]: unknown };
// Create client with access token
const client = new GitLabClient({
accessToken: validatedArgs.accessToken,
baseUrl: process.env.GITLAB_API_URL,
});
// Execute the appropriate handler
let result: schemas.StandardResponse;
switch (name) {
// Project Operations
case 'gitlab_search_projects':
result = await client.searchProjects(validatedArgs as schemas.SearchProjectsInput);
break;
case 'gitlab_get_project':
result = await client.getProject((validatedArgs as schemas.GetProjectInput).project_id);
break;
case 'gitlab_list_group_projects':
result = await client.listGroupProjects(validatedArgs as schemas.ListGroupProjectsInput);
break;
// File Operations
case 'gitlab_get_file_contents':
result = await client.getFileContents(validatedArgs as schemas.GetFileContentsInput);
break;
case 'gitlab_get_tree':
result = await client.getTree(validatedArgs as schemas.GetTreeInput);
break;
case 'gitlab_create_or_update_file':
result = await client.createOrUpdateFile(validatedArgs as schemas.CreateOrUpdateFileInput);
break;
// Branch Operations
case 'gitlab_list_branches':
result = await client.listBranches(validatedArgs as schemas.ListBranchesInput);
break;
case 'gitlab_create_branch':
result = await client.createBranch(validatedArgs as schemas.CreateBranchInput);
break;
// Commit Operations
case 'gitlab_list_commits':
result = await client.listCommits(validatedArgs as schemas.ListCommitsInput);
break;
case 'gitlab_get_commit':
result = await client.getCommit(validatedArgs as schemas.GetCommitInput);
break;
case 'gitlab_get_commit_diff':
result = await client.getCommitDiff(validatedArgs as schemas.GetCommitDiffInput);
break;
case 'gitlab_compare':
result = await client.compare(validatedArgs as schemas.CompareInput);
break;
// Events/Activity
case 'gitlab_get_project_events':
result = await client.getProjectEvents(validatedArgs as schemas.GetProjectEventsInput);
break;
// Issue Operations
case 'gitlab_list_issues':
result = await client.listIssues(validatedArgs as schemas.ListIssuesInput);
break;
case 'gitlab_get_issue':
result = await client.getIssue(validatedArgs as schemas.GetIssueInput);
break;
case 'gitlab_create_issue':
result = await client.createIssue(validatedArgs as schemas.CreateIssueInput);
break;
case 'gitlab_list_issue_notes':
result = await client.listIssueNotes(validatedArgs as schemas.ListIssueNotesInput);
break;
// Merge Request Operations
case 'gitlab_list_merge_requests':
result = await client.listMergeRequests(validatedArgs as schemas.ListMergeRequestsInput);
break;
case 'gitlab_get_merge_request':
result = await client.getMergeRequest(validatedArgs as schemas.GetMergeRequestInput);
break;
case 'gitlab_get_merge_request_changes':
result = await client.getMergeRequestChanges(validatedArgs as schemas.GetMergeRequestChangesInput);
break;
case 'gitlab_create_merge_request':
result = await client.createMergeRequest(validatedArgs as schemas.CreateMergeRequestInput);
break;
// Wiki Operations
case 'gitlab_list_wiki_pages':
result = await client.listWikiPages(validatedArgs as schemas.ListWikiPagesInput);
break;
case 'gitlab_get_wiki_page':
result = await client.getWikiPage(validatedArgs as schemas.GetWikiPageInput);
break;
case 'gitlab_create_wiki_page':
result = await client.createWikiPage(validatedArgs as schemas.CreateWikiPageInput);
break;
case 'gitlab_update_wiki_page':
result = await client.updateWikiPage(validatedArgs as schemas.UpdateWikiPageInput);
break;
// Member Operations
case 'gitlab_list_project_members':
result = await client.listProjectMembers(validatedArgs as schemas.ListProjectMembersInput);
break;
case 'gitlab_list_group_members':
result = await client.listGroupMembers(validatedArgs as schemas.ListGroupMembersInput);
break;
// Search Operations
case 'gitlab_search':
result = await client.search(validatedArgs as schemas.SearchInput);
break;
default:
result = { success: false, error: `Tool handler not implemented: ${name}` };
}
return {
content: [
{
type: 'text',
text: JSON.stringify(result),
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return {
content: [
{
type: 'text',
text: JSON.stringify({ success: false, error: errorMessage }),
},
],
};
}
});
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
// Server is ready (no console.log to avoid breaking MCP protocol)
}
main().catch((error) => {
// Log to stderr only
process.stderr.write(`Fatal error: ${error}\n`);
process.exit(1);
});