Math board
A local, view-only chalkboard the model draws on: equations, plots, molecules and diagrams.
Install
Add this to your client's MCP configuration (Claude Desktop: claude_desktop_config.json; Cursor: .cursor/mcp.json; VS Code and others use the same "mcpServers" shape).
pi-math-boardclaude mcp add math-board -e 'MATH_BOARD_NO_BROWSER=<MATH_BOARD_NO_BROWSER>' -e 'MATH_BOARD_DATA_DIR=<MATH_BOARD_DATA_DIR>' -- npx -y pi-math-board@0.3.0
{
"mcpServers": {
"math-board": {
"command": "npx",
"args": [
"-y",
"pi-math-board@0.3.0"
],
"env": {
"MATH_BOARD_NO_BROWSER": "<MATH_BOARD_NO_BROWSER>",
"MATH_BOARD_DATA_DIR": "<MATH_BOARD_DATA_DIR>"
}
}
}
}
Environment variables
MATH_BOARD_NO_BROWSERSet to 1 and board_open only returns the board's URL instead of opening it in the default browser.MATH_BOARD_DATA_DIRFolder for this board's autosave and named saves, used as given (PI_MATH_BOARD_DATA_DIR, the old name, also works). Default: the mcp/ subfolder of the board's data folder. A plain checkout keeps lessons in <package>/.data; an npm or npx install uses a per-user folder: ~/Library/Application Support/math-board on macOS, $XDG_STATE_HOME/math-board or ~/.local/state/math-board on Linux, %LOCALAPPDATA%\math-board on Windows.
Documentation
From the project's README on GitHub, refreshed weekly. Links open on GitHub.
Contents
math-board
A black chalkboard in the browser that a terminal tutor draws on while it explains, like a professor at a blackboard. The conversation stays in the terminal. The board is view-only: the model puts up equations, plots, molecules, diagrams and science illustrations through tools, one step at a time, and I watch, step back through the lesson, pan and zoom. I never draw on it. The model does. (The page has two buttons that change anything: Clear, which wipes the board after asking, and Save.)
I work through physics problem sets and study MRI physics (k-space, spins and signal), neuroscience, chemistry and biology for medicine. I tutor myself with pi, a terminal coding agent, running Eero Alvar's learn setup: question-based, one concept at a time. A terminal tutor has no visual channel, and most of what I learn needs a picture, so I built one. It's local and free (no cloud, no extra API keys). It's a separate extension, so Eero's tools stay as he wrote them. And the same tools run as an MCP server, so Claude Code can teach on the same kind of board. Before and after building it I checked about a hundred existing tools (survey); none was view-only, local, lesson-structured and agent-driven at once.
Install for pi
You need pi and Node 22.19 or later. I use it on macOS; Linux works too. On Windows the board runs, but nothing opens the browser for you, so open the URL from /board yourself.
From npm, once it's published:
pi install npm:pi-math-board
From a checkout, which is how I run it:
git clone https://github.com/smdesai27/math-board ~/math-board
cd ~/math-board && npm ci
pi install ~/math-board
pi loads a local package in place, without copying it, so the checkout is what runs. It doesn't install a local package's dependencies, which is why npm ci comes first. Then run /reload in pi, or restart it.
Two things worth knowing first:
- The layout check after each update,
board_snapshotand the PNGs from/board exportneed a headless Chromium: Playwright's or the system Chrome. For Playwright's, install the build the package expects withnpx -y playwright-core@1.64.1 install chromium(the error message names the exact command). Without one the board still works, and those report why they can't. Chromium runs with its OS sandbox; where the sandbox can't start (some Linux containers) it runs without it, and the results say so. - Where the boards live: a checkout keeps them in
.data/next to the code (git-ignored). An installed copy, from npm or pi's git installs, gets replaced or cleaned on update, so it keeps them in a per-user folder instead:~/Library/Application Support/math-boardon macOS,$XDG_STATE_HOME/math-board(usually~/.local/state/math-board) on Linux,%LOCALAPPDATA%\math-boardon Windows.MATH_BOARD_DATA_DIRoverrides both (PI_MATH_BOARD_DATA_DIR, the old name, still works)./board statusshows which folder is in use.
Use it in pi
Ask the tutor to explain something "on the board". Or:
/board open the board in your browser (also /board open)
/board status server URL, revision, data folder, viewers, autosave problems
/board save <name> named snapshot (several words become one name: my lesson -> my-lesson)
/board load <name> restore it
/board export [name] write the lesson to ./viz/: board.json, PNGs, lesson.md
/board clear clear everything (or /board clear equations|scene)
/board stop stop the server
The server starts on first use and listens only on 127.0.0.1. The URL carries a private #token=…; don't share it. After /reload or /board stop the next board in the same pi session reuses the port and token, so a tab that's still open reconnects by itself. If the server is gone for good, the page says so after about 10 seconds and keeps trying in the background.
Use it from Claude Code and other MCP clients
The same tools run as a stdio MCP server, mcp/server.mjs. Both the pi extension (index.ts) and the MCP server are thin adapters over one implementation, tools.mjs, so the model gets the same tool names, descriptions, schemas and results either way, and the board_teaching rhythm (read, draw one step, ask one check question) is the server's instructions. One learner workflow, whichever agent is teaching.
Register it with Claude Code once:
claude mcp add math-board -- npx -y pi-math-board # from npm, once published
claude mcp add math-board -- node ~/math-board/mcp/server.mjs # from a checkout, after npm ci
That registers it for the folder you run it in (scope local). I run it in my learning folder, so the board and its instructions exist only there, as pi's board_teaching does; add --scope user for every project. claude mcp list should show ✔ Connected, and /mcp inside Claude Code reconnects it after you change the code. In pi, use the extension rather than pi mcp add: with both you'd get two separate boards.
Other MCP clients take the same command as a stdio server:
{
"mcpServers": {
"math-board": {
"command": "npx",
"args": ["-y", "pi-math-board"]
}
}
}
For a checkout, use "command": "node" and "args": ["/absolute/path/to/math-board/mcp/server.mjs"].
- Tools: the seven in Model tools. All seven are listed from the start (MCP has no lazy activation like pi's); the model calls
board_opento show the board.board_snapshotreturns the PNG as an MCP image block, followed by the text summary. - Browser:
board_openopens the board in your default browser unless a browser already shows it. The URL, with its token, goes into the model's context only when no browser could be opened; then the model passes it on to you. WithMATH_BOARD_NO_BROWSER=1it never opens one and always returns the URL:claude mcp add math-board -e MATH_BOARD_NO_BROWSER=1 -- npx -y pi-math-board. - Separate boards: same tools, separate boards. The MCP server hosts its own board server, started by the first tool call that needs it, on
127.0.0.1with its own token, and keeps its saves in themcp/subfolder of the data folder (see Install for pi), apart from pi's, so the two never overwrite each other's autosave.MATH_BOARD_DATA_DIRoverrides it; if you set it for both, give each its own. To continue a lesson in the other agent, copysaves/<name>.jsonacross and load it withboard_save. - Lifecycle and privacy: stdout carries only JSON-RPC (diagnostics go to stderr), nothing listens beyond loopback, and there is no telemetry. When the client disconnects, or on SIGINT, SIGTERM or SIGHUP, the server flushes the autosave, closes the headless browser and exits.
/boardand/board exportare pi commands; the MCP server has no counterpart.
What the tutor can draw
- Math: KaTeX equations (
\color,\boxed,\cancel, …) and chemistry with\ce{…}(mhchem). - Plots: function graphs, parametric curves, shaded areas, points, asymptotes. Expressions go through a small parser, never
eval. - Molecules: 2D structures from SMILES strings (SmilesDrawer).
- Science illustrations: about 2000 offline Bioicons (anatomy, organs, cells, organelles, blood and immunology, microbes, viruses, DNA, plants, animals, lab equipment), searchable by keyword.
- Diagrams: hand-written SVG (markers, gradients, symbols, CSS, SMIL animation) and Mermaid (flowcharts, sequence, class, state, ER, gantt, mind maps, graphs; automatic layout).
- Marks: lines, arrows, curves, rectangles, ellipses and text (a LaTeX-style serif by default).
How lessons are built
- Coordinates: each chapter, and the home board, is a 1200×800 space with the origin top-left. Text
yis the baseline; equationx,yis the top-left of its box; rect, ellipse and artifactx,yis the top-left corner. - Chapters: named 1200×800 regions placed left to right on an unbounded canvas (a chapter without
xgoes to the right of the rightmost one; one givenxorykeeps it). Items withchapter: iduse chapter-local coordinates, so moving a chapter moves its content. Overlapping chapters are reported. Deleting a chapter erases its items unlesskeepItems. - Equations without
x,yare stacked down the left side of their own chapter (x 88, 115 px apart), and the position is stored, so they never move when something else changes. - Camera:
camera: {chapter}(or a board rect, ornullfor home) glides every following viewer to where the tutor is teaching, with a smooth pan and zoom. - Steps: every
board_updatethat adds items is one lesson step. New steps are drawn live: diagrams sweep in, strokes are traced like handwriting, plots draw axes then curves, text and equations are written left to right. Items can setstep,reveal(draw,write,wipe,fade,none),durationandorder. Emphasis uses the same machinery: an ellipse withreveal: "draw"circles a term, a translucent rect withreveal: "wipe"highlights it. - Layering:
zorders items of every kind. Alsoopacity,dash,fill, textfont(serif,sans,mono),anchor,bold,italic, multi-line text.
The artifacts the tutor writes into scene.artifacts look like this:
{ "type": "svg", "source": "<svg viewBox=…>…</svg>", "x": 40, "y": 90, "width": 680, "height": 520 }
{ "type": "mermaid", "source": "graph LR\n A[Input] --> B(Process) --> C[Output]", "x": 620, "y": 120, "width": 420, "height": 200 }
{ "type": "plot", "x": 40, "y": 40, "width": 560, "height": 360, "xRange": [-6.3, 6.3],
"functions": [{ "expr": "sin(x)", "label": "sin x" }, { "expr": "x^2/8 - 1", "dash": "7 5" }],
"areas": [{ "expr": "sin(x)", "from": 0, "to": 3.14159 }], "points": [{ "x": 0, "y": 0, "label": "O" }],
"parametric": [{ "x": "cos(t)", "y": "sin(t)", "t": [0, 6.283] }], "vlines": [{ "x": 3.14159 }],
"xLabel": "x", "yLabel": "y", "equalAspect": false }
{ "type": "molecule", "smiles": "CC(=O)Oc1ccccc1C(=O)O", "x": 640, "y": 40, "width": 260, "height": 200 }
{ "type": "icon", "icon": "heart-cross-section-2", "x": 700, "y": 100, "width": 220, "card": true }
All artifacts accept chapter, z, opacity, label, step, reveal, duration and order. card: true puts an illustration on a soft light panel, for drawings made for white paper. Plot expressions support + - * / ^, implicit multiplication (2x, 3(x+1)), pi, e, tau and sin cos tan asin acos atan sinh cosh tanh asinh acosh atanh sec csc cot exp ln log log2 log10 sqrt cbrt abs sign floor ceil round min max pow atan2 hypot mod step sinc gauss. board_docs gives the model the full reference, with an example for every format.
Rendering defaults, set after building real lessons: Mermaid uses a chalk theme (serif text, dark nodes with chalk outlines and edges, opaque label backgrounds so edges never cross their labels), and the layout check warns when a diagram is shrunk until its text falls below about 12 px, with the size it needs. Plots draw tick, axis, curve and marker labels (vlines and hlines take a label) above the curves with a thin black outline, and the y-axis label sits above the plot area. Molecules are framed to the drawn structure so they fill their box (zoom capped at about 2x). Zoomed out, in the overview and overview snapshots, chapter titles keep a readable size.
Viewer controls
| Action | Keys / mouse |
|---|---|
| Pan | scroll / two-finger swipe, drag, Shift+arrows |
| Zoom at cursor | ⌘/Ctrl+scroll, pinch, + / - |
| Step back / forward through the lesson | ← / → |
| Replay the chapter in view | r (or Replay) |
| Play on from the current step / pause | Space |
| Back to live (latest step) | End |
| Previous / next chapter | [ / ], PageUp / PageDown |
| Whole lesson | o (Overview) |
| Follow the tutor's camera again | f, double-click, 0 (Follow) |
| Point the tutor at an item | hover shows step n · id · chapter; a click copies step n, id (chapter c) to paste into the terminal |
The controls and a step n / N indicator appear in the top-right on hover. Panning by hand pauses camera following until you press Follow.
Model tools
| Tool | Purpose |
|---|---|
board_open | Open the board: starts the server and opens the browser; returns the URL only when no browser could be opened. Safe to call again. In pi it opens the browser in interactive mode only, and it switches on the other board tools, which stay inactive until then: an unused board costs the model 255 characters per turn (board_open alone) instead of 12,197 for all seven tools. /board open and /board load switch them on too. |
board_read | Revision, title, chapter table of contents, camera, steps, and the layout (id, type, position, size) of one chapter at a time (chapter parameter; default: the camera's chapter; an unknown chapter is an error). Read before updating. |
board_update | Compare-and-swap update (baseRevision): chapters, equations (add, update, delete), scene (shapes, artifacts; append or replace), items (update, move or delete any item by id), camera, title, clear. Input that doesn't fit (an unknown or misspelled field, a number given as a string, an empty text, an id over 64 characters or used by another item) is rejected with the reason, and nothing is applied. After each update the rendered layout is measured, and overlapping text and equations, overlapping artifacts, items spilling out of their chapter and artifacts that failed to render are reported, this update's problems first. |
board_snapshot | PNG of the board as the viewer sees it (default: what the camera shows; or chapter, overview, region, width), plus each item's measured bounding box and the layout warnings. Uses Playwright's Chromium or the system Chrome; the headless page closes after 5 idle minutes. |
board_library | Search the offline illustration library by keyword or category; returns icon ids. |
board_save | save, list or load named snapshots (load needs baseRevision). |
board_docs | Reference text by topic (overview, workflow, chapters, equations, marks, plot, svg, mermaid, molecule, icon, animation, layout, saves): artifact formats with examples, SVG rules, the plot expression grammar, reveal and animation options, chapters and camera, layout conventions, saves. |
Inside my learning folder (~/Learn, or wherever MATH_BOARD_LEARN_DIR points; PI_LEARN_DIR works too), and not in subagents (those that set PI_SUBAGENT_NAME), the pi extension adds a short, constant board_teaching section to the system prompt: when to draw, one step then one check question, one concept per chapter, and reading the board to resolve references like "step 4, eq2". MATH_BOARD_TEACHING=always adds it everywhere, never nowhere. Read, update and load results end with "No browser is showing the board" when nobody is watching, so the tutor knows to call board_open.
Saves and export
- Autosave: every change is written, debounced and atomically, to
autosave.jsonin the data folder, with three older copies (autosave.1.jsonis the newest). A fresh board server restores it on start, so/reloador restarting pi keeps the board. If a write fails (say the folder isn't writable), the next tool result and/board statussay so. An autosave that can't be restored is moved aside asautosave.corrupt-<time>.jsonand reported, never overwritten. Two boards on the same data folder still work, but each warns about the other, since the last change wins the autosave. - Named snapshots:
/board save <name>,board_saveor the Save button writesaves/<name>.json. Loading re-validates everything; items it can't use are skipped rather than failing the load, and the result lists what was skipped. Saves carry a format version, and a save from a newer version is refused rather than misread. - Export:
/board export [name]writes the running board toviz/board-<name>-<YYYYMMDD-HHMM>/in pi's current folder, adding-2,-3, … rather than overwriting. Inside:board.json(the named-save format: copy it tosaves/<name>.jsonin the data folder and/board load <name>opens it),chapter-<id>.pngfor each chapter (1600 px, rendered likeboard_snapshot),home.pngfor items outside the chapters (unless a chapter covers the home board),overview.pngwhen there is more than one region, andlesson.md, a note with standard markdown image links that Obsidian and GitHub both show. The files are private to you (folders 0700, files 0600, like saves), and avizthat is a symbolic link is refused rather than followed. It's manual only: it runs when you type it, never on autosave, on shutdown or from a model tool, and it never starts a board server. Without a headless browser it still writesboard.jsonandlesson.mdand says why the images are missing. - Data folder:
.data/in a checkout (git-ignored, so boards never end up in commits), the per-user folder for an installed copy, orMATH_BOARD_DATA_DIR(see Install for pi). The MCP server uses itsmcp/subfolder, so its board and pi's never share an autosave. The tests and scripts always use temporary folders, so they never touch real saves. - Limits: 200 chapters, 1,000 equations, 5,000 shapes and artifacts in total, 2,000 shapes and 100 artifacts per update, 6 MiB per board, 128 KiB per SVG.
Safety
The short version. SECURITY.md has the threat model, what's out of scope, and how to report a problem.
- Loopback-only server, a random bearer token per session, exact Host and Origin checks, bounded request bodies, compare-and-swap revisions, atomic saves.
- The page is view-only: nobody draws on it. Its only writes are the Clear button (which asks again if the board changed meanwhile) and Save, and nothing it does starts a model turn.
- The URL's token reaches the model only when no browser could be opened; otherwise tool results name just
127.0.0.1:<port>. - KaTeX renders with
trust: false, strict mode and expansion limits. Labels and text are inserted as text, never HTML. - SVG, Mermaid, molecules and icons are all displayed through
<image>(image context: no scripts, no external loads), after validation. Mermaid runs withsecurityLevel: "strict"and rejects links, callbacks, embedded markup and%%{init}%%directives. SMILES strings are restricted to SMILES characters. Plot expressions are compiled by a small parser (shared/expr.mjs) that only knows arithmetic, a fixed function table and the plot variables. - SVG is parsed with a strict XML parser (
@rgrove/parse-xml), checked on the parsed tree and written back out from it, so what's stored is exactly what was checked. It may use shapes,line,text/tspan/textPath,defs/symbol/use, markers, gradients, patterns, clip paths, masks, a<style>block and SMIL animation. Rejected: anything that isn't well-formed XML (with the parser's message), DOCTYPE, CDATA, processing instructions, scripts,foreignObject,image, event attributes, external URLs,data:URIs, other namespaces, CSS escapes, and every CSS at-rule and function except colors,calc/var, transforms,@keyframes,@mediaandurl(#id).url(…)andhrefmust point to an#idin the same SVG, checked after character references are decoded. A missingxmlnsis added. Errors name the offending element, attribute or construct. - The headless Chromium behind the layout check and snapshots runs with its OS sandbox (falling back without it, and saying so, only where it can't start).
- Static files are served only from
public/, chosen by file extension, with strict path checks. The icons are sanitized at build time with the same validator.
Build and tests
cd ~/math-board
npm ci # includes pi 1.4.0 and TypeScript, for the type check and loader tests
npm run build # bundle the browser app into public/, write mcp/schemas.json and THIRD_PARTY_NOTICES.md
npm run typecheck # index.ts against pi's types
npm run build:icons # (re)build the icon library (network)
npm test # unit, HTTP, hardening, validator and snapshotter tests, and the MCP server over stdio
npm run test:browser # Playwright browser tests
npm run test:load # real pi loader + /reload regression
node scripts/e2e-snapshot.mjs # end to end: update, layout check, snapshot, /board export
node scripts/demo-lessons.mjs <out-dir> [physics|mri|chem] # visual QA on three sample lessons
test:load,e2e-snapshot.mjsanddemo-lessons.mjsload the extension through pi's real loader. They usePI_HOST_ROOTwhen it's set (an installed@earendil-works/pi-coding-agentpackage directory), else the pi thatnpm ciput innode_modules, else a global install (npm root -g).npm testincludestest/mcp.test.mjs, which spawnsnode mcp/server.mjswith a temporary data folder andMATH_BOARD_NO_BROWSER=1and drives it with the MCP SDK client; itsboard_snapshotcheck is skipped, with the reason, when no headless browser is available.- The browser tests need Playwright's Chromium (
npx playwright install chromium). The snapshot script uses it or the system Chrome, checks that the layout check catches an overlap and an off-board item and that the fix clears them, writes the PNG to a temporary folder unless given a path, and exports to a temporary folder.demo-lessons.mjswrites every chapter, the camera view and the overview at 1200 and 1600 px, pluswarnings.jsonwith every layout warning the tools returned. - CI (
.github/workflows/ci.yml) runs the type check, rebuilds and fails ifpublic/,mcp/schemas.jsonorTHIRD_PARTY_NOTICES.mddiffer from the commit, then runsnpm test,test:browser,test:load(against pi 1.4.0) and the end-to-end script on Node 22 for every push and pull request tomain. So when you changebrowser/ortools.mjs, runnpm run buildand commit what it writes. - While developing: tool output warns when the extension's files changed after the server loaded them, and
/reloadpicks up server changes (server code is imported with a version query). The check compares modification times, so a copy that keeps an older time (cp -p,rsync -a, some sync tools) isn't noticed;/reloadanyway after those. - Dependencies: at runtime only three, pinned: the official MCP TypeScript SDK's current stable major, v2, published as
@modelcontextprotocol/server(@modelcontextprotocol/sdkis the v1 maintenance line),playwright-corefor the headless page, and@rgrove/parse-xmlfor the SVG check. React, KaTeX, Mermaid and SmilesDrawer are bundled intopublic/at build time, so they're devDependencies. The MCP server reads its tool schemas frommcp/schemas.json, which the build writes fromtools.mjswith TypeBox; pi builds the same schemas with its own TypeBox, and the tests check that the two match. The tests use@modelcontextprotocol/client.
The icon library is built by npm run build:icons: it downloads Bioicons, keeps CC0, CC-BY 3.0/4.0, MIT and BSD icons up to 128 KiB, inlines their CSS, strips editor metadata, filters, raster images and external references, and validates each one with the board's SVG validator.
Credits
- Eero Alvar's learn: the teaching setup this board was built to sit next to. Question-based, one concept at a time.
- pi, the terminal agent it extends.
- Bioicons for the illustrations. Each icon keeps its own license (CC0, CC-BY 3.0, CC-BY 4.0 or MIT), and the author and license of every icon are listed in public/icons/CREDITS.md. CC-BY icons need attribution to their authors if you publish material that uses them.
- KaTeX, Mermaid, SmilesDrawer and React, bundled into the page.
Why it's built this way, and what's still open (equation morphs, layout maps, vectors, quiz mirroring), is in docs/design.md. Research notes on prior art (tldraw's agent kit, TheoremExplainAgent, Code2Video, HeyTutor, Manim-style timelines and more) are in docs/prior-art.md.
License
MIT, see LICENSE. Bundled libraries and the icons keep their own licenses: THIRD_PARTY_NOTICES.md has the full texts (the build generates it from the bundle) and public/icons/CREDITS.md the author and license of every icon.
Frequently asked questions
What is Math board?
A local, view-only chalkboard the model draws on: equations, plots, molecules and diagrams.
How do I install Math board?
It runs on your machine. In Claude Code: claude mcp add math-board -e 'MATH_BOARD_NO_BROWSER=<MATH_BOARD_NO_BROWSER>' -e 'MATH_BOARD_DATA_DIR=<MATH_BOARD_DATA_DIR>' -- npx -y pi-math-board@0.3.0. For other clients, paste the JSON configuration from this page into their MCP settings.
Does Math board need an API key?
Its registry entry lists no required key or secret. Some features may still need an account with the service it connects to.
Is Math board open source?
Its code is public on GitHub, but the repository does not declare a standard license.
Who maintains Math board?
It is published by smdesai27. AI.tn lists it but does not maintain or endorse it.
From the official MCP Registry. Listing is not an endorsement: review a server's code and permissions before giving it access to your data.