New MCP tool `export_sch_pdf` wrapping `kicad-cli sch export pdf`: exposes
the full option set (drawing-sheet override, theme, B&W,
exclude-drawing-sheet, default-font, the PDF property-popup /
hierarchical-link / metadata excludes, no-background-color, and page
selection). schematicPath is required and validated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New MCP tool `export_sch_bom` wrapping `kicad-cli sch export bom`: exposes
the full BOM option set (presets, field/label lists, group-by, sort
field/direction, filter, exclude-DNP, include-excluded-from-BOM, and the
field/string/ref/ref-range delimiters, plus keep-tabs/keep-line-breaks).
schematicPath is required and validated. Self-contained interface handler.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New MCP tool `export_3d_cli` wrapping the kicad-cli 3D subcommands. A
`format` enum {step,glb,stl,ply,brep,xao,vrml} selects the subcommand and
the handler forwards only flags valid for that subcommand: the shared
geometry/include set for step/glb/stl/ply/brep/xao, no-optimize-step for
STEP only, and units + models-dir/models-relative for VRML. Rich CLI
sibling of the existing export_3d / export_vrml (left untouched). Reads
the saved .kicad_pcb.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New MCP tool `export_gerber_single` wrapping `kicad-cli pcb export gerber`:
plots the selected layers to a SINGLE Gerber file (singular sibling of
export_gerbers). Exposes the full single-file Plot option set (X2, netlist
attributes, the four DNP fab-layer modes, soldermask subtraction, aperture
macros, drill-file origin, precision, Protel extension). Reads the saved
.kicad_pcb. Note: this subcommand is deprecated in KiCad 9.0 (still exits 0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New MCP tool `export_pcb_dxf` wrapping `kicad-cli pcb export dxf`: exposes
the full layer-plot surface (layer/common-layer lists, refdes/value
exclusion, soldermask subtraction, use-contours, use-drill-origin,
border+title, output-units, the four DNP fab-layer modes, drill-shape, and
the single/multi output modes). Reads the saved .kicad_pcb.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New MCP tool `export_pcb_svg` wrapping `kicad-cli pcb export svg`: exposes
the full layer-plot surface (layer/common-layer lists, mirror, soldermask
subtraction, negative, B&W, theme, the four DNP fab-layer modes,
page-size-mode, fit-page-to-board, exclude-drawing-sheet, drill-shape, and
the single/multi output modes). Rich CLI sibling of the existing SWIG
export_svg (left untouched). Reads the saved .kicad_pcb.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New MCP tool `export_pcb_pdf` wrapping `kicad-cli pcb export pdf`: exposes
the full layer-plot surface (layer/common-layer lists, mirror,
refdes/value exclusion, border+title, soldermask subtraction, the four
DNP fab-layer modes, negative, B&W, theme, drill-shape, and the
single/separate/multipage output modes). Rich CLI sibling of the existing
SWIG export_pdf (left untouched). Reads the saved .kicad_pcb.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New MCP tool `export_gencad` wrapping `kicad-cli pcb export gencad`:
exposes define-var plus the flip-bottom-pads, unique-pins,
unique-footprints, use-drill-origin and store-origin-coord flags.
Self-contained interface handler reading the saved .kicad_pcb.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New MCP tool `export_ipcd356` wrapping `kicad-cli pcb export ipcd356`:
generates an IPC-D-356 bare-board electrical-test netlist for flying-probe
and bed-of-nails testers. Minimal option set (outputPath + boardPath only,
matching the subcommand's --help). Self-contained interface handler reading
the saved .kicad_pcb.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New MCP tool `export_odb` wrapping `kicad-cli pcb export odb`: precision,
compression mode (zip/tgz/none), and units. Single ODB++ job archive for
CAM/MES/assembly. Self-contained interface handler reading the saved
.kicad_pcb.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New MCP tool `export_ipc2581` wrapping `kicad-cli pcb export ipc2581`:
precision, compression, standard version, units, and the five
--bom-col-* field mappings (internal id, mfg P/N, mfg, distributor P/N,
distributor) that control the BOM part data embedded in the IPC-2581
file — directly useful for MES/assembly imports that need a real
internal P/N rather than the description. Self-contained interface
handler reading the saved .kicad_pcb.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New MCP tool `export_gerbers` wrapping `kicad-cli pcb export gerbers`,
exposing the complete Plot-dialog option surface that the existing
SWIG-based export_gerber lacks: layer + common-layer lists, X2 on/off,
netlist attributes, soldermask subtraction, aperture macros, the three
DNP fab-layer modes, refdes/value exclusion, border+title, drill-file
origin, coordinate precision (5/6), Protel vs KiCad extensions, and
--board-plot-params to reuse the board's stored plot settings.
Implemented as a self-contained interface handler (_handle_export_gerbers)
that resolves the board path (param or current board) and shells out to
kicad-cli, mirroring the _handle_export_netlist pattern — no dependence
on a live SWIG board, since the KiCad IPC API exposes no plot/export
endpoints. Reads the saved .kicad_pcb on disk.
Wiring: dispatch map + src/tools/export.ts registration + export
category in registry.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PyMuPDF (`fitz`) has no type stubs, so mypy's import-not-found fails on
any edit to files importing it (commands/board/view.py,
kicad_interface.py). Same treatment as the existing pcbnew/kipy/PIL
entries. Needed so the export-tool commits on this branch pass the mypy
pre-commit hook independently.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds batch authoring tools that collapse the dozens of round-trips needed to
stand up a schematic into a handful: batch_add_components,
batch_edit_schematic_components, replace_schematic_component,
batch_add_no_connects, batch_connect, and batch_add_and_connect (place a set of
parts and wire them in one call). These reuse the existing single-item handlers
internally, so behavior matches exactly.
- python/commands/schematic_batch.py: SchematicBatchCommands(iface)
- python/commands/schematic_text_utils.py: shared .kicad_sch text helpers
- src/tools/schematic-batch.ts + registry 'schematic_batch' category
- python/kicad_interface.py: import + instantiate + dispatch routes
- tests/test_schematic_batch.py: 13 unit tests
Note: schematic_text_utils.py is also shipped by the field-placement and
hierarchy PRs; if those merge first, drop the duplicate copy on rebase.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds field-placement tools:
- set_schematic_property_position / batch_set_schematic_property_positions:
move a symbol's Reference/Value field labels
- autoplace_schematic_fields: place every symbol's fields clear of its body
and nearby net labels (the #1 readability problem in generated schematics)
- check_schematic_layout: audit out-of-bounds / fields-in-body / duplicate
labels (note: overlaps upstream find_overlapping_elements etc. — reuse or
drop on request)
Generic .kicad_sch text helpers are factored into commands/schematic_text_utils.py
so the batch/hierarchy modules don't import from one another.
- python/commands/schematic_text_utils.py: shared text/S-expr helpers
- python/commands/schematic_field_layout.py: SchematicFieldLayoutCommands
- src/tools/schematic-layout.ts + registry 'schematic_layout' category
- python/kicad_interface.py: import + instantiate + dispatch routes
- tests/test_schematic_field_layout.py: 23 unit tests incl. end-to-end on real .kicad_sch
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds add_hierarchical_sheet (insert a sheet symbol referencing a child
.kicad_sch, with sheet_instances + fixed component instance paths) and
create_hierarchical_subsheet (create the child file + wire it into the parent
in one call). Upstream has add_sheet_pin / add_schematic_hierarchical_label
but no way to create a sheet or stand up a child sheet, so hierarchical
designs can't be built through the MCP server today.
- python/commands/schematic_hierarchy.py: SchematicHierarchyCommands
- src/tools/schematic-hierarchy.ts + registry 'schematic_hierarchy' category
- python/kicad_interface.py: import + instantiate + dispatch routes
- tests/test_schematic_hierarchy.py: 5 unit/integration tests on real .kicad_sch
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds list_symbol_pins and batch_list_symbol_pins: read a symbol's pin
number/name/type/local-position straight from the .kicad_sym libraries,
without placing it on a schematic. Fills a gap in library_symbol.py
(which can search/list symbols and read properties but not pins) and
complements get_schematic_pin_locations (placed-symbol coords only).
- python/commands/symbol_pins.py: SymbolPinCommands (stateless)
- src/tools/library-symbol.ts: tool wrappers; registry 'symbol_pins' category
- python/kicad_interface.py: import + instantiate + dispatch routes
- tests/test_symbol_pins.py: 12 unit tests (MagicMock loader; no system KiCad)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Swap Phase 3 (transport connect) before Phase 4 (warm-up) in
server.ts startup. Previously the transport was connected only after
warm-up completed (55-125s), which exceeded the MCP client's 30s
connection timeout on macOS cold-start.
PR #210 fixed the Python-side by daemon-threading warm_cache, but
the Node-side still blocked transport connect on warm-up completion.
Complements #210. Replaces the server.ts portion of #206.
When a project is opened for the first time (e.g. right after
create_project) the sym-lib-table may not exist on disk yet.
SymbolLibraryManager.__init__ succeeds but leaves self.libraries={}.
The original guard in use_project() was:
if self.library_manager.project_path == project_path:
return
Because the path already matched the newly-created manager, the
early-return fired and the manager was never rebuilt once the
sym-lib-table appeared. All subsequent list_symbols / search calls
returned nothing.
Fix: also require that at least one library was loaded before
treating the cache as valid.
Adds three unit tests that cover:
- rebuild triggered when project_path matches but libraries={}
- no spurious rebuild when libraries are already loaded
- rebuild on project_path change (existing behaviour)
The server wrote gigabytes to ~/.kicad-mcp/logs and ignored LOG_LEVEL. Three
root causes, all fixed here (the logging carve-out of #182):
- Python (kicad_interface.py): replace the unbounded FileHandler with a
RotatingFileHandler (10 MB x 3 backups, env-tunable via KICAD_MCP_LOG_MAX_BYTES
/ KICAD_MCP_LOG_BACKUP_COUNT); read the level from KICAD_MCP_LOG_LEVEL or
LOG_LEVEL (default INFO) instead of hardcoding DEBUG; mute the noisy
skip / skip.sexp.* loggers to WARNING unless KICAD_MCP_DEBUG_SKIP is set.
- TypeScript (config.ts): honor KICAD_MCP_LOG_LEVEL / LOG_LEVEL for the TS logger.
- TypeScript (logger.ts): size-cap the per-day log files with the same env knobs.
- Docs + a no-network test for the env helpers, skip muting, and that no
unbounded handler targets kicad_interface.log.
Verified: LOG_LEVEL is now applied, skip is muted, and the file rotates instead
of growing forever. Full suite unchanged from baseline.
The hierarchical-sheet rewrite from #182 is intentionally left out (stays as a
separate PR pending the #169/#170 design discussion).
Co-Authored-By: angelorodem <angelorodem@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
findPythonExecutable already auto-detects KiCAD's bundled python across versions
(10.0/9.0/…) and both Program Files and per-user %LOCALAPPDATA% installs, but the
spawn still defaulted PYTHONPATH to a hardcoded "C:/Program Files/KiCad/9.0/..."
path. So a KiCad 10 or per-user user whose python.exe was found correctly still
got the wrong PYTHONPATH unless they set it by hand — the exact manual fixup
people keep posting in #84.
Add deriveKiCadSitePackages(pythonExe): for a detected KiCAD python at
<root>/<version>/bin/python.exe it returns the matching site-packages
(<...>/bin/Lib/site-packages, or the older lib/python3/dist-packages layout),
verified to exist. Use it as the PYTHONPATH default. Precedence is unchanged at
the top: explicit PYTHONPATH env override still wins; the legacy 9.0 string
remains only as a last-resort fallback, so this can't regress below current
behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
KiCad 10 changed `kicad-cli pcb export svg` in three ways that broke
get_board_2d_view (reported with a verified fix in #209):
- `--output` is now a FILE path, not a directory (a dir fails with
"Failed to create file '<dir>'").
- `--mode-single` is required to merge layers into one SVG (the default
multi-file behavior is deprecated).
- exit code 2 is returned for the deprecation warning even on success.
Pass a file path to `--output` and add `--mode-single` (both also valid on
KiCad 8/9). Stop gating on the exit code — judge success by whether an SVG was
actually produced — so the exit-2 deprecation warning is no longer a false
failure. Add regression tests for the exit-2-is-success case and the command
shape (file output + --mode-single).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bhoot found that download_cdfer 416-loops when a complete cdfer.sqlite3 already
exists in the cache (e.g. a prior run downloaded it but died before
conversion/cleanup): resuming a complete file sends Range: bytes=<size>- which
the server answers with HTTP 416, and raise_for_status treated that as a
retryable error, spinning until max_retries then failing.
Fix: HEAD once for Content-Length up front and short-circuit when the local file
already matches the full size; and in the loop, treat a 416 as "complete" when
the size matches (else drop the stale partial and restart fresh). Remove the now
-unused _head_last_modified helper (HEAD is done inline). Add a no-network test.
Verified live: happy path still downloads+converts 616k parts (~50s); a
pre-existing complete file now short-circuits in ~0.4s instead of looping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The background warm-cache change made list_symbols() guard cache writes with
self._cache_lock. _manager_for_fixture() builds the manager via __new__ (to skip
disk I/O to system libs), bypassing __init__ where the lock is created, so the 8
SPICE-parsing tests raised AttributeError. Set the lock in the fixture. The
string-aware parser itself is correct — all 8 pass once the lock exists.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add optional `justify` property to `fieldPositions` entries in
`edit_schematic_component` and to `set_schematic_component_property`.
Changes:
- `python/kicad_interface.py`: new `_set_justify_on_property()` helper
that adds/replaces/removes the `(justify ...)` token inside a property's
`(effects ...)` block. Passing "center" (the KiCad default) removes the
directive entirely. Integrated into `_set_property_in_block()` (for the
`properties` dict path) and into the `field_positions` loop in
`_handle_edit_schematic_component()`. `_handle_set_schematic_component_property()`
now forwards `justify` from params through to the spec dict.
Also fixes pre-existing mypy type-ignore on `circle.radius` (kipy stub).
- `src/tools/schematic.ts`: extend the `fieldPositions` Zod schema to accept
`justify?: string | string[]` (array form normalised to a space-separated
string before the Python call). Add `justify?: string` to
`set_schematic_component_property`.
- `python/commands/routing.py`: fix pre-existing mypy error — annotate `ex`
and `ey` as `float` in `_point_to_segment_distance_nm`.
- `python/commands/pin_locator.py`: fix pre-existing mypy error — use explicit
`str()` cast on `pin_data["number"]` before `dict.get()` call.
- `tests/test_schematic_field_justify.py`: 14 unit + integration tests
covering add/replace/remove of the justify directive and backward
compatibility (calls without justify leave existing directives untouched).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cacb27b omitted the closing parenthesis anchor in _extract_lib_property_positions, leaving lib_props empty and falling back to generic resistor positions.