diff --git a/docs-mcp/src/Directory.Build.props b/docs-mcp/src/Directory.Build.props index 6b15a4956..d50713caa 100644 --- a/docs-mcp/src/Directory.Build.props +++ b/docs-mcp/src/Directory.Build.props @@ -9,6 +9,7 @@ Duende Documentation MCP Server dmcp- 1.0 + enable $(NoWarn);CA2007 diff --git a/docs-mcp/src/Documentation.Mcp/Database/FTSBlogArticle.cs b/docs-mcp/src/Documentation.Mcp/Database/FTSBlogArticle.cs index 8d10712eb..c989b8491 100644 --- a/docs-mcp/src/Documentation.Mcp/Database/FTSBlogArticle.cs +++ b/docs-mcp/src/Documentation.Mcp/Database/FTSBlogArticle.cs @@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations; namespace Documentation.Mcp.Database; -public class FTSBlogArticle +internal class FTSBlogArticle { [Key] public required string Id { get; init; } diff --git a/docs-mcp/src/Documentation.Mcp/Database/FTSDocsArticle.cs b/docs-mcp/src/Documentation.Mcp/Database/FTSDocsArticle.cs index d905cee46..a4606cbb3 100644 --- a/docs-mcp/src/Documentation.Mcp/Database/FTSDocsArticle.cs +++ b/docs-mcp/src/Documentation.Mcp/Database/FTSDocsArticle.cs @@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations; namespace Documentation.Mcp.Database; -public class FTSDocsArticle +internal class FTSDocsArticle { [Key] public required string Id { get; init; } diff --git a/docs-mcp/src/Documentation.Mcp/Database/FTSSampleProject.cs b/docs-mcp/src/Documentation.Mcp/Database/FTSSampleProject.cs index b4392b5ad..7621289c0 100644 --- a/docs-mcp/src/Documentation.Mcp/Database/FTSSampleProject.cs +++ b/docs-mcp/src/Documentation.Mcp/Database/FTSSampleProject.cs @@ -5,12 +5,16 @@ using System.ComponentModel.DataAnnotations; namespace Documentation.Mcp.Database; -public class FTSSampleProject +internal class FTSSampleProject { [Key] public required string Id { get; init; } + public required string Product { get; init; } + public required string Title { get; init; } + public required string Description { get; init; } + public List Files { get; init; } = new(0); } diff --git a/docs-mcp/src/Documentation.Mcp/Database/McpDb.cs b/docs-mcp/src/Documentation.Mcp/Database/McpDb.cs index ef7c4526b..b4c899b22 100644 --- a/docs-mcp/src/Documentation.Mcp/Database/McpDb.cs +++ b/docs-mcp/src/Documentation.Mcp/Database/McpDb.cs @@ -6,14 +6,14 @@ using Microsoft.EntityFrameworkCore; namespace Documentation.Mcp.Database; -public class McpDb : DbContext +internal class McpDb(DbContextOptions options) : DbContext(options) { - public McpDb(DbContextOptions options) - : base(options) { } - public DbSet State => Set(); + public DbSet FTSDocsArticle => Set(); + public DbSet FTSBlogArticle => Set(); + public DbSet FTSSampleProject => Set(); public async Task SetLastUpdateStateAsync(string key, DateTimeOffset value) @@ -39,20 +39,17 @@ public class McpDb : DbContext public async Task GetLastUpdateStateAsync(string key) { var stateEntity = await State.FirstOrDefaultAsync(it => it.Key == key); - if (stateEntity == null) - { - return DateTimeOffset.MinValue; - } - - return JsonSerializer.Deserialize(stateEntity.Value); + return stateEntity == null + ? DateTimeOffset.MinValue + : JsonSerializer.Deserialize(stateEntity.Value); } - public string? EscapeFtsQueryString(string? query) + public static string? EscapeFtsQueryString(string? query) => !string.IsNullOrEmpty(query) ? string.Join(" ", query.Split(' ').Select(q => $"\"{q.Replace("\"", "\"\"")}\"")) : query; - public string? EscapeFtsQueryString(string? query, string joinWith) + public static string? EscapeFtsQueryString(string? query, string joinWith) => !string.IsNullOrEmpty(query) ? string.Join($" {joinWith} ", query.Split(' ').Select(q => $"\"{q.Replace("\"", "\"\"")}\"")) : query; diff --git a/docs-mcp/src/Documentation.Mcp/Database/Migrations/20250828191400_Initial.cs b/docs-mcp/src/Documentation.Mcp/Database/Migrations/20250828191400_Initial.cs index 9c4e985fc..0f2e1613d 100644 --- a/docs-mcp/src/Documentation.Mcp/Database/Migrations/20250828191400_Initial.cs +++ b/docs-mcp/src/Documentation.Mcp/Database/Migrations/20250828191400_Initial.cs @@ -3,58 +3,14 @@ using Microsoft.EntityFrameworkCore.Migrations; -#nullable disable - namespace Duende.Documentation.Mcp.Server.Database.Migrations; /// -public partial class Initial : Migration +internal partial class Initial : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { - // migrationBuilder.CreateTable( - // name: "FTSBlogArticle", - // columns: table => new - // { - // Id = table.Column(type: "TEXT", nullable: false), - // Title = table.Column(type: "TEXT", nullable: false), - // Content = table.Column(type: "TEXT", nullable: false) - // }, - // constraints: table => - // { - // table.PrimaryKey("PK_FTSBlogArticle", x => x.Id); - // }); - // - // migrationBuilder.CreateTable( - // name: "FTSDocsArticle", - // columns: table => new - // { - // Id = table.Column(type: "TEXT", nullable: false), - // Product = table.Column(type: "TEXT", nullable: false), - // Title = table.Column(type: "TEXT", nullable: false), - // Content = table.Column(type: "TEXT", nullable: false) - // }, - // constraints: table => - // { - // table.PrimaryKey("PK_FTSDocsArticle", x => x.Id); - // }); - // - // migrationBuilder.CreateTable( - // name: "FTSSampleProject", - // columns: table => new - // { - // Id = table.Column(type: "TEXT", nullable: false), - // Product = table.Column(type: "TEXT", nullable: false), - // Title = table.Column(type: "TEXT", nullable: false), - // Description = table.Column(type: "TEXT", nullable: false), - // Files = table.Column(type: "TEXT", nullable: false) - // }, - // constraints: table => - // { - // table.PrimaryKey("PK_FTSSampleProject", x => x.Id); - // }); - migrationBuilder.Sql(@"CREATE VIRTUAL TABLE FTSBlogArticle USING fts5(Id, Title, Content, tokenize = 'porter unicode61');"); migrationBuilder.Sql(@"CREATE VIRTUAL TABLE FTSDocsArticle USING fts5(Id, Product, Title, Content, tokenize = 'porter unicode61');"); migrationBuilder.Sql(@"CREATE VIRTUAL TABLE FTSSampleProject USING fts5(Id, Product, Title, Description, Files, tokenize = 'porter unicode61');"); diff --git a/docs-mcp/src/Documentation.Mcp/Database/State.cs b/docs-mcp/src/Documentation.Mcp/Database/State.cs index 9022b16bd..13899973c 100644 --- a/docs-mcp/src/Documentation.Mcp/Database/State.cs +++ b/docs-mcp/src/Documentation.Mcp/Database/State.cs @@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations; namespace Documentation.Mcp.Database; -public class State +internal class State { [Key] public required string Id { get; init; } diff --git a/docs-mcp/src/Documentation.Mcp/Infrastructure/TemporaryFileStream.cs b/docs-mcp/src/Documentation.Mcp/Infrastructure/TemporaryFileStream.cs index a529d4fc3..e4c76257e 100644 --- a/docs-mcp/src/Documentation.Mcp/Infrastructure/TemporaryFileStream.cs +++ b/docs-mcp/src/Documentation.Mcp/Infrastructure/TemporaryFileStream.cs @@ -3,7 +3,7 @@ namespace Documentation.Mcp.Infrastructure; -public class TemporaryFileStream : FileStream +internal class TemporaryFileStream : FileStream { public static TemporaryFileStream Create() { diff --git a/docs-mcp/src/Documentation.Mcp/Program.cs b/docs-mcp/src/Documentation.Mcp/Program.cs index c20453e35..dc247c2b8 100644 --- a/docs-mcp/src/Documentation.Mcp/Program.cs +++ b/docs-mcp/src/Documentation.Mcp/Program.cs @@ -5,7 +5,6 @@ using Documentation.Mcp.Database; using Documentation.Mcp.Sources.Blog; using Documentation.Mcp.Sources.Docs; using Documentation.Mcp.Sources.Samples; -using Duende.Documentation.Mcp.Server.Database; using Microsoft.AspNetCore.Builder; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; diff --git a/docs-mcp/src/Documentation.Mcp/Sources/Blog/BlogArticleIndexer.cs b/docs-mcp/src/Documentation.Mcp/Sources/Blog/BlogArticleIndexer.cs index 8cec45d37..323509055 100644 --- a/docs-mcp/src/Documentation.Mcp/Sources/Blog/BlogArticleIndexer.cs +++ b/docs-mcp/src/Documentation.Mcp/Sources/Blog/BlogArticleIndexer.cs @@ -12,7 +12,7 @@ using SimpleFeedReader; namespace Documentation.Mcp.Sources.Blog; -public class BlogArticleIndexer(IServiceProvider services, ILogger logger) : BackgroundService +internal class BlogArticleIndexer(IServiceProvider services, ILogger logger) : BackgroundService { private readonly TimeSpan _maxAge = TimeSpan.FromDays(2); private static readonly DateTime ReferenceDate = new(2024, 10, 01); @@ -59,14 +59,20 @@ public class BlogArticleIndexer(IServiceProvider services, ILogger Fetch( - [Description("The document id.")] string id) + public async Task Fetch([Description("The document id.")] string id) { var result = await db.FTSBlogArticle .FromSqlRaw("SELECT * FROM FTSBlogArticle WHERE Id = {0} ORDER BY rank", id) .AsNoTracking() .FirstOrDefaultAsync(); - if (result == null) - { - return $"No data found for document: \"{id}\"."; - } - - return $"# {result.Title}\n\n{result.Content}"; + return result == null + ? $"No data found for document: \"{id}\"." + : $"# {result.Title}\n\n{result.Content}"; } } diff --git a/docs-mcp/src/Documentation.Mcp/Sources/Docs/DocsArticleIndexer.cs b/docs-mcp/src/Documentation.Mcp/Sources/Docs/DocsArticleIndexer.cs index 7cdc1c489..436377e59 100644 --- a/docs-mcp/src/Documentation.Mcp/Sources/Docs/DocsArticleIndexer.cs +++ b/docs-mcp/src/Documentation.Mcp/Sources/Docs/DocsArticleIndexer.cs @@ -12,7 +12,7 @@ using Microsoft.Extensions.Logging; namespace Documentation.Mcp.Sources.Docs; -public class DocsArticleIndexer(IServiceProvider services, ILogger logger) : BackgroundService +internal class DocsArticleIndexer(IServiceProvider services, ILogger logger) : BackgroundService { private readonly TimeSpan _maxAge = TimeSpan.FromDays(2); @@ -40,12 +40,13 @@ public class DocsArticleIndexer(IServiceProvider services, ILogger DateTimeOffset.UtcNow.Add(-_maxAge)) { - logger.LogInformation("Skipping docs indexer, last update was {lastUpdate}", lastUpdate); + logger.LogInformation("Skipping docs indexer, last update was {LastUpdate}", lastUpdate); return; } // Explore llms.txt - var llmsTxt = await httpClient.GetStringAsync("https://docs.duendesoftware.com/llms.txt", stoppingToken); + var llmsUrl = new Uri("https://docs.duendesoftware.com/llms.txt"); + var llmsTxt = await httpClient.GetStringAsync(llmsUrl, stoppingToken); var llmsMd = Markdig.Markdown.Parse(llmsTxt); await db.FTSDocsArticle.ExecuteDeleteAsync(stoppingToken); @@ -57,19 +58,25 @@ public class DocsArticleIndexer(IServiceProvider services, ILogger logger) : BackgroundService +internal class SamplesIndexer(IServiceProvider services, ILogger logger) : BackgroundService { private readonly TimeSpan _maxAge = TimeSpan.FromDays(7); @@ -48,7 +48,8 @@ public class SamplesIndexer(IServiceProvider services, ILogger new SampleProject { Title = "No data found.", Description = "" }; @@ -97,7 +97,7 @@ public class SamplesSearchTool(McpDb db) public List Files { get; set; } = new(0); } - public class SampleProjectFile + internal class SampleProjectFile { public static SampleProjectFile NotFound() => new SampleProjectFile { Content = "No data found." };