Files
excalidraw-mcp-sentinel/AGENTS.md
T
f770c811e9 chore: document similar-project scan in AGENTS.md; ignore .serena/ (#8)
* chore: document similar-project scan in AGENTS.md; ignore .serena/ and .DS_Store

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: add .shipguard.yml — exclude node_modules, acknowledge upstream JS debt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: actually ignore .serena/ (prior commit added negation only)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: newblacc <refactor code>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 09:57:07 +02:00

129 lines
5.7 KiB
Markdown

# 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](https://github.com/sanjibdevnathlabs/mcp-excalidraw-local).
## 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.
## Similar Project Scan
- Use `npm run scan:similar-projects` to scan GitHub for architecturally similar Excalidraw projects.
- The scanner is capability-based, not fork-based: it looks for Excalidraw plus MCP, backend sync, persistence, security, workspace isolation, and self-hosting signals.
- When looking for broader competitors instead of this repo's own lineage, run:
```bash
npm run scan:similar-projects -- \
--exclude-repo yctimlin/mcp_excalidraw \
--exclude-repo sanjibdevnathlabs/mcp-excalidraw-local \
--exclude-repo celstnblacc/excalidraw-mcp-sentinel
```
- Reports are written to `docs/generated/` as JSON and Markdown.
- For repeated or larger scans, prefer setting `GITHUB_TOKEN` to avoid GitHub anonymous API rate limits.
- Rerun the scan after significant product or architecture changes. Changes to MCP features, persistence, security, or backend topology can materially change which repos are the closest matches.
- Reference docs:
- `docs/GUIDE-excalidraw-similar-project-search.md`
- `docs/AUDIT-excalidraw-similar-project-scan.md`
- `docs/COMPARISON-excalidraw-top-repos.md`
## 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.0.0`)
- [ ] `npm test` → 369/369
- [ ] `npm run build` → zero errors
- [ ] `shipguard scan .` → 0 CRITICAL
- [ ] `npm publish --dry-run` → only `dist/`, `skills/`, `README.md`, `LICENSE` included