wpa-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@wpa-mcpanalyze this trace file for high CPU usage"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
wpa-mcp
An MCP (Model Context Protocol) server that turns Windows WPR
.etltraces into structured, LLM-friendly performance insights — using WPAExporter + xperf under the hood, and optionally emitting flamegraph-ready folded stacks.
wpa-mcp bridges two worlds:
Windows Performance Analyzer (WPA) — the gold standard for analyzing ETW / WPR traces, but GUI-heavy and hard to automate.
LLMs (Claude, Copilot, GPT, …) — great at reasoning across evidence, but blind to
.etlfiles.
This server exposes a small set of MCP tools so an LLM can:
Validate a trace (does it actually contain the events needed for analysis?)
Export the right WPA tables to CSV via predefined profiles
Summarize the CSVs into a compact JSON (Top N processes, hot stacks, ready-thread latency, DPC/ISR offenders, UI jank)
Render a Brendan-Gregg-style folded stack file for flamegraphs — or for the LLM to read directly
Table of contents
Architecture
+------------------+ stdio (MCP) +--------------------+
| LLM / MCP host | <--------------------> | wpa-mcp server |
| (Claude, VSCode) | | (this repo) |
+------------------+ +----------+---------+
|
subprocess |
v
+---------------------+---------------------+
| xperf.exe | wpaexporter.exe |
| (validate / stats) | (+ .wpaProfile) |
+---------------------+---------------------+
|
v
CSV tables (per profile)
|
v
summarizer -> JSON / flamegraph -> .foldedEverything that the LLM sees is structured JSON or compact folded-stack text — never raw gigabyte CSVs.
Prerequisites
Windows 10/11 (required; the analysis tools are Windows-only)
Windows Performance Toolkit (WPT) installed (ships with Windows ADK / Windows SDK)
wpaexporter.exexperf.exe
Python 3.10+
If WPT is installed to a non-default path, set:
setx WPAEXPORTER_PATH \"C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\wpaexporter.exe\"
setx XPERF_PATH \"C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\xperf.exe\"Install
Via pipx (recommended — works once published to PyPI)
pipx install wpa-mcp
wpa-mcp # starts the MCP stdio serverFrom source
git clone https://github.com/Jialong-zhong/wpr-xperf-mcp-server.git
cd wpr-xperf-mcp-server
pip install -e .
wpa-mcpCapture a trace
The server's analyses are only as good as the providers you captured. Recommended capture for the four problem classes this server targets:
# Run as Administrator
wpr -start CPU ^
-start GeneralProfile ^
-start DesktopComposition ^
-start Registry ^
-filemode
# ... reproduce the issue ...
wpr -stop C:\traces\case01.etl \"repro notes here\"WPR profile | What it adds that wpa-mcp uses |
| Sampled CPU, CSwitch, ReadyThread, StackWalk |
| Processes, images, DPC/ISR |
| DWM frame timing, Window-in-focus (UI hang evidence) |
| Registry activity (optional; useful for startup/UI hangs) |
If you skip
CPU, the most valuable analyses (hot stacks, scheduling latency) won't work —validate_tracewill tell you so.
MCP tools
Tool | Purpose | Typical caller |
| Run | LLM, always first |
| Run one WPA profile via | Advanced / targeted |
| Validate → export (by focus) → summarize. Returns one structured JSON | LLM, default entry point |
| MXA-style audio glitch analysis: long DPC/ISR buckets, dispatch-level suspects, and pipeline risk | LLM, audio crackle/skip triage |
| Executable MXA Exercise 4 method: long DPC/ISR, dispatch blocking, I/O starvation, decoder realtime checks | LLM, root-cause oriented audio triage |
| Aggregate | After |
analyze_etl input schema
{
\"etl_path\": \"C:\\traces\\case01.etl\",
\"focus\": \"cpu | latency | ui | dpc_isr | all\",
\"out_dir\": \"optional override\",
\"top_n\": 20
}analyze_etl output shape (abbreviated)
{
\"etl\": \"C:\\traces\\case01.etl\",
\"focus\": \"all\",
\"validation\": {
\"duration_sec\": 42.7,
\"has_cpu_sampling\": true,
\"has_cswitch\": true,
\"has_readythread\": true,
\"has_stacks\": true,
\"has_dpc_isr\": true,
\"has_dwm\": true,
\"warnings\": []
},
\"exports\": [\"...\\cpu\\CPU Usage (Sampled)_...csv\", \"...\"],
\"summary\": {
\"cpu_top_processes\": [{\"process\": \"chrome.exe\", \"weight_ms\": 8421.3}],
\"cpu_top_modules\": [{\"module\": \"ntdll.dll\", \"weight_ms\": 2310.0}],
\"cpu_hot_stacks\": [{\"stack\": \"ntdll!... ; app!hot_fn\", \"weight_ms\": 1240.0}],
\"ready_latency_top\": [{\"process\": \"explorer.exe\", \"tid\": 1234, \"p95_ms\": 187.0}],
\"dpc_isr_top\": [{\"driver\": \"ndis.sys\", \"total_ms\": 95.2, \"max_us\": 820}],
\"ui_focus_top\": [{\"process\": \"myapp.exe\", \"focus_ms\": 5400.0}],
\"dwm_slow_frames\": {\"count\": 38, \"p95_ms\": 41.7, \"max_ms\": 128.0}
}
}analyze_audio_glitch input schema
{
\"etl_path\": \"C:\\traces\\AudioGlitches_ThreadsAtDispatchLevel.etl\",
\"top_n\": 20,
\"dispatch_threshold_us\": 1000,
\"exclude_idle\": true
}analyze_audio_glitch output shape (abbreviated)
{
\"etl\": \"C:\\traces\\AudioGlitches_ThreadsAtDispatchLevel.etl\",
\"validation\": { \"duration_sec\": 69.5, \"has_dpc_isr\": true },
\"analysis\": {
\"mxa_criteria\": { \"long_dpc_or_isr_threshold_us\": 1000 },
\"audio_glitch_assessment\": {
\"risk\": \"medium\",
\"long_running_dpc\": { \"total_count\": 43808, \"over_threshold_count\": 0 },
\"long_running_isr\": { \"total_count\": 23838, \"over_threshold_count\": 0 },
\"audio_related_drivers_in_dpc_top\": [{ \"driver\": \"portcls.sys\", \"total_ms\": 116.7 }],
\"audio_pipeline_processes\": [{ \"process\": \"audiodg.exe\", \"weight_ms\": 244.7 }]
},
\"base_summary\": { \"dpc_isr_top\": [], \"cpu_business_processes\": [] }
}
}analyze_audio_glitch_ex4 output shape (abbreviated)
{
"etl": "C:\\traces\\AudioGlitches_ThreadsAtDispatchLevel.etl",
"validation": { "duration_sec": 69.5, "has_dpc_isr": true },
"analysis": {
"method": {
"name": "mxa_exercise4_audio_glitch_method",
"version": "1.0"
},
"checks": [
{ "id": "long_dpc_isr", "status": "pass|warn|fail" },
{ "id": "dispatch_level_blocking", "status": "pass|warn" },
{ "id": "io_delivery_pressure", "status": "pass|warn" },
{ "id": "decoder_realtime_capacity", "status": "pass|warn" }
],
"glitch_likelihood": "low|medium|high",
"most_likely_causes": ["..."],
"actions": ["..."]
}
}Built-in WPA profiles
Each profile is a .wpaProfile XML that tells wpaexporter which WPA tables + columns to dump.
Focus key | File | Tables exported |
|
| CPU Usage (Sampled) |
|
| CPU Usage (Precise), Ready Thread |
|
| Window In Focus, DWM Frame Details |
|
| DPC/ISR Duration |
Column sets are deliberately minimal to keep CSVs small and summarizer-friendly.
Analysis examples
These are end-to-end, copy-pasteable walkthroughs. Each shows the user prompt, the tool calls the LLM should make, the JSON shape you can expect, and the conclusions a well-prompted LLM should draw.
Example 1: Runaway CPU
User: "C:\traces\cpu_spike.etl — some process is pinning my CPU at 100%. Find it and tell me which function."
LLM tool calls:
// 1) validate
validate_trace({ \"etl_path\": \"C:\\traces\\cpu_spike.etl\" })
// 2) analyze focused on CPU
analyze_etl({ \"etl_path\": \"C:\\traces\\cpu_spike.etl\", \"focus\": \"cpu\", \"top_n\": 10 })
// 3) (optional) generate folded stacks for the hottest paths
render_flamegraph({
\"out_dir\": \"C:\\traces\\cpu_spike_wpa_out\\cpu\",
\"top_n\": 200,
\"min_weight_ms\": 5
})Typical summary returned:
{
\"cpu_top_processes\": [
{ \"process\": \"myapp.exe\", \"weight_ms\": 38210.0 },
{ \"process\": \"chrome.exe\", \"weight_ms\": 2100.0 }
],
\"cpu_top_modules\": [
{ \"module\": \"myapp.exe\", \"weight_ms\": 31020.0 },
{ \"module\": \"ucrtbase.dll\", \"weight_ms\": 4100.0 }
],
\"cpu_hot_stacks\": [
{ \"stack\": \"ntdll!RtlUserThreadStart;kernel32!BaseThreadInitThunk;myapp!worker_loop;myapp!compress_chunk\",
\"weight_ms\": 27800.0 }
]
}What the LLM should conclude:
Trace duration 42 s,
myapp.execonsumed ~38 s of CPU ≈ ~90% of wall time on a single core equivalent.Nearly all weight is in
myapp!compress_chunkviaworker_loop→ this is a hot loop in the app itself, not the runtime or OS.Recommend: inspect
compress_chunk, consider SIMD / chunking, and capture a second trace with-start CPU -start FileIOto see if the loop is also I/O-bound.
Example 2: UI hang / "not responding"
User: "When the user clicks Save, the window freezes for ~2 seconds. Trace at C:\traces\save_hang.etl."
LLM tool calls:
validate_trace({ \"etl_path\": \"C:\\traces\\save_hang.etl\" })
analyze_etl({ \"etl_path\": \"C:\\traces\\save_hang.etl\", \"focus\": \"ui\" })
analyze_etl({ \"etl_path\": \"C:\\traces\\save_hang.etl\", \"focus\": \"latency\" })Expected signals:
{
\"ui_focus_top\": [{ \"process\": \"myapp.exe\", \"focus_ms\": 5400 }],
\"dwm_slow_frames\": { \"count\": 42, \"p95_ms\": 58.0, \"max_ms\": 1980.0 },
\"ready_latency_top\": [
{ \"process\": \"myapp.exe\", \"tid\": 7321, \"p95_ms\": 14.0 }
]
}Reasoning rubric the LLM should apply:
dwm_slow_frames.max_ms ≈ 1980while the app is in focus → the hang is real and UI-thread-bound.ready_latency_topfor the UI thread is low (≈14 ms p95) → the thread wasn't starved for CPU; it was busy on its own work (otherwise we'd see hundreds of ms of ready-wait).Therefore the UI thread is doing synchronous work during Save. Likely culprits: synchronous disk/network I/O, lock contention, or a heavy compute path on the UI thread.
Next step: re-capture with
-start CPU -start FileIO -start DiskIOand focus on the UI thread's stacks withrender_flamegraphto see exactly what it's doing during those 2 seconds.
Contrast: if
ready_latency_tophad been, say,p95_ms: 900— then the UI thread was ready but not scheduled. That's a scheduling/contention story, not a "heavy work on UI thread" story.
Example 3: Audio/mouse glitch caused by a driver
User: "Audio crackles and the mouse skips for a split second every few minutes. C:\traces\glitch.etl."
LLM tool calls:
validate_trace({ \"etl_path\": \"C:\\traces\\glitch.etl\" })
analyze_audio_glitch_ex4({ \"etl_path\": \"C:\\traces\\glitch.etl\", \"dispatch_threshold_us\": 1000 })Expected signal:
{
\"glitch_likelihood\": \"high\",
\"checks\": [
{ \"id\": \"long_dpc_isr\", \"status\": \"fail\" },
{ \"id\": \"dispatch_level_blocking\", \"status\": \"warn\" },
{ \"id\": \"io_delivery_pressure\", \"status\": \"warn\" },
{ \"id\": \"decoder_realtime_capacity\", \"status\": \"pass\" }
],
\"top_dpc_drivers\": [
{ \"driver\": \"Netwtw10.sys\", \"total_ms\": 312.4, \"count\": 1820 },
{ \"driver\": \"ndis.sys\", \"total_ms\": 95.1, \"count\": 4300 }
]
}What the LLM should conclude:
Netwtw10.sys(Intel Wi-Fi driver) has a single DPC over 4 ms — that's well above the ~1 ms "don't cause audio glitches" rule of thumb.Correlation with symptom: Wi-Fi DPC storms typically line up with mouse/audio skips because DPCs run at elevated IRQL and block the audio/HID stack.
Recommend: update the Wi-Fi driver; if the problem persists, disable power-saving for the Wi-Fi adapter and re-capture.
Quality rules wpa-mcp's prompting guide bakes in: any driver with
max_us > 1000is suspicious,>= 500worth mentioning.
Example 3B: Exercise 4 workflow (Chinese quick-start)
用户:“C:\\traces\\audio_noise.etl 分析是否有音频噪点,并给出最可能原因和修复优先级。”
推荐调用顺序:
validate_trace({ \"etl_path\": \"C:\\traces\\audio_noise.etl\" })
analyze_audio_glitch_ex4({
\"etl_path\": \"C:\\traces\\audio_noise.etl\",
\"dispatch_threshold_us\": 1000,
\"top_n\": 20,
\"exclude_idle\": true
})建议输出模板:
结论:confirmed | suspected | not_observed
噪点风险:low | medium | high
证据:
1) long_dpc_isr:<status>,关键数值:<dpc/isr over-threshold count>
2) dispatch_level_blocking:<status>,关键模块:<top .sys suspects>
3) io_delivery_pressure:<status>,关键数值:<disk/network weight>
4) decoder_realtime_capacity:<status>,关键数值:<decoder+audiodg weight>
最可能原因(按优先级):
1) <cause #1>
2) <cause #2>
3) <cause #3>
建议动作:
1) 立即动作:<driver update/rollback, power policy>
2) 复现验证:<same scenario recapture>
3) 深挖采集:<CPU + DPC/ISR + CSwitch + ReadyThread + StackWalk>判读提示:
long_dpc_isr=fail基本可判为强噪点证据。glitch_likelihood=medium/high且前 2 项存在warn/fail时,应优先处理驱动与调度干扰。若
has_stacks=false或has_readythread=false,结论需降级为“疑似”,并建议补采集。
Example 4: Feeding folded stacks to the LLM
After analyze_etl with focus="cpu", you can ask the LLM to drill deeper:
render_flamegraph({
\"out_dir\": \"C:\\traces\\cpu_spike_wpa_out\\cpu\",
\"output_path\": \"C:\\traces\\cpu_spike.folded\",
\"top_n\": 300,
\"min_weight_ms\": 2
})Returns:
{
\"folded_file\": \"C:\\traces\\cpu_spike.folded\",
\"source_csv\": \"C:\\traces\\cpu_spike_wpa_out\\cpu\\CPU Usage (Sampled)_....csv\",
\"line_count\": 287,
\"total_weight_ms\": 39120.0,
\"preview\": \"ntdll!RtlUserThreadStart;kernel32!BaseThreadInitThunk;myapp!worker_loop;myapp!compress_chunk 27800\\nntdll!... ; myapp!parse_header 410\\n...\"
}You can now either:
Render an SVG flamegraph (requires Perl + Brendan Gregg's script):
flamegraph.pl C:\traces\cpu_spike.folded > C:\traces\cpu_spike.svgOr just let the LLM read the
preview— the folded format is already much easier for an LLM than raw CSV.
Client configuration
Claude Desktop — %APPDATA%\\Claude\\claude_desktop_config.json
{
\"mcpServers\": {
\"wpa\": {
\"command\": \"wpa-mcp\",
\"env\": {
\"WPAEXPORTER_PATH\": \"C:/Program Files (x86)/Windows Kits/10/Windows Performance Toolkit/wpaexporter.exe\",
\"XPERF_PATH\": \"C:/Program Files (x86)/Windows Kits/10/Windows Performance Toolkit/xperf.exe\"
}
}
}
}VS Code (GitHub Copilot Chat / MCP) — .vscode/mcp.json
Already included in this repo. It points at server.py in the workspace.
Custom MCP host
Any MCP client that speaks stdio works. Launch wpa-mcp (or python server.py) as a child process and send tools/list + tools/call over stdio.
Release process
This repo publishes to PyPI via GitHub Actions + PyPI trusted publishing (OIDC) — no secrets required.
One-time PyPI setup:
Claim the
wpa-mcpproject on PyPI.Add a Trusted Publisher:
Owner:
Jialong-zhongRepository:
wpr-xperf-mcp-serverWorkflow:
publish.ymlEnvironment:
pypi
Then, to ship a new version:
# bump version in pyproject.toml, commit, then:
git tag v0.2.0
git push origin v0.2.0The Publish to PyPI workflow (on tag v*) will build the sdist + wheel and publish automatically.
Troubleshooting
Symptom | Likely cause | Fix |
| WPT not installed or path wrong | Install Windows Performance Toolkit; set |
| ETL corrupted or not a WPR trace | Re-capture; ensure you ran |
| Your WPA version renamed columns | Open the corresponding |
|
| Re-capture with |
Empty |
| Re-capture with |
| The thread isn't ready-waiting → it's doing work | Run |
New tool added in | MCP host cached old tool manifest | Restart MCP host/client session and run a tools/list check |
MCP tool visibility quick check
After adding a new MCP tool (for example analyze_audio_glitch_ex4), verify in this order:
Restart the MCP client session (or restart VS Code window / host app).
Confirm the server command points to the updated
server.py.Trigger a
tools/listfrom the client and verify the tool name appears.Run one dry call with a known ETL path to confirm runtime import succeeds.
If the tool still does not appear, close all host processes that keep persistent MCP connections, then reconnect.
You can run a local preflight check before reconnecting the client:
python scripts\check_mcp_tools.pyWith ETL dry-run validation:
python scripts\check_mcp_tools.py --etl-path C:\DumpFiles\wpr_example.etlStrict gating mode (fail if validate_trace emits warnings):
python scripts\check_mcp_tools.py --etl-path C:\DumpFiles\wpr_example.etl --fail-on-validation-warningExit codes:
0: pass2: required MCP tools missing3: ETL path not found4: strict mode enabled and validation warnings found5: validation interrupted6: validation failed due to runtime/tooling error
FAQ
Q: Does this need WPA GUI installed?
No. Only wpaexporter.exe and xperf.exe (both from the Windows Performance Toolkit) are called. WPA GUI never launches.
Q: Can I use this on Linux/macOS?
The MCP server itself is pure Python. But wpaexporter / xperf only exist on Windows, so analysis must run on Windows. A common setup is: capture on Windows, copy ETL to a Windows analysis box, run wpa-mcp there.
Q: Why not parse ETL directly in Python?
ETL parsing is deep. Microsoft already ships an excellent, correct parser (wpaexporter) that understands every kernel + provider schema. Reusing it is cheaper and more accurate than reimplementing.
Q: Can I add my own WPA profile?
Yes. Drop a .wpaProfile into wpa/profiles/, add a key to PROFILE_MAP in server.py, and (optionally) a summarizer in wpa/summarizer.py.
Q: Does the LLM see the full CSV? No — by design. The LLM sees compact summary JSON plus (optionally) folded-stack text. Raw CSVs stay on disk and are referenced by path.
License
MIT. See LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Jialong-zhong/wpr-xperf-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server