CA

coffeenmusic/altium-mcp

Developer tools
209 stars 68 forks Quality 79 Trend 39MIT

TLDR: Use Claude to control or ask questions about your Altium project. This is a Model Context Protocol (MCP) server that provides an interface to interact with Altium Designer through Python.

Overview

TLDR: Use Claude to control or ask questions about your Altium project. This is a Model Context Protocol (MCP) server that provides an interface to interact with Altium Designer through Python.

README

Altium MCP Server

TLDR: Use Claude to control or ask questions about your Altium project. This is a Model Context Protocol (MCP) server that provides an interface to interact with Altium Designer through Python. The server allows for querying and manipulation of PCB designs programmatically.

Example commands

  • Run all output jobs
  • Create a symbol for the part in the attached datasheet and use the currently open symbol as a reference example.
  • Create a schematic symbol from the attached MPM3650 switching regulator datasheet and make sure to strictly follow the symbol placement rules. (Note: Need to open a schematic library. Uses server\symbol_placement_rules.txt in the extension’s install folder as pin placement rules. Please modify for your own preferences; an extension update replaces the file, so keep a copy.)
  • Create an op-amp symbol with a triangle body like the other op-amps in my library (symbols support line/polygon/arc/ellipse/label body graphics, not just rectangles)
  • Find me the LM358 symbol in my opamp library and open it
  • Create a multi-part symbol for a quad op-amp from the attached LM324 datasheet (creates parts A, B, C, D with shared V+/V- power pins)
  • Create a PCB footprint for the SMD part in the attached datasheet and add it to my open PcbLib
  • Duplicate my selected layout. (Will prompt user to now select destination components. Supports Component, Track, Arc, Via, Polygon, & Region)
  • Show all my inner layers. Show the top and bottom layer. Turn off solder paste.
  • Get me all parts on my design made by Molex
  • Give me the description and part number of U4
  • Place the selected parts on my pcb with best practices for a switching regulator, then verify clearances and show me a screenshot of the result
  • Auto-place my silkscreen, then fix the designators the placer left on top of parts or far from them
  • Give me a list of all IC designators in my design
  • Get me all length matching rules
  • Check my project is ready for fabrication: unsaved documents, ERC errors, and which layers my Gerber output job plots
  • Draw a schematic of an LED with a current-limiting resistor on a new sheet, using symbols from my library

Installing the MCP Server

The easiest way to install is to use Claude Code, point it to this repo and ask it to install it for you. Or alternatively, see below.

Watch on YouTube

  1. Make sure Claude has Python 3.10 to 3.14 installed: drop down > File > Settings > Extensions > Advanced > Python. If not, install Python and add it to PATH. Python 3.15 is untested (see Bootstrap Venv).
  2. Download the altium-mcp.dxt desktop extension file from releases
  3. In Claude Desktop on Windows: drop down > File > Settings > Extensions > Advanced > Install Extension... Select the .dxt file

You shouldn’t need to restart Claude and you should now see altium-mcp in the tool menu near the search bar.

Creating a new .dxt (For Developers)

This approach ships a small bootstrap script (start_server.py) that creates a virtual environment and pip-installs dependencies on the user’s machine at first launch. The .dxt is tiny (~60 KB) and works on Python 3.10 to 3.14.

Python versions: 3.10 to 3.13 install the hash-locked requirements.txt. 3.14 has no wheel for the locked pillow, so it installs the fallback list in start_server.py instead. With mcp 1.30 that list no longer caps pydantic, so it also resolves on 3.15, but 3.15 has not been tested.

The older approach of bundling pre-compiled packages in server/lib/ is no longer recommended — it breaks when the user’s Python version doesn’t match the version used to build the bundled .pyd files (e.g. pydantic_core compiled for 3.11 fails on 3.13).

How it works:

  1. start_server.py (at the repo root) looks for a ready virtual environment in %USERPROFILE%\.altium-mcp\venvs\, where `` is a hash of the pinned requirements and the Python version
  2. If it isn’t ready, it starts a detached builder process (start_server.py --build) that creates the venv and pip-installs the dependencies, then waits for it. It installs the hash-locked requirements.txt when the package contains it, and falls back to the short REQUIREMENTS list in start_server.py when the file is missing or the locked wheels don’t fit the interpreter. Claude Desktop kills a server that has not answered within about 60 seconds, and restarts servers freely; a build that ran inside the launcher died with it. The detached builder finishes regardless, so a restarted launcher finds the venv ready or a build still in progress.
  3. A lock file (holding the builder’s PID) makes sure only one build runs at a time; other launchers wait for it. A lock whose owner has exited is taken over immediately.
  4. It then launches server/main.py using the venv’s Python
  5. First launch takes ~20-30 seconds; subsequent launches are instant

The venv, config.json, build.log and the server log altium_mcp.log live in %USERPROFILE%\.altium-mcp, not in the extension folder, because Claude Desktop replaces the extension folder on every update. The slow build therefore happens once per machine (and again only when the requirements or Python version change), and a hand-picked Altium path survives updates. AppData is deliberately avoided: Claude Desktop is an MSIX package, and writes its child processes make under AppData are redirected into ...\Packages\Claude_...\LocalCache, so a venv built from a terminal and one built by the server would end up in different places. Set the ALTIUM_MCP_HOME environment variable to use a different folder. A config.json left beside server/main.py by an older version is migrated automatically on first start. The server log rotates at 1 MB (three backups) and records INFO and above; set ALTIUM_MCP_LOG_LEVEL=DEBUG to include every MCP protocol message.

On a slow network the first build can exceed the client’s 60-second startup timeout. The build carries on in the background, so restarting the extension once it has finished is enough; build.log shows progress.

Build steps:

  1. Make sure start_server.py exists at the repo root
  2. Make sure server/main.py does NOT have the old site.addsitedir hack at the top
  3. Update manifest.json: set entry_point to start_server.py, remove any env/PYTHONPATH fields, and use manifest_version: "0.3"
  4. Package the DXT — either use dxt pack or manually zip and rename:
Compress-Archive -Path manifest.json, start_server.py, pyproject.toml, requirements.txt, server -DestinationPath altium-mcp.zip
Rename-Item altium-mcp.zip altium-mcp.dxt

Do NOT include server/lib/ or server/.venv/ in the .dxt. The whole point is that these are created on the user’s machine.

start_server.py: use the file at the repo root as is. It is no longer reproduced here, so the two cannot drift apart.

manifest.json server section:

"server": {
    "type": "python",
    "entry_point": "start_server.py",
    "mcp_config": {
      "command": "python",
      "args": ["${__dirname}/start_server.py"]
    }
  }

Pitfalls

These are hard-won lessons from debugging DXT builds. Violating any of these will produce errors that are difficult to diagnose.

  1. Do NOT use os.execv() in start_server.py. The DXT installs to a path containing spaces (Claude Extensions). On Windows, os.execv splits the path at the space and fails. Use sys.exit(subprocess.call([...])) instead.

  2. Pin mcp to an exact version (now ==1.30.0) and upgrade it deliberately. An open range pulls in whatever is newest, and FastMCP has changed under the server before: 1.x dropped the description kwarg of FastMCP.__init__(), and in later 1.x releases a tool declared -> str sends its result a second time as structuredContent unless it is registered with structured_output=False (what _tool() in main.py does). mcp 2.x is not supported.

  3. Do NOT use manifest_version: "0.4" with "type": "uv". Claude Desktop does not support it yet. You will get Invalid manifest: server: Required. Use "type": "python" with manifest_version: "0.3".

  4. After removing the site.addsitedir hack from main.py, fix the pathlib.Path reference. The hack included import pathlib at the top of the file. The logging setup later uses pathlib.Path(...) which will throw NameError once that import is gone. Change it to Path(...) — the from pathlib import Path import already exists in the file, just make sure it comes before the logging setup.

  5. Do NOT bundle server/lib/ in the DXT. That was the old approach and defeats the purpose of the venv bootstrap.

This approach bundles all dependencies in server/lib/ and sets PYTHONPATH to point to it. It produces a much larger .dxt (~17 MB) and only works if the user’s Python version matches the version used to compile the bundled packages.

  1. Populate packages: python -m pip install --no-cache-dir --target server/lib -r requirements.txt
  2. Set the manifest entry_point to server/main.py and add "env": {"PYTHONPATH": "${__dirname}/server/lib"} to mcp_config
  3. Package: npm install -g @anthropic-ai/dxt && dxt pack

DXT Resources

Configuration

When launching claude for the first time, the server will automatically try to locate your Altium Designer installation. It will search for all directories that start with C:\Program Files\Altium\AD* and use the one with the largest revision number. If it cannot find any, you will be prompted to select the Altium executable (X2.EXE) manually when you first run the server. Altium’s DelphiScript scripting is used to create an API between the mcp server and Altium.

Available Tools

The server provides several tools to interact with Altium Designer:

Output Jobs

  • get_output_job_containers: Reads all available output containers from the .OutJob at outjob_path (a full path, optional) or, without one, from the first OutJob open in a project
  • run_output_jobs: Pass a list of output job container names to run any number of them, from the .OutJob at outjob_path or the first open one. The OutJob’s project must be open in Altium. PDF containers report output_verified once the PDF is written. If the PDF already exists and the container prompts before overwriting, Altium asks in a dialog and the run waits for the answer.
  • get_outjob_settings: Read any .OutJob file from disk (no Altium needed, the OutJob does not have to be open) and return its containers (output paths, file name expression, add-to-project) and outputs (connected containers, variant, document) with decoded settings: Gerber units/format/origin and the plotted and “add to all plots” layers decoded from Altium layer IDs, NC Drill, IPC-D-356 test points, pick and place, print layers and BOM sections. Use it to audit fabrication settings before generating. Reads the saved file, so unsaved changes in Altium are not visible.

Project & Validation

  • get_project_info: Focused project’s parameters, documents with their state (closed / open / modified = unsaved changes in memory), schematic sheet count and variants with their variations (e.g. Not Fitted parts). Read-only; use it to confirm nothing is unsaved before generating outputs.
  • get_project_components: The components of the compiled project with their schematic Comment, footprint, library reference and sheet (optionally all parameters, or only the ones you name). Filter by designator (wildcards such as R*) or sheet; a large project comes in pages, and next_offset says where to go on. Use it for BOM work: the instance Comment is where generic passives keep their value, which PCB-side tools and ActiveBOM may not show.
  • get_erc_violations: Compile the focused project in memory (nothing is saved) and list compiler/ERC violations with their level (fatal / error / warning). Violations are read from the project object - the per-document DM_ViolationCount does not exist in recent Altium versions.
  • get_board_summary: Fabrication-oriented summary of the focused PCB in mm: board outline (bounding box, size, vertices) and origin, dimension objects, vias by hole size, components per side, primitive counts per layer (free vs. inside components, e.g. to see which mechanical layers hold the assembly outlines) and the free texts on both overlays.
  • get_component_primitives: Every primitive of the given components with its layer (pads with size and solder/paste mask expansion, arcs, tracks, texts). Catches footprints that were flipped to the bottom side but kept part of their copper or mask opening on the top (e.g. fiducials).

Component Information

  • get_all_designators: Get a list of all component designators in the current board
  • get_all_component_property_names: Get a list of all available component property names
  • get_component_property_values: Get the values of a specific property for all components
  • get_component_data: Get detailed data for specific components by designator
  • get_component_pins: Get pin information for specified components

Schematic/Symbol

  • get_schematic_data: Get schematic data for specified components

  • create_schematic_symbol (YouTube): Passes pin list with pin type & coordinates to Altium script. Supports multi-part symbols (e.g. quad op-amps) via a part_count parameter and an owner_part_id field on each pin (use 0 for shared power/GND pins), active-low pin name overbars by placing a backslash after each overbarred character (e.g. R\E\S\E\T\ renders as RESET with overbar), per-pin length and name/designator visibility, and an optional graphics list (lines, polylines, polygons, rectangles, arcs, ellipses, labels) for non-rectangular bodies like op-amp triangles and diode glyphs. When creating a symbol, the agent is instructed to first look up a similar symbol in an available library as a style reference (skipped gracefully if no library is available).

  • get_symbol_primitives: Inventory a .SchLib (every symbol with per-type primitive counts) or dump one symbol’s complete geometry (all graphics + pin details, in mils; symbol_name="*" dumps every symbol). Used to gap-analyze a library, to give the agent a reference example when drawing a new symbol, and to verify recreations. The symbol creator round-trips a complete production library set exactly - 514/514 symbols across IC, connector, misc, and passives libraries (dump -> recreate -> dump -> diff, zero differences). Footprint/model links (implementations) are metadata outside the graphics round-trip. Each dumped symbol also reports its Comment (text, visible) and its parameters (name, value, visible, location in mils as Altium holds it, justification, autoposition, font name and size), and each pin its description, its four IEEE symbols (symbol_inside, symbol_inside_edge, symbol_outside_edge, symbol_outside) and its parameters (name, value), so the agent can check symbol properties after setting them.

  • create_symbols_batch: Create or edit many symbols in one script run from a plain-text spec file (bulk imports/migrations) - far faster and more robust than one create call per symbol. Besides SYMBOL, PIN and GRAPHIC records it takes:

    • EDITSYMBOL|: edit an existing symbol. A symbol that is not in the library is reported under failed, never created.
    • COMMENT|[|]: set the Comment. It is hidden unless the visible field is 1. The library editor keeps its own copy of the Comment of the symbol on screen and writes that copy back when it shows another symbol, so for that symbol the tool sets both.
    • DESCRIPTION|: set the Description.
    • SYMPARAM|||[|x|y[|justification[|font]]]: add or replace a symbol parameter. By default, visible parameters without a location go below the body’s bottom-left corner, left-aligned, 100 mil apart in record order. The optional justification is bottom_left, bottom_center, bottom_right, center_left, center, center_right, top_left, top_center or top_right. The optional font is :, for example Arial:10; it is looked up by name with FontManager.GetFontID, which adds it to the document when missing, because font IDs differ between documents. Leave a field empty to skip it and still give a later one; omitted fields keep the parameter’s own. A parameter the tool positions gets Autoposition off, so Altium does not move it afterwards.
    • PINPARAM|||: add or replace a hidden parameter on every pin with that number (every part of a multi-part symbol).
    • PINDESC||: set the Description of every pin with that number.
    • PINSYMBOL|||: set an IEEE symbol of every pin with that number. The edge is inside, inside_edge, outside_edge or outside. The symbol is a TIeeeSymbol name without the leading e, in any case: none, dot, clock, activelowinput, schmitt and so on (the full list is in the altium-script skill, SCH_API_Reference.md, under TIeeeSymbol).

    These records work after SYMBOL and EDITSYMBOL alike. An unknown pin number, edge, symbol or justification is listed under problems; the rest of the batch still runs.

    The result lists created, edited, failed and problems. Created and edited symbols leave the library modified and unsaved, so the user reviews them and saves; the tool never saves a library. Objects a script adds through PreProcess/PostProcess do not reliably set Altium’s modified flag (in a PCB library they never do), so the symbol and footprint tools set it themselves. A LIBRARY| line focuses that library if it is already open (it never reloads it, which would discard unsaved symbols) and opens it otherwise. If that library cannot be focused, the batch stops before changing anything.

  • build_schematic: Build a wired schematic on a new sheet from a circuit description: parts from a .SchLib (full path; .IntLib is not supported), wires, junctions, net labels, power ports and notes, in mils (fractions are kept, so metric positions are exact). Returns every placed pin’s true connection point, so a second call can route wires from measured coordinates, and lists any part it could not place. The new sheet is kept out of your project.

  • check_schematic: Check saved sheets for drafting problems: two-pin parts tapping their net at the pin, net labels away from their wire’s free end or hanging off it, text touching wires, pins or other text (with a clear layout suggested), dangling wires, wires slightly out of level, and sheet symbol layout. Takes a .SchDoc, a .PrjPcb or a folder and reads the saved files, so it needs no running Altium. Contributed by SiboVG (PR #64).

  • edit_schematic: Change an existing sheet: move, rotate, mirror, delete or place parts, set parameters, move designator and parameter text, add wires, net labels, power ports, junctions and no-ERC markers, and delete objects at a point. Coordinates are in mil or mm, so check_schematic’s suggestions apply directly. The whole edit is one undo step in Altium, and the sheet is saved only when asked. Contributed by SiboVG (PR #70).

  • get_symbol_placement_rules: Create symbol’s helper tool that reads server\symbol_placement_rules.txt in the extension’s install folder to get pin placement rules for symbol creation. The file ships empty, so the tool returns neutral guidance (follow a comparable symbol in the user’s library, keep pins on a 100 mil grid); put your own rules in it.

  • get_library_symbol_reference: Create symbol’s helper tool to use an open library symbol as an example to create the symbol

  • search_library_symbol: Search for a symbol by name in a schematic library (.SchLib) and navigate to it. Supports partial name matching. Will open the library file in Altium if a path is provided, or show a file picker if not.

Library paths (library_path, LIBRARY|, FPLIB|) may contain commas and may be spelled in a different case or with forward slashes. An open library is matched to its path after normalizing case and separators, so the tools never open a second copy of it.

Layout Operations

  • get_all_nets: Returns a list of unique nets from the pcb
  • create_net_class (YouTube): Create a net class from a list of nets
  • get_pcb_layers: Get detailed layer information including electrical, mechanical, layer pairs, etc.
  • get_pcb_layer_stackup: Gets stackup info like dielectric, layer thickness, etc.
  • set_pcb_layer_visibility (YouTube): Turn on or off any group of layers. For example turn on inner layers. Turn off silk.
  • get_pcb_rules: Gets the rule descriptions for all pcb rules in layout.
  • run_drc: Run Altium’s design rule check on a board and list the violations, each with its rule, description, position and the objects involved, plus the per-rule counts from Altium’s report. The Design Rule Checker dialog flashes on screen while the tool presses Run; ran says whether the check really ran. Takes an optional board_path. Contributed by SiboVG (PR #66).
  • create_pcb_rule: Add a design rule: clearance, width, hole size, routing via, solder or paste mask expansion, or polygon connect, with values in mils and Altium query scopes such as InNet('GND'). A rule added by script becomes priority 1 of its kind, so give it a narrow scope; the result warns when a rule scoped to everything now overrides others. Takes an optional board_path. Contributed by SiboVG (PR #67).
  • get_selected_components_coordinates: Get position, rotation, layer, and footprint information for currently selected components
  • move_components: Move specified components by X and Y offsets
  • set_component_position: Set one component’s absolute position and rotation
  • place_components: Batch absolute placement - place any number of components (x, y, rotation, top/bottom layer) in a single transaction / one undo step. The workhorse for AI-driven placement.
  • check_placement: Verify a placement - finds overlaps and clearance violations against every other component on the board using true primitive-to-primitive distances (designator text excluded). Run after placing; a screenshot is not verification.
  • check_orientation: Advisory check for 2-pad passives whose rotation could be improved (e.g. a decoupling cap with its GND pad facing away from the IC). Geometry only - review suggestions with judgement; parts like pull-ups don’t care.
  • get_net_connections: For the nets touching a set of components, list every pad on each net board-wide with per-net airline (MST) lengths - the data behind connectivity-driven placement (shorten critical nets, see off-cluster loads and filter banks).
  • check_routing: Check the routed copper for segments that are not 0, 45 or 90 degrees and for right-angle and acute corners, with the net, layer and position of each. Run after editing tracks or vias; a screenshot does not show a stub 2 degrees off 45. Contributed by SiboVG (PR #60).
  • route_track: Add a track through waypoints with every segment exactly 0, 45 or 90 degrees, optionally chamfering square corners; dry_run only returns the segments. It adds tracks only: no vias and no DRC. Contributed by SiboVG (PR #60).
  • place_vias: Add vias at positions in mils from the board origin, with a default size, net and layer span that each via can override (blind and buried vias too). Every net and layer is checked against the board first, by Altium’s layer name or the stack’s own (“Inner 1”), so a bad entry adds nothing, and all the vias are one undo step. Takes an optional board_path. Contributed by SiboVG (PR #68).
  • place_polygon_pour: Add a polygon pour from an outline of corners in mils on a signal layer, solid or hatched, with an optional net, and pour it. Takes an optional board_path. Contributed by SiboVG (PR #69).
  • place_cutout: Add a polygon pour cutout on one signal layer or all of them, or a board cutout, and repour the polygons it overlaps. Takes an optional board_path. Contributed by SiboVG (PR #69).
  • layout_duplicator (YouTube): Starts layout duplication assuming you have already selected the source components on the PCB.
  • layout_duplicator_apply: Action #2 of layout_duplicator. Agent will use part info automatically to predict the match between source and destination components, then will send those matches to the place script.

The cool thing about layout duplication this way as opposed to with Altium’s built in layout replication, is that the exact components don’t have to match because the LLM can look through the descriptions and understand which components match and which don’t have a match. That’s something that can’t really be hard coded.

Silkscreen

Bulk placement with the Silkscreen Auto Placer script, then an agent cleans up what it leaves. The loop is: auto-place, find what needs work, look at it, pick a spot, apply, and get Altium’s verdict.

  • auto_place_silkscreen: Run the Silkscreen Auto Placer headless inside Altium (server/AltiumScript/silk_autoplacer.pas, a port of the GUI script): autopositions on an offset grid with shrinking text, a retry pass and a 2nd-pass wiggle search, avoiding pads, bodies, silk and optionally vias. Options default to the settings last saved by the script’s GUI (AutoPlaceSilkscreen.ini); any argument overrides them. Failures are centred on their part (or hidden/restored). One undo step. Differences from the GUI script: “try altered rotation” on the bottom layer flips 0/270 (the script turned mirrored text to 90/180, upside down); labels keep the Board Outline Clearance rule’s distance from the board edge (the script only kept them on the board); and each placed designator’s geometry is rebuilt at the end (see below). Save the board first: a run touches every designator, and Altium crashed once during one (not reproduced).
  • check_silkscreen: What needs work. Each designator is tested with Altium’s own Silk To Silk, Silk To Solder Mask and Board Outline Clearance rules (Rule.ActualCheck, the same test a batch DRC runs), plus quality problems DRC does not flag: text far from its part (noting when it is still in line with it, as in a label block), reading as another same-type part’s label, over a part body or left on top of its own part, over open vias, off the board, or upside down. Lists hidden designators too. Optional whole-board view.
  • view_silkscreen: Look at an area - designators or a mil region - rendered cleanly (mask openings, silk, part names, designator boxes) with a grid labelled in board coordinates, so a free spot can be read straight off the image. Independent of Altium’s window and zoom.
  • get_designator_options: Ranked legal spots for designators, drawn as numbered boxes, each clear of silk, mask openings, open vias, the board edge and every other designator, readable, and nearer its own part than any same-type part (or level with its own part when a nearer part is not). When there is none, says what blocks it. Can include smaller-text options (min_height_mils). Ask for a designator together with its neighbours: the designators asked about are not obstacles to each other, so their labels can move to make room.
  • get_label_block_options: For parts in a row whose designators do not fit next to them: their designators in a row too, in the same order, where there is room, even some way off. Give one designator to use its whole row (parts side by side along x or y, similar size, gaps up to 50 mil; mounting holes, fiducials and hidden test points get no label) or list the parts; a single part works as a block of one. Each option is a block (labels across the row, in line with each part when the pitch allows or side by side; or end to end along it) and, unless it sits right against the row, a link: a silk leader line with an arrowhead pointing at the parts, or matching index markers - a capital letter in a circle, then square, then triangle - next to the block and next to the parts. Drawn per option.
  • place_label_block: Apply an option: the labels move (shown, at the block’s text size), the link is drawn on the overlay as free tracks, arcs and text, and both are checked with Altium’s silk rules. Replaces an earlier block of the same parts. Blocks are recorded per board (C:\Users\Public\altium_mcp\silk_label_blocks.json, never next to the board), so check_silkscreen knows a block’s labels are linked to their parts and reports a block whose labels or link have been disturbed. remove_label_block deletes a block’s link.
  • remove_label_block: Remove the label block of some parts: its link graphics (leader or index markers) are deleted, and the designators stay where they are, ready to be placed again.

Designators are not hidden to get rid of problems: only mounting holes and fiducials go without one. A designator with no room goes in a label block.

  • set_designator_positions: Place designators by coordinates (text-box centre) or relative to their part (side = above/below/left/right/inside, gap, offset), with optional rotation, height, stroke width and visibility. dry_run checks a set of neighbours together without touching the board; otherwise all moves are one undo step and each moved designator comes back with Altium’s DRC verdict. Moves are tied to the board they were computed for: if another PCB has been focused since, Altium refuses them and nothing moves (check_silkscreen reports which board it read).

Typical run on a dense two-sided board (845 parts, 804 visible designators): the auto placer placed 725 in about 7 minutes. Many of its labels still touched each other, because the script tolerates a 4 mil designator overlap, or sat far from their part. Agent rounds of check_silkscreen → get_designator_options → set_designator_positions (dry run, then apply), allowing 25 mil text, took the board from 637 silk DRC violations to 367. The rest are designators with no legal spot in the densest passive fields; those go in label blocks.

The geometry model lives in server/silkscreen.py and label blocks in server/label_blocks.py (pure Python; unit tests in server/tests/test_silkscreen.py and test_label_blocks.py run without Altium). The links are drawn by the edit_silk_graphics bridge command, which adds or removes free overlay tracks, arcs and centred stroke text in one undo step and checks each new primitive with the silk rules. Altium’s Silk To Silk rule only checks text against other silk (a free track crossing another free track or arc passes), so a marker letter must clear its shape. It measures stroke text by its ink: Altium’s bounding box runs half a stroke beyond the strokes, and its silk rules check the strokes. The Board Outline Clearance rule is the exception: it measures from the bounding box, and its text clearance comes from the rule’s per-object table (GetClearance(eObjectClearanceID_Text, eObjectClearanceID_OutlineEdge)), not its generic Gap.

Altium pitfalls the tools work around:

  • Batch DRC checks a cached copy of each text’s stroke geometry, and a scripted move or resize does not refresh it. The batch DRC then reports collisions where the text used to be and can miss them where it is now (on the test board, 457 phantom silk-to-silk hits, while every label passed Rule.ActualCheck). set_designator_positions and auto_place_silkscreen rebuild each designator they touch with SetState_XSizeYSize inside Begin/EndModify. Text moved by other scripts, such as the GUI auto placer, probably has the same problem (not tested).
  • Pad.BoundingRectangleOnLayer(solder mask) can be offset from the pad for some custom pad shapes (a SOT-89 tab was 55 mil off). The export takes the union with the pad’s own bounding rectangle and flags the pad, and the model then treats it as a plain rectangle.

PCB Footprint Library

  • create_pcb_footprint: Create a new PCB footprint in the currently active .PcbLib document. Supports SMD pads (Rect, Round, Oval shapes) defined in mm relative to the component origin. Auto-generates a courtyard on Mech 15 and silkscreen with a pin 1 indicator (gap in the top-left corner), or accepts explicit courtyard dimensions. Contributed by coffeedust (PR #7).

  • get_footprint_primitives: Inventory a .PcbLib (per-footprint primitive counts) or dump complete footprint geometry - pads with full stack/hole detail, tracks, arcs, fills, texts, regions - in mils, relative to the footprint origin (also for the footprint shown in the editor). Pads also report their paste and solder mask mode and expansion and whether they are locked. The reference source when recreating or verifying footprints. A dumped footprint also lists its 3D bodies (bodies_3d: model file, whether it is embedded, its X/Y offset, standoff and overall height, layer, model rotation X/Y/Z and Z offset); the bodies themselves are not recreated. The rotations and Z offset are read from the saved library file, because IPCB_Model.GetState returns them through out parameters that DelphiScript never receives. A body that is not saved yet has them as null (placement says which).

  • create_footprints_batch: Create many footprints in one script run from a plain-text spec file: SMD + through-hole pads (holes, slots, plating, rotation, full pad stack, paste and solder mask expansion), tracks, arcs, fills, texts, regions on any layer, and 3D models. Every pad it creates is locked. Records beyond the original set:

    • PAD|...|bot_shape|paste_mode|paste_expansion|mask_mode|mask_expansion: optional paste and solder mask expansion at the end of a PAD record. Modes are TMaskExpansionMode ints: 1 rule, 2 manual (expansion in mils). Mode 0 (no mask) cannot be set on a pad by script: the mode setters ignore it, and the paste-enable and tenting properties are not kept, so it is reported as a problem. Manual values are set through the pad cache, because the PasteMaskExpansion/SolderMaskExpansion setters stop the script.
    • MODEL3D||||||[||[|]]: add a 3D body from a STEP file, embedded unless embed is 0.
    • EDITFOOTPRINT|: add MODEL3D records to an existing footprint; it never creates a footprint, and other records under it are reported and ignored.

    Altium keeps the footprint shown in the library editor on the library board, offset by the library origin, and writes it back when another footprint is shown. So a model for the shown footprint is added to the board, at that offset; added to the footprint itself, it would be lost. Round-trip verified against complete production SMD and through-hole libraries. Like create_symbols_batch, it leaves the library modified and unsaved, and an FPLIB| line focuses the open library rather than reloading it.

Both

  • get_screenshot: Take a screenshot of the Altium PCB window or Schematic Window that is the current view, returned as a proper image the agent can see. For PCB views, an optional zoom_to list of designators makes Altium zoom to those components before capture so they fill the frame. It should auto focus either document type if it is open but a different document type is focused. Altium comes to the front for about a second for the capture, then the window that was in front gets the foreground back.

Scripting / Development

  • run_altium_script: Run a DelphiScript snippet in an isolated sandbox and get back a step-by-step log, the script’s result, and - when a script fails - Altium’s own error message with the line in your script. Each run is written to a fresh copy of SandboxScript/Sandbox.pas with a unique file name and started with ScriptingSystem:RunScriptFile, so a script that failed earlier can never be re-run from Altium’s script editor. Errors inside the script (undeclared identifiers, calls on nil, bad conversions) are caught and reported without blocking Altium; syntax errors are reported from Altium’s error dialog, which the tool answers before stopping the debugger (-REditScript:Stop) and confirming with a probe run that the next script will work. The other MCP tools recover the same way when one of their own commands gets stuck. Only scripts the MCP started are ever stopped: a script you are running or debugging in Altium is left alone, and the tool says Altium is busy instead. Scripts may start with their own var block; assignments to undeclared names are refused before Altium is called. The template provides MM(), OpenSchDoc() and SaveDoc() helpers. While waiting it closes blocking Altium dialogs (only windows owned by X2.EXE) and returns their titles in dismissed_dialog_titles - close your own Altium dialogs before running scripts, since an open dialog of yours may be closed too.
  • ensure_altium_script_skill: Check whether the altium-script skill (Altium DelphiScript API reference, examples, and conventions) is installed, and install it on request. Skills load at client startup, so a newly installed skill appears after a restart.

Server Status

  • get_server_status: Check the status of the MCP server, including paths to Altium and script files

How It Works

The server communicates with Altium Designer using a scripting bridge:

  1. It writes command requests to C:\Users\Public\altium_mcp\request.json
  2. It launches Altium with instructions to run the bridge script: the units of server/AltiumScript/Altium_API.PrjScr merged into one file in C:\Users\Public\altium_mcp\bridge, named after its content so Altium never runs a stale copy it still holds open (ScriptingSystem:RunScriptFile)
  3. The script processes the request and writes results to C:\Users\Public\altium_mcp\response.json
  4. The server reads and returns the response

Why one merged file, not the script project. Running a script project (ScriptingSystem:RunScript with ProjectName) makes Altium create a hidden copy of every form in the script projects on the user’s menus, and never free it. On one machine with 14 such forms that was about 105 Windows USER objects per call. Windows caps a process at 10,000 (USERProcessHandleQuota). Near the cap Altium shows “System Error. Code: 1158 … used all of its system allowance of handles for Window Manager objects” and then crashes, losing unsaved work, after about 90 calls. Running a single script file (RunScriptFile) or script text (RunScriptText) leaks nothing, so server/script_bundle.py merges the units into one file: global var/const blocks first, then each routine after the routines it calls (a single file must define a routine before its first use). The bundle is rebuilt whenever a unit changes. The project is still the source to edit, and the fallback if bundling fails.

When adding bridge code: start every routine, and its begin and final end;, at column 0, and don’t let routines call each other in a cycle (the bundle has no forward declarations). tests/test_script_bundle.py checks the real units. The sandbox runs its script as a file too.

Altium’s script engine also leaks memory: every property read of a board object costs roughly 100-200 bytes that are never freed. On a 40,000-object board that is about 25 MB per silkscreen export and 50 MB per full silkscreen check, so a long session of exports grows Altium by gigabytes until it runs out of memory and dies with unsaved work. The bridge scripts read each object’s properties once and skip objects early; the silkscreen tools reuse one board export for up to 15 minutes and update it themselves after their own moves and link graphics.

As a backstop, the server checks Altium before every call and refuses when it is within 1,000 USER objects of the cap (the user’s own menu scripts still leak each time they run) or using more than half of the machine’s RAM, asking for a save and restart.

Files exchanged with the scripts are read and written the way Altium does: in the Windows ANSI code page, or as UTF-8 when the file starts with a byte-order mark.

References

  • Get scripts’ project path from Jeff Collins and William Kitchen’s stripped down version
  • BlenderMCP: I got inspired by hearing about MCP being used in Blender and used it as a reference. https://github.com/ahujasid/blender-mcp
  • Used CopyDesignatorsToMechLayerPair script by Petar Perisin and Randy Clemmons for reference on how to .Replicate objects (used in layout duplicator)
  • Petar Perisin’s Select Bad Connections Script: For understanding how to walk pcb primitives (track, arc, via, etc) connected to a pad
  • Matija Markovic and Petar Perisin Distribute Script: For understanding how to properly let the GUI know when I’ve updated tracks’ nets
  • Petar Perisin’s Room from Poly: Used as reference to detect poly to pad overlap since I couldn’t get more tradition methods to work.
  • Petar Perisin’s Layer Panel Script: Used as reference for getting layers and changing layer visibility
  • Jeff Collins has an XIA_Release_Manager.pas script that taught me the art of the Output Job. See his post on the Altium Forums: https://forum.live.altium.com/#/posts/189423

Contributors

  • daescobarh — project validation tools (get_project_info, get_project_components, get_erc_violations, get_board_summary, get_component_primitives, get_outjob_settings) and safer dialog dismissal (PR #18)
  • coffeedust — create_pcb_footprint tool for PcbLib automation (PR #7)
  • fwolter — Fix JSON parsing error when the decimal separator is a comma (PR #3)

Disclaimer

This is a third-party integration and not made by Altium. Made by coffeenmusic

TODO:

  • Change selection filter:
    • scripts-libraries\Scripts - PCB\FilterObjects\
    • scripts-libraries\Scripts - SCH\SelectionFilter\
  • Show/Hide Panels: DXP/ReportPCBViews.pas
  • Move cursor to position: IPCB_Board.XCursor, IPCB_Board.YCursor
  • Add get schematic & pcb library path for footprint.
  • log response time of each tool
  • Add go to schematic sheet
  • Go to sheet with component designator
  • Board.ChooseLocation(x, y, ‘Test’);
  • Zoom to selected objects:
  • Change Schematic Selection Filter: SelectionFilter.pas
  • How can I read through components from libraries in Components panel?

TODO Tests: Need to add the following test units

  • get_pcb_layers
  • set_pcb_layer_visibility
  • layout_duplicator
  • get_screenshot
View the full README on GitHub

Install

This server does not publish a one-line install command.

Open the repository installation guide New to MCP? Start with the mcpskill.org guide →