Description

cicsim-mcp is an MCP server that exposes cicsim’s simulation bookkeeping as tools an agent can call directly. It answers the questions that otherwise cost a waveform viewer session or a hexdump: what vectors does this .raw actually contain, did this node ever move, which measurements are out of spec and in which corners, and are these results still valid for the netlist they were run against.

Every tool is read-only. Nothing here launches ngspice — cicsim run is long and corner-expanded, and belongs in a Makefile where its progress is visible, not behind a tool call that blocks until it finishes.

It requires the mcp extra, and Python 3.10+ (the MCP Python SDK’s own requirement — the rest of cicsim supports 3.8+):

pip install "cicsim[mcp]"

Both SDK generations work: 1.x (FastMCP) and 2.x, which renamed that class to MCPServer and moved it out of mcp.server.fastmcp.

Running it

cicsim-mcp

This speaks MCP over stdio, so it’s normally launched by an MCP client rather than run directly. For Claude Code:

claude mcp add cicsim -- cicsim-mcp

Tools

raw_info

Lists the vectors present in an ngspice .raw file, with the plot metadata and the span of the x axis — the latter tells you whether the run actually reached the time a measurement asks about. Takes an optional case-insensitive regex to filter the names.

Read this when a measurement fails with no such vector as v(x.y). A probe path is only valid for the view it was written for. An extracted (Lay) netlist is flat, so the layout’s cell hierarchy ends up inside the node name:

schematic:  v(xdut.vc)
extracted:  v(xdut.xbias.vc)

if the layout groups blocks into a wrapper cell the schematic does not have. LVS passing does not tell you the internal names match — only the ports are compared — so this shows up first as a failed measurement.

raw_signals

Reports min/max/mean plus first and last value for chosen vectors, and optionally the value at a given point on the x axis. A node whose min equals its max never switched, which is usually what you want to know before opening a viewer at all.

results

Returns the measurements of a .run with their spec verdict, scaled into the units the spec is written in. Specs come from <testbench>.yaml, so the verdict matches what cicsim summary puts in the README. Pass only_fails on a wide corner sweep to get just the violations and the corners they occurred in.

sim_status

Lists the runs in a simulation directory and reports how many of each run’s corners are stale, and which files changed.

Staleness is decided by re-hashing the dependencies cicsim recorded in each corner’s .sha — the same comparison cicsim run uses to decide whether to re-simulate — so “stale” means those corners really would re-run. Modification times are not usable for this: the design flow regenerates the netlist on every invocation, so its mtime always moves and an mtime test calls everything stale.

One false positive is worth knowing about. A magic parasitic extraction (<cell>_lpe.spi) is not byte-stable: re-extracting an unchanged layout emits the same capacitors, with the same values and the same nodes, in a different order and under different C<n> names. The hash moves, the circuit does not. So a Lay run reported stale only on a _lpe.spi is usually still valid — check with something like

diff <(grep '^C' a.spi | sed 's/^C[0-9]* //' | sort) \
     <(grep '^C' b.spi | sed 's/^C[0-9]* //' | sort)

before spending the runtime on a re-simulation.

corners

Lists the corner names available to cicsim run, read from cicsim.yaml here and in the parent directory, grouped by the leading letter of the naming convention (K process, T temperature, V supply).