Repository navigation
mcp_server
Let an AI assistant (Claude, or any other MCP client) read and verify QElectroTech projects directly, instead of reasoning from a screenshot: what a project contains, what an edit actually changed, and what a whole corpus of projects contains.
It lives at misc/qet-mcp/qet_mcp.py in the source tree β a small stdio
Model Context Protocol server, Python
3.9+ and the standard library only, no MCP SDK dependency. Added in
PR #969;
its scripting-API foundation landed alongside in
PR #970.
Both merged 2026-09-21.
Verifying a change by screenshot is unreliable, and this tool exists because that unreliability produced two wrong conclusions in one review session:
- A drag of a multi-element selection looked like it had left the symbols
behind and detached their labels. Diffing the saved file showed all four
elements had moved by an identical
(0, -80)and no label had moved at all. A bug report was one step away from being filed. - An "Apply" button looked like it did nothing. It was disabled, because a required field was empty.
Both times the pixels misled and the model told the truth. So the tools here read the model β the project XML and, where one exists, the project database β instead of the rendered scene.
Most tools parse the .qet/.elmt file directly: fast, no display needed,
immune to a stray dialog. Two (qet_export, qet_edit) launch
QElectroTech itself, in an isolated sandbox, because exporting and editing
through the real application is the only way to get the real behaviour β
see JavaScript Scripting for the engine qet_edit and
qet_query drive underneath.
# from the QElectroTech source tree
python3 misc/qet-mcp/qet_mcp.py --list # list the tools and exit
python3 misc/qet-mcp/qet_mcp.py # speak MCP on stdin/stdout
python3 misc/qet-mcp/qet_mcp.py --call qet_project_info '{"path": "drawing.qet"}' # run one tool and exitRegister it with an MCP client β for Claude Code or Claude Desktop, a
mcpServers block:
{
"mcpServers": {
"qet": {
"command": "python3",
"args": ["/path/to/qelectrotech/misc/qet-mcp/qet_mcp.py"],
"env": {
"QET_MCP_WORKSPACE": "/home/you/drawings",
"QET_ENABLE_SCRIPTING": "1"
}
}
}
}Nothing to build, nothing to pip install β the two environment variables
above are the only setup that matters, and both are covered below.
Added in PR #1128.
A chat assistant in a web browser β claude.ai, or any other web chat β cannot start a program on your computer, so it cannot run this server. There are two ways round that.
The Claude desktop app for Windows and macOS runs local MCP servers, and
it uses the same account as the website. All the tools work, including
qet_edit and qet_export, because QElectroTech runs on your own
machine.
-
Install Python 3.9 or later. Nothing else is needed.
-
In the desktop app, open Settings β Developer β Edit Config. This opens
claude_desktop_config.json. -
Add the server, with your own paths:
{ "mcpServers": { "qet": { "command": "python", "args": ["C:\\path\\to\\qelectrotech\\misc\\qet-mcp\\qet_mcp.py"], "env": { "QET_MCP_WORKSPACE": "C:\\Users\\you\\Documents\\drawings", "QET_ENABLE_SCRIPTING": "1" } } } }On macOS use
python3and ordinary/paths. In JSON every\in a Windows path is written\\. -
Quit the app completely and start it again. The tools appear under the chat box's tools menu.
-
If the server did not come installed with QElectroTech, add
"QET_BINARY": "C:\\Program Files\\QElectroTech\\bin\\qelectrotech.exe"(your own path) to theenvblock, so the tools that start QElectroTech can find it β see The program the server runs.
Only files under QET_MCP_WORKSPACE can be read or written (see
Workspace confinement). Leave out
QET_ENABLE_SCRIPTING if you do not want the assistant to edit projects;
see Scripting gate for what that switches off.
The server's tests run on Linux. It uses nothing platform-specific, but these steps have not yet been tested on Windows or macOS.
If your web chat can run Python (on claude.ai, code execution), upload
qet_mcp.py together with your project and ask the assistant to run
single tools with --call:
python3 qet_mcp.py --call qet_elements '{"path": "drawing.qet"}'
echo '{"path": "drawing.qet"}' | python3 qet_mcp.py --call qet_conductors ---call <tool> <arguments> runs one tool and exits. The arguments are a
JSON object, or - to read it from standard input, which saves quoting
JSON for a shell. The result is the tool's JSON, printed to standard
output.
| Exit code | Meaning |
|---|---|
| 0 | the tool succeeded |
| 1 | the tool reported an error (a missing file, a path outside the workspace) β the message is on standard output |
| 2 | the call itself was malformed: unknown tool, arguments that are not a JSON object β the message is on standard error |
The workspace rule applies exactly as it does in the server: with
QET_MCP_WORKSPACE unset, only the folder the script was started in.
The sandbox has no QElectroTech in it, so only the tools that read files
work there: qet_project_info, qet_elements, qet_conductors,
qet_items, qet_diff, qet_scan, qet_element_info,
qet_element_search and qet_element_build. Editing, exporting,
qet_check and qet_query need the desktop app route above.
| Variable | Effect |
|---|---|
QET_MCP_WORKSPACE |
Directories tool calls may read and write, :-separated (; on Windows). Unset: the directory the server was started in. |
QET_MCP_ALLOW_ANY_PATH=1 |
Turns the workspace check off entirely β equivalent to giving the client local filesystem access with this process's privileges. |
QET_ENABLE_SCRIPTING=1 |
Required by five tools (see below); QElectroTech refuses --run without it, default off since PR #984. |
Every path in a tool call is chosen by the model. Without a policy, that
makes the server a read/write primitive for anything the OS lets the
process reach β read any project on disk, export somewhere unrelated,
overwrite a file, embed an arbitrary local image or PDF. So data paths are
confined to QET_MCP_WORKSPACE, checked at the point arguments enter the
server. A path outside it is refused with an error naming what was allowed;
symlinks are resolved first, so a link planted inside the workspace is
judged by where it points.
binary (the qelectrotech executable) and elements_dir (the element
collection) have their own rule, below.
Since PR #1129
(merged 2026-09-29). Before it, binary was taken from each tool call and whatever
executable file it named was run, with the call's own paths as arguments.
Text inside a project from someone else could therefore steer an assistant
into starting another program. Now the server finds QElectroTech itself
and a call cannot choose:
| Variable | Effect |
|---|---|
QET_BINARY |
the QElectroTech the tools launch. Without it: the installation the server sits in (<prefix>/share/qelectrotech/mcp/qet_mcp.py), else qelectrotech on PATH
|
QET_MCP_BINARIES |
other executables a call may name as binary, separated like QET_MCP_WORKSPACE β for comparing two builds |
QET_MCP_ALLOW_ANY_BINARY=1 |
turns the check off: a call can then run any program |
QET_MCP_ELEMENTS |
element collections a call may name as elements_dir, besides the workspace and the installed one |
binary can be left out of every call. When given, it must be the same file
(after following symlinks) or one listed in QET_MCP_BINARIES; anything else
is refused, even a file inside the workspace. A setup that relied on passing
a program that is not on PATH needs QET_BINARY in its env block.
Nothing is overwritten unasked. qet_export, qet_edit,
qet_project_new and qet_element_build refuse an output that already
exists unless the call passes "overwrite": true β the one step this
server cannot undo is the one step it will not take on its own.
Needs QET_ENABLE_SCRIPTING=1
|
qet_query, qet_continuity, qet_check, qet_layout_check, qet_project_new, qet_edit, and the script, live and recording tools marked so on their own pages |
| Unaffected | Everything else β they read the .qet/.elmt directly, or, for qet_export, use a plain CLI flag |
The variable goes in the environment the server is started in (the env
block above), and the server passes it straight through to QElectroTech β
it does not set the variable itself. A switch a program turns on for itself
is not a switch: whoever configured the server and pointed it at a
QElectroTech binary made that choice, and their own interactive
QElectroTech keeps whatever its own setting says. Without it, the tools
above return "ok": false with a hint naming the variable. Builds
from before the setting existed need nothing.
| Tool | What it answers | Launches QET? |
|---|---|---|
qet_project_info |
Title, format version, sheets with their uuids, element/conductor counts per sheet | No |
qet_items |
The drawn items that are not symbols or wires β free texts, shapes, pictures, tables, symbols' text fields β each with its uuid, sheet and main fields; filter by sheet or kind (text, shape, image, table, element_text) |
No |
qet_elements |
Placed elements: uuid, type, position, label, information bag; filter by sheet or name | No |
qet_conductors |
Conductors and their documentation fields (num, formula, cable, bus, function, colour, section); filter by attribute |
No |
qet_diff |
What an edit actually changed β element moves/adds/removes/relabels, conductor field changes, sheet fields/texts/shapes/images/symbol text fields/terminal strips | No |
qet_scan |
Sweep a directory of projects, counting nodes carrying an attribute, with distinct values found | No |
qet_element_info |
Introspect a .elmt: translated names, terminals, dynamic-text info fields, part counts |
No |
qet_export |
Headless export: pdf, png, svg, bom, cables, wires, wiring, nets, links, info | Yes |
qet_edit |
Change a project β place, move, rotate, label, wire, number, cross-reference, add text/shapes/images, restyle a symbol's text fields, delete, line symbols up, route wires around symbols, size a sheet for paper; returns a qet_diff of the result |
Yes |
qet_query |
Read-only SQL SELECT/WITH against the project's SQLite database; omit sql to list queryable views/tables |
Yes* |
qet_continuity |
ERC-style checks against the live Terminal/Conductor graph: unconnected terminals, potential mismatches, sheet-report link mismatches | Yes* |
qet_project_new |
Start from nothing: an empty project with a title and sheets, written and read back by QElectroTech itself | Yes* |
qet_element_search |
Find a symbol in a collection by name (any language), link type, kind or terminal count; results carry the common:// path and terminal index order qet_edit needs |
No |
qet_check |
Design-rule checks: duplicate labels, unlabelled masters, unnumbered conductors, empty sheets, masters missing a manufacturer reference, crowded terminals, folio reports with several wires | Yes* |
qet_layout_check |
How well a drawing reads, scored 0β100: wires that jog, extra bends, wires through symbols, overlaps, symbols off the grid, crossings, labels over wires, 4-way junctions β with the moves that fix the jogs | Yes* |
qet_element_build |
Author a new .elmt: draw from lines/rects/ellipses/circles/arcs/polygons/text, with terminals to wire it by; computes and checks the size header |
No |
* Needs QET_ENABLE_SCRIPTING=1.
Three more groups of tools, merged from 2 October 2026, are described on their own pages:
| Tools | Page | PR |
|---|---|---|
qet_script_api, qet_script_test, qet_script_install, qet_script_list, qet_script_read, qet_script_remove, qet_about
|
Script buttons | #1224 |
qet_live_status, qet_live_run_script, qet_live_run_stored, qet_live_command, qet_live_show_folio, qet_live_undo_last, qet_live_screenshot
|
Live mode | #1225 |
qet_live_new_project, qet_live_open_project, qet_live_switch_project, qet_live_save_project, qet_live_close_project, qet_live_print, qet_live_changes, qet_live_layout_check
|
Live mode | #1312, #1313 |
qet_recording_list, qet_recording_read, qet_recording_check, qet_recording_remove
|
Macro recorder | #1228 |
qet_about is the one to call first: it reads what QElectroTech last wrote
about itself (version, folders, whether scripting and live mode are on), and
house_style, your own drawing rules if you set any, which its description
tells the assistant to follow β see
Your house style.
{"name": "qet_diff", "arguments": {"before": "a.qet", "after": "b.qet"}}"elements": { "moved_count": 4,
"distinct_move_deltas": [[0.0, -80.0]],
"relabelled": [], "info_changed": [] }Four elements moved by one uniform delta; nothing was relabelled. That is the answer a screenshot gave wrongly.
{"name": "qet_edit", "arguments": {
"binary": "/path/to/qelectrotech",
"project": "in.qet", "output": "out.qet",
"elements_dir": "/path/to/qelectrotech/elements",
"operations": [
{"op": "add_folio", "id": "f"},
{"op": "set_folio_title", "folio": "$f", "title": "Starter"},
{"op": "add_element", "id": "k1", "folio": "$f", "path": "common://.../coil.elmt", "x": 100, "y": 100},
{"op": "add_element", "id": "k2", "folio": "$f", "path": "common://.../coil.elmt", "x": 320, "y": 100},
{"op": "add_conductor", "folio": "$f", "from": "$k1", "from_terminal": 0, "to": "$k2", "to_terminal": 0},
{"op": "set_conductor", "folio": "$f", "element": "$k1", "terminal": 0, "property": "num", "value": "W7"},
{"op": "set_label", "folio": "$f", "element": "$k1", "label": "KM1"}
]}}An op that creates something takes an "id"; later ops name it as "$id".
Terminals are addressed by index β top to bottom, then left to right,
not the order the .elmt lists them; qet_element_info and
qet_element_search both report that index order. The result carries a
per-operation outcome and a qet_diff, because "addConductor β true"
says the call was accepted, not that the file came out right:
"diff": {"elements": {"before": 11, "after": 13, "added": ["{0aa3β¦}", "{6f63β¦}"]},
"conductors": {"before": 47, "after": 48, "added": ["4:{0aa3β¦}/{2904β¦}--{6f63β¦}/{2904β¦}"],
"removed": []}}A wire is straight only when its two terminals are exactly in line; a
symbol 3 px off gives a wire with a small jog in it. Four qet_edit
operations line symbols up
(PR #1266):
| Op | Arguments | Does |
|---|---|---|
place_element |
folio, path, terminal, next_to, next_to_terminal; optional side (below, above, right, left; default: the way next_to_terminal faces), gap (default 40 px, terminal to terminal), angle (a rotation applied first) |
adds a symbol with its terminal in line with next_to's terminal, and returns its uuid like add_element. Notes when that terminal faces the wrong way, so the wire would bend |
align_terminal |
folio, element, terminal, to, to_terminal
|
moves a placed symbol across, so its terminal is in line with the other one |
align_elements |
folio, elements, edge (left, center, right, top, middle, bottom); optional to
|
lines up the symbols' boxes, as Γdition > Aligner does; with to, on that symbol |
distribute_elements |
folio, elements, axis (horizontal, vertical); optional pitch
|
spaces the symbols' origins evenly, or pitch apart |
place_element and align_terminal need a QElectroTech with
qet.terminalPosition() (development builds since 3 October 2026); with an
older one the call says which call is missing and changes nothing.
{"name": "qet_edit", "arguments": {
"project": "in.qet", "output": "out.qet", "elements_dir": "/path/to/qelectrotech/elements",
"operations": [
{"op": "add_element", "id": "k1", "folio": 0, "path": "common://.../coil.elmt", "x": 200, "y": 100},
{"op": "place_element", "id": "h1", "folio": 0, "path": "common://.../lamp.elmt",
"terminal": 0, "next_to": "$k1", "next_to_terminal": 1},
{"op": "add_conductor", "folio": 0, "from": "$k1", "from_terminal": 1, "to": "$h1", "to_terminal": 0}
]}}Then qet_layout_check
(PR #1265)
scores the result from 0 to 100 and lists what spoils it:
{"name": "qet_layout_check", "arguments": {"project": "out.qet"}}| Finding | Severity | Means |
|---|---|---|
avoidable_bend |
warning | two terminals face each other but are a few pixels out of line, so the wire jogs. fixes holds the move that makes it straight |
extra_bends |
info | the wire bends more often than its two ends need, often a segment moved by hand; route_conductor redraws it |
wire_through_symbol |
warning | a wire runs through a symbol it is not connected to |
overlapping_symbols |
warning | two symbols overlap by more than one grid step |
off_grid |
warning | a symbol is off the 10 px grid; fixes moves it on |
label_on_wire |
warning | a symbol's label is drawn over a wire: move the label, not the symbol |
four_way_junction |
warning | wires leave one point in all four directions, which reads as a crossing when printed: make it two T-junctions |
misaligned_branch |
info | two symbols stacked in one side branch are a little out of line, so the branch is not one straight column |
crossing |
info | two wires cross; counted so two drafts can be compared, but does not lower the score |
| Argument | |
|---|---|
folio |
one sheet, counted from 1; leave out for all |
style |
iec (current paths are columns, wires mostly vertical), nfpa (ladder rungs are rows, wires mostly horizontal) or auto (default: decided from the drawing) |
max_shift |
the largest move a fix may suggest, default 40 px; a bigger jog is taken as meant |
limit |
how many findings to return, default 50; the summary counts them all |
The score is 60 % the share of wires with no finding and 40 % the share of
symbols with none. fixes is a list of move_element operations, one per
symbol, planned together: pass the whole list to one qet_edit call, then run the check
again. It reads only; nothing is saved.
add_conductor takes "route": "avoid" to draw the new wire around the
symbols in its way, instead of QElectroTech's usual two or three straight
segments that may cross a symbol. route_conductor does the same to a wire
already there, named by element + terminal or by "conductor": "{uuid}",
and answers routed or no-route:
{"op": "add_conductor", "folio": 0, "from": "$k1", "from_terminal": 1,
"to": "$h1", "to_terminal": 0, "route": "avoid"}Where there is no way round, the wire keeps its path and the op's note says so: not a failure. Route after placing everything; moving a symbol later stretches the path rather than routing it again. What counts as an obstacle is on JavaScript Scripting (PR #1245).
{"op": "set_folio_border", "folio": 0, "property": "preset", "value": "a3-landscape"}a0βa5, letter, legal, tabloid, ledger, each -portrait or
-landscape. It picks the column and row counts and sizes that fill that
sheet, allowing for the title block, as one undo step; the op's note says
what it chose and the frame's size, and qet_export's PDF of it comes out
on that paper (PR #1250).
set_folio_border also sets columns, column-width, display-columns,
rows, row-height and display-rows one at a time.
{"name": "qet_element_build", "arguments": {
"output": "/path/to/collection/99_custom/my_resistor.elmt",
"names": {"en": "Test resistor", "fr": "RΓ©sistance de test"},
"parts": [
{"type": "rect", "x": -10, "y": -20, "width": 20, "height": 40},
{"type": "line", "x1": 0, "y1": -30, "x2": 0, "y2": -20},
{"type": "line", "x1": 0, "y1": 20, "x2": 0, "y2": 30},
{"type": "text", "x": 14, "y": -4, "text": "R"}
],
"terminals": [{"x": 0, "y": -30, "orientation": "n", "name": "1"},
{"x": 0, "y": 30, "orientation": "s", "name": "2"}]}}Then place it with qet_edit like any catalogue element. Unlike a project,
a .elmt is not rewritten by QElectroTech on a round trip, so generating
one here is safe in a way that generating a .qet would not be β there is
no toXml() waiting to drop what this writer did not know to emit.
{"name": "qet_query", "arguments": {
"binary": "/path/to/qelectrotech", "project": "industrial.qet",
"sql": "SELECT label, COUNT(*) AS n FROM element_nomenclature_view WHERE label <> '' GROUP BY label HAVING n > 1 ORDER BY n DESC"}}"rows": [{"label": "V6", "n": 7}, {"label": "V5", "n": 6}, {"label": "V4", "n": 6}]Duplicate element labels in a shipped example β a design-rule question,
answered by the database that already knew it. See
The project database for what element_nomenclature_view,
project_summary_view and wiring_list_view cover.
{"name": "qet_scan",
"arguments": {"directory": "examples", "tag": "conductor", "attribute": "cable"}}{ "files": 24, "total": 3190, "non_empty": 0, "distinct_values": [] }Across the shipped examples: 3190 conductors, not one with a cable value.
-
qet_exportisolates its launch. SingleApplication keys its socket onapplicationFilePath(), so a second launch of the same binary path forwards its request to an already-running instance and returns that process's answer, with no error. The tool copies the binary to a unique temporary path, gives it a privateHOME, and runs it on the offscreen platform. A symlink would not work βapplicationFilePath()resolves it back to the real path. -
The CLI matches its flags exactly.
--export-bom out.csvis the supported form;--export-bom=out.csvis not recognised as an export at all, so the application starts its interface instead and a headless run hangs. The tool uses the positional form. -
How
qet_diffmatches items. Wires, sheets, free texts, shapes, pictures, tables and symbols' text fields are matched by their uuid when every item of that kind in both files has one (PR #1099); each section of the answer says what it was matched by inkeyed_by. A file saved by an older QElectroTech may lack some, and then that kind is matched as before: wires by the element uuid and terminal at each end, the other items by position. Matched by position, a moved item reads as one removed and one added. The file's own sheet-scoped wire ids are never used: they are renumbered on every save. -
Every drawn item can be named by its uuid.
qet_itemslists them;qet_edittakes a uuid wherever it takes a sheet (folio), a wire (conductor), a terminal, a table (table), a symbol's text field (index, looked up within the op's element) or a free text, shape or picture (index). Unlike a position, a uuid still names the same item after an earlier op adds or removes one (PRs #1101, #1108, #1115, #1126). -
qet_editneeds a build whose scripting API carries the drawing verbs. Against an older one it reports exactly which methods are missing and changes nothing. -
elements_diris not optional forcommon://paths. The sandboxed run has its own emptyHOME, so QElectroTech falls back to the compiled-in collection path, which on a machine that never ranmake installdoes not exist. The only symptom isadd_elementreporting that a file plainly present "does not resolve to an element". An absolute.elmtpath works without it. -
set_conductorchanges the whole potential, not one segment β that is what the application does, since a wire number describes a potential. Name a terminal carrying exactly one conductor; a terminal several conductors meet at names none of them and is refused.set_conductor,move_conductor_segmentanddelete_conductoralso take"conductor": "{uuid}"(asqet_conductorsreports it) in place ofelement+terminal, which works where two conductors meet at a terminal, as long as one of the conductor's two terminals carries only it. -
A sheet's wire defaults:
set_conductor_default.Sets one of the defaults in PropriΓ©tΓ©s du folio (Sheet properties).
onetextperfoliois the option activer l'option un texte par potentiel:"true"draws one wire number per potential on that sheet instead of one per wire. Anyset_conductorproperty is accepted too, as the starting value for wires drawn later on that sheet.{"op": "set_conductor_default", "folio": 0, "property": "onetextperfolio", "value": "true"}"folio": -1sets the project's defaults instead. Sheets added afterwards copy them; existing sheets keep their own, so set each one. Like the dialogs, this has no undo. -
A terminal can be given by its uuid (PR #1126):
terminal,from_terminalandto_terminalalso take the terminal's uuid (asqet_element_infolists it) in place of its index, on the op's own element β foradd_conductor, each end's own. Unlike the index, it tells apart two terminals at the same point. A uuid the element does not have stops the run, with a note naming the argument. -
link_elementstakes a sheet for each end, because a master and its slave are normally on different sheets. Whether a pair may be linked is decided by QElectroTech's ownisLinkable(), so a script cannot make a link the GUI would refuse. -
An element must live inside a collection to be placeable. An absolute
.elmtpath works, but only if the file sits under a directory QElectroTech knows as a collection β write it under the tree passed aselements_dir. -
qet_element_buildchecks its size header against a containment constraint, not a formula: the declared box runs from(-hotspot_x, -hotspot_y)to(width - hotspot_x, height - hotspot_y)and the drawing must fit inside it. A drawing that escaped its box is the classic way a hand-written element renders clipped in the collection panel while looking fine in the XML. -
Two
qet_checkrules come from the wires-per-terminal discussion (#1158, PR #1271), both info:crowded_terminalslists terminals with more than four wires (two double ferrules, one each side of the screw, is already a lot; cable, busbar and single-line symbols carry more on purpose), andreports_with_several_wireslists folio report arrows with more than one wire β a style some drawings use on purpose, not an error. Pass"checks": [...]to run only some rules. -
qet_element_searchtreats contact names as the same word in every form: NO, NC, N/O, NF, and "normally open/closed". A one- or two-letter word such asNCmatches only as a whole word, so "NC contact" no longer offers coils whose names merely contain those letters (PR #1285). -
Each launch has its own settings, on every system. The tools that start QElectroTech give it a private home folder and set
QET_SETTINGS_DIR, which makes QElectroTech keep its settings in<folder>/QElectroTech/QElectroTech.iniinstead of the registry (Windows), the system preferences (macOS) or~/.config(Linux). That is howelements_dirreaches QElectroTech on Windows and macOS too, and why a tool run never changes your own settings (PRs #1183, #1254). You can use the same variable yourself, for example to run QElectroTech with a separate set of settings. -
QElectroTech interrupts a script at 30 s of its own accord, separate from the tool's own
timeout. A very long operation list hits that first. -
qet_editnever writes the input. It saves to a separate file and diffs the two, so the original is always the thing the diff is against.
python3 misc/qet-mcp/test_qet_mcp.py # unit + protocol, no QElectroTech needed
QET_BINARY=/path/to/qelectrotech \
QET_ELEMENTS=/path/to/qelectrotech/elements \
QET_EXAMPLES=/path/to/qelectrotech/examples \
QET_ENABLE_SCRIPTING=1 \
python3 misc/qet-mcp/test_qet_mcp.py # everything, integration includedQET_ENABLE_SCRIPTING=1 matters here too: without it the integration tests
that drive QElectroTech through a script all fail, and they fail as "the
edit did nothing" rather than as "scripting is off" β which reads like a
regression in the thing under test, not a missing switch.
- Connecting an AI assistant β setup for each assistant
-
JavaScript Scripting β the
qet.*engineqet_editandqet_querydrive underneath - Script buttons, Live mode, Macro recorder β the script, live and recording tools
-
The project database β what
qet_queryreads -
CLI Reference β the export flags
qet_exportwraps - Automating QElectroTech β the file-format and headless-export ground this builds on
- PR #969 β the server itself
- PR #970 β the scripting-API verbs it depends on
Getting Started
π Languages β English Β· FranΓ§ais Β· Deutsch
Windows without admin rights β the portable archive, no installer
Guides
Conductors β wire properties, what feeds which export, cables, and hops where wires cross
Wires per terminal β limit the wires on a terminal, chain wiring instead of stars
Printing and exporting β paper, PDF, images, and what each path does differently
Linking elements β master, slave, terminal
PLC modules β I/O tables and linking a wire to a specific point
Using the element editor β drawing tools, saving, checks
Grid size and element size β why symbols aren't all the same scale, and scaling one without leaving the grid
Preferences reference β what each settings page does
Saving and loading settings β your whole setup in one file, to copy or keep
Keyboard-only control β mouseless QET, and what still needs a mouse
Mouse modifiers β what Shift, Ctrl and Alt change while you drag
3D mouse β SpaceMouse pan, zoom and buttons
Aligning items β snap symbols back to the grid, or line them up
Pictures on a sheet β labels, crop, transparency, what they cost in the file
Arcs and curved wires β the Arc tool, pulling an arc in or out, rounding a corner with a fillet, dashed arcs for lighting layouts
Grouping items β select, move and copy several items as one
Finding your place on a sheet β go to a cell like B13 or 4-B7, keep the headers in sight, show the cell limits, zoom and pan
Showing and hiding kinds of items β hide texts, wire numbers, shapes, pictures, tables or cross-references on every sheet
Drawing faster β place without dragging, the S shortcut bar, command search, gestures
Customising QElectroTech β keys, toolbar size and contents, the gesture ring (partly pending)
Managing collections β folders, writability, building your own shortlist
Templates β reusable multi-element blocks, placed by double-click or drag
Search & Replace β bulk property changes
Building a nomenclature query β the BOM/summary table builder
Linking wires across pages β sheet reports
Variables & formulas β %f, %{label}, sequences
Auto-numbering β schemes, sequences, freezing
Terminal strips β strips, levels, bridges
Title block templates β the .titleblock format
Importing EPLAN parts (.edz) β EPLAN Data Portal
DXF import & export β two unrelated features, one format; command-line export and layers
The project database β the in-memory SQLite cache
Development
Automating QET β CLI, XML formats, external tools
CLI Reference β command line usage
JavaScript Scripting β --run, geometry editing, undo
MCP server β let an AI assistant read, verify and edit projects
Connecting an AI assistant β setup for Claude, Copilot, Gemini, Codex, Cursor, LM Studio
Script buttons β stored scripts with an icon, by hand or by an assistant
Live mode β an assistant working in the open project while you watch
Macro recorder β record a task by hand, for an assistant to script
Vision β proposal, under discussion
