Files
excalidraw-mcp-sentinel/CHANGELOG.md
T
046719aabc feat(transport): Streamable HTTP mode — one shared MCP process for all sessions (v1.2.0) (#13)
* chore(ci): release fires on workflow_dispatch, not on every CI pass

Replaced workflow_run trigger (fired automatically when CI completed
on main) with workflow_dispatch. The full semantic-release automation
is preserved — bump detection, version commit, tag, GitHub Release,
NPM + Docker publish — but now runs only when explicitly triggered.

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

* feat(transport): add Streamable HTTP mode — one shared process for all MCP clients (v1.2.0)

Add MCP_TRANSPORT=http mode backed by StreamableHTTPServerTransport. Each
Claude Code session connects to the shared long-lived process via HTTP (port
3031 by default) instead of spawning a new stdio process per session, eliminating
per-session process multiplication. Sessions are isolated by mcp-session-id header.

Also refactor src/index.ts to extract createMcpServer()/registerHandlers() for
clean per-session server instantiation, and upgrade fs.writeFileSync/readFileSync
calls to fs.promises async variants in export/import tool handlers.

9 new tests cover transport resolution, session isolation, teardown, and
startMcpHttpServer. All 528 tests pass. Bumps v1.1.0 → v1.2.0.

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

---------

Co-authored-by: newblacc <newblacc@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:51:08 +02:00

11 KiB

Changelog

All notable changes to this project are documented here. Format: Keep a Changelog

[Unreleased]

[1.1.0] - 2026-04-06

Added

  • Batch workspace select/delete UI: Select/Unselect All, per-row checkboxes, Delete N workspaces button with confirmation — active workspace is disabled from selection
  • POST /api/tenants/batch-delete endpoint — delete up to 50 tenants in one request with per-tenant cascade (projects, elements, snapshots)
  • fillNativeFields() in db layer — fills all universal and type-specific native Excalidraw fields (angle, strokeColor, roundness, seed, etc.) on every write so elements are identical to those produced by the VSCode Excalidraw extension
  • repairContainerBinding() in db layer — enforces bidirectional containerIdboundElements binding on every write path (create, update, batch, sync/v2) so text labels always follow their container when moved
  • Server-side label materialization (materializeLabel in server.ts): MCP create_element/update_element calls with label.text or text on a shape now produce a native bound text element in the DB instead of an MCP label stub — no synthetic generation required on export
  • 42 new backend non-regression tests for native field preservation, container binding repair, and label materialization (519 total)

[1.0.6] - 2026-04-06

Added

  • DELETE /api/tenants/:id endpoint — delete workspaces (tenants) with cascade (projects, elements, snapshots)
  • Workspace delete UI: inline confirm buttons in the workspace switcher panel

Fixed

  • Project switch in browser did not load new project's elements — switchProjectUI now directly clears canvas and calls loadExistingElements() instead of relying on WS roundtrip

[1.0.5] - 2026-04-06

Added

  • Project management UI in canvas header: create, switch, delete projects with inline confirm
  • Sync countdown timer in header — shows seconds until next auto-sync after drawing stops
  • REST endpoints: GET /api/projects, POST /api/projects, PUT /api/project/active, DELETE /api/projects/:id
  • E2e test suite for project switching round-trips (project-switch-e2e.test.ts)
  • Sync countdown unit tests with fake timers (sync-countdown.test.ts)

Fixed

  • resolveTenantProject always returned first project by creation date instead of the active project — switching projects had no effect on element queries
  • resolveScope had the same bug, causing WebSocket broadcasts to target the wrong project
  • Switching projects while a sync countdown was pending could overwrite the new project with the old project's elements — pending sync now auto-saves before switching
  • onChange triggered sync countdown on selection/appState changes (not just element changes) — added element hash comparison to filter false triggers

Changed

  • Test count: 477/477 (was 446)

[1.0.3] - 2026-03-30

Fixed

  • Global install crash (Schema method literal must be a string) — upgraded zod from 3.25.5 to ^4.3.6 so the package uses real zod v4 rather than falling back to zod 3.x's v4 compatibility shim, which lacks the .value getter required by @modelcontextprotocol/sdk@1.26.0
  • z.record(z.any()) call updated to z.record(z.string(), z.any()) to satisfy zod v4's stricter record key-type requirement

[1.0.2] - 2026-03-30

Added

  • frontend/src/utils/scenePreparation.ts — centralized scene-preparation utilities (expandLabelsToNative, prepareElementsForScene, convertElementsPreservingImageProps)
  • E2E regression suite: tests/e2e/phase2-regressions.spec.ts (4 tests: position stability, auto-title, two-tab sync, curved arrow deformability)
  • Backend tests: db-unit, mcp-contract, mcp-sanitization, security-unit, smoke-ws, tenant-authz-behavior
  • Frontend tests: scene-preparation, helpers, sync-logic

Changed

  • computeElementHash is now order-stable for equivalent element sets
  • frontend/src/App.tsx uses centralized scene-preparation utilities for label expansion and native-vs-converted routing
  • Rate limits raised: general 100→500 req/15min, write burst 10→30 req/min
  • MCP unknown tool calls now return JSON-RPC MethodNotFound (-32601) instead of generic error
  • Test count: 446/446

Fixed

  • Double WebSocket connection race — second WS created during CONNECTING state seeded knownContainerIdsRef prematurely, blocking title auto-injection; guard now also blocks CONNECTING state
  • Title/subtitle not injected for WS-delivered containersCaptureUpdateAction.NEVER suppresses onChange; handleCanvasChange() now called explicitly after element_created
  • Text alignment lost after syncElementSharedFieldsSchema did not declare textAlign, verticalAlign, containerId; Zod silently stripped these on every REST round-trip
  • Curved arrow deforms after sync — element replace strategy discarded Excalidraw-internal control point state; now merges incoming over existing
  • MCP import_scene prototype pollutionassertNoDangerousKeys() now called on all parsed JSON payloads (MCP stdio bypasses Express middleware)
  • FTS5 colon column-filter injection: added to blocked character set in sanitizeSearchQuery
  • Global state race in createProject/listProjects — explicit tenantId? param added; MCP callers pass captured ID at call time
  • Subtitle elements in MCP create_element now set textAlign: "center" and verticalAlign: "top"
  • ServerElement type updated with textAlign?, verticalAlign?, containerId?
  • pendingTitleTimerRef now cleaned up on component unmount (prevented stale closure after unmount)
  • localStorage JSON.parse for widget position wrapped in try/catch (malformed value no longer crashes component)
  • Docker LABEL org.opencontainers.image.source corrected to fork URL

[1.0.1] - 2026-03-29

Fixed

  • better-sqlite3 native module now rebuilt on install via postinstall script, fixing Node.js version mismatch errors (e.g. Node v22 vs v25) when installing via npx

[1.0.0] - 2026-03-29

Changed

  • Renamed project from @sanjibdevnath/mcp-excalidraw-local to excalidraw-mcp-sentinel
  • New npm package name: excalidraw-mcp-sentinel (unscoped)
  • GitHub repo: celstnblacc/excalidraw-mcp-sentinel
  • Docker images: celstnblacc/excalidraw-mcp-sentinel and celstnblacc/excalidraw-mcp-sentinel-canvas
  • CLI binary renamed: excalidraw-mcp-sentinel
  • Version reset to 1.0.0 for independent release track
  • Added "Why this fork?" section to README with full attribution

Removed

  • Superseded planning docs (PLAN.md, PLAN_v2.md, REVIEW.md, HANDOFF.md)

[1.6.3] - 2026-03-29

Security

  • Added security.ts middleware module: helmet headers, explicit CORS allowlist, API key auth with timing-safe comparison, prototype pollution guard, Mermaid input size limits
  • WebSocket authentication: challenge-response (auth_requiredhello + apiKey) with 5 s timeout and close code 4001 on failure; origin verification via verifyClient
  • Rate limiting on all /api/* routes: 100 req/15 min general, 10 req/min destructive, 10 req/min sync write burst
  • sanitizeSearchQuery now throws typed InvalidSearchQueryError instead of generic Error
  • Docker: added deploy.resources.limits (canvas 1 CPU/512M, mcp 0.5 CPU/256M) to docker-compose.yml; extended .dockerignore with tests/ and sensitive key file patterns

Fixed

  • POST /api/elements/sync: array validation now runs before logger access, preventing a TypeError crash (500) on null/non-array input — now returns 400
  • POST /api/elements/sync/v2: element type validated against EXCALIDRAW_ELEMENT_TYPES before write; invalid types return 400 instead of being persisted silently
  • Upgraded zod from 3.22.4 to 3.25.5 to resolve ERR_PACKAGE_PATH_NOT_EXPORTED crash at MCP server startup caused by zod-to-json-schema peer dependency mismatch

Changed

  • ElementSharedFieldsSchema extracted from CreateElementSchema/UpdateElementSchema to eliminate 25-field duplication; both schemas now use .extend()
  • VALID_ELEMENT_TYPES moved to module-level constant (was allocated per-request)
  • resolveHelloTenantAndProject parameter typed as HelloMessage (was any)
  • getAllFilesObject() helper extracted; sendFilesAdded() and GET /api/files share it
  • sendLegacyInitialWsMessages renamed to sendAuthlessInitialMessages
  • .project-hooks/pre-commit added to run vitest on every commit

[Unreleased] - 2026-03-30

Fixed

  • Bidirectional sync conflict: WS-applied updates no longer reverted by browser auto-sync (lastSyncedElementsRef now updated on element_updated, element_deleted, elements_batch_created)
  • Labeled container updates (rectangle, ellipse, diamond, arrow) now use convertToExcalidrawElements with ID transplant for correct text layout instead of in-place text patch that caused clipping
  • Standalone text element updates now write label.text into text/originalText fields so Excalidraw renders the new value
  • convertTextToLabel now maps text→label for arrows and empty strings (previously skipped falsy text)

Changed

  • Default theme set to dark

Fixed

  • Labels stored as label.text (e.g. from MCP updates) now survive page refresh — expandLabelsToNative pre-converts them to bound text before Excalidraw renders

[1.0.4] - 2026-03-31

Fixed

  • npm install -g excalidraw-mcp-sentinel crashed on Windows — postinstall script used Unix-only 2>/dev/null || true syntax which cmd.exe does not support; replaced with a cross-platform node -e inline script

  • 2026-05-14: chore(ci): release workflow now manual (workflow_dispatch) -- no longer fires automatically on every CI pass on main

[1.2.0] - 2026-05-20

Added

  • Streamable HTTP transport mode (MCP_TRANSPORT=http): single long-lived process serves all MCP clients over HTTP instead of spawning a new stdio process per session. Each client gets its own isolated Server instance routed by mcp-session-id header. Eliminates per-session process overhead for multi-session setups.
  • src/mcp-http.ts: mountMcpRoutes, startMcpHttpServer, resolveTransportMode — full HTTP session lifecycle (POST/GET/DELETE /mcp, session map, StreamableHTTPServerTransport)
  • createMcpServer() factory and registerHandlers() in src/index.ts — clean per-session server instantiation for HTTP mode
  • 9 new tests in tests/backend/mcp-http.test.ts covering transport resolution, session isolation, session teardown, and startMcpHttpServer
  • CLAUDE.md: Strict Installation Decoupling rule
  • launchd agent (~/Library/LaunchAgents/com.user.excalidraw-mcp.plist) for single-instance persistence on macOS

Changed

  • runServer() now checks MCP_TRANSPORT env var; defaults to stdio (backward-compatible)
  • fs.writeFileSync/readFileSync calls in export/import tool handlers converted to fs.promises async variants

Total tests: 528 (31 files)