mcpField
P
Perfboard Designer

Perfboard Designer

Remote
perfboarddesigner.comScience & Education18Updated 1d ago

About Perfboard Designer

Perfboard Designer is an MCP server for AI agents designing perfboard and stripboard circuits. It combines a parts catalog, layout editing, wiring checks, board rendering, and links that open projects in the editor.

Key features

  1. 01Look up parts and pin details
  2. 02Create and edit board layouts
  3. 03Render boards and connection nets
  4. 04Check wiring and circuit errors
  5. 05Generate editor share links

Use cases

  1. 01Lay out a 555 LED blinker
  2. 02Check component pins and wire connections
  3. 03Review and edit a board in the editor
  4. 04Load a shared board and continue editing

MCP support

The MCP server exposes tools for catalog lookup, project editing, rendering, validation, and share links. It also provides resources for the project format, parts catalog, guides, and examples.

Configuration

Remote servers need no local install. Pick your client, then copy the config or install in one click.

Endpointhttps://mcp.perfboarddesigner.com/mcp
Copy install command
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

Run commands in a terminal; paste JSON into the client’s MCP config and restart. Servers that need sign-in will prompt on first connect.

Packages
npmperfboard-designer-mcp0.1.0

Tools

Details

From the project README

Perfboard Designer MCP server

An MCP server that lets an AI agent (Claude, or any MCP client) design perfboard and stripboard layouts for Perfboard Designer. The agent looks up parts, writes the board as a project file, checks it, and gives you a share link. Clicking the link opens the board in the editor, where you can look at it and change it.

"Lay out a 555 LED blinker on a small perfboard and send me the link."

It runs on your computer (stdio), needs no account and saves nothing to disk; or it can be hosted on Cloudflare Workers (below). While it runs it keeps the projects an agent is working on in memory, each under a projectId, so the agent sends a project at most once and then changes it with small edits. The project file stays the source of truth: get_project returns it, and every tool also takes a whole file instead of an id.

Setup

Node.js 22 or newer. It's on npm as perfboard-designer-mcp, which npx downloads the first time.

Claude Code:

claude mcp add --scope user perfboard -- npx -y perfboard-designer-mcp

Claude Desktop (claude_desktop_config.json, Settings → Developer → Edit Config) and other clients that take a command:

{
  "mcpServers": {
    "perfboard": {
      "command": "npx",
      "args": ["-y", "perfboard-designer-mcp"]
    }
  }
}

To run it from this repo instead, build it and use node /path/to/perfboard-designer/mcp/dist/server.js as the command:

cd mcp
npm install
npm run build

Links open the public editor, https://perfboarddesigner.com/app/. To point them at another copy (a local dev server, say), set PERFBOARD_EDITOR_URL in the server's environment:

{
  "mcpServers": {
    "perfboard": {
      "command": "npx",
      "args": ["-y", "perfboard-designer-mcp"],
      "env": {"PERFBOARD_EDITOR_URL": "http://localhost:3000/app/"}
    }
  }
}

The server works on its own, without the hosted Worker. It only points at it for two extras, each set in the server's environment like PERFBOARD_EDITOR_URL, public unless set, and off to do without (agents are then not offered it):

SettingExtraDefault
PERFBOARD_IMAGE_URLmake_share_link's image, a picture of the board (render_board's PNGs are drawn here and need nothing)https://mcp.perfboarddesigner.com/render.svg
PERFBOARD_PROJECTS_URLmake_share_link's store: a short link to a stored project. Nothing is sent unless an agent askshttps://projects.perfboarddesigner.com

Point them at a local Worker with http://localhost:8787/render.svg and http://localhost:8787.

Tools

ToolWhat it does
list_partsLists the parts a project can place (79 today). Without a query, every part by category, one line each (its id, name and pin count). With words such as "capacitor" or "555", the parts matching all of them, or the closest ones when none do.
get_partParts in full, as many as you name in ids: which list each goes in, its pins and pin labels, polarity, value unit, holes its body covers, and an example entry to copy.
new_projectStarts a project kept in memory and returns its projectId: a blank perfboard or stripboard of the size you give, or a copy of an example (example: "555-led-blinker").
edit_projectChanges a kept project with a list of ops, all or none: add_part (by part id, with at, or from/to for two-lead parts; refs are numbered if left out; or a part of the agent's own: custom, pins anywhere or a catalog part's package via shape with its own pin names, or chip, a dual-row chip or dev board, placed again later by name), update_part (move, turn, change value or ref), remove_parts, add_wire (from/to, with via for the holes it bends at, or a path of holes made into one wire per leg; any end can be a pin by name, e.g. "U1.7" or "D1.K", for the hole it's in), update_wire (ends by hole or pin too, via to replace its bends), remove_wires, set_board, add_cuts/remove_cuts, set_pad (a note on a hole). Returns what changed, where the new or moved parts' pins landed, and the board checked as validate_project does; when it has no errors, also its nets.
get_projectThe whole project file of a kept project.
get_pin_positionsWhere every placed part's pins land on the board after rotation, e.g. U1 pin 8 (VCC) at [9, 3], so wires can be routed to the right holes.
render_boardDraws the board as text, one cell per hole with column and row numbers: pins by part key and number (a1), holes under a body, wire ends, holes a wire runs over without joining them, strip cuts and holes with notes, then which part each key is and every wire with its net. area draws only part of the board (needed above 6000 holes). format: "png" draws a picture instead, as the editor draws it (holes 50 px apart, scaled down to fit 1000 px; area crops it), for an agent to look at.
get_netsWhich pins the board connects: each net with its name (e.g. GND), its pins (U1.8 VCC for a chip pin, D1.K or C1.+ for a polarized lead, R1.1 otherwise) and its wires, plus pins connected to nothing, junctions (holes where three or more things join) and crossings (where a wire goes over another wire or a hole without joining it).
validate_projectLists every problem, with where it is: unreadable entries, unknown parts, pins off the board, two parts' pins or leads in one hole, overlapping parts, wires that connect nothing (a loose end, or no pins on their net), a wire passing over a pin without ending there, IC pins and two-lead parts' leads connected to nothing, supply rails shorted together (VCC with 5V is one rail, VCC with GND a short), LEDs, electrolytic capacitors and + and − pins in backwards or a diode conducting straight across the supply (judged by the rails: pins named like VCC, GND or 5V, battery holders' terminals and holes noted +9V), and on a stripboard, pins shorted by a strip or in a cut hole. Wires crossing are listed as notes, not problems. Pass expectNets (the netlist you meant, e.g. [["U1.8", "U1.4", "R1.1", "J1.1"], ["U1.2", "U1.6", "C1.+"]]) and every missing or extra connection is an error; for a kept project it's remembered, so edit_project and make_share_link check against it too ([] forgets it). A whole project sent here is kept, and the reply gives its projectId.
make_share_linkValidates the project (with expectNets, given or remembered) and returns the link that opens it, and image: the address of an SVG picture of the board, drawn as the editor draws it (served by the hosted Worker, below). It refuses a project with errors unless allowErrors is set. editorUrl points it at another copy of the editor, e.g. http://localhost:3000/app/; it defaults to PERFBOARD_EDITOR_URL. With store: true (or storeAs: "led-blinker", the id it'd like), it also stores the project (below) and returns stored: its id, a short link and its file and pictures. The self-contained link is made either way, and if storing fails, storeError says why.
show_boardShows the user a picture of the board right in the chat, as the editor draws it, with a button that opens it in the editor: an MCP App, for clients that show them (e.g. Claude, ChatGPT), which don't load Markdown images from links. The picture travels in the result's _meta, to the page and not to the agent; the page is the ui://perfboard/board-view.html resource. Clients without MCP Apps show nothing.
read_share_linkReads the project out of a link you share with the agent (z=, json= or src=, a stored project's short link too) and keeps it under a projectId, so the agent can change an existing board.

Every tool that reads a project takes either projectId or the whole file as project. Kept projects last as long as the server process (the 200 most recently used); on Cloudflare, 30 days after they were last used.

Its instructions to agents are kept short (clients cut them off at about 2 KB); the full guide is the resource perfboard://docs/guide (src/guide.ts).

It also serves the format as resources: perfboard://docs/project-file (the project file format, field by field), perfboard://docs/share-links (the link format), perfboard://parts (the whole parts catalog as JSON), and example projects: perfboard://examples/555-led-blinker (a 555 LED blinker on a perfboard, all wired with jumpers) and perfboard://examples/555-led-blinker-stripboard (the same circuit on a stripboard), read from ../examples/.

Hosting it on Cloudflare Workers

The public copy runs at https://mcp.perfboarddesigner.com/mcp; how to add it to Claude Code, claude.ai, ChatGPT, Cursor and other clients is on https://perfboarddesigner.com/mcp-server/ (../mcp-server/index.html).

The same server also runs as a Cloudflare Worker, so an MCP client can reach it over the web with nothing installed: Streamable HTTP at /mcp (src/worker.ts). It's stateless, every request getting a fresh server, so kept projects can't live in memory: each is kept in its own Durable Object (ProjectHolder), named by its projectId, and forgotten after 30 days unused. Project ids are random, so they can't be guessed. There's no sign-in: anyone with the address can use it.

It also draws boards: /render.svg? followed by a share link's part after the # (name=…&z=… or json=…; src= links aren't fetched) answers with an SVG picture of that board, drawn by the editor's own board renderer (../src/board-render/) with the part icons baked in. /render.png? with the same parameters answers with a PNG of it instead, for places that don't show SVG, such as chat apps and link previews. An address says everything in its picture, so it's cached for good (src/image-requests.ts).

The PNGs, from /render.png and render_board, are drawn from the same SVG by resvg, compiled to WebAssembly (@resvg/resvg-wasm, MPL-2.0), so they come out the same from the Worker and from stdio (src/board-png.ts). Drawing one takes around a tenth of a second of CPU for a small board, far more than the Workers Free plan's 10 ms a request, so the hosted server needs a paid plan to draw them.

So pictures aren't drawn again for every request, each Cloudflare data center keeps a copy of every picture and stored project file it serves (the Workers Cache API), for as long as its Cache-Control says: a year for /render.*, a day for stored projects. Repeat requests there are answered from it (CF-Cache-Status: HIT) without drawing again or waking the stored project's Durable Object (edgeCached() in src/worker.ts). Only answers that worked are kept, so an unknown id is looked up again next time. After a change to how boards are drawn, old pictures stay until they expire or the zone's cache is purged in the Cloudflare dashboard. The cache does nothing on the workers.dev address, only on the custom domains.

Stored projects

A share link carries the whole project, so it works without any server, but it gets long. The Worker can also store a project under a short id and serve it, at https://projects.perfboarddesigner.com (the same Worker, at its own address so the storage could move to a Worker of its own without breaking links). It's the project storage behind short links, for agents (make_share_link with store) and for people (the editor's Share dialog), documented for both in docs/share-links.md:

RequestAnswer
POST /projects with {"project": {…}, "id": "led-blinker", "name": "LED blinker"} (id and name optional){id, link, file, image, png}: the id it got, a short editor link, the file and its pictures
GET /projects?q=blinker&limit=20Stored projects matching the id or name, newest first, with links and a pagination cursor
GET /<id>.jsonthe project file
GET /<id>.png, GET /<id>.svgpictures of it
  • Ids are lowercase letters, digits and dashes, made from the wanted id or else the name ("Mágneses kapcsoló!" → magneses-kapcsolo), at most 40 characters, ignoring case. If another board has that id, a few characters of the board's SHA-256 are added (led-blinker-08196a); storing the same board again under the same id gives the same id back. With neither, the id is the hash. Ids shorter than 3 characters or naming the server's own addresses (mcp, render, projects…) always get the hash added.
  • A stored project never changes: storing again makes a new id. It's deleted after a year unopened (every request for its file or pictures pushes that back).
  • Only valid project files are stored, as the editor reads them, up to 1 MB.
  • The short link is a p= share link (…/app/#name=LED%20blinker&p=led-blinker), which the editor opens from this storage without asking. Stored anywhere else (PERFBOARD_PROJECTS_URL), it's a src= link to the file instead, which any editor opens. Anyone with a link can read the board.
  • Each stored project is a Durable Object (StoredProjectHolder) named by its id, so two boards stored at once can't both get an id (src/stored-projects.ts, src/project-requests.ts). The stdio server stores through POST /projects.

There's no sign-in or rate limit yet: anyone can store projects.

Querying stored projects

The listing is public and requires no token. Deploy from mcp/:

npm run worker:deploy

Then query it:

curl 'https://projects.perfboarddesigner.com/projects?q=blinker&limit=20'

Omit q to list all indexed projects. Search matches a literal substring of the id or name (ASCII case-insensitive). limit defaults to 20 and accepts 1–100. The response is {projects: [{id, name?, storedAt, link, file, image, png}], nextCursor}; storedAt is the original save time in Unix milliseconds. Pass nextCursor as the URL-encoded cursor query parameter with the same q to get the next page; null means there are no more results. Listings are not cached and do not extend projects' lifetimes.

The StoredProjectIndex Durable Object tracks new saves and removes expired projects. Existing projects from before this index was deployed appear when saved again or read from their storage object (an edge-cache hit does not reach storage). This is not a complete historical inventory: there was no central index before. Temporary MCP working projects are not included.

For local use, run npm run worker:dev and query http://localhost:8787/projects.

Try it locally (at http://localhost:8787/mcp):

npm run worker:dev

Publish it to your Cloudflare account (npx wrangler login once first):

npm run worker:deploy

Both build first; worker:dev serves stored projects at http://localhost:8787 too. wrangler.toml is the Worker's config: its name, its domains, the Durable Object bindings and, in [vars], PERFBOARD_EDITOR_URL for where links open and PERFBOARD_PROJECTS_URL for where stored projects are served. Then add it to a client by its address:

claude mcp add --transport http perfboard https://perfboard-designer-mcp.<your-subdomain>.workers.dev/mcp

On the Workers Free plan a request gets 10 ms of CPU time, which checking a big board can run over; the Paid plan allows far more.

Publishing a release

The package is on npm as perfboard-designer-mcp, and the server is listed in the MCP Registry as com.perfboarddesigner/mcp, described by server.json: the hosted address (remotes) and the npm package (packages). The registry checks that package.json's mcpName is that name, and that you own perfboarddesigner.com, by a DNS TXT record on the apex domain holding a public key.

  1. Bump the version: npm version patch (or minor), and set the same version in server.json, both at the top and in its npm package entry.
  2. npm publish (it builds first; npm asks for 2FA). Wait until npm view perfboard-designer-mcp version shows the new version.
  3. Deploy the Worker if the server changed: npm run worker:deploy.
  4. Publish to the registry with mcp-publisher: mcp-publisher validate, then mcp-publisher login dns --domain perfboarddesigner.com --private-key "$PRIVATE_KEY" and mcp-publisher publish, run in mcp/.

The key pair is made once with OpenSSL 3, its public half added as a TXT record on perfboarddesigner.com (Cloudflare DNS); keep key.pem outside the repo:

openssl genpkey -algorithm Ed25519 -out ~/.config/mcp-registry/key.pem
echo "v=MCPv1; k=ed25519; p=$(openssl pkey -in ~/.config/mcp-registry/key.pem -pubout -outform DER | tail -c 32 | base64)"
PRIVATE_KEY="$(openssl pkey -in ~/.config/mcp-registry/key.pem -noout -text | grep -A3 'priv:' | tail -n +2 | tr -d ' :\n')"

How things connect

Wires are insulated jumpers: a wire joins only the holes at its two ends. Running over a hole doesn't connect it, and two wires crossing aren't shorted, so a crossing is fine (the editor marks it so it doesn't pass for a joint, and the 3D view arches the upper wire). A hole takes one part's pin or lead at most (a second part there is an error, as on a real board) plus any number of wire ends, and everything in a hole is joined: to connect two parts, run a wire between their pins. A jumper can lie on the solder side (side: "bottom", format version 9): it joins its end holes the same way but never crosses a top wire. On a stripboard, every hole along a strip is joined up to the next cut, and a cut hole connects to nothing. get_nets and validate_project work this out with the same code as the editor's Nets panel, so they agree with what you see there.

How it's built

src/ is the server: create-server.ts puts it together, server.ts runs it over stdio and worker.ts on Cloudflare, tools/ holds the tools by group, resources.ts the documents it serves, project-handles.ts the kept projects (and where they're stored), edit-ops.ts and edit-project.ts edit_project's ops, validate.ts the checks, guide.ts its instructions and guide, board-view/ the page show_board shows the board in (an MCP App; view.ts runs in the client's frame and speaks the few MCP Apps messages it needs itself, so the page stays a few KB). It builds on the editor's own pure code, all of it taken through editor.ts, the one file that imports from ../src/: the project file reader, share-link code, pin positions, nets (nets.ts) and wire crossings (wire-crossings.ts) in ../src/project-file/, the part definitions and part geometry in ../src/parts/, and the board renderer (../src/board-render/) for pictures of a board. That way it always agrees with the editor about the format, the parts and what's connected. The editor never imports anything from here. npm run build bundles that code and the MCP SDK into a single dist/server.js (and dist/worker.js for Cloudflare) with no runtime dependencies, and bakes in the docs and examples it serves, the part icons its pictures embed and show_board's page (board-view/view.html with view.ts bundled for the browser into it). PNG pictures also need resvg's WebAssembly, which the build copies beside them as dist/resvg.wasm (uploaded with the Worker), and fonts, since resvg can't use the system's: Noto Sans and Noto Sans Mono, cut down to the characters boards use, in fonts/ (SIL Open Font License; fonts/README.md says how to remake them).

CommandWhat it does
npm run buildBundle dist/server.js and dist/worker.js
npm run typecheckType-check the server and the editor code it imports
npm run worker:devRun the Cloudflare Worker locally, at http://localhost:8787/mcp
npm run worker:deployPublish the Worker to Cloudflare
npm version patch, then npm publishRelease a new version to npm (prepublishOnly builds first; npm asks for 2FA)
npm run inspectOpen the server in the MCP Inspector to try the tools by hand
Requirements and component identities

The shared reader supports format 8, including optional independent requirements; format 7 adds project-scoped component instanceId fields. edit_project preserves all intended nets, gives new parts fresh identities, and includes them in the requirement inventory without assigning connections. Removed required parts remain reportable by identity, even if another part reuses their reference name. A schematic field from older editors is ignored.

When requirements.validate is true, checks also compare the board with the saved requirements: missing components/pins, missing connections and unexpected joins are errors; incomplete coverage is a warning and never described as a full requirement match. Unspecified pins remain unchecked. This is independent of the existing complete-list expectNets contract, which is unchanged. The project-format resource includes the requirement types.

Deploy this shared reader with the frontend before sharing version-8 files through hosted storage. Embedded links remain usable without hosted storage.

Comments

Markdown supported

New to MCP?

From what the protocol is to installing your first server, these answers cover most first questions.

An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.