feat: добавить поддержку GitLab (api, clients, tools) и обновить документацию
This commit is contained in:
175
LazyBear.MCP/Services/GitLab/GitLabRepositoryTools.cs
Normal file
175
LazyBear.MCP/Services/GitLab/GitLabRepositoryTools.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
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<string> 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<string>();
|
||||
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<string> 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}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user