dsh-eda-mcp
DeepSeek Harness (DSH) plugin that connects DSH to 嘉立创EDA专业版 / EasyEDA Pro and lets the agent draw schematics through MCP tools.
中文文档:README.zh.md · Tool reference: docs/tools.md · Changes: CHANGELOG.md
How it works
DSH agent
└─ mcp__jlceda__eda_* tools
└─ @deepseek-ai/dsh-mcp-client (stdio)
└─ lib/bridge.mjs (local MCP + WebSocket server, 127.0.0.1:39009)
└─ JLCEDA Pro extension dsh-eda-bridge-extension.zip
└─ official EasyEDA Pro extension API (eda.sch_*)
The DSH plugin starts a zero-dependency bridge child process. The bridge speaks MCP over stdio to DSH and WebSocket to a small JLCEDA Pro extension. The extension executes a strict allow-list of official EDA APIs, so the model cannot run arbitrary code inside JLCEDA.
Install
Runtime requirement: DSH
0.1.1-rc.x,0.1.5-rc.xor0.2.0-rc.x(Web GUI and the official Desktop). The host settings seam and the browser bundle are adapted to every generation, so one build loads on any of them.
1. Install into the DSH web profile
# directly from GitHub (pnpm runs the package's `prepare` build)
dsh plugin --profile web add "github:MCviseron/dsh-eda-mcp"
# or from a local clone, so every `pnpm build` is picked up by a profile restart
git clone https://github.com/MCviseron/dsh-eda-mcp.git
cd dsh-eda-mcp && pnpm install && dsh plugin --profile web add "link:$PWD"
dsh plugin forwards to pnpm and reconciles dsh.profile.bundles automatically. Restart the running DSH Web profile after installation.
1b. Running inside the official Desktop app
The Desktop app hosts plugins in Electron, so there process.execPath is DeepSeek Harness.exe, not Node: spawning it with lib/bridge.mjs starts (or single-instance-aborts) a second app instead of a bridge, and port 39009 never listens — the settings card exists, /test always fails and the agent gets no mcp__jlceda__* tools.
The plugin now resolves a real Node executable (src/node-runtime.ts), in order: DSH_EDA_MCP_NODE override → DSH_NODE_EXECUTABLE / DSH_DESKTOP_NODE_EXECUTABLE (with ELECTRON_RUN_AS_NODE=1) → process.execPath on a Node host → the Desktop payload's own resources/runtime/primary-runtime/dependencies/node/bin/node.exe → node on PATH → Electron-as-Node.
POST /api/dsh-eda-mcp/test reports which one it picked:
{"ok":true,"health":{"status":"ok","version":"0.3.5","clients":1},
"launch":{"command":"…\\runtime\\primary-runtime\\dependencies\\node\\bin\\node.exe","source":"desktop runtime Node"}}
The Desktop loads the packaged
lib/index.js, and a plugin reload does not re-import a cached ESM module — after changingsrc/, runnode build.mjsand restart the Desktop app.
2. Import the JLCEDA Pro bridge extension
Build produces dsh-eda-bridge-extension.zip. In JLCEDA Pro:
- Open 扩展 → 导入 (Extensions → Import).
- Select
dsh-eda-bridge-extension.zip. - In the extension list find DSH EDA MCP Bridge.
- Enable it and, importantly, enable 允许外部交互 / Allow external interactions.
The extension connects to ws://127.0.0.1:39009/ws. Keep JLCEDA Pro running and a schematic page open.
3. Verify
In the DSH Web GUI settings page, the 嘉立创EDA MCP card has a 测试连接 button. It checks the local bridge health endpoint. The MCP tools appear as mcp__jlceda__eda_status, mcp__jlceda__eda_place_component, mcp__jlceda__eda_draw_wire, etc.
MCP tools (schematic-first MVP)
| Tool | Purpose |
|---|---|
eda_status |
Bridge status and connected JLCEDA clients |
eda_search_components |
Search JLCEDA library devices |
eda_place_component |
Place a component/symbol |
eda_place_net_flag |
Place VCC/GND/Power net flag |
eda_place_net_port |
Place IN/OUT/BI net port |
eda_draw_wire |
Draw a wire/polyline |
eda_draw_rectangle |
Draw a rectangle |
eda_draw_circle |
Draw a circle |
eda_draw_text |
Draw text |
eda_save_document |
Save the active schematic |
eda_zoom_to_fit / eda_zoom_to_region |
Zoom canvas |
eda_get_components / eda_get_wires |
Inspect primitives |
eda_delete_primitive |
Delete one primitive |
eda_api_call |
Allow-listed generic API call (can be disabled in settings) |
pcb_* / eda_pcb_* (65 tools total) |
PCB query, place/move/delete components, tracks, vias, regions, board outline, auto-place/auto-route, Gerber export |
Schematic/symbol coordinates are in 0.01 inch units; PCB coordinates are in mil. Rotation values are 0/90/180/270. Wire points are a flat array [x1, y1, x2, y2, ...].
Every PCB tool has an equivalent eda_pcb_* alias (eda_pcb_get_components = pcb_get_components). See README.zh.md for the full PCB table and the region-layer rules (pcb_PrimitiveRegion.create accepts copper layers and MULTI only, so the board outline stays a closed line loop on layer 11).
Tool reference
docs/tools.md is generated from the built bridge's MCP tools/list (81 tools) and is the authoritative list of names, parameters and required fields.
Workflows
A. Schematic (make connections real)
eda_get_page_infofor the A4 frame, title-block keep-out and safe areas; theneda_search_components+eda_place_component(out-of-frame placement is rejected and rolled back).- Connect with
eda_draw_wirenet parameter (same-name nets merge) - the most reliable electrical connection.eda_place_net_flag_at_pinplaces power/ground flags.eda_place_net_label_at_pinprefers a REAL net label and reportsmethod: "netLabel"; when it reportsmethod: "text"the EDA build has nocreateNetLabeland the text label does not create an electrical connection - fall back to the wire net parameter. eda_set_no_connectfor unused pins,eda_draw_functional_boxto group, theneda_run_drc+eda_save_document.
B. Schematic -> PCB sync (EDA shows its own confirmation dialog)
eda_create_boardlinks schematic and PCB (first time only).pcb_import_changesis asynchronous by default:pcb_Document.importChangesopens EDA's confirmation dialog and blocks until it is answered, so the tool returns immediately withcomponentsBeforeand keeps the import running in the background.- Ask the user to click OK, then call
eda_pcb_wait_for_components: it polls every 5 s by default (minimum 3 s, deliberately low) for up to 45 s (pollIntervalMs/maxWaitMs). Confirmation is detected as "new PCB components appeared that did not exist before". - On
confirmed: truerunpcb_save_document; onconfirmed: falseask the user and call again.
C. PCB layout and routing (long jobs are always polled)
- Outline:
pcb_draw_board_outline(one closedpcb_PrimitivePolylineon layer 11). - Placement:
eda_pcb_get_components(includePins: truefor accurate pad nets) +eda_pcb_move_component;eda_pcb_auto_place(blocking by default,wait: falsefor a background job). - Routing:
eda_pcb_auto_routestarts asynchronously (EDA shows its own progress bar); polleda_pcb_job_statusuntilrunning=false. Never await it and never raise timeouts - a whole board or a 35+ pin net such as GND always exceeds any sane timeout while EDA keeps working. Useeda_pcb_clear_routingto start over. - Check:
eda_pcb_run_drc(strictincludes warnings,includeVerboseErrorreturns details) +eda_pcb_get_drc_rules. - Finish:
eda_pcb_set_net_track_widthfor power/GND,eda_pcb_create_pourfor copper pours (45grid/90grid/solid, copper layers only),eda_pcb_export_gerber(base64 data URL, can be several MB).
D. Active-document rule
sch_Net.*, netlist export and the schematic get/getAll APIs only act on the active tab: with a PCB in front they return [] / null. eda_get_nets therefore degrades through getCurrentProjectAllNets -> getAllNets -> parsing sch_Netlist.getNetlist and reports source; when it is empty use eda_get_active_document and eda_open_document to bring the schematic page forward.
Configuration
The Web GUI settings card exposes:
enabled— mount/unmount the MCP bridge.announceToAgent— inject plugin guidance into the system prompt.toolCallTimeoutMs— per-call EDA timeout.allowRawApi— expose the genericeda_api_calltool.
The WebSocket port is fixed at 39009 in the first version. Do not run another service on that port.
Development
Requirements: Node ^22.19.0 || >=24, pnpm (pinned by packageManager), and Windows for the extension zip (PowerShell Compress-Archive).
pnpm install
pnpm typecheck # tsc --noEmit
pnpm test # unit tests
pnpm build # bundles + extension zip
pnpm check # all three
Build outputs:
lib/index.js— DSH host plugin.lib/client.js— DSH Web GUI settings card.lib/bridge.mjs— standalone MCP/WebSocket bridge child.dsh-eda-bridge-extension.zip— JLCEDA Pro extension.
lib/ and the zip are build outputs and are git-ignored; the host loads the packaged lib/index.js, so a change under src/ needs pnpm build plus a Host restart (a plugin reload does not re-import a cached ESM module).
See CONTRIBUTING.md for the repository layout, the release/tag flow, and the bridge/extension version rule.
Security
- The bridge listens on loopback only (
127.0.0.1). - The JLCEDA extension and the bridge both enforce the same API allow-list.
- Generic
eda_api_callis allow-list-only and can be disabled. - No raw JavaScript evaluation is exposed.
Known limitations
- The board outline is one
pcb_PrimitivePolylineon layer 11 (polygon source + line width). It cannot be created withpcb_PrimitiveLine(a copper-track primitive; the core rejects layer 11) nor withpcb_PrimitiveRegion(TPCB_LayersOfRegionallows copper layers and MULTI(12) only). Verified against real.eprj2project files, where an outline is stored as["POLY", id, 0, "", 11, 10, [...], 1]. pcb_Document.autoRoutingreturnssuccess=false, duration=0for every net when no net list is passed (emptyroutingRangein this EDA build), soeda_pcb_auto_routeenumerates all net names first.- Auto-routing is asynchronous by default:
eda_pcb_auto_routereturns as soon as EDA starts routing (EDA shows its own progress bar) andeda_pcb_job_statusis polled untilrunning=false. Awaiting a whole-board or many-pin net (e.g. GND) always exceeds any sane timeout while EDA keeps working. - Schematic APIs (
sch_Net.*, netlist export) only act on the ACTIVE document; when they return empty/null, useeda_get_active_documentandeda_open_documentto bring the schematic page to the front. eda_pcb_export_gerberreturns the Gerber zip as a base64 data URL, which can be several MB for a large board.- The JLCEDA extension socket is registered through
sys_WebSocket, which has neither a close callback nor auto-reconnect; versions before 0.3.3 therefore had to be reconnected by hand after the DSH bridge child restarted. 0.3.3 adds a 10 s heartbeat that re-registers the socket whenpongstops arriving. - Screenshot/canvas image return is not yet wired into MCP image blocks.
- Changing the bridge WebSocket port requires editing both the bridge env and
jlc-extension/api-bridge.js, then rebuilding/reimporting the extension zip.
Repository
| Path | What it is |
|---|---|
src/index.ts, src/routes.ts, src/node-runtime.ts, src/config-values.ts, src/bridge-diagnostics.ts, src/guidance.ts |
DSH host half |
src/bridge/index.ts |
Standalone MCP/WebSocket bridge child |
src/client/ |
Web settings card |
jlc-extension/ |
JLCEDA Pro extension packaged into the zip |
test/ |
Unit tests |
docs/tools.md |
Generated tool reference (every MCP tool, its parameters, and required fields) |
build.mjs |
esbuild bundles + extension zip |
License
Apache-2.0 © dsh-eda-mcp contributors. See LICENSE.
还没有评论,来写第一条。