Skip to content
ispyisail edited this page Sep 24, 2026 · 1 revision

Serveur MCP

Permet à un assistant IA (Claude, ou tout autre client MCP) de lire et de vérifier des projets QElectroTech directement, plutôt que de raisonner à partir d'une capture d'écran : ce que contient un projet, ce qu'une modification a réellement changé, et ce que contient tout un corpus de projets.

Il se trouve dans misc/qet-mcp/qet_mcp.py dans l'arborescence des sources — un petit serveur Model Context Protocol en stdio, Python 3.9+ et la bibliothèque standard uniquement, sans dépendance au SDK MCP. Ajouté dans la PR #969 ; sa fondation d'API de script a été ajoutée en même temps dans la PR #970. Les deux ont été fusionnées le 2026-09-21.


Pourquoi ce serveur existe

Vérifier une modification par capture d'écran n'est pas fiable, et cet outil existe parce que cette non-fiabilité a produit deux conclusions erronées dans une seule session de relecture :

  • Un glisser-déposer d'une sélection multi-éléments semblait avoir laissé les symboles en place et détaché leurs étiquettes. La comparaison du fichier enregistré a montré que les quatre éléments avaient tous bougé d'un même delta (0, -80) et qu'aucune étiquette n'avait bougé du tout. Un rapport de bogue est passé à un cheveu d'être déposé.
  • Un bouton « Appliquer » semblait ne rien faire. Il était désactivé, car un champ obligatoire était vide.

Les deux fois, les pixels ont induit en erreur et le modèle a dit la vérité. Les outils présentés ici lisent donc le modèle — le XML du projet et, quand elle existe, la base de données du projet — plutôt que la scène rendue.

La plupart des outils analysent directement le fichier .qet/.elmt : rapide, sans affichage nécessaire, insensible à une boîte de dialogue égarée. Deux d'entre eux (qet_export, qet_edit) lancent QElectroTech lui-même, dans un bac à sable isolé, parce qu'exporter et modifier via l'application réelle est le seul moyen d'obtenir le comportement réel — voir Scripts JavaScript pour le moteur que qet_edit et qet_query pilotent en dessous.


Installation

# depuis l'arborescence des sources de QElectroTech
python3 misc/qet-mcp/qet_mcp.py --list   # liste les outils puis quitte
python3 misc/qet-mcp/qet_mcp.py          # parle MCP sur stdin/stdout

Enregistrez-le auprès d'un client MCP — pour Claude Code ou Claude Desktop, un bloc mcpServers :

{
  "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"
      }
    }
  }
}

Rien à compiler, rien à installer via pip — les deux variables d'environnement ci-dessus sont la seule configuration qui compte, et les deux sont détaillées plus bas.


Variables d'environnement

Variable Effet
QET_MCP_WORKSPACE Répertoires que les appels d'outils peuvent lire et écrire, séparés par : (; sous Windows). Non définie : le répertoire depuis lequel le serveur a été démarré.
QET_MCP_ALLOW_ANY_PATH=1 Désactive entièrement la vérification de l'espace de travail — équivalent à donner au client un accès au système de fichiers local avec les privilèges de ce processus.
QET_ENABLE_SCRIPTING=1 Requis par cinq outils (voir plus bas) ; QElectroTech refuse --run sans cette variable, désactivée par défaut depuis la PR #984.

Confinement de l'espace de travail

Chaque chemin passé dans un appel d'outil est choisi par le modèle. Sans politique de contrôle, cela ferait du serveur une primitive de lecture/écriture pour tout ce que le système d'exploitation permet au processus d'atteindre : lire n'importe quel projet sur le disque, exporter ailleurs, écraser un fichier sans rapport, intégrer une image ou un PDF local arbitraire. C'est pourquoi les chemins de données sont confinés à QET_MCP_WORKSPACE, vérifiés au point où les arguments entrent dans le serveur. Un chemin en dehors de celui-ci est refusé avec une erreur nommant ce qui était autorisé ; les liens symboliques sont résolus au préalable, si bien qu'un lien planté à l'intérieur de l'espace de travail est jugé d'après ce vers quoi il pointe.

Deux arguments ne sont volontairement pas confinés : binary (l'exécutable qelectrotech) et elements_dir (la collection d'éléments). Ce sont des réglages de configuration, choisis une fois par la personne qui exécute le serveur, et tous deux vivent normalement dans /usr ou dans une arborescence de compilation — en dehors de tout espace de travail raisonnable. Les confiner rejetterait le cas ordinaire sans rien empêcher.

Rien n'est écrasé sans autorisation. qet_export, qet_edit, qet_project_new et qet_element_build refusent une output déjà existante à moins que l'appel ne passe "overwrite": true — la seule étape que ce serveur ne peut pas annuler est la seule qu'il ne franchira pas de lui-même.

Verrou des scripts

Nécessite QET_ENABLE_SCRIPTING=1 qet_query, qet_continuity, qet_check, qet_project_new, qet_edit
Non concernés Tous les autres — ils lisent le .qet/.elmt directement, ou, pour qet_export, utilisent un simple indicateur de ligne de commande

La variable se règle dans l'environnement où le serveur est démarré (le bloc env ci-dessus), et le serveur la transmet telle quelle à QElectroTech — il ne la définit pas lui-même. Un interrupteur qu'un programme active pour lui-même n'est pas un interrupteur : c'est la personne qui a configuré le serveur et l'a pointé vers un binaire QElectroTech qui a fait ce choix, et son propre QElectroTech interactif conserve ce que dit son propre réglage. Sans la variable, les cinq outils ci-dessus renvoient "ok": false avec un hint nommant la variable. Les versions antérieures à l'existence de ce réglage n'ont besoin de rien.


Outils

Outil À quoi il répond Lance QET ?
qet_project_info Titre, version du format, folios, décompte d'éléments/conducteurs par folio Non
qet_elements Éléments placés : uuid, type, position, étiquette, sac d'informations ; filtre par folio ou par nom Non
qet_conductors Conducteurs et leurs champs documentaires (num, formula, cable, bus, function, colour, section) ; filtre par attribut Non
qet_diff Ce qu'une modification a réellement changé — déplacements/ajouts/suppressions/réétiquetages d'éléments, changements des champs de conducteur, champs/textes/formes/images/champs de texte de symboles/borniers de folio Non
qet_scan Parcourt un répertoire de projets, comptant les nœuds portant un attribut, avec les valeurs distinctes trouvées Non
qet_element_info Introspection d'un .elmt : noms traduits, bornes, champs de texte dynamique, décompte des parties Non
qet_export Export sans interface : pdf, png, svg, bom, cables, wires, wiring, nets, links, info Oui
qet_edit Modifier un projet — placer, déplacer, faire pivoter, étiqueter, câbler, numéroter, faire des renvois, ajouter texte/formes/images, restyler les champs de texte d'un symbole, supprimer ; renvoie un qet_diff du résultat Oui
qet_query Requête SQL en lecture seule (SELECT/WITH) sur la base SQLite du projet ; omettre sql liste les vues/tables interrogeables Oui*
qet_continuity Vérifications de type ERC sur le graphe Borne/Conducteur en direct : bornes non connectées, incohérences de potentiel, incohérences de renvoi de folio Oui*
qet_project_new Démarrer de zéro : un projet vide avec un titre et des folios, écrit et relu par QElectroTech lui-même Oui*
qet_element_search Trouver un symbole dans une collection par nom (toute langue), type de liaison, genre ou nombre de bornes ; les résultats portent le chemin common:// et l'ordre d'index des bornes dont qet_edit a besoin Non
qet_check Vérifications de règles de conception : étiquettes en double, bornes non connectées, conducteurs sans numéro, folios vides, maîtres sans référence fabricant Oui*
qet_element_build Créer un nouveau .elmt : dessiné à partir de lignes/rectangles/ellipses/cercles/arcs/polygones/texte, avec des bornes pour le câbler ; calcule et vérifie l'en-tête de dimensions Non

* Nécessite QET_ENABLE_SCRIPTING=1.


Exemples commentés

Qu'est-ce que cette modification a changé ?

{"name": "qet_diff", "arguments": {"before": "a.qet", "after": "b.qet"}}
"elements": { "moved_count": 4,
              "distinct_move_deltas": [[0.0, -80.0]],
              "relabelled": [], "info_changed": [] }

Quatre éléments ont bougé d'un même delta ; rien n'a été réétiqueté. C'est la réponse qu'une capture d'écran donnait à tort.

Dessiner quelque chose, et vérifier que c'est bien arrivé

{"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"}
  ]}}

Une opération qui crée quelque chose porte un "id" ; les opérations suivantes le désignent comme "$id". Les bornes sont adressées par index — de haut en bas puis de gauche à droite, pas l'ordre dans lequel le .elmt les liste ; qet_element_info et qet_element_search rapportent tous deux cet ordre d'index. Le résultat porte à la fois un résultat par opération et un qet_diff, car "addConductor → true" dit que l'appel a été accepté, pas que le fichier obtenu est correct :

"diff": {"elements":   {"before": 11, "after": 13, "added": ["{0aa3…}", "{6f63…}"]},
         "conductors": {"before": 47, "after": 48, "added": ["4:{0aa3…}/{2904…}--{6f63…}/{2904…}"],
                        "removed": []}}

Dessiner un symbole qui n'existe pas encore

{"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"}]}}

Placez-le ensuite avec qet_edit comme n'importe quel élément de catalogue. Contrairement à un projet, un .elmt n'est pas réécrit par QElectroTech lors d'un aller-retour, donc en générer un ici est sûr d'une façon qui ne le serait pas pour un .qet — il n'y a pas de toXml() en embuscade pour perdre ce que cet écrivain ne savait pas produire.

Poser une question à laquelle le XML ne peut pas répondre

{"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}]

Des étiquettes d'éléments en double dans un exemple fourni — une question de règle de conception, à laquelle la base de données savait déjà répondre. Voir La base de données du projet pour ce que couvrent element_nomenclature_view, project_summary_view et wiring_list_view.

Quelle part d'un corpus utilise un champ ?

{"name": "qet_scan",
 "arguments": {"directory": "examples", "tag": "conductor", "attribute": "cable"}}
{ "files": 24, "total": 3190, "non_empty": 0, "distinct_values": [] }

Sur les exemples fournis : 3190 conducteurs, aucun n'a de valeur de câble.


Remarques et limites

  • qet_export isole son lancement. SingleApplication indexe son socket sur applicationFilePath(), donc un second lancement du même chemin binaire redirige sa requête vers une instance déjà en cours d'exécution et renvoie sa réponse, sans erreur. L'outil copie le binaire vers un chemin temporaire unique, lui donne un HOME privé, et le lance sur la plateforme hors écran. Un lien symbolique ne fonctionnerait pas — applicationFilePath() le résout jusqu'au chemin réel.
  • La ligne de commande exige la forme exacte des indicateurs. --export-bom out.csv est la forme prise en charge ; --export-bom=out.csv n'est pas reconnu comme un export du tout, donc l'application démarre son interface à la place et un lancement sans interface reste bloqué. L'outil utilise la forme positionnelle.
  • L'identité des conducteurs est la partie difficile de qet_diff. Les conducteurs sont identifiés par l'uuid de l'élément propriétaire plus la borne, ce qui est stable d'un enregistrement à l'autre — les identifiants entiers propres au folio du fichier sont renumérotés à chaque enregistrement et feraient lire chaque conducteur d'un folio non modifié comme supprimé puis rajouté. Quand un élément est antérieur aux uuid persistés, l'extrémité ne peut pas être résolue et conserve une clé instable marquée # ; le diff rapporte alors unstable_keys plutôt que de prétendre être comparable.
  • Les textes, formes et images n'ont pas d'uuid, donc un texte modifié est lu comme l'ancien supprimé et un nouveau ajouté, les deux étant affichés. Les formes et images sont identifiées par position, donc un changement de style ou d'échelle est rapporté comme un changement de cet élément, mais un déplacement se lit comme une suppression plus un ajout.
  • qet_edit nécessite une version dont l'API de script porte les verbes de dessin. Face à une version plus ancienne, il indique précisément quelles méthodes manquent et ne change rien.
  • elements_dir n'est pas optionnel pour les chemins common://. Le lancement en bac à sable a son propre HOME vide, donc QElectroTech se rabat sur le chemin de collection compilé en dur, qui n'existe pas sur une machine où make install n'a jamais été exécuté. Le seul symptôme est que add_element rapporte qu'un fichier pourtant bien présent « ne correspond à aucun élément ». Un chemin .elmt absolu fonctionne sans cela.
  • set_conductor change tout le potentiel, pas un seul segment — c'est ce que fait l'application, puisqu'un numéro de fil décrit un potentiel. Nommez une borne portant exactement un conducteur ; une borne où plusieurs conducteurs se rejoignent n'en désigne aucun et est refusée.
  • link_elements prend un folio pour chaque extrémité, car un maître et son esclave sont normalement sur des folios différents. La possibilité de lier une paire est décidée par le isLinkable() propre à QElectroTech, donc un script ne peut pas créer un lien que l'interface refuserait.
  • Un élément doit se trouver dans une collection pour être plaçable. Un chemin .elmt absolu fonctionne, mais seulement si le fichier se trouve sous un répertoire que QElectroTech connaît comme collection — écrivez-le sous l'arborescence passée comme elements_dir.
  • qet_element_build vérifie son en-tête de dimensions par rapport à une contrainte de contenance, pas une formule : la boîte déclarée va de (-hotspot_x, -hotspot_y) à (width - hotspot_x, height - hotspot_y) et le dessin doit tenir à l'intérieur. Un dessin qui a débordé de sa boîte est la façon classique dont un élément écrit à la main s'affiche tronqué dans le panneau de collection tout en semblant correct dans le XML.
  • QElectroTech interrompt un script au bout de 30 s de sa propre initiative, indépendamment du timeout propre à l'outil. Une très longue liste d'opérations atteint cette limite en premier.
  • qet_edit n'écrit jamais le fichier d'entrée. Il enregistre dans un fichier séparé et compare les deux, donc l'original reste toujours ce contre quoi le diff est fait.

Tests

python3 misc/qet-mcp/test_qet_mcp.py                      # unitaires + protocole, sans QElectroTech

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                  # tout, y compris l'intégration

QET_ENABLE_SCRIPTING=1 compte aussi ici : sans cette variable, les tests d'intégration qui pilotent QElectroTech via un script échouent tous, et ils échouent en disant « la modification n'a rien fait » plutôt que « les scripts sont désactivés » — ce qui ressemble à une régression de la chose testée, pas à un interrupteur manquant.


Voir aussi

Getting Started

Home

🌐 Languages — English · Français · Deutsch

Downloads

Windows without admin rights — the portable archive, no installer

Quick Start Guide

User Manual

FAQ

Tips & Tricks

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

File formats
Elements XML
Project XML
Development

Building from Source

Contributing Code

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

Development Roadmap

Vision — proposal, under discussion

Developer Tools

About

Features

History

Community

License

Contributing to this Wiki

Clone this wiki locally