#
tokens: 35185/50000 26/26 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── git-hooks
│   └── pre-commit
├── LICENSE
├── memory-bank
│   ├── .clinerules
│   ├── activeContext.md
│   ├── productContext.md
│   ├── progress.md
│   ├── projectbrief.md
│   ├── systemPatterns.md
│   └── techContext.md
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   └── generate-tools-md.js
├── src
│   ├── ci-cd.ts
│   ├── handlers
│   │   ├── cicd-handlers.ts
│   │   ├── integration-handlers.ts
│   │   ├── repository-handlers.ts
│   │   └── users-groups-handlers.ts
│   ├── index.ts
│   ├── integrations.ts
│   ├── users-groups.ts
│   └── utils
│       ├── handler-types.ts
│       ├── resource-handlers.ts
│       ├── response-formatter.ts
│       ├── tool-registry.ts
│       └── tools-data.ts
├── test.js
├── TOOLS.md
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
node_modules/
build/
*.log
.env*
```

--------------------------------------------------------------------------------
/memory-bank/.clinerules:
--------------------------------------------------------------------------------

```
# Cline Rules: GitLab MCP Server

## Project Patterns

### Code Style and Structure
- Use TypeScript for all new code
- Follow ES6+ syntax and patterns
- Use async/await for asynchronous operations
- Maintain clear error handling with descriptive messages
- Use detailed JSDoc comments for functions and classes
- Keep Git commits focused and descriptive
- Use modular file organization with domain-specific managers
- Implement tool registry pattern for mapping tool names to handlers
- When adding new tools, update the tool registry, tool definitions and documentation

### Documentation and Workflow Patterns
- Use the generate-tools-md.js script to generate TOOLS.md from tools-data.ts
- Git hooks automatically update documentation on commit
- Always run npm run install-hooks after cloning the repository
- When modifying tools-data.ts, the pre-commit hook will update TOOLS.md
- Document all new tools with proper descriptions and parameters
- Organize tools by category in documentation
- Use the npm run install-hooks script to set up git hooks
- Keep README.md and TOOLS.md in sync through automated tooling
- Include both implementation and documentation in the same PR
- For sections with special characters like ampersands, use the correct GitHub-compatible anchor format
  - "User & Group Management" → "#user--group-management" 
  - "Integrations & Webhooks" → "#integrations--webhooks"

### Server Implementation Patterns
- Use `connect` method with the transport for server initialization
- Implement async/await pattern for server startup
- Add proper error handling for server initialization
- Create separate methods for server setup and startup
- Follow MCP SDK patterns for transport configuration
- Add try/catch blocks for async operations to handle errors gracefully

### Tool Definition Patterns
- Create complete tool definitions for all implemented tools in tools-data.ts
- Keep toolDefinitions array and toolRegistry object in sync
- Use consistent schema patterns for similar tools
- Ensure all required parameters are marked as required in the schema
- Categorize tools by domain using array slicing for exports
- Include clear descriptions for tools and their parameters
- Use enum for parameters with specific valid values
- Use proper schema for complex types like pipeline variables
- Document parameter requirements in tool descriptions

### Type Safety Patterns
- Ensure proper type casting for all API parameters
- Validate required parameters before making API calls
- Add explicit validation for required parameters with descriptive error messages
- Use type assertions only when you're certain of the type
- Add interface definitions for complex parameters
- Ensure webhook parameters include required URL field
- Type pipeline variables properly as Record<string, string> | undefined
- Ensure proper types for all exported functions and interfaces

### API Patterns
- All GitLab API calls should use the central axiosInstance
- Properly encode URL parameters for GitLab API paths
- Handle and transform GitLab API responses consistently
- Implement proper error handling for all API calls
- Follow GitLab API v4 conventions for endpoints
- Use domain manager classes to organize related API functionality
- Create reusable utility functions for common API operations

### MCP Protocol Patterns
- Ensure all tool implementations follow MCP protocol specifications
- Return structured error messages for easier troubleshooting
- Format responses according to MCP content types
- Document all tools with clear descriptions and parameter details
- Use the proper method for setting up MCP server transport
- Follow async patterns for server connection and startup
- Ensure all implemented tool handlers have corresponding tool definitions

## Development Workflow
- Use `npm run build` to compile TypeScript code
- Use `npm run install-hooks` to set up git hooks
- Fix any TypeScript compilation errors immediately before proceeding
- Test changes with real GitLab repositories
- Keep documentation in sync with code changes using git hooks
- Consider backward compatibility when modifying existing tools
- Update the progress.md file when implementing new features
- Ensure server initialization and connection works properly before adding new features
- Verify that all implemented tools are properly defined in tools-data.ts

## Implementation Guidelines
- Keep tool implementations modular and focused
- Use the domain manager classes for related functionality
- Reuse API calling patterns where appropriate
- Consider GitLab API rate limits in implementation
- Implement pagination for list operations where needed
- Follow security best practices for handling tokens and sensitive data
- Use consistent error handling patterns across all domain managers
- Use proper TypeScript type casting for parameters to ensure type safety
- Validate all required parameters before making API calls
- Extract common functionality into shared methods within domain managers
- Follow a consistent pattern for handler functions in index.ts
- Use the tool registry pattern for registering and accessing tool handlers
- Maintain synchronization between tool registry and tool definitions

## Documentation Generation
- Use the scripts/generate-tools-md.js script to generate TOOLS.md
- The script parses tools-data.ts to extract tool definitions
- Tool documentation is organized by category
- Each tool includes its name, description, and parameters
- Parameters include name, type, required flag, and description
- Git pre-commit hook automatically updates TOOLS.md when tools-data.ts changes
- The hook is stored in git-hooks/ and can be installed with npm run install-hooks
- Handle special characters in anchor links correctly for GitHub compatibility
- Use hardcoded anchor links for sections with ampersands to ensure correct navigation
- Always run the documentation generator after making changes to tools-data.ts if not using the git hook

## Testing Approach
- Test with both GitLab.com and self-hosted instances
- Verify error cases and edge conditions
- Test with repositories of varying sizes and structures
- Ensure proper handling of non-ASCII characters in responses
- Validate error messages are helpful and descriptive
- Verify proper type checking for all parameters
- Test server initialization and connection with different environments
- Verify proper error handling for failed connections
- Verify all tools are visible to the AI assistant
- Test the documentation generation script with various tool definitions
- Verify the git pre-commit hook correctly updates documentation
- Check that anchor links work correctly in generated documentation

## Documentation Standards
- Maintain clear README with setup instructions
- Document all tools with parameters and return values
- Include usage examples for common operations
- Keep configuration instructions up to date
- Document known limitations and issues
- Document type requirements for tool parameters
- Document server initialization and startup process
- Use TOOLS.md as the single source of truth for tool documentation
- Keep README.md focused on setup, configuration, and general usage
- Include attribution to original projects when extending them
- Use proper licensing information in LICENSE file
- Maintain consistent formatting in documentation
- Use Markdown tables for structured information
- Include table of contents for longer documentation files
- Ensure anchor links work correctly in documentation
- Organize tools by functional category in documentation

## Project Organization
- Keep scripts in the scripts/ directory
- Store git hooks in git-hooks/ directory
- Use src/utils for utility functions
- Organize tool handlers by domain
- Use domain-specific manager classes for related functionality
- Keep tool definitions in a central location (tools-data.ts)
- Maintain memory-bank directory for project documentation
- Group related functions in appropriate files
- Keep the root directory clean and focused on essential files
- Store configuration examples in a dedicated section of README.md

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# GitLab MCP Server

A Model Context Protocol (MCP) server that enables you to interact with your GitLab account. Get diffs, analyze merge requests, review code, cherry-pick changes, and more. This is an extended version of the [MCP GitLab Server](https://github.com/modelcontextprotocol/servers/tree/main/src/gitlab) from the Model Context Protocol project.

<a href="https://glama.ai/mcp/servers/@rifqi96/mcp-gitlab">
  <img width="380" height="200" src="https://glama.ai/mcp/servers/@rifqi96/mcp-gitlab/badge" alt="GitLab Server MCP server" />
</a>

## Features

This MCP server provides comprehensive tools for interacting with GitLab repositories, including:

### Core Repository Features
- Listing projects and retrieving details
- Managing branches and repositories
- Working with merge requests and diffs
- Adding comments and internal notes to merge requests
- Updating merge request attributes
- Listing and working with issues
- Getting and comparing repository file contents

### Project Settings & Integrations
- Managing project integrations and services
- Configuring and controlling Slack integration
- Setting up, updating, and testing webhooks

### CI/CD Management
- Working with pipeline trigger tokens
- Managing CI/CD variables
- Triggering and controlling pipelines

### User & Group Administration
- Listing and managing users
- Working with groups and group memberships
- Managing project members and access levels

## Installation

### Prerequisites

- Node.js (v16 or higher)
- npm
- A GitLab account with an API token

### Setup

1. Clone the repository:

```bash
git clone https://github.com/rifqi96/mcp-gitlab.git
cd mcp-gitlab
```

2. Install dependencies:

```bash
npm install
```

3. Build the server:

```bash
npm run build
```

4. Install git hooks (optional, but recommended for contributors):

```bash
npm run install-hooks
```

This installs a pre-commit hook that automatically regenerates TOOLS.md when src/utils/tools-data.ts changes.

5. Configure your GitLab API token:

You need to provide your GitLab API token in the MCP settings configuration file. The token is used to authenticate with the GitLab API.

For Cursor/Roo Cline, add the following to your MCP settings file (`~/Library/Application Support/Cursor/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json`):

```json
{
  "mcpServers": {
    "gitlab": {
      "command": "node",
      "args": [
        "/path/to/mcp-gitlab/build/index.js"
      ],
      "env": {
        "GITLAB_API_TOKEN": "YOUR_GITLAB_API_TOKEN",
        "GITLAB_API_URL": "https://gitlab.com/api/v4"
      }
    }
  }
}
```

For Claude Desktop, add the following to your MCP settings file (`~/Library/Application Support/Claude/claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "gitlab": {
      "command": "node",
      "args": [
        "/path/to/mcp-gitlab/build/index.js"
      ],
      "env": {
        "GITLAB_API_TOKEN": "YOUR_GITLAB_API_TOKEN",
        "GITLAB_API_URL": "https://gitlab.com/api/v4"
      }
    }
  }
}
```

Replace `YOUR_GITLAB_API_TOKEN` with your actual GitLab API token. You can generate a token in your GitLab account under Settings > Access Tokens.

## Available Tools

For a complete list of available tools and their parameters, see [TOOLS.md](./TOOLS.md).

## Example Usage

Here are examples of how to use these tools with AI assistants that support MCP:

### List your projects

```
Could you list my GitLab projects?
```

### Get information about a specific merge request

```
Can you show me the details of merge request with ID 123 in the project 'mygroup/myproject'?
```

### Add a comment to a merge request

```
Please add a comment to merge request 123 in project 'mygroup/myproject' saying "This looks good, but please add more tests."
```

### Add an internal note to a merge request

```
Add an internal note to merge request 123 in project 'mygroup/myproject' that says "Needs security review before merging." Make sure it's only visible to team members.
```

### Update a merge request title and description

```
Update the title of merge request 123 in project 'mygroup/myproject' to "Fix login page performance issues" and update the description to include "This PR addresses the slow loading times on the login page by optimizing database queries."
```

### Compare branches

```
Compare the 'feature-branch' with 'main' in the project 'mygroup/myproject' and show me the differences.
```

## Practical Workflows

### Reviewing a Merge Request

```
1. Show me merge request 123 in project 'mygroup/myproject'
2. Show me the changes for this merge request
3. Add an internal note with my review comments
4. Update the merge request title to better reflect the changes
```

### Project Exploration

```
1. List all my GitLab projects
2. Show me the details of project 'mygroup/myproject'
3. List all branches in this project
4. Show me the content of the README.md file in the main branch
```

## Available Resources

### gitlab://projects

List of GitLab projects accessible with your API token.

## Integration with AI Assistants

The GitLab MCP Server integrates with AI assistants that support the Model Context Protocol (MCP). 

### Capabilities

When connected to an AI assistant, this server enables the assistant to:

1. **View and analyze code**: The assistant can fetch file contents, view branch differences, and examine merge request changes for better code understanding.

2. **Provide code reviews**: The assistant can analyze merge requests and provide feedback through comments or internal notes.

3. **Manage project workflows**: The assistant can update merge request attributes, add comments, and help with repository management tasks.

4. **Explore project structure**: The assistant can browse projects, branches, and files to understand the codebase structure.

5. **Configure CI/CD and integrations**: The assistant can help set up webhooks, manage CI/CD variables, and configure project integrations.

### Getting the Most from AI Assistant Integration

- Be specific when asking about projects, merge requests, or files
- Provide project IDs or paths when possible
- Use the assistant for code review by asking it to analyze specific merge requests
- Have the assistant help with repository configuration and management tasks
- Use internal notes for team-only feedback on merge requests

## License

MIT
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

--------------------------------------------------------------------------------
/src/utils/handler-types.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Common types and interfaces for GitLab tool handlers
 */

import { AxiosInstance } from "axios";
import { CallToolRequest } from "@modelcontextprotocol/sdk/types.js";
import { IntegrationsManager } from "../integrations.js";
import { CiCdManager } from "../ci-cd.js";
import { UsersGroupsManager } from "../users-groups.js";

/**
 * Context object passed to all tool handlers
 */
export interface HandlerContext {
  axiosInstance: AxiosInstance;
  integrationsManager: IntegrationsManager;
  ciCdManager: CiCdManager;
  usersGroupsManager: UsersGroupsManager;
}

/**
 * Function signature for tool handlers
 */
export type ToolHandler = (
  params: CallToolRequest['params'],
  context: HandlerContext
) => Promise<any>;

/**
 * Definition for tool registry
 */
export interface ToolRegistry {
  [toolName: string]: ToolHandler;
} 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "mcp-gitlab",
  "version": "0.1.0",
  "description": "A gitlab MCP server that enables you to interact with your gitlab account. Get diff, analyse MR, review MR, cherry-picks, etc have never been easier.",
  "private": true,
  "type": "module",
  "bin": {
    "mcp-gitlab": "./build/index.js"
  },
  "files": [
    "build"
  ],
  "scripts": {
    "build": "tsc && node -e \"import('fs').then(fs => fs.chmodSync('build/index.js', '755'))\"",
    "prepare": "npm run build",
    "watch": "tsc --watch",
    "inspector": "npx @modelcontextprotocol/inspector build/index.js",
    "install-hooks": "cp -f git-hooks/pre-commit .git/hooks/ && chmod +x .git/hooks/pre-commit"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.7.0",
    "axios": "^1.8.3"
  },
  "devDependencies": {
    "@types/node": "^20.11.24",
    "typescript": "^5.3.3"
  }
}

```

--------------------------------------------------------------------------------
/src/utils/response-formatter.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Utility functions for formatting MCP responses
 */

import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
import axios from "axios";

/**
 * Format an API response for MCP protocol
 * 
 * @param data The data to format
 * @returns Formatted MCP response
 */
export function formatResponse(data: any) {
  return {
    content: [{
      type: 'text',
      text: JSON.stringify(data, null, 2)
    }]
  };
}

/**
 * Handle errors from API calls
 * 
 * @param error The error object
 * @param defaultMessage Default message to use
 * @returns McpError object
 */
export function handleApiError(error: unknown, defaultMessage: string): McpError {
  if (axios.isAxiosError(error)) {
    return new McpError(
      ErrorCode.InternalError,
      `GitLab API error: ${error.response?.data?.message || error.message}`
    );
  }
  if (error instanceof Error) {
    return new McpError(
      ErrorCode.InternalError,
      `${defaultMessage}: ${error.message}`
    );
  }
  return new McpError(
    ErrorCode.InternalError,
    defaultMessage
  );
} 
```

--------------------------------------------------------------------------------
/src/utils/resource-handlers.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Resource handlers for GitLab MCP Server
 */

import { AxiosInstance } from "axios";
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
import { handleApiError } from "./response-formatter.js";

/**
 * Handle the listing of resources
 * 
 * @param axiosInstance The axios instance for API calls
 * @returns Response with available resources
 */
export async function handleListResources(axiosInstance: AxiosInstance) {
  return {
    resources: [
      {
        uri: 'gitlab://projects',
        name: 'GitLab Projects',
        description: 'List of GitLab projects accessible to the authenticated user'
      }
    ]
  };
}

/**
 * Handle reading of resources
 * 
 * @param uri Resource URI to read
 * @param axiosInstance The axios instance for API calls
 * @returns Resource contents
 */
export async function handleReadResource(uri: string, axiosInstance: AxiosInstance) {
  try {
    // Handle different resource types
    if (uri === 'gitlab://projects') {
      const response = await axiosInstance.get('/projects', {
        params: { membership: true, per_page: 20 }
      });
      
      return {
        contents: [{
          uri,
          mimeType: 'application/json',
          text: JSON.stringify(response.data, null, 2)
        }]
      };
    }
    
    // Handle project-specific resources
    const projectMatch = uri.match(/^gitlab:\/\/projects\/(\d+)$/);
    if (projectMatch) {
      const projectId = projectMatch[1];
      const response = await axiosInstance.get(`/projects/${projectId}`);
      
      return {
        contents: [{
          uri,
          mimeType: 'application/json',
          text: JSON.stringify(response.data, null, 2)
        }]
      };
    }
    
    throw new McpError(ErrorCode.InvalidRequest, `Resource not found: ${uri}`);
  } catch (error) {
    throw handleApiError(error, `Failed to read resource: ${uri}`);
  }
} 
```

--------------------------------------------------------------------------------
/memory-bank/projectbrief.md:
--------------------------------------------------------------------------------

```markdown
# Project Brief: GitLab MCP Server

## Project Overview
The GitLab MCP Server is a Model Context Protocol (MCP) server that enables AI assistants to interact with GitLab repositories and features. It provides a comprehensive set of tools for repository management, project integrations, CI/CD operations, and user/group administration through a standardized interface.

## Core Requirements
1. Provide a complete set of tools for interacting with GitLab APIs
2. Enable AI assistants to perform GitLab operations through the MCP protocol
3. Support authentication via GitLab API tokens
4. Handle errors gracefully and provide meaningful error messages
5. Maintain security and privacy of GitLab repositories and data
6. Support both GitLab.com and self-hosted GitLab instances
7. Provide tools for managing project integrations and webhooks
8. Enable CI/CD pipeline configuration and management
9. Support user and group administration

## Goals
- Simplify GitLab interactions for AI assistants
- Improve developer workflow by enabling AI code review and repository management
- Support a comprehensive set of GitLab features through the MCP protocol
- Create a maintainable and extendable codebase
- Document the server functionality clearly for developers and users

## Constraints
- Requires a valid GitLab API token for authentication
- Limited to the operations supported by the GitLab API
- Must comply with GitLab API rate limits and policies
- Must ensure security of sensitive repository data

## Success Criteria
- All listed tools function correctly with the GitLab API
- Error handling provides clear feedback on issues
- Documentation covers installation, configuration, and usage
- The server can be easily configured with different GitLab instances
- Project integrations and webhooks can be managed effectively
- CI/CD pipelines can be configured and triggered
- User and group management operations work correctly
- Type safety is maintained throughout the codebase

```

--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

/**
 * Test script for the GitLab MCP server
 * 
 * This script tests the GitLab MCP server by listing projects.
 * It requires a GitLab API token to be set in the environment.
 * 
 * Usage:
 * GITLAB_API_TOKEN=your_token node test.js
 */

import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';

// Get the directory of the current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Path to the built server
const serverPath = resolve(__dirname, 'build/index.js');

// Check if GitLab API token is provided
const GITLAB_API_TOKEN = process.env.GITLAB_API_TOKEN;
if (!GITLAB_API_TOKEN) {
  console.error('Error: GITLAB_API_TOKEN environment variable is required');
  console.error('Usage: GITLAB_API_TOKEN=your_token node test.js');
  process.exit(1);
}

// Start the server process
const serverProcess = spawn('node', [serverPath], {
  env: {
    ...process.env,
    GITLAB_API_TOKEN
  },
  stdio: ['pipe', 'pipe', 'inherit'] // Pipe stdin/stdout, inherit stderr
});

// Handle server process errors
serverProcess.on('error', (error) => {
  console.error('Failed to start server process:', error);
  process.exit(1);
});

// Prepare the list projects request
const listProjectsRequest = {
  jsonrpc: '2.0',
  id: 1,
  method: 'callTool',
  params: {
    name: 'gitlab_list_projects',
    arguments: {
      membership: true,
      per_page: 5
    }
  }
};

// Send the request to the server
serverProcess.stdin.write(JSON.stringify(listProjectsRequest) + '\n');

// Handle server response
let responseData = '';
serverProcess.stdout.on('data', (data) => {
  responseData += data.toString();
  
  try {
    // Try to parse the response as JSON
    const response = JSON.parse(responseData);
    
    // Check if the response is valid
    if (response.id === 1) {
      console.log('Test successful! Server responded with:');
      console.log(JSON.stringify(response.result, null, 2));
      
      // Terminate the server process
      serverProcess.kill();
      process.exit(0);
    }
  } catch (error) {
    // If the response is not complete JSON yet, continue collecting data
  }
});

// Set a timeout to prevent hanging
setTimeout(() => {
  console.error('Test timed out after 10 seconds');
  serverProcess.kill();
  process.exit(1);
}, 10000);
```

--------------------------------------------------------------------------------
/memory-bank/productContext.md:
--------------------------------------------------------------------------------

```markdown
# Product Context: GitLab MCP Server

## Purpose
The GitLab MCP Server fills a crucial need for developers who want to use AI assistants with their GitLab repositories. By implementing the Model Context Protocol (MCP), it allows AI assistants to interact directly with GitLab's API, providing capabilities for code review, project management, and repository operations. This integration bridges the gap between AI assistants and GitLab workflows.

## Problems Solved
1. **AI-GitLab Integration Gap**: Enables AI assistants to interact with GitLab without manual copying/pasting of data
2. **Context Limitations**: Allows AI assistants to access repository code, merge requests, and issues directly
3. **Workflow Friction**: Streamlines developer workflows by enabling AI to assist with GitLab operations
4. **Repository Exploration**: Provides AI assistants with tools to explore and understand repository structure
5. **Code Review Assistance**: Enables AI to review merge requests and provide feedback

## User Experience Goals
- **Simple Setup**: Quick installation and configuration with minimal prerequisites
- **Seamless Integration**: Transparent connection between AI assistants and GitLab repositories
- **Complete Functionality**: Comprehensive coverage of essential GitLab operations
- **Error Clarity**: Clear error messages that help troubleshoot issues
- **Security**: Safe handling of API tokens and sensitive repository data

## Target Users
1. **Developers**: Software developers who use GitLab for version control and want AI assistance
2. **DevOps Engineers**: Teams managing GitLab repositories and CI/CD pipelines
3. **Technical Leads**: Team leaders who review code and manage GitLab projects
4. **Open Source Contributors**: Contributors who interact with GitLab-hosted open source projects

## Use Cases
1. **Code Review**: AI assistants analyze merge requests and provide feedback
2. **Repository Exploration**: AI assistants explore repository structure and file contents
3. **Issue Management**: AI assistants help with issue tracking and management
4. **Merge Request Analysis**: AI assistants analyze code changes in merge requests
5. **Branch Comparison**: AI assistants compare branches and review differences
6. **Repository Documentation**: AI assistants explore repositories to understand codebases
7. **Automated Commenting**: AI assistants add comments to merge requests or issues
8. **Project Integration Management**: AI assistants help configure and manage project integrations and webhooks
9. **CI/CD Pipeline Management**: AI assistants configure and trigger CI/CD pipelines, manage variables and triggers
10. **User and Group Administration**: AI assistants help with user management, group configuration, and access control
11. **Slack Integration Setup**: AI assistants configure Slack notifications for GitLab events

```

--------------------------------------------------------------------------------
/src/utils/tool-registry.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tool registry - maps tool names to handler functions
 */

import { ToolRegistry } from "./handler-types.js";

// Import repository handlers
import * as repoHandlers from "../handlers/repository-handlers.js";

// Import integration handlers
import * as integrationHandlers from "../handlers/integration-handlers.js";

// Import CI/CD handlers
import * as cicdHandlers from "../handlers/cicd-handlers.js";

// Import users and groups handlers
import * as usersGroupsHandlers from "../handlers/users-groups-handlers.js";

/**
 * Registry of all available tools mapped to their handler functions
 */
export const toolRegistry: ToolRegistry = {
  // Repository tools
  gitlab_list_projects: repoHandlers.listProjects,
  gitlab_get_project: repoHandlers.getProject,
  gitlab_list_branches: repoHandlers.listBranches,
  gitlab_list_merge_requests: repoHandlers.listMergeRequests,
  gitlab_get_merge_request: repoHandlers.getMergeRequest,
  gitlab_get_merge_request_changes: repoHandlers.getMergeRequestChanges,
  gitlab_create_merge_request_note: repoHandlers.createMergeRequestNote,
  gitlab_create_merge_request_note_internal: repoHandlers.createMergeRequestNoteInternal,
  gitlab_update_merge_request: repoHandlers.updateMergeRequest,
  gitlab_list_issues: repoHandlers.listIssues,
  gitlab_get_repository_file: repoHandlers.getRepositoryFile,
  gitlab_compare_branches: repoHandlers.compareBranches,

  // Integration tools
  gitlab_list_integrations: integrationHandlers.listIntegrations,
  gitlab_get_integration: integrationHandlers.getIntegration,
  gitlab_update_slack_integration: integrationHandlers.updateSlackIntegration,
  gitlab_disable_slack_integration: integrationHandlers.disableSlackIntegration,
  gitlab_list_webhooks: integrationHandlers.listWebhooks,
  gitlab_get_webhook: integrationHandlers.getWebhook,
  gitlab_add_webhook: integrationHandlers.addWebhook,
  gitlab_update_webhook: integrationHandlers.updateWebhook,
  gitlab_delete_webhook: integrationHandlers.deleteWebhook,
  gitlab_test_webhook: integrationHandlers.testWebhook,

  // CI/CD tools
  gitlab_list_trigger_tokens: cicdHandlers.listTriggerTokens,
  gitlab_get_trigger_token: cicdHandlers.getTriggerToken,
  gitlab_create_trigger_token: cicdHandlers.createTriggerToken,
  gitlab_update_trigger_token: cicdHandlers.updateTriggerToken,
  gitlab_delete_trigger_token: cicdHandlers.deleteTriggerToken,
  gitlab_trigger_pipeline: cicdHandlers.triggerPipeline,
  gitlab_list_cicd_variables: cicdHandlers.listCiCdVariables,
  gitlab_get_cicd_variable: cicdHandlers.getCiCdVariable,
  gitlab_create_cicd_variable: cicdHandlers.createCiCdVariable,
  gitlab_update_cicd_variable: cicdHandlers.updateCiCdVariable,
  gitlab_delete_cicd_variable: cicdHandlers.deleteCiCdVariable,

  // Users and Groups tools
  gitlab_list_users: usersGroupsHandlers.listUsers,
  gitlab_get_user: usersGroupsHandlers.getUser,
  gitlab_list_groups: usersGroupsHandlers.listGroups,
  gitlab_get_group: usersGroupsHandlers.getGroup,
  gitlab_list_group_members: usersGroupsHandlers.listGroupMembers,
  gitlab_add_group_member: usersGroupsHandlers.addGroupMember,
  gitlab_list_project_members: usersGroupsHandlers.listProjectMembers,
  gitlab_add_project_member: usersGroupsHandlers.addProjectMember
}; 
```

--------------------------------------------------------------------------------
/memory-bank/techContext.md:
--------------------------------------------------------------------------------

```markdown
# Technical Context: GitLab MCP Server

## Technologies Used

### Core Technologies
- **Node.js**: Runtime environment (v16 or higher required)
- **TypeScript**: Programming language for type safety and better developer experience
- **Model Context Protocol (MCP)**: Protocol for communication with AI assistants
- **GitLab API v4**: RESTful API for GitLab operations

### Dependencies
- **@modelcontextprotocol/sdk (v1.7.0)**: MCP SDK for server implementation
- **axios**: HTTP client for GitLab API communication
- **TypeScript**: For type definitions and compilation

### Development Dependencies
- **@types/node**: TypeScript definitions for Node.js
- **typescript**: TypeScript compiler

## Development Setup

### Prerequisites
- Node.js (v16 or higher)
- npm
- A GitLab account with API token

### Installation Steps
1. Clone the repository
2. Run `npm install` to install dependencies
3. Run `npm run build` to compile TypeScript to JavaScript
4. Configure GitLab API token in environment variables

### Environment Configuration
Required environment variables:
- `GITLAB_API_TOKEN`: Personal access token from GitLab
- `GITLAB_API_URL` (optional): URL for GitLab API, defaults to 'https://gitlab.com/api/v4'

### Build Process
The build process uses TypeScript compiler (tsc) to generate JavaScript files in the `build` directory. After compilation, the main executable file is made executable with chmod.

## Technical Constraints

### GitLab API Limitations
- Rate limiting according to GitLab's policies
- API token permissions define available operations
- Some operations require specific user permissions in GitLab
- Integration modification may require OAuth tokens that can't be obtained through the API alone
- Webhook test operations have stricter rate limiting (5 requests per minute)

### MCP Protocol Constraints
- Limited to operations defined in MCP protocol
- Communication through stdio only
- Limited support for streaming or large data transfers
- Server must properly configure capabilities for tools and resources

### Security Considerations
- API tokens must be kept secure
- Sensitive repository data should be handled carefully
- No persistent storage of credentials

## Integration Points

### GitLab API Integration
- Uses GitLab API v4
- Authenticates with personal access token
- Supports both GitLab.com and self-hosted instances

### AI Assistant Integration
- Uses MCP protocol for communication
- Integrates with MCP-compatible AI assistants
- Provides tools and resources for GitLab operations

## Deployment

### Local Deployment
Configure in MCP settings file for local AI assistant applications:

#### For Cursor/Roo Cline
```json
{
  "mcpServers": {
    "gitlab": {
      "command": "node",
      "args": [
        "/path/to/mcp-gitlab/build/index.js"
      ],
      "env": {
        "GITLAB_API_TOKEN": "YOUR_GITLAB_API_TOKEN",
        "GITLAB_API_URL": "https://gitlab.com/api/v4"
      }
    }
  }
}
```

#### For Claude Desktop
```json
{
  "mcpServers": {
    "gitlab": {
      "command": "node",
      "args": [
        "/path/to/mcp-gitlab/build/index.js"
      ],
      "env": {
        "GITLAB_API_TOKEN": "YOUR_GITLAB_API_TOKEN",
        "GITLAB_API_URL": "https://gitlab.com/api/v4"
      }
    }
  }
}
```

### Production Deployment
- Can be deployed as a service on a server
- Configure with appropriate environment variables
- Ensure security of API tokens in production environments

```

--------------------------------------------------------------------------------
/src/handlers/users-groups-handlers.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Users and Groups related tool handlers
 */

import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
import { ToolHandler } from "../utils/handler-types.js";
import { formatResponse } from "../utils/response-formatter.js";

/**
 * List users handler
 */
export const listUsers: ToolHandler = async (params, context) => {
  const { username, search, active, blocked, external } = params.arguments || {};
  
  const data = await context.usersGroupsManager.listUsers({
    username: username as string | undefined,
    search: search as string | undefined,
    active: active as boolean | undefined,
    blocked: blocked as boolean | undefined,
    external: external as boolean | undefined
  });
  return formatResponse(data);
};

/**
 * Get user handler
 */
export const getUser: ToolHandler = async (params, context) => {
  const { user_id } = params.arguments || {};
  if (!user_id) {
    throw new McpError(ErrorCode.InvalidParams, 'user_id is required');
  }
  
  const data = await context.usersGroupsManager.getUser(user_id as number);
  return formatResponse(data);
};

/**
 * List groups handler
 */
export const listGroups: ToolHandler = async (params, context) => {
  const { search, owned, min_access_level } = params.arguments || {};
  
  const data = await context.usersGroupsManager.listGroups({
    search: search as string | undefined,
    owned: owned as boolean | undefined,
    min_access_level: min_access_level as number | undefined
  });
  return formatResponse(data);
};

/**
 * Get group handler
 */
export const getGroup: ToolHandler = async (params, context) => {
  const { group_id } = params.arguments || {};
  if (!group_id) {
    throw new McpError(ErrorCode.InvalidParams, 'group_id is required');
  }
  
  const data = await context.usersGroupsManager.getGroup(group_id as string | number);
  return formatResponse(data);
};

/**
 * List group members handler
 */
export const listGroupMembers: ToolHandler = async (params, context) => {
  const { group_id } = params.arguments || {};
  if (!group_id) {
    throw new McpError(ErrorCode.InvalidParams, 'group_id is required');
  }
  
  const data = await context.usersGroupsManager.listGroupMembers(group_id as string | number);
  return formatResponse(data);
};

/**
 * Add group member handler
 */
export const addGroupMember: ToolHandler = async (params, context) => {
  const { group_id, user_id, access_level, expires_at } = params.arguments || {};
  if (!group_id || !user_id || !access_level) {
    throw new McpError(ErrorCode.InvalidParams, 'group_id, user_id, and access_level are required');
  }
  
  const data = await context.usersGroupsManager.addGroupMember(
    group_id as string | number,
    user_id as number,
    access_level as number,
    expires_at as string | undefined
  );
  return formatResponse(data);
};

/**
 * List project members handler
 */
export const listProjectMembers: ToolHandler = async (params, context) => {
  const { project_id } = params.arguments || {};
  if (!project_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id is required');
  }
  
  const data = await context.usersGroupsManager.listProjectMembers(project_id as string | number);
  return formatResponse(data);
};

/**
 * Add project member handler
 */
export const addProjectMember: ToolHandler = async (params, context) => {
  const { project_id, user_id, access_level, expires_at } = params.arguments || {};
  if (!project_id || !user_id || !access_level) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id, user_id, and access_level are required');
  }
  
  const data = await context.usersGroupsManager.addProjectMember(
    project_id as string | number,
    user_id as number,
    access_level as number,
    expires_at as string | undefined
  );
  return formatResponse(data);
}; 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

/**
 * GitLab MCP Server
 * 
 * This server provides tools and resources for interacting with GitLab repositories,
 * merge requests, issues, and more through the GitLab API.
 */

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListResourcesRequestSchema,
  ListToolsRequestSchema,
  ReadResourceRequestSchema,
  ErrorCode,
  McpError,
} from "@modelcontextprotocol/sdk/types.js";
import axios, { AxiosInstance } from "axios";

// Import manager classes
import { IntegrationsManager } from "./integrations.js";
import { CiCdManager } from "./ci-cd.js";
import { UsersGroupsManager } from "./users-groups.js";

// Import utility modules
import { toolRegistry } from "./utils/tool-registry.js";
import { toolDefinitions } from "./utils/tools-data.js";
import { handleListResources, handleReadResource } from "./utils/resource-handlers.js";
import { handleApiError } from "./utils/response-formatter.js";
import { HandlerContext } from "./utils/handler-types.js";

// Get GitLab API token from environment variables
const GITLAB_API_TOKEN = process.env.GITLAB_API_TOKEN;
const GITLAB_API_URL = process.env.GITLAB_API_URL || 'https://gitlab.com/api/v4';

if (!GITLAB_API_TOKEN) {
  console.error("GITLAB_API_TOKEN environment variable is required");
  process.exit(1);
}

/**
 * GitLab MCP Server class
 */
class GitLabServer {
  private server: Server;
  private axiosInstance: AxiosInstance;
  private integrationsManager: IntegrationsManager;
  private ciCdManager: CiCdManager;
  private usersGroupsManager: UsersGroupsManager;
  private handlerContext: HandlerContext;

  constructor() {
    // Initialize server with metadata and capabilities
    this.server = new Server(
      {
        name: "mcp-gitlab",
        version: "0.1.0",
      },
      {
        capabilities: {
          canListTools: true,
          canCallTools: true,
          canListResources: true,
          canReadResources: true,
          tools: { listChanged: false }, // Enable tools capability with proper structure
          resources: { listChanged: false } // Enable resources capability with proper structure
        }
      }
    );
    
    // Create axios instance with base URL and auth headers
    this.axiosInstance = axios.create({
      baseURL: GITLAB_API_URL,
      headers: {
        'PRIVATE-TOKEN': GITLAB_API_TOKEN
      }
    });

    // Initialize manager classes
    this.integrationsManager = new IntegrationsManager(this.axiosInstance);
    this.ciCdManager = new CiCdManager(this.axiosInstance);
    this.usersGroupsManager = new UsersGroupsManager(this.axiosInstance);

    // Create handler context
    this.handlerContext = {
      axiosInstance: this.axiosInstance,
      integrationsManager: this.integrationsManager,
      ciCdManager: this.ciCdManager,
      usersGroupsManager: this.usersGroupsManager
    };

    // Setup request handlers
    this.setupRequestHandlers();
  }

  /**
   * Set up MCP request handlers
   */
  private setupRequestHandlers() {
    // List available tools
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: toolDefinitions
      };
    });

    // List available resources
    this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
      return handleListResources(this.axiosInstance);
    });

    // Read GitLab resources
    this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
      return handleReadResource(request.params.uri, this.axiosInstance);
    });

    // Call GitLab tools
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      try {
        const toolName = request.params.name;
        const handler = toolRegistry[toolName];
        
        if (!handler) {
          throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${toolName}`);
        }
        
        return await handler(request.params, this.handlerContext);
      } catch (error) {
        if (error instanceof McpError) {
          throw error;
        }
        throw handleApiError(error, 'Error executing GitLab operation');
      }
    });
  }

  /**
   * Start the GitLab MCP server
   */
  public async start() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
  }
}

// Create and start the server
const server = new GitLabServer();
server.start().catch(error => {
  console.error("Failed to start server:", error);
  process.exit(1);
}); 
```

--------------------------------------------------------------------------------
/memory-bank/activeContext.md:
--------------------------------------------------------------------------------

```markdown
# Active Context: GitLab MCP Server

## Current Focus
The current development focus has been on enhancing the documentation and developer workflow for the GitLab MCP server:

1. **Documentation Improvements**: Added a centralized tools documentation in TOOLS.md generated from the source code to make it easier for developers to understand the available tools
2. **Developer Workflow Enhancements**: Implemented git hooks to automatically keep documentation in sync with code
3. **License Clarification**: Added MIT license file to clarify the project's licensing
4. **Project Attribution**: Updated README.md to acknowledge this is an extension of the Model Context Protocol project

Most recently, we focused on:
1. Creating a script that automatically generates TOOLS.md from the src/utils/tools-data.ts file
2. Setting up a git pre-commit hook to regenerate TOOLS.md whenever tools-data.ts changes
3. Ensuring proper anchor links in the TOOLS.md table of contents using GitHub-compatible formats
4. Adding the MIT license file to formally license the project
5. Updating the README.md to mention the project's origin and link to the tools documentation

## Recent Changes
- Added a script (`scripts/generate-tools-md.js`) to convert tools-data.ts to TOOLS.md in a well-formatted markdown table
- Created a pre-commit git hook that automatically regenerates TOOLS.md when tools-data.ts changes
- Created a versioned copy of the git hook in the repository for other developers
- Updated package.json to add an `install-hooks` script for easier hook installation
- Updated README.md installation instructions to mention the git hooks
- Fixed anchor link generation in TOOLS.md for sections with special characters, particularly:
  - "Integrations & Webhooks" → "#integrations--webhooks"
  - "User & Group Management" → "#user--group-management"
- Added the MIT license file to the project
- Updated README.md to mention that this is an extended version of the MCP GitLab server from the Model Context Protocol project
- Restructured the README.md to use TOOLS.md as the source of truth for tool documentation

## Active Decisions

### Auto-generated Documentation
- Created a script that parses the tools-data.ts TypeScript file and generates a markdown table of all tools
- Used regex patterns to extract tool names, descriptions, parameters, and required flags
- Implemented special handling for anchor links with ampersands
- Created a git hook to ensure documentation stays in sync with code

### Git Hook Implementation
- Created a git pre-commit hook that detects changes to tools-data.ts and automatically regenerates TOOLS.md
- Added the hook to both .git/hooks (local) and git-hooks/ (versioned) directories
- Added an npm script for easy installation of the hooks
- Configured the hook to only run when relevant files change to avoid unnecessary processing

### Documentation Organization
- Moved detailed tool documentation from README.md to TOOLS.md
- Organized tools by category in the documentation:
  - Repository Management
  - Integrations & Webhooks
  - CI/CD Management
  - User & Group Management
- Added proper anchors for each section to make navigation easier
- Updated README.md to link to TOOLS.md instead of containing duplicate information

### Anchor Link Handling
- Identified issues with GitHub's anchor link format for sections with special characters
- Implemented hardcoded special cases for sections with ampersands to ensure correct navigation
- Used single-dash format for ampersands (e.g., "User & Group Management" → "#user--group-management")
- Ensured all table of contents links correctly point to their respective sections

### License and Attribution
- Added the MIT license file to clarify the project's licensing
- Updated README.md to acknowledge that this is an extended version of the MCP GitLab server
- Added a link to the original project repository for attribution

## Next Steps

### Short-term Tasks
1. Complete unit tests for all tools
2. Add support for pagination in list operations
3. Support for wiki management
4. Support for repository commits and tags

### Medium-term Goals
1. Expand tool set to cover more GitLab API endpoints
2. Implement caching for improved performance
3. Add support for webhook callbacks
4. Create helper functions for common operations

### Long-term Vision
1. Support for GitLab GraphQL API
2. Support for advanced authentication methods
3. Implementation of resource streaming for large files
4. Integration with CI/CD pipelines for automated testing and deployment

## Open Questions
1. How to handle GitLab API rate limiting effectively?
2. What's the best approach for handling large repository files?
3. How to structure more complex GitLab operations that require multiple API calls?
4. What additional metadata should be provided to AI assistants for better context?

## Current Challenges
1. Ensuring consistent type safety across all GitLab API interactions
2. Managing GitLab API token security
3. Supporting various GitLab API versions and endpoints
4. Handling large responses efficiently within MCP protocol constraints

```

--------------------------------------------------------------------------------
/src/handlers/cicd-handlers.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * CI/CD-related tool handlers
 */

import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
import { ToolHandler } from "../utils/handler-types.js";
import { formatResponse } from "../utils/response-formatter.js";

/**
 * List trigger tokens handler
 */
export const listTriggerTokens: ToolHandler = async (params, context) => {
  const { project_id } = params.arguments || {};
  if (!project_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id is required');
  }
  
  const data = await context.ciCdManager.listTriggerTokens(project_id as string | number);
  return formatResponse(data);
};

/**
 * Get trigger token handler
 */
export const getTriggerToken: ToolHandler = async (params, context) => {
  const { project_id, trigger_id } = params.arguments || {};
  if (!project_id || !trigger_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and trigger_id are required');
  }
  
  const data = await context.ciCdManager.getTriggerToken(project_id as string | number, trigger_id as number);
  return formatResponse(data);
};

/**
 * Create trigger token handler
 */
export const createTriggerToken: ToolHandler = async (params, context) => {
  const { project_id, description } = params.arguments || {};
  if (!project_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id is required');
  }
  
  const data = await context.ciCdManager.createTriggerToken(project_id as string | number, description as string);
  return formatResponse(data);
};

/**
 * Update trigger token handler
 */
export const updateTriggerToken: ToolHandler = async (params, context) => {
  const { project_id, trigger_id, description } = params.arguments || {};
  if (!project_id || !trigger_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and trigger_id are required');
  }
  
  const data = await context.ciCdManager.updateTriggerToken(project_id as string | number, trigger_id as number, description as string);
  return formatResponse(data);
};

/**
 * Delete trigger token handler
 */
export const deleteTriggerToken: ToolHandler = async (params, context) => {
  const { project_id, trigger_id } = params.arguments || {};
  if (!project_id || !trigger_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and trigger_id are required');
  }
  
  const data = await context.ciCdManager.deleteTriggerToken(project_id as string | number, trigger_id as number);
  return formatResponse(data);
};

/**
 * Trigger pipeline handler
 */
export const triggerPipeline: ToolHandler = async (params, context) => {
  const { project_id, ref, token, variables } = params.arguments || {};
  if (!project_id || !ref || !token) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id, ref, and token are required');
  }
  
  const data = await context.ciCdManager.triggerPipeline(
    project_id as string | number, 
    ref as string, 
    token as string, 
    variables as Record<string, string> | undefined
  );
  return formatResponse(data);
};

/**
 * List CI/CD variables handler
 */
export const listCiCdVariables: ToolHandler = async (params, context) => {
  const { project_id } = params.arguments || {};
  if (!project_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id is required');
  }
  
  const data = await context.ciCdManager.listCiCdVariables(project_id as string | number);
  return formatResponse(data);
};

/**
 * Get CI/CD variable handler
 */
export const getCiCdVariable: ToolHandler = async (params, context) => {
  const { project_id, key } = params.arguments || {};
  if (!project_id || !key) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and key are required');
  }
  
  const data = await context.ciCdManager.getCiCdVariable(project_id as string | number, key as string);
  return formatResponse(data);
};

/**
 * Create CI/CD variable handler
 */
export const createCiCdVariable: ToolHandler = async (params, context) => {
  const { project_id, key, value, protected: isProtected, masked, variable_type, environment_scope } = params.arguments || {};
  if (!project_id || !key || !value) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id, key, and value are required');
  }
  
  const data = await context.ciCdManager.createCiCdVariable(project_id as string | number, {
    key: key as string,
    value: value as string,
    protected: isProtected as boolean | undefined,
    masked: masked as boolean | undefined,
    variable_type: variable_type as 'env_var' | 'file' | undefined,
    environment_scope: environment_scope as string | undefined
  });
  return formatResponse(data);
};

/**
 * Update CI/CD variable handler
 */
export const updateCiCdVariable: ToolHandler = async (params, context) => {
  const { project_id, key, value, protected: isProtected, masked, variable_type, environment_scope } = params.arguments || {};
  if (!project_id || !key) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and key are required');
  }
  
  const data = await context.ciCdManager.updateCiCdVariable(project_id as string | number, key as string, {
    value: value as string,
    protected: isProtected as boolean | undefined,
    masked: masked as boolean | undefined,
    variable_type: variable_type as 'env_var' | 'file' | undefined,
    environment_scope: environment_scope as string | undefined
  });
  return formatResponse(data);
};

/**
 * Delete CI/CD variable handler
 */
export const deleteCiCdVariable: ToolHandler = async (params, context) => {
  const { project_id, key } = params.arguments || {};
  if (!project_id || !key) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and key are required');
  }
  
  const data = await context.ciCdManager.deleteCiCdVariable(project_id as string | number, key as string);
  return formatResponse(data);
}; 
```

--------------------------------------------------------------------------------
/scripts/generate-tools-md.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

/**
 * Script to convert src/utils/tools-data.ts to TOOLS.md
 */

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

// Get current file path and directory
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Read tools-data.ts file
const toolsDataPath = path.resolve(__dirname, '../src/utils/tools-data.ts');
const outputPath = path.resolve(__dirname, '../TOOLS.md');

// Convert heading text to GitHub-compatible anchor
function generateAnchor(text) {
  // Hard-coded anchors for specific categories
  if (text === 'Integrations & Webhooks') {
    return 'integrations--webhooks';
  } else if (text === 'User & Group Management') {
    return 'user--group-management';
  }
  
  // Generic anchor generation for other categories
  return text
    .toLowerCase()
    .replace(/[\/]/g, '')     // Remove forward slashes
    .replace(/\s+/g, '-');    // Replace spaces with hyphens
}

try {
  // Read the source file
  const toolsData = fs.readFileSync(toolsDataPath, 'utf8');
  
  // Extract tool definitions using regex
  const toolDefinitionsRegex = /export const toolDefinitions = \[([\s\S]*?)\];/;
  const match = toolsData.match(toolDefinitionsRegex);
  
  if (!match) {
    console.error('Could not parse tool definitions from source file');
    process.exit(1);
  }
  
  // Split into individual tool definitions
  const toolDefinitions = [];
  let toolsContent = match[1];
  let braceCount = 0;
  let currentTool = '';

  // Extract individual tool definitions respecting nested objects
  for (let i = 0; i < toolsContent.length; i++) {
    const char = toolsContent[i];
    currentTool += char;
    
    if (char === '{') {
      braceCount++;
    } else if (char === '}') {
      braceCount--;
      if (braceCount === 0 && i < toolsContent.length - 1) {
        if (toolsContent[i+1] === ',') {
          // Include the comma in the current tool definition
          currentTool += toolsContent[i+1];
          i++;
        }
        toolDefinitions.push(currentTool.trim());
        currentTool = '';
      }
    }
  }
  
  // Parse each tool definition
  const tools = [];
  
  for (const toolDef of toolDefinitions) {
    // Extract basic tool info
    const nameMatch = toolDef.match(/name:\s*['"]([^'"]+)['"]/);
    const descMatch = toolDef.match(/description:\s*['"]([^'"]+)['"]/);
    
    if (!nameMatch || !descMatch) continue;
    
    const name = nameMatch[1];
    const description = descMatch[1];
    
    // Extract required parameters
    const requiredMatch = toolDef.match(/required:\s*\[([\s\S]*?)\]/);
    const required = requiredMatch ? 
      (requiredMatch[1].match(/['"]([^'"]+)['"]/g) || []).map(r => r.replace(/['"]/g, '')) : 
      [];
    
    // Extract properties
    const propertiesMatch = toolDef.match(/properties:\s*{([\s\S]*?)}\s*,\s*(?:required:|type:)/);
    if (!propertiesMatch) {
      tools.push({
        name,
        description,
        parameters: []
      });
      continue;
    }
    
    const propertiesContent = propertiesMatch[1];
    
    // Parse individual parameters
    const parameters = [];
    const paramMatches = [...propertiesContent.matchAll(/([a-zA-Z0-9_]+):\s*{([\s\S]*?)(?=\s*},\s*[a-zA-Z0-9_]+:|$)/g)];
    
    for (const paramMatch of paramMatches) {
      const paramName = paramMatch[1];
      const paramContent = paramMatch[2];
      
      const typeMatch = paramContent.match(/type:\s*['"]([^'"]+)['"]/);
      const descMatch = paramContent.match(/description:\s*['"]([^'"]+)['"]/);
      
      if (typeMatch && descMatch) {
        parameters.push({
          name: paramName,
          type: typeMatch[1],
          description: descMatch[1],
          required: required.includes(paramName)
        });
      }
    }
    
    tools.push({
      name,
      description,
      parameters
    });
  }
  
  // Generate markdown content
  let markdown = '# GitLab MCP Server Tools\n\n';
  markdown += 'This document provides details on all available tools in the GitLab MCP server.\n\n';
  markdown += 'Each tool is designed to interact with GitLab APIs, allowing AI assistants to work with repositories, merge requests, issues, CI/CD pipelines, and more.\n\n';
  
  // Group tools by category based on name prefix
  const categories = {
    'Repository Management': tools.filter(t => 
      t.name.includes('project') || 
      t.name.includes('branch') || 
      t.name.includes('merge_request') || 
      t.name.includes('issue') || 
      t.name.includes('repository')),
    'Integrations & Webhooks': tools.filter(t => 
      t.name.includes('integration') || 
      t.name.includes('webhook')),
    'CI/CD Management': tools.filter(t => 
      t.name.includes('trigger') || 
      t.name.includes('pipeline') || 
      t.name.includes('cicd')),
    'User & Group Management': tools.filter(t => 
      t.name.includes('user') || 
      t.name.includes('group') || 
      t.name.includes('member'))
  };
  
  // Generate table of contents
  markdown += '## Table of Contents\n\n';
  Object.keys(categories).forEach(category => {
    const anchor = generateAnchor(category);
    markdown += `- [${category}](#${anchor})\n`;
  });
  markdown += '\n';
  
  // Generate tool documentation by category
  Object.entries(categories).forEach(([category, categoryTools]) => {
    markdown += `## ${category}\n\n`;
    
    categoryTools.forEach(tool => {
      markdown += `### ${tool.name}\n\n`;
      markdown += `${tool.description}\n\n`;
      
      if (tool.parameters.length > 0) {
        markdown += '**Parameters:**\n\n';
        markdown += '| Name | Type | Required | Description |\n';
        markdown += '| ---- | ---- | -------- | ----------- |\n';
        
        tool.parameters.forEach(param => {
          markdown += `| \`${param.name}\` | \`${param.type}\` | ${param.required ? 'Yes' : 'No'} | ${param.description} |\n`;
        });
        
        markdown += '\n';
      } else {
        markdown += 'This tool does not require any parameters.\n\n';
      }
    });
  });
  
  // Add footer
  markdown += '---\n\n';
  markdown += 'Generated automatically from `src/utils/tools-data.ts`\n';
  
  // Write to TOOLS.md
  fs.writeFileSync(outputPath, markdown);
  console.log(`Successfully generated ${outputPath}`);
  
} catch (error) {
  console.error('Error generating markdown:', error);
  process.exit(1);
}
```

--------------------------------------------------------------------------------
/src/handlers/integration-handlers.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Integration-related tool handlers
 */

import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
import { ToolHandler } from "../utils/handler-types.js";
import { formatResponse } from "../utils/response-formatter.js";

/**
 * List integrations handler
 */
export const listIntegrations: ToolHandler = async (params, context) => {
  const { project_id } = params.arguments || {};
  if (!project_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id is required');
  }
  
  const data = await context.integrationsManager.listIntegrations(project_id as string | number);
  return formatResponse(data);
};

/**
 * Get integration handler
 */
export const getIntegration: ToolHandler = async (params, context) => {
  const { project_id, integration_slug } = params.arguments || {};
  if (!project_id || !integration_slug) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and integration_slug are required');
  }
  
  const data = await context.integrationsManager.getIntegration(project_id as string | number, integration_slug as string);
  return formatResponse(data);
};

/**
 * Update Slack integration handler
 */
export const updateSlackIntegration: ToolHandler = async (params, context) => {
  const { project_id, webhook, username, channel, notify_only_broken_pipelines, notify_only_default_branch, ...options } = params.arguments || {};
  if (!project_id || !webhook) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and webhook are required');
  }
  
  const data = await context.integrationsManager.updateSlackIntegration(project_id as string | number, {
    webhook,
    username,
    channel,
    notify_only_broken_pipelines,
    notify_only_default_branch,
    ...options
  });
  return formatResponse(data);
};

/**
 * Disable Slack integration handler
 */
export const disableSlackIntegration: ToolHandler = async (params, context) => {
  const { project_id } = params.arguments || {};
  if (!project_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id is required');
  }
  
  const data = await context.integrationsManager.disableSlackIntegration(project_id as string | number);
  return formatResponse(data);
};

/**
 * List webhooks handler
 */
export const listWebhooks: ToolHandler = async (params, context) => {
  const { project_id } = params.arguments || {};
  if (!project_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id is required');
  }
  
  const data = await context.integrationsManager.listWebhooks(project_id as string | number);
  return formatResponse(data);
};

/**
 * Get webhook handler
 */
export const getWebhook: ToolHandler = async (params, context) => {
  const { project_id, webhook_id } = params.arguments || {};
  if (!project_id || !webhook_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and webhook_id are required');
  }
  
  const data = await context.integrationsManager.getWebhook(project_id as string | number, webhook_id as number);
  return formatResponse(data);
};

/**
 * Add webhook handler
 */
export const addWebhook: ToolHandler = async (params, context) => {
  const { project_id, ...options } = params.arguments || {};
  if (!project_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id is required');
  }
  
  if (!options.url) {
    throw new McpError(ErrorCode.InvalidParams, 'url is required for webhook');
  }
  
  const data = await context.integrationsManager.addWebhook(project_id as string | number, options as {
    url: string;
    name?: string;
    description?: string;
    token?: string;
    push_events?: boolean;
    push_events_branch_filter?: string;
    issues_events?: boolean;
    confidential_issues_events?: boolean;
    merge_requests_events?: boolean;
    tag_push_events?: boolean;
    note_events?: boolean;
    confidential_note_events?: boolean;
    job_events?: boolean;
    pipeline_events?: boolean;
    wiki_page_events?: boolean;
    deployment_events?: boolean;
    releases_events?: boolean;
    feature_flag_events?: boolean;
    enable_ssl_verification?: boolean;
    custom_webhook_template?: string;
    custom_headers?: Array<{key: string; value?: string}>;
  });
  return formatResponse(data);
};

/**
 * Update webhook handler
 */
export const updateWebhook: ToolHandler = async (params, context) => {
  const { project_id, webhook_id, ...options } = params.arguments || {};
  if (!project_id || !webhook_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and webhook_id are required');
  }
  
  if (!options.url) {
    throw new McpError(ErrorCode.InvalidParams, 'url is required for webhook');
  }
  
  const data = await context.integrationsManager.updateWebhook(project_id as string | number, webhook_id as number, options as {
    url: string;
    name?: string;
    description?: string;
    token?: string;
    push_events?: boolean;
    push_events_branch_filter?: string;
    issues_events?: boolean;
    confidential_issues_events?: boolean;
    merge_requests_events?: boolean;
    tag_push_events?: boolean;
    note_events?: boolean;
    confidential_note_events?: boolean;
    job_events?: boolean;
    pipeline_events?: boolean;
    wiki_page_events?: boolean;
    deployment_events?: boolean;
    releases_events?: boolean;
    feature_flag_events?: boolean;
    enable_ssl_verification?: boolean;
    custom_webhook_template?: string;
    custom_headers?: Array<{key: string; value?: string}>;
  });
  return formatResponse(data);
};

/**
 * Delete webhook handler
 */
export const deleteWebhook: ToolHandler = async (params, context) => {
  const { project_id, webhook_id } = params.arguments || {};
  if (!project_id || !webhook_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and webhook_id are required');
  }
  
  const data = await context.integrationsManager.deleteWebhook(project_id as string | number, webhook_id as number);
  return formatResponse(data);
};

/**
 * Test webhook handler
 */
export const testWebhook: ToolHandler = async (params, context) => {
  const { project_id, webhook_id, trigger_type } = params.arguments || {};
  if (!project_id || !webhook_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and webhook_id are required');
  }
  
  const data = await context.integrationsManager.testWebhook(
    project_id as string | number, 
    webhook_id as number, 
    (trigger_type as string) || 'push_events'
  );
  return formatResponse(data);
}; 
```

--------------------------------------------------------------------------------
/src/handlers/repository-handlers.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Repository-related tool handlers
 */

import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
import { ToolHandler } from "../utils/handler-types.js";
import { formatResponse } from "../utils/response-formatter.js";

/**
 * List projects handler
 */
export const listProjects: ToolHandler = async (params, context) => {
  const { search, owned, membership, per_page } = params.arguments || {};
  const response = await context.axiosInstance.get('/projects', {
    params: {
      search,
      owned: owned === true ? true : undefined,
      membership: membership === true ? true : undefined,
      per_page: per_page || 20
    }
  });
  
  return formatResponse(response.data);
};

/**
 * Get project details handler
 */
export const getProject: ToolHandler = async (params, context) => {
  const { project_id } = params.arguments || {};
  if (!project_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id is required');
  }
  
  const response = await context.axiosInstance.get(`/projects/${encodeURIComponent(String(project_id))}`);
  return formatResponse(response.data);
};

/**
 * List branches handler
 */
export const listBranches: ToolHandler = async (params, context) => {
  const { project_id, search } = params.arguments || {};
  if (!project_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id is required');
  }
  
  const response = await context.axiosInstance.get(
    `/projects/${encodeURIComponent(String(project_id))}/repository/branches`,
    { params: { search } }
  );
  return formatResponse(response.data);
};

/**
 * List merge requests handler
 */
export const listMergeRequests: ToolHandler = async (params, context) => {
  const { project_id, state, scope } = params.arguments || {};
  if (!project_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id is required');
  }
  
  const response = await context.axiosInstance.get(
    `/projects/${encodeURIComponent(String(project_id))}/merge_requests`,
    { params: { state, scope } }
  );
  return formatResponse(response.data);
};

/**
 * Get merge request details handler
 */
export const getMergeRequest: ToolHandler = async (params, context) => {
  const { project_id, merge_request_iid } = params.arguments || {};
  if (!project_id || !merge_request_iid) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and merge_request_iid are required');
  }
  
  const response = await context.axiosInstance.get(
    `/projects/${encodeURIComponent(String(project_id))}/merge_requests/${merge_request_iid}`
  );
  return formatResponse(response.data);
};

/**
 * Get merge request changes handler
 */
export const getMergeRequestChanges: ToolHandler = async (params, context) => {
  const { project_id, merge_request_iid } = params.arguments || {};
  if (!project_id || !merge_request_iid) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and merge_request_iid are required');
  }
  
  const response = await context.axiosInstance.get(
    `/projects/${encodeURIComponent(String(project_id))}/merge_requests/${merge_request_iid}/changes`
  );
  return formatResponse(response.data);
};

/**
 * Create merge request note handler
 */
export const createMergeRequestNote: ToolHandler = async (params, context) => {
  const { project_id, merge_request_iid, body } = params.arguments || {};
  if (!project_id || !merge_request_iid || !body) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id, merge_request_iid, and body are required');
  }
  
  const response = await context.axiosInstance.post(
    `/projects/${encodeURIComponent(String(project_id))}/merge_requests/${merge_request_iid}/notes`,
    { body }
  );
  return formatResponse(response.data);
};

/**
 * List issues handler
 */
export const listIssues: ToolHandler = async (params, context) => {
  const { project_id, state, labels } = params.arguments || {};
  if (!project_id) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id is required');
  }
  
  const response = await context.axiosInstance.get(
    `/projects/${encodeURIComponent(String(project_id))}/issues`,
    { params: { state, labels } }
  );
  return formatResponse(response.data);
};

/**
 * Get repository file handler
 */
export const getRepositoryFile: ToolHandler = async (params, context) => {
  const { project_id, file_path, ref } = params.arguments || {};
  if (!project_id || !file_path) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and file_path are required');
  }
  
  const response = await context.axiosInstance.get(
    `/projects/${encodeURIComponent(String(project_id))}/repository/files/${encodeURIComponent(String(file_path))}`,
    { params: { ref: ref || 'main' } }
  );
  return formatResponse(response.data);
};

/**
 * Compare branches handler
 */
export const compareBranches: ToolHandler = async (params, context) => {
  const { project_id, from, to } = params.arguments || {};
  if (!project_id || !from || !to) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id, from, and to are required');
  }
  
  const response = await context.axiosInstance.get(
    `/projects/${encodeURIComponent(String(project_id))}/repository/compare`,
    { params: { from, to } }
  );
  return formatResponse(response.data);
};

/**
 * Update merge request title and description handler
 */
export const updateMergeRequest: ToolHandler = async (params, context) => {
  const { project_id, merge_request_iid, title, description } = params.arguments || {};
  if (!project_id || !merge_request_iid) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id and merge_request_iid are required');
  }
  
  if (!title && !description) {
    throw new McpError(ErrorCode.InvalidParams, 'At least one of title or description is required');
  }
  
  const response = await context.axiosInstance.put(
    `/projects/${encodeURIComponent(String(project_id))}/merge_requests/${merge_request_iid}`,
    { title, description }
  );
  return formatResponse(response.data);
};

/**
 * Create merge request note handler with internal note option
 */
export const createMergeRequestNoteInternal: ToolHandler = async (params, context) => {
  const { project_id, merge_request_iid, body, internal } = params.arguments || {};
  if (!project_id || !merge_request_iid || !body) {
    throw new McpError(ErrorCode.InvalidParams, 'project_id, merge_request_iid, and body are required');
  }
  
  const response = await context.axiosInstance.post(
    `/projects/${encodeURIComponent(String(project_id))}/merge_requests/${merge_request_iid}/notes`,
    { body, internal: internal === true }
  );
  return formatResponse(response.data);
}; 
```

--------------------------------------------------------------------------------
/memory-bank/progress.md:
--------------------------------------------------------------------------------

```markdown
# Progress: GitLab MCP Server

## What Works

### Core Functionality
- ✅ MCP server setup and configuration with proper capabilities for tools and resources
- ✅ Integration with MCP SDK 1.7.0
- ✅ GitLab API integration with axios
- ✅ Error handling framework
- ✅ Modular codebase structure with domain-specific managers
- ✅ Tool registry for mapping tool names to handler functions
- ✅ Complete tool definitions for all implemented tools
- ✅ TypeScript compilation with no errors
- ✅ Async server initialization with error handling
- ✅ Auto-generated tool documentation (TOOLS.md)
- ✅ Pre-commit hook for keeping documentation in sync
- ✅ MIT license file added
- ✅ Clear attribution to original project

### Implemented Tools
- ✅ `gitlab_list_projects`: List GitLab projects
- ✅ `gitlab_get_project`: Get project details
- ✅ `gitlab_list_branches`: List project branches
- ✅ `gitlab_list_merge_requests`: List project merge requests
- ✅ `gitlab_get_merge_request`: Get merge request details
- ✅ `gitlab_get_merge_request_changes`: Get merge request changes/diff
- ✅ `gitlab_create_merge_request_note`: Add comment to merge request
- ✅ `gitlab_create_merge_request_note_internal`: Add internal comment to merge request
- ✅ `gitlab_update_merge_request`: Update merge request title and description
- ✅ `gitlab_list_issues`: List project issues
- ✅ `gitlab_get_repository_file`: Get repository file content
- ✅ `gitlab_compare_branches`: Compare branches/tags/commits

### Project Setting Tools
- ✅ `gitlab_list_integrations`: List project integrations
- ✅ `gitlab_get_integration`: Get integration details
- ✅ `gitlab_update_slack_integration`: Update Slack integration
- ✅ `gitlab_disable_slack_integration`: Disable Slack integration
- ✅ `gitlab_list_webhooks`: List webhooks
- ✅ `gitlab_get_webhook`: Get webhook details
- ✅ `gitlab_add_webhook`: Add webhook with proper type safety
- ✅ `gitlab_update_webhook`: Update webhook with proper type safety
- ✅ `gitlab_delete_webhook`: Delete webhook
- ✅ `gitlab_test_webhook`: Test webhook

### CI/CD Tools
- ✅ `gitlab_list_trigger_tokens`: List pipeline trigger tokens
- ✅ `gitlab_get_trigger_token`: Get trigger token details
- ✅ `gitlab_create_trigger_token`: Create trigger token
- ✅ `gitlab_update_trigger_token`: Update trigger token
- ✅ `gitlab_delete_trigger_token`: Delete trigger token
- ✅ `gitlab_trigger_pipeline`: Trigger pipeline with proper type safety for variables
- ✅ `gitlab_list_cicd_variables`: List CI/CD variables
- ✅ `gitlab_get_cicd_variable`: Get CI/CD variable
- ✅ `gitlab_create_cicd_variable`: Create CI/CD variable
- ✅ `gitlab_update_cicd_variable`: Update CI/CD variable
- ✅ `gitlab_delete_cicd_variable`: Delete CI/CD variable

### User and Group Tools
- ✅ `gitlab_list_users`: List users
- ✅ `gitlab_get_user`: Get user details
- ✅ `gitlab_list_groups`: List groups
- ✅ `gitlab_get_group`: Get group details
- ✅ `gitlab_list_group_members`: List group members
- ✅ `gitlab_add_group_member`: Add group member
- ✅ `gitlab_list_project_members`: List project members
- ✅ `gitlab_add_project_member`: Add project member

### Implemented Resources
- ✅ `gitlab://projects`: List of GitLab projects

### Type Safety and Error Handling
- ✅ Parameter validation for required fields
- ✅ Proper type casting for API parameters
- ✅ Error handling for API errors
- ✅ Type-safe webhook management
- ✅ Type-safe pipeline variables
- ✅ Proper server initialization with capabilities support
- ✅ Compatibility with MCP SDK 1.7.0
- ✅ Async/await pattern for server startup

### Code Organization
- ✅ Tool registry for mapping tool names to handler functions
- ✅ Modular file structure with domain-specific manager classes
- ✅ Centralized error handling utilities
- ✅ Separated resource handler functions
- ✅ Clean type definitions and interfaces
- ✅ Complete tool definitions for all implemented tools

### Documentation and Developer Experience
- ✅ Basic setup instructions
- ✅ Auto-generated tool documentation (TOOLS.md)
- ✅ Git pre-commit hook for keeping documentation in sync
- ✅ npm script for easy hook installation
- ✅ Environment configuration guidance
- ✅ MIT license file added
- ✅ Clear attribution to original project
- ✅ Correct anchor links for all documentation sections

## What's Left to Build

### Additional Tools
- ✅ Support for project settings management
  - ✅ Project integrations/webhooks management
  - ✅ Slack integration management
- ✅ Support for GitLab CI/CD operations
  - ✅ Pipeline triggers management
  - ✅ CI/CD variables management
  - ✅ Pipeline execution
- ✅ Support for user and group management
  - ✅ User administration
  - ✅ Group management
  - ✅ Project/group membership management
- ⬜ Support for wiki management
- ⬜ Support for repository commits and tags

### Additional Resources
- ⬜ Project-specific resources (branches, issues, etc.)
- ⬜ User-specific resources
- ⬜ Group-specific resources

### Enhanced Features
- ⬜ Pagination support for list operations
- ⬜ Caching of API responses
- ⬜ Advanced filtering of results
- ⬜ Support for GitLab GraphQL API
- ⬜ Webhook support for events

### Testing & Documentation
- ⬜ Unit tests for all tools
- ⬜ Integration tests with GitLab API
- ⬜ Advanced usage examples
- ⬜ Troubleshooting guide
- ⬜ API reference documentation

## Current Status
The GitLab MCP Server has been enhanced with improved documentation, developer workflows, and project attribution:

1. **Auto-generated Documentation**: Created a script that generates TOOLS.md from tools-data.ts to provide a complete reference of available tools
2. **Git Hooks**: Implemented a pre-commit hook to keep documentation in sync with code
3. **License**: Added MIT license file to clarify the project's licensing
4. **Attribution**: Updated README.md to acknowledge that this is an extended version of the MCP GitLab server
5. **Better Navigation**: Fixed anchor links in TOOLS.md for improved navigation, particularly for sections with special characters
6. **Documentation Organization**: Moved detailed tool documentation from README.md to TOOLS.md
7. **Developer Experience**: Added npm script for easy installation of git hooks

The server continues to provide a comprehensive set of GitLab operations through the MCP protocol, allowing AI assistants to interact with:

1. **GitLab Repositories**: Browse repositories, branches, files, and commit information
2. **Project Integrations**: Manage webhooks and service integrations, with specific support for Slack integration
3. **CI/CD Pipelines**: Configure and trigger pipelines, manage variables and schedules
4. **User & Group Management**: Administer users, groups, and access permissions

## Known Issues
1. No pagination support for list operations, which may result in incomplete results for large repositories
2. No caching mechanism for API responses
3. No support for GraphQL API (only REST API v4)
4. Limited test coverage for the new functionality

```

--------------------------------------------------------------------------------
/src/integrations.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * GitLab Integrations Manager
 * 
 * This module provides functions for managing GitLab project integrations/webhooks
 * through the GitLab API.
 */

import axios, { AxiosInstance } from "axios";

/**
 * Class to manage GitLab project integrations and webhooks
 */
export class IntegrationsManager {
  private axiosInstance: AxiosInstance;

  constructor(axiosInstance: AxiosInstance) {
    this.axiosInstance = axiosInstance;
  }

  /**
   * List all active integrations for a project
   * 
   * @param projectId ID or URL-encoded path of the project
   * @returns List of project integrations
   */
  async listIntegrations(projectId: string | number) {
    try {
      const response = await this.axiosInstance.get(`/projects/${encodeURIComponent(String(projectId))}/integrations`);
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to list integrations');
    }
  }

  /**
   * Get details of a specific integration
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param integrationSlug Slug of the integration (e.g., 'slack', 'jira', 'gitlab-slack-application')
   * @returns Integration details
   */
  async getIntegration(projectId: string | number, integrationSlug: string) {
    try {
      const response = await this.axiosInstance.get(`/projects/${encodeURIComponent(String(projectId))}/integrations/${integrationSlug}`);
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to get integration: ${integrationSlug}`);
    }
  }

  /**
   * Update GitLab Slack integration
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param options Integration settings to update
   * @returns Updated integration settings
   */
  async updateSlackIntegration(projectId: string | number, options: any) {
    try {
      const response = await this.axiosInstance.put(
        `/projects/${encodeURIComponent(String(projectId))}/integrations/gitlab-slack-application`,
        options
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to update Slack integration');
    }
  }

  /**
   * Disable GitLab Slack integration
   * 
   * @param projectId ID or URL-encoded path of the project
   * @returns Response data
   */
  async disableSlackIntegration(projectId: string | number) {
    try {
      const response = await this.axiosInstance.delete(
        `/projects/${encodeURIComponent(String(projectId))}/integrations/gitlab-slack-application`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to disable Slack integration');
    }
  }

  /**
   * List project webhooks
   * 
   * @param projectId ID or URL-encoded path of the project
   * @returns List of project webhooks
   */
  async listWebhooks(projectId: string | number) {
    try {
      const response = await this.axiosInstance.get(`/projects/${encodeURIComponent(String(projectId))}/hooks`);
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to list webhooks');
    }
  }

  /**
   * Get details of a specific webhook
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param webhookId ID of the webhook
   * @returns Webhook details
   */
  async getWebhook(projectId: string | number, webhookId: number) {
    try {
      const response = await this.axiosInstance.get(
        `/projects/${encodeURIComponent(String(projectId))}/hooks/${webhookId}`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to get webhook: ${webhookId}`);
    }
  }

  /**
   * Add a webhook to a project
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param options Webhook options
   * @returns Created webhook details
   */
  async addWebhook(projectId: string | number, options: {
    url: string;
    name?: string;
    description?: string;
    token?: string;
    push_events?: boolean;
    push_events_branch_filter?: string;
    issues_events?: boolean;
    confidential_issues_events?: boolean;
    merge_requests_events?: boolean;
    tag_push_events?: boolean;
    note_events?: boolean;
    confidential_note_events?: boolean;
    job_events?: boolean;
    pipeline_events?: boolean;
    wiki_page_events?: boolean;
    deployment_events?: boolean;
    releases_events?: boolean;
    feature_flag_events?: boolean;
    enable_ssl_verification?: boolean;
    custom_webhook_template?: string;
    custom_headers?: Array<{key: string; value?: string}>;
  }) {
    try {
      const response = await this.axiosInstance.post(
        `/projects/${encodeURIComponent(String(projectId))}/hooks`,
        options
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to add webhook');
    }
  }

  /**
   * Update a project webhook
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param webhookId ID of the webhook
   * @param options Webhook options to update
   * @returns Updated webhook details
   */
  async updateWebhook(projectId: string | number, webhookId: number, options: {
    url: string;
    name?: string;
    description?: string;
    token?: string;
    push_events?: boolean;
    push_events_branch_filter?: string;
    issues_events?: boolean;
    confidential_issues_events?: boolean;
    merge_requests_events?: boolean;
    tag_push_events?: boolean;
    note_events?: boolean;
    confidential_note_events?: boolean;
    job_events?: boolean;
    pipeline_events?: boolean;
    wiki_page_events?: boolean;
    deployment_events?: boolean;
    releases_events?: boolean;
    feature_flag_events?: boolean;
    enable_ssl_verification?: boolean;
    custom_webhook_template?: string;
    custom_headers?: Array<{key: string; value?: string}>;
  }) {
    try {
      const response = await this.axiosInstance.put(
        `/projects/${encodeURIComponent(String(projectId))}/hooks/${webhookId}`,
        options
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to update webhook: ${webhookId}`);
    }
  }

  /**
   * Delete a project webhook
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param webhookId ID of the webhook
   * @returns Response data
   */
  async deleteWebhook(projectId: string | number, webhookId: number) {
    try {
      const response = await this.axiosInstance.delete(
        `/projects/${encodeURIComponent(String(projectId))}/hooks/${webhookId}`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to delete webhook: ${webhookId}`);
    }
  }

  /**
   * Test a project webhook
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param webhookId ID of the webhook
   * @param triggerType Type of trigger to test
   * @returns Response data
   */
  async testWebhook(projectId: string | number, webhookId: number, triggerType: string) {
    try {
      const response = await this.axiosInstance.post(
        `/projects/${encodeURIComponent(String(projectId))}/hooks/${webhookId}/test/${triggerType}`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to test webhook: ${webhookId}`);
    }
  }

  /**
   * Handle API errors
   * 
   * @param error Error object
   * @param defaultMessage Default error message
   * @returns Error object with appropriate message
   */
  private handleError(error: any, defaultMessage: string) {
    if (axios.isAxiosError(error)) {
      return new Error(`${defaultMessage}: ${error.response?.data?.message || error.message}`);
    }
    return new Error(`${defaultMessage}: ${error.message}`);
  }
}

```

--------------------------------------------------------------------------------
/src/ci-cd.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * GitLab CI/CD Manager
 * 
 * This module provides functions for managing GitLab CI/CD pipelines, variables,
 * triggers, and runners through the GitLab API.
 */

import axios, { AxiosInstance } from "axios";

/**
 * Class to manage GitLab CI/CD features
 */
export class CiCdManager {
  private axiosInstance: AxiosInstance;

  constructor(axiosInstance: AxiosInstance) {
    this.axiosInstance = axiosInstance;
  }

  /**
   * List pipeline trigger tokens for a project
   * 
   * @param projectId ID or URL-encoded path of the project
   * @returns List of pipeline trigger tokens
   */
  async listTriggerTokens(projectId: string | number) {
    try {
      const response = await this.axiosInstance.get(`/projects/${encodeURIComponent(String(projectId))}/triggers`);
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to list trigger tokens');
    }
  }

  /**
   * Get details of a specific trigger token
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param triggerId ID of the trigger token
   * @returns Trigger token details
   */
  async getTriggerToken(projectId: string | number, triggerId: number) {
    try {
      const response = await this.axiosInstance.get(
        `/projects/${encodeURIComponent(String(projectId))}/triggers/${triggerId}`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to get trigger token: ${triggerId}`);
    }
  }

  /**
   * Create a new trigger token for a project
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param description Description of the trigger token
   * @returns Created trigger token details
   */
  async createTriggerToken(projectId: string | number, description: string) {
    try {
      const response = await this.axiosInstance.post(
        `/projects/${encodeURIComponent(String(projectId))}/triggers`,
        { description }
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to create trigger token');
    }
  }

  /**
   * Update a trigger token
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param triggerId ID of the trigger token
   * @param description New description for the trigger token
   * @returns Updated trigger token details
   */
  async updateTriggerToken(projectId: string | number, triggerId: number, description: string) {
    try {
      const response = await this.axiosInstance.put(
        `/projects/${encodeURIComponent(String(projectId))}/triggers/${triggerId}`,
        { description }
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to update trigger token: ${triggerId}`);
    }
  }

  /**
   * Delete a trigger token
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param triggerId ID of the trigger token
   * @returns Response data
   */
  async deleteTriggerToken(projectId: string | number, triggerId: number) {
    try {
      const response = await this.axiosInstance.delete(
        `/projects/${encodeURIComponent(String(projectId))}/triggers/${triggerId}`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to delete trigger token: ${triggerId}`);
    }
  }

  /**
   * Trigger a pipeline with a token
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param ref Branch or tag name to run the pipeline on
   * @param token Trigger token or CI/CD job token
   * @param variables Variables to pass to the pipeline
   * @returns Triggered pipeline details
   */
  async triggerPipeline(
    projectId: string | number, 
    ref: string, 
    token: string, 
    variables?: Record<string, string>
  ) {
    try {
      const params = { token, ref };
      const response = await this.axiosInstance.post(
        `/projects/${encodeURIComponent(String(projectId))}/trigger/pipeline`,
        { variables },
        { params }
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to trigger pipeline');
    }
  }

  /**
   * List project CI/CD variables
   * 
   * @param projectId ID or URL-encoded path of the project
   * @returns List of CI/CD variables
   */
  async listCiCdVariables(projectId: string | number) {
    try {
      const response = await this.axiosInstance.get(`/projects/${encodeURIComponent(String(projectId))}/variables`);
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to list CI/CD variables');
    }
  }

  /**
   * Get a specific CI/CD variable
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param key Key of the variable
   * @returns Variable details
   */
  async getCiCdVariable(projectId: string | number, key: string) {
    try {
      const response = await this.axiosInstance.get(
        `/projects/${encodeURIComponent(String(projectId))}/variables/${encodeURIComponent(key)}`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to get CI/CD variable: ${key}`);
    }
  }

  /**
   * Create a new CI/CD variable
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param options Variable options
   * @returns Created variable details
   */
  async createCiCdVariable(projectId: string | number, options: {
    key: string;
    value: string;
    protected?: boolean;
    masked?: boolean;
    environment_scope?: string;
    variable_type?: 'env_var' | 'file';
  }) {
    try {
      const response = await this.axiosInstance.post(
        `/projects/${encodeURIComponent(String(projectId))}/variables`,
        options
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to create CI/CD variable: ${options.key}`);
    }
  }

  /**
   * Update a CI/CD variable
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param key Key of the variable to update
   * @param options Variable options to update
   * @returns Updated variable details
   */
  async updateCiCdVariable(projectId: string | number, key: string, options: {
    value: string;
    protected?: boolean;
    masked?: boolean;
    environment_scope?: string;
    variable_type?: 'env_var' | 'file';
  }) {
    try {
      const response = await this.axiosInstance.put(
        `/projects/${encodeURIComponent(String(projectId))}/variables/${encodeURIComponent(key)}`,
        options
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to update CI/CD variable: ${key}`);
    }
  }

  /**
   * Delete a CI/CD variable
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param key Key of the variable to delete
   * @returns Response data
   */
  async deleteCiCdVariable(projectId: string | number, key: string) {
    try {
      const response = await this.axiosInstance.delete(
        `/projects/${encodeURIComponent(String(projectId))}/variables/${encodeURIComponent(key)}`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to delete CI/CD variable: ${key}`);
    }
  }

  /**
   * List project runners
   * 
   * @param projectId ID or URL-encoded path of the project
   * @returns List of project runners
   */
  async listRunners(projectId: string | number) {
    try {
      const response = await this.axiosInstance.get(`/projects/${encodeURIComponent(String(projectId))}/runners`);
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to list runners');
    }
  }

  /**
   * Enable a runner for a project
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param runnerId ID of the runner to enable
   * @returns Response data
   */
  async enableRunner(projectId: string | number, runnerId: number) {
    try {
      const response = await this.axiosInstance.post(
        `/projects/${encodeURIComponent(String(projectId))}/runners`,
        { runner_id: runnerId }
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to enable runner: ${runnerId}`);
    }
  }

  /**
   * Disable a runner for a project
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param runnerId ID of the runner to disable
   * @returns Response data
   */
  async disableRunner(projectId: string | number, runnerId: number) {
    try {
      const response = await this.axiosInstance.delete(
        `/projects/${encodeURIComponent(String(projectId))}/runners/${runnerId}`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to disable runner: ${runnerId}`);
    }
  }

  /**
   * List pipeline schedules
   * 
   * @param projectId ID or URL-encoded path of the project
   * @returns List of pipeline schedules
   */
  async listPipelineSchedules(projectId: string | number) {
    try {
      const response = await this.axiosInstance.get(`/projects/${encodeURIComponent(String(projectId))}/pipeline_schedules`);
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to list pipeline schedules');
    }
  }

  /**
   * Get a specific pipeline schedule
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param scheduleId ID of the pipeline schedule
   * @returns Pipeline schedule details
   */
  async getPipelineSchedule(projectId: string | number, scheduleId: number) {
    try {
      const response = await this.axiosInstance.get(
        `/projects/${encodeURIComponent(String(projectId))}/pipeline_schedules/${scheduleId}`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to get pipeline schedule: ${scheduleId}`);
    }
  }

  /**
   * Create a new pipeline schedule
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param options Pipeline schedule options
   * @returns Created pipeline schedule details
   */
  async createPipelineSchedule(projectId: string | number, options: {
    description: string;
    ref: string;
    cron: string;
    cron_timezone?: string;
    active?: boolean;
  }) {
    try {
      const response = await this.axiosInstance.post(
        `/projects/${encodeURIComponent(String(projectId))}/pipeline_schedules`,
        options
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to create pipeline schedule');
    }
  }

  /**
   * Update a pipeline schedule
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param scheduleId ID of the pipeline schedule
   * @param options Pipeline schedule options to update
   * @returns Updated pipeline schedule details
   */
  async updatePipelineSchedule(projectId: string | number, scheduleId: number, options: {
    description?: string;
    ref?: string;
    cron?: string;
    cron_timezone?: string;
    active?: boolean;
  }) {
    try {
      const response = await this.axiosInstance.put(
        `/projects/${encodeURIComponent(String(projectId))}/pipeline_schedules/${scheduleId}`,
        options
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to update pipeline schedule: ${scheduleId}`);
    }
  }

  /**
   * Delete a pipeline schedule
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param scheduleId ID of the pipeline schedule
   * @returns Response data
   */
  async deletePipelineSchedule(projectId: string | number, scheduleId: number) {
    try {
      const response = await this.axiosInstance.delete(
        `/projects/${encodeURIComponent(String(projectId))}/pipeline_schedules/${scheduleId}`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to delete pipeline schedule: ${scheduleId}`);
    }
  }

  /**
   * Handle API errors
   * 
   * @param error Error object
   * @param defaultMessage Default error message
   * @returns Error object with appropriate message
   */
  private handleError(error: any, defaultMessage: string) {
    if (axios.isAxiosError(error)) {
      return new Error(`${defaultMessage}: ${error.response?.data?.message || error.message}`);
    }
    return new Error(`${defaultMessage}: ${error.message}`);
  }
}

```

--------------------------------------------------------------------------------
/src/users-groups.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * GitLab Users and Groups Manager
 * 
 * This module provides functions for managing GitLab users, groups, and their memberships
 * through the GitLab API.
 */

import axios, { AxiosInstance } from "axios";

/**
 * Class to manage GitLab users and groups
 */
export class UsersGroupsManager {
  private axiosInstance: AxiosInstance;

  constructor(axiosInstance: AxiosInstance) {
    this.axiosInstance = axiosInstance;
  }

  /**
   * List users
   * 
   * @param options Query options
   * @returns List of users
   */
  async listUsers(options?: {
    username?: string;
    search?: string;
    active?: boolean;
    blocked?: boolean;
    external?: boolean;
    exclude_external?: boolean;
    exclude_internal?: boolean;
    order_by?: string;
    sort?: string;
    two_factor?: string;
    without_project_bots?: boolean;
    page?: number;
    per_page?: number;
  }) {
    try {
      const response = await this.axiosInstance.get('/users', { params: options });
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to list users');
    }
  }

  /**
   * Get a specific user
   * 
   * @param userId ID of the user
   * @returns User details
   */
  async getUser(userId: number) {
    try {
      const response = await this.axiosInstance.get(`/users/${userId}`);
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to get user: ${userId}`);
    }
  }

  /**
   * Create a new user
   * 
   * @param options User options
   * @returns Created user details
   */
  async createUser(options: {
    email: string;
    password?: string;
    reset_password?: boolean;
    force_random_password?: boolean;
    name: string;
    username: string;
    admin?: boolean;
    can_create_group?: boolean;
    skip_confirmation?: boolean;
    external?: boolean;
    private_profile?: boolean;
    projects_limit?: number;
    bio?: string;
    location?: string;
    public_email?: string;
    skype?: string;
    linkedin?: string;
    twitter?: string;
    discord?: string;
    website_url?: string;
    organization?: string;
    note?: string;
    pronouns?: string;
  }) {
    try {
      const response = await this.axiosInstance.post('/users', options);
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to create user');
    }
  }

  /**
   * Update a user
   * 
   * @param userId ID of the user
   * @param options User options to update
   * @returns Updated user details
   */
  async updateUser(userId: number, options: {
    email?: string;
    password?: string;
    name?: string;
    username?: string;
    admin?: boolean;
    can_create_group?: boolean;
    skip_reconfirmation?: boolean;
    external?: boolean;
    private_profile?: boolean;
    projects_limit?: number;
    bio?: string;
    location?: string;
    public_email?: string;
    skype?: string;
    linkedin?: string;
    twitter?: string;
    discord?: string;
    website_url?: string;
    organization?: string;
    note?: string;
    pronouns?: string;
  }) {
    try {
      const response = await this.axiosInstance.put(`/users/${userId}`, options);
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to update user: ${userId}`);
    }
  }

  /**
   * Delete a user
   * 
   * @param userId ID of the user
   * @param hardDelete If true, contributions will be deleted instead of moving to Ghost User
   * @returns Response data
   */
  async deleteUser(userId: number, hardDelete?: boolean) {
    try {
      const response = await this.axiosInstance.delete(`/users/${userId}`, {
        params: { hard_delete: hardDelete }
      });
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to delete user: ${userId}`);
    }
  }

  /**
   * Get a list of projects and groups that a user is a member of
   * 
   * @param userId ID of the user
   * @param type Filter memberships by type (Project or Namespace)
   * @returns List of user memberships
   */
  async getUserMemberships(userId: number, type?: 'Project' | 'Namespace') {
    try {
      const response = await this.axiosInstance.get(`/users/${userId}/memberships`, {
        params: { type }
      });
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to get user memberships: ${userId}`);
    }
  }

  /**
   * List groups
   * 
   * @param options Query options
   * @returns List of groups
   */
  async listGroups(options?: {
    skip_groups?: number[];
    all_available?: boolean;
    search?: string;
    order_by?: string;
    sort?: string;
    statistics?: boolean;
    owned?: boolean;
    min_access_level?: number;
    top_level_only?: boolean;
    page?: number;
    per_page?: number;
  }) {
    try {
      const response = await this.axiosInstance.get('/groups', { params: options });
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to list groups');
    }
  }

  /**
   * Get a specific group
   * 
   * @param groupId ID or URL-encoded path of the group
   * @returns Group details
   */
  async getGroup(groupId: string | number) {
    try {
      const response = await this.axiosInstance.get(`/groups/${encodeURIComponent(String(groupId))}`);
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to get group: ${groupId}`);
    }
  }

  /**
   * Create a new group
   * 
   * @param options Group options
   * @returns Created group details
   */
  async createGroup(options: {
    name: string;
    path: string;
    description?: string;
    visibility?: string;
    parent_id?: number;
    lfs_enabled?: boolean;
    request_access_enabled?: boolean;
    project_creation_level?: string;
    auto_devops_enabled?: boolean;
    subgroup_creation_level?: string;
  }) {
    try {
      const response = await this.axiosInstance.post('/groups', options);
      return response.data;
    } catch (error) {
      throw this.handleError(error, 'Failed to create group');
    }
  }

  /**
   * Update a group
   * 
   * @param groupId ID or URL-encoded path of the group
   * @param options Group options to update
   * @returns Updated group details
   */
  async updateGroup(groupId: string | number, options: {
    name?: string;
    path?: string;
    description?: string;
    visibility?: string;
    lfs_enabled?: boolean;
    request_access_enabled?: boolean;
    project_creation_level?: string;
    auto_devops_enabled?: boolean;
    subgroup_creation_level?: string;
  }) {
    try {
      const response = await this.axiosInstance.put(`/groups/${encodeURIComponent(String(groupId))}`, options);
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to update group: ${groupId}`);
    }
  }

  /**
   * Delete a group
   * 
   * @param groupId ID or URL-encoded path of the group
   * @returns Response data
   */
  async deleteGroup(groupId: string | number) {
    try {
      const response = await this.axiosInstance.delete(`/groups/${encodeURIComponent(String(groupId))}`);
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to delete group: ${groupId}`);
    }
  }

  /**
   * List group members
   * 
   * @param groupId ID or URL-encoded path of the group
   * @returns List of group members
   */
  async listGroupMembers(groupId: string | number) {
    try {
      const response = await this.axiosInstance.get(`/groups/${encodeURIComponent(String(groupId))}/members`);
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to list group members: ${groupId}`);
    }
  }

  /**
   * Get a specific group member
   * 
   * @param groupId ID or URL-encoded path of the group
   * @param userId ID of the user
   * @returns Group member details
   */
  async getGroupMember(groupId: string | number, userId: number) {
    try {
      const response = await this.axiosInstance.get(
        `/groups/${encodeURIComponent(String(groupId))}/members/${userId}`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to get group member: ${groupId}/${userId}`);
    }
  }

  /**
   * Add a member to a group
   * 
   * @param groupId ID or URL-encoded path of the group
   * @param userId ID of the user
   * @param accessLevel Access level for the user (10=Guest, 20=Reporter, 30=Developer, 40=Maintainer, 50=Owner)
   * @param expiresAt Expiration date for the membership
   * @returns Added group member details
   */
  async addGroupMember(groupId: string | number, userId: number, accessLevel: number, expiresAt?: string) {
    try {
      const response = await this.axiosInstance.post(
        `/groups/${encodeURIComponent(String(groupId))}/members`,
        {
          user_id: userId,
          access_level: accessLevel,
          expires_at: expiresAt
        }
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to add group member: ${groupId}/${userId}`);
    }
  }

  /**
   * Update a group member
   * 
   * @param groupId ID or URL-encoded path of the group
   * @param userId ID of the user
   * @param accessLevel Access level for the user
   * @param expiresAt Expiration date for the membership
   * @returns Updated group member details
   */
  async updateGroupMember(groupId: string | number, userId: number, accessLevel: number, expiresAt?: string) {
    try {
      const response = await this.axiosInstance.put(
        `/groups/${encodeURIComponent(String(groupId))}/members/${userId}`,
        {
          access_level: accessLevel,
          expires_at: expiresAt
        }
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to update group member: ${groupId}/${userId}`);
    }
  }

  /**
   * Remove a member from a group
   * 
   * @param groupId ID or URL-encoded path of the group
   * @param userId ID of the user
   * @returns Response data
   */
  async removeGroupMember(groupId: string | number, userId: number) {
    try {
      const response = await this.axiosInstance.delete(
        `/groups/${encodeURIComponent(String(groupId))}/members/${userId}`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to remove group member: ${groupId}/${userId}`);
    }
  }

  /**
   * List project members
   * 
   * @param projectId ID or URL-encoded path of the project
   * @returns List of project members
   */
  async listProjectMembers(projectId: string | number) {
    try {
      const response = await this.axiosInstance.get(
        `/projects/${encodeURIComponent(String(projectId))}/members`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to list project members: ${projectId}`);
    }
  }

  /**
   * Get a specific project member
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param userId ID of the user
   * @returns Project member details
   */
  async getProjectMember(projectId: string | number, userId: number) {
    try {
      const response = await this.axiosInstance.get(
        `/projects/${encodeURIComponent(String(projectId))}/members/${userId}`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to get project member: ${projectId}/${userId}`);
    }
  }

  /**
   * Add a member to a project
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param userId ID of the user
   * @param accessLevel Access level for the user (10=Guest, 20=Reporter, 30=Developer, 40=Maintainer, 50=Owner)
   * @param expiresAt Expiration date for the membership
   * @returns Added project member details
   */
  async addProjectMember(projectId: string | number, userId: number, accessLevel: number, expiresAt?: string) {
    try {
      const response = await this.axiosInstance.post(
        `/projects/${encodeURIComponent(String(projectId))}/members`,
        {
          user_id: userId,
          access_level: accessLevel,
          expires_at: expiresAt
        }
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to add project member: ${projectId}/${userId}`);
    }
  }

  /**
   * Update a project member
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param userId ID of the user
   * @param accessLevel Access level for the user
   * @param expiresAt Expiration date for the membership
   * @returns Updated project member details
   */
  async updateProjectMember(projectId: string | number, userId: number, accessLevel: number, expiresAt?: string) {
    try {
      const response = await this.axiosInstance.put(
        `/projects/${encodeURIComponent(String(projectId))}/members/${userId}`,
        {
          access_level: accessLevel,
          expires_at: expiresAt
        }
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to update project member: ${projectId}/${userId}`);
    }
  }

  /**
   * Remove a member from a project
   * 
   * @param projectId ID or URL-encoded path of the project
   * @param userId ID of the user
   * @returns Response data
   */
  async removeProjectMember(projectId: string | number, userId: number) {
    try {
      const response = await this.axiosInstance.delete(
        `/projects/${encodeURIComponent(String(projectId))}/members/${userId}`
      );
      return response.data;
    } catch (error) {
      throw this.handleError(error, `Failed to remove project member: ${projectId}/${userId}`);
    }
  }

  /**
   * Handle API errors
   * 
   * @param error Error object
   * @param defaultMessage Default error message
   * @returns Error object with appropriate message
   */
  private handleError(error: any, defaultMessage: string) {
    if (axios.isAxiosError(error)) {
      return new Error(`${defaultMessage}: ${error.response?.data?.message || error.message}`);
    }
    return new Error(`${defaultMessage}: ${error.message}`);
  }
}

```

--------------------------------------------------------------------------------
/TOOLS.md:
--------------------------------------------------------------------------------

```markdown
# GitLab MCP Server Tools

This document provides details on all available tools in the GitLab MCP server.

Each tool is designed to interact with GitLab APIs, allowing AI assistants to work with repositories, merge requests, issues, CI/CD pipelines, and more.

## Table of Contents

- [Repository Management](#repository-management)
- [Integrations & Webhooks](#integrations--webhooks)
- [CI/CD Management](#cicd-management)
- [User & Group Management](#user--group-management)

## Repository Management

### gitlab_list_projects

List GitLab projects accessible to the user

This tool does not require any parameters.

### gitlab_get_project

Get details of a specific GitLab project

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |

### gitlab_list_branches

List branches of a GitLab project

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `search` | `string` | No | Search branches by name |

### gitlab_list_merge_requests

List merge requests in a GitLab project

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `state` | `string` | No | Return merge requests with specified state (opened, closed, locked, merged) |
| `scope` | `string` | No | Return merge requests for the specified scope (created_by_me, assigned_to_me, all) |

### gitlab_get_merge_request

Get details of a specific merge request

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `merge_request_iid` | `number` | Yes | The internal ID of the merge request |

### gitlab_get_merge_request_changes

Get changes (diff) of a specific merge request

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `merge_request_iid` | `number` | Yes | The internal ID of the merge request |

### gitlab_create_merge_request_note

Add a comment to a merge request

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `merge_request_iid` | `number` | Yes | The internal ID of the merge request |
| `body` | `string` | Yes | The content of the note/comment |

### gitlab_create_merge_request_note_internal

Add a comment to a merge request with option to make it an internal note

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `merge_request_iid` | `number` | Yes | The internal ID of the merge request |
| `body` | `string` | Yes | The content of the note/comment |
| `internal` | `boolean` | No | If true, the note will be marked as an internal note visible only to project members |

### gitlab_update_merge_request

Update a merge request title and description

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `merge_request_iid` | `number` | Yes | The internal ID of the merge request |
| `title` | `string` | No | The title of the merge request |
| `description` | `string` | No | The description of the merge request |

### gitlab_list_issues

List issues in a GitLab project

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `state` | `string` | No | Return issues with specified state (opened, closed) |
| `labels` | `string` | No | Comma-separated list of label names |

### gitlab_get_repository_file

Get content of a file in a repository

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `file_path` | `string` | Yes | Path of the file in the repository |
| `ref` | `string` | No | The name of branch, tag or commit |

### gitlab_compare_branches

Compare branches, tags or commits

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `from` | `string` | Yes | The commit SHA or branch name to compare from |
| `to` | `string` | Yes | The commit SHA or branch name to compare to |

### gitlab_list_project_members

List members of a project

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |

### gitlab_add_project_member

Add a user to a project

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `user_id` | `number` | Yes | The ID of the user |
| `access_level` | `number` | Yes | Access level (10=Guest, 20=Reporter, 30=Developer, 40=Maintainer, 50=Owner) |

## Integrations & Webhooks

### gitlab_list_integrations

List all available project integrations/services

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |

### gitlab_get_integration

Get integration details for a project

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `integration` | `string` | Yes | The name of the integration (e.g., slack) |

### gitlab_update_slack_integration

Update Slack integration settings for a project

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `webhook` | `string` | Yes | The Slack webhook URL |
| `username` | `string` | No | The Slack username |
| `channel` | `string` | No | The Slack channel name |

### gitlab_disable_slack_integration

Disable Slack integration for a project

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |

### gitlab_list_webhooks

List webhooks for a project

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |

### gitlab_get_webhook

Get details of a specific webhook

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `webhook_id` | `number` | Yes | The ID of the webhook |

### gitlab_add_webhook

Add a new webhook to a project

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `url` | `string` | Yes | The webhook URL |
| `token` | `string` | No | Secret token to validate received payloads |
| `push_events` | `boolean` | No | Trigger webhook for push events |
| `issues_events` | `boolean` | No | Trigger webhook for issues events |
| `merge_requests_events` | `boolean` | No | Trigger webhook for merge request events |
| `enable_ssl_verification` | `boolean` | No | Enable SSL verification for the webhook |

### gitlab_update_webhook

Update an existing webhook

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `webhook_id` | `number` | Yes | The ID of the webhook |
| `url` | `string` | Yes | The webhook URL |
| `token` | `string` | No | Secret token to validate received payloads |
| `push_events` | `boolean` | No | Trigger webhook for push events |
| `issues_events` | `boolean` | No | Trigger webhook for issues events |
| `merge_requests_events` | `boolean` | No | Trigger webhook for merge request events |
| `enable_ssl_verification` | `boolean` | No | Enable SSL verification for the webhook |

### gitlab_delete_webhook

Delete a webhook

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `webhook_id` | `number` | Yes | The ID of the webhook |

### gitlab_test_webhook

Test a webhook

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `webhook_id` | `number` | Yes | The ID of the webhook |

## CI/CD Management

### gitlab_list_trigger_tokens

List pipeline trigger tokens

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |

### gitlab_get_trigger_token

Get details of a pipeline trigger token

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `trigger_id` | `number` | Yes | The ID of the trigger |

### gitlab_create_trigger_token

Create a new pipeline trigger token

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `description` | `string` | Yes | The trigger description |

### gitlab_update_trigger_token

Update a pipeline trigger token

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `trigger_id` | `number` | Yes | The ID of the trigger |
| `description` | `string` | Yes | The new trigger description |

### gitlab_delete_trigger_token

Delete a pipeline trigger token

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `trigger_id` | `number` | Yes | The ID of the trigger |

### gitlab_trigger_pipeline

Trigger a pipeline run

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `ref` | `string` | Yes | The branch or tag name to run the pipeline for |
| `token` | `string` | Yes | The trigger token |
| `variables` | `object` | No | Variables to pass to the pipeline |

### gitlab_list_cicd_variables

List CI/CD variables for a project

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |

### gitlab_get_cicd_variable

Get a specific CI/CD variable

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `key` | `string` | Yes | The key of the variable |

### gitlab_create_cicd_variable

Create a new CI/CD variable

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `key` | `string` | Yes | The key of the variable |
| `value` | `string` | Yes | The value of the variable |
| `protected` | `boolean` | No | Whether the variable is protected |
| `masked` | `boolean` | No | Whether the variable is masked |

### gitlab_update_cicd_variable

Update a CI/CD variable

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `key` | `string` | Yes | The key of the variable |
| `value` | `string` | Yes | The value of the variable |
| `protected` | `boolean` | No | Whether the variable is protected |
| `masked` | `boolean` | No | Whether the variable is masked |

### gitlab_delete_cicd_variable

Delete a CI/CD variable

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `key` | `string` | Yes | The key of the variable |

## User & Group Management

### gitlab_list_users

List GitLab users

This tool does not require any parameters.

### gitlab_get_user

Get details of a specific user

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `user_id` | `number` | Yes | The ID of the user |

### gitlab_list_groups

List GitLab groups

This tool does not require any parameters.

### gitlab_get_group

Get details of a specific group

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `group_id` | `string` | Yes | The ID or URL-encoded path of the group |

### gitlab_list_group_members

List members of a group

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `group_id` | `string` | Yes | The ID or URL-encoded path of the group |

### gitlab_add_group_member

Add a user to a group

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `group_id` | `string` | Yes | The ID or URL-encoded path of the group |
| `user_id` | `number` | Yes | The ID of the user |
| `access_level` | `number` | Yes | Access level (10=Guest, 20=Reporter, 30=Developer, 40=Maintainer, 50=Owner) |

### gitlab_list_project_members

List members of a project

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |

### gitlab_add_project_member

Add a user to a project

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `project_id` | `string` | Yes | The ID or URL-encoded path of the project |
| `user_id` | `number` | Yes | The ID of the user |
| `access_level` | `number` | Yes | Access level (10=Guest, 20=Reporter, 30=Developer, 40=Maintainer, 50=Owner) |

---

Generated automatically from `src/utils/tools-data.ts`

```

--------------------------------------------------------------------------------
/src/utils/tools-data.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tool definitions for GitLab MCP Server
 */

export const toolDefinitions = [
  // Repository tools
  {
    name: 'gitlab_list_projects',
    description: 'List GitLab projects accessible to the user',
    inputSchema: {
      type: 'object',
      properties: {
        search: {
          type: 'string',
          description: 'Search projects by name'
        },
        owned: {
          type: 'boolean',
          description: 'Limit to projects explicitly owned by the current user'
        },
        membership: {
          type: 'boolean',
          description: 'Limit to projects the current user is a member of'
        },
        per_page: {
          type: 'number',
          description: 'Number of projects to return per page (max 100)'
        }
      }
    }
  },
  {
    name: 'gitlab_get_project',
    description: 'Get details of a specific GitLab project',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        }
      },
      required: ['project_id']
    }
  },
  {
    name: 'gitlab_list_branches',
    description: 'List branches of a GitLab project',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        search: {
          type: 'string',
          description: 'Search branches by name'
        }
      },
      required: ['project_id']
    }
  },
  {
    name: 'gitlab_list_merge_requests',
    description: 'List merge requests in a GitLab project',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        state: {
          type: 'string',
          description: 'Return merge requests with specified state (opened, closed, locked, merged)',
          enum: ['opened', 'closed', 'locked', 'merged']
        },
        scope: {
          type: 'string',
          description: 'Return merge requests for the specified scope (created_by_me, assigned_to_me, all)',
          enum: ['created_by_me', 'assigned_to_me', 'all']
        }
      },
      required: ['project_id']
    }
  },
  {
    name: 'gitlab_get_merge_request',
    description: 'Get details of a specific merge request',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        merge_request_iid: {
          type: 'number',
          description: 'The internal ID of the merge request'
        }
      },
      required: ['project_id', 'merge_request_iid']
    }
  },
  {
    name: 'gitlab_get_merge_request_changes',
    description: 'Get changes (diff) of a specific merge request',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        merge_request_iid: {
          type: 'number',
          description: 'The internal ID of the merge request'
        }
      },
      required: ['project_id', 'merge_request_iid']
    }
  },
  {
    name: 'gitlab_create_merge_request_note',
    description: 'Add a comment to a merge request',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        merge_request_iid: {
          type: 'number',
          description: 'The internal ID of the merge request'
        },
        body: {
          type: 'string',
          description: 'The content of the note/comment'
        }
      },
      required: ['project_id', 'merge_request_iid', 'body']
    }
  },
  {
    name: 'gitlab_create_merge_request_note_internal',
    description: 'Add a comment to a merge request with option to make it an internal note',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        merge_request_iid: {
          type: 'number',
          description: 'The internal ID of the merge request'
        },
        body: {
          type: 'string',
          description: 'The content of the note/comment'
        },
        internal: {
          type: 'boolean',
          description: 'If true, the note will be marked as an internal note visible only to project members'
        }
      },
      required: ['project_id', 'merge_request_iid', 'body']
    }
  },
  {
    name: 'gitlab_update_merge_request',
    description: 'Update a merge request title and description',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        merge_request_iid: {
          type: 'number',
          description: 'The internal ID of the merge request'
        },
        title: {
          type: 'string',
          description: 'The title of the merge request'
        },
        description: {
          type: 'string',
          description: 'The description of the merge request'
        }
      },
      required: ['project_id', 'merge_request_iid']
    }
  },
  {
    name: 'gitlab_list_issues',
    description: 'List issues in a GitLab project',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        state: {
          type: 'string',
          description: 'Return issues with specified state (opened, closed)',
          enum: ['opened', 'closed']
        },
        labels: {
          type: 'string',
          description: 'Comma-separated list of label names'
        }
      },
      required: ['project_id']
    }
  },
  {
    name: 'gitlab_get_repository_file',
    description: 'Get content of a file in a repository',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        file_path: {
          type: 'string',
          description: 'Path of the file in the repository'
        },
        ref: {
          type: 'string',
          description: 'The name of branch, tag or commit'
        }
      },
      required: ['project_id', 'file_path']
    }
  },
  {
    name: 'gitlab_compare_branches',
    description: 'Compare branches, tags or commits',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        from: {
          type: 'string',
          description: 'The commit SHA or branch name to compare from'
        },
        to: {
          type: 'string',
          description: 'The commit SHA or branch name to compare to'
        }
      },
      required: ['project_id', 'from', 'to']
    }
  },
  
  // Integration tools
  {
    name: 'gitlab_list_integrations',
    description: 'List all available project integrations/services',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        }
      },
      required: ['project_id']
    }
  },
  {
    name: 'gitlab_get_integration',
    description: 'Get integration details for a project',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        integration: {
          type: 'string',
          description: 'The name of the integration (e.g., slack)'
        }
      },
      required: ['project_id', 'integration']
    }
  },
  {
    name: 'gitlab_update_slack_integration',
    description: 'Update Slack integration settings for a project',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        webhook: {
          type: 'string',
          description: 'The Slack webhook URL'
        },
        username: {
          type: 'string',
          description: 'The Slack username'
        },
        channel: {
          type: 'string',
          description: 'The Slack channel name'
        }
      },
      required: ['project_id', 'webhook']
    }
  },
  {
    name: 'gitlab_disable_slack_integration',
    description: 'Disable Slack integration for a project',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        }
      },
      required: ['project_id']
    }
  },
  {
    name: 'gitlab_list_webhooks',
    description: 'List webhooks for a project',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        }
      },
      required: ['project_id']
    }
  },
  {
    name: 'gitlab_get_webhook',
    description: 'Get details of a specific webhook',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        webhook_id: {
          type: 'number',
          description: 'The ID of the webhook'
        }
      },
      required: ['project_id', 'webhook_id']
    }
  },
  {
    name: 'gitlab_add_webhook',
    description: 'Add a new webhook to a project',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        url: {
          type: 'string',
          description: 'The webhook URL'
        },
        token: {
          type: 'string',
          description: 'Secret token to validate received payloads'
        },
        push_events: {
          type: 'boolean',
          description: 'Trigger webhook for push events'
        },
        issues_events: {
          type: 'boolean',
          description: 'Trigger webhook for issues events'
        },
        merge_requests_events: {
          type: 'boolean',
          description: 'Trigger webhook for merge request events'
        },
        enable_ssl_verification: {
          type: 'boolean',
          description: 'Enable SSL verification for the webhook'
        }
      },
      required: ['project_id', 'url']
    }
  },
  {
    name: 'gitlab_update_webhook',
    description: 'Update an existing webhook',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        webhook_id: {
          type: 'number',
          description: 'The ID of the webhook'
        },
        url: {
          type: 'string',
          description: 'The webhook URL'
        },
        token: {
          type: 'string',
          description: 'Secret token to validate received payloads'
        },
        push_events: {
          type: 'boolean',
          description: 'Trigger webhook for push events'
        },
        issues_events: {
          type: 'boolean',
          description: 'Trigger webhook for issues events'
        },
        merge_requests_events: {
          type: 'boolean',
          description: 'Trigger webhook for merge request events'
        },
        enable_ssl_verification: {
          type: 'boolean',
          description: 'Enable SSL verification for the webhook'
        }
      },
      required: ['project_id', 'webhook_id', 'url']
    }
  },
  {
    name: 'gitlab_delete_webhook',
    description: 'Delete a webhook',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        webhook_id: {
          type: 'number',
          description: 'The ID of the webhook'
        }
      },
      required: ['project_id', 'webhook_id']
    }
  },
  {
    name: 'gitlab_test_webhook',
    description: 'Test a webhook',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        webhook_id: {
          type: 'number',
          description: 'The ID of the webhook'
        }
      },
      required: ['project_id', 'webhook_id']
    }
  },

  // CI/CD tools
  {
    name: 'gitlab_list_trigger_tokens',
    description: 'List pipeline trigger tokens',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        }
      },
      required: ['project_id']
    }
  },
  {
    name: 'gitlab_get_trigger_token',
    description: 'Get details of a pipeline trigger token',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        trigger_id: {
          type: 'number',
          description: 'The ID of the trigger'
        }
      },
      required: ['project_id', 'trigger_id']
    }
  },
  {
    name: 'gitlab_create_trigger_token',
    description: 'Create a new pipeline trigger token',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        description: {
          type: 'string',
          description: 'The trigger description'
        }
      },
      required: ['project_id', 'description']
    }
  },
  {
    name: 'gitlab_update_trigger_token',
    description: 'Update a pipeline trigger token',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        trigger_id: {
          type: 'number',
          description: 'The ID of the trigger'
        },
        description: {
          type: 'string',
          description: 'The new trigger description'
        }
      },
      required: ['project_id', 'trigger_id', 'description']
    }
  },
  {
    name: 'gitlab_delete_trigger_token',
    description: 'Delete a pipeline trigger token',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        trigger_id: {
          type: 'number',
          description: 'The ID of the trigger'
        }
      },
      required: ['project_id', 'trigger_id']
    }
  },
  {
    name: 'gitlab_trigger_pipeline',
    description: 'Trigger a pipeline run',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        ref: {
          type: 'string',
          description: 'The branch or tag name to run the pipeline for'
        },
        token: {
          type: 'string',
          description: 'The trigger token'
        },
        variables: {
          type: 'object',
          description: 'Variables to pass to the pipeline',
          additionalProperties: { type: 'string' }
        }
      },
      required: ['project_id', 'ref', 'token']
    }
  },
  {
    name: 'gitlab_list_cicd_variables',
    description: 'List CI/CD variables for a project',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        }
      },
      required: ['project_id']
    }
  },
  {
    name: 'gitlab_get_cicd_variable',
    description: 'Get a specific CI/CD variable',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        key: {
          type: 'string',
          description: 'The key of the variable'
        }
      },
      required: ['project_id', 'key']
    }
  },
  {
    name: 'gitlab_create_cicd_variable',
    description: 'Create a new CI/CD variable',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        key: {
          type: 'string',
          description: 'The key of the variable'
        },
        value: {
          type: 'string',
          description: 'The value of the variable'
        },
        protected: {
          type: 'boolean',
          description: 'Whether the variable is protected'
        },
        masked: {
          type: 'boolean',
          description: 'Whether the variable is masked'
        }
      },
      required: ['project_id', 'key', 'value']
    }
  },
  {
    name: 'gitlab_update_cicd_variable',
    description: 'Update a CI/CD variable',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        key: {
          type: 'string',
          description: 'The key of the variable'
        },
        value: {
          type: 'string',
          description: 'The value of the variable'
        },
        protected: {
          type: 'boolean',
          description: 'Whether the variable is protected'
        },
        masked: {
          type: 'boolean',
          description: 'Whether the variable is masked'
        }
      },
      required: ['project_id', 'key', 'value']
    }
  },
  {
    name: 'gitlab_delete_cicd_variable',
    description: 'Delete a CI/CD variable',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        key: {
          type: 'string',
          description: 'The key of the variable'
        }
      },
      required: ['project_id', 'key']
    }
  },

  // Users and Groups tools
  {
    name: 'gitlab_list_users',
    description: 'List GitLab users',
    inputSchema: {
      type: 'object',
      properties: {
        search: {
          type: 'string',
          description: 'Search users by username, name or email'
        },
        active: {
          type: 'boolean',
          description: 'Filter users by active status'
        }
      }
    }
  },
  {
    name: 'gitlab_get_user',
    description: 'Get details of a specific user',
    inputSchema: {
      type: 'object',
      properties: {
        user_id: {
          type: 'number',
          description: 'The ID of the user'
        }
      },
      required: ['user_id']
    }
  },
  {
    name: 'gitlab_list_groups',
    description: 'List GitLab groups',
    inputSchema: {
      type: 'object',
      properties: {
        search: {
          type: 'string',
          description: 'Search groups by name'
        },
        owned: {
          type: 'boolean',
          description: 'Limit to groups explicitly owned by the current user'
        }
      }
    }
  },
  {
    name: 'gitlab_get_group',
    description: 'Get details of a specific group',
    inputSchema: {
      type: 'object',
      properties: {
        group_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the group'
        }
      },
      required: ['group_id']
    }
  },
  {
    name: 'gitlab_list_group_members',
    description: 'List members of a group',
    inputSchema: {
      type: 'object',
      properties: {
        group_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the group'
        }
      },
      required: ['group_id']
    }
  },
  {
    name: 'gitlab_add_group_member',
    description: 'Add a user to a group',
    inputSchema: {
      type: 'object',
      properties: {
        group_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the group'
        },
        user_id: {
          type: 'number',
          description: 'The ID of the user'
        },
        access_level: {
          type: 'number',
          description: 'Access level (10=Guest, 20=Reporter, 30=Developer, 40=Maintainer, 50=Owner)',
          enum: [10, 20, 30, 40, 50]
        }
      },
      required: ['group_id', 'user_id', 'access_level']
    }
  },
  {
    name: 'gitlab_list_project_members',
    description: 'List members of a project',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        }
      },
      required: ['project_id']
    }
  },
  {
    name: 'gitlab_add_project_member',
    description: 'Add a user to a project',
    inputSchema: {
      type: 'object',
      properties: {
        project_id: {
          type: 'string',
          description: 'The ID or URL-encoded path of the project'
        },
        user_id: {
          type: 'number',
          description: 'The ID of the user'
        },
        access_level: {
          type: 'number',
          description: 'Access level (10=Guest, 20=Reporter, 30=Developer, 40=Maintainer, 50=Owner)',
          enum: [10, 20, 30, 40, 50]
        }
      },
      required: ['project_id', 'user_id', 'access_level']
    }
  }
];

// Export lists of tools by category for easier selection
export const repositoryTools = toolDefinitions.slice(0, 12);
export const integrationTools = toolDefinitions.slice(10, 20);
export const cicdTools = toolDefinitions.slice(20, 31);
export const usersGroupsTools = toolDefinitions.slice(31); 
```