Conversation
Reviewer's GuideReplaces 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 extractionsequenceDiagram
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
Sequence diagram for AskComponentExpert via MCP/REST with optional AIsequenceDiagram
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
Class diagram for new core services and configuration modelclassDiagram
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
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 3 issues, and left some high level feedback:
- Authentication and secrets handling could be hardened:
AdminPasswordandAiApiKeyare stored in plain text (and the password has a weak default of123456), 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 fromMcpServiceresponses (for example in/api/components/{name}/docsand/api/components/{name}/ask), which is brittle and error‑prone; it would be more robust to haveMcpServicereturn 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 fromdata/config.jsonviaAppSettingsManager; 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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| 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(); | ||
| } |
There was a problem hiding this comment.
🚨 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.
| 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); |
There was a problem hiding this comment.
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.
| 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); |
There was a problem hiding this comment.
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).
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:
Enhancements:
Build: