Skip to main content
Glama

mcp-server-gitlab

GitlabApiClient.ts8.48 kB
import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from "axios"; import { GitlabApiResponse } from "./GitlabApiTypes"; import { GitlabConfig } from "../../config/GitlabConfig"; export interface LoggerLike { info: (...args: any[]) => void; warn: (...args: any[]) => void; error: (...args: any[]) => void; debug: (...args: any[]) => void; } export class GitlabApiClient { private client: AxiosInstance; private config: GitlabConfig; private logger = { info(...args: any[]) {}, warn(...args: any[]) {}, error(...args: any[]) {}, debug(...args: any[]) {}, }; constructor(config: GitlabConfig) { this.config = config; this.client = this.createApiClient(); } public setLogger(logger: LoggerLike) { this.logger = logger } private createApiClient(): AxiosInstance { const normalizedBaseUrl = this.normalizeBaseUrl(this.config.baseUrl); const axiosConfig: AxiosRequestConfig = { baseURL: normalizedBaseUrl, timeout: this.config.timeout || 10000, headers: { 'Content-Type': 'application/json', 'PRIVATE-TOKEN': this.config.privateToken }, }; this.logger.info(`创建 GitLab API 客户端,基础 URL: ${normalizedBaseUrl}`); return axios.create(axiosConfig); } private normalizeBaseUrl(url: string): string { let normalizedUrl = String(url).trim(); if (!normalizedUrl) { this.logger.warn('空的 GitLab URL,使用默认值'); return 'https://gitlab.com/api/v4'; } if (!normalizedUrl.startsWith('http://') && !normalizedUrl.startsWith('https://')) { normalizedUrl = `https://${normalizedUrl}`; } normalizedUrl = normalizedUrl.replace(/\/+$/, ''); if (!/\/api\/v4(?:\/|$)/.test(normalizedUrl)) { return `${normalizedUrl}/api/v4`; } else { const apiPathIndex = normalizedUrl.indexOf('/api/v4'); return normalizedUrl.substring(0, apiPathIndex + '/api/v4'.length); } } async apiRequest(endpoint: string, method: string = 'GET', params?: Record<string, any>, data?: Record<string, any>): Promise<GitlabApiResponse> { try { if (endpoint && !endpoint.startsWith('/')) { endpoint = `/${endpoint}`; } this.logger.info(`执行 GitLab API 请求:${method} ${endpoint}`); const response = await this.client.request({ url: endpoint, method, params, data, }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { const axiosError = error as AxiosError; const statusCode = axiosError.response?.status || 0; let errorMessage = `GitLab API 请求失败:${axiosError.message}`; if (statusCode === 401) { errorMessage = 'GitLab API 认证失败:请检查您的访问令牌是否有效'; } else if (statusCode === 403) { errorMessage = 'GitLab API 权限不足:您没有足够的权限执行此操作'; } else if (statusCode === 404) { errorMessage = `GitLab API returned 404: ${endpoint} not found, parameter error or insufficient permissions`; } else if (statusCode === 429) { errorMessage = 'GitLab API 请求过于频繁:已达到速率限制,请稍后重试'; } else if (statusCode >= 500) { errorMessage = 'GitLab API server error: server temporarily unavailable or internal error'; } return { error: true, status: statusCode, message: errorMessage, details: axiosError.response?.data || {}, }; } return { error: true, message: `GitLab API 请求失败:${(error as Error).message}`, }; } } isValidResponse(response: any): boolean { return !(response && response.error === true); } /** * 尝试将用户名或用户 ID 解析为用户 ID。 * 优先使用配置中的 userMapping,然后尝试调用 GitLab API。 * @param usernameOrId 用户名(字符串)或用户 ID(数字)。 * @returns 解析后的用户 ID,如果无法解析则返回 null。 */ async resolveUserId(usernameOrId: string | number): Promise<number | null> { if (typeof usernameOrId === 'number') { return usernameOrId; } if (typeof usernameOrId === 'string') { const mappedId = this.config.userMapping?.[usernameOrId]; if (mappedId) { this.logger.debug(`通过映射找到用户 ID: ${usernameOrId} -> ${mappedId}`); return mappedId; } // 如果映射中没有,尝试通过 API 查询 this.logger.debug(`映射中未找到用户 ${usernameOrId},尝试通过 API 查询...`); const response = await this.apiRequest('/users', 'GET', { username: usernameOrId }); if (this.isValidResponse(response) && Array.isArray(response) && response.length === 1) { this.logger.debug(`通过 API 找到用户 ID: ${usernameOrId} -> ${response[0].id}`); return response[0].id; } else if (this.isValidResponse(response) && Array.isArray(response) && response.length > 1) { this.logger.warn(`通过 API 找到多个用户匹配 ${usernameOrId},无法确定唯一用户。`); } else if (this.isValidResponse(response) && Array.isArray(response) && response.length === 0) { this.logger.warn(`通过 API 未找到用户 ${usernameOrId}。`); } else if (!this.isValidResponse(response)) { // Only log error message if the response indicates an error this.logger.error(`API error when querying user ${usernameOrId}: ${(response as GitlabApiResponse)?.message || 'Unknown error'}`); } } this.logger.warn(`无法解析用户:${usernameOrId}`); return null; } /** * 尝试将项目名或项目 ID 解析为项目 ID。 * 优先使用配置中的 projectMapping,然后尝试调用 GitLab API。 * @param projectNameOrId 项目名(字符串)或项目 ID(数字或字符串形式的数字)。 * @returns 解析后的项目 ID,如果无法解析则返回 null。 */ async resolveProjectId(projectNameOrId: string | number): Promise<string | number | null> { if (typeof projectNameOrId === 'number') { return projectNameOrId; } if (typeof projectNameOrId === 'string') { const mappedId = this.config.projectMapping?.[projectNameOrId]; if (mappedId) { this.logger.debug(`通过映射找到项目 ID: ${projectNameOrId} -> ${mappedId}`); return mappedId; } // 如果映射中没有,尝试通过 API 查询 this.logger.debug(`映射中未找到项目 ${projectNameOrId},尝试通过 API 查询...`); // 使用 simple=true 减少返回数据量,search 会匹配 name 和 path const response = await this.apiRequest('/projects', 'GET', { search: projectNameOrId, simple: true, order_by: 'similarity', sort: 'desc' }); if (this.isValidResponse(response) && Array.isArray(response) && response.length > 0) { // 尝试寻找精确匹配 name 或 path_with_namespace 的项目 const exactMatch = response.find(p => p.name === projectNameOrId || p.path_with_namespace === projectNameOrId); if (exactMatch) { this.logger.debug(`通过 API 精确匹配找到项目 ID: ${projectNameOrId} -> ${exactMatch.id}`); return exactMatch.id; } // 如果没有精确匹配,但只有一个结果,则使用该结果 if (response.length === 1) { this.logger.debug(`通过 API 找到唯一模糊匹配项目 ID: ${projectNameOrId} -> ${response[0].id}`); return response[0].id; } else { this.logger.warn(`通过 API 找到多个项目匹配 ${projectNameOrId},且无精确匹配,无法确定唯一项目。`); // 可以考虑返回最相似的那个,但可能有风险 // return response[0].id; } } else if (this.isValidResponse(response) && Array.isArray(response) && response.length === 0) { this.logger.warn(`通过 API 未找到项目 ${projectNameOrId}。`); } else if (!this.isValidResponse(response)) { // Only log error message if the response indicates an error this.logger.error(`API error when querying project ${projectNameOrId}: ${(response as GitlabApiResponse)?.message || 'Unknown error'}`); } } this.logger.warn(`无法解析项目:${projectNameOrId}`); return null; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ZephyrDeng/mcp-server-gitlab'

If you have feedback or need assistance with the MCP directory API, please join our Discord server