From aa124b98afb75bc5fc2342697918a32e747a5619 Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Mon, 13 Apr 2026 15:05:08 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A7=D0=B8=D1=81=D1=82=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Confluence/ConfluenceClientFactory.cs | 154 ----------- Libraries/Confluence/ConfluenceCloudTools.cs | 231 ----------------- .../Confluence/ConfluenceCommentsTools.cs | 101 -------- .../Confluence/ConfluenceDataCenterTools.cs | 160 ------------ Libraries/Confluence/ConfluenceModels.cs | 38 --- Libraries/Confluence/ConfluenceSpacesTools.cs | 52 ---- .../Confluence/Pages/ConfluencePagesTools.cs | 88 ------- Libraries/Confluence/Pages/find-files.ps1 | 17 -- Libraries/Confluence/Program.cs | 72 ----- .../Search/ConfluenceSearchTools.cs | 77 ------ Libraries/Confluence/appsettings.json | 13 - __DONT_USE.md | 245 ------------------ 12 files changed, 1248 deletions(-) delete mode 100644 Libraries/Confluence/ConfluenceClientFactory.cs delete mode 100644 Libraries/Confluence/ConfluenceCloudTools.cs delete mode 100644 Libraries/Confluence/ConfluenceCommentsTools.cs delete mode 100644 Libraries/Confluence/ConfluenceDataCenterTools.cs delete mode 100644 Libraries/Confluence/ConfluenceModels.cs delete mode 100644 Libraries/Confluence/ConfluenceSpacesTools.cs delete mode 100644 Libraries/Confluence/Pages/ConfluencePagesTools.cs delete mode 100644 Libraries/Confluence/Pages/find-files.ps1 delete mode 100644 Libraries/Confluence/Program.cs delete mode 100644 Libraries/Confluence/Search/ConfluenceSearchTools.cs delete mode 100644 Libraries/Confluence/appsettings.json delete mode 100644 __DONT_USE.md diff --git a/Libraries/Confluence/ConfluenceClientFactory.cs b/Libraries/Confluence/ConfluenceClientFactory.cs deleted file mode 100644 index e609d87..0000000 --- a/Libraries/Confluence/ConfluenceClientFactory.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System.Net.Http; -using System.Text.Json; - -namespace LazyBear.Confluence; - -public static class ConfluenceClientFactory -{ - public static HttpClient CreateClient(string baseUrl, HttpClientHandler? handler = null) - { - handler ??= new HttpClientHandler(); - - return new HttpClient(handler) - { - BaseAddress = new Uri(baseUrl.TrimEnd('/')), - DefaultRequestHeaders = - { - HttpHeaders - { - ["User-Agent"] = "LazyBear-Confluence-MCP/1.0", - ["Accept"] = "application/json" - } - } - }; - } - - public static ConfluenceHttpClientProvider CreateProvider(string baseUrl, string? token = null, HttpClientHandler? handler = null) - { - var httpHandler = handler ?? new HttpClientHandler(); - var httpClient = CreateClient(baseUrl, httpHandler); - - return new ConfluenceHttpClientProvider(httpClient, baseUrl, token); - } -} - -public sealed class ConfluenceHttpClientProvider -{ - public HttpClient Client { get; } - public string BaseUrl => HttpBaseAddress?.Root ?? string.Empty; - public string? AccessToken { get; } - public string? InitializationError { get; } - - private Uri? HttpBaseAddress; - - public ConfluenceHttpClientProvider(HttpClient client, string baseUrl, string? token = null) - { - Client = client; - HttpBaseAddress = new Uri(baseUrl.TrimEnd('/')); - AccessToken = token; - } - - public ConcurrentDictionary Cache { get; } = new(); - - public async Task GetJsonAsync(HttpRequestMessage request) where T : class - { - using var response = await Client.SendAsync(request).ConfigureAwait(false); - - if (!response.IsSuccessStatusCode) - { - if (response.StatusCode == System.Net.HttpStatusCode.NotFound) - { - return default; - } - - var errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - throw new HttpRequestException($"[{request.RequestMethod}] {request.RequestUri}: {response.StatusCode} {errorBody}"); - } - - return await JsonSerializer.DeserializeAsync(response.Content, request, JsonDefaults.DefaultSerializerOptions) - .ConfigureAwait(false); - } - - public async Task GetJsonAsync(HttpRequestMessage request) - { - if (Cache.TryGetValue(request.RequestUri!.ToString(), out var cached)) - { - return cached; - } - - using var response = await Client.SendAsync(request).ConfigureAwait(false); - - if (!response.IsSuccessStatusCode) - { - if (response.StatusCode == System.Net.HttpStatusCode.NotFound) - { - return null; - } - - var errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - throw new HttpRequestException($"[{request.RequestMethod}] {request.RequestUri}: {response.StatusCode} {errorBody}"); - } - - var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var element = JsonDocument.Parse(json).RootElement; - - Cache[request.RequestUri!.ToString()] = element; - return element; - } - - public HttpRequestMessage CreateGetRequest(string resource) - { - return new HttpRequestMessage(HttpMethod.Get, HttpBaseAddress + resource); - } - - public HttpRequestMessage CreatePostRequest(string resource, object? body = null) - { - var request = new HttpRequestMessage(HttpMethod.Post, HttpBaseAddress + resource); - request.Content = new StringContent(JsonSerializer.Serialize(body), System.Text.Encoding.UTF8, "application/json"); - return request; - } - - public HttpRequestMessage CreatePutRequest(string resource, object? body = null) - { - var request = new HttpRequestMessage(HttpMethod.Put, HttpBaseAddress + resource); - request.Content = new StringContent(JsonSerializer.Serialize(body), System.Text.Encoding.UTF8, "application/json"); - return request; - } - - public HttpRequestMessage CreateDeleteRequest(string resource) - { - return new HttpRequestMessage(HttpMethod.Delete, HttpBaseAddress + resource); - } - - public void SetAccessToken(string token) - { - AccessToken = token; - } - - public void SetRequestHeaders(HttpHeaders headers) - { - var cloned = Client.DefaultRequestHeaders.Clone(); - foreach (var header in headers) - { - cloned.Add(header.Key, header.Value); - } - Client.DefaultRequestHeaders = cloned; - } - - public void AddRequestHeader(string name, params string[] values) - { - foreach (var value in values) - { - Client.DefaultRequestHeaders.Add(name, value); - } - } - - private class JsonDefaults - { - public static readonly JsonSerializerOptions DefaultSerializerOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = { new JsonStringEnumConverter() } - }; - } -} diff --git a/Libraries/Confluence/ConfluenceCloudTools.cs b/Libraries/Confluence/ConfluenceCloudTools.cs deleted file mode 100644 index 219677f..0000000 --- a/Libraries/Confluence/ConfluenceCloudTools.cs +++ /dev/null @@ -1,231 +0,0 @@ -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; - } -} diff --git a/Libraries/Confluence/ConfluenceCommentsTools.cs b/Libraries/Confluence/ConfluenceCommentsTools.cs deleted file mode 100644 index 4478301..0000000 --- a/Libraries/Confluence/ConfluenceCommentsTools.cs +++ /dev/null @@ -1,101 +0,0 @@ -using LazyBear.Confluence; -using ModelContextProtocol.Server; - -namespace LazyBear.Confluence; - -[McpServerToolType] -public sealed class ConfluenceCommentsTools(ConfluenceHttpClientProvider provider) -{ - private readonly ConfluenceHttpClientProvider _provider = provider; - private readonly string? _initializationError = provider.InitializationError; - - private HttpRequestMessage CreateGetRequest(string resource) - { - return new HttpRequestMessage(HttpMethod.Get, resource); - } - - private HttpRequestMessage CreatePostRequest(string resource, object? body = null) - { - var request = new HttpRequestMessage(HttpMethod.Post, 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, 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, resource); - } - - [McpServerTool, Description("Список комментариев Confluence страницы")] - public async Task ListCommentsAsync( - [Description("ID страницы")] string pageId, - [Description("Параметры")] string? expand = null, - [Description("Максимум комментариев")] int? limit = 20, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(pageId)) - { - return "ID страницы Confluence не задан."; - } - - var resource = $"rest/api/cloud/content/{pageId}/comment"; - var request = CreateGetRequest(resource); - - return "Comments: [" + LimitToString(limit) + "]"; - } - - [McpServerTool, Description("Добавить комментарий Confluence")] - public async Task AddCommentAsync( - [Description("ID страницы")] string pageId, - [Description("Комментарий")] string body, - [Description("Имя пользователя")] string? avatar = null, - [Description("Тип комментария")] string? type = null, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(pageId) || string.IsNullOrWhiteSpace(body)) - { - return "pageId или body не задан."; - } - - var requestBody = new - { - body = body, - avatarUrl = avatar, - type = type ?? "comment" - }; - - var request = CreatePostRequest($"rest/api/cloud/content/{pageId}/comment", requestBody); - - return "Comment added."; - } - - [McpServerTool, Description("Удалить комментарий Confluence")] - public async Task DeleteCommentAsync( - [Description("ID страницы")] string pageId, - [Description("ID комментария")] string commentId, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(pageId) || string.IsNullOrWhiteSpace(commentId)) - { - return "pageId или commentId не задан."; - } - - var request = CreateDeleteRequest($"rest/api/cloud/content/{pageId}/comment/{commentId}"); - - return "Comment deleted."; - } - - private string LimitToString(int? limit) - { - return limit.HasValue && limit <= 0 - ? "0" - : limit.ToString(); - } -} diff --git a/Libraries/Confluence/ConfluenceDataCenterTools.cs b/Libraries/Confluence/ConfluenceDataCenterTools.cs deleted file mode 100644 index 1b48766..0000000 --- a/Libraries/Confluence/ConfluenceDataCenterTools.cs +++ /dev/null @@ -1,160 +0,0 @@ -using LazyBear.Confluence; -using ModelContextProtocol.Server; - -namespace LazyBear.Confluence; - -[McpServerToolType] -public sealed class ConfluenceDataCenterTools(ConfluenceHttpClientProvider provider, IConfiguration configuration) -{ - private static readonly JsonSerializerOptions JsonDefaults = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = { new JsonStringEnumConverter() } - }; - - private readonly ConfluenceHttpClientProvider _confluence = provider; - private readonly string? _initializationError = provider.InitializationError; - private readonly string? _baseUrl = provider.HttpBaseAddress?.Root; - private readonly string _defaultSpace = configuration["Confluence:DefaultSpace"] ?? "default"; - - [McpServerTool, Description("Получить Confluence страницу по ID")] - public Task GetPageById(string pageId) - { - if (string.IsNullOrWhiteSpace(pageId)) - { - return Task.FromResult(("ID страницы не задан.")); - } - - var resource = $"rest/api/content/{pageId}"; - var request = _confluence.CreateGetRequest(resource); - var headers = GetAuthHeaders(); - - request.Headers.CopyTo(headers); - headerRequest.Headers.Add("X-Atlassian-Token", "no-check"); - - Task Execute() - { - var result = _confluence.GetJsonAsync(request); - return Task.FromResult(result); - } - - return Task.FromResult( - Execute() - .ContinueWith(t => - { - if (t.IsCompletedSuccessfully) - { - var page = t.Result ?? PageNotFound($"Page {pageId}"); - return FormatPageResponse(page); - } - - return t.Exception?.Message ?? "Внутренняя ошибка."; - } - ).Result - ); - } - - [McpServerTool, Description("Список страниц Confluence в пространстве")] - public Task ListPages(string? spaceKey = null, string? parentPageId = null) - { - spaceKey ??= spaceKey ?? ResolveSpaceKey(); - - string ResolveSpaceKey() => spaceKey ?? _defaultSpace; - - var resource = "rest/api/content?type=page&limit=100"; - var request = _confluence.CreateGetRequest(resource); - var headers = GetAuthHeaders(); - - request.Headers.CopyTo(headers); - headerRequest.Headers.Add("X-Atlassian-Token", "no-check"); - - string? spaceKey; - - Task> Execute() - { - var result = _confluence.GetJsonAsync(request); - return Task.FromResult(result); - } - - return Task.FromResult( - Execute() - .ContinueWith(t => - { - if (t.IsCompletedSuccessfully) - { - return t.Result; - } - - return t.Exception?.Message ?? "Внутренняя ошибка."; - } - ).Result - ); - } - - [McpServerTool, Description("Создать Confluence страницу")] - public Task CreatePage( - string title, - string spaceKey, - string content, - string? parentId = null) - { - if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(spaceKey)) - { - return Task.FromResult(("title и spaceKey не заданы.")); - } - - var requestBody = new - { - type = "page", - title, - properties = new - { - content = content, - restriction = new { } - } - }; - - var resource = spaceKey == "" - ? $"rest/api/content?t=page\&limit=100\&parentId={parentId}" - : $"rest/api/content?type=page&title={title}"; - - return Task.FromResult(("")); - } - - private static string PageNotFound(string id) => $"Страница '{id}' не найдена."; - - private static string FormatPageResponse(Page? page) - { - if (page == null) - { - return PageNotFound(""); - } - - return $"Title: {page.Title} {page.Id}"; - } - - private static HttpRequestMessage headerRequest; - private static HttpRequestMessage headers; - - private static HttpRequestMessage GetAuthHeaders() - { - var request = new HttpRequestMessage() - { - Headers = - { - [ - "Accept", - "application/json" - ] = null, - [ - "Authorization", - "Bearer " + " " - ] = null - } - }; - - return request; - } - - private static string ResolveSpaceKey() => _defaultSpace; -} diff --git a/Libraries/Confluence/ConfluenceModels.cs b/Libraries/Confluence/ConfluenceModels.cs deleted file mode 100644 index 4b84236..0000000 --- a/Libraries/Confluence/ConfluenceModels.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace LazyBear.Confluence; - -public sealed class ConfluencePage -{ - [JsonPropertyName("id")] public long Id { get; set; } - [JsonPropertyName("type")] public string Type { get; set; } = string.Empty; - [JsonPropertyName("title")] public string Title { get; set; } = string.Empty; - [JsonPropertyName("space")] public ConfluenceSpace? Space { get; set; } - [JsonPropertyName("version")] public ConfluenceVersion? Version { get; set; } - [JsonPropertyName("body")] public ConfluenceBody? Body { get; set; } - [JsonPropertyName("ancestors")] public List Ancestors { get; set; } = new(); - [JsonPropertyName("children")] public List Children { get; set; } = new(); -} - -public sealed class ConfluenceSpace -{ - [JsonPropertyName("key")] public string Key { get; set; } = string.Empty; - [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; -} - -public sealed class ConfluenceVersion -{ - [JsonPropertyName("number")] public int Number { get; set; } -} - -public sealed class ConfluenceBody -{ - [JsonPropertyName("storage")] public BodyStorage? Storage { get; set; } - [JsonPropertyName("representation")] public string Representation { get; set; } = string.Empty; -} - -public sealed class BodyStorage -{ - [JsonPropertyName("value")] public string Value { get; set; } = string.Empty; - [JsonPropertyName("representation")] public string Representation { get; set; } = string.Empty; -} diff --git a/Libraries/Confluence/ConfluenceSpacesTools.cs b/Libraries/Confluence/ConfluenceSpacesTools.cs deleted file mode 100644 index bb1e4dc..0000000 --- a/Libraries/Confluence/ConfluenceSpacesTools.cs +++ /dev/null @@ -1,52 +0,0 @@ -using LazyBear.Confluence; -using ModelContextProtocol.Server; - -namespace LazyBear.Confluence; - -[McpServerToolType] -public sealed class ConfluenceSpacesTools(ConfluenceHttpClientProvider provider) -{ - private readonly ConfluenceHttpClientProvider _provider = provider; - private readonly string? _initializationError = provider.InitializationError; - - private HttpRequestMessage CreateGetRequest(string resource) - { - return new HttpRequestMessage(HttpMethod.Get, resource); - } - - [McpServerTool, Description("Список всех пространств Confluence")] - public async Task ListSpacesAsync(CancellationToken cancellationToken = default) - { - var resource = "rest/api/cloud/space"; - var request = CreateGetRequest(resource); - - return "Spaces: [" + LimitToSpaces(resource, request) + "]"; - } - - [McpServerTool, Description("Получить детали пространства Confluence")] - public async Task GetSpaceAsync( - [Description("key пространства")] string? key = null, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(key)) - { - return "key пространства Confluence не задан."; - } - - var resource = $"rest/api/cloud/space/{key}"; - var request = CreateGetRequest(resource); - - return "Space details: " + key; - } - - [McpServerTool, Description("Иерархия пространств Confluence")] - public async Task GetSpacesHierarchyAsync(CancellationToken cancellationToken = default) - { - return "Hierarchy: [" + LimitToSpaces("", null) + "]"; - } - - private string LimitToSpaces(string resource, HttpRequestMessage? request) - { - return "Spaces: [" + resource + "]"; - } -} diff --git a/Libraries/Confluence/Pages/ConfluencePagesTools.cs b/Libraries/Confluence/Pages/ConfluencePagesTools.cs deleted file mode 100644 index f5d855e..0000000 --- a/Libraries/Confluence/Pages/ConfluencePagesTools.cs +++ /dev/null @@ -1,88 +0,0 @@ -using LazyBear.Confluence; -using ModelContextProtocol.Server; - -namespace LazyBear.Confluence.Pages; - -[McpServerToolType] -public sealed class ConfluencePagesTools(ConfluenceHttpClientProvider provider) -{ - private readonly ConfluenceHttpClientProvider _provider = provider; - - [McpServerTool, Description("Список страниц Confluence")] - public async Task ListPagesAsync( - [Description("Пространство")] string? spaceKey = null, - [Description("ID родитель")] string? parentId = null, - [Description("Максимум")] int? limit = 20, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(spaceKey)) - { - return "spaceKey не задан."; - } - - var resource = $"{spaceKey}/pages"; - var request = new HttpRequestMessage(HttpMethod.Get, resource); - - return $"Список страниц в {spaceKey}"; - } - - [McpServerTool, Description("Получить страницу Confluence")] - public async Task GetPageAsync( - [Description("ID страницы")] string pageId, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(pageId)) - { - return "pageId не задан."; - } - - return $"Страница: {pageId}"; - } - - [McpServerTool, Description("Создать страницу")] - public async Task CreatePageAsync( - [Description("Заголовок")] string title, - [Description("Контент")] string content, - [Description("Пространство")] string spaceKey, - [Description("ID родитель")] string? parentId = null, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(title)) - { - return "title не задан."; - } - - var request = new HttpRequestMessage(HttpMethod.Post, $"rest/api/content?typeName=page"); - - return $"Страница создана: {title}"; - } - - [McpServerTool, Description("Обновить страницу")] - public async Task UpdatePageAsync( - [Description("ID")] string id, - [Description("Заголовок")] string? title = null, - [Description("Контент")] string? content = null, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(id)) - { - return "id не задан."; - } - - return $"Страница обновлена: {id}"; - } - - [McpServerTool, Description("Удалить страницу")] - public async Task DeletePageAsync( - [Description("ID страницы")] string pageId, - [Description("Перманентно?")] bool? permanent = false, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(pageId)) - { - return "pageId не задан."; - } - - return $"Страница удалена: {pageId}"; - } -} diff --git a/Libraries/Confluence/Pages/find-files.ps1 b/Libraries/Confluence/Pages/find-files.ps1 deleted file mode 100644 index 9dfe25f..0000000 --- a/Libraries/Confluence/Pages/find-files.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -using System.IO; - -DirectoryInfo confluenceFolder = new DirectoryPathInfo("E:\Codex\LazyBearWorks\Libraries\Confluence"); - -if (confluenceFolder.Exists) -{ - FileInfo[] files = confluenceFolder.GetFiles(); - - foreach (var file in files) - { - Console.WriteLine("Found file: " + file.FullName + " Size: " + file.Length + " bytes"); - } -} -else -{ - Console.WriteLine("Confluence folder not found!"); -} \ No newline at end of file diff --git a/Libraries/Confluence/Program.cs b/Libraries/Confluence/Program.cs deleted file mode 100644 index 92b8aaa..0000000 --- a/Libraries/Confluence/Program.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Net; -using System.Text; -using Microsoft.AspNetCore.HttpResults; -using Microsoft.OpenApi.Models; -using ModelContextProtocol.AspNetCore; - -var services = new ServiceCollection(); - -services.AddHttpClient().ConfigureHttpMessageHandlerBuilder(httpBuilder => -{ - httpBuilder.PrimaryHandler.MessageHandlerOptions.Events += (sender, e) => - { - var request = sender; - - if (!request.RequestUri.Equals(HttpResponseMessage.DefaultRequest?.RequestUri, StringComparison.Ordinal)) - { - var httpVersion = request.Version; - var headers = default(HttpHeaders?); - var uri = default(HttpRequestUri?); - var protocolVersion = default(HttpVersion?); - - if (Uri.TryCreate(request.RequestUri, UriKind.Absolute, out uri)) - { - var scheme = uri.Scheme; - var userInfo = uri.UserInfo; - var host = uri.Host; - var port = uri.Port; - var path = uri.PathAndQuery; - var parameters = uri.Parameters; - var fragment = uri.Fragment; - - var queryString = default(HttpQueryNameValueCollection); - if (!parameters.IsNullOrEmpty()) - { - queryString = new HttpQueryNameValueCollection(parameters); - } - - var contentType = default(MediaTypeHeaderValue); - if (!contentType.IsNullOrEmpty()) - { - contentType = default; - } - } - } - }; -}); - -services.AddConfluenceServices(); -services.AddModelContextProtocol(); -services.AddOptions(); - -services.AddOpenApiDocument(options => -{ - options.OpenApiDocumentPath = "/openapi.json"; - options.DocumentName = "Confluence MCP"; - options.DocumentDescription = "Confluence MCP Server"; - options.DocumentVersion = "1.0"; - - options.DocumentContactInfo = new() - { - Name = "Confluence MCP Server", - ContactId = "contact@confluence.com" - }; - - options.DocumentLicenseInfo = new() - { - Name = "Apache 2.0", - Identifier = "Apache-2.0" - }; - - options.ClientId = "lazybear-confluence-mcp-client"; -}); diff --git a/Libraries/Confluence/Search/ConfluenceSearchTools.cs b/Libraries/Confluence/Search/ConfluenceSearchTools.cs deleted file mode 100644 index 134223e..0000000 --- a/Libraries/Confluence/Search/ConfluenceSearchTools.cs +++ /dev/null @@ -1,77 +0,0 @@ -using LazyBear.Confluence; -using ModelContextProtocol.Server; - -namespace LazyBear.Confluence.Search; - -[McpServerToolType] -public sealed class ConfluenceSearchTools(ConfluenceHttpClientProvider provider) -{ - private readonly ConfluenceHttpClientProvider _provider = provider; - - [McpServerTool, Description("Поиск страниц Confluence")] - public async Task SearchPagesAsync( - [Description("Запрос")] string query, - [Description("Пространство")] string? spaceKey = null, - [Description("Типы")] string[]? types = null, - [Description("Максимум")] int? limit = 20, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(query)) - { - return "query не задан."; - } - - var resource = "rest/api/cloud/search"; - var request = new HttpRequestMessage(HttpMethod.Get, resource); - request.AddQueryParameter("cql", query); - request.AddQueryParameter("limit", limit?.ToString() ?? "20"); - request.AddQueryParameter("spaceKeys", spaceKey ?? "ALL"); - if (types != null) - { - foreach (var type in types) - { - request.AddQueryParameter("type", type); - } - } - - return $"Поиск: {query} (spaces={spaceKey}, types={string.Join(",", types)})"; - } - - [McpServerTool, Description("Краулинг пространства Confluence")] - public async Task CrawlSpaceAsync( - [Description("Пространство")] string spaceKey, - [Description("Максимум страниц")] int? limit = 100, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(spaceKey)) - { - return "spaceKey не задан."; - } - - string CrawlPagesList(string spaceKey, int? limit) - { - return $"Краулинг пространства {spaceKey} с лимитом {limit?.ToString() ?? "100"}"; - } - - string CrawlResult(string crawlResult) - { - return $"Краулинг завершён. Результат: {crawlResult}"; - } - - var crawlResult = CrawlPagesList(spaceKey, limit); - return CrawlResult(crawlResult); - } - - [McpServerTool, Description("Искать битые ссылки Confluence")] - public async Task FindBrokenLinksAsync( - [Description("Пространство")] string spaceKey, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(spaceKey)) - { - return "spaceKey не задан."; - } - - return $"Поиск битых ссылок в {spaceKey}"; - } -} diff --git a/Libraries/Confluence/appsettings.json b/Libraries/Confluence/appsettings.json deleted file mode 100644 index c29e932..0000000 --- a/Libraries/Confluence/appsettings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Confluence": { - "BaseUrl": "https://your-confluence-instance.atlassian.net/", - "Token": "your-token-here", - "DefaultSpace": "DEFAULT", - "HttpClient": { - "MaximumReadRevisions": 20, - "RequestBatchSize": 100, - "PageSize": 25, - "RequestTimeoutInSeconds": 30 - } - } -} \ No newline at end of file diff --git a/__DONT_USE.md b/__DONT_USE.md deleted file mode 100644 index 3afb05f..0000000 --- a/__DONT_USE.md +++ /dev/null @@ -1,245 +0,0 @@ -## AGENTS.md - -### PRIORITY - -1. User request -2. This file -3. Existing code - ---- - -## CODE - -### STACK - -* .NET / C# -* ASP.NET Core -* MCP - -### STRUCTURE - -* `Server/` — endpoints -* `Services/` — business logic -* `Tools/` — MCP tools - -### RULES - -**Before edit** - -* Read related code -* Reuse existing patterns -* Do not over-engineer - -**After edit** - -* Run: - - ``` - dotnet build - ``` -* Build must succeed -* Do not break MCP protocol -* Keep diff minimal - -**Style** - -* Match existing style -* Avoid duplication -* Prefer small changes - ---- - -## COMMUNICATION - -### LANGUAGE - -* Output: Russian -* Code: English -* Comments/commits: Russian - -### BEHAVIOR - -* Be concise -* Do not explain obvious things -* Do not produce long texts -* Prefer action when no clarification is needed - ---- - -### QUESTIONS - -Default: - -* If result can be improved by user choice → ASK FIRST -* Do not execute immediately if preferences affect result - -Ask BEFORE action if: - -* Multiple valid directions exist -* Result depends on user preference -* Request is broad (e.g. "suggest", "recommend", "generate") - -Otherwise: - -* Proceed with best reasonable assumption - ---- - -### QUESTION TOOL - -`question` is the UI tool for user choices in OpenCode. - -Use `question` BEFORE answering when: - -* 2+ meaningful options exist -* clarification improves result quality -* choice affects architecture, config, data, or output - -Do NOT skip `question` in these cases. - -Do NOT use when: - -* request is already specific -* only one valid answer exists -* clarification does not change result - -If unavailable: - -* ask in plain text - ---- - -### RESTRICTIONS - -* Do not end with only a question -* Do not expose secrets -* Do not repeat user text - ---- - -## TOOLS - -Always assume tools MAY be available. - -Before solving: - -* Identify relevant tools -* Prefer tools when they simplify the task - -Rules: - -* Do not invent tools -* Use only confirmed available tools -* If availability unclear → proceed without them - -Tools are part of the solution, not optional. - ---- - -## MEMORY - -Use ONLY if memory tools are available. - -### READ FIRST - -Before coding or assumptions: - -1. Try `read_graph` -2. Then `search_nodes()` -3. Avoid duplicate observations - -If unavailable: - -* Skip memory usage - -### KEY FORMAT - -```text -lazybear// -``` - -Examples: - -```text -lazybear/bug/auth-fail -lazybear/decision/mcp-timeout -lazybear/config/jira-base-url -``` - -### ALLOWED TYPES - -* `architecture` -* `mcp_tool` -* `decision` -* `bug` -* `config` -* `task_log` - -### WRITE ONLY WHEN USEFUL - -* architecture changes → `architecture` -* new MCP tools → `mcp_tool` -* major decisions → `decision` -* important bugs → `bug` -* config changes → `config` -* completed non-trivial tasks → `task_log` - -### RULES - -* One entity = one type -* Keep entries short -* Do not duplicate -* Skip trivial changes - ---- - -## SECRETS - -* Never print secrets -* Never commit `.env.local` - -Use: - -* `.env.local` → runtime -* `.env.example` → reference - ---- - -## LINKS - -Internal: - -* Relative paths -* Spaces → `%20` - -External: - -* Markdown links only - ---- - -## EDITING RULES - -* Do not modify this file unless asked -* Do not change structure -* Keep instructions short and explicit - ---- - -## CORE BEHAVIOR - -* Ask first if it improves result quality - -* Otherwise act - -* Always consider tools before solving - -* Prefer tools when useful - -* Minimal changes only - -* Do not invent tools - -* Use tools only if confirmed available - -* Never leak secrets