| name | wcp-usage-guide |
| description | How to use the wcp-mcp MCP server to control waveform viewers (Surfer, VaporView, GTKWave) via the Waveform Control Protocol. Use this skill whenever you need to add signals, navigate waveforms, load VCD/FST files, or control any WCP-compatible waveform viewer through MCP tools. Also use it when the user mentions waveform debugging, signal inspection, simulation analysis, or any task involving .vcd/.fst/.ghw files with a viewer, even if they don't explicitly name the MCP server. |
WCP MCP Server Usage Guide
The wcp-mcp server exposes WCP (Waveform Control Protocol) viewer controls as MCP tools. Three viewers are supported with some behavioral differences — this guide covers what you need to know.
Quick Start
connect_viewer(viewer, host, port) — connect to a running viewer
load_waveform(path) — load a VCD/FST/GHW file
add_signals(variables) / add_items(items) — add signals to display (returns {id, name, type} for each signal)
get_item_info() — inspect all displayed items (omit ids to query everything)
Default ports: Surfer 54321, VaporView 54322, GTKWave 8765
Always call viewer_status() after connecting to see which commands are available.
Command Compatibility
Universal (all three viewers)
load_waveform, reload_waveform, add_signals, add_items, remove_items, get_item_list, get_item_info, set_viewport_to, set_viewport_range, zoom_to_fit, focus_item, set_item_color, clear
Surfer + GTKWave (not VaporView)
add_markers
Surfer only
add_scope, set_cursor, shutdown_viewer
VaporView only
get_viewer_state, get_values_at_time, set_marker, get_marker, set_value_format
add_signals vs add_items
Both add signals to the viewer. The difference:
add_signals(variables) — takes a list of signal names. Semantically clear: you're adding signals by name.
add_items(items, recursive) — takes a list of signal or scope paths. Supports recursive=true to expand scopes into all their signals. Use this when you want to add an entire scope at once.
In practice, add_items is the more flexible tool. Both return [{id, name, type}] for each added signal.
Silent failure: If a signal name doesn't exist in the waveform, it is silently skipped — no error is returned. The returned list will only contain entries for signals that were successfully added. Compare the return list length against your input list length to detect missing signals.
Markers appear in item queries
Markers added via add_markers or set_marker show up in get_item_list and get_item_info results alongside signals on Surfer and GTKWave. If you need to distinguish markers from signals, check the type field in get_item_info output.
VaporView does not include markers in item queries. Use get_marker() or get_viewer_state() to read marker positions on VaporView.
Signal Naming — The Biggest Gotcha
Signal naming conventions differ across viewers, especially for array elements (memory arrays, indexed signals). There is no universal format.
| Viewer | Array element format | Example |
|---|
| Surfer | scope.[index] (dot-bracket) | storage_tagRams_0.[10] |
| VaporView | scope.[index] (dot-bracket, internally) | storage_tagRams_0.[10] |
| GTKWave | scope[index][bit_range] (double bracket + bit range) | storage_tagRams_0[10][21:0] |
How to discover the correct naming format
WCP has no "list all signals" or "search signals" command. To discover the naming convention:
- Use
add_items with a known scope path and recursive=true
- The return value includes
{id, name, type} for each added signal — the name field reveals the viewer's naming convention
Example: adding TOP.NonBlockingICache recursively on GTKWave shows names like storage_tagRams_0[10][21:0], revealing the double-bracket format.
Return value format
add_signals, add_items, and add_scope all return a list of {id, name, type} objects instead of bare ID lists, so you can immediately see which ID corresponds to which signal without a separate get_item_info call.
get_item_info can be called without ids to return info for all currently displayed items.
Viewer-Specific Behavior
VaporView
add_items with scope + recursive may not expand scopes — VaporView sometimes does not expand scope paths into individual signals. If add_items(["some_scope"], recursive=true) doesn't add signals, fall back to using explicit signal names via add_signals.
add_items allows duplicate signal additions — adding the same signal twice creates two entries. No deduplication.
add_markers is not implemented — use set_marker instead.
get_viewer_state is the most reliable query on VaporView — returns marker times, zoom, scroll position, and displayed signal names with IDs.
remove_items may not work for some signals — due to a VaporView bug where extension-side and webview-side state can desync. If remove_items fails, use clear() as a workaround. After removing items, previously used IDs may be recycled and refer to different entities — do not cache IDs across remove operations.
clear() uses shutdown+reload internally — VaporView's native clear doesn't work, so the backend shuts down the document and reloads the waveform. This is transparent to the user.
Surfer
- Malformed commands can crash the connection — sending unknown command names or wrong parameter types via
send_wcp_command may cause an unrecoverable disconnect. You must reconnect after a crash. Stick to the provided MCP tools when possible; only use send_wcp_command for well-known commands.
add_scope is Surfer-specific and very useful — adds all signals within a scope. Use recursive=true to include sub-scopes.
get_item_info names are display names, not signal paths — two differences from the actual path:
- Bus signals have a space before the bit range:
data_in [7:0] instead of data_in[7:0]. When adding signals via add_signals, do NOT include the bit range — use fifo_tb.data_in, not fifo_tb.data_in[7:0] or fifo_tb.data_in [7:0].
- Sub-scope names may be truncated with a leading
… (ellipsis): …s_fifo.clk instead of fifo_tb.sync_fifo.clk. These truncated names cannot be used with add_signals. Use add_scope with the full scope path instead.
GTKWave
- String IDs required — item IDs are strings like
"T1", "T10" for signals and "M1" for markers. Integer IDs are rejected with an error — do not pass integers. Always use the string IDs returned by add_items, add_signals, or get_item_list.
- No
add_scope — use add_items with a scope path and recursive=true instead.
- No
set_cursor or shutdown — these features are not available in GTKWave's WCP implementation.
- Invalid IDs can crash the connection — querying
get_item_info with a non-existent ID causes GTKWave to close the connection. Always use IDs from get_item_list or add_items responses.
name field returns bare leaf names — get_item_info and add_items responses show only the leaf signal name without hierarchy (e.g., clk instead of TOP.cpu.clk). To identify which scope a signal belongs to, you need to track it yourself from the add_items call or use add_signals with full paths.
- Duplicate signals are allowed — adding the same signal multiple times creates separate entries with different IDs.
Connection Recovery
If a command returns "Not connected to a viewer" or "Connection lost", the viewer likely crashed or closed the connection. Recovery steps:
connect_viewer(viewer, port) — reconnect
load_waveform(path) — reload the waveform
- Re-add any signals you were viewing
Typical Workflows
Exploring an unknown waveform
connect_viewer("surfer", port=54321)
load_waveform("/path/to/wave.vcd")
add_items(["TOP"], recursive=true) # returns {id, name, type} for each signal
clear() # start over with specific signals
add_signals(["TOP.cpu.alu.result"]) # add what you need
Reading signal values at a point in time (VaporView)
connect_viewer("vaporview")
load_waveform("/path/to/wave.vcd")
add_signals(["TOP.data_out"])
set_marker(time=1500)
get_values_at_time(["TOP.data_out"], time=1500)
Navigating to a specific event
connect_viewer("gtkwave", port=9999)
load_waveform("/path/to/wave.vcd")
add_signals(["TOP.clk", "TOP.valid"])
set_viewport_range(start=0, end=5000)
add_markers([{"time": 2500, "name": "event"}])
Escape Hatch
send_wcp_command(method, params) sends a raw WCP command. Use this for:
- Commands not wrapped by dedicated tools (e.g.,
get_capabilities)
- Viewer-specific features (e.g., VaporView's
add_signal)
Always check viewer_status() first to see which commands are available. Be cautious with Surfer — malformed raw commands can disconnect the session.