pix-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| PIX_MCP_PIX_DIR | No | A PIX install directory to search instead of the default locations. | |
| PIX_MCP_CACHE_DIR | No | Where extracted artifacts live. | %TEMP%\pix-mcp-cache |
| PIX_MCP_BRIDGE_PATH | No | Full path to pixbridge.exe, skipping discovery. | |
| PIX_MCP_MAX_SESSIONS | No | Open captures kept before the least-recently-used is evicted. | 8 |
| PIX_MCP_PIXTOOL_PATH | No | Full path to a specific pixtool.exe, skipping discovery. | |
| PIX_MCP_XBOX_PIX_DIR | No | GDK bin directory containing Microsoft.PIX.Automation.dll. | |
| PIX_MCP_BRIDGE_DISABLED | No | Set to 1 to leave the bridge alone entirely. | |
| PIX_MCP_TIMEOUT_SECONDS | No | Per-invocation pixtool timeout. | 1800 |
| PIX_MCP_BRIDGE_AUTOBUILD | No | Set to 1 to build the bridge on first use if it is missing. | |
| PIX_MCP_XBOX_BRIDGE_PATH | No | Full path to pixbridge-xbox.exe. | |
| PIX_MCP_XBOX_BRIDGE_DISABLED | No | Set to 1 to disable the Xbox backend. | |
| PIX_MCP_BRIDGE_TIMEOUT_SECONDS | No | Per-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
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| 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.
|
| 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:
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
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
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 67 tools
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.
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.
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.
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.