Skip to content

feat: 完全重写 MCP Server - 新增组件文档提取、多语言支持、管理界面等功能#2

Open
j4587698 wants to merge 1 commit intomasterfrom
feature/rewrite-mcp-server
Open

feat: 完全重写 MCP Server - 新增组件文档提取、多语言支持、管理界面等功能#2
j4587698 wants to merge 1 commit intomasterfrom
feature/rewrite-mcp-server

Conversation

@j4587698
Copy link

@j4587698 j4587698 commented Feb 26, 2026

Summary by Sourcery

Replace the original minimal MCP server with a standalone BootstrapBlazor-based MCP + REST service that automatically syncs the BootstrapBlazor repo, extracts component docs into RAG-ready Markdown, and exposes them via MCP tools, HTTP APIs, and a secured admin dashboard.

New Features:

  • Add a DocsExtractor service that builds BootstrapBlazor, reflects over components, and generates API and sample Markdown files for RAG consumption.
  • Introduce an MCP service exposing tools to list, search, and retrieve component documentation and to answer component questions via optional AI integration.
  • Provide REST HTTP endpoints for listing, searching, and querying component docs to support non-MCP integrations such as external LLM agents.
  • Add a Blazor Server-based admin UI with authentication for monitoring status, triggering manual syncs, and editing server, AI, and auth configuration.
  • Implement a Git sync job that periodically clones or updates the BootstrapBlazor repository and triggers doc extraction on a configurable cron schedule.
  • Add runtime-configurable settings persisted to a JSON config file, including Git, AI, and admin credentials, with environment-variable-friendly defaults.
  • Introduce an AI integration service that calls OpenAI-compatible chat APIs (via Microsoft.Extensions.AI) to answer questions over the extracted docs.
  • Add English and Chinese localized READMEs and UI resources, and wire up ASP.NET Core localization for the dashboard.

Enhancements:

  • Refactor the hosting model into a single ASP.NET Core entry point that configures MCP transports, Blazor UI, authentication, localization, and Coravel scheduling.
  • Modernize project metadata and structure, including a new SDK-style project file, route setup, and BootstrapBlazor layout components for the dashboard.

Build:

  • Add a Dockerfile and container configuration for running the MCP server, including Git installation and default GitSync/ASPNETCORE settings.

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 26, 2026

Reviewer's Guide

Replaces the previous Aspire-based MCP server with a standalone ASP.NET Core + BootstrapBlazor MCP/REST server that can auto-sync the BootstrapBlazor repo, extract component docs into Markdown for RAG, expose MCP tools and REST APIs, and provide an authenticated admin UI with i18n and optional AI-backed Q&A, plus Docker packaging.

Sequence diagram for scheduled Git sync and docs extraction

sequenceDiagram
    participant Cron as Coravel_Scheduler
    participant GitJob as GitSyncInvocable
    participant Settings as AppSettingsManager
    participant Repo as BootstrapBlazor_Git_Repository
    participant Dotnet as dotnet_build_process
    participant Extractor as DocsExtractorService
    participant FS as Data_Directory

    Cron->>GitJob: Invoke()
    GitJob->>Settings: LoadSettings()
    Settings-->>GitJob: AppSettingsModel

    alt repo_missing_or_invalid
        GitJob->>Repo: Repository.Clone(RepositoryUrl, LocalPath)
    else repo_exists
        GitJob->>Repo: new Repository(LocalPath)
        GitJob->>Repo: Commands.Pull(...)
    end

    GitJob->>Dotnet: start "dotnet build" in LocalPath
    Dotnet-->>GitJob: ExitCode (0 or error)

    alt build_failed
        GitJob->>GitJob: log error and return
    else build_succeeded
        GitJob->>Extractor: Extract(LocalPath, OutputDir)
        Extractor->>FS: write API and Samples markdown
        Extractor-->>GitJob: completion
    end

    GitJob-->>Cron: Task.Completed
Loading

Sequence diagram for AskComponentExpert via MCP/REST with optional AI

sequenceDiagram
    actor Client
    participant Endpoint as Mcp_or_REST_Endpoint
    participant McpSvc as McpService
    participant Settings as AppSettingsManager
    participant FS as Data_Directory
    participant AiSvc as AiIntegrationService
    participant OpenAI as OpenAI_Compatible_API

    Client->>Endpoint: AskComponentExpert(ComponentName, Question)
    Endpoint->>McpSvc: AskComponentExpert(args)

    McpSvc->>Settings: LoadSettings()
    Settings-->>McpSvc: AppSettingsModel

    McpSvc->>FS: read API markdown for ComponentName
    McpSvc->>FS: read Samples markdown for ComponentName
    FS-->>McpSvc: apiContent, sampleContent or not_found

    alt docs_not_found
        McpSvc-->>Endpoint: "Component not found"
        Endpoint-->>Client: 404 or error text
    else docs_found
        alt AiEnabled == false
            McpSvc-->>Endpoint: raw docs (API + Samples)
            Endpoint-->>Client: markdown docs
        else AiEnabled == true
            McpSvc->>AiSvc: AskExpertAsync(systemPrompt_with_docs, Question)
            AiSvc->>Settings: LoadSettings()
            Settings-->>AiSvc: AppSettingsModel
            AiSvc->>OpenAI: chat completion request
            OpenAI-->>AiSvc: answer text
            AiSvc-->>McpSvc: expert answer
            McpSvc-->>Endpoint: answer
            Endpoint-->>Client: AI explanation
        end
    end
Loading

Class diagram for new core services and configuration model

classDiagram
    class DocsExtractorService {
        - ILogger~DocsExtractorService~ _logger
        - Dictionary~string,string~ _localizerDict
        + DocsExtractorService(ILogger_DocsExtractorService_ logger)
        + Extract(string basePath, string outputDir) void
        - CleanLocalization(string content, string className) string
        - CleanAttributeTable(string content, string outputDir) string
    }

    class GitSyncInvocable {
        - ILogger~GitSyncInvocable~ _logger
        - DocsExtractorService _extractorService
        - AppSettingsManager _settingsManager
        + GitSyncInvocable(ILogger_GitSyncInvocable_ logger, DocsExtractorService extractorService, AppSettingsManager settingsManager)
        + Invoke() Task
    }

    class McpService {
        - DocsExtractorService _extractor
        - AiIntegrationService _aiService
        - ILogger~McpService~ _logger
        - AppSettingsManager _settingsManager
        + McpService(DocsExtractorService extractor, AiIntegrationService aiService, ILogger_McpService_ logger, AppSettingsManager settingsManager)
        + GetComponentList() string
        + SearchComponentKeyword(SearchComponentArgs args) string
        + GetComponentDocs(GetComponentDocsArgs args) string
        + AskComponentExpert(AskComponentExpertArgs args) Task~string~
        - LoadComponentDocs(AppSettingsModel settings, string componentName) (string,string,string)
    }

    class SearchComponentArgs {
        + string Keyword
    }

    class GetComponentDocsArgs {
        + string ComponentName
    }

    class AskComponentExpertArgs {
        + string ComponentName
        + string Question
    }

    class AiIntegrationService {
        - HttpClient _httpClient
        - ILogger~AiIntegrationService~ _logger
        - AppSettingsManager _settingsManager
        + AiIntegrationService(HttpClient httpClient, AppSettingsManager settingsManager, ILogger_AiIntegrationService_ logger)
        + AskExpertAsync(string systemPrompt, string userMessage) Task~string~
    }

    class AppSettingsManager {
        - string _settingFilePath
        + AppSettingsManager()
        + LoadSettings() AppSettingsModel
        + SaveSettings(AppSettingsModel model) void
    }

    class AppSettingsModel {
        + string RepositoryUrl
        + string CronSchedule
        + string LocalPath
        + string OutputDir
        + string AiBaseUrl
        + string AiApiKey
        + string AiModel
        + bool AiEnabled
        + string AdminUsername
        + string AdminPassword
    }

    class ProgramStartup {
        + Main(args string[]) void
        + ConfigureServices() void
        + MapMcpAndRestEndpoints() void
    }

    DocsExtractorService <.. McpService : uses
    DocsExtractorService <.. GitSyncInvocable : uses
    AppSettingsManager <.. GitSyncInvocable : uses
    AppSettingsManager <.. McpService : uses
    AppSettingsManager <.. AiIntegrationService : uses

    McpService --> SearchComponentArgs
    McpService --> GetComponentDocsArgs
    McpService --> AskComponentExpertArgs

    AppSettingsManager --> AppSettingsModel

    ProgramStartup ..> DocsExtractorService : registers
    ProgramStartup ..> GitSyncInvocable : registers
    ProgramStartup ..> McpService : registers
    ProgramStartup ..> AiIntegrationService : registers
    ProgramStartup ..> AppSettingsManager : registers
Loading

File-Level Changes

Change Details Files
Introduce DocsExtractorService to build RAG-ready Markdown from the BootstrapBlazor repo via reflection, XML docs, Razor samples, and localization.
  • Scans compiled BootstrapBlazor assembly for IComponent implementations and emits per-component API tables as Markdown.
  • Parses XML documentation to map property summaries to component parameters, normalizing whitespace and stripping tags.
  • Reads docs.json and sample Razor/.cs files to generate component sample Markdown, inlining attribute tables from pre-generated API docs.
  • Resolves localized strings from zh-CN.json into sample code and cleans localization-specific constructs.
Services/DocsExtractorService.cs
Replace old MCP tool/service layer with McpService and a new configuration model backed by AppSettingsManager and AI integration.
  • Implements MCP tools for listing components, keyword search, raw docs retrieval, and AI-assisted Q&A over component docs.
  • Loads docs from the generated API/Samples directories with case-insensitive fallback and structured error messages.
  • Adds AppSettingsManager and AppSettingsModel to read/write GitSync, AI, and auth settings from a JSON config in a data directory.
  • Adds AiIntegrationService using Microsoft.Extensions.AI/OpenAI to call an OpenAI-compatible chat API with a system+user prompt scheme.
Services/McpService.cs
Services/AppSettingsManager.cs
Services/AiIntegrationService.cs
Add scheduled Git sync and build pipeline using Coravel and LibGit2Sharp to keep docs in sync with the upstream BootstrapBlazor repository.
  • Implements GitSyncInvocable that clones or pulls the configured BootstrapBlazor repo and runs dotnet build on the component project.
  • On successful build, triggers DocsExtractorService to regenerate RAG datasets in the configured output directory.
  • Logs progress and build output, and aborts extraction on build failure.
Services/GitSyncInvocable.cs
Introduce a Blazor Server admin dashboard with authentication, configuration UI, and manual sync trigger, plus route/layout and login handling.
  • Defines App.razor, Routes.razor, MainLayout, and RedirectToLogin for the Blazor shell, authorization flow, and shared layout.
  • Adds Index.razor dashboard showing server status and allowing manual execution of GitSyncInvocable with feedback messaging.
  • Adds Config.razor to view/edit GitSync, AI, and admin auth settings via AppSettingsManager with toasts on save.
  • Implements Login.razor page that calls /api/login via BootstrapBlazor AjaxService and redirects to the dashboard on success.
Components/App.razor
Components/Routes.razor
Components/Layout/MainLayout.razor
Components/RedirectToLogin.razor
Components/Pages/Index.razor
Components/Pages/Config.razor
Components/Pages/Login.razor
Rebuild Program.cs to host the MCP server over HTTP/SSE and optional stdio, configure localization, auth, scheduling, and REST APIs.
  • Registers new services (DocsExtractorService, GitSyncInvocable, McpService, AiIntegrationService, AppSettingsManager) and sets up BootstrapBlazor + Razor components.
  • Configures cookie authentication/authorization, localization for en-US/zh-CN, static files, antiforgery, and Coravel scheduler with cron from settings.
  • Configures MCP server builder with HTTP transport and conditional stdio transport when not in a container.
  • Maps REST endpoints for components list/search/docs/ask and login/logout APIs, and wires MapMcp('/mcp') and the Blazor root component.
Program.cs
Add Docker support and new configuration defaults while removing the old multi-project/Aspire structure and legacy files.
  • Adds Dockerfile that builds and publishes the app, installs git, sets default GitSync env vars, and exposes port 5251.
  • Adds new root appsettings.json with GitSync and AI configuration sections matching the new settings model.
  • Adds localized resource files for en-US and zh-CN UI strings and a new Chinese README alongside an overhauled English README.
  • Deletes the previous solution, Aspire AppHost/ServiceDefaults projects, old MCP server project and its services/tools, and repo metadata that are no longer used.
Dockerfile
appsettings.json
Locales/en-US.json
Locales/zh-CN.json
README.md
README.zh-CN.md
Properties/launchSettings.json
.editorconfig
.gitattributes
.gitignore
BootstrapBlazor.MCPServer.sln
LICENSE
README_zh_CN.md
src/BootstrapBlazor.MCPServer.AppHost/*
src/BootstrapBlazor.MCPServer.ServiceDefaults/*
src/BootstrapBlazor.MCPServer/*

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • Authentication and secrets handling could be hardened: AdminPassword and AiApiKey are stored in plain text (and the password has a weak default of 123456), and the login endpoint receives credentials via query string; consider moving credentials to the request body, reading secrets from environment variables or a dedicated secret store, and avoiding hard‑coded defaults.
  • The HTTP API endpoints rely on string.Contains("not found") to detect errors from McpService responses (for example in /api/components/{name}/docs and /api/components/{name}/ask), which is brittle and error‑prone; it would be more robust to have McpService return a structured result (e.g., success flag + payload + error message) and use that to drive HTTP status codes.
  • Configuration sources appear inconsistent: the README instructs users to edit appsettings.json, but the running app actually reads/writes settings from data/config.json via AppSettingsManager; aligning the documented configuration path with the real one (or merging the two mechanisms) would reduce confusion and misconfiguration.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Authentication and secrets handling could be hardened: `AdminPassword` and `AiApiKey` are stored in plain text (and the password has a weak default of `123456`), and the login endpoint receives credentials via query string; consider moving credentials to the request body, reading secrets from environment variables or a dedicated secret store, and avoiding hard‑coded defaults.
- The HTTP API endpoints rely on `string.Contains("not found")` to detect errors from `McpService` responses (for example in `/api/components/{name}/docs` and `/api/components/{name}/ask`), which is brittle and error‑prone; it would be more robust to have `McpService` return a structured result (e.g., success flag + payload + error message) and use that to drive HTTP status codes.
- Configuration sources appear inconsistent: the README instructs users to edit `appsettings.json`, but the running app actually reads/writes settings from `data/config.json` via `AppSettingsManager`; aligning the documented configuration path with the real one (or merging the two mechanisms) would reduce confusion and misconfiguration.

## Individual Comments

### Comment 1
<location path="Program.cs" line_range="109-118" />
<code_context>
+});
+
+// Authentication Endpoint for Ajax Login
+app.MapPost("/api/login", async (HttpContext context, AppSettingsManager config, string username, string password) =>
+{
+    var settings = config.LoadSettings();
+    if (username == settings.AdminUsername && password == settings.AdminPassword)
+    {
+        var claims = new List<Claim> { new Claim(ClaimTypes.Name, username) };
+        var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
+        await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
+        return Results.Ok();
+    }
+    return Results.Unauthorized();
+});
+
</code_context>
<issue_to_address>
**🚨 issue (security):** Sending credentials via query string and storing plain-text admin password is risky.

In `Login.razor`, avoid building `/api/login?username=...&password=...`; instead, post a JSON body and bind it on the server (e.g. `record LoginRequest(string Username, string Password)` as a `[FromBody]` parameter). Also, change `AppSettingsManager` so the admin password is not stored in clear text (e.g. store a hash or use a secret store), and ensure this endpoint is only served over HTTPS in production.
</issue_to_address>

### Comment 2
<location path="Program.cs" line_range="101-104" />
<code_context>
+    return Results.Text(answer, "text/markdown");
+});
+
+app.MapGet("/api/components/{name}/docs", (string name, McpService mcp) => 
+{
+    var docs = mcp.GetComponentDocs(new McpService.GetComponentDocsArgs { ComponentName = name });
+    if (docs.Contains("not found") || docs.Contains("not found")) return Results.NotFound(docs);
+    return Results.Text(docs, "text/markdown");
+});
</code_context>
<issue_to_address>
**issue:** Using string `Contains("not found")` to detect errors is brittle and duplicated.

This relies on inspecting the docs text and even duplicates the check, so any valid docs containing “not found” would incorrectly return 404. Since `McpService.GetComponentDocs` already knows whether the component exists, prefer a structured result (e.g. `{ bool Found; string Content; }`) or a custom exception that the endpoint maps to 404, instead of parsing the message text.
</issue_to_address>

### Comment 3
<location path="Services/DocsExtractorService.cs" line_range="17-26" />
<code_context>
+    private Dictionary<string, string> _localizerDict = new();
</code_context>
<issue_to_address>
**issue (bug_risk):** Shared `_localizerDict` is mutated without synchronization and may race under concurrent Extract calls.

Because `DocsExtractorService` is a singleton, concurrent `Extract` calls (e.g., scheduled vs. manual) can interleave `Clear()` and reads/writes on `_localizerDict`, causing race conditions and possible `KeyNotFoundException`s. Please either avoid shared mutable state (e.g., use a local dictionary per `Extract` or change the service lifetime) or protect `_localizerDict` with proper synchronization (lock or concurrent collection).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +109 to +118
app.MapPost("/api/login", async (HttpContext context, AppSettingsManager config, string username, string password) =>
{
var settings = config.LoadSettings();
if (username == settings.AdminUsername && password == settings.AdminPassword)
{
var claims = new List<Claim> { new Claim(ClaimTypes.Name, username) };
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
return Results.Ok();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): Sending credentials via query string and storing plain-text admin password is risky.

In Login.razor, avoid building /api/login?username=...&password=...; instead, post a JSON body and bind it on the server (e.g. record LoginRequest(string Username, string Password) as a [FromBody] parameter). Also, change AppSettingsManager so the admin password is not stored in clear text (e.g. store a hash or use a secret store), and ensure this endpoint is only served over HTTPS in production.

Comment on lines +101 to +104
app.MapGet("/api/components/{name}/docs", (string name, McpService mcp) =>
{
var docs = mcp.GetComponentDocs(new McpService.GetComponentDocsArgs { ComponentName = name });
if (docs.Contains("not found") || docs.Contains("not found")) return Results.NotFound(docs);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Using string Contains("not found") to detect errors is brittle and duplicated.

This relies on inspecting the docs text and even duplicates the check, so any valid docs containing “not found” would incorrectly return 404. Since McpService.GetComponentDocs already knows whether the component exists, prefer a structured result (e.g. { bool Found; string Content; }) or a custom exception that the endpoint maps to 404, instead of parsing the message text.

Comment on lines +17 to +26
private Dictionary<string, string> _localizerDict = new();

public DocsExtractorService(ILogger<DocsExtractorService> logger)
{
_logger = logger;
}

public void Extract(string basePath, string outputDir)
{
_logger.LogInformation("BootstrapBlazor RAG Extractor started for basePath: {BasePath}", basePath);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Shared _localizerDict is mutated without synchronization and may race under concurrent Extract calls.

Because DocsExtractorService is a singleton, concurrent Extract calls (e.g., scheduled vs. manual) can interleave Clear() and reads/writes on _localizerDict, causing race conditions and possible KeyNotFoundExceptions. Please either avoid shared mutable state (e.g., use a local dictionary per Extract or change the service lifetime) or protect _localizerDict with proper synchronization (lock or concurrent collection).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant