An MCP (Model Context Protocol) server for reading and analyzing MSBuild binary log files (.binlog).
This server exposes 54 tools that allow AI assistants to analyze MSBuild binary logs, including:
- Build Info - Summaries, errors, warnings, properties, items
- Performance - Slowest targets/tasks, compiler timing, parallelism analysis, I/O bottlenecks
- Dependencies - Project graph, assembly references, NuGet packages
- Comparison - Diff two builds, incremental build analysis
- Diagnostics - Failure diagnosis with root cause detection and fix suggestions
- Debugging - Target execution reasons, skipped targets, property origins, import chains
- Evaluation - Flattened project view showing final properties, items, and imports
An interactive REPL for analyzing MSBuild binlogs, powered by AIDavid - a virtual MSBuild debugging expert inspired by David Federman's debugging methodology. Uses the GitHub Copilot SDK for AI-powered analysis.
# Start interactive session with a binlog
dotnet run --project src/BinlogMcp.Client -- ./build.binlog
# Or launch and provide the path when prompted
dotnet run --project src/BinlogMcp.ClientRequirements: Authenticate with GitHub CLI first: gh auth login
Once loaded, you'll see an interactive prompt:
▐█▌ BinlogMCP ───────────────────────────────────
🔨 Build Log Investigator
inspired by dfederm.com/debugging-msbuild
msbuild.binlog loaded
50 analysis tools ready
───────────────────────────────────────────────────
Quick start:
[1] diagnose Automated build analysis
[2] visualize timeline Gantt chart of execution
[3] visualize slowest Slowest targets chart
[4] help All commands
Or ask: "Why did this build fail?"
───────────────────────────────────────────────────
binlog>
Interactive Commands:
diagnose- Run automated AIDavid diagnosisvisualize timeline- Open Gantt chart of build execution in browservisualize slowest- Open bar chart of slowest targetsset baseline <path>- Set a baseline binlog for comparisonscompare- Compare current build with baselinevisualize comparison- Visual comparison chart (requires baseline)help- Show available commandsexit- Exit the program
Or just ask questions naturally:
- "Why did this build fail?"
- "What targets took the longest?"
- "Show me the errors"
- "What version of Newtonsoft.Json is being used?"
The client maintains conversation history, so you can ask follow-up questions that reference previous answers.
Model Configuration:
- The default model is
claude-opus-4.7, configured in the session setup.
Logging: All LLM and tool interactions are logged to binlog-client.log in the current directory.
Requires GitHub CLI authentication (gh auth login) for Copilot SDK model access.
Detects and fixes path casing mismatches in MSBuild source files. On Windows, incorrect casing in paths (e.g., ..\librarya\ instead of ..\LibraryA\) can cause cache issues with NuGet and other tools.
Exposed as MCP tools — invoke them through the Client (e.g., ask "fix the casing issues in this binlog") or any MCP host:
GetCasingMismatches— scans a binlog for paths whose casing doesn't match disk and returns the definition site (source file + property/item) for each.FixCasingMismatch— applies an XML-aware fix to a specific source file. SupportsdryRunand arepoRootsafety guard that blocks edits outside the repo.
- .NET 10 SDK (pinned via
global.jsonwithrollForward: latestFeature)
dotnet buildThe repo uses Central Package Management (Directory.Packages.props), Nerdbank.GitVersioning, ReferenceTrimmer, and TreatWarningsAsErrors. All builds must be warning-free.
dotnet run --project src/BinlogMcp# Run unit tests
dotnet test tests/BinlogMcp.TestsThe interactive client (BinlogMcp.Client) uses the GitHub Copilot SDK and requires gh auth login.
Add to your MCP client configuration (e.g., Claude Desktop):
{
"mcpServers": {
"binlog": {
"command": "dotnet",
"args": ["run", "--project", "/path/to/binlog-mcp/src/BinlogMcp"]
}
}
}| Tool | Description |
|---|---|
GetCacheStats | Gets binlog cache statistics and optionally clears the cache |
ListBinlogs | Lists all binlog files in a directory (with optional recursive search) |
GetBuildSummary | Gets build summary: result, duration, error/warning counts, projects |
GetErrors | Extracts all errors with file, line, column, code, and message |
GetWarnings | Extracts all warnings with the same detail as errors |
GetTargets | Gets target execution details sorted by duration (slowest first) |
GetTasks | Gets task execution details with aggregation by task type |
GetCriticalPath | Identifies targets on the critical path that determined build duration |
GetProjectDependencies | Gets project dependency graph, build order, and parallel execution info |
SearchBinlog | Searches binlog content for messages, errors, warnings, targets, tasks, or properties |
GetProperties | Gets MSBuild properties with optional filtering and highlights important ones |
GetItems | Gets MSBuild item groups (Compile, Reference, PackageReference, etc.) |
CompareBinlogs | Compares two binlogs showing timing changes, new/fixed errors, and target differences |
DiffProperties | Compares property values between builds - added, removed, changed properties |
DiffItems | Compares items between builds - added/removed files, package version changes |
DiffTargetExecution | Compares target execution between builds - what ran differently |
DiffImports | Compares import chains between builds - .props/.targets file changes |
GetIncrementalBuildAnalysis | Analyzes incremental build behavior - executed vs skipped targets |
GetNuGetRestoreAnalysis | Analyzes NuGet restore - packages, timing, and any restore issues |
GetAssemblyReferences | Gets assembly and project references with metadata |
GetPerformanceReport | Comprehensive performance analysis - bottlenecks, slow targets/tasks, optimization hints |
GetCompilerPerformance | Detailed C#/VB/F# compilation timing analysis |
GetParallelismAnalysis | Build parallelism efficiency - concurrent operations, sequential bottlenecks |
GetSlowOperations | Analyzes slow file I/O operations (Copy, Move, Delete, Exec) |
GetProjectPerformance | Per-project timing rollup - identify which projects are slowest |
ComparePerformance | Focused performance comparison between builds - timing regressions/improvements |
GetParallelismBlockers | Identifies what's blocking parallelism - serialization points, dependency bottlenecks |
AnalyzeTarget | Deep dive into a single target - tasks, parameters, I/O, timing breakdown |
GetFailureDiagnosis | Analyzes build failures - categorizes errors, identifies root causes, suggests fixes |
GetDuplicateFileWrites | Detects files written multiple times during build (wasteful I/O) |
GetPropertyReassignments | Finds MSBuild properties set multiple times (conflicts/overrides) |
GetRedundantOperations | Detects tasks running with identical inputs (wasted work) |
GetUnusedProjectOutputs | Finds projects built but whose outputs aren't referenced (dead code) |
GetTargetDependencyGraph | Analyzes target dependencies, finds circular and redundant deps |
GetWarningTrendsAnalysis | Categorizes warnings, suggests bulk fixes and suppressions |
GetFileAccessPatterns | Identifies frequently read files and caching opportunities |
GetSdkFrameworkMismatch | Detects SDK/framework version conflicts across projects |
GetTargetExecutionReasons | Shows why targets executed (DependsOnTargets, BeforeTargets, AfterTargets) |
GetSkippedTargets | Lists targets that were skipped and explains why |
GetPropertyOrigin | Traces property values back to their source file and location |
GetImportChain | Shows the import hierarchy (.props/.targets files) for projects |
TraceProperty | Full property evaluation trace: initial → each assignment → final |
TraceItem | Track items through build (consumed, transformed, output) |
GetItemTransforms | Show item transformations within targets |
GetMSBuildTaskCalls | Show MSBuild task invocations between projects |
GetEnvironmentVariables | Extract environment variables used during the build |
GetItemMetadata | Deep dive into item metadata (versions, HintPaths, CopyLocal settings) |
GetTargetInputsOutputs | Show target incremental build inputs/outputs for debugging re-runs |
GetTimeline | Export timeline data for external visualization tools |
GetEvaluatedProject | Shows flattened project view - final properties, items, imports after evaluation |
ListEmbeddedSourceFiles | Lists all embedded source files in the binlog's source archive (.csproj, .props, .targets, etc.) |
GetEmbeddedSourceFile | Reads the content of a specific embedded source file from the binlog |
21 high-value tools support multiple output formats via a format parameter:
| Format | Description |
|---|---|
json | Default. Structured JSON for programmatic use |
markdown | Human-readable reports with tables and bullet lists |
csv | Tabular data for spreadsheet import |
timeline | JSON format for timing visualization |
Tools supporting formats: GetBuildSummary, GetErrors, GetWarnings, GetTargets, GetTasks, GetCriticalPath, GetPerformanceReport, GetParallelismAnalysis, GetFailureDiagnosis, CompareBinlogs, DiffProperties, DiffTargetExecution, GetProjectDependencies, GetAssemblyReferences, GetProperties, GetItems, GetEvaluatedProject, GetProjectPerformance, ComparePerformance, GetParallelismBlockers, AnalyzeTarget.
BinlogMcp indexes a binlog once into a memory-mapped columnar sidecar and answers every subsequent query from that index. Analysis of very large binlogs is fast and bounded in memory.
Measured on a 1 GB binlog (80,313,218 records, 67 M messages, 64 M items):
Object tree (BinaryLog.ReadBuild) | Streaming index | |
|---|---|---|
| First analysis | ~100 GB RAM, did not complete | 56 s, 6.2 GB peak |
| Subsequent runs | full reparse | 0.10 s (memory-mapped open) |
| Full-text search of 67 M messages | not feasible | ~0.9 s |
| All 36 analysis tools, end to end | not feasible | ~35 s total, 10 GB peak |
- One streaming pass. The indexer uses
BinaryLog.ReadRecords, which deserializes each record lazily and retains nothing, instead ofBinaryLog.ReadBuild, which materializes every project, target, task, message, item and property as a live object graph. - Messages are never pre-formatted.
BuildEventArgs.Messageformats its text from a format string plus arguments on demand. Doing that for every record in the sample binlog produces about 40 billion characters - roughly 80 GB of strings, and the bulk of the original memory cost. The index stores the format string and its arguments as interned string ids instead, which is about 25x smaller, and formats a message only when it appears in a query result. - Columnar, zero-copy layout. The sidecar's on-disk layout is its in-memory layout, so opening an index is a file mapping plus a few pointer casts rather than a deserialization pass. Parent links are stored as packed integer scope handles, so resolving "which project owns this row" is an array lookup rather than a walk up a tree.
- Two-pass search. Text search first marks which of the ~3 million distinct interned strings match the query, then scans the message columns testing that bit vector - integer work only. Cost scales with distinct strings, not with the 67 million message rows.
The index is written next to the binlog as <name>.binlog.binlogidx, and is rebuilt automatically
whenever the binlog changes. Expect it to be several times the size of the binlog: it is stored
uncompressed so it can be memory-mapped and scanned without decoding. If the binlog's directory is
not writable, the sidecar falls back to a cache directory under %TEMP%.
Add *.binlogidx to .gitignore if you keep binlogs in a repository.
# Build (or rebuild) the index and print statistics
BinlogMcp index path/to/build.binlog [--force]
# Print statistics for an existing index
BinlogMcp index-stats path/to/build.binlog
# Time a full-text search
BinlogMcp index-search path/to/build.binlog "Copying file"Configuration (optional):
BINLOG_INDEX_DIR- Directory to write sidecar indexes to (default: next to the binlog)BINLOG_INDEX_PERSIST=false- Never write sidecars next to the binlog; use the temp cache insteadBINLOG_CACHE_SIZE=10- Maximum indexes to keep mapped (default: 10)BINLOG_CACHE_ENABLED=false- Disable the in-process index cacheBINLOG_MAX_RESPONSE_CHARS=4000000- Global cap on the size of any single tool response
Use GetCacheStats to view cache status or clear it.
Tools that can return large result sets accept a limit parameter, default 200 and capped at 5000.
Responses include a truncated flag and the true total count when results were cut off. A global
response-size cap acts as a backstop: a query that would still exceed it returns a short error
suggesting a smaller limit or a narrower filter, rather than a payload no client can consume.
LargeBinlogSmokeTests runs every tool against a large binlog and fails if any tool errors, takes
longer than 30 seconds, or returns more than 5 MB. It skips silently unless a binlog is supplied:
# PowerShell$env:BINLOGMCP_LARGE_BINLOG = "path\to\big.binlog"
dotnet test tests/BinlogMcp.Tests --filter "FullyQualifiedName~LargeBinlogSmokeTests"Set BINLOGMCP_SMOKE_LOG to a file path to watch per-tool timings while the run is in progress.
Once configured, you can ask your AI assistant questions like:
- "List the binlog files in C:\builds"
- "What errors are in the latest build?"
- "Which targets took the longest to execute?"
- "What's on the critical path of this build?"
To create a binlog from any MSBuild/dotnet build:
dotnet build -bl # Creates msbuild.binlog
dotnet build -bl:mybuild.binlog # Custom filename
msbuild MySolution.sln -bl # Works with msbuild too- ModelContextProtocol - MCP SDK for .NET
- MSBuild.StructuredLogger - Library for reading binlog files
MIT