Companion to blazor-razor-mcp.md. This is the execution plan for building our own Blazor/Razor-aware MCP (Option 3 in the spec, revised).
- Language: C# (.NET)
- MCP SDK:
ModelContextProtocolv1.2.0+ (official, stdio transport) - Parsing: Roslyn (
Microsoft.CodeAnalysis.CSharp) overobj/**/*.razor.g.csgenerated files +.razor.cscode-behind - Rationale: Avoids unstable internal Razor APIs; generated files are compiler-authoritative; native .NET tooling; no Python/Go fork maintenance.
- Fallback (if Step 0 invalidates the approach): tree-sitter-razor + direct
.razorparsing.
C:\path\to\YourBlazorApp — real Blazor WASM codebase with the WidgetExample anchor case from spec §10.
- Inspect target repo structure — 3 Razor projects:
Client,User.Client,Components - Target framework: net10.0; SDKs
Microsoft.NET.Sdk.BlazorWebAssembly+Microsoft.NET.Sdk.Razor - Trigger build with
-p:EmitCompilerGeneratedFiles=true -p:CompilerGeneratedFilesOutputPath=generated— non-invasive; no source/csproj changes - Verified against
WidgetExample.razor+WidgetExamplePage.razor:__builder.OpenComponent<global::FullyQualifiedType>(seq)— trivialUSES_COMPONENTextractionAddComponentParameter(seq, nameof(Type.PropName), valueExpr)— semantic parameter resolution vianameofEventCallback.Factory.Create(this, HandlerName)— handler wiring, resolves to code-behind via SemanticModel#line (startLine,startCol)-(endLine,endCol)extended format — RoslynLocation.GetMappedLineSpan()handles natively[RouteAttribute(...)]per@pagedirective on partial class@inherits→BaseListof partial class declaration@using+_Imports.razorinherited chain all inlined (Phase 2 feature free)- Nested
RenderFragmentlambdas forChildContent/ named fragments
- Anchor case proven:
WidgetExample.razor:96OnLoginTypeChanged="HandleValueChanged"resolvable end-to-end - Decision: proceed to Step 1. Stack: .NET 10 + ModelContextProtocol SDK + Roslyn (
Microsoft.CodeAnalysis.CSharp.Workspaces+Microsoft.CodeAnalysis.Workspaces.MSBuild). No tree-sitter, no internal Razor APIs, no project mutation. - Assumption verified via
spikes/LoadProjectSpike:MSBuildWorkspace.OpenProjectAsync+GetCompilationAsyncmaterializes Razor generator outputs incompilation.SyntaxTreespurely in-memory. No files written to disk. NoEmitCompilerGeneratedFilesrequired. Measured: 1.5s project open + 3.1s compilation for User.Client (30 documents, 17 Razor trees).SemanticModelresolves handler identifiers to code-behind partial class symbols automatically (WidgetExampleBase.HandleValueChanged).Location.GetMappedLineSpan()returns source.razor:96:61— anchor case §10 proven. - Cold-start budget: extrapolated ~15–20s for full solution (3 projects, ~500 razor files). Within spec §10 criterion of <30s.
- Known MSBuildLocator gotcha: must pin
Microsoft.Build*package versions to17.11.48withExcludeAssets="runtime" PrivateAssets="all"to avoid MSBL001 runtime assembly-loading error. Csproj template inspikes/LoadProjectSpike/LoadProjectSpike.csproj.
- Setup private GIT repository —
github.com/tomasfil/blazedex(private),mainpushed - Create
BlazeDex.Mcpsolution at repo root —BlazeDex.Mcp.slnx+src/BlazeDex.Mcp/ - Target framework matching the test corpus clients —
net10.0 - Packages:
ModelContextProtocol1.2.0,Microsoft.CodeAnalysis.CSharp.Workspaces5.3.0,Microsoft.CodeAnalysis.Workspaces.MSBuild5.3.0,Microsoft.Extensions.Hosting10.0.0,Microsoft.Build.Locator1.11.2,Microsoft.Build*17.11.48 pinned (MSBL001 workaround) -
Program.cswith stdio transport +WithToolsFromAssembly()+MSBuildLocator.RegisterDefaults()+ console logging routed to stderr - Stub
[McpServerTool] Ping()returning"pong"(registered as tool nameping) - Register in
.mcp.json; smoke-tested via direct JSON-RPC (initialize→tools/list→tools/call ping→"pong"). Pending: manual verification from Claude Code.
- Load one
.csprojviaMSBuildWorkspace.OpenProjectAsync(ProjectIndexer.BuildAsync) - Walk
.razor.g.cssyntax trees, collectOpenComponent<T>invocations (CollectOpenComponentInvocations) - Resolve back to source
.razorfile + line via#linedirectives (ResolveUsageLocationwalks forward past synthesized#line hiddenblock, then usesLocation.GetMappedLineSpan()) - Implement
find_component_usages(name, limit, offset)→ rows{file, line, column, componentName, componentFullName, resolved}(ComponentTools.FindComponentUsagesAsync) - In-memory Tier 1 cache + per-call stat-fingerprint revalidation (
IndexService+Fingerprint+FingerprintBuilder) - Tool-boundary try/catch → structured error rows (every
[McpServerTool]body wraps;OperationCanceledExceptionrethrown) -
get_index_statusstub returning{state, project_path, last_built_at, last_build_ms, components_indexed, fingerprint_size, load_errors, parse_errors}; peeks cache without triggering rebuild - Success gate:
find_component_usages("WidgetExample")returnsWidgetExamplePage.razor:14:24(anchor row from spec §10) — verified viascripts/smoke-find-component.py. Cold build 5.4s, warm 6ms, post-touch rebuild 4.0s.
Runtime contract: target .csproj is configured via the BLAZEDEX_PROJECT environment variable. No path is baked into the binary — the MCP stays host-agnostic. Step 4 will replace this with solution-level multi-project loading.
-
list_razor_components(glob?, limit, offset)— file enumeration -
list_pages(glob?)— extract@page/[Route]attributes -
find_handler_bindings(method_qn)— markup sites binding to a code-behind handler -
get_component_api(component_qn)— parameters + events + injects - In-memory index (no SQLite yet); rebuild on workspace load
- Success gate: all four Phase 1 tools verified end-to-end against MyApp.User.Client.
find_component_usages("WidgetExample")hits the spec §10 anchor;find_handler_bindings("HandleValueChanged")resolves toWidgetExample.razor:96:61;get_component_api("WidgetExample")returns 2 params + 0 events + 9 injects (event-callback slice is non-load-bearing; test corpus is actively edited). Verified viascripts/smoke-list-razor-components.py,scripts/smoke-list-pages.py,scripts/smoke-find-handler-bindings.py,scripts/smoke-get-component-api.py. Cold build ~5.0s, warm call ~5ms, post-touch rebuild ~4.0s. Index totals: 65 usages, 16 components, 7 pages, 67 bindings, 16 APIs.
- Multi-project solution load via
BLAZEDEX_SOLUTION(hybrid D1 — solution mode + single-project fallback with two-stage Razor-SDK filter) - Per-project snapshot reshape (D2 Layer 1
ProjectSnapshot+ Layer 2 derived union rows) -
ProjectDependencyGraph+ reverse-dep closure — built, logged, consumed on drift; Step 4 still rebuilds full solution (partial rebuild deferred, see Findings §Step 4 deferral — partial rebuild on drift) - Codebehind triple (
.razor↔.razor.cs↔.razor.css) surfaced onRazorComponentRow+ComponentApiRow - Tier 2 on-disk SQLite cache (D3,
Microsoft.Data.Sqlite10.0.5) — per-project write-back, warm-load, corrupt-DB graceful degradation, schema v1 - Passive
FileSystemWatcherdirty hint (D4) — fast-path window, sliding 10s/3-failure recreation budget, watcher health inget_index_status,BLAZEDEX_WATCHER/BLAZEDEX_VERIFIED_CLEAN_WINDOW_MStunables
- Multi-project index totals (test corpus, solution mode):
projectCount=3, components=160, pages=37, handler bindings=578, component usages=626, component APIs=157. - Cold Roslyn build: ~10-16s full solution (spec §10 budget <30s).
- Tier 2 warm-load: 18-22 ms (target <500 ms, hard limit 2000 ms). First
list_razor_componentson warm restart: ~82 ms wall-clock vs ~11 s cold → ~134× speedup. - Tier 2 DB size after cold write-back (the test corpus): ~876-884 KB (meta + 3 projects + 271 fingerprint entries + row tables).
- Watcher fast-path hit (Phase 1 of
smoke-watcher.py): immediate 2nd tool call returns in 0.001 s vs 10.72 s cold. - Watcher touch → rebuild (Phase 2 of
smoke-watcher.py):os.utimeon.razor→ 500 ms wait → next call detects drift, runs full rebuild 8.28 s,lastBuiltAtUtcadvances ~8.8 s. - Corrupt-DB degradation (Phase 2 of
smoke-tier2.py): overwrite DB with garbage → server still serves via Tier 1 cold build,state ∈ {green, yellow},tier2_loaded=false, no crash. - Smoke suite: 11 scripts green in solution mode (
smoke-find-component,smoke-find-handler-bindings,smoke-list-razor-components,smoke-list-pages,smoke-get-component-api,smoke-freshness,smoke-invalidation,smoke-multi-project,smoke-codebehind-partners,smoke-tier2,smoke-watcher). - Anchor case at every gate:
find_handler_bindings("HandleValueChanged")→WidgetExample.razor:96:61 → WidgetExampleBase.HandleValueChangedresolves on cold Roslyn, Tier 2 warm-load, and watcher-disabled paths.
[COMPLETE 2026-04-12]
- Phase 2 shipped per
.claude/specs/main/2026-04-11-blazor-mcp-phase2-spec.md: 13 new tools, 13 POCO types, schema v3, partial rebuild, add_parameter edit tool.
Phase 1 get_component_api returns [Parameter] + [Inject] with inheritance-chain walking via INamedTypeSymbol.BaseType + name-dedupe — confirmed working cross-project (WidgetExample reaches AnonymousBaseComponent in the sibling Components project, 9 injects total, 7 inherited correctly). Phase 2 closes the remaining surface gaps:
[CascadingParameter]properties → newcascadingParametersbucket onComponentApiRow[EditorRequired]flag on Parameter rows[SupplyParameterFromQuery]flag on Parameter rows (Blazor routing metadata)[Parameter(CaptureUnmatchedValues = true)]flag on Parameter rowsRenderFragment/RenderFragment<T>split out of theparametersbucket into a dedicatedfragmentsbucket — today they land inparametersundifferentiated@code { }members beyond[Parameter]/[Inject]: public/protected methods, lifecycle overrides (OnInitializedAsync,OnParametersSet,OnAfterRender, …), public fields, nested types → newmethods/fieldsbuckets or a singleotherMembersbucket (TBD)- C#
eventdeclarations — plain .NET events (e.g.AuthStateChanged += …), NOTEventCallback - XML doc comments (
<summary>) on parameters, events, injects, methods → powers IDE-style hover - Class-level attributes:
[Authorize]/[AllowAnonymous]/[Route]/[RenderModeXxx]→ newclassAttributesbucket - Generic type constraints (
where TItem : …) on the component type signature
All doable via the same INamedTypeSymbol walk + GetAttributes() / GetMembers() filters — no new workspace plumbing, just extra collector branches in ProjectIndexer.CollectComponentApis.
Phase 1 verified working: cross-project, cross-partial, base-of-base inheritance (see Step 3 Success gate — 7/9 WidgetExample injects come from AnonymousBaseComponent in a sibling project).
- Edit operations (spec Phase 3)
- CascadingValue flow tracking (spec Phase 2)
- RenderFragment resolution (spec Phase 2)
Verified end-to-end against real the test corpus code via spikes/LoadProjectSpike. The .razor.g.cs generator output is a clean, compiler-authoritative projection that answers every spec §4/§5 node and edge type:
| Spec requirement | How it appears in generated C# | Roslyn handle |
|---|---|---|
USES_COMPONENT |
__builder.OpenComponent<global::FullyQualifiedType>(seq) |
Match generic invocation; type via SemanticModel.GetSymbolInfo |
PASSES_PARAMETER / BINDS_TO |
AddComponentParameter(seq, nameof(Type.PropName), valueExpr) |
nameof semantically resolved; value expr has exact .razor location |
INVOKES_HANDLER / WIRES_EVENTCALLBACK |
EventCallback.Factory.Create(this, HandlerName) or Create<T>(...) |
Handler symbol resolved via SemanticModel — lands on code-behind partial class automatically |
ROUTES_TO (@page) |
[global::...RouteAttribute("/...")] on partial class, one per @page |
ClassDeclarationSyntax.AttributeLists |
INHERITS_COMPONENT (@inherits) |
public partial class X : BaseName |
ClassDeclarationSyntax.BaseList |
INJECTS (@inject) |
[Inject] property on generated partial class |
Property with attribute lookup |
_Imports.razor inheritance |
Inlined at top of each .g.cs with #line back to _Imports.razor |
Free — SemanticModel already sees them |
CODEBEHIND_PARTNERS |
partial class across .razor.g.cs + .razor.cs |
Free — INamedTypeSymbol unifies partials |
RENDERS_FRAGMENT / ChildContent |
Nested (RenderFragment)((__builder2) => {...}) lambdas |
Walk nested invocations on the inner builder |
| Source-location mapping | #line (startLine,startCol)-(endLine,endCol) "source.razor" extended format |
Location.GetMappedLineSpan() — native, no manual parsing |
Anchor case §10 proven:
[spike] HandleValueChanged:
mapped source: ...WidgetExample.razor
mapped line/col: 96:61
semantic symbol: WidgetExampleBase.HandleValueChanged()
containing type: WidgetExampleBase
Measured on the real repo: User.Client project opens in 1.5s, full compilation (including 17 Razor-generated trees) in 3.1s. Extrapolated full-solution cold start: 15–20s.
MSBuildWorkspace requires MSBuildLocator.RegisterDefaults() called before any MSBuild assembly loads, plus Microsoft.Build* package references pinned with ExcludeAssets="runtime" PrivateAssets="all" to avoid error MSBL001. Verified working template:
<PackageReference Include="Microsoft.Build.Locator" Version="1.11.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="5.3.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="5.3.0" />
<PackageReference Include="Microsoft.Build" Version="17.11.48" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="Microsoft.Build.Framework" Version="17.11.48" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="17.11.48" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.11.48" ExcludeAssets="runtime" PrivateAssets="all" />Copy this into BlazeDex.Mcp.csproj in Step 1. Reference implementation: spikes/LoadProjectSpike/.
| MCP | Where | Mode | Invalidation |
|---|---|---|---|
| serena | .serena/cache/ inside the project (pickle files like document_symbols_cache_v23-06-25.pkl) |
Per-project on-disk cache, version-suffixed | Explicit project index or cache-version bump; delegates semantic work to the underlying LSP's in-memory index |
| codebase-memory-mcp | ~/.cache/codebase-memory-mcp/ SQLite DB (override via CBM_CACHE_DIR) |
Central SQLite graph, persists across restarts | Background file watcher + git-based change detection; explicit index_repository tool |
| BlazeDex (this MCP) | In-memory Tier 1 always; optional on-disk Tier 2 in Step 4. Location TBD — likely .blazedex/cache.json or SQLite alongside the solution |
Cache is allowed, but never trusted without validation — see Freshness section | Stat-based fingerprint check on every tool call. Explicit reindex tool as escape hatch. |
The problem. Roslyn's MSBuildWorkspace.GetCompilationAsync() returns a frozen snapshot. If the user (or Claude) edits a file after the snapshot, the index is silently stale until rebuilt. Unacceptable.
The rule. Every tool call — without exception, regardless of which cache tier is in play — must validate cache freshness before serving. Validation is authoritative; it is not a hint.
The mechanism. Per-call stat-based fingerprint check:
- At cache-build time, capture
fingerprint = { path → last-write-time, path → size }for every file the index depends on: all.razor,.razor.cs,.razor.css,.cs,.csproj,_Imports.razor,Directory.Build.props,global.jsonunder each loaded project directory. Store thesetof paths too, so file additions/deletions are detected. - On every tool call, before returning any row: recompute the fingerprint against the current filesystem. Cheap — ~500 files × ~100μs stat = ~50ms.
- If the current fingerprint equals the cached one → serve from the cache. Sub-ms.
- If anything differs (added, removed, mtime changed, size changed) → invalidate and rebuild from scratch before returning. Tool call blocks ~15–20s. Correct by construction.
Why stat and not FileSystemWatcher as the source of truth. Watchers silently miss events on:
- Network shares / SMB
- WSL ↔ Windows bridged paths
- VS Code "save all" bursts
- Symlinks and junctions
- Rapid programmatic edits
- Crash / suspend / resume cycles
A watcher can be layered on top purely as a fast-path hint that pre-flags the cache as dirty, so on the next call we skip straight to rebuild. But the stat check always runs after, so watcher failures cannot cause stale results. Reliability floor is the stat check.
Two-tier cache design.
- Tier 1 — in-memory (always): the index lives in the server process after the first build. Stat check on every call decides if it's still valid. Process restart = cold rebuild. This is what we implement in Step 2/3.
- Tier 2 — on-disk (Step 4, optional): after building Tier 1, serialize to
.blazedex/cache.jsonor SQLite. On MCP process start, deserialize Tier 2 into Tier 1, run the stat check, serve or rebuild. Survives process restarts. Uses the same validation path as Tier 1 — same fingerprint format, same stat check, zero new invalidation logic.
Roslyn already tolerates most of this — we just don't get in its way:
GetCompilationAsync()returns a non-nullCompilationeven when the project has compile errors — it does best-effort parsing.SyntaxTreesare walkable regardless of semantic errors.SemanticModel.GetSymbolInfo()returns a nullSymbolfor things it can't resolve, rather than throwing.- Razor source-generator crashes are isolated per input file — one broken
.razordoes not take down sibling trees.
What the MCP adds on top:
- Catch at the tool boundary. Every
[McpServerTool]method wraps its body in atry/catch. Exceptions become structured error rows in the response, never propagate to the MCP client. - Subscribe to
MSBuildWorkspace.WorkspaceFailed(viaRegisterWorkspaceFailedHandler— the event handler is obsolete in 5.3.0), log every diagnostic, never block loading. Partial projects are still usable. - Per-file degradation in the walker. If one
.razor.g.cstree throws while we're walking it, catch, log, skip that file, continue indexing the rest. resolved: falserows. When a row is recovered syntactically but semantic resolution fails (e.g.,HandleValueChangedcan't be resolved because its enclosing class has a syntax error elsewhere), we emit the row anyway with{resolved: false, reason: "..."}. Better a yellow answer than no answer.get_index_statustool. Returns{last_indexed_at, components_indexed, projects_loaded, load_errors[], parse_errors_by_file[], cache_fingerprint_size}. First thing to call when something looks wrong.reindextool. Manual escape hatch if the user suspects drift or wants to force a reload after fixing a build problem.
Response states the MCP serves:
- Green — clean build; all rows have
resolved: true;get_index_status.load_errorsis empty. - Yellow — some files have errors; affected rows come back as
resolved: falsewith a reason; other rows are fully correct;get_index_status.parse_errors_by_filelists the affected files. - Red — whole solution fails to load (e.g., missing SDK, unrestored packages);
get_index_status.load_errorsexplains; other tools return empty results without throwing.
- Chosen:
Microsoft.Data.Sqlite10.0.5 metapackage (NOT.Core— bundlese_sqlite3native viaSQLitePCLRawRID asset, no<Target>wiring) - Rejected:
System.Data.SQLite→SQLite.Interop.dllside-loads into same unmanaged resolution path asMicrosoft.Build.*(MSBL001 region); two native-DLL side-loads sharing that loader path = documented fragility - Maturity argument inverted post-2016:
Microsoft.Data.Sqliteships w/ .NET release train (10.0.5 @ 2026-03-12, MS .NET team, MIT) |System.Data.SQLitelast substantive release 2024-09-29 - Transitive closure verified via
dotnet list package --include-transitive→ zero SQLite today, clean slate - ADO.NET surface:
DbConnection/DbCommand/DbDataReader| WAL via explicitPRAGMA journal_mode=WAL| pooling opt-in (off by default) | parameterized viacommand.Parameters.AddWithValue System.Data.SQLite's only differentiator =DataSet/DataAdapter→ not used in hand-written cache layer- Ref: https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/compare
Step 2 must already implement the freshness rule end-to-end — it's not a later add-on. The minimal slice is:
- MSBuildWorkspace open + compilation + Razor tree filter
- Walker collecting
OpenComponent<T>invocations →{source_razor_path, line, component_type_qn}rows - In-memory Tier 1 cache with fingerprint capture
- Per-call stat-based fingerprint revalidation
- One tool:
find_component_usages(name)with rebuild-on-stale behavior - Tool-boundary exception catch
get_index_statusstub returning at least{last_indexed_at, fingerprint_size}
Adding freshness in Step 4 instead of Step 2 would mean shipping a slice that's knowingly-broken on any file edit. Not acceptable under the reliability rule.
ProjectDependencyGraph + reverse-dep closure are built and persisted in-memory, and drift detection logs the closure scope. Step 4 still triggers a full solution rebuild on any drift. Rationale: MSBuildWorkspace does not support in-place project reload, and fresh-workspace-per-affected-project cascades to ~whole-solution reload once transitive ProjectReference closure is required for symbol resolution (the test corpus: Components referenced by both clients). Full cold rebuild measured 10-16 s in the test corpus, within spec §10 <30 s budget. Rows are already Roslyn-independent (pre-mapped Location.GetMappedLineSpan() at index build time), so a future step can switch drift handling to true partial rebuild without schema changes.
Fire-and-forget Tier 2 write-back (Task 08) may commit only a subset of projects before the server process exits. On next warm-load, IndexSnapshot.ProjectSnapshots contains only the committed subset and BootstrapWatcher initialises FileSystemWatcher instances for that subset only. Drift in an uncommitted project's directory then never fires the watcher hint, and the stat-check still catches it (because stat IS authoritative), but the fast-path window may hide the drift for up to BLAZEDEX_VERIFIED_CLEAN_WINDOW_MS (default 250 ms).
Surfaced by smoke-watcher.py Phase 2, worked around by clearing the Tier 2 DB before the phase to force a cold build that loads all projects. Correctness is preserved — stat check still detects drift outside the fast-path window — but latency-bounded drift may be missed under pathological timing.
Fix options for a later step (Step 5+):
- (a) Block first tool-call response until write-back drains (adds <1s to the first call, trivial).
- (b) Run write-back synchronously on
IHostApplicationLifetime.ApplicationStoppingso every shutdown commits all projects. - (c) On warm-load, validate that the loaded project set matches solution discovery; mismatch → discard warm-load, fall through to cold build.
Option (c) is the safest — matches the "validate before serve" freshness invariant.
Layer 2 derived rows (solution-wide) are exposed as union ImmutableArrays across all ProjectSnapshot.ProjectSnapshots values. Scan cost matches Step 3 (no regression). If a future tool demands O(1) FQN lookup, ImmutableDictionary indexes can layer on top without disturbing the per-project Layer 1 rows.
The invalidation smoke test must run in solution mode (BLAZEDEX_SOLUTION) because it touches Card.razor in the Components project. In single-project mode (BLAZEDEX_PROJECT=User.Client.csproj) the Components project is not loaded, so drift on a file outside the tracked fingerprint does not trigger a rebuild. The script is intentionally locked to solution mode — this matches the "drift across dependent projects" semantic it is designed to test. A latency post-touch (time.sleep(0.5)) was added in Task 10 to escape the watcher fast-path window in the default 250 ms configuration.
- Watcher bootstrap race closed: cold-build Tier 2 write-back is awaited on the first tool call before the response returns; live project-set validation on warm-load via
DiscoverExpectedProjectPathsAsyncdiscards mismatched warm-loads and falls through to a cold build (matches the "validate before serve" freshness invariant). Watcher now sees the full project set on every warm-load. .mcp.jsonredacted of references to the internal test corpus prior to publish.- Build lock decoupled from the released DLL: published to
artifacts/publish/BlazeDex.Mcp/(separate frombin/Release); CI uses-c Releaseconsistently to avoid mixed-config artifacts. README.mdshipped (10 sections, 400–600 lines): TL;DR, Status / compatibility, Install, Configure, First run, Tool catalog (20 tools across Discovery / Usage / API / Quality / Edit / Diagnostics), Troubleshooting, Known limitations, License, Architecture pointer.docs/env.mdshipped: environment variable reference forBLAZEDEX_SOLUTION,BLAZEDEX_PROJECT,BLAZEDEX_WATCHER,BLAZEDEX_VERIFIED_CLEAN_WINDOW_MS, plus per-OS setup.docs/mcp.json.exampleshipped: consumer.mcp.jsontemplate withcommand: dotnet,args: [BlazeDex.Mcp.dll],env: { BLAZEDEX_SOLUTION }.LICENSEshipped: Elastic License 2.0; copyrightCopyright (c) 2026 Tomáš Filip.CHANGELOG.mdshipped: keep-a-changelog format with single## [0.1.0] - 2026-04-13entry covering Phase 1, Phase 2, the race fix, and publishing readiness.- v0.1.0 publish profile +
scripts/release.shfor producing the release zip. - CI workflow (
.github/workflows/ci.yml) enforcingdotnet format --verify-no-changes+dotnet build+dotnet teston every push and pull request. proj-verifieragent's format gate is now mandatory before any/commitinvocation.