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.
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>
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>
- New angle parameter for component rotation (degrees, KiCad CCW-in-screen)
- New mirrorY parameter for horizontal symbol flipping (transistor orientation)
- Property positions (Reference, Value, Footprint) automatically rotated
- Property text effects preserved from library definitions
- Updates to add_schematic_component tool with new parameters
- Implements proper S-expression formatting for (mirror y) attribute
- Text angles and offsets derived from library properties for correct placement
People want to be able to pull the whole catalog, and prior downloads stalled or
repeated the same parts. Two fixes:
- Full catalog correctness: yaqwsx's cache.sqlite3 (the full ~10GB set) stores
category/manufacturer as IDs with no v_components view, so the convert left
those fields blank. Build an equivalent v_components join for yaqwsx-style
sources so the full catalog converts with category/subcategory/manufacturer
populated. Clarify in the tool schema that source="yaqwsx" = full catalog
(needs 7z) vs cdfer = in-stock subset.
- Resumable downloads: CDFER stream download now uses a read timeout and resumes
from the partial file via HTTP Range on interruption (up to 5 retries); the
yaqwsx curl calls use -C - / --retry. Addresses the stall/partial-download
complaints.
Adds a yaqwsx-schema conversion test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per review feedback: CDFER's upstream scraper pipeline has stalled for weeks
(broken cart API + a bug in their scraper), so a "fresh download" can still be
old data. Compute the catalog age from the source Last-Modified header, expose
catalog_age_days, and emit a stale=True + warning when older than 14 days that
points users to source='yaqwsx' (fresh, needs 7z) or source='official'. Surface
the warning in the MCP tool output.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The download_jlcpcb_database tool paged the community JLCSearch API with an
offset parameter, but that endpoint is a search front-end that ignores offset
and returns the same first 100 parts on every page, so a full catalog download
was impossible.
Add commands/jlcpcb_downloader.py with a layered strategy that reuses prebuilt
catalogs the whole ecosystem already trusts:
- CDFER single-file SQLite (primary; no 7z/zip, reliable on Windows)
- yaqwsx/jlcparts split 7z (fallback; only if a 7z CLI is present)
- official JLCPCB API (optional; cursor pagination, if credentials set)
Conversion reads CDFER's v_components view (or sniffs the largest table for
yaqwsx), C-prefixes integer lcsc, derives library_type from basic/preferred,
maps mfr->mfr_part, and normalizes price JSON to the manager's [{qty,price}]
shape. Rewire _handle_download_jlcpcb_database to use it (closing/reopening the
manager connection so the on-disk db can be rewritten on Windows). Remove the
broken offset loop from jlcsearch.py (client kept for interactive lookups).
Reduce download_jlcpcb.py to a thin CLI wrapper and update the TS tool schema.
Verified end-to-end against live CDFER: 616k parts downloaded + converted in
~40s, FTS search and price-break parsing correct. New unit tests cover the
conversion and source fall-through; no network in tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three-phase MCP server startup: wait for Python READY handshake, send
_warmup command (pcbnew.BOARD() triggers wxApp init on macOS), connect
to MCP transport only after warm-up completes.
Also pre-populate symbol library cache during SymbolLibraryManager
init so the first search_symbols call doesn't parse 241 .kicad_sym
files from disk (30-120s). Both warm-up and cache population happen
before tools are registered with the MCP client.
Fixes#195
* feat(units): add mil unit support across all position/coordinate commands
KiCad natively supports mils, so the MCP server should too. Added "mil"
as a valid unit option in tool schemas and updated all unit-to-nanometer
scale conversions across component, routing, outline, view, and IPC
handler code paths. 1 mil = 25400 nm (0.0254 mm).
Also fixes a pre-existing mypy overload error in pin_locator.py (str cast
on dict.get key) that was blocking pre-commit on any Python file change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(units): add mil to TypeScript tool schemas
The Python-side mil support was added but the actual input validation
happens in the TypeScript/Zod schemas. Updated all z.enum(["mm", "inch"])
to include "mil" across board, component, routing, design-rules, and
export tool definitions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(tools): replace CP-1252 mojibake with correct Unicode in board.ts
Replace U+00C3 U+00D7 (×) with U+00D7 (×) in add_logo size output string.
Character was mangled when file was saved as CP-1252 instead of UTF-8.
* fix: restore em-dash and fix pre-commit mypy in component/routing
component.py: replace CP-1252 mojibake (â€") with correct Unicode
em-dash (—) in the 'Add to board first' comment. Addresses
maintainer review on PR #162.
routing.py: annotate ex/ey as float at first assignment site in
_point_to_segment_distance_nm so mypy pre-commit hook passes
cleanly on this branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop GND stitching vias across the board with collision checking
against every non-GND segment, via, and pad on every copper layer.
PTH vias penetrate the full stackup, so an F.Cu-only check (the most
common shortcut) silently creates shorts on inner / B.Cu copper —
this implementation explicitly walks all layers.
grid Regular grid across the board interior. Default
spacing 5mm.
around_refs Densify around specified footprints (e.g. MCUs,
switching regulators, RF parts). Configurable
density via densifyRadius.
in_zones Restrict placements to candidates inside the filled
polygons of GND copper zones, so each new via lands
on copper that's already a GND equipotential.
Recommended on boards where the GND zone is fragmented:
these vias actually stitch real polygons rather than
floating on silkscreen.
All three strategies use the same collision check + intra-call
clump-prevention, so passing `["grid", "around_refs", "in_zones"]`
is a safe kitchen-sink configuration.
- Auto-detect GND net (tries GND / GROUND / VSS / /GND in order)
OR explicit `gndNet` parameter.
- Per-via geometry control: viaSize, viaDrill, clearance.
- edgeMargin: keep-out distance from board edge.
- maxVias: cap on total placements (useful for incremental work).
- dryRun: return placements without modifying the board — for
previewing before committing.
- Validates viaDrill < viaSize, rejects unknown strategy names,
surfaces clear errors when GND net can't be resolved or the
board outline is missing.
Approach ported from morningfire-pcb-automation
(https://github.com/NiNjA-CodE/morningfire-pcb-automation,
scripts/ground/add_gnd_vias.py). The original parses the PCB text
with regex and writes vias by string concatenation; this port reads
obstacles via the pcbnew API (handles rotated footprints, integrates
with the live in-memory board so two sequential calls see each
other's placements, picks up net codes from the loaded board) and
adds the in_zones strategy, the maxVias cap, and dry-run mode.
Credit is in the docstring, the TypeScript wrapper comment, the MCP
tool description (visible to clients), and the CHANGELOG entry.
tests/test_add_gnd_stitching_vias.py — 18 cases, all passing.
Uses mocked pcbnew objects so the suite runs under both the conftest
stub and a real pcbnew install.
- grid strategy fills empty board with correct count
- collision blocks via near a signal track (with extent assertion)
- GND-net obstacles are correctly ignored
- around_refs densifies near footprints with bounded extent
- in_zones rejects candidates outside HitTestFilledArea
- dryRun does NOT call board.Add
- actual run calls board.Add per placement
- maxVias caps total placements
- intra-call clump prevention (asserts pairwise distance)
- viaDrill >= viaSize is rejected
- unknown strategy name is rejected
- missing GND net returns clear error payload
- no board loaded returns clear error
- named GND net (e.g. VSS) is honoured even when GND also exists
- direct unit tests for _point_to_segment_distance_nm helper
Real-board smoke test on TuneForge_TF001 (4-layer, 44 footprints):
- GND net auto-detected
- grid spacing 4mm: 141 placements, 129 blocked by collision
- grid + in_zones: 140 placed, 15 rejected by zone membership,
115 blocked by collision
python/commands/routing.py (+impl, ~370 LOC)
python/kicad_interface.py (+handler registration)
python/schemas/tool_schemas.py (+MCP schema)
src/tools/routing.ts (+TypeScript surface, builds clean)
tests/test_add_gnd_stitching_vias.py (+18 tests)
CHANGELOG.md (+Unreleased -> New MCP Tools)
The existing single-shot autoroute leaves 1-7 nets unrouted on dense
boards in my testing. Best-of-N drives that to 0 most of the time by
running Freerouting a few times with varied --max-passes and keeping
the SES with the best routing score.
New optional parameters (all backward-compatible):
attempts: int, default 1 (unchanged behaviour). When > 1, run
Freerouting N times and pick the highest-scoring SES.
targetNets: list of critical net names. An attempt that routes all
of them earns a 50,000-point scoring bonus.
passSchedule: list of --max-passes values to cycle through across
attempts. Default: [50, 60, 65, 70, 75, 80, 85, 90, 55,
95] (wraps if attempts > len). Ignored when attempts=1
(legacy maxPasses still used).
Scoring contract (pinned by tests):
score = nets_routed * 1000 + segments
if targetNets and all routed: score += 50_000
- +1 net always beats any segment-count delta (1000 pt step).
- Segments break ties at equal net count.
- Target bonus dominates net-count gains from unrelated nets.
## Implementation notes
- When attempts > 1, each attempt runs with `-mt 1` (single-thread
optimisation). Freerouting 2.x's multi-threaded optimiser is
documented to introduce clearance violations, so forcing
single-thread during scoring keeps the comparison apples-to-apples.
- One failed attempt does not abort the whole best-of-N run. The
failure is recorded in the response under attempts[] with ok=False,
and the remaining attempts compete for best. If every attempt fails
the response surfaces a clear error.
- The winning SES is preserved as <stem>_best.ses next to the
canonical <stem>.ses so the caller can inspect it after the run.
- Response shape:
attempts == 1: unchanged (no attempts/best_attempt fields)
attempts > 1: adds attempts[], best_attempt, best_score,
best_ses_path
## Attribution
Scoring approach and default pass schedule ported from
morningfire-pcb-automation
(https://github.com/NiNjA-CodE/morningfire-pcb-automation,
scripts/routing/freeroute_runner.py). Credited in the function
docstring, the TypeScript wrapper comment, the tool description (visible
to MCP clients), and the CHANGELOG entry.
The MCP version adds: cleaner per-attempt result reporting, automatic
single-thread optimisation, graceful degradation on partial failure,
and explicit validation that surfaces clean error payloads for invalid
attempts values.
## Tests
tests/test_autoroute_score.py 8 cases, scoring contract
tests/test_autoroute_best_of_n.py 6 cases, orchestration logic
All 14 passing. Tests are pure-Python: subprocess is mocked so the
suite runs in any environment (no Java / Freerouting / KiCad required).
- Single-attempt response shape unchanged
- Best-of-three picks the highest-scoring SES
- One nonzero exit attempt doesn't abort the run
- passSchedule wraps when attempts exceeds len
- targetNets bonus wins over higher raw net count
- attempts=0 rejected with clean error before DSN export
- +1 net (1000 pts) dominates any segment delta
- Segments tiebreak at equal net count
- Quoted net names in SES are normalised vs unquoted targets
TypeScript builds clean.
Detects courtyard overlaps between footprints and flags courtyards that
extend past the board outline. Returns overlap pairs with intersection
extents (mm), per-component boundary violations, and a placement summary.
The killer feature for AI-driven workflows is the `positions` parameter,
which accepts hypothetical placements `{ref: [x, y]}` or
`{ref: [x, y, rotation_degrees]}`. The tool evaluates the proposed
placement WITHOUT writing to the board file — so an AI agent can validate
a move_component / place_component before committing it, instead of the
current loop of write -> run DRC -> parse violations -> revert.
## Implementation
- Uses the real courtyard polygons from pcbnew (`fp.GetCourtyard(F_CrtYd)`
or B_CrtYd) for accurate AABBs even on custom and rotated footprints.
- Falls back to `fp.GetBoundingBox()` when no F/B.Courtyard polygon is
present.
- For virtual rotation, rotates the four AABB corners and re-axis-aligns.
Conservative: the rotated-AABB is always >= the rotated-polygon, so
overlap reports are never false-negatives (may be marginally
over-cautious on diagonal rectangles, which is the right error bias
for a placement validator).
- Optional `margin` parameter expands every courtyard by N mm — useful
for enforcing a manufacturing keepout wider than the symbol's
declared courtyard.
## Attribution
The approach is ported from morningfire-pcb-automation
(https://github.com/NiNjA-CodE/morningfire-pcb-automation), specifically
`scripts/placement/check_overlaps.py`. The upstream uses a static
per-footprint-type courtyard lookup table; this implementation reads
the real polygons from pcbnew so it works on any footprint without
maintaining a table. Attribution is in the function docstring, the
TypeScript wrapper, the tool's description (visible to MCP clients),
and the CHANGELOG entry.
## Tests
12 pytest cases in tests/test_check_courtyard_overlaps.py, all passing:
- No overlaps when spaced; overlap detected on intersect
- Margin pushes borderline pairs into overlap
- `refs` filter restricts the check
- Boundary violations are flagged; `include_boundary=false` suppresses
- Virtual position does not mutate the footprint (asserts
`SetPosition` is never called)
- Virtual rotation swaps a tall-narrow courtyard's x/y extents
- No-board-loaded returns clean error payload
- Bad position spec (wrong arity) returns clean error payload
- GetCourtyard() OutlineCount=0 -> fallback to GetBoundingBox()
- `board_outline` override replaces the Edge.Cuts bbox
Tests use mocked pcbnew objects so they run under both the conftest stub
and a real pcbnew install. Real-board smoke test on a 44-footprint
production board succeeds: 1 known overlap detected (SW1<->SW2), 0
boundary violations, virtual placement test reports 6 expected overlaps.
## Files touched
- python/commands/component.py (impl + helpers)
- python/kicad_interface.py (tool registration)
- python/schemas/tool_schemas.py (MCP schema entry)
- src/tools/component.ts (TypeScript surface, builds clean)
- tests/test_check_courtyard_overlaps.py (12 cases)
- CHANGELOG.md (Unreleased -> New MCP Tools)
query_traces silently omits PCB_ZONE_T objects, so layer-usage audits
miss power planes and GND pours entirely. query_zones complements it by
iterating board.Zones() and returning each zone's net, layers, priority,
fill state, min thickness, bounding box, and filled area, with the same
net/layer/boundingBox filter surface as query_traces.
* Feat: save 2D board view to file instead of returning base64
The 2D view was returning base64-encoded image data in JSON, which
often exceeded token/message size limits. Now saves the rendered
image (PNG/JPG/SVG) next to the PCB file and returns the file path.
This makes the output usable by tools that can read image files
directly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(board): add opt-in responseMode param to get_board_2d_view
Add a responseMode string parameter (enum: inline | file, default inline)
so callers can choose how the rendered image is delivered.
- inline (default, pre-PR behavior): image bytes are base64-encoded
and returned in the imageData response field -- backward-compatible.
- file: image is written next to the .kicad_pcb as
<board>_2d_view.<ext> and filePath is returned -- resolves the
MCP message-size limit problem on large boards.
Rendering logic is shared between both modes; only response packaging
differs. Updated tool schema (Python + TypeScript) and replaced the
existing test file with 5 focused unit tests covering inline/file modes
for PNG and SVG formats plus the default-is-inline contract.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
import.meta.url.pathname returns /C:/... on Windows (leading slash),
breaking path.join. fileURLToPath handles Windows paths correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hardcoded Windows path broke Linux/macOS startup. Use import.meta.url to
derive the script path relative to the compiled JS, with KICAD_SCRIPT_PATH
env override for custom installs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
search_symbols, list_symbol_libraries, list_library_symbols, and
get_symbol_info previously only consulted the global sym-lib-table. A
library registered with project scope (an entry in
<project>/sym-lib-table) was therefore invisible — even right after
open_project succeeded — making add_schematic_component the only tool
that could see it.
Fix has two parts:
1. Wrap project_commands.open_project and project_commands.create_project
in handlers that rebuild SymbolLibraryCommands.library_manager against
the project directory. After open_project, project-scope libraries are
automatically visible to subsequent search/list/info calls.
2. Add an optional projectPath parameter to the four discovery tools
(accepts a project directory, .kicad_pro, .kicad_pcb, or .kicad_sch
path). Stateless callers can resolve project libraries without first
calling open_project. SymbolLibraryCommands._derive_project_path also
walks up from schematicPath/boardPath to find the directory that owns
the project, mirroring the logic in _handle_add_schematic_component.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the manual add_schematic_junction tool with automatic junction
management. WireManager.sync_junctions inserts/removes junction dots
based on wire endpoints plus component pin positions and is invoked
after add_wire, add_polyline_wire, delete_wire, move, and rotate.
- Pin-aware: parses lib_symbols and applies KiCad's mirror/rotate/
translate transform to compute world pin coordinates
- Multi-unit safe: filters lib_symbols sub-units by the placed
symbol's (unit N) field plus the unit-0 common body
- Removes the now-unused WireManager.add_junction static method
- Updates CHANGELOG [Unreleased] with the tool removal notice
- Adds .mcp.json to .gitignore (machine-local paths)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Promotes BOM / sourcing fields (MPN, Manufacturer, DigiKey_PN, LCSC,
JLCPCB_PN, Voltage, Tolerance, Dielectric, ...) to first-class citizens
on placed schematic symbols.
New MCP tools:
- set_schematic_component_property: add or update one custom property
on a component (convenience wrapper around edit_schematic_component).
- remove_schematic_component_property: delete one custom property.
The four built-in fields (Reference, Value, Footprint, Datasheet) are
protected and rejected.
edit_schematic_component enhancements:
- New `properties` parameter: map of property name to either a string
value or a full spec object { value, x?, y?, angle?, hide?, fontSize? }.
Adds the property when missing, otherwise updates the existing field
(and optionally its label position / visibility). Lets a single tool
call attach an entire BOM payload to a component.
- New `removeProperties` parameter: list of custom property names to
delete in the same call.
- Property values are now backslash-escaped so descriptions containing
a double-quote or a backslash no longer corrupt the .kicad_sch file.
- New properties default to (hide yes) so they appear in BOM exports
without cluttering the schematic canvas.
get_schematic_component description clarified to highlight that it
already returns every field on the symbol, including custom ones.
New MCP prompt component_sourcing_properties guides agents through the
conventional property names recognised by downstream BOM tooling and
the recommended call sequence.
Implementation (python/kicad_interface.py):
- _PROTECTED_PROPERTY_FIELDS frozenset
- _escape_sexpr_string / _find_matching_paren static helpers
- _set_property_in_block / _set_hide_on_property /
_remove_property_from_block surgical text-level edits that preserve
formatting and the property's UUID
- _handle_edit_schematic_component rewritten to orchestrate
add/update/remove and return a per-property summary
- New handlers _handle_set_schematic_component_property and
_handle_remove_schematic_component_property registered in the
command dispatch table
Tests (tests/test_schematic_component_properties.py):
32 tests covering escape helper, paren matcher, add/update/remove
(single + batched), full spec dicts, default position, default
(hide yes), special-character escaping, UUID preservation, protected
built-in field rejection, no-op removal, both new convenience tools,
and input validation. All 590 tests in the project still pass.
Docs: README, SCHEMATIC_TOOLS_REFERENCE, TOOL_INVENTORY, CHANGELOG.
Adds two new MCP tools for working with free-form text annotations
(SCH_TEXT elements) in KiCad schematics:
- add_schematic_text: place a text note with optional angle, font size,
bold/italic, and justification
- list_schematic_texts: list all text annotations with optional
case-insensitive substring filter
Includes WireManager.add_text / list_texts using _text_insert + sexpdata,
handler dispatch in KiCADInterface, TypeScript tool definitions, registry
entry, reference doc updates, and 30 unit tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allows placing a specific unit (A=1, B=2, C=3, …) of a multi-unit KiCad
symbol rather than always defaulting to unit 1. Required for quad
optocouplers, dual op-amps, and other multi-unit parts where each channel
must be placed independently.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves a net label (local, global, or hierarchical) to a new position in
place, avoiding the error-prone delete-then-re-add workflow. Supports an
optional currentPosition disambiguator and labelType filter.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Python executable and PYTHONPATH were hardcoded to Windows paths,
causing the server to fail silently on macOS/Linux. Now reads
KICAD_PYTHON from the environment (set by setup-macos.sh) with
python3 as the fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add optional netName (exact case-sensitive match) and labelType
(net/global/power enum) parameters. Both are optional and AND
together when combined. Omitting both preserves current behaviour.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>