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.
Move the shared JSON-compaction helpers (round_sig, compact_list,
downsample_indices) out of server.py into output_format.py, and extract the
get_waveform and analyze_waveform cores into waveform_query.py
(extract_waveform, analyze_signal) -- both take a parsed RawFile and return a
JSON-ready dict, so they test without a simulator. The tools keep only the
.raw read.
server.py: 2677 -> 2472 lines. Output verified byte-identical across
AC/transient/downsample/error/all-analysis cases.
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.
plot_waveform was the largest tool at 207 lines, interleaving signal
resolution, auto plot-type detection, X-range clipping, downsampling, and
svg_plot dispatch with the .raw read and SVG write. Move the RawFile -> SVG
transform into plotting.build_waveform_svg (returns the SVG string + metadata,
no filesystem); the tool keeps only the parse and write I/O.
server.py: 2799 -> 2677 lines. Output verified byte-identical (SVG sha256 +
metadata) to the prior version across bode/spectrum/time, x-range+downsample,
custom dimensions, and both error paths.
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.
tune_circuit interleaved ~260 lines of pure computation (param validation,
metric extraction, target evaluation, suggestion generation) with the async
Wine orchestration, so that logic could only be tested through a full
simulation. Split the four pure pieces into tuning.py:
build_effective_params, extract_metrics, evaluate_targets, make_suggestions
(plus UnknownParamError). The tool keeps the async generate-and-simulate
flow and error-dict shaping.
server.py: 2949 -> 2799 lines. Output verified byte-identical to the prior
version across AC, transient, and all error paths.
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.
The two inline registries (_TEMPLATES, _ASC_TEMPLATES) plus their ~30
generator-function imports were pure data living in server.py. Move them
to a dedicated templates.py as NETLIST_TEMPLATES / ASC_TEMPLATES, and add
resolve_template() (netlist-first precedence) + all_template_names() to
replace the '.get() or .get()' dual-lookup dance that tune_circuit used.
server.py: 3257 -> 2949 lines. Registries verified byte-identical to the
originals (keys, params, descriptions, func identities); netlist-first
precedence for names in both registries preserved; live tune_circuit /
create_from_template / generate_schematic unchanged.
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.
tune_circuit and plot_waveform each hand-rolled exact case-insensitive
name->row-index resolution -- a manual 3-way loop and a next(enumerate)
generator respectively. Centralize as RawFile.resolve_index(), next to
the existing get_variable() (which does partial match, so it can't be
reused here without changing semantics).
No behavior change: exact case-insensitive match and the not-found error
paths (with available_signals) are preserved. Verified against the live
LTspice path -- tune_circuit bandwidth and plot_waveform resolution
unchanged.
get_waveform with 3 AC signals at 700 points produced ~92K chars of JSON,
exceeding MCP client token limits. Three fixes:
- Round output values (dB to 2dp, phase to 2dp, freq/time to 6 sig figs),
cutting JSON size ~63% (97K -> 36K chars for the same data)
- Peak-preserving downsampling for AC data: bucket-based selection keeps
the point with the largest magnitude deviation per bucket, so narrow
resonance peaks (high-Q filters) aren't lost by blind stride sampling
- Add x_min/x_max parameters to get_waveform and evaluate_waveform_expression
for zooming into a frequency/time range without excessive global point counts
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.
Dark mode had inverted gray scale (:root[data-theme="dark"] should
have been [data-theme="light"]), causing near-invisible text. Override
SocialIcons with Lucide GitBranch/Package icons instead of duplicate
external-link icons. Add footer source link to Gitea.
Bump opacity on section labels, knob labels, brand text, and
attribution bar. Slightly increase font sizes on smallest labels.
Maintains vintage aesthetic while meeting readable contrast levels.
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.
8-position volume control (5%-40%, default 20%) between Source
and Vertical sections. Persists via localStorage, keyboard
accessible, speech volume scales proportionally.
Opening narration cycles through a 9-stop gradient (red → amber →
yellow → green → teal → blue → violet → pink → red) at 6s per
loop. Closing narration stays teal for visual hierarchy.
Replace disconnected bottom link with the show's closing narration
subverted: "We now return control of your television set to
oscilloscopemusic.com" — fades in after typewriter finishes with
a dramatic 1.2s delay. Also add oscilloscopemusic.com to the
always-visible attribution bar for N-Spheres tracks.
Clicking the Vertical or Horizontal knobs triggers The Outer Limits
(1963) opening narration as a typewriter overlay on the CRT display,
with Web Speech API narration. Dismiss via click, Escape, or knob
re-click. Works on both 465 and 545A skins.
Source knob cycles through 6 tracks: the original CC-licensed
Spirals intro plus 5 N-Spheres tracks (Function, Intersect,
Attractor, Flux, Core) by Fenderson & Hansi3D.
- 48kHz FLAC conversions served via git LFS (~189MB total)
- Rotary knob with CSS custom property composable transforms
- Dynamic attribution (CC link for Spirals, album credit for N-Spheres)
- Signal selection persisted in localStorage
- Loading state overlay while buffering larger tracks
- Skin-aware labels (465: "Source", 545A: "Input")
- Keyboard accessible (Enter/Space to cycle)
Second oscilloscope skin: 1959 Type 545A with blue-green hammertone
panel, cream silk-screened labels, Bakelite knobs, deeper CRT recess,
and ventilation holes. Click the model name to cycle between 465 and
545A skins. Selection persists via localStorage.
XY-mode Lissajous display renders stereo audio on a canvas
inside a warm champagne bezel with recessed CRT bay, labeled
control sections (Vertical/Horizontal/Trigger), rotary knobs,
and power LED. Uses modern AnalyserNode + rAF pipeline instead
of deprecated ScriptProcessor.
Audio: "Spirals" by Jerobeam Fenderson (CC BY-NC-SA 4.0)
Visual: Nick Watton, adapted from gist by rsp2k
COPY patches/ before npm ci so the postinstall Starlight
head.ts patch runs during install. Replace duplicate Tutorials
card on the landing page with a Concepts card.
Astro/Starlight documentation at docs/ with 21 pages:
- Getting started (prerequisites, Claude Code setup, first simulation)
- Tutorials (filter design, Monte Carlo yield)
- Reference (all 37 tools, 5 resources, 7 prompts)
- Concepts (LTspice on Linux, simulation types)
Docker infrastructure with dev/prod compose overlays, Caddy
reverse proxy for mcltspice.warehack.ing, and Makefile targets.
Includes patch for Starlight 0.37 head schema default bug.
Tools organized by category: simulation, waveform extraction,
signal analysis, noise, stability/power, schematic/netlist,
and library/templates. Added resources and prompts tables.
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.
Add 7 new graphical schematic templates (differential amp, buck converter,
LDO regulator, H-bridge, common emitter, Colpitts oscillator) and rewrite
inverting amp to actually include an op-amp instead of just passive components.
Fix UniversalOpamp2 subcircuit error: the .asy symbol defines SpiceModel as
"level2", so SYMATTR Value must be omitted to let the built-in model name
resolve. Previously emitting SYMATTR Value UniversalOpamp2 caused LTspice
to search for a non-existent subcircuit.
Fix wire-through-pin routing bugs: vertical wires crossing intermediate
opamp/source pins auto-connect at those pins, creating unintended shorts.
Rerouted V1-to-In+ paths to avoid crossing In- pins in non-inverting,
common-emitter, Colpitts, and differential amp templates.
Refactor generate_schematic tool from hardcoded if/elif to registry dispatch
via _ASC_TEMPLATES dict, matching the _TEMPLATES pattern for netlists.
All 10 templates verified: simulate with zero errors and zero NC nodes.
255 tests pass, source lint clean.
The .asc schematic templates had wrong pin offsets, causing LTspice
to extract netlists with disconnected (NC_*) nodes and singular
matrix errors.
Fixed by reading pin positions from the .asy symbol files and applying
the correct CCW rotation transform: R90 maps (px, py) → (-py, px).
Pin offsets: voltage (+0,+16)/(+0,+96), res (+16,+16)/(+16,+96),
cap (+16,+0)/(+16,+64). Added pin_position() helper and _PIN_OFFSETS
table for reuse by all layout functions.
Verified end-to-end: generate_rc_lowpass → simulate → bandwidth gives
1587.8 Hz vs theoretical 1591.5 Hz (0.24% error).
Phase 3 features bringing the server to 27 tools:
- Stepped/multi-run .raw file parsing (.step, .mc, .temp)
- Stability analysis (gain/phase margin from AC loop gain)
- Power analysis (average, RMS, efficiency, power factor)
- Safe waveform expression evaluator (recursive-descent parser)
- Component value optimizer (binary search + coordinate descent)
- Batch simulation: parameter sweep, temperature sweep, Monte Carlo
- .asc schematic generation from templates (RC filter, divider, inverting amp)
- Touchstone .s1p/.s2p/.snp S-parameter file parsing
- 7 new netlist templates (diff amp, common emitter, buck, LDO, oscillator, H-bridge)
- Full ruff lint and format compliance across all modules
New modules:
- log_parser: Extract .meas results and errors from sim logs
- waveform_math: FFT, THD, RMS, settling time, rise time, bandwidth
- netlist: Programmatic SPICE netlist builder with templates
- models: Search 2800+ SPICE models and subcircuits in library
- diff: Compare two schematics for component/topology changes
- drc: Design rule checks (ground, floating nodes, missing values)
Server now has 18 tools, 3 resources, and 3 guided prompts.
- Handle UTF-16 LE encoded .raw file headers (Windows/Wine output)
- Fix mixed-precision transient data: float64 time + float32 signals
- Fix AC analysis: all variables stored as complex128, not mixed
- Fix schematic parser losing components at SYMBOL boundaries
- Use proper atan2 for phase calculation, report magnitude in dB