Skip to main content
Glama
scarletfantasy

pix-mcp

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
PIX_MCP_PIX_DIRNoA PIX install directory to search instead of the default locations.
PIX_MCP_CACHE_DIRNoWhere extracted artifacts live.%TEMP%\pix-mcp-cache
PIX_MCP_BRIDGE_PATHNoFull path to pixbridge.exe, skipping discovery.
PIX_MCP_MAX_SESSIONSNoOpen captures kept before the least-recently-used is evicted.8
PIX_MCP_PIXTOOL_PATHNoFull path to a specific pixtool.exe, skipping discovery.
PIX_MCP_XBOX_PIX_DIRNoGDK bin directory containing Microsoft.PIX.Automation.dll.
PIX_MCP_BRIDGE_DISABLEDNoSet to 1 to leave the bridge alone entirely.
PIX_MCP_TIMEOUT_SECONDSNoPer-invocation pixtool timeout.1800
PIX_MCP_BRIDGE_AUTOBUILDNoSet to 1 to build the bridge on first use if it is missing.
PIX_MCP_XBOX_BRIDGE_PATHNoFull path to pixbridge-xbox.exe.
PIX_MCP_XBOX_BRIDGE_DISABLEDNoSet to 1 to disable the Xbox backend.
PIX_MCP_BRIDGE_TIMEOUT_SECONDSNoPer-request bridge timeout.300

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
pix_get_server_statusA

Report PIX installs, pixtool health, limits, and open sessions.

pix_open_captureC

Register a capture and return a capture_id plus a compact overview.

pix_list_open_capturesA

List open capture sessions with their ids, paths, and cached artifacts.

pix_get_capture_overviewB

Return frame, event, and category statistics for an open GPU capture.

pix_discover_queuesA

Find which named contexts (command queues) a capture exposes.

With the analysis bridge available this is the real queue table, read from PIX's engine. Without it, pixtool has no command that lists queues, so each candidate name has to be tried and a miss still costs a full capture open; results are cached per capture, so asking twice is free.

pix_close_captureB

Close a capture session, optionally deleting its cached artifacts.

pix_clear_capture_cacheA

Delete cached artifacts for a capture so the next query re-extracts them.

pix_list_eventsC

List capture events, optionally restricted to one parent marker or category.

pix_search_eventsA

Search events by name (substring, glob, or regex) with optional filters.

pix_get_eventB

Return one event by queue_id or global_id, with its parent chain.

pix_get_event_treeC

Return the marker hierarchy as a bounded tree.

pix_export_event_listA

Write a full event-list CSV to a caller-chosen path (pixtool save-event-list).

pix_list_countersB

List GPU counters available for this capture, grouped and searchable.

pix_suggest_counter_patternB

Show the regexes that select a counter exactly, or its whole family.

pix_get_counter_hotspotsA

Rank events by a counter to find the most expensive GPU work.

With nothing specified the capture's best per-event duration counter is picked automatically, which is the usual starting point for a performance investigation.

pix_collect_countersB

Replay the capture collecting counters, returning per-event counter values.

counters takes exact names from pix_list_counters and escapes them. Use counter_regexes to match families, remembering that every counter matched is another replay pass over the capture.

pix_save_high_frequency_countersC

Collect High Frequency Counters (a GPU-wide time series) and return sample rows.

pix_save_screenshotB

Save the capture's frame screenshot as a PNG.

pix_save_resourceA

Save a bound resource (render target or depth buffer) from a draw call.

The file format follows the destination extension. Without global_id or marker, pixtool uses the last event in the capture that has the resource bound.

pix_list_queuesA

List every command queue in a capture, with its name, type, and event count.

This is the real queue table, not a guess. Use the id or name of the busiest graphics queue with pix_list_gpu_events or pix_get_pipeline_state; a capture whose default queue looks empty usually has its draws in a named one.

pix_list_gpu_eventsA

List events from one queue, straight from PIX's engine.

Unlike pix_list_events this needs no CSV export, sees every queue, and can return the decoded D3D12 call with its arguments. Without queue_id the busiest GPU queue is used. Paging is by cursor_position; the response carries next_cursor.

pix_list_resourcesA

List the D3D12 resources in a capture with their dimensions and formats.

This is PIX's resource table: every texture and buffer the frame touched, with the debug name the application set. Filter with name_contains, then pass an entry's index to pix_get_resource or pix_get_resource_views.

pix_get_resourceA

Describe one resource: full descriptor, heap, initial state, and view summary.

index comes from pix_list_resources.

pix_get_resource_viewsA

Every view of one resource, and the events that bind it.

This answers "where is this texture actually used, and as what": each view carries its descriptor, and each binding names the event plus how it was reached - root parameter, bindless descriptor heap, or an API argument such as a copy source or a barrier transition.

pix_get_pipeline_stateA

The pipeline state bound at one event: program type, root signature, and bound views.

This is PIX's Pipeline State view. event_index is the queue-relative index from pix_list_gpu_events, not the global id used by the pixtool-backed tools. Events that bind no pipeline - Signal, Present, ResourceBarrier - report no_pipeline_state.

pix_get_root_signatureA

The root signature bound at one event, without the bound-view detail.

A narrower pix_get_pipeline_state for when only the binding layout matters.

pix_start_analysisA

Replay a capture on a GPU so that PIX's timing data exists. Slow, and once only.

PIX gets per-event GPU timing by replaying the capture on a real D3D12 device; the 4.5 GB reference capture takes about twenty-five seconds. This blocks for up to wait_seconds (90 by default, enough for most captures) and reports where it got to. The replay keeps running between calls, so polling is free; pass wait_seconds=0 to start it and return at once. Once ready is true, pix_get_event_timing answers for every event in every queue at no further cost.

Windows defaults to this machine. Xbox requires an explicit device_address matching the GDK PIX analysis devkit and may interrupt its running title.

pix_get_event_timingA

Per-event GPU timing from PIX's own timeline, with start and duration for each.

top=N ranks the whole queue by GPU duration, which is the cheapest way to find the expensive work in a frame: pix_get_counter_hotspots replays the capture once per counter, and this reads a timeline that already exists. The ranking is dominated by marker scopes - Frame, then Scene, then PostProcessing - which is the shape of the frame; leaves_only=true ranks the individual draws and dispatches inside instead. Without top, events come in queue order from cursor_position.

Windows starts replay if needed. Xbox requires pix_start_analysis first, with an explicit device_address; its returned time_unit is nanoseconds.

pix_get_bridge_statusA

Report whether the deep-analysis bridge is available, and how to build it.

pix_build_bridgeA

Compile the pixbridge sidecar. Needs the .NET SDK and a local PIX install.

pix_get_timing_summaryA

What a timing capture covers: its window, the machine, and which tables have data.

Start here. The contains flags say which of the other tools will return anything - a capture of a program that never presented has no gpu_frames, and one taken of an uninstrumented process has no pix_events - so this is the difference between an empty answer that is a bug and one that is the truth.

pix_list_timing_processesA

Every process alive during the capture, busiest first.

A timing capture is machine-wide: capturing one program still records the several hundred others that happened to be running. Only the one PIX attached to is sampled, so the first entry is almost always the one worth looking at and the rest are context for why it was slow. sampled=false means the process existed but did no measured work.

pix_list_timing_threadsA

Every thread of one process, with what PIX recorded for each, busiest first.

process_id comes from pix_list_timing_processes. The thread_id here is PIX's own, packing the process and thread together, and is what the per-thread tools take; os_thread_id is the one a debugger would show. stack_levels is how deep this thread's PIX event nesting goes.

pix_list_timing_gpu_queuesA

The GPU queues this capture saw, with the work counted on each.

queue_id from here goes to pix_get_gpu_timeline. A queue with event_count 0 was seen but never recorded doing anything, usually because the capture did not include the process that owns it.

pix_get_gpu_framesA

The present timeline: one row per frame, from the Present call to the flip.

This is the frame-pacing view, and the reason to take a timing capture rather than a GPU one. Each frame carries the whole chain as separate timestamps - when Present was called, when it reached the queue, when the GPU finished writing, when the display scanned it out - so a stutter can be attributed to the part of the chain that grew rather than just to the frame that was late. block_on_fence and wait_on_swapchain are where a CPU thread sat waiting for the GPU or for a buffer to free up.

pix_get_thread_timelineA

One thread's named PIX scopes: what it was doing, and for how long.

This is the lane PIX draws for a thread. Each row is one PIXBeginEvent/End pair with its own name, span and nesting depth; without stack_level the whole tree comes back flattened, deepest scopes included. duration is wall clock and execution_time is the part of it the thread was actually on a core for - the gap between them is time spent descheduled, which is the difference between slow code and a thread that was waiting.

Empty means the process never called PIXBeginEvent. Check pix_get_timing_summary.

pix_get_gpu_timelineA

One GPU queue's named scopes, as the GPU actually ran them.

The same shape as pix_get_thread_timeline but indexed by queue rather than thread, and the times are when the GPU executed the work rather than when the CPU submitted it. Without queue_id the busiest queue is used.

pix_get_cpu_samplesA

Where one thread's CPU time went, as PIX's sampler saw it.

One row per sample: the sampler interrupted the machine, found this thread running, and recorded when and on which core. Counting samples in a time range is how much of that range the thread spent executing; the spread of core ids is whether the scheduler kept it in one place.

No instruction pointer comes back with these - resolving a sample to a function needs PIX's symbol server, which is a UI concern - so this answers when a thread ran, not what line it was on.

pix_get_context_switchesA

One thread's scheduling history: every time it came on or off a core, and why.

A row where from_thread_id is this thread is the thread losing the core, and from_thread_wait_reason says what for. That is the column that separates a thread that is slow because it is computing from one that is slow because it is blocked - a distinction a sample count cannot make, since a blocked thread simply produces fewer samples and looks idle.

Wait reasons are the Windows KWAIT_REASON values: 0 is Executive, 1 FreePage, 5 Suspended, 6 UserRequest, 7 WrExecutive, 13 WrQueue, 31 WrDispatchInt.

pix_get_core_activityA

Everything that ran on one core, whichever process it belonged to.

pix_get_cpu_samples answers "where did this thread's time go". This answers "what was on this core", which is the question behind an affinity or contention problem: a core pinned by another process is a cause of stutter that no amount of looking at the title's own threads will reveal. threads_in_page is the page's own breakdown, and process_id on each row is how to tell whose thread it was.

busiest_process_share names the busiest real process; idle_share_of_page is the part of the page that was process 0, the Windows Idle process, which is the core having nothing to do rather than a competitor for it. A core with a low idle share and a busiest process that is not the title is the contention case.

core is an index; pix_get_timing_summary reports how many this machine had. Empty means either nothing ran there or the capture was taken with cpu_samples disabled.

pix_get_residency_operationsA

What the GPU made resident, evicted, or paged, and when.

Video memory is oversubscribed when the working set does not fit, and Windows resolves it by evicting an allocation and paging it back over PCIe when the GPU next needs it. The cost lands on a frame that otherwise looks ordinary - the same draws, the same state, several milliseconds longer - so it is invisible in the event list and in every GPU-capture tool here.

Each row names the object that moved, which is what makes it actionable: a texture atlas evicted every frame is a budget problem with an address. residency_count is the running number of resident objects after the operation.

operation filters to one of MakeResident, Evict, PageIn, PageOut; by default all four are queried and merged in time order. An empty result is the normal case for a title comfortably inside its video memory budget.

pix_list_timing_countersA

The counters a timing capture carries, grouped as PIX groups them.

These are not the same thing as pix_list_counters. Those are replay-time hardware metrics for a GPU capture, measured by re-running draws; these were reported live while the timing capture ran - what the title passed to PIXReportCounter, plus what the platform contributed, such as the video memory budget and usage that the driver publishes per adapter.

Each row names the process that reported it, so a machine-wide capture separates the title's counters from everything else's.

pix_list_storage_devicesA

The storage devices this capture watched, busiest first.

device_id from here goes to pix_get_file_io. Sorted by title_operation_count - the I/O done by the process PIX attached to - rather than by the machine-wide total, because the busiest disk overall is often some other program's.

Usually empty. PIX records file I/O only when the session enabled the ETW file provider, which pix_capture_timing has no switch for, so captures taken through this server have none. A capture recorded from the PIX UI with file I/O turned on will populate this and pix_get_file_io.

pix_get_file_ioA

Every file operation on one device: what was touched, when, and for how long.

This is the lane that explains a hitch the CPU sampler cannot. A thread blocked in a synchronous read is off-core and looks idle; here the same stall is one row with a path, a start and an end. offset and size_bytes together are the access pattern, which is what separates a streaming system reading ahead from one seeking per request. status is the NTSTATUS: 0 succeeded, negative is the failure the caller saw.

Without device_id the device the captured title used most is chosen. Fails with no_devices when the capture recorded no file I/O at all - see pix_list_storage_devices for why that is the usual case.

pix_get_page_faultsA

One thread's page faults - the stalls that no CPU sample can show.

fault_type is what it cost. HardPageFault is the expensive one: the page was not in memory and the thread waited on the disk. DemandZero is a first touch of new memory, Transition a page reclaimed from the standby list, CopyOnWrite a private copy being made - all cheap, and worth having mainly to tell them apart from the hard kind.

A burst of hard faults on the game thread is a hitch caused by memory pressure rather than by work, and it appears in no other table here. thread_id comes from pix_list_timing_threads.

Like file I/O this needs a provider pix_capture_timing does not enable, so captures taken through this server return nothing; the query is correct and the table is empty.

pix_get_api_objectsA

Named D3D12 API work on one thread, as spans.

This is PIX's API object lane: a span for each named piece of driver-level work the thread did, such as "Pipeline State Compilation". It is what turns an object id in some other table into a name, and what shows a compile or a resource creation landing on a thread that was supposed to be doing something else.

It is per thread and sparse. pix_list_timing_threads reports api_object_events on every row, which is how to find a thread that has any before querying one that does not.

pix_get_command_listsA

The command lists one GPU queue executed, grouped by submission.

pix_get_gpu_timeline says which named scopes the GPU ran; this says which command lists carried them. Rows sharing an execution_id went in on one ExecuteCommandLists call, and parallel_level is how PIX stacks lists that overlap - which distinguishes real parallelism from a submission that got split.

Omit queue_id for the busiest queue. Empty means the capture was recorded without GPU timings, which is what the --noGpuTimings switch does.

pix_get_allocation_movesA

Allocations the driver moved between heaps, or demoted out of video memory.

The other half of pix_get_residency_operations. Residency says an allocation was evicted and made resident again; a migration says it was moved between heaps and whether that worked, and a demotion says it was pushed into system memory for good. A demoted texture still reads, just over PCIe on every frame that touches it, so the cost is permanent rather than a one-off.

kind selects "migration" or "demotion"; omitting it returns both in time order. Empty is the normal answer for a title inside its video memory budget.

pix_list_custom_data_typesA

The custom data lanes this capture carries, with how much is in each.

Two sources end up here. A title that calls PIX's custom data API declares its own lanes - a gameplay state, a streaming bucket, whatever it defines. PIX also records one per monitor, carrying that display's VSync signal, which is present on any capture and is the reference a present-to-display measurement needs.

event_count is spans, marker_count is instants. data_type_id is what pix_get_custom_data takes.

pix_get_custom_dataA

One custom data lane's events and markers, in time order.

For a monitor lane this is the VSync train, and the gap between a present in pix_get_gpu_frames and the next VSync here is the wait a frame actually spent on the display - the part of latency that no GPU capture contains.

Omit data_type_id for the fullest lane; kind selects "event" or "marker". pix_list_custom_data_types lists what is available.

pix_list_controllersC

The input controllers the capture saw.

latency_instrumented says whether PIX's Dynamic Latency Instrumentation was running for that device, which is the difference between input timestamps anchored to the hardware and timestamps inferred from when the title read them.

pix_get_controller_inputA

Controller reads, which is where an input latency measurement starts.

Each row is one read of a controller's state, timestamped when the title read it. Paired with pix_get_gpu_frames and the monitor lane from pix_get_custom_data, the span from an input row to the VSync that displayed its consequence is end-to-end latency - the number a player feels, and the one no GPU capture can produce.

Omit controller_id for the busiest device. Empty means no controller was present or the title reads input through an API PIX does not instrument.

pix_list_metricsA

The metric series this capture can plot, and what each one is keyed on.

These are the graph lanes above PIX's thread tracks: a value sampled repeatedly across the capture rather than a list of events. keys_available says how many keys exist for each lane - how many counters were reported, how many cores the machine had - which is a different question from whether the series has rows.

Feed a lane name to pix_get_metric_values.

pix_get_metric_valuesA

One metric sampled over time: a series rather than a list of events.

The lanes, and what each needs:

  • "pix_counter" (the default) - a counter reported while the capture ran, either by the title through PIXReportCounter or by the driver, such as the per-adapter video memory budget and usage. Takes metric_id from pix_list_timing_counters.

  • "cpu_usage" - one core's utilisation as a percentage, sampled every 100 ms. Takes core, plus either process_id or all_processes=true. Asking for the same core both ways is how to see whether a busy core was busy with the title or with something else.

  • "api_object_memory" - video memory held by one process's D3D12 objects, in bytes. Takes process_id.

  • "device_utilization" and "device_bandwidth" - per Win32 device. Take device_id from pix_list_storage_devices, and need a capture recorded with the ETW file provider.

in_page reports min, max, mean, first and last over the rows returned, which for a series is usually the whole question.

pix_run_debug_layerA

Replay the capture with the D3D12 debug layer on, to surface validation errors.

pix_collect_occupancyB

Replay the capture collecting GPU occupancy data.

pix_perform_playbackC

Replay the capture once (or in a loop), optionally timing command-list recording.

pix_recapture_playbackB

Capture a replay of the open capture (pixtool must itself be launched for capture).

pix_recapture_regionA

Write a new .wpix containing only the events in [start_global_id, end_global_id].

pix_capture_appB

Launch a D3D12 executable, take GPU captures, and save them.

pix_capture_uwp_appC

Launch a UWP app and capture it.

Get the package and application ids from the PIX UI: PC Connection / Select Target Process / Launch UWP, right-click the app, 'Copy pixtool Launch Command'.

pix_capture_attached_processC

Attach to a running process and capture it.

pix_capture_programmaticC

Wait for the app to trigger a programmatic capture, then save the results.

The app must call the PIX capture API (for example PIXBeginCapture) itself.

pix_capture_timingC

Record a PIX timing capture (CPU/GPU timeline) of a launched or attached app.

pix_export_to_cppA

Export the captured frame as a standalone C++ D3D12 project.

use_winpixeventruntime and use_agility_sdk acknowledge the WinPixEventRuntime and DirectX 12 Agility SDK licenses respectively.

pix_upgrade_captureA

Upgrade a GPU capture file to the latest PIX format.

Without a destination the source file is overwritten in place.

pix_run_pixtoolA

Run an arbitrary pixtool command pipeline.

This is the escape hatch for pixtool features without a dedicated tool. Pass commands as a list of objects: {"name": "save-event-list", "positionals": ["out.csv"], "options": {"counters": "gpu__*"}}. Options with a null value become bare flags. If capture_id is given, open-capture is prepended.

pixtool aborts the remaining commands once one fails. Values containing spaces are quoted automatically; a value containing a double quote cannot be expressed.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

TDQS

B3.4/5.0

Scored across 67 tools

Disambiguation3/5

Most tools have distinct purposes, but a few pairs overlap, such as pix_list_queues vs pix_discover_queues and pix_list_events vs pix_list_gpu_events. The detailed descriptions help, but the large number of tools makes misselection more likely.

Naming Consistency5/5

All tools follow a consistent snake_case verb_noun pattern with the pix_ prefix, such as list_resources, get_pipeline_state, and capture_timing. This consistency makes the tool's action and target predictable across the entire set.

Tool Count2/5

With 67 tools, this far exceeds the typical well-scoped MCP server range. While the PIX domain is broad, the count is unwieldy and could benefit from being split into focused servers by area such as capture, timing, or GPU analysis.

Completeness5/5

The tool set covers the full capture-analysis lifecycle: capture from various targets, open/close/upgrade captures, inspect GPU/CPU timing, events, resources, pipeline state, counters, occupancy, memory, input, and export. No obvious dead ends or missing core operations are apparent.

Maintenance

ActivityMaintained
ResponsivenessNo issues