Local Development

Prerequisites

  • Node.js (for wrangler dev server)
  • Cloudflare account with Workers + KV enabled
  • Two TTF fonts uploaded to KV (see Configuration page)

Install

git clone https://github.com/lingion/plot-mcp-worker
cd plot-mcp-worker
npm install    # installs wrangler, typescript, expr-eval, opentype.js, resvg-wasm

Locked deps in package-lock.json; npm ci also works.

Scripts (from package.json)

npm scriptCommandPurpose
devwrangler devLocal Worker dev server (Miniflare under the hood)
deploywrangler deployPush to Cloudflare Workers
checktsc --noEmitTypeScript type-check only (no emit)
test:smokenode scripts/smoke.mjsHit /healthz and a few tool calls
probe:force-analysisnode scripts/local-force-analysis-probe.mjsGenerate 8 force-analysis samples locally
check:force-driftnode scripts/force-analysis-drift-check.mjsCompare local vs deployed SVG byte hash
check:force-structnode scripts/force-analysis-struct-diff.mjsDiff structure of local vs deployed SVG
check:force-compactnode scripts/force-analysis-compact-diagnose.mjsCompact diagnose of force analysis output
check:force-verifynode scripts/force-analysis-verify.mjsFull verification of force analysis
check:bundle-fingerprintnode scripts/deploy-bundle-fingerprint.mjsFingerprint the deployed worker bundle
check:deploy-preflightnode scripts/deploy-preflight.mjsPre-deploy sanity checks
check:deploy-and-verifynode scripts/deploy-and-verify.mjsDeploy then immediately verify
check:remote-healthnode scripts/remote-health.mjsHit deployed /healthz and parse JSON

Typical workflow

# 1. Type-check
npm run check

# 2. Smoke test against local dev server (needs wrangler dev running in another terminal)
npm run dev   # in terminal 1
npm run test:smoke  # in terminal 2

# 3. Verify force analysis output before deploying
npm run probe:force-analysis
npm run check:force-drift

# 4. Deploy
npm run deploy

# 5. Verify deployed
npm run check:remote-health

Calling the MCP endpoint from curl

# initialize
curl -s -X POST https://your-worker.workers.dev/mcp   -H "Content-Type: application/json"   -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'

# tools/list
curl -s -X POST https://your-worker.workers.dev/mcp   -H "Content-Type: application/json"   -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# tools/call -> plot
curl -s -X POST https://your-worker.workers.dev/mcp   -H "Content-Type: application/json"   -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"plot","arguments":{"expr":"sin(x)","x_min":-6.28,"x_max":6.28,"title":"Sine","render":{"format":"link"}}}}'

Calling web endpoints

The web endpoints (/plot, /png, etc.) take a ?d= query parameter with gzipped base64url JSON. Easiest path: use the MCP tool (which returns the URL), or generate the encoded payload in Node:

import { toCompressedBase64UrlFromJson } from "./src/utils.js";
const d = await toCompressedBase64UrlFromJson({
  expr: "sin(x)", x_min: -6.28, x_max: 6.28, points: 1000,
  title: "Sine", xlabel: "x", ylabel: "y"
});
console.log("https://your-worker.workers.dev/plot?d=" + d);