From f32a7564349aeb41a682d37286ab09abea8e435b Mon Sep 17 00:00:00 2001 From: newblacc Date: Sun, 29 Mar 2026 16:36:32 +0200 Subject: [PATCH] chore: add AGENTS.md and publish readiness checklist to CLAUDE.md - AGENTS.md: agent instructions covering commands, architecture, key constraints, security middleware map, env vars, testing rules, and pre-publish checklist - CLAUDE.md: add Publish Readiness section with security posture summary and pre-publish checklist Co-Authored-By: Claude Sonnet 4.6 --- AGENTS.md | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 23 ++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..76b49d4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,107 @@ +# AGENTS.md + +Agent instructions for mcp-excalidraw-local. Read this before starting any task. + +## What This Is + +A fully local, self-hosted Excalidraw MCP server. Single Node.js/TypeScript process +running an MCP server (stdio, 32 tools), an Express+WebSocket canvas server, and +SQLite persistence with multi-tenancy. + +## Commands + +```bash +# Install +npm ci + +# Build (frontend + server) +npm run build + +# Build server only (TypeScript) +npm run build:server + +# Type check +npm run type-check + +# Tests +npm test # full suite (vitest, 369 tests) +npm run test:api # API tests only +npm run test:ws # WebSocket tests only + +# Run canvas server +node dist/server.js + +# Health check +curl http://localhost:3000/health +``` + +## Architecture + +``` +src/index.ts MCP server (stdio) — 32 tools, HTTP client to canvas +src/server.ts Express canvas server — REST API, WebSocket, Zod validation +src/security.ts Security middleware — auth, CORS, rate limiting, sanitization +src/db.ts SQLite persistence — CRUD, FTS5, migrations, tenants +src/types.ts Shared TypeScript types, ID generation, element validation +frontend/ React + Excalidraw UI (Vite, output → dist/frontend/) +``` + +Data flow: MCP tool → `index.ts` → HTTP → `server.ts` → SQLite + WS broadcast → frontend. + +## Key Constraints + +- **ESM only** — all imports use `.js` extension. Do not use `require()`. +- **Strict TypeScript** — `noUncheckedIndexedAccess` enabled. No `any` without justification. +- **No `===` on secrets** — use `crypto.timingSafeEqual`. See `src/security.ts`. +- **Validation before DB write** — always validate element types against `VALID_ELEMENT_TYPES` before persisting. +- **Logger after validation** — never access `req.body` fields in logger calls before the array/type checks run (crash risk). +- **Auth env vars read at request time** — `security.ts` reads `process.env` on each call so tests can mutate env between cases. Do not cache `process.env.EXCALIDRAW_API_KEY`. +- **Canvas sync is fire-and-forget** — MCP handlers call canvas REST but never fail if canvas is down. Use `syncToCanvas()`. +- **Logging to file only** — never log to stdout (breaks MCP stdio JSON protocol). Use the Winston logger in `src/utils/logger.ts`. + +## Security Middleware (`src/security.ts`) + +All middleware lives here — do not duplicate in routes: +- `helmetMiddleware` — security headers +- `corsMiddleware` — explicit origin allowlist (env: `ALLOWED_ORIGINS`) +- `apiKeyAuth` — timing-safe API key check (env: `EXCALIDRAW_API_KEY`) +- `sanitizeBody` — strips `__proto__`/`constructor`/`prototype` keys +- `validateMermaidInput` — caps diagram size at 50 KB +- `generalRateLimit` / `destructiveRateLimit` / `writeBurstLimit` — 3-tier rate limiting +- `requireConfirm` — requires `?confirm=true` on destructive endpoints +- `verifyWsClient` — WS origin check at upgrade time +- `sanitizeSearchQuery` / `InvalidSearchQueryError` — FTS input sanitization + +## Environment Variables + +| Variable | Default | Notes | +|----------|---------|-------| +| `CANVAS_PORT` | `3000` | Canvas server port | +| `EXCALIDRAW_API_KEY` | _(unset)_ | Enables API key auth on all `/api/*` routes | +| `ALLOWED_ORIGINS` | `http://localhost:3000,...` | Comma-separated CORS allowlist | +| `EXCALIDRAW_DB_PATH` | `$HOME/.excalidraw-mcp/excalidraw.db` | SQLite path | +| `EXCALIDRAW_EXPORT_DIR` | `process.cwd()` | Export directory (path traversal guard) | +| `EXCALIDRAW_RATE_LIMIT_GENERAL_MAX` | `100` | Requests per 15-minute window | +| `EXCALIDRAW_RATE_LIMIT_DESTRUCTIVE_MAX` | `10` | Requests per 1-minute window | +| `EXCALIDRAW_RATE_LIMIT_WRITE_BURST_MAX` | `10` | Sync writes per 1-minute window | + +## Testing Rules + +- 369 tests across 20 files — all must pass before any commit. +- New security-relevant behaviour must have a regression test. +- Tests mutate `process.env` between cases — do not cache env values at module init. +- Integration tests use real SQLite (tmpdir). Do not mock the DB. + +## Protected Files + +- `AGENTS.md` — immutable unless explicitly named in the request. +- `CLAUDE.md` — immutable unless explicitly named in the request. +- `CHANGELOG.md` — append-only. Never edit or reorder existing entries. + +## Before `npm publish` + +- [ ] Bump `version` in `package.json` (current: `1.6.2`, next: `1.6.3`) +- [ ] `npm test` → 369/369 +- [ ] `npm run build` → zero errors +- [ ] `shipguard scan .` → 0 CRITICAL +- [ ] `npm publish --dry-run` → only `dist/`, `skills/`, `README.md`, `LICENSE` included diff --git a/CLAUDE.md b/CLAUDE.md index 55a71e2..d185ae7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -106,6 +106,29 @@ frontend/ ── React + Excalidraw UI (Vite build → dist/frontend/) Two Dockerfiles: `Dockerfile` (MCP server only), `Dockerfile.canvas` (canvas with frontend). `docker-compose.yml` orchestrates both with a `full` profile. +## Publish Readiness + +**Last hardened:** 2026-03-29 — gauntlet all-green, PR #1 merged. + +### Security posture (as of 1.6.3) +- `src/security.ts`: helmet, CORS allowlist, timing-safe API key auth, prototype pollution guard, 3-tier rate limiting, WS challenge-response auth, Mermaid input size cap +- 369/369 tests passing; 4 regression tests cover previously crash-able sync paths +- Docker: non-root user, resource limits, hardened `.dockerignore` + +### Before running `npm publish` +- [ ] Bump `version` in `package.json` to match `CHANGELOG.md` entry (currently `1.6.2` — next is `1.6.3`) +- [ ] Run `npm test` — must be 369/369 +- [ ] Run `npm run build` — must be zero TS errors +- [ ] Run `shipguard scan .` — must be 0 CRITICAL findings +- [ ] Verify `CHANGELOG.md` has an entry for the version being published +- [ ] `npm publish --dry-run` to confirm only `dist/`, `skills/`, `README.md`, `LICENSE` are included + +### Safe to push to GitHub? +Yes — as of PR #1, the repo is clean for public visibility: +- No secrets, hardcoded paths, or private identifiers in tracked files +- Auth is opt-in (`EXCALIDRAW_API_KEY` unset = dev mode, by design) +- Docker images run non-root with resource limits + ## Code Search Optimization When exploring or understanding code in supported languages (JS, TS, Python, Go, Rust, Java, C, C++, Ruby):