Repository navigation
MCP server for coding agents, and findings that weigh what a net carries - #13
Merged
Merged
Conversation
The MCP server needs the version diff the compare page shows. The server cannot do it (Blob has no Get, by design), so server/compare ports diffFindings, groupByRule, the headline and diffNets from compare.ts to run in the client on artifacts it downloads. testdata/compare_fixtures.json holds the cases from compare.test.ts with explicit answers, and both the Go and the vitest suites check them, so the two implementations cannot drift apart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
server/localapp is the Go twin of the KiCad plugin's desktop.py: read endpoint.json (loopback URLs only, never believed without a probe of /api/local/status), honour EMI_ANALYZER_URL and EMI_ANALYZER_APP, and start the installed app one way at a time so there is never a second copy with a second data folder. One addition: a client that is emi-local itself can start it headless (-open=false) when no app could be started at all, never after one did. Started processes are detached and log to the user cache folder (emi-analyzer/launch.log). `open -b` is waited for, since it exits 1 at once when the app is not registered. Tests port desktop.py's cases; checked live against a real emi-local started headless with a scratch endpoint file and data folder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
server/emiclient is the Go twin of the KiCad plugin's client.py and project.py, reading from disk instead of the editor: - Pack takes a .kicad_pcb (with its .kicad_pro and the first committed rules file), a .kicad_pro, a folder with one board or a Gerber set (the files the worker reads, by its own rules), or a .zip sent as is. Archives are sorted with a fixed timestamp so the same files give the same bytes. - Client covers projects, boards, runs, artifacts (through the URL the app mints) and the two-step upload. - Open files every version of a board in one project, remembered per board path, skips the upload when the app has these bytes analysed, and returns the still-pending ingest when the same bytes were just sent from here. That last check has to be client side: the app only records a board's hash once its ingest has read it. Checked live against emi-local (scratch data, no worker): upload, the pending-ingest reuse and a forced re-run. The analysed-board lookup is covered by the fake-app tests only; the local worker could not ingest (Docker disk full). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`emi-local mcp` serves the EMI Analyzer over MCP on stdio, so Claude Code or Codex can analyse a board from disk. It is a client of the running app (found or started through localapp), so its boards show up in the app's window and nothing leaves the computer. First tools, in server/mcpserver: - status: the app, its data folder and the Docker worker, with what to do when Docker is missing or stopped - analyze_board: pack and upload a board (emiclient.Open), optionally waiting for the analysis - run_status: state, stage and progress, the summary once done; wait_s (max 600) waits and sends progress notifications when asked - list_projects, list_versions: versions numbered oldest first as on the compare page, each with its newest finished analysis The instructions tell the agent the results compare versions of one board and are not a pre-compliance test. Adds github.com/modelcontextprotocol/go-sdk v1.8.0. No existing dependency moves; embeddedci-server builds and vendors against this branch unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven tools that read an analysis, shaped for an agent rather than passed through: a whole rules.json is hundreds of kilobytes. - findings: counts by severity and check, then one page worst first (no long explanations unless asked); filters for severity, check, net, layer and a point on the board - finding: one in full, what its check looks for, and the suppress entry to accept it on purpose - decoupling: one line per rail, or per IC its capacitors, gaps and only the worker's recommendations that help - nets: lengths, delay and skew, filtered by name, group or tolerance - compare: two analysed versions (default the newest two) through server/compare, grouped by check, plus the nets that changed - checks: the rule catalogue and the emi.rules.yaml format - open_in_app: the project or compare page, optionally in the browser An analysis is named by run_id or by project (newest finished version, saying when a newer one is still running). Artifacts of finished runs are cached in memory. The rule catalogue is embedded from a copy the worker's exporter now also writes; a test fails if it drifts. Checked live on the BenchPod digital board (156 findings, 11 rails) and on two of its revisions for compare. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
run_esd and run_cables start the two runs that go beyond the ingest on an analysed version (CreateRun in emiclient), with the same wait and progress as analyze_board. esd and cables read the newest finished one: - esd: per connector line the clamp and its model quality, the peak as laid out (at the IC pin when there is one) and with the clamp at the connector, worst first by how far a pin goes over its supply; with the worker's assumptions and what the numbers are not. - cables: each declared cable's tightest common-mode budget, and the connectors with no cable declared with the worker's suggestion; with no project it lists the cable types run_cables takes. The cable types are the worker's library, embedded from a copy a test keeps equal. Checked live on the BenchPod digital board: 33 lines simulated in 20 s, 10 connectors recognised with suggestions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Running the MCP server on the BenchPod boards opened the critical list with I2C, reset and LED nets as radiators, a current monitor's sense pin and a power mux's status line as undecoupled supplies, a USB charging strap as a crossed plane gap, and a SPI chip select length- matched to an RMII clock. Every one is decided by evidence already in the board file: - net_activity (rules/model.py): fast, slow or unknown by name (FAST_NET moves here from stubs.py and also reads unnamed nets). Radiator, return-via and plane-gap make slow lines advisory and cap unknown long nets at warning; the radiator list fills with fast nets first. - On a board with planes a long fast net stays critical only when the same net has a critical plane-gap or return-via finding. - Pads keep KiCad's pinfunction and pintype; is_supply_pin leaves a pin out of decoupling only on positive evidence (signal pin type, or a GPIO, sense, enable, op-amp or mux pin name). - Nets that only strap pins of one part are not signals; chassis and shield names are ground. - Length groups: address/command needs DQS or a byte lane; CAN, RS-485, RS-422 and LIN pairs get no skew budget. - dedupe keeps the most severe findings of a rule under its cap. BenchPod digital: 27 -> 4 criticals; analog: 20 -> 1 (a real one: the THS4551's VS- pin has no capacitor within 25 mm). 1479 worker tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two BenchPod boards, benchpod/vbench-pod.kicad_pcb and benchpod-digital/vbench-pod.kicad_pcb, both became "vbench-pod" in list_projects. A project folder named after its board keeps the plain name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments, docs, multi-word strings and JSX text across the server, the worker, the webapp and the KiCad plugin: analyzed, modeled, center, meter and millimeter, recognized, organization, normalized, labeled, canceled, behavior, color, toward, and the rest (about 720 changes). Changed with each language's own tokenizer (go/scanner, Python's tokenize, the TypeScript parser), so identifiers, keys and one-word strings were never touched: the stored artifact keys "modelled" (cable summary) and "centre" (compliance) stay, since saved results and the webapp read them. The uncertainty label "cable idealisation" became "cable idealization"; compliance_fixtures.json is regenerated, and the component and driver document fixtures match the new message. The rule catalogue copies are re-exported. Go, 1479 worker tests, 423 webapp tests and tsc pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
make e2e-mcp builds emi-local, starts it headless in a temp folder with a worker container, and drives `emi-local mcp` over stdio the way a plugin does: status, analyze_board (waiting for the run), findings, finding, decoupling, an unchanged board not sent again, then an emi.rules.yaml beside the board turning the busiest check off, and compare saying those findings are fixed and nothing is new. Opt-in (build tag e2e: it needs Docker and the worker image); about 6 s. EMI_ANALYZER_MCP_STATE moves the server's board-to-project memory out of the user's config folder, which the test (and a second, separate setup) needs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README: a "Using it from Claude Code" section (the plugin, running `emi-local mcp` from any MCP client, its two environment variables, and the tools). CHANGELOG: the MCP server under Unreleased. CONTRIBUTING: make e2e-mcp. The em dashes in the README and CONTRIBUTING became colons, commas or sentences. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Finding titles and details, notes and error messages read with a colon, a comma, parentheses or a second sentence instead. The rule catalogue and component fixtures are regenerated by their scripts; the webapp's copy of the component messages matches. Docstrings keep theirs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UI copy and JSX text. The six left are chart legend line samples, drawn next to the - - - and – – samples beside them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Left: table cells holding only a placeholder dash, and code blocks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
emi-local mcp: an MCP server in the app, so Claude Code (or any MCP client) can analyze a board from disk, read what the checks found, run ESD and cable budgets, and compare versions, against the app on this computer. 16 tools; see "Using it from Claude Code" in the README.server/compare: the version diff in Go, sharing its cases withcompare.ts(testdata/compare_fixtures.json, checked by Go and vitest). It cannot live in the server:emi.Blobhas no Get.server/localapp: finds or starts the app, the Go twin of the KiCad plugin'sdesktop.py.server/emiclient: packs a board from disk (.kicad_pcb+.kicad_pro+emi.rules.yaml, Gerber folders, zips) and does the two-step upload; one project per board file, no duplicate queueing.server/mcpserver: the tools, shaped and paginated for an agent.Analyzer: findings weigh what a net carries and what a pin is (
0c5635a). Running the MCP server on the BenchPod boards opened the critical list with I2C, reset and LED nets as radiators, a current monitor's sense pin as an undecoupled supply, and a SPI chip select length-matched to an RMII clock. Net names now set fast/slow/unknown, pads keep KiCad's pin types and names, and a long net is critical only where its return path is also broken. BenchPod digital: 27 to 4 criticals; analog: 20 to 1, every remaining one real. Details in the CHANGELOG.American English in comments, docs and every string people read (
be05f83), changed with each language's tokenizer so identifiers and stored artifact keys ("modelled","centre") are untouched.Testing
make e2e-mcp(new, opt-in, needs Docker): emi-local with a worker container in a temp folder,emi-local mcpover stdio: analyze, findings, an unchanged board not resent, a rules file turning a check off, compare. Passes in about 6 s.After merge
emi-analyzer-plugin.🤖 Generated with Claude Code