Skip to main content
Published
Aug 14, 2026
2mo ago
Total Downloads
100.2K
100,224

Install

dotnet add package Microsoft.AITools.BinlogMcp --version 3.0.0

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.Message is 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

No structural changes between v2.0.1 and v3.0.0.

Adoption Assessment

A verified publisher signal is available. Assess fit from the package's supported frameworks, license and dependency graph rather than popularity alone.

No SPDX license expression is published. Review the license text and your organisation's policy before redistribution.No target framework metadata is available. Confirm compatibility in a representative project before standardising on this package.

Related Guides & Workflows

Learn how to evaluate, integrate, or audit this package in your .NET project.