Skip to main content
Any Framework

Package Description

Published
Oct 12, 2026
8d ago
Total Downloads
100.2K
100,224

Install

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

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.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 v3.0.3 and v3.0.4.

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.