Hardware testing on a native Apple Silicon Mac (LTspice XVII 17.2.4) surfaced
that model/subcircuit-bearing circuits failed with 'Unable to find definition
of model' / 'Could not open library file': native Mac LTspice in batch mode
does NOT auto-include the standard device libs or search the lib dir the way
Wine does (Wine netlists the .asc itself on Linux, so this never showed there).
Two fixes:
- netlister.py: emit .lib standard.{bjt,mos,dio,jft} for each semiconductor
family present, since Mac LTspice won't auto-include them in batch mode.
- runner.py: _resolve_lib_paths() rewrites bare-name .lib/.include refs to
absolute paths under the Mac lib root before running -b (Mac LTspice can't
find them by name). darwin-only; Linux path untouched.
- test_netlister.py: skip the Wine-oracle comparison class when wine is absent
(it's a Linux-only oracle).
Validated on hardware: full suite 534 passed / 8 skipped on native macOS
(all 10 integration circuits simulate, incl. op-amp + BJT); Linux unchanged
(524 unit + 18 integration). Also answered Kevin's open questions on hardware:
#2 -- 'LTspice -b <netlist>' DOES run over bare SSH (Background session), so
mcltspice is fully headless on Mac since it always netlists itself; #3 --
examples are not pre-extracted on Mac (examples_exist reports false).
Platform-branches the Wine-specific execution so Linux behaves identically
while macOS drives LTspice.app natively. Implemented from muel9560's measured
spec + validated where possible against a Wine -netlist oracle on Linux.
- config.py: get_launch() abstraction (argv prefix + env + path translator);
IS_DARWIN, LTSPICE_BIN (env-overridable), LTSPICE_LIB_ROOT under
~/Library/Application Support/LTspice on macOS. Linux resolves to the
historical Wine paths unchanged.
- runner.py: darwin branch uses the native binary, POSIX paths (no Z: map),
plain -b (never -Run), and completion by polling for a stable .raw (the Mac
GUI app's exit code is unreliable). Linux keeps communicate()+wait_for.
- netlister.py (new): asc_to_netlist() converts a schematic to a netlist on
macOS, since the Mac CLI can't netlist a .asc. Union-find net extraction;
all 15 templates netlist, topology matches the Wine -netlist oracle.
- library_tools.py: check_installation reports platform/binary/lib-root/
version/gui-session on macOS.
- packaging/docs: drop 'Linux only'; document LTSPICE_BIN + GUI-session need.
Adversarial review fixes: restore kill+reap of the Wine subprocess on Linux
timeout (was orphaning the process -- a real regression); alias duplicate net
labels instead of aborting (sallen-key); synthesize NC_xx nodes for floating
pins (op-amps) to match the oracle; use asyncio.get_running_loop().
Linux unaffected: 476 unit + 10 integration (real Wine) still pass; +48 unit
+8 integration for the platform/netlister paths. macOS runtime (LTspice.app
invocation, GUI session, poll timing) needs validation on Kevin's hardware.
run_parameter_sweep/run_monte_carlo write each variant .cir into a work_dir,
then call run_netlist with that same work_dir -- which tried to shutil.copy2
the file onto itself (SameFileError), so every batch run failed before
simulating. Add _safe_copy() that skips when src and dst resolve to the same
path, used at all four staging-copy sites in run_simulation/run_netlist.
Caught by headless claude-cli testing of the batch tools (no batch test
existed). Adds test_runner.py (_safe_copy unit tests) and a TestBatchSweeps
integration class.
ltspice://symbols, ltspice://examples, ltspice://status raised TypeError on
read -- they called list_symbols/list_examples/check_installation, which are
@mcp.tool() FunctionTool objects (not callable). Split those three into plain
*_impl functions + thin tool wrappers, and point the resources at the impls.
The other library tools move verbatim.
Adds test_resources.py to read every resource (regression guard). Library tool
outputs verified byte-identical old-vs-new. server.py: 2168 -> 1876 lines.
Introduce the multi-file FastMCP layout. The mcp instance moves to _app.py
(imports nothing from tool modules, keeping the registration graph acyclic),
and the self-contained SpiceBook domain (5 tools + status resource + publish
prompt + 2 helpers) moves to spicebook_tools.py. server.py imports it for the
registration side effect.
Tool/resource/prompt counts unchanged (42/6/8); names identical. Repointed
test_spicebook's _read_netlist import. server.py: 2472 -> 2168 lines.
17 tests: round_sig/compact_list rounding, downsample_indices stride vs
peak-preserving (a narrow spike must survive), and extract_waveform/
analyze_signal across AC/transient/x-range/error/all-analysis paths.
11 tests over auto plot-type detection (bode/spectrum/time), missing-signal
and multi-signal-overlay errors, dimension/height defaults, downsampling, and
x-range clipping -- all with hand-built RawFiles, no .raw read or SVG write.
15 tests over build_effective_params, extract_metrics (AC + transient via
hand-built RawFiles), evaluate_targets (all comparison operators +
unmeasurable), and make_suggestions. This logic previously required a full
LTspice run to exercise.
Covers the netlist-first precedence contract (names in both registries),
asc-only/netlist-only resolution, unknown -> None, and the deduped union
in all_template_names -- the lookup logic tune_circuit depends on.
Locks in the exact-match semantics that distinguish resolve_index from
get_variable, so the lookup used by tune_circuit and plot_waveform is now
covered without needing the live LTspice path.
Bridge mcltspice to SpiceBook (spicebook.warehack.ing) with 5 new MCP
tools (publish, list, get, delete, simulate), a status resource, and a
guided publish prompt. Includes LTspice-to-ngspice netlist conversion
(strips .backanno, Rser=, Windows paths) with structured warnings.
Path traversal protection on file reads (extension allowlist + resolve),
notebook ID validation, narrowed exception handling, and size limits.
35 unit tests + 4 live integration tests.
Row-major data from raw_parser was indexed as column-major, producing
garbled plots for digital waveforms. Also adds signals parameter for
overlaying multiple time-domain traces with legend, and adaptive X-axis
labels (ns/µs/ms/s) based on time span.
Expose x_min/x_max/y_min/y_max, max_points, width/height, and title
override through the MCP tool. Data is clipped to X range before
stride-based downsampling for better zoomed resolution. All params
default to None/current behavior for backward compatibility.
Rename package from mcp-ltspice/mcp_ltspice to mcltspice throughout:
source directory, imports, pyproject.toml, tests, and README.
Remove startup banner prints from main() since FastMCP handles
its own banner and stdout is the MCP JSON-RPC transport.
Point repo URL at git.supported.systems/MCP/mcltspice.
CE amplifier schematic: the input coupling cap CC_in was placed
horizontally (R90) at y=336 — the same y as the RB1-to-base bias
wire. Both cap pins sat on the wire, shorting the cap and allowing
Vin's 0V DC to override the bias divider, putting Q1 in cutoff.
Fix: move CC_in to vertical orientation (R0) above the base wire.
Now pinA=(400,256) and pinB=(400,320) are off the y=336 bias path.
The cap properly blocks DC while passing the 1kHz input signal.
Result: V(out) swings 2.2Vpp (gain ≈ 110) instead of stuck at Vcc.
Colpitts oscillator test: the schematic was actually working (V(out)
pp=2.05V) but the test's fallback variable selection picked V(n001)
(the Vcc rail, constant 12V) instead of V(out). Fix: look for V(out)
first since the schematic labels the collector with "out".
Integration tests: 4/4 pass, unit tests: 360/360 pass.