AgentStack
MCP unreviewed Apache-2.0 Self-run

Wpa Mcp

mcp-tooluse-labs-wpa-mcp · by tooluse-labs

C# MCP server exposing Windows ETW (.etl) trace analyzers — CPU, scheduler waits, image loads, file/disk/mmap/network I/O, memory resources, CLR runtime — over Claude Code, Codex, and other MCP clients. Common uses: app startup, slow process creation, AV/EDR stalls, disk regressions.

No reviews yet
0 installs
8 views
0.0% view→install

Install

$ agentstack add mcp-tooluse-labs-wpa-mcp

Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Pipes remote content directly into a shell (remote code execution).

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Wpa Mcp? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

English | 简体中文 | Changelog


A C# MCP server that exposes Windows ETW (.etl) trace analyzers — CPU, scheduler waits, image loads, file / disk / mmap / network I/O, registry, memory resources, and CLR runtime events — over any MCP-compatible client (Claude Code, Claude Desktop, Codex, Cursor). Domain-neutral: works on any Windows trace; common uses include diagnosing app startup, slow process creation, AV / EDR-induced stalls, and disk-bound regressions.

> Status — PoC. Broad MCP tool surface available. Windows-only (TraceEvent kernel parsers are not portable). Apache-2.0.

> See it in action: [a real investigation](docs/CASE_STUDIES.md) — process creation 50× slower than baseline, traced to multiple EDR stacks colliding on PsSetCreateProcessNotifyRoutineEx. Reproduced independently by two LLM agents on the same trace.


Quickstart

Once installed ([one-liner below](#install)), ask the agent in plain language and it picks the matching tools:

> Load this trace: C:\path\to\trace.etl
(load_trace — first call takes 30 s – 3 min while the .etlx index is built;
 subsequent calls are instant. Returns trace metadata plus a Capabilities map
 listing which ETW keywords are present.)

> Inspect the trace and tell me what it can answer.
(inspect_trace — capability flags, quality warnings, symbol health, and
 applicable next tools)

> Diagnose high wait in PID  between  and .
(diagnose_high_wait — one window-consistent call returning candidates,
 evidence, not-concluded reasons, executed-call provenance, and next tools)

> For parent PID , what was each child's kernel-side gap?
(process_create_timing — one call gives the kernel-window distribution across
 every child of one parent)

> Drill into one of the top wait frames from the evidence: who calls it?
(wait_caller_callee — caller / callee neighbors of the focus frame)

The same summary → stacks → caller/callee pattern works across stack-oriented domains — CPU (cpu_top_functionscpu_caller_callee), file / disk / mmap I/O, image loads, CLR allocation / exception / contention, network, registry. Lifecycle and resource tools that don't fit a stack shape (memory resource snapshots, thread lifetime, process creation) have their own rows in the tables below.

For an end-to-end walkthrough — symptoms, tool chain, evidence, root cause, recommendations — see [docs/CASE_STUDIES.md](docs/CASE_STUDIES.md).


Install

One-liner (no clone, no build)

PowerShell:

iex "& { $(irm https://raw.githubusercontent.com/tooluse-labs/wpa-mcp/main/scripts/install.ps1) }"

Git Bash on Windows:

curl -fsSL https://raw.githubusercontent.com/tooluse-labs/wpa-mcp/main/scripts/install.sh | bash

Both routes do the same thing: download the latest self-contained wpa-mcp-win-x64.exe from GitHub Releases into %USERPROFILE%\.local\bin\wpa-mcp.exe, then register that executable directly with every detected MCP client (Claude Code / Codex / Claude Desktop). No local .NET runtime or SDK is required.

Forward extra flags through the one-liner:

# PowerShell — pin tag, force a single client, set custom symbol path
iex "& { $(irm https://raw.githubusercontent.com/tooluse-labs/wpa-mcp/main/scripts/install.ps1) } -Tag v0.2.16 -Client claude-desktop -SymbolPath 'SRV*C:\Symbols*https://msdl.microsoft.com/download/symbols'"
# Bash — flags after `bash -s --` go to install.ps1
curl -fsSL https://raw.githubusercontent.com/tooluse-labs/wpa-mcp/main/scripts/install.sh | bash -s -- -Tag v0.2.16

Uninstall (one-liner, symmetric)

Web-invokable, edits the same client configs in reverse. No download / cache touched.

iex "& { $(irm https://raw.githubusercontent.com/tooluse-labs/wpa-mcp/main/scripts/uninstall.ps1) }"
curl -fsSL https://raw.githubusercontent.com/tooluse-labs/wpa-mcp/main/scripts/uninstall.sh | bash

This removes the wpa-mcp entry from every detected MCP client and deletes %USERPROFILE%\.local\bin\wpa-mcp.exe. The symbol cache stays (delete %LocalAppData%\WprMcp\Symbols\ to remove it).

Requirements

  • Windows 10 / 11 (TraceEvent kernel APIs are Windows-only)
  • No .NET runtime is required for the one-line installer; releases ship a self-contained Windows executable.
  • For symbol resolution: pass -SymbolPath at install time, set _NT_SYMBOL_PATH, or use the symbol tools at runtime (see [Configuration → Symbols](#symbols)).

Install from a clone (developers)

git clone https://github.com/tooluse-labs/wpa-mcp
cd wpa-mcp
.\scripts\setup.ps1
git clone https://github.com/tooluse-labs/wpa-mcp
cd wpa-mcp
./scripts/setup.sh

Builds (Release) and registers wpa-mcp with every detected MCP client. Idempotent — re-run to update.

Common flags:

.\scripts\setup.ps1 -Client claude-desktop                    # force a specific client
.\scripts\setup.ps1 -SymbolPath "SRV*C:\Symbols*https://..." # custom _NT_SYMBOL_PATH
.\scripts\setup.ps1 -SkipBuild                                # use existing DLL

Uninstall from clone (also -CleanBuild to wipe bin/ obj/):

.\scripts\uninstall.ps1
.\scripts\uninstall.ps1 -CleanBuild
./scripts/uninstall.sh
./scripts/uninstall.sh -CleanBuild

Install manually (custom JSON / non-standard MCP client)

Build:

git clone https://github.com/tooluse-labs/wpa-mcp
cd wpa-mcp
dotnet build -c Release
# DLL: src\WprMcp\bin\Release\net8.0\WprMcp.dll

Smoke-check:

dotnet src\WprMcp\bin\Release\net8.0\WprMcp.dll --version    # prints "WprMcp 0.2.16"
dotnet test                                                   # runs the xUnit suite (needs fixtures, see CONTRIBUTING.md)

Then register with your MCP client. The command path must be absolute. For release installs, use %USERPROFILE%\.local\bin\wpa-mcp.exe; for clone builds, use dotnet plus the absolute DLL path.

Claude Code — per-project (/.mcp.json) or global (~/.claude.json):

{
  "mcpServers": {
    "wpa-mcp": {
      "command": "C:/Users/me/.local/bin/wpa-mcp.exe",
      "args": [
        "--symbol-path",
        "SRV*C:\\Symbols*https://msdl.microsoft.com/download/symbols",
        "--cache-size",
        "2"
      ]
    }
  }
}

Or via the CLI helper:

claude mcp add wpa-mcp --scope user -- C:/Users/me/.local/bin/wpa-mcp.exe --symbol-path "SRV*C:\Symbols*https://msdl.microsoft.com/download/symbols" --cache-size 2

Claude Desktop%APPDATA%\Claude\claude_desktop_config.json, same shape as above.

Codex / Cursor / other MCP-compatible clients — the server speaks stdio MCP; any client that accepts a command + args config works. Use the same JSON snippet.

Verify — after restart, the client exposes the tools as mcp__wpa-mcp__load_trace, etc. First call to load_trace on a fresh .etl takes 30 s – 3 min while the .etlx index is built (logged to stderr).


Tools

The MCP surface covers multiple ETW analysis domains, all built on the same Microsoft.Diagnostics.Tracing.TraceEvent library PerfView uses — analysis quality matches PerfView. What changes is the surface (stdio MCP + JSON instead of a Windows GUI) plus a small set of composite tools that fold multi-step PerfView workflows into a single call.

What wpa-mcp adds vs PerfView

  • Agent-driven, not UI-driven. PerfView is a Windows GUI you click through; wpa-mcp is a stdio MCP server you talk to in plain language. Same data, no UI fatigue, easy to compose into CI / regression scripts.
  • Composite tools. diagnose_window, diagnose_high_wait, diagnose_slow_startup, process_create_timing, image_load_top_gaps fold multi-step PerfView workflows into one call.
  • Capabilities-aware. Every tool's "won't return data" state maps to a single keyword bit in load_trace's Capabilities map — no more "why is this view empty" detective work.
  • Per-trace symbol recommendations. load_trace inspects modules in the trace and recommends which symbol servers to add. PerfView leaves symbol setup to the user.

Design philosophy

wpa-mcp is built to avoid misleading the model without constraining what the model can infer.

  • Orientation tools (load_trace, inspect_trace) expose capabilities, enabled-signal lists, quality gaps, recommended diagnostic flows, and symbol health up front, so the model picks the next call from real signals instead of inferring from empty results.
  • Diagnostic composites (diagnose_window, diagnose_high_wait, diagnose_slow_startup) shorten the call path but preserve the evidence chain through Evidence, NotConcluded, ExecutedToolCalls, and NextTools. They deliberately do not return a synthesized "root cause" field.
  • Per-domain row and stack tools stay close to the PerfView shape. When they return empty, the capability signals from load_trace / inspect_trace distinguish "the data isn't in this trace" from "no work matched the query".

Usage pattern

Always call load_trace first. It opens the .etl, builds (or reuses) the .etlx index, and returns a Capabilities map showing which ETW keywords are present. Every other tool's behavior depends on those keywords. The map covers:

  • CPU sampling and schedulingHasCpuSamples, HasCSwitch, HasReadyThread, HasStackWalks
  • File / disk / mmap I/O and loaderHasFileIo, HasDiskIo, HasHardFaults, HasImageLoad
  • MemoryHasVirtualAlloc, HasNtHeap, HasMemoryProcessInfo, HasHandleEvents, HasPoolEvents
  • NetworkHasNetIo, HasNetConnections
  • Kernel infrastructureHasRegistry, HasInterrupt, HasAlpc, HasThreadEvents
  • CLR runtimeHasClrGc, HasClrJit, HasClrAlloc, HasClrException, HasClrContention

The full call flow:

.etl trace
    │
    ▼
load_trace  ──►  returns Capabilities map
    │
    │  (optional: inspect_trace if capture profile / path unclear)
    ▼

  Composite  (recommended for known workflows)
  ─────────────────────────────────────────────
  diagnose_window, diagnose_slow_startup, diagnose_high_wait
  returns Evidence + NotConcluded + ExecutedToolCalls + NextTools
                                                          │
                                                          │  via NextTools
                                                          ▼

  Domain drill  (custom investigation or composite follow-up)
  ────────────────────────────────────────────────────────────
  summary  ──►  stacks  ──►  caller_callee
  top-N         top-N         focus-frame
  rows          call chains   drill

  Example: cpu_top_functions  ──►  cpu_top_stacks  ──►  cpu_caller_callee

If the capture profile or investigation path is unclear, call inspect_trace next. For common workflows, prefer composites such as diagnose_window, diagnose_high_wait, and diagnose_slow_startup before manually stitching individual calls together — their Evidence, NotConcluded, ExecutedToolCalls, and NextTools fields show what was run, what could not be concluded, and where to drill down.

Most stack-oriented groups follow the same three-tool shape: a summary (top-N flat rows), a stacks view (top-N call stacks weighted by the metric), and a caller-callee drill-down (given a focus frame, returns its caller / callee neighbors weighted by the same metric — same shape as PerfView's "Callers" / "Callees" tabs).

In the tables below, "PerfView equivalent" is the matching view in PerfView's GUI. Entries tagged [Composite] combine multiple PerfView views into one call, [Manual filter] expose raw events that PerfView's Events view shows but doesn't pre-aggregate, and [Programmatic] replace a GUI dialog with structured JSON. Most other tools are 1:1 mappings of PerfView views.

Time-window semantics

Tools that accept startUs and endUs use a half-open interval: an event is included only when startUs element). | HeapAllocStacks | | heap_alloc_caller_callee | Drill on a focus frame; metric is NT-heap bytes. | HeapAllocStacks → Callers / Callees tabs |

Network I/O

| Tool | What it does | PerfView equivalent | |---|---|---| | net_top_stacks | Top-N stacks by network bytes — TCP + UDP, IPv4 + IPv6 send/recv merged. Splits TcpBytes / UdpBytes in the response. Pairs well with wait_analysis for "high wall, low CPU" cases where the wait is on a network round-trip. Connect / Accept / Disconnect events have no byte metric — use find_marker for those. Requires the NetworkTrace keyword (NOT in default CPU profiles). | TCP/IP Stacks + UDP/IP Stacks (merged) | | net_caller_callee | Drill on a focus frame; metric is network bytes. | TCP/IP Stacks → Callers / Callees tabs | | net_connections | Per-connection lifecycle list — Connect/Accept paired with Disconnect/Reconnect by connid to give "connection X opened at T1, closed at T2, lasted T2−T1". Useful for "connect-to-disconnect latency outliers" / "is RPC slow because of connection setup". IPv4 + IPv6 merged with an IsIPv6 flag. Connections still open at trace end have TraceResidentEnd=true. | [Manual filter] — Events view, pair TcpIp/Connect with TcpIp/Disconnect by connid by hand |

Registry

| Tool | What it does | PerfView equivalent | |---|---|---| | registry_top_stacks | Top-N stacks by registry-operation count (Query / Open / Create / SetValue / EnumerateKey / etc.). Useful for "who's pounding the registry on every hot-path call". Metric is op count (no natural byte cost for registry). Requires the Registry keyword (NOT in default CPU profiles). | Registry Stacks | | registry_caller_callee | Drill on a focus frame; metric is registry op count. | Registry Stacks → Callers / Callees tabs |

ReadyThread (causality)

| Tool | What it does | PerfView equivalent | |---|---|---| | ready_thread_top_stacks | Top-N readier stacks (the code that did the SetEvent / lock release / IOCP completion that woke a blocked thread). Pair with wait_analysis: that one says "thread X blocked on Y for Z μs" — this one closes the loop with "and here's who finally unblocked it". Filter awakenedPid to focus on "who readied threads in this PID". Requires CSwitch / ReadyThread keywords (in default kernel profiles). | ReadyThread Stacks | | ready_thread_caller_callee | Drill on a focus frame; metric is ready-event count. | ReadyThread Stacks → Callers / Callees tabs |

Interrupts (DPC / ISR)

| Tool | What it does | PerfView equivalent | |---|---|---| | interrupt_top_stacks | Top-N stacks by kernel interrupt time (DPC + ISR microseconds). Surfaces hot driver routines burning CPU at high IRQL — frequent offenders are consumer-grade GPU drivers, network drivers under load, AV mini-filter callbacks. On a healthy system this should show for it). For minimal JIT-only traces, run tests/WprMcp.Tests/fixtures/Capture-JitOnly.ps1 or use JitOnlyCapture.wprp!ClrJitOnly; it enables the CLR JIT + Loader bits needed by clrjitanalysis` without GC/allocation/exception/contention runtime keywords.

| Tool | What it does | PerfView equivalent | |---|---|---| | clr_gc_analysis | Per-GC list with wall duration AND stop-the-world pause time. GCStartGCStop brackets the wall interval; GCSuspendEEStartGCRestartEEStop is the actual mutator pause (matters for background / concurrent GC, where the wall covers far more than the pause). Reports per-row Generation / Reason / PauseUs plus aggregate TotalGcCount / Gen0Count / Gen1Count / Gen2Count / TotalPauseUs. | GCStats | | clr_jit_analysis | Top-N methods by JIT compilation duration. Matches MethodJittingStartedMethodLoadVerbose on (PID, MethodID). R2R / NGen / pre-jitted methods don't fire JittingStarted, so they're invisible — which is correct for "what's the JIT cost in this trace". | JIT Stats | | `clralloctop_sta

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.