Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.
From 0.6.2 the repository publishes one PyPI package, mcp-ltspice-qucs,
with one version.
Unreleased¶
Nothing yet.
[0.6.2]: 2026-11-02¶
Added¶
- First release to PyPI, as one package:
pip install mcp-ltspice-qucs. It installs themcp-ltspice,mcp-qucs-s, andmcp-rf-analysiscommands and therf_mcp_common,mcp_ltspice,mcp_qucs_s, andmcp_rf_analysismodules. The wheel carries the license text. - A tag-driven release workflow.
release-validationgates it, and it publishes one sdist and one wheel through PyPI trusted publishing.
Changed¶
- The four workspace packages (
rf-mcp-common,mcp-rf-analysis,mcp-ltspice,mcp-qucs-s) are merged into the onemcp-ltspice-qucsdistribution. Import names and command names are unchanged. Every module now reports themcp-ltspice-qucsversion. In a checkout,uv syncreplacesuv sync --all-packages, and the bundled client configs drop--package. - Tool descriptions say what
spec,sweep, andcomponentsactually want.
Fixed¶
- The sandbox probe checks the same system paths the real sandbox binds.
0.6.1: 2026-07-24¶
Fixed: dependency security audit¶
- v0.6.0's tag-triggered
release-validationworkflow ran for the first time and found 47 disclosed CVEs across 10 locked transitive dependencies:cryptography,idna,joserfc, themcpSDK,pillow,pydantic-settings,pyjwt,pymdown-extensions,python-multipart, andurllib3. All ten are bumped to their patched versions within the existing dependency bounds. Nopyproject.tomlconstraint changed, including the tightfastmcp>=3.4.4,<3.5pin.pip-auditnow reports zero known vulnerabilities. No API or behavior change; the full test suite was re-verified against the upgradedmcpSDK (1.27.0 → 1.28.1) with zero regressions.
0.6.0: 2026-07-24¶
Fixed: simulator run isolation and stale-artifact rejection¶
- Qucsator and Xyce harmonic-balance invocations now execute in unique
per-run workspaces. A failed process can no longer satisfy the run with a
.dator.HB.FD.prnfile left by an earlier invocation. - Non-zero exits, empty output, malformed Qucs datasets, and malformed Xyce HB results fail before publication. Validated outputs are copied atomically; the canonical default result remains in its immutable run workspace so repeated or concurrent runs cannot overwrite it.
- Every run writes a JSON manifest with input/output hashes, backend identity and version, command, allowlisted environment keys, timestamps, streams, return code, status, and published artifacts. Qucs-S MCP results now expose the manifest path.
Fixed: S-parameter comparison on mismatched frequency grids¶
compare_sparametersnow compares the union of both measured grids inside their common frequency span instead of using exact frequency intersection. Shared endpoints can no longer hide every interior difference, partial overlaps never extrapolate, and disjoint ranges fail explicitly.- Phase comparison now reports the shortest circular difference, so
179°versus-179°is 2° rather than 358°. Results include the overlap, interpolation policy, and original input ranges.
Fixed: single-source package and server versions¶
- Package
__version__attributes and FastMCP server metadata now derive from installed distribution metadata. Thev0.5.0packages no longer identify themselves as0.1.0. - Default shared envelopes use the installed
rf-mcp-commonversion, and consistency tests cover module, server, and envelope surfaces. - Updated stale architecture, envelope, Qucs implementation-status, and bug template text.
Added: shared simulation workspace contract¶
rf-mcp-commonnow providesSimulationWorkspace, SHA-256 artifact hashing, a minimal subprocess environment, and backend-version probing for reuse by the LTspice/ngspice runners and future durable job API.
Fixed: documentation contract and capability boundaries¶
- Corrected current tool names, signatures, counts, examples, license text, package relationships, Qucs-S implementation status, and backend coverage.
- Documentation now distinguishes ideal analytical response, legacy catalog snapping with first-order parasitic metadata, and actual simulator validation. The bounded circuit workbench separately supports explicit model attachment and supported LTspice/Qucs/SPICE import; unsupported constructs are rejected. One-sweep S-parameter extraction remains explicitly limited to reciprocal symmetric two-ports.
- The generated tool catalog now reports primary and alias counts and supports
--check; CI and workspace tests fail when it drifts from live registries.
Fixed: true two-excitation SPICE S-parameter extraction¶
- The public
extract_sparameterstool now accepts a source.asc, validates theV1/Rs1/RL1matched-port fixture, constructs a port-2 excitation, and runs both sweeps on the same LTspice or ngspice backend. - S11/S21 and S12/S22 are measured independently and merged only when the frequency grids match. Missing traces, fixture-current inconsistency, ambiguous rewiring, and a missing second result fail instead of emitting a structurally valid matrix containing copied or zero terms.
- Results include the extraction method, fixture, source/raw/log paths,
assumptions, validation residuals, backend, return codes, and isolated
workspace. The legacy single-sweep helper now requires an explicit
assume_reciprocal_symmetric=Trueopt-in.
Added: model-preserving passive realization¶
- Every selected passive now carries a
ComponentModelrecord with provider, immutable source reference, model kind, pin map, operating-range fields, source path/subcircuit identity where applicable, reduction method, and SHA-256 checksum. - User
.libregistration accepts exactly one unambiguous two-pin subcircuit; missing/ambiguous pin maps block registration. Measured.s2pfiles retain their source checksum and explicitly record the first-order reduction used by analytical paths. simulate_realized_filterinstantiates curated loss/SRF subcircuits or registered.libmodels into isolated port-1/port-2 netlists, runs both sweeps on one backend, and returns a full Touchstone matrix with model manifests and extraction provenance. Changing a supplied library changes both its checksum and the simulated response.
Fixed: topology, transforms, and engineering summaries¶
- Sweep, optimization, Monte Carlo, comparison, and validation now carry explicit filter kind/topology, reject unsupported combinations, and include exact band edges and point targets in their evaluation grids.
- TDR now builds a uniform DC-anchored spectrum or an explicitly labelled band-pass response, preserves complex data, reports range/time resolution, and rejects transforms whose assumptions are not met.
- Delay extraction requires an explicit band. Equivalent-circuit fits report residuals, convergence, bounds, validity range, and identifiability warnings. Passband checks interpolate complex values at exact edges and reject partial frequency coverage.
- The schematic renderer accepts only package-generated LC fixtures it can
reconstruct exactly. Arbitrary
.asccontent fails explicitly.
Added: confined execution, budgets, capabilities, and durable jobs¶
- Simulator input dependency trees are imported into immutable, checksum-addressed workspaces. Include/path/symlink escapes are rejected, ngspice has a no-network bubblewrap profile, and processes use timeout/cancellation tree cleanup.
- Expensive calls enforce component, point, iteration, core, trace, and combined-work limits. Large outputs stream to bounded JSONL artifacts.
- All servers expose cached
capabilities://resources andprobe_backendtools that distinguish installed, launchable, validated, and unavailable states through tiny known-answer checks. mcp-ltspiceexposes durable workspace/artifact/job primitives for import, parse/validate, submit, progress, cancel, retry, recovery, listing, and bounded artifact reads.
Fixed: MCP contracts and release engineering¶
- Error envelopes become structured protocol
ToolErrorfailures, and every tool carries safety annotations. Underscore names are canonical; 59 dotted compatibility aliases carry deprecation/removal metadata. - Real in-memory and stdio initialize/list/call/error tests cover every server. Tool schemas, annotations, and metadata are hash-snapshotted.
- Codex
.codex/config.tomland Claude Code.mcp.jsonproject fixtures are executable and contract-tested. - Dependencies have tested upper bounds. CI exercises locked and minimum-direct sets, builds wheels/source distributions, and installs wheels in isolation. Weekly self-hosted jobs cover Qucsator, Xyce, native and Wine LTspice, current Codex, and current Claude Code.
- Tag validation audits exported requirements, builds strict docs/contracts, emits an SPDX SBOM, and creates build-provenance attestations.
Added: auditable component provenance¶
- Catalog records distinguish generic technology models from orderable MPNs and include provider, source document/revision/retrieval time, license, checksum scope, package/tolerance/ratings, and test conditions.
- A provider protocol and checksum-addressed local-directory provider support deterministic user-model indexing and refresh. User filenames are never promoted to claimed manufacturer part numbers.
Added: bounded general circuit workbench¶
- Added versioned
CircuitDocument1.0 with explicit pin-to-net connectivity, ports, analyses, dependencies, provenance, drawing geometry, transformations, electrical fingerprints, and source-located unsupported diagnostics. - Added bounded import/export for SPICE-family netlists, supported LTspice ASC symbols/wires, Qucs/Qucs-S schematics, and Qucsator netlists. Supported fixtures round-trip without connectivity/value loss; unknown electrical constructs block export and simulation.
- Added common ngspice, LTspice, Qucsator, and Xyce adapter contracts, normalized complex result datasets, readiness negotiation, and stable tolerance policies. A real asymmetric two-port agrees across ngspice and Qucsator for all four S-parameters.
- Added MCP workspace/import/parse/export flows with checksum-addressed
artifact://resources. The live catalog now contains 140 primary tools (76 LTspice, 29 Qucs-S, 35 RF analysis) plus 59 deprecated LT aliases.
Added: constrained component search and generic IR optimization¶
- Component search now applies hard value, package, availability/orderability, Q/SRF, tolerance, rating, bias, temperature, model-kind, and provider constraints. Unknown requested data fails instead of being treated as a match.
- Selected model records attach exact hashes, provenance, pin maps, validity, and source dependencies to the IR. SPICE/Xyce compilation stages checksum-verified sources and honors logical pin order; Qucsator rejects unsupported cross-dialect models instead of substituting ideal parts.
- Added deterministic bounded/discrete topology-preserving optimization with hard constraints, named SPICE temperature/parameter corners, yield estimation, cooperative progress/cancellation, exact model attestation, independent-backend validation, and JSON/Markdown design-change reports. Fixed models cannot be nominally retuned; explicit instance parameters and generic lumped approximations can be.
Fixed: validate_against_spice no longer emits a fabricated two-port¶
- The general two-sweep S-parameter fix above closed the fabrication in the
public
extract_sparameters/simulate_realized_filtertools, butvalidate_against_spice'soutput_spice_s2pstill wrote the single-sweep network that mirrors S22 from S11 and S12 from S21: silently wrong for any asymmetric or non-reciprocal ladder. It now runs the same honest two-sweep extraction for the emitted file; the fast |S21| verdict still comes from the cheap single sweep. The tool description andoutput_spice_s2pfield now disclose the two-sweep method and its V1+Rs1/RL1 fixture requirement.
Added: durable simulation jobs for mcp-qucs-s¶
mcp-qucs-sgains the same simulate-through-IR job pipeline asmcp-ltspice:circuit_validate,simulation_submit,job_get,job_cancel,job_retry,job_list_artifacts, andartifact_read(22 → 29 tools).simulation_submitcompiles the imported artifact, runs it, parses the result into a normalizedResultDataset, and validates it against the requested analysis;analysis.kindroutessparameters/noiseto Qucsator andharmonic_balanceto Xyce. This wires theQucsatorAdapter/XyceAdapterbackend adapters, which existed with full compile/run/parse coverage but no caller.- Qucsator and Xyce have no verified OS sandbox profile, so these jobs run
unsandboxed on the immutable per-run workspace snapshot the adapter's
runcreates; only submit trusted local inputs.
[0.5.0]: 2026-07-23¶
The release that emptied the issue tracker. Drivers exist for ngspice, LTspice, qucsator-RF, and Xyce; required CI validates ngspice while the other simulator integrations are capability-gated. The filter portfolio gains the full elliptic family and all five distributed microstrip topologies on an exact coupled-line TEM model; the coexistence chain runs closed-loop from victim bands to a catalog-snapped analytical design; mypy runs with zero disabled error codes; and CI covers Linux (3.11–3.13) plus native Windows discovery. Highlights below, in reverse-merge order.
CI: windows-latest job for native simulator discovery (#40)¶
A windows-latest job now runs the suite on real nt: it asserts
_needs_wine() is False for a native .exe without the os_name
test hook, exercises find_ltspice() walking the C:/Program Files
candidates with no WINEPREFIX, and shakes out Windows Path
semantics across the tests (LTspice itself is proprietary and not
installed; simulator-gated tests skip cleanly). First Windows landmine
fixed on the way: the render server-tool test used /proc/... as an
"unwritable" path, which Windows would happily create: replaced with a
file-as-parent-directory, which fails on every platform.
Chore: mypy ratchet complete: zero disabled error codes (#37)¶
The last three deferred type-completeness codes are re-enabled and
their 54 accumulated errors fixed: no-any-return (numpy/JSON returns
wrapped in float()/int() or precisely cast(...)), type-arg
(bare dict/list/ndarray generics parameterized with the real
types), and no-untyped-call (all 10 were genuinely third-party:
schemdraw element constructors and matplotlib's PdfPages.savefig:
handled by a targeted two-module [[tool.mypy.overrides]], not per-line
ignores). The global disable_error_code list is now empty: type
correctness AND completeness are both enforced. Also fixed
test_runs_headless from #58 to delete DISPLAY and render, instead
of asserting the environment happens to be headless.
Tests: EMC closed-form anchors + schematic_render coverage (#34, #36)¶
predict_radiated_emissions_loopis now pinned to the textbook far-field small-loop expression (Ott'sE = 131.6e-16·f²·A·I/r; the module's exact constant η₀·π/c² = 131.68e-16 agrees to 0.005 dB), with dimensional-consistency checks (double area → +6.02 dB, double distance → −6.02 dB) and a new near-field guard: measurements inside the radian sphere (r ≤ λ/2π) now raise instead of returning a silently wrong regulatory number, and the docstring states the regime.predict_conducted_emissionsis pinned to a hand-computed CISPR 16 LISN divider at 1 MHz (|Z| = 49.53 Ω, 100 µA → 73.90 dBµV, the full complex arithmetic written out in the test docstring) plus current scaling.schematic_render.py(previously zero tests): ladder + elliptic designs rendered to SVG and PNG headless, every refdes and formatted value asserted present in the SVG text (catches silent mislabeling a size check would miss), the .asc round-trip, and both server tools' ok/error envelopes.
Added: synthesize_for_coex_target closed loop (#14, mcp-ltspice)¶
One MCP call now closes the loop the engineer used to orchestrate by
hand: place transmission zeros on the victim-weighted harmonic
centroids (#12) → synthesize the elliptic LPF and aim its traps
(E24-snapped) → substitute real vendor parts → evaluate the realized
ladder's rejection analytically → run the GNSS-aware coex matrix
(#15) → escalate the order by 2 until the worst-case desense meets the
target or max_order is reached (best-so-far returned with
converged=False and the full iteration log either way).
Realism handled explicitly rather than hidden:
- The SRF spec targets the highest placed zero with the issue's 1.2 margin, degrading gracefully (1.2 → 1.0 → unchecked) with the margin used reported per iteration. A 14 nH 0402 with SRF ≥ 3.3 GHz does not exist, and a turn-key loop should say so, not die.
- Zeros default to the dominant 2H/3H PA products (4H/5H sit ≥ 50 dBc down pre-filter and their broad FCC landings would otherwise both outrank 2H/3H and push the SRF spec beyond what 0402 can build); the matrix still evaluates all harmonics through 5H.
- The fundamental sees the filter's in-band rejection, each harmonic's dBc is deepened by the rejection at n·f0, and GNSS victims get the realized filter's rejection at their frequency injected into the ΔC/N₀ model automatically.
This is the one place mcp-ltspice depends on mcp-rf-analysis (one-directional, no cycle). Tests drive the real pipeline end to end: real synthesis fits, real vendor tables, real matrix, no mocks.
Added: GNSS-specific desense in check_coex_matrix (#15, mcp-rf-analysis)¶
RX entries may now set victim_type: "gnss", switching that victim to
the GNSS-specific model with the industry ΔC/N₀ (dB-Hz) metric instead
of a generic blocking margin:
- Broadband PA noise (the dominant real mechanism, evaluated once
per TX×RX pair):
I₀ = PA_noise − filter_rejection_at_victim − antenna_iso;ΔC/N₀ = 10·log₁₀(1 + 10^((I₀−N₀)/10))againstN₀ = −174 + NF. PA noise comes from the victim'spa_broadband_noise_dbm_hz_at_offset, else the TX'sbroadband_noise_dbm_hz, else a documented −150 dBm/Hz default. - In-band CW landings (fundamental/harmonics falling inside the
GNSS band): the correlator spreads the tone over the code rate,
I₀ = J − 10·log₁₀(chip_rate)(GPS C/A 1.023 Mcps default, Q = 1 assumed and stated in the per-entryassumptions). - Entries carry
mechanism(fundamental/harmonic_n/broadband_noise),delta_cn0_db_hz,i0_dbm_hz, the noise floor, and full assumptions;desense_margin_dbfor GNSS = a 1 dB C/N₀ budget minus ΔC/N₀ so mixed matrices stay worst-first sortable. Generic victims are unchanged. All model points are pinned by hand-computed tests (I₀ = N₀ → exactly 3.010 dB, etc.).
Added: place_zeros_for_coex (#12, mcp-rf-analysis)¶
Restricted-band-aware transmission-zero placement: for each harmonic
landing [n·f_lo, n·f_hi], the optimal elliptic-filter zero is the
severity-weighted centroid of the victim-band overlap intervals:
TZ = Σ s·w·mid / Σ s·w. Not the landing's geometric centre (which is
suboptimal whenever victims overlap the landing asymmetrically; the
hand-pinned reference case moves the zero from 1800.0 to 1811.48 MHz).
Victims come from the caller with optional severity weights, plus
optional auto-loads from the package's GNSS and FCC-restricted tables
(a 525 MHz TX's 3rd harmonic finds GPS L1 with no user input). The zero
budget assigns highest-aggregate harmonics first, spare zeros fall back
to landing centres (flagged), and the result carries trap-index hints
matching mcp-ltspice's elliptic convention (lowest zero → trap 2) for
direct composition with place_transmission_zero, a markdown
rationale, plus unprotected_victims / victims_not_at_risk lists.
Implemented in mcp-rf-analysis (coex_zeros.py) rather than
mcp-ltspice as issue #12 sketched: the band data and harmonic lookups
live here, and mcp-ltspice carries no runtime dependency on this
package: the trap-hint contract is the composition point instead.
Added: combline BPF (#27 complete, mcp-qucs-s)¶
synthesize_combline_bpf finishes the #27 distributed-filter set:
N coupled lines shorted at the same end, each tuned by a lumped
capacitor at the open end (C = Y_r·cot(θ0)/ω0; pure TEM combline
genuinely needs the caps), resonator length θ0 default 45°. Couplings
hit k = Δ/√(g_i·g_{i+1}) by inverting the exact pair transcendental
ωC = (Y_r ± y_m)·cot(θ(ω)) (new combline_pair_split); the tap comes
from the loaded-resonator slope parameter
b = (Y_r/2)(cotθ0 + θ0·csc²θ0), which reduces to the interdigital
formula at θ0 = 90°. segmented_array_sparams gains cap_loads
(lumped C on any node), the graph netlister generalises to
generate_coupled_array_netlist (ideal C elements at the loaded
nodes; generate_interdigital_netlist kept as an alias), and the
design self-reports achieved metrics like the interdigital tool.
Validation: qucsator graph netlist (TLIN + TLIN4P + C) matches the exact solver at numerical precision; on the 2 GHz / 10% / 0.5 dB N=3 design: 11.39 mm resonators (half the interdigital length), 1.137 pF loading caps, band centre 1.994 GHz, BW 11.7%, and the topology's selling point confirmed: first upper spurious at 3.75·f0 with the 1.5–3·f0 stopband below −39 dB (edge-coupled spurs at 2·f0).
Added: interdigital BPF on exact N-line TEM machinery (#27, mcp-qucs-s)¶
The N-conductor obstacle (a physical line cannot sit in two MCOUPLED
elements) is bypassed exactly: a same-velocity TEM array's 2N-port
Y-matrix is linear in the characteristic-admittance matrix, so a
tridiagonal array decomposes into ordinary TLIN stubs plus floating
TLIN4P connector lines: probe-verified to reproduce the CTLIN coupled
section at 0.0000 mdB. New multiconductor module:
segmented_array_sparams: exact S-parameters of stacked commensurate array segments with per-line short/open/port terminations and interior tap ports (validated against the closed-form coupled section at 1e-9 and against the qucsator graph netlist at 0.0001 mdB).interdigital_pair_k/mutual_for_k. The interdigitally-terminated pair's resonance split is closed form (cosθ = ±y_m/Y_r), giving the coupling coefficient without book tables.
synthesize_interdigital_bpf: N coupled λ/4 resonators, alternately
shorted, tapped I/O. Couplings hit k = Δ/√(g_i·g_{i+1}) exactly on the
TEM model; the tap angle comes from the shorted-λ/4 slope parameter
θ_t = arcsin(√(π·Y_r/(4·G0·Qe))). Unrealizable Δ/Z_resonator
combinations (stub admittance ≤ 0, Qe below the end-fed minimum) are
rejected with explanations. The design self-reports its achieved
response (achieved: band centre, −3 dB bandwidth, worst in-band return
loss) computed on the exact model. The tapped-feed isolated-resonator
approximation degrades ripple vs the prototype (measured: band centre
2.003 GHz on a 2 GHz/10% design, BW 11.8%, worst in-band S11 −5.9 dB)
and an MCP consumer should see that number, not assume the spec.
generate_interdigital_netlist emits the exact ideal-TEM graph netlist
(stubs to gnd for shorts, TLIN4P per coupling, ports on tap nodes).
Physical per-pair (W, S) via the existing Garg-Bahl inversion, flagged
first-cut.
Added: hairpin microstrip BPF (#27, mcp-qucs-s)¶
synthesize_hairpin_bpf folds the edge-coupled filter into the
Cristal-Frankel hairpin-line. The fold is a circuit-graph no-op apart
from the U-bend connector: each half-wave resonator (the two arm halves
the diagonal MCOUPLED chain already joins) gains the bend's electrical
length θ_b, so every coupled section is shortened to 90° − θ_b/2: with
one bend length (an MLIN at the mean arm width, default 3×W) this keeps
every resonator at exactly 180° at f₀ in closed form. Zero bend
degenerates to the edge-coupled design bit-for-bit.
Validated in real qucsator via the new generate_hairpin_netlist
(MCOUPLED cascade + bend MLINs at the internal junctions): with a 2 mm
bend (8.1° at 2 GHz) the compensated design centres at 1.976 GHz
(−1.2%, matching the unfolded filter's −1.1%) with 9.0% bandwidth,
while the uncompensated variant sags to 1.891 GHz (−5.5%). The
differential test proves the compensation mechanism rather than just
plausibility. Corner discontinuities and cross-arm self-coupling are
documented as the field-solver residual.
Added: edge-coupled (parallel coupled-line) microstrip BPF (#27, mcp-qucs-s)¶
synthesize_coupled_line_bpf implements the coupled-section BPF core
that hairpin / interdigital / combline all build on. Input is the LPF
prototype g_coefficients vector (exactly what the lumped synthesis
tools return); order N yields N+1 quarter-wave coupled sections via the
J-inverter constants (Pozar §8.7 eq. 8.121). The electrical math is
pinned to Pozar 4e Example 8.7: Z₀e/Z₀o = 70.61/39.24 and 56.64/44.77 Ω
reproduced to 0.05 Ω.
Physical (W, S) realisation comes from a new coupled_microstrip module:
quasi-static Garg-Bahl even/odd-mode capacitance analysis (single-line
fringe reused from the Hammerstad-Jensen code, exact elliptic-integral
gap term via scipy) plus numerical inversion. Unrealizable couplings
(coupler-grade Ze/Zo ratios) are rejected with an explanation rather
than returning a bound-clipped corner.
Validation, probe-verified against qucsator-RF 1.0.7:
- qucsator's ideal coupled line is
CTLIN(vacuum velocity; node order line1-near / line1-far / line2-far / line2-near, established by terminating all four ports and reading through/coupled/isolated = 0.9573/0.2849/0.0140 against theory 0.9585/0.2856/0). - The diagonal-connected section (others open) matches the closed-form
section ABCD to 0.0000 mdB; the new
coupled_line_sectionnetlist element andcoupled_section_sparamscascade agree with qucsator at numerical precision across the whole sweep. - End-to-end through qucsator's real Kirschning
MCOUPLEDmodel (newgenerate_coupled_microstrip_netlist, diagonal chaining): on a Rogers-class laminate the Pozar design centres at 1.979 GHz (−1.1% vs design), −3 dB bandwidth 9.4% vs the 10% spec, −1.76 dB midband loss. On FR-4 the measured 4.05 dB midband loss matches the classical 4.343·Σg/(Δ·Qu) estimate: real dielectric loss, noted in the tests.
Added: stepped-impedance microstrip LPF (#27, first distributed topology, mcp-qucs-s)¶
synthesize_stepped_impedance_lpf maps a lumped LPF ladder (the same
composition contract as lumped_to_distributed) to alternating high-Z /
low-Z microstrip sections per Pozar §8.6: series L → high-Z section with
βl = ω_c·L/Z_h, shunt C → low-Z section with βl = ω_c·C·Z_l, widths
and lengths from the existing Hammerstad-Jensen synthesis. Sections
exceeding the βl < 45° short-line approximation are flagged in notes.
The math is pinned to Pozar 4e Example 8.6. The six electrical lengths
(11.86°, 33.76°, 44.27°, 46.12°, 32.41°, 12.36°) and the section widths
(11.27 mm / 0.430 mm vs the published 11.3 mm / 0.428 mm) reproduce the
book design.
Validation plumbing, all probe-verified against qucsator-RF 1.0.7:
generate_ladder_netlistgains aseries_tlineelement (ideal line; qucsator'sTLINpropagates at c, so the emitter encodes electrical length asL = θ/360 · c/f_ref: established with a λ/4-inverter probe netlist that returned |S11| = 0.6000 exactly).- New
generate_microstrip_ladder_netlistemits a sharedSUBSTcard plus anMLINcascade (Hammerstad model, Kirschning dispersion), so designs run through qucsator's real microstrip model. - New
tline_cascade_sparamscomputes the analytical ideal-line cascade; it matches the qucsator TLIN netlist to numerical precision (< 1 µdB). - End-to-end on the Pozar example: analytical −3 dB at 2.418 GHz (−3.3% vs design. The known approximation shift), real-microstrip MLIN run −3 dB at 2.398 GHz (−4.1%) with 21 dB stopband at 4 GHz, matching the published response.
Remaining #27 topologies (hairpin, interdigital, combline) ship separately.
Added: elliptic band-pass / band-stop synthesis (#26, mcp-ltspice + mcp-qucs-s)¶
synthesize_lc_bpf and synthesize_lc_bsf now accept
filter_type="elliptic" (odd order ≥ 3), completing the elliptic family
started by the HPF in the previous release. The Pozar §8.5 band transforms
act on each L and C of the LPF prototype separately, so every shunt
series-LC trap becomes a four-element composite branch. A series-LC in
series with a parallel-LC tank, to ground. That shorts at the two images
of the prototype zero: ω = ω₀(√(b²+1) ± b) with b = ω_z·Δ/2 (BPF,
notch pairs straddling the passband) or b = Δ/(2ω_z) (BSF, pairs inside
the notch; the main-path tanks add a zero at ω₀ itself).
Supporting plumbing, since no existing element type could express that branch:
mcp_ltspice.extractgains theshunt_composite_trapelement ({L_s, C_s, L_p, C_p}), andcomponents_dict_to_elementspairs the four-key refdes group{Lk_s, Ck_s, Lk, Ck}emitted by the synthesis.mcp_qucs_s.netlist.generate_ladder_netlistemits the branch as four uniquely-named parts (L3s/C3s/L3p/C3p).- The v0.2.0 math-consistency invariant generalises: reported
transmission_zeros_hzare the roots ofu²·L_sC_sL_pC_p − u·(L_sC_s + L_pC_p + L_pC_s) + 1 = 0computed from the physical component values, and tests pin them to the closed-form mapped zeros to 1e-9 relative. - Validated against real qucsator-RF at orders 5/7/9 for both kinds: analytical ladder and simulator agree to within 0.02 mdB across a 100 MHz–10 GHz sweep.
- The
synthesize_lc_hpf_filter/_bpf_filter/_bsf_filterMCP tool descriptions no longer claim elliptic is unsupported (stale since the HPF landed), and all three now returntransmission_zeros_hz.
Elliptic BPF/BSF designs are always series_first (the prototype
extraction is T-form only); a shunt_first request is coerced.
Added: register_user_vendor_dir (#11, mcp-ltspice)¶
Engineers routinely have third-party or measured .s2p files: Würth, AVX,
TDK, distributor exports, in-house lab data. That live on no public URL and
so could not be used as substitution candidates without hand-editing .asc
files or forking the curated table.
register_user_vendor_dir scans a directory of .s2p (and, by filename,
.lib) models and registers them under a namespace, after which
substitute_real_components(inductor_vendor="<namespace>", ...) uses them
like any curated series. Each part's kind, value and SRF are recovered from
the measured reactance: via Z = 2·Z0·(1−S21)/S21 for the
series-through fixture that is standard for two-terminal parts, not from
Z11 (singular for a series element), and cross-checked against the
filename shorthand (part_L_3n3.s2p → 3.3 nH). Value is averaged over the
extracted L/C rather than the reactance, since a capacitor's reactance goes
as 1/f and averaging it directly biases the result. A file whose measurement
contradicts its name is indexed by the measurement with the conflict
reported; per-file errors never abort the scan; re-registering a directory
refreshes the index; and namespaces cannot shadow a curated catalogue.
lookup_part and lookup_part_with_srf_margin now filter a vendor table by
component kind instead of classifying it from one sampled entry, so a user
directory holding both inductors and capacitors resolves correctly.
Added: validate_against_spice (#16, mcp-ltspice)¶
The whole pipeline: synthesise, place zeros, substitute real vendor parts,
optimise, Monte Carlo: can run end to end on the closed-form ladder without
a single SPICE run. That is the right default for a fast inner loop, but it
silently drops everything a real simulation captures (vendor .lib
subcircuits, DC-bias/temperature behaviour, ground and supply
non-idealities, measured vendor S-parameters via .include), so a reported
yield could be an artefact of the lumped-element approximation.
validate_against_spice closes the loop: it runs the schematic through a
real simulator, extracts the S-parameters, computes the analytical response
for the same components, and returns a per-region |S21| divergence plus a
verdict: agree, minor_disagreement, disagree, or spice_unavailable.
Passband and stopband are separated by the analytical response itself, each
judged against its own threshold. A passband disagreement adds an explicit
warning that the analytical margin should not be trusted. With no simulator
installed it returns the analytical response and spice_unavailable rather
than failing, so it is safe to call unconditionally.
Added: elliptic high-pass synthesis (#26, partial: HPF)¶
synthesize_lc_hpf("elliptic", ...) now works; it previously raised
NotImplementedError. An elliptic HPF is the frequency mirror of an
elliptic LPF: the LPF→HPF map ω → ω_c²/ω inverts each prototype element
into its dual. A series inductor becomes a series capacitor, and a shunt
series-LC trap stays a shunt series-LC trap with L and C swapped, so a
finite zero at ω_z moves to ω_c²/ω_z, landing in the lower stopband.
Validated at orders 5/7/9: equiripple passband above ω_c that mirrors the
LPF to 0.05 dB, deep notches at the mirrored zeros, and the v0.2.0
math-consistency invariant (each reported zero equals 1/(2π√(L·C)) of its
trap) holds to 1e-9. Cross-checked end-to-end against real qucsator:
the analytical ladder and the simulated response agree to within 0.05 dB
across the band.
Elliptic BPF and BSF remain unimplemented and #26 stays open for them.
The bandpass/bandstop transforms turn each shunt trap into a four-element
composite branch (a series-LC in series with a parallel-LC tank) with no
existing element type or .asc netlister support. A materially larger
change than the HPF mirror, and not worth rushing into a subtly-wrong
filter.
Added: Qucs-S noise parameters (#25, mcp-qucs-s)¶
extract_noise_parameters was the last scaffolded tool in the suite. It now
runs a real noise analysis and returns the four classical noise parameters
per frequency: NF50, Fmin, Γopt and Rn. It also evaluates the noise figure
at any source reflection coefficient via
F(Γs) = Fmin + (4·Rn/Z0)·|Γs − Γopt|² / ((1−|Γs|²)·|1+Γopt|²)
which is the number that actually matters once a real LNA input match replaces the ideal Γopt.
Anchored on an identity with no fitting in it: a passive network's noise figure equals its insertion loss, exactly, at T₀ = 290 K. Matched pads of 3, 6, 10 and 20 dB all report NF equal to their loss to within 1e-4 dB, with Fmin = NF and Γopt = 0.
Two Qucs conventions were established empirically rather than assumed, since either would produce plausible-but-wrong numbers:
Rnis in absolute ohms, not normalised to Z₀. Recomputing F at Γs = 0 from Fmin/Rn/Γopt reproduces the reported NF50 to 4e-16 in ohms, and is off by 0.01 in noise factor if treated as normalised.-
- Noise temperature is per-component, not an analysis setting.
.SP … Temp="16.85"does not change device noise. Qucs defaults- components to 26.85 °C, where a 10 dB pad correctly reads 10.13 dB
1 + (L−1)·T/T₀.build_noise_netlisttherefore applies the IEEE reference 16.85 °C to resistor lines by default, leaving any explicitTempalone and never rewriting non-resistor device cards.
Added: harmonic balance via Xyce (#24, mcp-qucs-s)¶
run_harmonic_balance was scaffolded: it detected Xyce and then returned
error("not yet implemented"). It now runs a real analysis.
mcp_qucs_s.harmonic_balancebuilds the.HBnetlist, runs Xyce, and parses the frequency-domain output into a single-sided spectrum. One tone gives harmonic distortion; two give the intermodulation products, IM3, OIP3 and IIP3.sweep_compression_pointis a new tool: sweep drive level and locate P1dB by interpolating where gain falls 1 dB below its small-signal value.- Missing Xyce still returns an
error()envelope, now pointing at the source-build instructions rather than at binaries that do not exist for Debian/Ubuntu.
Validated against a closed form rather than a plausible-looking number: for
a behavioural cubic V(out) = a1·V(in) + a3·V(in)³ the third-order
intercept is exactly A = sqrt(4·a1/(3·a3)), and the computed IIP3 matches
it to 0.001 dB. IM3 tracks the textbook 3:1 slope (30.00 dB per 10 dB
of drive) and the intercept does not drift with drive level. A diode pair
is the more realistic-looking test circuit but a poor validator. Its I-V
is exponential, not cubic, so its products never follow a 3:1 slope.
Three Xyce behaviours found the hard way and now documented in
docs/installation.md:
.HBneeds oneNUMFREQentry per tone; a single value with two tones aborts the run.^in a behaviouralB-source expression makes the HB startup transient diverge; explicit multiplication works..PRINT HB_FDemits a two-sided spectrum, so a positive-frequency bin folds to twice its printed magnitude (DC does not).
Fixed: documentation (docs/installation.md)¶
The Xyce section pointed at "pre-built debs" from Sandia. Those do not exist: Sandia's Linux binaries are RHEL 8 RPMs that explicitly do not run on Debian/Ubuntu, and the team no longer ships open-source binaries at all. Replaced with the verified source build (Trilinos 14.4 + Xyce 7.10, ~17 min on 8 cores), including the GCC-15 incompatibility that otherwise fails the Trilinos build at ~30%.
Fixed: three silent-correctness bugs (#31, #32, #35)¶
.ascI/O assumed UTF-8/LF (#31). LTspice XVII writes UTF-16LE with a BOM, soread_componentsdecoded mojibake, matched noSYMBOLline, and returned{}as though the schematic held no parts. Worse,update_componentthen rewrote the file as UTF-8/LF: silently re-encoding a user-authored schematic it had never successfully read. Encoding and line endings are now detected and preserved byte-for-byte outside the edited line; an undecodable file raisesAscDecodeErrorinstead of looking empty; and editing a refdes that is not present raises rather than rewriting the file unchanged.- The ngspice netlister mis-netlisted every non-lowpass ladder (#32). It
read each element's position from its refdes letter, so a highpass
(series C, shunt L) was emitted as its dual and ngspice returned
S-parameters for a circuit nobody designed.
z0was hardcoded to 50 Ω besides. Newmcp_ltspice.asc_netlistnetlists from schematic geometry using the same coordinate rules LTspice applies, which is correct for any topology: including schematics this package did not generate, and reads the port impedance off the terminating resistors. On a generated lowpass it reproduces LTspice's own netlist node for node. Unknown symbol kinds raiseNotImplementedErrornaming the symbol instead of being skipped. cascade_networksextrapolated silently (#35). It found the common grid withnumpy.intersect1d, which needs exact float equality; two instruments never produce bit-identical grids, so real inputs hit the fallback that resampled every other network onto network 1's grid: extrapolating past the end of their measured data with no warning.deembed_networkraised in the same situation. Both now share one documented policy incommon_frequency_grid: restrict to the overlap of the measured ranges so resampling is always interpolation, raise when the ranges are disjoint, and report any trimming through the envelope'swarnings.
Fixed: Qucs-S could not be driven at all (mcp-qucs-s)¶
Running qucsator-RF 1.0.7 for the first time showed the Qucs-S backend was unreachable end to end, for three independent reasons.
- Nothing could produce simulator input.
run_sp_analysis,export_touchstoneandextract_noise_parametersall take a path to a file no code in the package could generate, so using them meant hand-authoring a schematic in the Qucs GUI first. Newmcp_qucs_s.netlistgenerates qucsator netlists, and a newsimulate_lc_ladderMCP tool goes design → netlist → simulation → Touchstone in one call. find_qucs_s()returned the GUI. Discovery triedqucs-sfirst, which is the Qt application, not the engine. Passing it-i netlist -o datopens a window and blocks forever on a headless box. Discovery now looks forqucsator_rf/qucsatorand never falls back to the GUI; when only the GUI is present the error says so and points at the missing--recurse-submodulessubmodule build.parse_qucs_datcould not read Qucs output. It calledfloat()on every line, but Qucs writes complex values as+1.23e-10+j6.18e-08, so every real.datraisedValueError. It also expected each S-parameter split into.r/.isections, a format qucsator-RF does not emit. Both layouts now parse.
Elements are specified by explicit position (series_l vs shunt_l)
rather than inferred from the refdes letter, so highpass and bandstop
ladders netlist correctly. The failure mode behind #32 in the ngspice
netlister, deliberately not repeated here.
Verified against real qucsator output: lowpass and highpass ladders
track the closed-form Butterworth response to within 0.001 dB, and a shunt
LC trap notches at its resonance. Regression cover includes a real
qucsator-RF .dat committed as a fixture, so the parser cannot drift from
the format the simulator actually writes.
sch_path is renamed to netlist_path throughout: qucsator consumes a
netlist, not the GUI's .sch, and the old name sent users looking for the
wrong file.
Fixed: generated schematics were electrically disconnected (mcp-ltspice)¶
Found by running the pipeline against real LTspice 26.0.2 (Wine) and
ngspice 44.2 for the first time. Both simulator paths were broken
end-to-end: run_simulation reported success and extract_sparameters
then failed, so no user could get S-parameters out of either.
generate_lpf_ascnever emitted a singleWIRE. The_wirehelper was dead code, and LTspice determines connectivity purely from coordinates, so it netlisted every component onto its ownNC_*nodes . No filter at all. The AC analysis returnedNo. Points: 0and a 692-byte.raw, which the artifact-presence success policy accepted. Extraction then died with "No plots found in the RAW file". Symbols are now placed by solving for the anchor that puts their pins on the intended nodes, using pin offsets from the stock symbol library and the R90 convention(dx, dy) → (-dy, dx)verified against LTspice.- S-parameter extraction required an
I(Rs1)trace that ngspice never writes, so every ngspice run raisedIndexErrorfromspicelib. The port-1 current is now derived fromV(p1)by Ohm's law, which is exact under the port convention the function already assumed. This also removes a sign-convention trap: SPICE orients a device's current by netlist node order, and LTspice emitsRs1 p1 N001for our schematic, so itsI(Rs1)runs out of port 1: pinning S11 at 0 dB for a perfectly matched filter. When the trace is present it is now used only as a consistency check, warning if magnitudes disagree. get_traceraises instead of returningNone, so the "Missing required traces"ValueErrorwas unreachable. It now reports the missing and available trace names.
Verified against both simulators: |S21| tracks the closed-form doubly-terminated Butterworth response within 0.15 dB from 0.1–4 GHz, with passband |S11| below −94 dB.
Regression cover for the class of bug, not just the instance:
test_asc_connectivity.py reimplements LTspice's coordinate-based
connectivity rule in pure Python, so CI catches a floating pin with no
simulator installed; the end-to-end tests now compare against theory
instead of asserting raw_path.is_file(), which a zero-point stub
satisfied.
Fixed: LTspice first run blocks batch mode under Wine (mcp-ltspice)¶
Recent LTspice releases open a modal "Anonymously Share LTspice Usage
Data" dialog the first time they run in a Wine prefix. It appears even
under -b, so batch runs hang until the caller's timeout with an empty
log and no .raw. The symptom points nowhere near a consent prompt.
run_simulation now warns up front when LTspice.ini is absent from
the prefix, and converts the resulting TimeoutExpired into a
RuntimeError naming the dialog and giving both remedies.
Fixed: documentation (docs/installation.md)¶
- The Qucs-S build recipe cloned without
--recurse-submodules, so qucsator-RF: the actual simulation engine: was never built; the result surfaces much later as "Qucs-S not installed". Also adds the missingflex,bison,gperfanddos2unixbuild deps (gperffails at cmake time,dos2unixonly at ~78% of the build) and correctslibqt6charts6-dev→qt6-charts-dev. - Documents the LTspice first-run dialog and the
wineboot -u/msiexec /qnsilent-install path.
Fixed: simulator portability (mcp-ltspice)¶
Reported by @cr4i50n in #28 and #29 after trying to run the LTspice server on their own machine.
- LTspice under Wine exits 1 on a successful run, so the runner's
strict return-code check rejected every good simulation. Success is
now decided by whether the
.rawartifact was produced; the return code is retained inRunResultand logged as a warning. Any stale.rawis deleted first, so the artifact cannot be a leftover. Same policy now applies to ngspice, matchingmcp-qucs-s. - Wine does not translate POSIX paths in argv, so
-Run /home/…reached LTspice as a malformed drive spec. The.ascpath is now converted withwinepath -w. - A
.exeno longer forces Wine on native Windows, andfind_ltspice()learned the Windows install locations and honoursWINEPREFIX. Windows users previously always got "LTspice.exe found but Wine is not installed". - A set-but-invalid
LTSPICE_PATHsilently fell through to ngspice; it now warns. - New
MCP_LTSPICE_SIMULATOR(ltspice|ngspice) pins simulator selection for ngspice-only deployments. Read bydetect_simulator()andrun_simulation(): deliberately not byfind_ltspice(), which must keep reporting what is actually installed.
Fixed: correctness (all packages)¶
Surfaced by enabling mypy, which had never actually run (see below).
ladder_sparams_from_componentsviolated reciprocity. S12 was computed as2·(a·d − b·c)/denom. That determinant is identically 1 for a cascade of series-Z / shunt-Y two-ports, but evaluating it numerically overflows on long ladders: a 9th-order bandstop producedinfat one bin, and the finite-value guard rewrote S12 to 0 while S21 stayed finite.series_lc_parallelleaked divide-by-zero / invalid-value RuntimeWarnings and its clamp could not fire at the anti-resonant bin: it tested the quotient (NaN there) instead of flooring the denominator before dividing, as the neighbouring trap branch does.RF_MCP_LOG_LEVEL=debugcrashed every server at import. The raw env string went straight toLogger.setLevel, which only accepts an int or an exact uppercase name. Case is now normalized, numeric levels are accepted, and an unusable value warns and falls back to INFO instead of taking down the server.mcp-rf-analysiserror envelopes were unattributable._wrapreportedfunc.__name__, but most tools pass a lambda, so failures read"<lambda> failed: …". They now name the tool that was called.- Unrecognized band filters returned an empty list, so a misspelled
region read as "there are no such bands".
list_lte_bands,list_gnss_bands, andlist_ism_bandsnow raise with the available values, matchinglist_5gnr_bands/list_halow_channels. list_spec_templatesused.suffixon aTraversable, which only exists for filesystem-backed packages:AttributeErrorwhen zip-installed.richards.py/runner.pycalled.group()on a possibly-Noneregex match, crashing on any refdes without a digit.render_responseaccepted an unvalidated list asfreq_range_hz, building a wrong-arity tuple from a 1- or 3-element list.network_from_datskipped the validation its sibling performed, turning a partial Qucs-S.datinto a bareKeyError. Both loaders now share a checked path that also rejects ragged component arrays.
Fixed: CI and tooling¶
- CI had been red since 2026-05-13.
tests/test_workspace_smoke.pyfailed bothruff format --checkandruff check, and never ran: the top-leveltests/directory was missing fromtestpaths. - mypy had never checked a single line. It aborted with "Duplicate
module named conftest" before reaching any source file, and CI hid
that with
|| true. Both fixed; the suppression is gone and the workspace is clean under the enforced rule set. - The docs site never deployed: GitHub Pages was not enabled, so
the
deployjob failed on every push whilebuildpassed. - Fork PRs never got CI. Approval was required for all first-time contributors, so neither #28 nor #29 was ever validated.
- Simulator test gating matched too broadly.
"ngspice" in item.keywordsalso matches test names and parametrize ids, so a case parametrized over the string"ngspice"was skipped as though it needed the binary. Now usesget_closest_marker. uv.lockwas left stale by the v0.4.0 version bump.
Added: tests (458 → 533)¶
mcp-rf-analysishad no server-layer tests at all; all 33 tools are now swept for the shared envelope contract, with the tool list discovered by introspection so new tools are covered automatically.mcp-qucs-shad noconftest.py, so its registeredqucsmarker gated nothing. Added, plusxyce.sparams.py. The only module that runs once Qucs-S is installed, and one that needs no binary to test: went from zero tests to full parse / round-trip / malformed-input coverage.check_coex_matrix's only test asserted nothing. It was named for a 2H collision, conceded in its own comment that the chosen pair does not collide, and checked only that two keys existed. Repointed at LTE B3 DL, which HaLow 2H = 1830 MHz genuinely falls into, plus a clean-pair negative case.- Portability coverage for the runner:
LTSPICE_PATHprecedence, Wine path translation and its fallback, native-Windows invocation, the simulator pin, and both halves of the artifact-vs-returncode policy.
[0.4.0]: 2026-05-13¶
Changed¶
- License: Apache-2.0 → AGPL-3.0-or-later (all four workspace packages: mcp-ltspice, mcp-qucs-s, mcp-rf-analysis, rf-mcp-common). Aligns with the eng-mcp-suite toolkit-wide AGPL move (the AGPL closes the "wrap as a paid SaaS without contributing back" gap by extending copyleft to network use). Underlying tools: Qucs-S (GPL), LTspice (proprietary), scikit-rf (BSD): are runtime-invoked, not redistributed by these wrappers, so the wrapper's AGPL license is independent of theirs. See the LICENSE_SUMMARY for the toolkit-wide rationale.
(no changes yet: next stream picks up from the v0.3.0 roadmap)
[0.3.0]: 2026-05-06¶
Correctness: silent failures resolved (all packages)¶
A five-agent code review identified six silent-failure paths the v0.2.0 correctness-honesty pass missed. All are fixed; the user-visible impact:
mcp-ltspice.extract_sparams_from_rawpreviously filled only the diagonal of the S-matrix, leavingS21andS12as zero. Every Touchstone file produced via the LTspice → ngspice →extract_sparametersflow claimed −∞ dB through the network, which silently failed anyevaluate_filter_speccheck. The fix recovers the full S column from a single AC sweep (S11from(V₁ − Z₀·I_Rs1)/(2√Z₀·a₁),S21fromV₂/(√Z₀·a₁)) and fills column 2 by reciprocity + symmetry for the passive ladder filters this package synthesises. New flagassume_reciprocal_symmetric(defaultTrue) for the rare case where a user wants to merge two separate sweeps.mcp-rf-analysis.cispr_limit_atlog-interpolated across the 5 MHz step in the CISPR 22/32 Class B QP table (56 dBµV → 60 dBµV), reporting ~58 dBµV at 12 MHz instead of the actual 60 dBµV. The same flaw was present in the FCC 15B mirror table. Both tables now encode the step with a paired(5e6, 56.0), (5.001e6, 60.0)entry, mirroring the pattern already used inradiated.py. Dead_FCC_15_109_B_AT_3Mtable removed.mcp-ltspice.runnernever assertedproc.returncodeon either the LTspice or ngspice path, so a non-zero exit (convergence failure, missing model lib) that left a stale.rawfrom a previous run was reported as success. TheRunResult.log_pathfield was constructed but the file was never written for the LTspice path. Both fixed: returncode is now checked explicitly with stdout/stderr tail in the exception, and the log file is persisted on every run.mcp-ltspice.srf_checksilentlycontinued past components whose vendor lookup raisedValueError/KeyError, then returnedseverity='ok'even though half the BoM was unaudited. Components that can't be audited are now surfaced via a newunauditedfield and a warning per refdes;severityrises to at leastcaution.mcp-ltspice.synthesis.lc_filterunpackedscipy.optimize.least_squaresresults without checkingres.success, so an unconverged elliptic fit silently returned wrong L/C values. Now raisesRuntimeErrorwith the optimiser status when convergence fails.
Test pinning (31 new regression tests)¶
- New
tests/test_hardening_v030.py(mcp-ltspice): contract tests for every fix above, including amonkeypatch-based round-trip ofextract_sparams_from_rawthat doesn't need a simulator install. - New
tests/test_microstrip_loss_pin.py(mcp-qucs-s): scaling-law tests for dielectric and conductor loss (linear intan_d, linear inffor α_d, √f for α_c, 1/√σ for α_c) plus order-of-magnitude guardrails across FR-4 / Rogers / Duroid at 1–28 GHz. Catches missing- factor and wrong-conversion bugs without depending on textbook example values that vary across editions. - HPF / BPF / BSF response now verified at orders 3, 5, 7, 9 (was only 3 and 5 before). BPF additionally checked at narrow (Δ ≈ 0.01) and wide (Δ ≈ 0.67) fractional bandwidth. The regimes where classical LPF→BPF formulas break down.
- Vendor parasitic substitution:
lookup_part("coilcraft_0402hp", 4.7e-9)now pinned to return an SRF in the documented 4–8 GHz neighbourhood, not just "non-zero". - CISPR 22/32 Class B step at 5 MHz pinned with explicit
at 5.0 MHz/at 5.1 MHz/at 15 MHzregression tests.
Security¶
SECURITY.mdnow spells out the untrusted-.ascattack surface explicitly.runner.pyinvokes external simulators on the supplied path with no sandboxing; a malicious.asccan include.SUBCKT,.lib, or.includedirectives the simulator will load. Document guidance: run inside a container/chroot if accepting external schematics.
Cleanup¶
- Empty
packages/mcp-ltspice/src/mcp_ltspice/resources/scaffolding tree (specs/ templates/ models/{coilcraft,johanson,murata,tdk}/, all empty) removed. tasks/(private workflow scratch) untracked and gitignored.- All four packages bumped from 0.1.0 (stale since the suite tagged v0.2.0 without bumping pyprojects) to 0.3.0 to align with the suite tag.
Test count¶
458 passed, 4 simulator-gated skips, 0 failures (was 427 / 4 / 0 in 0.2.0).
0.2.0: 2026-04-28¶
Filter synthesis: HPF / BPF / BSF (mcp-ltspice)¶
Closes the biggest user-visible gap in filter coverage. Three new top-level synthesis functions backed by classical Pozar §8.5 frequency transformations from the LPF prototype:
synthesize_lc_hpf(filter.synthesize_lc_hpf): high-pass via series-L → series-C and shunt-C → shunt-L. Component count equals the LPF prototype's. -3 dB at the specified cutoff (Butterworth) or equiripple edge (Chebyshev).synthesize_lc_bpf(filter.synthesize_lc_bpf): band-pass via series-L → series-LC tank, shunt-C → shunt-LC tank. Component count doubles. f₀ = √(f_low · f_high), Δ = (f_high − f_low) / f₀; each LC pair resonates at f₀ exactly.synthesize_lc_bsf(filter.synthesize_lc_bsf): band-stop via series-L → series parallel-LC (anti-resonant), shunt-C → shunt series-LC (resonant). Used to notch a specific band (LO leakage, image rejection).
Currently supports Butterworth and Chebyshev I across all three. Elliptic HPF/BPF/BSF needs a separate transformation for finite transmission zeros and is on the roadmap.
Analytical S-parameter analysis is wired up for all four kinds (LPF, HPF, BPF, BSF). components_dict_to_elements extended with a kind parameter ("lowpass", "highpass", "bandpass", "bandstop"). Three new ABCD element types added to support BPF/BSF resonator topologies:
series_lc_series: series-LC in main path (BPF series section).Z = sL + 1/(sC): dips at f₀.shunt_lc_parallel: parallel-LC to ground (BPF shunt section).Y = sC + 1/(sL): dips at f₀.series_lc_parallel: parallel-LC in main path (BSF series section).Z = sL/(s²LC+1): peaks at f₀.
The existing shunt_lc_trap kind (series-LC to ground; elliptic LPF trap) doubles as the BSF shunt section. Verified BPF response: -3 dB at band edges, deep stopband > 50 dB one decade out. Verified BSF response: > 60 dB notch at f₀, lossless passband one decade out.
Substrate preset library + microstrip loss (mcp-qucs-s)¶
- New
mcp_qucs_s.substratesmodule with 16 curated presets covering FR-4 (4 thicknesses), Rogers RO4350B (3), Rogers RO4003C (2), RT/Duroid 5880 (2) + 6002, PTFE, Isola FR408HR (2), Taconic TLY5. Each carries documented εr / h_mm / t_um / tan_d from the manufacturer datasheet. - New
list_substrate_presets_toolMCP tool returns the full catalogue with descriptions. synthesize_microstrip_lineandanalyze_microstrip_toolnow accept a preset name string (e.g."Rogers4350B_0508") as thesubstrateargument in addition to the parameter dict. Saves engineers from re-typing{er, h_mm, t_um, tan_d}every call.analyze_microstripnow uses the substrate'stan_d(previously accepted but ignored) to compute conductor + dielectric attenuation in dB/mm, surfaced asalpha_d_db_per_mm,alpha_c_db_per_mm,alpha_total_db_per_mm. Pozar §3.8.1 dielectric formula plus skin-effect conductor loss (default σ = 5.8 × 10⁷ S/m for copper; override for gold / aluminium / measured plating). Verified against published values for FR-4 / Rogers / Duroid at 5 GHz.
Monte Carlo trace mode (mcp-ltspice)¶
monte_carlo_analysis gains a trace=True flag that emits a JSONL file (one record per trial: seed, sampled components, metrics, pass/fail status, failures list). Lets engineers do offline sensitivity / root-cause analysis of yield loss without re-running MC. Default path is mc_trace_<base_seed>.jsonl in cwd; override via trace_path.
monte_carlo_analysis(transmission_zeros=...) default changed from True to None: now auto-infers topology from the components dict (matches the components_dict_to_elements behaviour added earlier in this Unreleased stream). Old code passing True/False continues to work unchanged.
Tests¶
30 new tests across test_hpf_bpf_bsf_synthesis.py, test_substrate_presets.py, test_mc_trace.py. Total pass count 421 (+30 vs. prior 391 baseline), 0 regressions.
mcp-ltspice: power-supply EMC pre-compliance toolkit¶
Five new tools fill the gap between SMPS sizing (existing buck / boost / LDO) and a real product passing conducted-emissions:
design_pi_output_filter(power.design_pi_output_filter): Pi-section LC output filter (C-L-C, 3rd-order LPF) sized for an attenuation target at a given frequency. Returns L, C_in, C_out, resonant frequency, achieved attenuation at f_target / f_sw, plus a damping-resistor recipe and BOM notes (inductor saturation, MLCC DC-bias derating).design_dm_input_filter(power.design_dm_input_filter): 2nd-order differential-mode LC input filter sized for a conducted-emissions target. Includes the Middlebrook stability check (|Z_out_filter| < |Z_in_converter| / safety_factor) so the filter doesn't destabilise the converter's loop, and surfaces a damping-branch recipe (R_d, C_d) per Erickson & Maksimović §10.4.predict_conducted_emissions(power.predict_conducted_emissions): harmonic decomposition of a trapezoidal switching waveform with two-sinc envelope (duty-cycle and edge-rate cutoffs), LISN-loaded prediction, CISPR 22 / 32 Class A or B limit overlay (QP or AVG detector). Returns per-harmonic frequency / emission / limit / margin arrays plus a worst-margin pass/fail summary.design_rc_snubber(power.design_rc_snubber): RC snubber for switch-node ringing. Inputs: parasitic loop inductance, switch C_oss, peak voltage, switching frequency. Returns R, C, ring frequency, achieved damping factor, and per-cycle dissipation. Standard recipe: C_snub = C_oss, R_snub = √(L_par/C_oss) × 2ζ.design_cm_choke(power.design_cm_choke): common-mode choke selection from a curated catalogue (Würth WE-CMB, TDK ZJYS / ACT, Murata DLW). Filters by DC current rating, target CM impedance at design frequency, and DM-leakage cap. Returns ranked candidate list plus the highest-margin pick.
All five register under both their flat names and power.* namespaced aliases. Tests in test_power_emc.py (31 cases) cover return-shape, math correctness (resonance / attenuation / dissipation scaling), CISPR class / detector relationships, and edge cases.
mcp-qucs-s: implementation status corrections¶
Earlier [Unreleased] notes claimed "Implemented all 4 simulator-driven tools with graceful degradation." This is corrected to the actual status:
- Implemented (4 closed-form synthesis tools, no Qucs-S install required):
synthesize_microstrip_line: Hammerstad-Jensen W/L from Z₀analyze_microstrip_tool: Z₀ / ε_eff / wavelength from Wsynthesize_coupler: branch-line / rat-race / coupled-line / Lange (single-line approximation; coupled-line gap synthesis pending)lumped_to_distributed: Richards transformation + Kuroda identities- Implemented (2 simulator-driven tools, require Qucs-S):
run_sp_analysis: Qucs-S.datparser + Touchstone exporterexport_touchstone. Run + export.s2pin one call- Scaffolded: return
error("not yet implemented")envelope (Tier-6 roadmap): run_harmonic_balance: detects Xyce, but Xyce-netlist generation and harmonic-content parsing (IM3 / IIP3 / 1 dB compression) are pendingextract_noise_parameters: detects Qucs-S, but noise-analysis dataset parser (Fmin / Γopt / Rn / NF50) is pending
Both scaffolded tools previously returned ok() envelopes with a "note: scaffolded" field: calling agents could mistake this for success. They now return error() envelopes with clear "not yet implemented" messages.
- The README's tool table previously listed
synthesize_microstrip_filter(stepped-impedance / hairpin / interdigital filter realisations). The tool was never implemented. The row has been removed; filter synthesis in the distributed domain is on the Tier-6 roadmap.
Architecture hygiene (mcp-ltspice)¶
- Vendor catalogue:
JOHANSON_LandTDK_MLGwereCOILCRAFT_0402HP.copy(): alias-only, not real data. Replaced with measured tables for the Johanson L-07W series (0402) and TDK MLK1005S series. compare_filter_ordersnow acceptssrf_marginandmax_value_drift_pctand passes them through to the innersubstitute_real_componentscall. Previous behaviour ran the comparison with legacy semantics, so winners could differ from a final design that enabled SRF gating.- Tool namespacing: every flat tool name now has a namespaced alias (
filter.*/analog.*/power.*/digital.*/vendor.*/sim.*/coex.*). Old names continue to work but emit a one-shotDeprecationWarningper process pointing to the namespaced equivalent. Removal in a later major release.
Correctness fixes (mcp-ltspice)¶
components_dict_to_elements(components, transmission_zeros=...). Thetransmission_zerosflag now defaults toNone(auto-infer from the components dict) instead ofFalse. Prior default silently produced wrong S-parameters when an elliptic ladder was passed without the flag set explicitly. ExplicitFalseon elliptic-shape components now emits aRuntimeWarning.place_transmission_zeroandtrap_lc_for_freq. Thepreserve_ratio: boolparameter is replaced bymode: Literal["preserve_ratio", "hold_l", "hold_c"]. The old API withpreserve_ratio=Falseand bothl_existingandc_existingprovided fell through none of the conditional branches and silently substitutedL=1 nH. That bug is fixed. Legacypreserve_ratiocalls continue to work via a deprecation shim.- Elliptic synthesis (
synthesize_lc_lpffilter_type="elliptic"). The reportedtransmission_zeros_hzfield was inconsistent with the actual1/(2π√(L_k C_k))of the synthesised trap pairs at certain orders. Root cause:_fit_lc_to_prototypewas an unconstrained least-squares fit over (L, C) pairs that drifted the L·C product away from the target trap resonance. Fix: each trap now has onlyL_trapas an optimisation variable;C_trapis computed as1/(ω_zk² · L_trap)so the resonance is pinned exactly to the prototype's transmission zero. New regression test (test_elliptic_synth_consistency.py) covers orders 3 / 5 / 7 / 9 across multiple(fc, ripple, stopband)combinations and asserts the reported and achieved TZ frequencies agree to within 1 %.
Vendor catalogue (mcp-ltspice): Johanson L-07W and TDK MLK1005S replacements¶
The previous JOHANSON_L = COILCRAFT_0402HP.copy() and TDK_MLG = COILCRAFT_0402HP.copy() aliases were misleading. They suggested broader vendor coverage than the package actually had. Replaced with nominal datasheet-derived tables for:
- Johanson L-07W series (0402): 19 values from 1.0 nH to 39 nH, with SRFs ~10–15 % higher than Coilcraft 0402HP at equal inductance (wirewound construction). Extends the available value range upward.
- TDK MLK1005S series (0402): 19 values from 0.6 nH to 22 nH, extending the catalogue to sub-nH values that Coilcraft 0402HP doesn't carry.
These are first-order parasitic estimates suitable for synthesis-time sims; for design-final precision, fetch a real S-parameter file from the vendor (planned via the vendor.fetch_* tools on the Tier-3 roadmap).
Docs / branding¶
- Logo SVG (full and mark variants) under
assets/ - mkdocs-material site at
mkdocs.ymlwith auto-generated tool API docs via mkdocstrings - Per-page docs: index, getting-started, architecture, installation, tool-catalog, examples, reference (envelope / Touchstone / E-series), contributing, changelog, security
.github/workflows/docs.ymlbuilds and deploys to GitHub Pages
Repo conventions¶
- Issue templates: bug, feature, question (config disables blank issues)
- Pull request template
- CODEOWNERS
- Dependabot config (weekly, grouped dev-tools / runtime / actions)
- Code of Conduct (Contributor Covenant 2.1)
0.1.0: 2026-04-22¶
Initial release of the four-package monorepo.
rf-mcp-common 0.1.0¶
- Envelope response model with
ok()/error()constructors andTimerfor runtime metadata. - Hz-strict Touchstone I/O wrapping
skrf.Network:read_touchstone,write_touchstone,network_to_touchstone,sparams_at. - E6/E12/E24/E48/E96/E192 component snap helper with signed percent error.
- Frequency, dB, dBm conversion helpers.
- Structured JSON logger +
tool_timercontext manager.
mcp-ltspice 0.1.0¶
- 11 MCP tools covering the full synthesis-through-Monte-Carlo workflow:
run_simulation: LTspice (Wine) or ngspice fallbackextract_sparameters:.raw→.s2pvia voltage/current methodsynthesize_lc_filter: Butterworth + Chebyshev I (closed-form g) + Elliptic (scipyellipap+ weighted least-squares LC fit)place_transmission_zero: Move shunt-LC notch to a target frequency, preserve L/C ratio, snap to E24/E96find_transmission_zeros: peak-detect notches in S21substitute_real_components: vendor parasitic tables (Coilcraft 0402HP / 0603CS, Murata GJM C0G, Johanson L, TDK MLG)evaluate_filter_spec: pass/fail per criterion with margin in dBoptimize_filter: Nelder-Mead, loss = sum of negative margins, E24/E96 snapmonte_carlo_analysis: joblib parallel Gaussian sampling, yield% + per-metric histogramsstability_check: Rollett K, |Δ|, Edwards-Sinsky μrender_response: Bode PNG with frequency marker lines- Analytical ABCD-chain S-parameter computation in
extract.py(no simulator required for synthesis sanity checks) - LTspice
.ascgenerator + spicelib-based reader/modifier
mcp-rf-analysis 0.1.0¶
- 19 MCP tools, all simulator-agnostic (Touchstone in/out)
- Bundled regulatory databases:
- LTE bands 1-71 (3GPP TS 36.101)
- 5G NR FR1 + FR2 (3GPP TS 38.101)
- GNSS (GPS L1/L2/L5, GLONASS L1/L2, Galileo E1/E5/E6, BeiDou B1/B2/B3)
- ISM allocations by ITU region
- 802.11ah HaLow channels per region (US, EU, JP, KR, CN, SG, AU/NZ, IN)
- FCC §15.247 + §15.205 restricted bands
- ETSI EN 300 220 / 300 328 / 301 893 limits
- 3GPP UE spurious + GNSS protection
- Spec template library (FCC Part 15.247, HaLow US LPF)
- Multi-radio coex matrix with predicted desense per aggressor/victim pair
- Link budget tools (Friis, log-distance, antenna isolation, desense)
mcp-qucs-s 0.1.0¶
- Initial scaffold + status tool. Full implementation lands in Unreleased above.
Examples¶
examples/halow_lpf/: full end-to-end design of a 9th-order elliptic LPF for 802.11ah HaLow + multi-radio coex. Passes all 8 spec criteria with 86.6% Monte Carlo yield at 2% component tolerance.