mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-18 12:04:13 +02:00
f850d0bee86ac0b56b29418bcfccc64879fa2beb
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
08b83f23e3 |
qet-mcp: say what to do when QElectroTech refuses to run scripts
#984 switches JavaScript scripting off by default, and five tools here drive QElectroTech through --run: qet_query, qet_continuity, qet_check, qet_project_new, qet_edit. Against such a build they all stop working, and what came back was exit code 3 and a paragraph of French naming a settings dialog nobody driving an MCP server is looking at. Nothing needed building to make them work again -- _run_qet() inherits its environment, so QET_ENABLE_SCRIPTING=1 in the "env" block of the client's own configuration already reaches QElectroTech. Verified both ways against a #984 binary: without it qet_query returns ok=false exit=3, with it ok=true and the rows. So this is about saying so. The refusal is now recognised and answered with an instruction the caller can act on, keyed on QElectroTech naming the variable with exit 3 as a fallback for a future build that words it differently. Two older hints fitted the same symptom and were overwriting it -- "the binary never ran the script ... is it a build with --run support?" sends the reader to check the one thing that is fine -- so both now yield to whatever the launch already reported. qet_check builds its answer fresh rather than layering onto the launch result, so it carries the reason across explicitly; without that every check read "no result came back", which is true and tells nobody why. The server does not set the variable itself, on purpose. A switch a program turns on for itself is not a switch: whoever configured this server and pointed it at a QElectroTech binary made that choice, and their interactive QElectroTech keeps whatever its own setting says. README says this, and the registration example now shows the env block with both variables in it. Six tests, faking subprocess.run so they cost no launch. Two are structural rather than behavioural: one fails if either older hint goes back to assigning over the specific one, the other reads which tools actually pass script= to _run_qet and fails if the hint's list of them drifts. Both were mutation-checked by reintroducing exactly those mistakes. 176 tests pass with QET_BINARY, QET_ELEMENTS, QET_EXAMPLES and QET_ENABLE_SCRIPTING set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4a636b14e3 |
qet-mcp: confine tool paths to a workspace, and never clobber silently
Two security reviews of #980 landed on the same gap: every path in a tool call is chosen by the model, and nothing checked where those paths pointed. That made the server a read/write primitive for anything the process could reach -- read any project on the disk, export one somewhere else, overwrite an unrelated file, embed an arbitrary local image or PDF. The sandboxed HOME each QElectroTech launch gets isolates settings, not the filesystem. Data paths are now confined to a workspace: QET_MCP_WORKSPACE (os.pathsep separated), defaulting to the directory the server was started in, which is what an MCP host normally sets anyway. QET_MCP_ALLOW_ANY_PATH=1 turns the check off; it exists so that is a visible choice rather than the default. Paths are resolved before comparison, so a symlink planted inside the workspace is judged by where it points -- the case a string-prefix check gets wrong. Two arguments are deliberately exempt: "binary" and "elements_dir". Those are configuration, chosen once by whoever runs the server, and both normally live in /usr or a build tree. Confining them would reject the ordinary case while stopping nothing -- they are not where a model gets to point the server at /etc. Enforcement sits at the dispatcher, where model-supplied arguments enter, not inside each tool. Importing the module and calling tool_export() from Python stays unconfined and is meant to: that is the caller's own code with the caller's own paths. Separately, an existing "output" is now refused unless the call passes "overwrite": true. qet_project_new already worked this way; qet_export, qet_edit and qet_element_build now match it. Replacing a file is the one step this server cannot undo. 17 tests cover it, including the symlink escape, the traversal, the overwrite gate and the operation-level file paths that add_image and add_pdf_page carry one level down. Two of them compare the policy table against the tool schemas, because a write tool missing from either list fails silently in opposite directions. Two more drive a real server process over stdio, which is the only thing that shows a call is gated rather than merely gate-able. Mutation-checked: removing the confinement fails 8, and desynchronising the two lists fails the drift pair. 170 tests pass, with QET_BINARY, QET_ELEMENTS and QET_EXAMPLES set so none are skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3b05f5cf13 |
Sync misc/qet-mcp with the extended scripting API
Brings misc/qet-mcp up to date with the scripting API additions in this branch: qet_edit gains ops for tables, PLC master IO tables and PLC-slave linking, manual conductor segment routing, polygon and path shapes, PDF page import, and project-wide search & replace; a new qet_continuity tool exposes the electrical continuity/ERC-style checks. Adds the test suite that did not exist here before (150 tests: unit validation, JSON-RPC protocol, and Integration/PlcIntegration/ CorpusIntegration runs against a built binary) plus the two minimal PLC fixture .elmt files it needs (no shipped element has masterType/ slaveType "plc" to test against). Also removes a __pycache__/*.pyc that had been committed by mistake, and ignores __pycache__/*.pyc going forward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
6dcb6a2a8f |
Key a conductor by something that survives a save
qet_diff keyed each conductor on its raw terminal1/terminal2 pair, with a comment claiming that pair was "stable within a folio". It is stable within a folio; it is not stable across a save. QElectroTech reassigns those folio-scoped integer ids on every write, in whatever order it serialises the elements, so one untouched conductor of ArduinoLCD.qet goes from terminal1="1" terminal2="16" to terminal1="34" terminal2="15". Diffing a project against a re-saved copy of itself therefore reported 29 of its 47 conductors as removed and 29 as added, with nothing changed. That is the main thing this tool is for, so the conductor half of the answer was noise in exactly the case it was wanted. The format has two addressing schemes and a file can hold both at once. Older conductors use the integer ids with no element1/element2; current ones use terminal uuids from the .elmt definition plus element1/element2 naming the placed instances. A terminal uuid alone is not an identity -- it belongs to the definition, so two coils of one type share it and a conductor between them keys as a self-loop -- so an end is identified by the (instance, terminal) pair, taken from the conductor where it carries one and resolved through the folio's elements where it does not. Where an element predates persisted uuids there is nothing stable to key on. Keying those on terminal geometry alone collapsed nine distinct conductors of schema_indus.qet onto a single key, which is worse than the instability it was meant to fix, so such ends stay unresolved, keep a "#"-marked key, and the diff reports unstable_keys and says in words that added/removed may not mean what they look like. Measured over the 24 shipped example projects, 3190 conductors: 0 colliding keys, against 8 for the geometry-only key. On a re-saved but otherwise untouched project: 0 added, 0 removed, against 29 and 29 before this change. A project with two conductors genuinely added still reports exactly two added and none removed, so the check still discriminates. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
aab0a18506 |
Add misc/qet-mcp: a Model Context Protocol server over QET projects
A small stdio MCP server that lets an assistant read a project, ask what an edit actually changed, and sweep a corpus. Standard library only -- Python 3.9+, no third-party dependencies, and the MCP SDK is not required. Nothing in the build or the application refers to it; it sits in misc/ beside make_icon_themes.py and is inert unless run. It exists because verifying a change by screenshot is unreliable, and that unreliability produced two wrong conclusions in a single 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. An Apply button looked like it did nothing; it was disabled because a required field was empty. Both times the pixels misled and the file told the truth, so these tools read the file. Seven tools: qet_project_info, qet_elements, qet_conductors, qet_diff, qet_scan, qet_element_info and qet_export. Only qet_export launches QElectroTech; everything else parses the .qet or .elmt directly, which needs no display and cannot be confused by a dialog. Two behaviours of QElectroTech are carried inside the tool rather than left for the caller to rediscover. SingleApplication keys its socket on applicationFilePath(), so a second launch of the same path forwards its request to a running instance and returns that process's answer with no error; qet_export therefore copies the binary to a unique temporary path, gives it a private HOME and runs it offscreen. A symlink would not do, because applicationFilePath() resolves it back. And the CLI matches its export flags by exact string (cli_export.cpp:828) with the project and output as positional arguments (:862, :882), so --export-bom=out.csv is not recognised as an export at all and the run starts the interface and hangs headless; the tool uses the positional form. Worth recording for anyone extending this: the project database would be a better query surface than the XML, but it is not reachable from outside the application. projectDataBase::newQuery() and isReadOnlySelect() are C++-internal and the JavaScript scripting API exposes no SQL binding. A --query CLI verb, or a scripting binding, would let this expose the guarded read-only SELECT surface instead. Verified against the shipped examples: qet_scan reports 3190 conductors across the 24 example projects with no cable value, and qet_diff reproduces the four-element move above from the two saved files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |