using System.ComponentModel; using System.Text.Json; using LazyBear.MCP.Services.ToolRegistry; using ModelContextProtocol.Server; using RestSharp; namespace LazyBear.MCP.Services.GitLab; public sealed class GitLabRepositoryTools( GitLabClientProvider provider, IConfiguration configuration, ToolRegistryService registry) { private readonly string _token = configuration["GitLab:Token"] ?? string.Empty; private readonly string _baseUrl = configuration["GitLab:Url"] ?? string.Empty; private const string ModuleName = "GitLab"; private bool TryCheckEnabled(string toolName, out string error) { if (!registry.IsToolEnabled(ModuleName, toolName)) { error = $"Инструмент '{toolName}' модуля GitLab отключён в TUI."; return false; } error = string.Empty; return true; } private bool TryGetClient(out RestSharp.IRestClient client, out string error) { if (provider.InitializationError is not null) { client = null!; error = $"GitLab клиент не инициализирован. Детали: {provider.InitializationError}"; return false; } var clientInstance = provider.GetClient(); if (clientInstance is null) { client = null!; error = "GitLab клиент не создан."; return false; } client = clientInstance.RestClient; error = string.Empty; return true; } [McpServerTool, Description("Получить список репозиториев текущего пользователя")] public async Task ListProjects(CancellationToken cancellationToken = default) { if (!TryCheckEnabled("ListProjects", out var enabledError)) return enabledError; if (!TryGetClient(out var client, out var error)) return error; try { var request = new RestRequest("/user/projects", RestSharp.Method.Get); request.AddHeader("Accept", "application/json"); request.AddHeader("PRIVATE-TOKEN", _token); request.AddQueryParameter("per_page", "100"); var response = await client.ExecuteAsync(request, cancellationToken); if (!response.IsSuccessful || string.IsNullOrWhiteSpace(response.Content)) { return FormatResponseError("list_projects", response, "/user/projects"); } using var document = JsonDocument.Parse(response.Content); var root = document.RootElement; if (!root.TryGetProperty("projects", out var projectsElement) || projectsElement.GetArrayLength() == 0) { return "Репозитории GitLab не найдены."; } var lines = new List(); foreach (var project in projectsElement.EnumerateArray()) { var name = GetNestedString(project, "name") ?? "unknown"; var path = GetNestedString(project, "path") ?? "-"; var visibility = GetVisibility(GetNestedString(project, "visibility") ?? ""); lines.Add($"{name} [{visibility}] - {path}"); } return $"Репозитории GitLab ({projectsElement.GetArrayLength()} шт.):\n{string.Join('\n', lines)}"; } catch (Exception ex) { return FormatException("list_projects", ex); } } [McpServerTool, Description("Получить конкретный репозиторий по ID")] public async Task GetProject( [Description("ID репозитория")] int projectId, CancellationToken cancellationToken = default) { if (!TryCheckEnabled("GetProject", out var enabledError)) return enabledError; if (projectId <= 0) { return "ID репозитория GitLab некорректно задан."; } if (!TryGetClient(out var client, out var error)) return error; try { var request = new RestRequest($"/projects/{projectId}", RestSharp.Method.Get); request.AddHeader("Accept", "application/json"); request.AddHeader("PRIVATE-TOKEN", _token); var response = await client.ExecuteAsync(request, cancellationToken); if (!response.IsSuccessful || string.IsNullOrWhiteSpace(response.Content)) { return FormatResponseError("get_project", response, $"/projects/{projectId}"); } using var document = JsonDocument.Parse(response.Content); var root = document.RootElement; var name = GetNestedString(root, "name") ?? "-"; var path = GetNestedString(root, "path") ?? "-"; var visibility = GetVisibility(GetNestedString(root, "visibility") ?? ""); var httpUrl = GetNestedString(root, "http_url_to_repo") ?? "-"; var webUrl = GetNestedString(root, "web_url") ?? "-"; var sshUrl = GetNestedString(root, "ssh_url_to_repo") ?? "-"; return $"Репозиторий #{projectId}:\n{name} [{visibility}] - {path}\nURL: {httpUrl}\nWeb: {webUrl}\nSSH: {sshUrl}"; } catch (Exception ex) { return FormatException("get_project", ex, $"/projects/{projectId}"); } } private static string GetVisibility(string visibility) => visibility switch { "public" => "Public", "internal" => "Internal", "private" => "Private", _ => visibility ?? "unknown" }; private static string? GetNestedString(JsonElement element, params string[] path) { var current = element; foreach (var segment in path) { if (!current.TryGetProperty(segment, out current)) { return null; } } return current.ValueKind == JsonValueKind.String ? current.GetString() : current.ToString(); } private string FormatResponseError(string toolName, RestResponse response, string? resource = null) { var resourcePart = string.IsNullOrWhiteSpace(resource) ? string.Empty : $", resource='{resource}'"; var body = string.IsNullOrWhiteSpace(response.Content) ? "-" : response.Content; return $"Ошибка GitLab в tool '{toolName}'{resourcePart}: status={(int)response.StatusCode}, details={body}"; } private string FormatException(string toolName, Exception exception, string? resource = null) { var resourcePart = string.IsNullOrWhiteSpace(resource) ? string.Empty : $", resource='{resource}'"; return $"Ошибка GitLab в tool '{toolName}'{resourcePart}: {exception.GetType().Name}: {exception.Message}"; } }