Microsoft.AITools.BinlogMcp 3.0.5

Prefix Reserved
dotnet tool install --global Microsoft.AITools.BinlogMcp --version 3.0.5
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Microsoft.AITools.BinlogMcp --version 3.0.5
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Microsoft.AITools.BinlogMcp&version=3.0.5
                    
nuke :add-package Microsoft.AITools.BinlogMcp --version 3.0.5
                    

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. Status folds every execution of a file: an optional status of blocked (failed with no error of its own, usually a failed dependency), unknown (an execution never finished) or not-built (evaluated only) qualifies succeeded
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 Selected input parameter_names and paged output messages (10 by default; limit=0 for inputs only). 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 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. The default response reports exact scoped counts and up to 5 representative examples, rather than filling the response with a listing. limit=0 returns counts and roll-ups only. All diagnostic fields remain available through explicit canonical paging; the JSON envelope contracts are unchanged.

The --envelope (structured JSON) path is independent and selects its schema per request:

Invocation binlog_errors / binlog_warnings output Audience
(no args) scoped counts and up to 5 representative deduplicated diagnostics 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 representative groups (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. A sample is not page 1: start at offset=0 only when a full listing is needed, then follow the exact next offset with unchanged filters and limit. Later pages omit repeated roll-ups and workflow hints. The 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).

Bounded investigation

Match retrieval depth to the question. Surface questions should normally stop at the scoped counts and representative diagnostics, not inspect every compiler input or enumerate every error. Root-cause reports should distinguish observed facts, inferences, conditional fixes, and unavailable evidence.

Use binlog_search with a project/TFM-scoped query and a few results (10 by default). For source, start with binlog_search_files (path_filter, max_results=5, context_lines=1); its default is 10 matching lines with one context line per side. It pages by matching-line offset, preserving query and filters. Then read only the relevant binlog_files slice: the default is 80 lines from startLine, and the continuation names the next actually returned line, including when the byte budget shortened a requested range. File listings default to 50 entries and also report an exact next offset.

For task inputs, use binlog_task_details parameter_names="AppConfigFile,AutoUnify" limit=0 (or SourceFiles,DestinationFiles / Command). For output-only pages, parameter_names="" avoids repeating inputs. Follow the returned message offset, not a guessed page size.

Once a file is established as not embedded, stop searching for its contents in that log. RAR inputs and unification messages can support a binding-redirect hypothesis but cannot reveal unavailable XML. A post-unification "required by" list does not prove which version dependencies were compiled against. If a redundant hardcoded redirect is confirmed and automatic generation is enabled for the affected .NET Framework output, remove it and rely on generation there; keep .NET Framework config out of modern .NET reference resolution.

For a missing copy input, connect the producer's actual configuration/output, ProjectReference request, solution membership and active mapping before proposing a fix. For signing sweeps, distinguish the output's owning producer from the project sweeping a shared folder. Cached/read-only hardlinks are a possible mechanism, not a fact established by access denial alone. Prefer producer-owned signing or private staging, not mutating shared cache outputs.

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.

Bounded source-access recovery

Source-access recovery is on by default, including when BINLOG_MCP_SOURCE_RECOVERY is unset. Set it to 0, false, no, off, or disabled and restart the server to disable recovery and exhausted-busy fallback suppression. 1, true, yes, on, or enabled explicitly enable it. Values are trimmed and words are case-insensitive. Empty or whitespace-only values retain the enabled default. Unrecognized nonempty values disable recovery and produce a fixed stderr warning without echoing the supplied value. No tool arguments change. Control retains normal disk-sidecar loading inside the existing index single-flight and tree/query publication under the producing size/mtime stamps. Additional tree/query generation refusals apply only after a verified busy probe, not to a flag-on cold load whose first probe succeeds.

On a genuine read miss, after checking usable caches and sidecars, concurrent requests for one normalized path and size/last-write generation share up to four accessibility probes: the initial open and three retries, with 50/150/300 ms backoff (500 ms configured total). Waiting happens before replay/tree admission, not while holding parse gates or memory reservations. An independent two-second operation lifetime and individually cancellable waiters protect shared work. Pre-probe sidecar deserialization is shared, not repeated for every prefetch caller. Lifetime expiry after a verified busy open retains busy guidance; it does not silently turn into backend cancellation or a new tree attempt. The initial probe uses a dedicated worker; expiry does not suppress its first open or discard a successful open already in progress. Last-waiter abandonment is not retry exhaustion. Sidecar waiters must match both their captured source generation and the sidecar generation; unexpected read faults report Failed. Synchronous OS/network opens cannot be forcibly interrupted: neither number is a guaranteed wall-clock deadline. Normal backend opens are counted separately; no replay or parse is retried by this policy.

Only a verified Windows source-open sharing violation (0x80070020) is eligible. Other codes, including unverified lock violations, and sidecar/output/import failures are not inferred to be source locks. Changing size or last-write time during recovery aborts that attempt; replacements preserving both are not detected. The extraction writer is not newly retried: its source failures have backend extraction and remain overall failures, outside the recovery-eligible cohort. Automatic-extract writer failures retain this cause through the existing refusal without changing its output.

An exhausted busy source returns IsError=true in both contract modes: Error: The binlog is currently in use and cannot be read. Retry the same call after it is released. This is not evidence of corruption. If the index and tree fail for different reasons, both backend messages are preserved instead of attributing the entire call to one cause. This is not an indexing-progress response or a successful answer. Disabling recovery retains this guidance, causal diagnostics, reason-aware index failure caching, and all existing synchronization. Genuine indexing-progress contracts remain unchanged.

The canary and rollback guide describes diagnostic-first authorization, accounting, comparison queries, release gates, and unexecuted backport instructions. Production effectiveness is unmeasured.

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: only if needed, binlog_explore_node node_id=<id> for a hit's context
      binlog_task_details with recorded execution IDs, parameter_names and limit=0 for selected inputs

[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 10 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.commit, binlog.os, binlog.runtime Available assembly commit suffix and coarse platform/runtime Compare equivalent product exposure without host/user identity
binlog.recovery_enabled, binlog.canary_assignment Startup recovery switch Distinguish same-build control/recovery processes
binlog.recovery.configuration Normalized process switch: default_enabled, explicit_enabled, explicit_disabled, or invalid_disabled Distinguish a fail-closed invalid value from an intentional opt-out without exporting the value

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.

Tool logs distinguish start and terminal using binlog.event_phase and an opaque per-invocation binlog.request_id. Only the terminal increments the call counter. Outcomes are answer, not_ready, cancelled, and failure; legacy progress with IsError=false is still not_ready. Cancellations remain in original-request accounting, so older incomplete denominators are not directly comparable. Probe/work records are not extra RPCs. Durations are exported invariantly, including under comma-decimal cultures.

Recovery records include attempts, actual backoff wait, verified-busy eligibility, and whether accessibility recovered. Separate source/index/tree/extraction descriptors carry fixed reasons, stages, causal types/HRESULTs, and work-started flags without raw messages. Different backend causes remain separate. These diagnostics remain enabled when recovery is disabled. recovered=true is not an answer or a client-delivery acknowledgement; use terminal outcomes and the evaluation queries.

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
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
3.0.5 0 11/11/2026
3.0.4 12,527 10/12/2026
3.0.3 9,753 9/29/2026
3.0.2 33,205 8/26/2026
3.0.0 25,355 8/14/2026
2.0.1 23,412 7/28/2026
2.0.0 12,841 7/1/2026
1.0.0 6,005 6/16/2026