Install
No framework information available.
README
MSBuild Binlog MCP Server
An MCP (Model Context Protocol) server for MSBuild binary log (.binlog) analysis, providing 47 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 (47)
Investigation (11)
| 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, scope-labeled 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, scope-labeled 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 |
Execution/evaluation inventory with status and duration; presence does not establish solution membership or configuration mapping |
binlog_solution_membership |
Parse embedded .sln / .slnx text for definitive per-project membership when parseable, reported separately from active configuration mappings and ProjectReference request contexts |
binlog_properties |
MSBuild property observations per evaluation/execution context (curated defaults or filtered), optionally scoped by exact IDs |
binlog_compare_property |
Compare recorded property values and coverage across projects. Distinguishes differences, agreement with complete observed coverage, explicit empty values, captured absence, and missing evidence |
binlog_items |
MSBuild items (PackageReference, Compile, etc.) |
binlog_imports |
Full import chain (.props/.targets files) |
binlog_explain_property |
Separates evaluation snapshots, global inputs and recorded assignments per context; no project-wide "final value" across distinct executions |
Specialized diagnostics (8)
| Tool | Description |
|---|---|
binlog_assembly_conflicts |
MSB3277 / RAR analysis β extracts conflicts and RAR parameters per target framework; a non-matching TFM-qualified selector reports recorded RAR framework candidates instead of claiming no invocation exists; when an empty unified path, AppConfigFile, and cross-TFM evidence coincide, adds a bounded set of deterministic non-verdict hypotheses with facts for/against and separating checks |
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 recorded duplicate file writes and their sources, plus directories shared by completed Copy operations. Other writers are outside that directory-sharing analysis; use binlog_path_activity for a specific path |
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_reference_provenance |
Correlate the asset selected in project.assets.json for one consumer TFM with RAR reference items, resolved paths, FusionName/assembly versions, requested versions, and dependency introducers. Returns a paged JSON evidence envelope. |
binlog_path_activity |
Trace a file or directory across Copy, compiler, signing, Exec, and custom-task parameters, including executed/skipped state and explicit observability limits. Returns a paged JSON evidence envelope. |
When an individual provenance result is too large, these tools visibly clip text and add
data.recovery entries keyed by the original finding/activity offset, including when that
entry must be skipped. Reuse the original binlog_file with each recovery entry's tool
and arguments: task evidence uses an integer task_id, an uncut project_path for exact
file identity, a basename/TFM project selector (the TFM portion only, without its RID),
and recorded project_id, evaluation_id, and target_id execution identities.
Task details preserve the public case-insensitive substring behavior of project, including
relative path-shaped selectors such as src\App\App.csproj. The separate project_path
argument uses normalized exact identity and preserves POSIX/unknown casing. When both
filters are supplied, both must match; project_path is never suffixed with a TFM.
The optional execution IDs further constrain both task-ID and name lookup, distinguishing
repeated executions even when their path and TFM are identical. Omitted IDs preserve the
legacy first-match behavior; an explicit ID (including zero or a sentinel) is an exact filter.
Assets evidence uses binlog_files with filePath and line paging. Oversized or unavailable
selectors instead produce discovery calls (binlog_projects or an embedded-file listing).
Composite evidence nodeId values are labels, not task/node tool arguments. Recovery
selects the owning task execution, not an exact nested node: task details may clip or omit
nested items, and clipped selectors must not be reused. Missing execution identities produce
discovery calls rather than potentially selecting another execution.
If compaction also omits unresolved coverage dimensions, data.coverageTruncation reports
their original total, actual returned count, and explicit omitted count in reason,
independently of finding/activity paging and the main truncation notice. This metadata
also accompanies empty or skipped-result pages. Coverage-only truncation uses the same
notice shape at the envelope's truncation field. Retrying with limit=1 and the original
uncut arguments may leave more room, but unresolved coverage is not paged: narrowing
changes scope and cannot guarantee recovery of every omitted dimension. Omitted
uncertainty must not be treated as resolved.
Reference provenance keeps project and evaluation identities case-sensitive unless their
paths establish Windows/UNC semantics. Embedded assets files use the same identity rule:
normalized drive/UNC case variants are one file, while POSIX and unknown path casing remains
significant. Deduplication retains the first recorded spelling for evidence and file recovery,
so aliases do not multiply selected assets, dependency chains, or page totals.
Rooted or separator-containing project selectors are
full path identities and never fall back to suffix matching; only bare project names allow
substring matching. Package correlation considers every recorded
package folder; without package-owner metadata, a resolution must match an assets-derived
candidate path. Package paths also preserve POSIX/unknown casing, even when package ownership
is recorded; only established Windows/UNC paths permit case folding. Equivalent platform
versions such as 26, 26.0, and 026.00 are one fact, not a conflict: numeric components
are canonicalized before trailing zero components are removed.
Missing or conflicting platform-version facts leave versioned targets as
candidates. Framework-only queries correlate RID-less executions, with RID-specific RAR
evidence reported separately as candidates rather than attached to neutral assets.
RAR observations with unresolved platform applicability also remain partial candidates,
with finding-level exclusions. Observations without a recorded framework are likewise partial
candidates and cannot supply the resolved path or identity of a selected asset.
RAR outputs are classified by their task parameter, using the recorded parameter name ahead
of a renamed destination item/property. Only recognized path-bearing collections can supply
resolutions; redirect suggestions and unresolved conflict identities remain partial diagnostic
evidence and never become resolved files or requested references.
RAR requested-reference inputs likewise require an authoritative Assemblies or
AssemblyFiles parameter. Configuration, cache/state files, search candidates/paths,
framework tables and control values do not become requested references merely because
they contain an assembly-like filename. Nested values retain their owning parameter's
classification.
Localized structured conflict headings and their nested details, plus
UnresolvedAssemblyConflicts' logMessage/logMessageDetails metadata, retain the
recorded conflicting identities as diagnostics without proving a selected asset or path.
Structured Results references combine the parent assembly
identity with its child resolved-path message using paired localized resource templates
and an English fallback, including logs without OutputItems. Traditional Chinese templates
are independent of older StructuredLogger resource support; recognized result pairs are
accepted even when those versions incorrectly place them under Inputs. Relative project references
retain / as their base when the consuming project is at the filesystem root.
Path activity recognizes quoted command switches and preserves commas in path operands
unless a recognized option defines a list. A bare slash token such as /nologo is not
evidence of filesystem activity; ambiguous extensionless root operands need path context
(such as -- or an explicit path-valued option) to be reported. A potentially matching
ambiguous operand is reported as an unresolved coverage dimension, not as a path observation.
Exact project-path selectors preserve POSIX/unknown casing; project-name substrings still
work. Known /p:name=value and /property:name=value assignments expose nested path values.
Unknown slash-colon tokens such as /artifact:file.dll remain ambiguous rather than being
rewritten as relative filenames. In Exec, adjacent unquoted & and && delimit commands; quoted
ampersands remain part of their path operands. In known POSIX Exec commands, shell escapes
are decoded before path filtering, so /tmp/My\ File.dll identifies /tmp/My File.dll, not
partial operands. Known Windows Exec commands likewise decode unquoted caret escapes:
C:\repo\out\a^&b.dll identifies C:\repo\out\a&b.dll, while a caret inside quotes is literal.
Structured values and direct compiler/custom-task arguments do not acquire shell escape rules.
If an Exec command's platform is unknown and shell escaping changes a
potentially matching operand, the query reports uncertainty instead of guessing a path.
Root-level projects retain / as the base for relative command and structured task paths.
Custom tasks do not inherit a project-relative base merely because they belong to a project.
When a relative path-shaped custom value cannot be matched without such a base, coverage
includes path-resolution uncertainty, even for directory queries whose text does not appear
in the value. Literal relative-text matches remain observable; an unknown base does not prove
that the requested absolute file or directory was unused.
Exec tokenization also separates file redirections (<, >, >>, including recorded
descriptor prefixes) from descriptor duplication such as 2>&1. A redirection operand
does not consume a pending tool option; command boundaries reset the option state.
POSIX quoted newlines remain literal, while Windows physical newlines end commands.
Escaped line continuations remain in the same command; POSIX continuations also join
multi-character operators and expansion prefixes before those are classified. Windows uses
double quotes for grouping: apostrophes and semicolons are literal, not POSIX quoting or
command separators. POSIX single/double quoting and protected operators retain their
own rules. Unknown-platform disagreements are explicitly partial, including differences
in quoting and command boundaries, not just escaping.
Grouping delimiters are separate from path operands: POSIX subshells and Windows command
groups (including ordinary if bodies) retain paths adjacent to ), nested groups,
and group redirections. Group boundaries reset pending option state. Windows recognizes
opening groups at command/control-prefix positions, not inside ordinary filename operands;
quoted or escaped parentheses remain literal, as do direct compiler/custom-task arguments.
Unknown-platform disagreements between grouping and literal filenames remain partial.
In POSIX Exec commands, an unquoted, unescaped # at the start of a word begins a comment
through the next newline. Comment text is not path evidence; the newline still resets
command state, even after a backslash inside the comment. Quoted/escaped hashes and hashes
inside existing words remain literal. Unknown-platform comment disagreements are uncertain;
Windows and direct compiler/custom-task arguments do not acquire POSIX comment rules.
Exec logs command source, not expanded arguments. POSIX unquoted pathname patterns (*,
?, and bracket expressions) and active tilde prefixes are unresolved because the producing
filesystem, home directories, and account database are unavailable. Quoting and escaping
protect literal patterns; assignment/declaration values do not acquire pathname expansion.
Windows batch environment/argument expansion (%NAME%, %1, etc.), for substitutions,
and percent re-expansion through call are also unresolved. Carets and quotes do not protect
percent expansion. Doubled %% is decoded as a literal percent outside those re-expansion
contexts. Bang-containing Windows commands remain uncertain because delayed-expansion state
is not established. These rules are not applied to direct compiler/custom-task arguments.
Operands are buffered until a complete command unit is available. Unmatched POSIX quotes or parentheses, incomplete redirections/pipelines, and unsupported syntax or expansions suppress observations from the affected unit, not just its final token. Semicolon-separated commands on the same line and multi-line groups/pipelines share that unit. Earlier completed top-level lines remain observable, with partial coverage for the unparsed remainder.
This is not a complete shell interpreter. Here-documents, here-strings and unsupported
redirection dialects, POSIX command/process substitutions (including backticks), parameter
expansions (${name}, ${name:-default}, $name, and special parameters), arithmetic forms,
and unsupported control structures leave the unparsed remainder explicitly uncertain rather
than interpreting it as filesystem operands or simple groups. Coverage remains partial even
when the requested path is absent from that text: expansions can produce unrecorded values.
Unquoted dollar-prefixed ANSI-C/localized quoting ($'...' and $"...") is likewise
dialect-dependent and unresolved, not a path with a literal $ prefix. POSIX reserved words,
including in, and dialect-specific introducers such as select, function, [[, and
coproc are not treated as executable names in reserved-word positions. Quoted/escaped
spellings and ordinary argument positions retain their literal meaning.
Escaped and single-quoted POSIX expansion characters remain literal. Unknown-platform Exec
commands consider both shell interpretations; the reader's shell, environment, and filesystem
are never used to evaluate expressions. Redirection mentions, like other Exec
arguments, do not prove that a process actually read or wrote the file. Direct compiler
and custom-task arguments do not acquire these Exec shell rules.
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. project remains a substring/TFM selector; optional project_path supplies exact file identity, and project_id/evaluation_id/target_id distinguish repeated executions. |
Evaluations (3)
| Tool | Description |
|---|---|
binlog_evaluations |
List evaluation snapshots and linked execution IDs, including TFM, Configuration, Platform and recorded restore/build context |
binlog_evaluation_properties |
Evaluated property dictionary for an exact evaluation_id, optionally constrained by project_id |
binlog_evaluation_global_properties |
Global-property dictionary for an exact evaluation, optionally selecting one execution with project_id; conflicting unscoped executions require disambiguation |
Property inspection preserves evaluation and execution identity rather than combining
restore and build observations into one apparent final value. binlog_properties,
binlog_explain_property, binlog_compare_property and binlog_evaluations accept
optional exact project_id and evaluation_id filters, intersected with the existing
substring/TFM project selector. A project_id is the recorded project execution ID,
also accepted by task details, not the project filename or a composite node label.
Evaluation listings expose every linked execution rather than assuming one per evaluation.
An evaluation context with project_id=none has no matching execution selected by this
query; it does not prove the project never executed.
Restore/build labels require recorded markers or target evidence; absent markers alone
leave context unknown. Evaluation snapshots and execution assignments remain distinct.
Comparison coverage distinguishes nonempty observed values from execution coverage. One observed value among several projects does not establish agreement; explicit empty values, absence from a captured evaluation and missing evidence are reported separately. Neither an inventory entry nor a global Configuration value proves solution membership or selected configuration mapping. A project can build successfully through a reference without being listed in the selected solution. Establish membership/mapping separately from that solution's contents and configuration evidence; otherwise they remain unknown. These tools do not add a solution-membership parser.
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 holds substantial, workload-dependent retained memory for its binlog β an observed footprint, not a configured cap); 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: the active
project/code/category filters, matched distinct and occurrence counts, 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:
Scope: queried log; no project, code or category filters.
Matched: 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]
All counts and roll-ups describe the matched scope, not a separate whole-build total.
A TFM-qualified project selector narrows that scope as before; warnings also accept
code, and both tools accept category. Even zero matches retain the scope so a
filtered empty result cannot be mistaken for a clean build. Unfiltered counts describe
the queried log, which may itself be an extract. Omitted offset selects summary/sample
mode; explicit offset selects canonical traversal. The existing omission footer
distinguishes returned groups from matched groups. Filter values are escaped and long
values visibly clipped within the response budget; JSON envelope shapes are unchanged.
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). Messages are whitespace-collapsed.
Oversized messages and breakdowns may be visibly clipped to preserve the inline response
budget; narrow or page the query rather than treating omitted evidence as absent.
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.
Eleven 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, binlog_path_activity |
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_preprocess, binlog_assets, binlog_reference_provenance, binlog_solution_membership |
Read the embedded source archive, which extracts do not carry β they would report "not found" or unknown membership 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_preprocess, binlog_assets, binlog_reference_provenance, or binlog_solution_membership 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:
1 result(s)
Project Distrib.csproj (2 ms) [1]
Target SignCopiedFiles (1 ms) [1]
Task Exec [1]
Message ERROR: File 'D:\out\StorageLibrary.dll' test-sign failed! [in 1]
Message ERROR: File 'D:\out\Contracts.dll' test-sign failed! [in 1]
Next: binlog_explore_node node_id=<id> to walk a hit's ancestors and children
binlog_task_details task_id=<id> project=<project> for a task's parameters and full output
binlog_diagnose for an automated root-cause report of the whole build
[123] is the node's own id; [in 123] marks a node that has no id of its own
(a message, for instance) and names the nearest node that does. Either form is
accepted by binlog_explore_node.
A match on a Target or Task is rendered together with the child messages
that carry its evidence β the two ERROR: lines above are under
SignCopiedFiles, not on it. Control that with:
| Parameter | Default | Meaning |
|---|---|---|
context |
2 |
Levels of children to render under each match (0 = the match alone, max 5) |
contextLines |
20 |
Max child lines rendered per match |
maxResults |
200 |
Matches per page |
offset |
0 |
Start of the page β the footer reports the next offset when results are truncated |
| 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.