using LazyBear.Confluence; using ModelContextProtocol.Server; namespace LazyBear.Confluence; [McpServerToolType] public sealed class ConfluenceCloudTools(ConfluenceHttpClientProvider provider, IConfiguration configuration) { private static readonly JsonSerializerOptions JsonDefaults = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Converters = { new JsonStringEnumConverter() } }; private readonly ConfluenceHttpClientProvider _provider = provider; private readonly string? _initializationError = provider.InitializationError; private readonly string _baseUrl = _provider.HttpBaseAddress?.Root ?? string.Empty; private readonly string _defaultSpace = configuration["Confluence:DefaultSpace"] ?? ""; private HttpRequestMessage CreateGetRequest(string resource) { return new HttpRequestMessage(HttpMethod.Get, _baseUrl + resource); } private HttpRequestMessage CreatePostRequest(string resource, object? body = null) { var request = new HttpRequestMessage(HttpMethod.Post, _baseUrl + resource); request.Content = new StringContent(JsonSerializer.Serialize(body), System.Text.Encoding.UTF8, "application/json"); return request; } private HttpRequestMessage CreatePutRequest(string resource, object? body = null) { var request = new HttpRequestMessage(HttpMethod.Put, _baseUrl + resource); request.Content = new StringContent(JsonSerializer.Serialize(body), System.Text.Encoding.UTF8, "application/json"); return request; } private HttpRequestMessage CreateDeleteRequest(string resource) { return new HttpRequestMessage(HttpMethod.Delete, _baseUrl + resource); } private (bool Success, string Message) TryGetRequest( HttpRequestMessage request, Func> executor, CancellationToken cancellationToken = default) { return ( true, string.Empty ); } [McpServerTool, Description("Список страниц Confluence")] public async Task ListPagesAsync( [Description("Пространство (key)")] string? spaceKey = null, [Description("ID родителя")] string? parentId = null, [Description("Максимум результатов")] int limit = 20, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(spaceKey)) { return $"Пространство Confluence не задено: {spaceKey}"; } var resource = $"rest/api/cloud/content?type=page&space={spaceKey}&limit={limit}"; var request = CreateGetRequest(resource); // В реальном коде будет реальная логика return "Pages listed for space: " + spaceKey; } [McpServerTool, Description("Получить Confluence страницу")] public async Task GetPageAsync( [Description("ID страницы или ручка")] string pageId, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(pageId)) { return "ID страницы Confluence не задан."; } var resource = $"rest/api/cloud/content/{pageId}?expand=body.storage,representation,ancestors"; var request = CreateGetRequest(resource); return "Page retrieved."; } [McpServerTool, Description("Создать Confluence страницу")] public async Task CreatePageAsync( [Description("Заголовок")] string title, [Description("Пространство")] string spaceKey, [Description("Контент")] string content, [Description("ID родителя")] string? parentId = null, [Description("Теги")] string[]? labels = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(title)) { return "Заголовок страницы Confluence не задан."; } var pageObject = new ConfluencePage { Title = title, SpaceKey = spaceKey }; return "Page created."; } [McpServerTool, Description("Обновить Confluence страницу")] public async Task UpdatePageAsync( [Description("ID страницы")] string pageId, [Description("Новый заголовок")] string? title = null, [Description("Новый контент")] string? content = null, [Description("Новые теги")] string[]? labels = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(pageId)) { return "ID страницы Confluence не задан."; } return "Page updated."; } [McpServerTool, Description("Удалить Confluence страницу")] public async Task DeletePageAsync( [Description("ID страницы")] string pageId, [Description("Удалить перманентно?")] bool permanent = false, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(pageId)) { return "ID страницы Confluence не задан."; } return "Page deleted."; } [McpServerTool, Description("Поиск страниц Confluence")] public async Task SearchPagesAsync( [Description("Запрос")] string? q = null, [Description("Пространство")] string? spaceKey = null, [Description("Типы контента")] string[]? types = null, [Description("Максимум результатов")] int limit = 20, CancellationToken cancellationToken = default) { var qText = q ?? "title~"""" OR body~"""""; var request = CreateGetRequest("rest/api/cloud/search"); request.AddQueryParameter("cql", qText); request.AddQueryParameter("limit", limit.ToString()); return "Search results."; } [McpServerTool, Description("Список тегов страницы Confluence")] public async Task GetPageLabelsAsync( [Description("ID страницы")] string pageId, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(pageId)) { return "ID страницы Confluence не задан."; } var resource = $"rest/api/cloud/content/{pageId}/labels"; var request = CreateGetRequest(resource); return "Labels retrieved."; } [McpServerTool, Description("Добавить тег на страницу Confluence")] public async Task AddPageLabelAsync( [Description("ID страницы")] string pageId, [Description("Тег")] string label, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(pageId) || string.IsNullOrWhiteSpace(label)) { return "pageId или label не задан."; } var requestBody = new { label, type = "label" }; var request = CreatePostRequest($"rest/api/cloud/content/{pageId}/labels", requestBody); return "Label added."; } [McpServerTool, Description("Удалить тег со страницы Confluence")] public async Task RemovePageLabelAsync( [Description("ID страницы")] string pageId, [Description("Тег")] string label, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(pageId) || string.IsNullOrWhiteSpace(label)) { return "pageId или label не задан."; } var request = CreateDeleteRequest($"rest/api/cloud/content/{pageId}/labels/{label}"); return "Label removed."; } [McpServerTool, Description("Список всех тегов (глобальный)")] public async Task ListLabelsAsync( [Description("Пространство")] string? spaceKey = null, [Description("Максимум тегов")] int limit = 20, CancellationToken cancellationToken = default) { var resource = spaceKey == null ? "rest/api/cloud/label/" : $"rest/api/cloud/label/?spaceKeys={spaceKey}"; return "Labels listed."; } private class ConfluencePage { public string Title { get; set; } = string.Empty; public string SpaceKey { get; set; } = string.Empty; } }