- Package name: @sanjibdevnath/mcp-excalidraw-local → excalidraw-mcp-sentinel - GitHub repo: celstnblacc/mcp-excalidraw-local → celstnblacc/excalidraw-mcp-sentinel - Docker images, CLI binary, CI workflows, docs all updated - Version reset to 1.0.0 for independent release track - Added "Why this fork?" section to README - Removed superseded planning docs (PLAN.md, PLAN_v2.md, REVIEW.md, HANDOFF.md) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.6 KiB
4.6 KiB
AGENTS.md
Agent instructions for excalidraw-mcp-sentinel. Read this before starting any task.
What This Is
A hardened, self-hosted Excalidraw MCP server (excalidraw-mcp-sentinel). Single Node.js/TypeScript process
running an MCP server (stdio, 32 tools), an Express+WebSocket canvas server, and
SQLite persistence with multi-tenancy. Forked from sanjibdevnathlabs/mcp-excalidraw-local.
Commands
# 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
.jsextension. Do not userequire(). - Strict TypeScript —
noUncheckedIndexedAccessenabled. Noanywithout justification. - No
===on secrets — usecrypto.timingSafeEqual. Seesrc/security.ts. - Validation before DB write — always validate element types against
VALID_ELEMENT_TYPESbefore persisting. - Logger after validation — never access
req.bodyfields in logger calls before the array/type checks run (crash risk). - Auth env vars read at request time —
security.tsreadsprocess.envon each call so tests can mutate env between cases. Do not cacheprocess.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 headerscorsMiddleware— explicit origin allowlist (env:ALLOWED_ORIGINS)apiKeyAuth— timing-safe API key check (env:EXCALIDRAW_API_KEY)sanitizeBody— strips__proto__/constructor/prototypekeysvalidateMermaidInput— caps diagram size at 50 KBgeneralRateLimit/destructiveRateLimit/writeBurstLimit— 3-tier rate limitingrequireConfirm— requires?confirm=trueon destructive endpointsverifyWsClient— WS origin check at upgrade timesanitizeSearchQuery/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.envbetween 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
versioninpackage.json(current:1.0.0) npm test→ 369/369npm run build→ zero errorsshipguard scan .→ 0 CRITICALnpm publish --dry-run→ onlydist/,skills/,README.md,LICENSEincluded