Install
No framework information available.
README
MSBuild Binlog MCP Server
An MCP (Model Context Protocol) server for MSBuild binary log (.binlog) analysis, providing 45 tools for AI-assisted build investigation, performance analysis, and comparison.
Bug reports
For bug reports and support please use the https://github.com/dotnet/skills repository
Notice
The tool is meant to be used solely by the plugins distributed via https://github.com/dotnet/skills, Microsoft does not guarantees and compatibility nor support for direct usages. The MCP and its implementation are considered internal technical details of the msbuild skills.
Overview
This tool parses MSBuild binary logs using the StructuredLogger library and exposes them through the MCP protocol. It enables AI assistants to investigate build failures, trace property origins, analyze performance bottlenecks, and compare builds β all through structured tool calls.
Key capabilities:
- StructuredLog Viewer search DSL with tree-format output and stable node
[id]s - Property origin tracing β find which file/target/task set a property
- Performance analysis β expensive projects, targets, and tasks
- Embedded file inspection β read and search .csproj/.props/.targets files captured during build
- Query result caching for repeated tool calls
- LRU memory management for multiple loaded binlogs
- Very large binlogs stay usable β logs too big to open are answered from an
automatically extracted subtree, or can be sliced explicitly with
binlog_extract(see Very large binlogs)
Tools (45)
Investigation (10)
| Tool | Description |
|---|---|
binlog_overview |
Build status, duration, project count, error/warning counts. Start here. |
binlog_errors |
Build errors with project, target, task, file, and line context. Returns deduplicated, complete triage text by default (agents); under --envelope returns the v1 flat JSON envelope, or the v2 grouped envelope when called with schemaVersion: 2 (see Deduplicated diagnostics) |
binlog_warnings |
Build warnings, filterable by warning code. Returns deduplicated, complete triage text by default (agents); under --envelope returns the v1 flat JSON envelope, or the v2 grouped envelope when called with schemaVersion: 2 (see Deduplicated diagnostics) |
binlog_search |
Search using StructuredLog Viewer DSL β returns hierarchical tree with node [id]s |
binlog_projects |
List all projects with status and duration |
binlog_properties |
MSBuild property values (curated defaults or filtered) |
binlog_compare_property |
Compare a property's value across all projects in one call (e.g. find which projects lack Configuration as a global property) |
binlog_items |
MSBuild items (PackageReference, Compile, etc.) |
binlog_imports |
Full import chain (.props/.targets files) |
binlog_explain_property |
Traces where a property gets its value (file/target/task that set it) |
Specialized diagnostics (7)
| Tool | Description |
|---|---|
binlog_assembly_conflicts |
MSB3277 / RAR analysis β extracts assembly version conflicts plus the RAR task parameters (AppConfigFile, Assemblies) per target framework |
binlog_diagnose |
Automated build diagnosis: failed targets, missing references, double writes, slow analyzers β plus a distinct-root-cause count (by code/file/line) for errors and warnings |
binlog_double_writes |
Detect files written by multiple tasks/targets (build correctness issue) |
binlog_compiler |
Extract compiler (Csc/Vbc/Fsc) command-line invocations, response files, and key switches |
binlog_nuget |
NuGet package restore information: resolved packages, versions, sources, duration |
binlog_assets |
Investigate embedded project.assets.json files: target frameworks, resolved package/project libraries, direct dependencies, CPM transitive pins, and a package's reverse-dependency chain |
binlog_secrets |
Scan for sensitive data (usernames, common secret formats, explicit secrets) before sharing a binlog β reports locations with a redacted preview; raw values are never returned |
Files (3)
| Tool | Description |
|---|---|
binlog_files |
List or retrieve source files embedded in the binlog (App.config, .csproj, .props, .targets) with paginated line-range reads |
binlog_search_files |
Search text or regex across all embedded source files with configurable context lines |
binlog_preprocess |
Preprocessed view of a project file with its full import chain (equivalent to msbuild /pp) |
Tree navigation (1)
| Tool | Description |
|---|---|
binlog_explore_node |
Navigate the build log tree by node ID β shows ancestor chain, node details, and children at configurable depth. Use with IDs from binlog_search to freely walk the tree. |
Targets & tasks (5)
| Tool | Description |
|---|---|
binlog_project_targets |
List targets executed in a specific project with timing and skip status |
binlog_search_targets |
Search for targets by name across all projects |
binlog_target_reasons |
Why a target ran or was skipped (inputs/outputs, dependency chain) |
binlog_tasks_in_target |
List all tasks within a specific target of a project |
binlog_task_details |
Full input parameters and output messages of a specific task execution (essential for RAR / Exec / Copy investigation) |
Evaluations (3)
| Tool | Description |
|---|---|
binlog_evaluations |
List project evaluations (project + TFM combinations) with their IDs |
binlog_evaluation_properties |
All evaluated properties for a specific project+TFM |
binlog_evaluation_global_properties |
Global properties (Configuration, Platform, TargetFramework) per evaluation β essential for diagnosing configuration mismatches |
Performance (5)
| Tool | Description |
|---|---|
binlog_expensive_projects |
Slowest projects by exclusive duration |
binlog_expensive_targets |
Slowest targets across the build |
binlog_expensive_tasks |
Slowest tasks across the build |
binlog_expensive_analyzers |
Slowest Roslyn analyzers and source generators |
binlog_project_target_times |
Target-level timing breakdown for a specific project |
Comparison (1)
| Tool | Description |
|---|---|
binlog_compare |
Diff two binlogs (properties, packages) |
Structured output & graphs (5)
| Tool | Description |
|---|---|
binlog_capabilities |
Server contract version and which tools emit the canonical JSON envelope β call first to negotiate structured output |
binlog_build_graph |
Project dependency graph with durations, project references, and the critical path (buildGraph JSON envelope) |
binlog_target_graph |
Executed-target timeline for a single project evaluation (targetGraph JSON envelope) |
binlog_incremental_analysis |
Which targets were skipped as up-to-date vs rebuilt, with inferred reason and triggering files, plus files IncrementalClean deleted that a skipped target had declared as its outputs (incrementalAnalysis JSON envelope) |
binlog_analyzer_summary |
Roslyn analyzer execution summary β total time and invocation count per analyzer assembly (analyzerSummary JSON envelope) |
Extraction (2)
| Tool | Description |
|---|---|
binlog_extract_preview |
Describe what an extraction would produce β matched projects, counts, estimated events and output size β without writing a file. Works on binlogs too large to open. |
binlog_extract |
Write a new, valid, standalone .binlog containing only the selected projects and the scaffolding needed to reach them. Works on binlogs too large to open. |
Server control (3)
| Tool | Description |
|---|---|
list_mcp_instances |
List running binlog-mcp instances on this machine (each keeps its binlog resident, ~1 GB); find stale/orphaned ones to reap |
stop_instance |
Stop a specific binlog-mcp instance by PID to free its memory (verifies the target first; never touches unrelated processes) |
stop |
Stop only this binlog-mcp server instance (upgrades, local iteration, or to free its memory) |
Prompts (2)
| Prompt | Description |
|---|---|
analyze_errors |
Step-by-step error investigation workflow |
analyze_performance |
Step-by-step performance investigation workflow |
Deduplicated diagnostics
Multi-targeted builds emit the same diagnostic once per target framework (TFM) β
sometimes twice per TFM β so a raw row-per-occurrence listing of binlog_errors /
binlog_warnings is full of duplicates: e.g. 66 raw rows that are really 19 distinct
errors. Worse, with a limit the first N raw rows can all be duplicates of a few
errors, hiding distinct errors past the cap.
To fix this for agents, the no-args (non---envelope) binlog_errors /
binlog_warnings output is always deduplicated, complete text: raw rows are
folded into one entry per distinct (code, file, line, column, message), and the
same error across multiple TFMs/projects collapses to a single line that preserves the
raw occurrence total and a per-(project, targetFramework, targetName, taskName)
breakdown. It is a field-superset of the old bare JSON array β no field is lost,
agents simply gain the dedup β so it needs no flag and is just the default. Only
binlog_errors / binlog_warnings are affected; all other tools are unchanged.
The --envelope (structured JSON) path is independent and selects its schema per
request:
| Invocation | binlog_errors / binlog_warnings output |
Audience |
|---|---|---|
| (no args) | deduplicated, complete text (triage header + one line per group) | agents |
--envelope |
v1 flat envelope { "schemaVersion": "1.0.0", "kind", "data": [flat] } |
released public extension |
--envelope, tool arg schemaVersion: 2 |
v2 grouped envelope { "schemaVersion": "2.0.0", "kind", "data": [group] } |
next-gen extension |
The --envelope errors/warnings tool defaults to schemaVersion 1 (flat); pass
schemaVersion: 2 on the call to get the grouped v2 envelope (any other value falls back
to flat v1). binlog_capabilities reports a stable schemaVersion 1.0.0 baseline
in every mode β the v2 grouped schema is request-driven (selected via schemaVersion: 2),
not advertised by capabilities.
Compact dedup text (the no-args default) β agent-facing and token-dense: a triage
header, then by framework: / by code: roll-up lines (by framework is the per-TFM occurrence
split, by code the per-code distinct-diagnostic split; shown when there are 2+ of each), then one line per distinct group (sorted by code,
then occurrence total descending, then file:line). Each line carries the full message plus a
per-(project, TFM) breakdown with target/task, so it is a field-superset of the legacy bare row:
19 distinct warnings (66 occurrences)
by framework: net10.0 Γ44, netstandard2.0 Γ22
by code: CS0219 Γ12, CS0618 Γ7
CS0219 Foo.cs(12,5): The variable x is assigned but its value is never used Γ4 [App.csproj net10.0 CoreCompile/Csc Γ2, App.csproj netstandard2.0 CoreCompile/Csc Γ2]
The by framework: / by code: lines let an agent read the failure's shape (which framework and
which codes dominate) directly, instead of re-tallying the per-issue lines. Each sums along its
natural axis: by framework totals raw occurrences (each occurrence is on one framework) and by code totals distinct diagnostics (each carries one code), so they reconcile with the header's
occurrence and distinct totals respectively. Each issue line is
{code} {file(line,col)}: {message} Γ{total} [{project} {tfm} {target}/{task} Γ{count}, ...];
every segment but the message is omitted when absent (a missing code, location, target or
task is dropped, and a lone occurrence omits its Γ1). The message is rendered in full
(whitespace-collapsed, never truncated), so the text never falls below the legacy array's
field fidelity.
Grouped JSON envelope (--envelope + schemaVersion: 2) β lossless, for structured consumers:
{
"schemaVersion": "2.0.0",
"kind": "warnings",
"data": [
{
"severity": "warning",
"code": "CS0219",
"file": "Foo.cs",
"line": 12,
"column": 5,
"message": "The variable x is assigned but its value is never used",
"occurrences": [
{ "project": "App.csproj", "targetFramework": "net10.0", "targetName": "CoreCompile", "taskName": "Csc", "count": 2 },
{ "project": "App.csproj", "targetFramework": "netstandard2.0", "targetName": "CoreCompile", "taskName": "Csc", "count": 2 }
],
"total": 4
}
]
}
Optional fields (code, file, line, column, and an occurrence's
project / targetFramework / targetName / taskName) are omitted when unknown β
a TFM, target, or task is never guessed (each is null when it cannot be resolved
losslessly).
Setup
VS Code
Add to your .vscode/mcp.json:
{
"servers": {
"binlog-mcp": {
"type": "stdio",
"command": "dotnet",
"args": ["run", "--project", "<path-to>/src/Microsoft.AITools.BinlogMcp/Microsoft.AITools.BinlogMcp.csproj"]
}
}
}
Or if installed as a global tool:
{
"servers": {
"binlog-mcp": {
"type": "stdio",
"command": "binlog-mcp"
}
}
}
Pre-loading binlogs
Pass --binlog <path> to pre-load a binlog at startup:
{
"servers": {
"binlog-mcp": {
"type": "stdio",
"command": "binlog-mcp",
"args": ["--binlog", "msbuild.binlog"]
}
}
}
Ahead-of-time indexing
The first query against a binlog pays for one streaming pass to build the index (roughly 75 s for a 450 MB log). Subsequent queries, including from later server processes, answer from that index in milliseconds.
To keep even the first query fast, build the index before any client connects:
# Build (or refresh) the index for a binlog
binlog-mcp index msbuild.binlog
# Force a rebuild even if the existing index is current
binlog-mcp index msbuild.binlog --force
# Report whether an index exists and is current
binlog-mcp index-stats msbuild.binlog
Both verbs accept several binlogs at once. index reports the elapsed time,
index path and size, and the project/error/warning counts it recorded; it is
idempotent, so re-running it on an unchanged binlog reports Up to date and
exits immediately. index-stats exits non-zero when any binlog is missing an
index or its index is stale, which makes it usable as a CI check.
The index is written to the per-user cache directory the OS provides β
%LOCALAPPDATA%\binlog-mcp\index on Windows, and $XDG_CACHE_HOME/binlog-mcp/index
or ~/.cache/binlog-mcp/index elsewhere β under a name derived from the binlog's
full path, size and last-write time. It is therefore local to the machine that
built it and is invalidated automatically if the binlog changes.
That directory is deliberately not under the system temp path, which is world-writable and so lets another user pre-create or symlink a predictable path and plant a sidecar the server would then trust. On Unix the directory is additionally required to be a real directory (not a symlink) that is owned by the current user and readable only by them; if it is not, persistence is silently disabled and every query is answered from a freshly built in-memory index instead. Correctness never depends on the cache, only speed.
Run index on the machine where the agent will query β for example
immediately after downloading the binlog artifact, before the agent session
starts β rather than on the machine that produced the binlog, since the index
does not travel with the artifact.
Very large binlogs
Opening a binlog as a navigable tree costs far more memory than the file itself β roughly 58x, so a 200 MB log needs about 11.6 GB and a 450 MB log about 26 GB. Above a 200 MB threshold the server therefore stops trying to open the whole log and answers from a subtree instead.
Most tools are answered from an automatically extracted subtree selected to suit the question: error- and warning-seeded projects for diagnostic tools, the heaviest projects for performance tools. This is transparent but never silent β every such response carries a notice naming the subtree, why it was chosen, and what fraction of the build it covers. Extracts are cached next to the index and reused for 7 days.
Nine tools are not substituted, because a subtree would let them draw a false conclusion from missing data rather than merely a narrow one:
| Tool | Why |
|---|---|
binlog_double_writes, binlog_diagnose, binlog_compare, binlog_compare_property |
Answer questions about the build as a whole; a subtree would report conflicts or absences that are artifacts of the slicing |
binlog_files, binlog_search_files, binlog_secrets, binlog_preprocess, binlog_assets |
Read the embedded source archive, which extracts do not carry β they would report "not found" for files that exist |
These fall through to the normal memory check, so on a machine with enough RAM they still work on a large log; on one without, they refuse with an explanation rather than answering from data that cannot support the question.
To slice a large log yourself β for sharing or repeat analysis β use
binlog_extract_preview and binlog_extract. Both work on binlogs too large to
open, and the result is a real .binlog that any tool (or the Structured Log
Viewer) accepts. A manual extract still has no embedded source archive, so it
cannot make binlog_files, binlog_search_files, binlog_secrets,
binlog_preprocess, or binlog_assets usable for content that existed only in
the source archive. Whole-build tools can run on a manual extract, but their
findings are scoped to the selected projects: a negative result such as βno
double writesβ is not evidence about omitted projects or the original whole
build.
Set BINLOG_MCP_AUTO_EXTRACT_MB to change the threshold, or to 0 or less to
disable substitution entirely and always attempt a full load.
Diagnostics output (agents vs. extensions)
Agents need no configuration for high-signal diagnostics: with no extra args,
binlog_errors / binlog_warnings already return deduplicated, complete triage text
(see Deduplicated diagnostics). Nothing has to be added to
mcp.json beyond the basic setup above.
Structured-JSON consumers add --envelope. That path returns the v1 flat envelope
(schemaVersion 1.0.0) by default; a consumer that wants the v2 grouped envelope passes
schemaVersion: 2 on each binlog_errors / binlog_warnings call β there is no launch
flag for grouping.
{
"servers": {
"binlog-mcp": {
"type": "stdio",
"command": "binlog-mcp",
"args": ["--envelope"]
}
}
}
Search DSL Reference
The binlog_search tool uses the native StructuredLog Viewer search syntax and returns results as a hierarchical tree with stable [id] references:
200 result(s) (matched=200)
Project MyApp.csproj β Build [123]
Target CoreCompile [124]
Task Csc [125]
Error CS0246: The type 'Foo' could not be found [126]
| Token | Example | Meaning |
|---|---|---|
| plain text | error |
Case-insensitive substring match |
"quoted" |
"error CS0246" |
Exact phrase match |
$error |
$error |
All error nodes |
$warning |
$warning |
All warning nodes |
$task |
$task Csc |
Tasks (optionally named) |
$target |
$target CoreCompile |
Targets (optionally named) |
$project |
$project MyApp |
Project nodes |
$property |
$property name=Config |
Properties with field match |
$item |
$item PackageReference |
Items by type |
$time |
$task $time |
Include timing info, sorted slowest first |
under(query) |
under($project MyApp) $error |
Scope to under a parent |
not(query) |
not($warning) |
Exclude nodes |
name=value |
name=Configuration value=Debug |
Field match |
Architecture
Microsoft.AITools.BinlogMcp.Core/ Query engine (no MCP dependency)
βββ BinlogAnalyzer.cs Binlog loading via StructuredLogger
βββ TreeHelpers.cs Ancestor traversal utilities
βββ Models/Models.cs Shared result types
βββ Queries/ One query class per tool category
βββ BuildOverviewQuery.cs
βββ DiagnosticsQuery.cs
βββ PropertiesQuery.cs
βββ PerformanceQueries.cs
βββ ...
Microsoft.AITools.BinlogMcp/ MCP server (stdio transport)
βββ Program.cs Host setup, tool registration
βββ BinlogCache.cs LRU cache with file-change detection
βββ Tools/ One tool class per MCP tool
β βββ OverviewTool.cs
β βββ SearchTool.cs
β βββ ...
βββ Prompts/ MCP prompt templates
β βββ BinlogPrompts.cs
βββ instructions/ LLM guidance
βββ server-instructions.md
The Core library has no MCP dependency and can be reused independently. The MCP layer is a thin wrapper that registers tools, manages caching, and handles the stdio transport.
Data Collection
The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described below. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft's privacy statement. You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices.
Telemetry configuration
Telemetry collection is on by default to help improve the product. It can be disabled at any time.
Disabling telemetry
Set the standard .NET CLI opt-out environment variable to 1 or true (case-insensitive) before starting the MCP server:
export DOTNET_CLI_TELEMETRY_OPTOUT=true
Windows PowerShell:
$env:DOTNET_CLI_TELEMETRY_OPTOUT = "true"
When opted out the server starts no telemetry spans, records no metric values, and performs no hashing β nothing is emitted from BinlogTelemetry at all.
What is collected
Telemetry is emitted via .NET System.Diagnostics.ActivitySource, System.Diagnostics.Metrics.Meter, and Microsoft.Extensions.Logging.ILogger, with the in-process OpenTelemetry SDK wired up at startup. By default the SDK subscribes to the binlog-mcp source, runs the BinlogTelemetryEnricher (which stamps the correlation tags described below), writes one JSON file per activity to a local folder, and ships a structured log record per emit point to Microsoft via Azure Application Insights using the Azure Monitor direct log exporter (no local collector required). Activity spans themselves are not exported to Application Insights β the log record carries the same payload and inherits the span's trace context for correlation. Set DOTNET_CLI_TELEMETRY_OPTOUT=1 to disable all destinations.
Where the data goes
The pipeline has three destinations:
| Destination | Default | How to opt out |
|---|---|---|
| Azure Application Insights (Microsoft-owned resource, ingested over HTTPS via the Azure Monitor OpenTelemetry direct log exporter; one log record per emit point) | On | DOTNET_CLI_TELEMETRY_OPTOUT=1 |
Local JSON dump β %LOCALAPPDATA%/Microsoft/BinlogMcp/telemetry/<run-guid>/N-tool_binlog_<name>.json (one file per activity span, never leaves the box) |
On | DOTNET_CLI_TELEMETRY_OPTOUT=1 or BINLOG_MCP_TELEMETRY_NO_LOCAL_LOG=1 |
| External OTLP collector (copilot-watson, Aspire Dashboard, Jaeger, ...) β receives activity spans | Off | Set OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 (and optionally OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf) to enable |
This matches the NuGet MCP server's posture: on by default, single canonical opt-out via DOTNET_CLI_TELEMETRY_OPTOUT, with finer-grained per-destination toggles available for advanced users.
On first run the server prints a one-line notice to stderr (never stdout β stdout is the MCP JSON-RPC transport) pointing here. A sentinel file at %LOCALAPPDATA%\Microsoft\BinlogMcp\.first-run-notice-<version> keeps subsequent runs silent.
Correlation tags (stamped on every span)
BinlogTelemetryEnricher automatically attaches these tags to every activity emitted by the server so a downstream collector can group them into one logical run:
| Tag | Source | Purpose |
|---|---|---|
binlog.run_guid |
Guid.NewGuid() at process startup |
Group all spans from a single MCP stdio session, even tool calls that aren't parented to each other. Also used as the folder name under LOCALAPPDATA for the JSON dump. |
binlog.custom_marker |
BINLOG_MCP_TELEMETRY_MARKER env var (truncated to 128 chars) |
Tag benchmark / eval runs so they can be filtered or grouped downstream |
binlog.build_version |
Assembly version | Distinguish runs by tool version |
binlog.is_development |
Only when DOTNET_ENVIRONMENT=Development |
Filter out local dev traffic |
binlog.run_guid is a process-scoped random GUID, not a stable user id. binlog.custom_marker is whatever the operator chooses to put in the env var β keep PII out of it.
Trace span tags (per tool call)
All custom properties carry the unique binlog. prefix so that telemetry-backend property classifications never collide with properties emitted by other products to the same table. Spans are named tool_binlog/<tool> so the root segment uniquely identifies this product for ownership inference.
| Tag | Example | Customer-influenced | Notes |
|---|---|---|---|
binlog.mcp.tool.name |
binlog_overview |
No | Fixed tool name from the server registration |
binlog.result.chars |
1234 |
No | Size of response in characters |
binlog.error.message.hash |
b71e2f4a90c83d65 |
Yes β hashed | HMAC-SHA256 of Exception.Message (only on error). Raw text never leaves the process. |
binlog.exception.type |
System.IO.FileNotFoundException |
No | Exception type name (only on error) |
Metrics
| Metric | Type | Tags | Description |
|---|---|---|---|
binlog.tool.call.count |
Counter | binlog.tool (fixed) |
Total tool calls |
binlog.tool.error.count |
Counter | binlog.tool (fixed) |
Failed tool calls |
binlog.tool.call.duration |
Histogram (ms) | binlog.tool (fixed) |
Tool call latency |
binlog.tool.result.size |
Histogram (chars) | binlog.tool (fixed) |
Response sizes |
binlog.load.count |
Counter | β | Binlog file loads |
binlog.load.duration |
Histogram (ms) | binlog.file.hash (hashed) |
Binlog load time |
binlog.cache.hit.count |
Counter | binlog.cache.type (build / query, fixed) |
Cache hits |
Privacy
- No binlog content (properties, items, build output, source files) is ever read by the telemetry layer.
- No file paths β only the filename component is considered, and it is hashed before being attached to any span or metric.
- No raw error messages β
Exception.Messageis hashed before being attached. - Hashing uses HMAC-SHA256 with a fixed per-tool key, so identical filenames within a session produce the same hash (enabling correlation) but the original value cannot be recovered.
- All data stays local to whichever OTLP listener (if any) you have configured.
Using with Aspire Dashboard
docker run --rm -p 18888:18888 -p 4318:18889 mcr.microsoft.com/dotnet/aspire-dashboard
# Open http://localhost:18888 to see traces
# Set OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
Changes from Previous Version
Adoption Assessment
A verified publisher signal is available. Assess fit from the package's supported frameworks, license and dependency graph rather than popularity alone.
Related Guides & Workflows
Learn how to evaluate, integrate, or audit this package in your .NET project.